diff options
Diffstat (limited to 'lisp')
1457 files changed, 66514 insertions, 48873 deletions
diff --git a/lisp/COPYING b/lisp/COPYING index 94a9ed024d3..f288702d2fa 100644 --- a/lisp/COPYING +++ b/lisp/COPYING @@ -1,7 +1,7 @@ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 - Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. @@ -645,7 +645,7 @@ the "copyright" line and a pointer to where the full notice is found. 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 <http://www.gnu.org/licenses/>. + along with this program. If not, see <https://www.gnu.org/licenses/>. Also add information on how to contact you by electronic and paper mail. @@ -664,11 +664,11 @@ might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see -<http://www.gnu.org/licenses/>. +<https://www.gnu.org/licenses/>. The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read -<http://www.gnu.org/philosophy/why-not-lgpl.html>. +<https://www.gnu.org/licenses/why-not-lgpl.html>. diff --git a/lisp/ChangeLog.1 b/lisp/ChangeLog.1 index 65997e189f1..b44f640dd55 100644 --- a/lisp/ChangeLog.1 +++ b/lisp/ChangeLog.1 @@ -3259,4 +3259,4 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. diff --git a/lisp/ChangeLog.10 b/lisp/ChangeLog.10 index 918825e6ac9..d654291739f 100644 --- a/lisp/ChangeLog.10 +++ b/lisp/ChangeLog.10 @@ -23556,4 +23556,4 @@ See ChangeLog.9 for earlier changes. GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. diff --git a/lisp/ChangeLog.11 b/lisp/ChangeLog.11 index f3d9840c0df..94a3cbfb582 100644 --- a/lisp/ChangeLog.11 +++ b/lisp/ChangeLog.11 @@ -6395,7 +6395,7 @@ * vc-svn.el (vc-svn-checkin): Use `nconc' instead of `list*', because the latter is a CL-ism. This fixes the bug reported by Shawn Boyette <mdxi@collapsar.net> in - http://lists.gnu.org/archive/html/emacs-devel/2004-05/msg00442.html. + https://lists.gnu.org/r/emacs-devel/2004-05/msg00442.html. 2004-06-04 Miles Bader <miles@gnu.org> @@ -14336,4 +14336,4 @@ See ChangeLog.10 for earlier changes. GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. diff --git a/lisp/ChangeLog.12 b/lisp/ChangeLog.12 index 04c5d8138dc..0d3bd88f3e2 100644 --- a/lisp/ChangeLog.12 +++ b/lisp/ChangeLog.12 @@ -33349,4 +33349,4 @@ See ChangeLog.11 for earlier changes. GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. diff --git a/lisp/ChangeLog.13 b/lisp/ChangeLog.13 index f86590bf273..d14325b5ff1 100644 --- a/lisp/ChangeLog.13 +++ b/lisp/ChangeLog.13 @@ -4667,7 +4667,7 @@ 2008-01-02 Karl Fogel <kfogel@red-bean.com> Change a return type, for greater extensibility. - See http://lists.gnu.org/archive/html/emacs-devel/2007-12/msg01077.html + See https://lists.gnu.org/r/emacs-devel/2007-12/msg01077.html and its thread for discussion leading to this change. * bookmark.el (bookmark-jump-noselect): @@ -11475,7 +11475,7 @@ (fancy-about-text): Add links "Authors" and "Contributing". (fancy-splash-head): Add text "Welcome to " on the startup screen, and "This is " on the about screen. Add link to - "http://www.gnu.org/software/emacs/" for "GNU Emacs". + "https://www.gnu.org/software/emacs/" for "GNU Emacs". For the about screen move emacs version to the header from `fancy-splash-tail' (as it's done already for normal about screen). (fancy-splash-tail): Insert emacs version only for startup screen. @@ -14464,7 +14464,7 @@ * bookmark.el: Revert 2007-07-13T18:16:17Z!kfogel@red-bean.com, thus restoring bookmark bindings to three slots under C-x r. See - http://lists.gnu.org/archive/html/emacs-devel/2007-07/msg00705.html. + https://lists.gnu.org/r/emacs-devel/2007-07/msg00705.html. 2007-07-15 Jeff Miller <jmiller@cablespeed.com> (tiny change) @@ -14511,7 +14511,7 @@ * bookmark.el (bookmark-jump-other-window): New function. (bookmark-map): Bind it to "o". - http://lists.gnu.org/archive/html/emacs-devel/2007-07/msg00633.html + https://lists.gnu.org/r/emacs-devel/2007-07/msg00633.html and its thread contains discussion about this change. The original patch was slightly tweaked by Karl Fogel <kfogel@red-bean.com> before committing. @@ -14525,7 +14525,7 @@ * bookmark.el: Don't define bookmark keys under the "C-xr" map; instead, make "C-xp" a prefix for bookmark-map. Patch by Drew Adams <drew.adams@oracle.com>, mildly tweaked by me. See - http://lists.gnu.org/archive/html/emacs-devel/2007-07/msg00633.html. + https://lists.gnu.org/r/emacs-devel/2007-07/msg00633.html. 2007-07-13 Carsten Dominik <dominik@science.uva.nl> @@ -16712,4 +16712,4 @@ See ChangeLog.12 for earlier changes. GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. diff --git a/lisp/ChangeLog.14 b/lisp/ChangeLog.14 index a3397b1e470..48f5c07b187 100644 --- a/lisp/ChangeLog.14 +++ b/lisp/ChangeLog.14 @@ -20562,4 +20562,4 @@ See ChangeLog.13 for earlier changes. GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. diff --git a/lisp/ChangeLog.15 b/lisp/ChangeLog.15 index 2512d35564f..11bc31f3b29 100644 --- a/lisp/ChangeLog.15 +++ b/lisp/ChangeLog.15 @@ -9833,7 +9833,7 @@ * window.el (pop-to-buffer): Remove the conditional that compares new-window and old-window, so it will reselect the selected window unconditionally. - http://lists.gnu.org/archive/html/emacs-devel/2010-06/msg00078.html + https://lists.gnu.org/r/emacs-devel/2010-06/msg00078.html 2010-06-07 Stefan Monnier <monnier@iro.umontreal.ca> @@ -9882,7 +9882,7 @@ of kill-ring: don't call menu-bar-update-yank-menu, don't push interprogram-paste strings to kill-ring, and don't push the input argument `string' to kill-ring. - http://lists.gnu.org/archive/html/emacs-devel/2010-06/msg00072.html + https://lists.gnu.org/r/emacs-devel/2010-06/msg00072.html 2010-06-04 Juanma Barranquero <lekktu@gmail.com> @@ -10445,7 +10445,7 @@ * dired-x.el (dired-jump, dired-jump-other-window): Add arg FILE-NAME to read from the minibuffer when called interactively with prefix argument instead of using buffer-file-name. - http://lists.gnu.org/archive/html/emacs-devel/2010-05/msg00534.html + https://lists.gnu.org/r/emacs-devel/2010-05/msg00534.html * dired.el: Update autoloads. @@ -11998,7 +11998,7 @@ 2010-04-05 Juri Linkov <juri@jurta.org> Scrolling commands which scroll a line instead of full screen. - http://lists.gnu.org/archive/html/emacs-devel/2010-03/msg01452.html + https://lists.gnu.org/r/emacs-devel/2010-03/msg01452.html * simple.el (scroll-up-line, scroll-down-line): New commands. Put property isearch-scroll=t on them. @@ -12009,7 +12009,7 @@ 2010-04-05 Juri Linkov <juri@jurta.org> Scrolling commands which do not signal errors at top/bottom. - http://lists.gnu.org/archive/html/emacs-devel/2010-03/msg01452.html + https://lists.gnu.org/r/emacs-devel/2010-03/msg01452.html * simple.el (scroll-up-command, scroll-down-command): New commands. Put property isearch-scroll=t on them. @@ -12063,7 +12063,7 @@ (electric-help-mode): Set it to original major-mode. Doc fix. (with-electric-help): Use `electric-help-orig-major-mode' instead of (default-value 'major-mode). Doc fix. - http://lists.gnu.org/archive/html/emacs-devel/2010-04/msg00069.html + https://lists.gnu.org/r/emacs-devel/2010-04/msg00069.html 2010-04-02 Sam Steingold <sds@gnu.org> @@ -12089,13 +12089,13 @@ * simple.el (next-line, previous-line): Re-throw a signal with `signal' instead of using `ding'. - http://lists.gnu.org/archive/html/emacs-devel/2010-03/msg01432.html + https://lists.gnu.org/r/emacs-devel/2010-03/msg01432.html 2010-03-31 Juri Linkov <juri@jurta.org> * simple.el (keyboard-escape-quit): Raise deselecting the active region higher than exiting the minibuffer. - http://lists.gnu.org/archive/html/emacs-devel/2010-03/msg00904.html + https://lists.gnu.org/r/emacs-devel/2010-03/msg00904.html 2010-03-31 Juri Linkov <juri@jurta.org> @@ -12184,7 +12184,7 @@ 2010-03-30 Juri Linkov <juri@jurta.org> Make occur handle multi-line matches cleanly with context. - http://lists.gnu.org/archive/html/emacs-devel/2010-03/msg01280.html + https://lists.gnu.org/r/emacs-devel/2010-03/msg01280.html * replace.el (occur-accumulate-lines): Add optional arg `pt'. (occur-engine): Add local variables `ret', `prev-after-lines', @@ -12379,7 +12379,7 @@ 2010-03-23 Juri Linkov <juri@jurta.org> Implement Occur multi-line matches. - http://lists.gnu.org/archive/html/emacs-devel/2010-03/msg01044.html + https://lists.gnu.org/r/emacs-devel/2010-03/msg01044.html * replace.el (occur): Doc fix. (occur-engine): Set `begpt' to the beginning of the first line. @@ -12456,7 +12456,7 @@ 2010-03-21 Juri Linkov <juri@jurta.org> Fix message of multi-line occur regexps and multi-buffer header lines. - http://lists.gnu.org/archive/html/emacs-devel/2010-03/msg00457.html + https://lists.gnu.org/r/emacs-devel/2010-03/msg00457.html * replace.el (occur-1): Don't display regexp if it is longer than window-width. Use `query-replace-descr' to display regexp. @@ -12750,7 +12750,7 @@ 2010-03-10 Kim F. Storm <storm@cua.dk> Animated image API. - http://lists.gnu.org/archive/html/emacs-devel/2010-03/msg00211.html + https://lists.gnu.org/r/emacs-devel/2010-03/msg00211.html * image.el (image-animate-max-time): New defcustom. (image-animated-types): New defconst. @@ -13908,7 +13908,7 @@ positions by using `bookmark-bmenu-marks-width', instead of hardcoding. This fixes the `bookmark-bmenu-execute-deletions' bug reported here: - http://lists.gnu.org/archive/html/emacs-devel/2009-12/msg00819.html + https://lists.gnu.org/r/emacs-devel/2009-12/msg00819.html From: Sun Yijiang <sunyijiang {_AT_} gmail.com> To: emacs-devel {_AT_} gnu.org Subject: bookmark.el bug report @@ -19816,7 +19816,7 @@ * files.el (find-alternate-file): If the old buffer is modified and visiting a file, behave similarly to `kill-buffer' when killing it, thus reverting to the pre-1.878 behavior; see - http://lists.gnu.org/archive/html/emacs-devel/2009-09/msg00101.html + https://lists.gnu.org/r/emacs-devel/2009-09/msg00101.html for discussion. Also, consult `buffer-file-name' as a variable not as a function, for consistency with the rest of the code. @@ -22817,4 +22817,4 @@ See ChangeLog.14 for earlier changes. GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. diff --git a/lisp/ChangeLog.16 b/lisp/ChangeLog.16 index 691b7945bf4..dcf2fd071d0 100644 --- a/lisp/ChangeLog.16 +++ b/lisp/ChangeLog.16 @@ -1371,7 +1371,7 @@ * frame.el (toggle-frame-maximized, toggle-frame-fullscreen): Use fullboth as an alias for fullscreen. Suggested by Jan Djärv in - <http://lists.gnu.org/archive/html/emacs-devel/2013-01/msg00203.html>. + <https://lists.gnu.org/r/emacs-devel/2013-01/msg00203.html>. 2013-01-30 Stefan Monnier <monnier@iro.umontreal.ca> @@ -2422,7 +2422,7 @@ * epg.el: Support pinentry-curses. Suggested by Werner Koch in - <http://lists.gnu.org/archive/html/emacs-devel/2007-02/msg00755.html>. + <https://lists.gnu.org/r/emacs-devel/2007-02/msg00755.html>. (epg-agent-file, epg-agent-mtime): New variable. (epg--start): Record the modified time of gpg-agent socket file, to restore Emacs frame after pinentry-curses termination. @@ -2448,7 +2448,7 @@ (toggle-frame-maximized): Rewrite and bind to M-<f10>. (toggle-frame-fullscreen): New command bound to <f11> instead of `toggle-frame-maximized'. - http://lists.gnu.org/archive/html/emacs-devel/2012-12/msg00703.html + https://lists.gnu.org/r/emacs-devel/2012-12/msg00703.html 2012-12-27 Michael Albinus <michael.albinus@gmx.de> @@ -2686,7 +2686,7 @@ (isearch-insert-char-by-name): New command. * international/mule-cmds.el (read-char-by-name): Let-bind `enable-recursive-minibuffers' to t. - http://lists.gnu.org/archive/html/emacs-devel/2012-12/msg00234.html + https://lists.gnu.org/r/emacs-devel/2012-12/msg00234.html 2012-12-15 Juri Linkov <juri@jurta.org> @@ -2728,7 +2728,7 @@ * vc/ediff-util.el (ediff-buffer-type): New function. (ediff-clone-buffer-for-current-diff-comparison): Compute the buf-type - rather than taking it as as argument. + rather than taking it as an argument. (ediff-inferior-compare-regions): Adjust calls accordingly (bug#11319). 2012-12-14 Ryan Crum <ryan.crum@eleostech.com> @@ -4831,7 +4831,7 @@ * progmodes/compile.el (compilation-error-regexp-alist-alist): Adjust the msft regexp to the output of Studio 2010, and move msft before edg-1. See the discussion on emacs-devel, - http://lists.gnu.org/archive/html/emacs-devel/2012-09/msg00579.html, + https://lists.gnu.org/r/emacs-devel/2012-09/msg00579.html, for the details. 2012-10-14 Stefan Monnier <monnier@iro.umontreal.ca> @@ -5453,7 +5453,7 @@ * profiler.el (profiler-sampling-interval): Change default back to 1. See Stefan Monnier in - <http://lists.gnu.org/archive/html/emacs-devel/2012-09/msg00863.html>. + <https://lists.gnu.org/r/emacs-devel/2012-09/msg00863.html>. 2012-10-01 Fabián Ezequiel Gallina <fgallina@cuca> @@ -6641,7 +6641,7 @@ search-whitespace-regexp if isearch-lax-whitespace or isearch-regexp-lax-whitespace is non-nil. (Info-mode): Don't set local variable search-whitespace-regexp. - http://lists.gnu.org/archive/html/emacs-devel/2012-08/msg00811.html + https://lists.gnu.org/r/emacs-devel/2012-08/msg00811.html 2012-09-12 Stefan Monnier <monnier@iro.umontreal.ca> @@ -7037,7 +7037,7 @@ 2012-09-02 Juri Linkov <juri@jurta.org> Toggle whitespace matching mode with M-s SPC. - http://lists.gnu.org/archive/html/emacs-devel/2012-09/msg00008.html + https://lists.gnu.org/r/emacs-devel/2012-09/msg00008.html * isearch.el (search-whitespace-regexp): Doc fix. Remove cons cell customization. @@ -8109,7 +8109,7 @@ * whitespace.el (whitespace-display-mappings): Use Unicode codepoints, instead of emacs-mule codepoints. See - http://lists.gnu.org/archive/html/help-gnu-emacs/2012-07/msg00366.html + https://lists.gnu.org/r/help-gnu-emacs/2012-07/msg00366.html for the details. * files.el (file-truename): Don't skip symlink-chasing part on @@ -8164,7 +8164,7 @@ * international/mule-cmds.el: Create inactivate-current-input-method-function as an obsolete alias for deactivate-current-input-method-function. See Katsumi Yamaoka in - <http://bugs.gnu.org/10150#46>. + <https://bugs.gnu.org/10150#46>. 2012-08-01 Jay Belanger <jay.p.belanger@gmail.com> @@ -8513,7 +8513,7 @@ * startup.el (command-line): Don't display an empty user name in the error message about non-existent home directory, when init-file-user was set to an empty string. See - http://lists.gnu.org/archive/html/bug-gnu-emacs/2012-07/msg00835.html + https://lists.gnu.org/r/bug-gnu-emacs/2012-07/msg00835.html for the details and context. 2012-07-22 Vincent Belaïche <vincentb1@users.sourceforge.net> @@ -9419,7 +9419,7 @@ * calendar/calendar.el (calendar-exit): Don't try to delete or iconify last frame. See: - http://lists.gnu.org/archive/html/emacs-devel/2012-06/msg00372.html + https://lists.gnu.org/r/emacs-devel/2012-06/msg00372.html 2012-06-25 Jim Diamond <Jim.Diamond@AcadiaU.ca> (tiny change) @@ -10430,7 +10430,7 @@ * descr-text.el (describe-char): Mention how to insert the character, if the current input method doesn't support it. See the discussion in this thread for the details: - http://lists.gnu.org/archive/html/emacs-devel/2012-05/msg00533.html. + https://lists.gnu.org/r/emacs-devel/2012-05/msg00533.html. 2012-06-08 Sam Steingold <sds@gnu.org> @@ -11992,11 +11992,11 @@ * progmodes/verilog-mode.el (verilog-pretty-expr): Don't line up assignment with tests in ifs and for loops. (verilog-extended-complete-re, verilog-complete-reg): Change so - that DPI inport functions don't look like fuction declarations. + that DPI inport functions don't look like function declarations. (verilog-pretty-expr): Don't line up assignment operations to the test and increment in if and for loops (verilog-extended-complete-re, verilog-complete-reg): Change so - that DPI inport functions don't look like fuction declarations. + that DPI inport functions don't look like function declarations. 2012-05-03 Kenichi Handa <handa@m17n.org> @@ -13909,7 +13909,7 @@ Insert invisible LRM characters before each character in a keyboard layout cell, to prevent their reordering by bidi display engine. For details, see the discussion in - http://lists.gnu.org/archive/html/emacs-devel/2012-03/msg00085.html. + https://lists.gnu.org/r/emacs-devel/2012-03/msg00085.html. 2012-03-08 Alan Mackenzie <acm@muc.de> @@ -13927,7 +13927,7 @@ * international/quail.el (quail-help): Force bidi-paragraph-direction be left-to-right. See discussion in - http://lists.gnu.org/archive/html/emacs-devel/2012-03/msg00062.html + https://lists.gnu.org/r/emacs-devel/2012-03/msg00062.html for the reason. 2012-03-07 Michael Albinus <michael.albinus@gmx.de> @@ -15004,7 +15004,7 @@ * descr-text.el (describe-char): Show the raw character, not only its display form at POS. Suggested by Kenichi Handa <handa@m17n.org>. - See http://lists.gnu.org/archive/html/emacs-devel/2012-01/msg00760.html + See https://lists.gnu.org/r/emacs-devel/2012-01/msg00760.html for the reasons. 2012-01-28 Phil Hagelberg <phil@hagelb.org> @@ -15362,7 +15362,7 @@ * time.el (display-time-load-average) (display-time-default-load-average): Doc fixes. See the thread starting at - http://lists.gnu.org/archive/html/help-gnu-emacs/2012-01/msg00059.html + https://lists.gnu.org/r/help-gnu-emacs/2012-01/msg00059.html for the details. 2012-01-06 Glenn Morris <rgm@gnu.org> @@ -15769,7 +15769,7 @@ (texinfo-insert-master-menu-list): Improve the error message displayed if there's no menu in the Top node. (Bug#2975) See also this thread: - http://lists.gnu.org/archive/html/emacs-devel/2011-12/msg00156.html. + https://lists.gnu.org/r/emacs-devel/2011-12/msg00156.html. 2011-12-09 Manuel Gómez <mgrojo@gmail.com> (tiny change) @@ -20919,7 +20919,7 @@ (ses-formula-references): Robustify against self-referring cells. (ses-mode): Use ses-set-localvars. (ses-command-hook): Add call to ses-initialize-Dijkstra-attempt - before lauching the update processing. + before launching the update processing. (ses-initialize-Dijkstra-attempt): New function. (ses-recalculate-cell): Update for cycle detection based on Dijkstra algorithm. @@ -24381,7 +24381,7 @@ * help-fns.el (describe-variable): Complete all variables having documentation, including keywords. - http://lists.gnu.org/archive/html/emacs-devel/2011-04/msg00112.html + https://lists.gnu.org/r/emacs-devel/2011-04/msg00112.html 2011-04-04 Juanma Barranquero <lekktu@gmail.com> @@ -25238,4 +25238,4 @@ See ChangeLog.15 for earlier changes. GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. diff --git a/lisp/ChangeLog.17 b/lisp/ChangeLog.17 index 6dfddf72e8f..b2d315c53f3 100644 --- a/lisp/ChangeLog.17 +++ b/lisp/ChangeLog.17 @@ -3598,7 +3598,7 @@ * comint.el (comint-history-isearch-message): Use field-beginning instead of comint-line-beginning-position - that's more fixes for - http://lists.gnu.org/archive/html/emacs-devel/2014-12/msg00305.html + https://lists.gnu.org/r/emacs-devel/2014-12/msg00305.html (comint-history-isearch-message): Fix args of isearch-message-prefix. 2014-12-29 Juri Linkov <juri@linkov.net> @@ -3698,7 +3698,7 @@ * language/misc-lang.el (composition-function-table): Add Syriac characters and also ZWJ/ZWNJ. - See http://lists.gnu.org/archive/html/help-gnu-emacs/2014-12/msg00248.html + See https://lists.gnu.org/r/help-gnu-emacs/2014-12/msg00248.html for the details. 2014-12-27 Fabián Ezequiel Gallina <fgallina@gnu.org> @@ -4489,14 +4489,14 @@ comint-line-beginning-position. (comint-send-input): Go to the end of the field instead of the end of the line to accept whole multi-line input. - http://lists.gnu.org/archive/html/emacs-devel/2014-12/msg00305.html + https://lists.gnu.org/r/emacs-devel/2014-12/msg00305.html 2014-12-05 Juri Linkov <juri@linkov.net> * minibuffer.el (minibuffer-completion-help): Compare selected-window with minibuffer-window to check whether completions should be displayed near the minibuffer. (Bug#17809) - http://lists.gnu.org/archive/html/emacs-devel/2014-12/msg00311.html + https://lists.gnu.org/r/emacs-devel/2014-12/msg00311.html 2014-12-05 Michael Albinus <michael.albinus@gmx.de> @@ -4605,7 +4605,7 @@ the remote repository were unreachable, because the VC hooks tried to run "svn status -u" on the file, where the "-u" tells svn to get update information from the remote repository. - http://lists.gnu.org/archive/html/emacs-devel/2014-12/msg00174.html + https://lists.gnu.org/r/emacs-devel/2014-12/msg00174.html * vc/vc-svn.el (vc-svn-state): Remove optional `localp' argument and always pass "-v" to "svn status", never "-u". @@ -5306,7 +5306,7 @@ (query-replace-read-from): Call custom-reevaluate-setting on query-replace-from-to-separator to reevaluate the separator depending on the return value of char-displayable-p. - http://lists.gnu.org/archive/html/emacs-devel/2014-11/msg00466.html + https://lists.gnu.org/r/emacs-devel/2014-11/msg00466.html 2014-11-18 Juri Linkov <juri@linkov.net> @@ -5316,7 +5316,7 @@ * simple.el (next-line-or-history-element) (previous-line-or-history-element): New commands. - http://lists.gnu.org/archive/html/emacs-devel/2014-11/msg00822.html + https://lists.gnu.org/r/emacs-devel/2014-11/msg00822.html 2014-11-18 Leo Liu <sdl.web@gmail.com> @@ -5441,7 +5441,7 @@ Improve time stamp handling, and be more consistent about it. This implements a suggestion made in: - http://lists.gnu.org/archive/html/emacs-devel/2014-10/msg00587.html + https://lists.gnu.org/r/emacs-devel/2014-10/msg00587.html Among other things, this means timer.el no longer needs to autoload the time-date module. * allout-widgets.el (allout-elapsed-time-seconds): Doc fix. @@ -5682,7 +5682,7 @@ 2014-11-10 Sylvain Chouleur <sylvain.chouleur@gmail.com> (tiny change) Allow VTIMEZONE where daylight and standard time zones are equal. - See: http://lists.gnu.org/archive/html/emacs-devel/2014-11/msg00494.html + See: https://lists.gnu.org/r/emacs-devel/2014-11/msg00494.html * calendar/icalendar.el (icalendar--convert-tz-offset): Support timezone without daylight saving time. @@ -5813,7 +5813,7 @@ to the history variables. (query-replace-read-to): Add FROM-TO pairs to query-replace-defaults. (query-replace-regexp-eval): Let-bind query-replace-defaults to nil. - http://lists.gnu.org/archive/html/emacs-devel/2014-11/msg00253.html + https://lists.gnu.org/r/emacs-devel/2014-11/msg00253.html * isearch.el (isearch-text-char-description): Keep characters intact and put formatted strings with the `display' property. @@ -7565,7 +7565,7 @@ (lisp--form-quoted-p): New functions. (lisp-completion-at-point): Use them to see if we're completing a variable reference, a function name, or just any symbol. - http://lists.gnu.org/archive/html/emacs-devel/2014-02/msg00229.html + https://lists.gnu.org/r/emacs-devel/2014-02/msg00229.html 2014-09-18 Ivan Kanis <ivan@kanis.fr> @@ -9937,7 +9937,7 @@ `window-configuration-change-hook'. (desktop-auto-save-set-timer): Change REPEAT arg of `run-with-idle-timer' from t to nil. - http://lists.gnu.org/archive/html/emacs-devel/2014-06/msg00147.html + https://lists.gnu.org/r/emacs-devel/2014-06/msg00147.html 2014-06-08 Santiago Payà i Miralta <santiagopim@gmail.com> @@ -10488,7 +10488,7 @@ * emacs-lisp/package.el (package-generate-description-file): Output first-line comment to set buffer-local var `no-byte-compile'. Suggested by Dmitry Gutov: - <http://lists.gnu.org/archive/html/emacs-devel/2014-05/msg00401.html>. + <https://lists.gnu.org/r/emacs-devel/2014-05/msg00401.html>. 2014-05-25 Thien-Thi Nguyen <ttn@gnu.org> @@ -12170,7 +12170,7 @@ to `comment-start-skip' if not `comment-use-syntax'. (Bug#16971) (comment-beginning): Use `narrow-to-region' instead of moving back one character. - (http://lists.gnu.org/archive/html/emacs-devel/2014-03/msg00488.html) + (https://lists.gnu.org/r/emacs-devel/2014-03/msg00488.html) (comment-start-skip): Update the docstring. 2014-03-18 Richard Stallman <rms@gnu.org> @@ -12467,7 +12467,7 @@ from `xterm-standard-colors' that look well on the default white background (and also on the black background) to avoid illegible color combinations like yellow-on-white and white-on-white. - http://lists.gnu.org/archive/html/emacs-devel/2014-02/msg00157.html + https://lists.gnu.org/r/emacs-devel/2014-02/msg00157.html 2014-03-08 Juanma Barranquero <lekktu@gmail.com> @@ -13224,7 +13224,7 @@ 2014-02-12 Dmitry Gutov <dgutov@yandex.ru> * progmodes/js.el (js-indent-line): Don't widen. - http://lists.gnu.org/archive/html/emacs-devel/2012-06/msg00276.html + https://lists.gnu.org/r/emacs-devel/2012-06/msg00276.html 2014-02-12 Glenn Morris <rgm@gnu.org> @@ -13965,7 +13965,7 @@ choices. (ruby-smie-rules): Instead of using a hardcoded list of alignable keywords, check against the value of `ruby-alignable-keywords' - (http://lists.gnu.org/archive/html/emacs-devel/2014-01/msg01439.html). + (https://lists.gnu.org/r/emacs-devel/2014-01/msg01439.html). 2014-01-17 Glenn Morris <rgm@gnu.org> @@ -14161,7 +14161,7 @@ 2014-01-10 Eric S. Raymond <esr@thyrsus.com> - * version.el (emacs-bzr-get-version): Restore compatibilty with + * version.el (emacs-bzr-get-version): Restore compatibility with 24.3 (Tested). 2014-01-10 Bozhidar Batsov <bozhidar@batsov.com> @@ -15408,7 +15408,7 @@ * simple.el (blink-matching--overlay): New variable. (blink-matching-open): Instead of moving point, highlight the matching paren with an overlay - (http://lists.gnu.org/archive/html/emacs-devel/2013-12/msg00333.html). + (https://lists.gnu.org/r/emacs-devel/2013-12/msg00333.html). * faces.el (paren-showing-faces, show-paren-match) (show-paren-mismatch): Move from paren.el. @@ -16628,7 +16628,7 @@ * textmodes/ispell.el (ispell-lookup-words): When `look' is not available and the word has no wildcards, append one to the grep pattern. - http://lists.gnu.org/archive/html/emacs-devel/2013-11/msg00258.html + https://lists.gnu.org/r/emacs-devel/2013-11/msg00258.html (ispell-complete-word): Call `ispell-lookup-words' with the value independent of `ispell-look-p'. @@ -18282,7 +18282,7 @@ * emacs-lisp/package.el (package-buffer-info, describe-package-1): Use :url instead of :homepage, as per - http://lists.gnu.org/archive/html/emacs-devel/2013-09/msg00622.html + https://lists.gnu.org/r/emacs-devel/2013-09/msg00622.html * newcomment.el (comment-beginning): When `comment-use-syntax' is non-nil, use `syntax-ppss' (Bug#15251). @@ -19828,7 +19828,7 @@ * xml.el (xml-parse-tag-1): Use looking-at (this reverts change in 2013-08-11T00:07:48Z!lekktu@gmail.com, which breaks the test suite). - https://lists.gnu.org/archive/html/emacs-devel/2013-08/msg00263.html + https://lists.gnu.org/r/emacs-devel/2013-08/msg00263.html 2013-08-12 Eli Zaretskii <eliz@gnu.org> @@ -21942,7 +21942,7 @@ 2013-06-25 Martin Rudalics <rudalics@gmx.at> * window.el (window--state-get-1): Workaround for bug#14527. - http://lists.gnu.org/archive/html/emacs-devel/2013-06/msg00941.html + https://lists.gnu.org/r/emacs-devel/2013-06/msg00941.html 2013-06-25 Lars Magne Ingebrigtsen <larsi@gnus.org> @@ -22055,7 +22055,7 @@ * progmodes/ruby-mode.el (ruby-font-lock-keywords): Move `catch', add some more keyword-like methods. - http://lists.gnu.org/archive/html/emacs-devel/2013-06/msg00911.html + https://lists.gnu.org/r/emacs-devel/2013-06/msg00911.html 2013-06-22 Juanma Barranquero <lekktu@gmail.com> @@ -22674,7 +22674,7 @@ 2013-06-18 Matthias Meulien <orontee@gmail.com> * tabify.el (untabify, tabify): With prefix, apply to entire buffer. - <http://lists.gnu.org/archive/html/emacs-devel/2013-03/msg00545.html> + <https://lists.gnu.org/r/emacs-devel/2013-03/msg00545.html> 2013-06-18 Glenn Morris <rgm@gnu.org> @@ -22704,7 +22704,7 @@ * emacs-lisp/package.el (package-load-descriptor): Remove `with-syntax-table' call, `read' doesn't need it. - http://lists.gnu.org/archive/html/emacs-devel/2013-06/msg00539.html + https://lists.gnu.org/r/emacs-devel/2013-06/msg00539.html 2013-06-17 Juanma Barranquero <lekktu@gmail.com> @@ -23666,7 +23666,7 @@ 2013-05-28 Alan Mackenzie <acm@muc.de> - Handle "capitalised keywords" correctly. + Handle "capitalized keywords" correctly. * progmodes/cc-mode.el (c-after-change): Bind case-fold-search to nil. 2013-05-28 Aidan Gauland <aidalgol@amuri.net> @@ -25118,7 +25118,7 @@ (desktop-auto-save, desktop-auto-save-set-timer): New functions. (after-init-hook): Call `desktop-auto-save-set-timer'. Suggested by Reuben Thomas <rrt@sc3d.org> in - <http://lists.gnu.org/archive/html/emacs-devel/2013-04/msg00327.html>. + <https://lists.gnu.org/r/emacs-devel/2013-04/msg00327.html>. 2013-04-27 Leo Liu <sdl.web@gmail.com> @@ -25144,7 +25144,7 @@ * ls-lisp.el (ls-lisp-insert-directory): If no files are displayed, move point to after the totals line. - See http://lists.gnu.org/archive/html/emacs-devel/2013-04/msg00677.html + See https://lists.gnu.org/r/emacs-devel/2013-04/msg00677.html for the details. 2013-04-27 Stefan Monnier <monnier@iro.umontreal.ca> @@ -25757,7 +25757,7 @@ Do not set x-display-name until X connection is established. This is needed to prevent from weird situation described at - <http://lists.gnu.org/archive/html/emacs-devel/2013-04/msg00212.html>. + <https://lists.gnu.org/r/emacs-devel/2013-04/msg00212.html>. * frame.el (make-frame): Set x-display-name after call to window system initialization function, not before. * term/x-win.el (x-initialize-window-system): Add optional @@ -26079,7 +26079,7 @@ (batch-skkdic-convert): Suppress most of the chatter. It's not needed so much now that machines are faster, and its non-ASCII component was confusing; see Dmitry Gutov in - <http://lists.gnu.org/archive/html/emacs-devel/2013-03/msg00508.html>. + <https://lists.gnu.org/r/emacs-devel/2013-03/msg00508.html>. 2013-03-20 Leo Liu <sdl.web@gmail.com> @@ -26223,7 +26223,7 @@ * startup.el (command-line-normalize-file-name): Fix handling of backslashes in DOS and Windows file names. Reported by Xue Fuqiao <xfq.free@gmail.com> in - http://lists.gnu.org/archive/html/help-gnu-emacs/2013-03/msg00245.html. + https://lists.gnu.org/r/help-gnu-emacs/2013-03/msg00245.html. 2013-03-15 Michael Albinus <michael.albinus@gmx.de> @@ -26309,4 +26309,4 @@ See ChangeLog.16 for earlier changes. GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. diff --git a/lisp/ChangeLog.2 b/lisp/ChangeLog.2 index 7a4845374f8..5087b943e3a 100644 --- a/lisp/ChangeLog.2 +++ b/lisp/ChangeLog.2 @@ -4007,4 +4007,4 @@ See ChangeLog.1 for earlier changes. GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. diff --git a/lisp/ChangeLog.3 b/lisp/ChangeLog.3 index 3bac72a69da..1ba33b1085f 100644 --- a/lisp/ChangeLog.3 +++ b/lisp/ChangeLog.3 @@ -12448,4 +12448,4 @@ See ChangeLog.2 for earlier changes. GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. diff --git a/lisp/ChangeLog.4 b/lisp/ChangeLog.4 index 00ce74e5150..00798e590c5 100644 --- a/lisp/ChangeLog.4 +++ b/lisp/ChangeLog.4 @@ -8949,4 +8949,4 @@ See ChangeLog.3 for earlier changes. GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. diff --git a/lisp/ChangeLog.5 b/lisp/ChangeLog.5 index 800277b1239..64abfe988f5 100644 --- a/lisp/ChangeLog.5 +++ b/lisp/ChangeLog.5 @@ -9283,4 +9283,4 @@ See ChangeLog.4 for earlier changes. GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. diff --git a/lisp/ChangeLog.6 b/lisp/ChangeLog.6 index 84826379890..64a9d4df8ed 100644 --- a/lisp/ChangeLog.6 +++ b/lisp/ChangeLog.6 @@ -1473,7 +1473,7 @@ 1996-04-18 Richard Stallman <rms@mole.gnu.ai.mit.edu> * term/win32-win.el (x-select-text): Remember selected text. - (x-get-selection-value): Return nil if the clipboard data is + (x-get-selection-value): Return nil if the clipboard data is the same as the remembered selected text. 1996-04-18 Lars Magne Ingebrigtsen <larsi@ifi.uio.no> @@ -8036,4 +8036,4 @@ See ChangeLog.5 for earlier changes. GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. diff --git a/lisp/ChangeLog.7 b/lisp/ChangeLog.7 index 52a0180c633..62ee295b899 100644 --- a/lisp/ChangeLog.7 +++ b/lisp/ChangeLog.7 @@ -23126,4 +23126,4 @@ See ChangeLog.6 for earlier changes. GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. diff --git a/lisp/ChangeLog.8 b/lisp/ChangeLog.8 index 7e4522f53f4..57b5584ebe4 100644 --- a/lisp/ChangeLog.8 +++ b/lisp/ChangeLog.8 @@ -10007,4 +10007,4 @@ See ChangeLog.7 for earlier changes. GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. diff --git a/lisp/ChangeLog.9 b/lisp/ChangeLog.9 index e51c0c5dad3..16e0a88052b 100644 --- a/lisp/ChangeLog.9 +++ b/lisp/ChangeLog.9 @@ -2033,7 +2033,7 @@ (uniquify-get-proposed-name): Arguments changed, callers changed. (uniquify-rationalize-conflicting-sublist): Explicitly reset the uniquify-possibly-resolvable flag, which is no more bound locally. - (uniquify-rename-buffer): Do not set the old unrationalised-buffer + (uniquify-rename-buffer): Do not set the old unrationalized-buffer flag, which does not exist any more. 2001-07-23 Eli Zaretskii <eliz@is.elta.co.il> @@ -20700,4 +20700,4 @@ See ChangeLog.8 for earlier changes. GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. diff --git a/lisp/Makefile.in b/lisp/Makefile.in index 653200577db..de3dc186170 100644 --- a/lisp/Makefile.in +++ b/lisp/Makefile.in @@ -15,7 +15,7 @@ # GNU General Public License for more details. # You should have received a copy of the GNU General Public License -# along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +# along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. SHELL = @SHELL@ @@ -132,13 +132,13 @@ PHONY_EXTRAS = # This could lead to problems in parallel builds if automatically # generated *.el files (eg loaddefs etc) were being changed at the same time. # One solution was to add autoloads as a prerequisite: -# http://lists.gnu.org/archive/html/emacs-pretest-bug/2007-01/msg00469.html -# http://lists.gnu.org/archive/html/bug-gnu-emacs/2007-12/msg00171.html +# https://lists.gnu.org/r/emacs-pretest-bug/2007-01/msg00469.html +# https://lists.gnu.org/r/bug-gnu-emacs/2007-12/msg00171.html # However, this meant that running these targets modified loaddefs.el, # every time (due to time-stamping). Calling these rules from # bootstrap-after would modify loaddefs after src/emacs, resulting # in make install remaking src/emacs for no real reason: -# http://lists.gnu.org/archive/html/emacs-devel/2008-02/msg00311.html +# https://lists.gnu.org/r/emacs-devel/2008-02/msg00311.html # Nowadays these commands don't scan automatically generated files, # since they will never contain any useful information # (see finder-no-scan-regexp and custom-dependencies-no-scan-regexp). @@ -481,7 +481,7 @@ check-defun-dups: ## ones that don't change very often at that) seems pretty pointless ## to me. -# http://debbugs.gnu.org/1004 +# https://debbugs.gnu.org/1004 # CC Mode uses a compile time macro system which causes a compile time # dependency in cc-*.elc files on the macros in other cc-*.el and the # version string in cc-defs.el. diff --git a/lisp/abbrev.el b/lisp/abbrev.el index 01ad3d478fc..dbda5b5d2ec 100644 --- a/lisp/abbrev.el +++ b/lisp/abbrev.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/align.el b/lisp/align.el index 081f587d4b2..084cd21b402 100644 --- a/lisp/align.el +++ b/lisp/align.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/allout-widgets.el b/lisp/allout-widgets.el index c07bbd0b768..9fa927ddcb3 100644 --- a/lisp/allout-widgets.el +++ b/lisp/allout-widgets.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -768,8 +768,7 @@ Optional RECURSING is for internal use, to limit recursion." (if allout-widgets-time-decoration-activity (setq allout-widgets-last-decoration-timing - (list (allout-elapsed-time-seconds (current-time) - start-time) + (list (allout-elapsed-time-seconds nil start-time) allout-widgets-changes-record))) (setq allout-widgets-changes-record nil) diff --git a/lisp/allout.el b/lisp/allout.el index 529de85cd42..9e83a2fb2c8 100644 --- a/lisp/allout.el +++ b/lisp/allout.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -357,7 +357,7 @@ Examples: grandchildren, but completely collapse the final top-level topic. (-1 () : 1 0) Close the first topic so only the immediate subtopics are shown, - leave the subsequent topics exposed as they are until the second + leave the subsequent topics exposed as they are until the second to last topic, which is exposed at least one level, and completely close the last topic. (-2 : -1 *) @@ -1687,7 +1687,7 @@ from what it did before, for backwards compatibility. MODE is the activation mode - see `allout-auto-activation' for valid values." (declare (obsolete allout-auto-activation "23.3")) - (custom-set-variables (list 'allout-auto-activation (format "%s" mode))) + (customize-set-variable 'allout-auto-activation (format "%s" mode)) (format "%s" mode)) ;;;_ > allout-setup-menubar () diff --git a/lisp/ansi-color.el b/lisp/ansi-color.el index 47437bb7c87..71b79223429 100644 --- a/lisp/ansi-color.el +++ b/lisp/ansi-color.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -150,17 +150,14 @@ foreground and background colors, respectively." :version "24.4" ; default colors copied from `xterm-standard-colors' :group 'ansi-colors) -(defconst ansi-color-regexp "\033\\[\\([0-9;]*m\\)" - "Regexp that matches SGR control sequences.") - -(defconst ansi-color-drop-regexp - "\033\\[\\([ABCDsuK]\\|[12][JK]\\|=[0-9]+[hI]\\|[0-9;]*[Hf]\\|\\?[0-9]+[hl]\\)" - "Regexp that matches ANSI control sequences to silently drop.") +(defconst ansi-color-control-seq-regexp + ;; See ECMA 48, section 5.4 "Control Sequences". + "\e\\[[\x30-\x3F]*[\x20-\x2F]*[\x40-\x7E]" + "Regexp matching an ANSI control sequence.") (defconst ansi-color-parameter-regexp "\\([0-9]*\\)[m;]" "Regexp that matches SGR control sequence parameters.") - ;; Convenience functions for comint modes (eg. shell-mode) @@ -185,7 +182,7 @@ in shell buffers. You set this variable by calling one of: :group 'ansi-colors :version "23.2") -(defvar ansi-color-apply-face-function 'ansi-color-apply-overlay-face +(defvar ansi-color-apply-face-function #'ansi-color-apply-overlay-face "Function for applying an Ansi Color face to text in a buffer. This function should accept three arguments: BEG, END, and FACE, and it should apply face FACE to the text between BEG and END.") @@ -259,22 +256,20 @@ This function can be added to `comint-preoutput-filter-functions'." (setq string (concat (cadr ansi-color-context) string) ansi-color-context nil)) ;; find the next escape sequence - (while (setq end (string-match ansi-color-regexp string start)) - (setq result (concat result (substring string start end)) - start (match-end 0))) - ;; eliminate unrecognized escape sequences - (while (string-match ansi-color-drop-regexp string) - (setq string - (replace-match "" nil nil string))) + (while (setq end (string-match ansi-color-control-seq-regexp string start)) + (push (substring string start end) result) + (setq start (match-end 0))) ;; save context, add the remainder of the string to the result (let (fragment) - (if (string-match "\033" string start) - (let ((pos (match-beginning 0))) - (setq fragment (substring string pos) - result (concat result (substring string start pos)))) - (setq result (concat result (substring string start)))) + (push (substring string start + (if (string-match "\033" string start) + (let ((pos (match-beginning 0))) + (setq fragment (substring string pos)) + pos) + nil)) + result) (setq ansi-color-context (if fragment (list nil fragment)))) - result)) + (apply #'concat (nreverse result)))) (defun ansi-color--find-face (codes) "Return the face corresponding to CODES." @@ -306,35 +301,29 @@ Set `ansi-color-context' to nil if you don't want this. This function can be added to `comint-preoutput-filter-functions'." (let ((codes (car ansi-color-context)) - (start 0) end escape-sequence result - colorized-substring) + (start 0) end result) ;; If context was saved and is a string, prepend it. (if (cadr ansi-color-context) (setq string (concat (cadr ansi-color-context) string) ansi-color-context nil)) ;; Find the next escape sequence. - (while (setq end (string-match ansi-color-regexp string start)) - (setq escape-sequence (match-string 1 string)) - ;; Colorize the old block from start to end using old face. - (when codes - (put-text-property start end 'font-lock-face (ansi-color--find-face codes) string)) - (setq colorized-substring (substring string start end) - start (match-end 0)) - ;; Eliminate unrecognized ANSI sequences. - (while (string-match ansi-color-drop-regexp colorized-substring) - (setq colorized-substring - (replace-match "" nil nil colorized-substring))) - (push colorized-substring result) - ;; Create new face, by applying escape sequence parameters. - (setq codes (ansi-color-apply-sequence escape-sequence codes))) + (while (setq end (string-match ansi-color-control-seq-regexp string start)) + (let ((esc-end (match-end 0))) + ;; Colorize the old block from start to end using old face. + (when codes + (put-text-property start end 'font-lock-face + (ansi-color--find-face codes) string)) + (push (substring string start end) result) + (setq start (match-end 0)) + ;; If this is a color escape sequence, + (when (eq (aref string (1- esc-end)) ?m) + ;; create a new face from it. + (setq codes (ansi-color-apply-sequence + (substring string end esc-end) codes))))) ;; if the rest of the string should have a face, put it there (when codes (put-text-property start (length string) 'font-lock-face (ansi-color--find-face codes) string)) - ;; eliminate unrecognized escape sequences - (while (string-match ansi-color-drop-regexp string) - (setq string - (replace-match "" nil nil string))) ;; save context, add the remainder of the string to the result (let (fragment) (if (string-match "\033" string start) @@ -367,13 +356,9 @@ it will override BEGIN, the start of the region. Set (start (or (cadr ansi-color-context-region) begin))) (save-excursion (goto-char start) - ;; Delete unrecognized escape sequences. - (while (re-search-forward ansi-color-drop-regexp end-marker t) - (replace-match "")) - (goto-char start) - ;; Delete SGR escape sequences. - (while (re-search-forward ansi-color-regexp end-marker t) - (replace-match "")) + ;; Delete escape sequences. + (while (re-search-forward ansi-color-control-seq-regexp end-marker t) + (delete-region (match-beginning 0) (match-end 0))) ;; save context, add the remainder of the string to the result (if (re-search-forward "\033" end-marker t) (setq ansi-color-context-region (list nil (match-beginning 0))) @@ -400,28 +385,24 @@ this." (let ((codes (car ansi-color-context-region)) (start-marker (or (cadr ansi-color-context-region) (copy-marker begin))) - (end-marker (copy-marker end)) - escape-sequence) - ;; First, eliminate unrecognized ANSI control sequences. - (save-excursion - (goto-char start-marker) - (while (re-search-forward ansi-color-drop-regexp end-marker t) - (replace-match ""))) + (end-marker (copy-marker end))) (save-excursion (goto-char start-marker) - ;; Find the next SGR sequence. - (while (re-search-forward ansi-color-regexp end-marker t) - ;; Colorize the old block from start to end using old face. - (funcall ansi-color-apply-face-function - start-marker (match-beginning 0) - (ansi-color--find-face codes)) - ;; store escape sequence and new start position - (setq escape-sequence (match-string 1) - start-marker (copy-marker (match-end 0))) - ;; delete the escape sequence - (replace-match "") - ;; Update the list of ansi codes. - (setq codes (ansi-color-apply-sequence escape-sequence codes))) + ;; Find the next escape sequence. + (while (re-search-forward ansi-color-control-seq-regexp end-marker t) + ;; Remove escape sequence. + (let ((esc-seq (delete-and-extract-region + (match-beginning 0) (point)))) + ;; Colorize the old block from start to end using old face. + (funcall ansi-color-apply-face-function + (prog1 (marker-position start-marker) + ;; Store new start position. + (set-marker start-marker (point))) + (match-beginning 0) (ansi-color--find-face codes)) + ;; If this is a color sequence, + (when (eq (aref esc-seq (1- (length esc-seq))) ?m) + ;; update the list of ansi codes. + (setq codes (ansi-color-apply-sequence esc-seq codes))))) ;; search for the possible start of a new escape sequence (if (re-search-forward "\033" end-marker t) (progn @@ -499,7 +480,9 @@ Emacs requires OBJECT to be a buffer." ;; In order to avoid this, we use the `insert-behind-hooks' overlay ;; property to make sure it works. (let ((overlay (make-overlay from to object))) + (overlay-put overlay 'evaporate t) (overlay-put overlay 'modification-hooks '(ansi-color-freeze-overlay)) + (overlay-put overlay 'insert-behind-hooks '(ansi-color-freeze-overlay)) overlay))) (defun ansi-color-freeze-overlay (overlay is-after begin end &optional len) diff --git a/lisp/apropos.el b/lisp/apropos.el index cbd9c71d3e3..807fd854c19 100644 --- a/lisp/apropos.el +++ b/lisp/apropos.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -514,6 +514,19 @@ options only, i.e. behave like `apropos-user-option'." (let ((apropos-do-all (if do-not-all nil t))) (apropos-user-option pattern))) +;;;###autoload +(defun apropos-local-variable (pattern &optional buffer) + "Show buffer-local variables that match PATTERN. +Optional arg BUFFER (default: current buffer) is the buffer to check. + +The output includes variables that are not yet set in BUFFER, but that +will be buffer-local when set." + (interactive (list (apropos-read-pattern "buffer-local variable"))) + (unless buffer (setq buffer (current-buffer))) + (apropos-command pattern nil (lambda (symbol) + (and (local-variable-if-set-p symbol) + (get symbol 'variable-documentation))))) + ;; For auld lang syne: ;;;###autoload (defalias 'command-apropos 'apropos-command) @@ -795,6 +808,35 @@ Returns list of symbols and values found." (let ((apropos-multi-type do-all)) (apropos-print nil "\n----------------\n"))) +;;;###autoload +(defun apropos-local-value (pattern &optional buffer) + "Show buffer-local variables whose values match PATTERN. +This is like `apropos-value', but only for buffer-local variables. +Optional arg BUFFER (default: current buffer) is the buffer to check." + (interactive (list (apropos-read-pattern "value of buffer-local variable"))) + (unless buffer (setq buffer (current-buffer))) + (apropos-parse-pattern pattern) + (setq apropos-accumulator ()) + (let ((var nil)) + (mapatoms + (lambda (symb) + (unless (memq symb '(apropos-regexp apropos-pattern apropos-all-words-regexp + apropos-words apropos-all-words apropos-accumulator symb var)) + (setq var (apropos-value-internal 'local-variable-if-set-p symb 'symbol-value))) + (when (and (fboundp 'apropos-false-hit-str) (apropos-false-hit-str var)) + (setq var nil)) + (when var + (setq apropos-accumulator (cons (list symb (apropos-score-str var) nil var) + apropos-accumulator)))))) + (let ((apropos-multi-type nil)) + (if (> emacs-major-version 20) + (apropos-print + nil "\n----------------\n" + (format "Buffer `%s' has the following local variables\nmatching %s`%s':" + (buffer-name buffer) + (if (consp pattern) "keywords " "") + pattern)) + (apropos-print nil "\n----------------\n")))) ;;;###autoload (defun apropos-documentation (pattern &optional do-all) diff --git a/lisp/arc-mode.el b/lisp/arc-mode.el index bd7548b704f..b06c07fea87 100644 --- a/lisp/arc-mode.el +++ b/lisp/arc-mode.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -559,13 +559,13 @@ FLOAT, if non-nil, means generate and return a float instead of an integer (if (zerop (logand 256 mode)) ?- ?r) (if (zerop (logand 128 mode)) ?- ?w) (if (zerop (logand 64 mode)) - (if (zerop (logand 1024 mode)) ?- ?S) - (if (zerop (logand 1024 mode)) ?x ?s)) + (if (zerop (logand 2048 mode)) ?- ?S) + (if (zerop (logand 2048 mode)) ?x ?s)) (if (zerop (logand 32 mode)) ?- ?r) (if (zerop (logand 16 mode)) ?- ?w) (if (zerop (logand 8 mode)) - (if (zerop (logand 2048 mode)) ?- ?S) - (if (zerop (logand 2048 mode)) ?x ?s)) + (if (zerop (logand 1024 mode)) ?- ?S) + (if (zerop (logand 1024 mode)) ?x ?s)) (if (zerop (logand 4 mode)) ?- ?r) (if (zerop (logand 2 mode)) ?- ?w) (if (zerop (logand 1 mode)) ?- ?x))) diff --git a/lisp/array.el b/lisp/array.el index 1481ff26df2..d9554618db4 100644 --- a/lisp/array.el +++ b/lisp/array.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/auth-source-pass.el b/lisp/auth-source-pass.el index 529e3024a62..f4f096160ef 100644 --- a/lisp/auth-source-pass.el +++ b/lisp/auth-source-pass.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -139,11 +139,6 @@ CONTENTS is the contents of a password-store formatted file." (mapconcat #'identity (cdr pair) ":"))))) (cdr lines))))) -(defun auth-source-pass--user-match-p (entry user) - "Return true iff ENTRY match USER." - (or (null user) - (string= user (auth-source-pass-get "user" entry)))) - (defun auth-source-pass--hostname (host) "Extract hostname from HOST." (let ((url (url-generic-parse-url host))) @@ -159,6 +154,11 @@ CONTENTS is the contents of a password-store formatted file." (hostname hostname) (t host)))) +(defun auth-source-pass--user (host) + "Extract user from HOST and return it. +Return nil if no match was found." + (url-user (url-generic-parse-url host))) + (defun auth-source-pass--do-debug (&rest msg) "Call `auth-source-do-debug` with MSG and a prefix." (apply #'auth-source-do-debug @@ -235,14 +235,17 @@ matching USER." If many matches are found, return the first one. If no match is found, return nil." (or - (if (url-user (url-generic-parse-url host)) + (if (auth-source-pass--user host) ;; if HOST contains a user (e.g., "user@host.com"), <HOST> (auth-source-pass--find-one-by-entry-name (auth-source-pass--hostname-with-user host) user) ;; otherwise, if USER is provided, search for <USER>@<HOST> (when (stringp user) (auth-source-pass--find-one-by-entry-name (concat user "@" (auth-source-pass--hostname host)) user))) - ;; if that didn't work, search for HOST without it's user component if any + ;; if that didn't work, search for HOST without its user component, if any (auth-source-pass--find-one-by-entry-name (auth-source-pass--hostname host) user) + ;; if that didn't work, search for HOST with user extracted from it + (auth-source-pass--find-one-by-entry-name + (auth-source-pass--hostname host) (auth-source-pass--user host)) ;; if that didn't work, remove subdomain: foo.bar.com -> bar.com (let ((components (split-string host "\\."))) (when (= (length components) 3) diff --git a/lisp/auth-source.el b/lisp/auth-source.el index 01d12c26141..1cb7f5d57ef 100644 --- a/lisp/auth-source.el +++ b/lisp/auth-source.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -200,8 +200,6 @@ Note that if EPA/EPG is not available, this should NOT be used." (const :tag "Save GPG-encrypted password tokens" gpg) (const :tag "Don't encrypt tokens" never)))))) -(defvar auth-source-magic "auth-source-magic ") - (defcustom auth-source-do-cache t "Whether auth-source should cache information with `password-cache'." :group 'auth-source @@ -782,16 +780,16 @@ Returns the deleted entries." (defun auth-source-forget-all-cached () "Forget all cached auth-source data." (interactive) - (cl-do-symbols (sym password-data) - ;; when the symbol name starts with auth-source-magic - (when (string-match (concat "^" auth-source-magic) (symbol-name sym)) - ;; remove that key - (password-cache-remove (symbol-name sym)))) + (maphash (lambda (key _password) + (when (eq 'auth-source (car-safe key)) + ;; remove that key + (password-cache-remove key))) + password-data) (setq auth-source-netrc-cache nil)) (defun auth-source-format-cache-entry (spec) "Format SPEC entry to put it in the password cache." - (concat auth-source-magic (format "%S" spec))) + `(auth-source . ,spec)) (defun auth-source-remember (spec found) "Remember FOUND search results for SPEC." @@ -822,18 +820,16 @@ This is not a full `auth-source-search' spec but works similarly. For instance, \(:host \"myhost\" \"yourhost\") would find all the cached data that was found with a search for those two hosts, while \(:host t) would find all host entries." - (let ((count 0) - sname) - (cl-do-symbols (sym password-data) - ;; when the symbol name matches with auth-source-magic - (when (and (setq sname (symbol-name sym)) - (string-match (concat "^" auth-source-magic "\\(.+\\)") - sname) - ;; and the spec matches what was stored in the cache - (auth-source-specmatchp spec (read (match-string 1 sname)))) - ;; remove that key - (password-cache-remove sname) - (cl-incf count))) + (let ((count 0)) + (maphash + (lambda (key _password) + (when (and (eq 'auth-source (car-safe key)) + ;; and the spec matches what was stored in the cache + (auth-source-specmatchp spec (cdr key))) + ;; remove that key + (password-cache-remove key) + (cl-incf count))) + password-data) count)) (defun auth-source-specmatchp (spec stored) @@ -934,7 +930,7 @@ Note that the MAX parameter is used so we can exit the parse early." (or ;; the required list of keys is nil, or (null require) - ;; every element of require is in n(ormalized) + ;; every element of require is in n (normalized) (let ((n (nth 0 (auth-source-netrc-normalize (list alist) file)))) (cl-loop for req in require diff --git a/lisp/autoarg.el b/lisp/autoarg.el index 79916933edb..7677b9ed7ba 100644 --- a/lisp/autoarg.el +++ b/lisp/autoarg.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/autoinsert.el b/lisp/autoinsert.el index fef42161bf3..a43e068a4dc 100644 --- a/lisp/autoinsert.el +++ b/lisp/autoinsert.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -141,14 +141,14 @@ If this contains a %s, that will be replaced by the matching rule." " .\\\" You may distribute this file under the terms of the GNU Free .\\\" Documentation License. -.TH " (file-name-base) +.TH " (file-name-base (buffer-file-name)) " " (file-name-extension (buffer-file-name)) " " (format-time-string "%Y-%m-%d ") "\n.SH NAME\n" - (file-name-base) + (file-name-base (buffer-file-name)) " \\- " str "\n.SH SYNOPSIS -.B " (file-name-base) +.B " (file-name-base (buffer-file-name)) "\n" _ " @@ -200,7 +200,7 @@ If this contains a %s, that will be replaced by the matching rule." \;; 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 <http://www.gnu.org/licenses/>. +\;; along with this program. If not, see <https://www.gnu.org/licenses/>. \;;; Commentary: @@ -211,7 +211,7 @@ If this contains a %s, that will be replaced by the matching rule." \(provide '" - (file-name-base) + (file-name-base (buffer-file-name)) ") \;;; " (file-name-nondirectory (buffer-file-name)) " ends here\n") (("\\.texi\\(nfo\\)?\\'" . "Texinfo file skeleton") @@ -219,7 +219,7 @@ If this contains a %s, that will be replaced by the matching rule." "\\input texinfo @c -*-texinfo-*- @c %**start of header @setfilename " - (file-name-base) ".info\n" + (file-name-base (buffer-file-name)) ".info\n" "@settitle " str " @c %**end of header @copying\n" @@ -237,7 +237,7 @@ A copy of the license is included in the section entitled ``GNU Free Documentation License''. A copy of the license is also available from the Free Software -Foundation Web site at @url{http://www.gnu.org/licenses/fdl.html}. +Foundation Web site at @url{https://www.gnu.org/licenses/fdl.html}. @end quotation @@ -284,7 +284,7 @@ The document was typeset with * GNU Free Documentation License:: License for copying this manual. @end menu -@c Get fdl.texi from http://www.gnu.org/licenses/fdl.html +@c Get fdl.texi from https://www.gnu.org/licenses/fdl.html @include fdl.texi @node Index diff --git a/lisp/autorevert.el b/lisp/autorevert.el index 79291624523..4b70f73fe3e 100644 --- a/lisp/autorevert.el +++ b/lisp/autorevert.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -319,10 +319,12 @@ the list of old buffers.") (defvar auto-revert-tail-pos 0 "Position of last known end of file.") +(defun auto-revert-find-file-function () + (setq-local auto-revert-tail-pos + (nth 7 (file-attributes buffer-file-name)))) + (add-hook 'find-file-hook - (lambda () - (setq-local auto-revert-tail-pos - (nth 7 (file-attributes buffer-file-name))))) + #'auto-revert-find-file-function) (defvar auto-revert-notify-watch-descriptor-hash-list (make-hash-table :test 'equal) @@ -341,6 +343,11 @@ This has been reported by a file notification event.") ;; Functions: +(defun auto-revert-remove-current-buffer () + "Remove dead buffer from `auto-revert-buffer-list'." + (setq auto-revert-buffer-list + (delq (current-buffer) auto-revert-buffer-list))) + ;;;###autoload (define-minor-mode auto-revert-mode "Toggle reverting buffer when the file changes (Auto-Revert Mode). @@ -364,13 +371,10 @@ without being changed in the part that is already in the buffer." (push (current-buffer) auto-revert-buffer-list) (add-hook 'kill-buffer-hook - (lambda () - (setq auto-revert-buffer-list - (delq (current-buffer) auto-revert-buffer-list))) + #'auto-revert-remove-current-buffer nil t)) (when auto-revert-use-notify (auto-revert-notify-rm-watch)) - (setq auto-revert-buffer-list - (delq (current-buffer) auto-revert-buffer-list))) + (auto-revert-remove-current-buffer)) (auto-revert-set-timer) (when auto-revert-mode (auto-revert-buffers) @@ -786,24 +790,24 @@ the timer when no buffers need to be checked." (not (and auto-revert-stop-on-user-input (input-pending-p)))) (let ((buf (car bufs))) - (if (buffer-live-p buf) - (with-current-buffer buf - ;; Test if someone has turned off Auto-Revert Mode in a - ;; non-standard way, for example by changing major mode. - (if (and (not auto-revert-mode) - (not auto-revert-tail-mode) - (memq buf auto-revert-buffer-list)) - (setq auto-revert-buffer-list - (delq buf auto-revert-buffer-list))) - (when (auto-revert-active-p) - ;; Enable file notification. - (when (and auto-revert-use-notify - (not auto-revert-notify-watch-descriptor)) - (auto-revert-notify-add-watch)) - (auto-revert-handler))) - ;; Remove dead buffer from `auto-revert-buffer-list'. - (setq auto-revert-buffer-list - (delq buf auto-revert-buffer-list)))) + (with-current-buffer buf + (if (buffer-live-p buf) + (progn + ;; Test if someone has turned off Auto-Revert Mode + ;; in a non-standard way, for example by changing + ;; major mode. + (if (and (not auto-revert-mode) + (not auto-revert-tail-mode) + (memq buf auto-revert-buffer-list)) + (auto-revert-remove-current-buffer)) + (when (auto-revert-active-p) + ;; Enable file notification. + (when (and auto-revert-use-notify + (not auto-revert-notify-watch-descriptor)) + (auto-revert-notify-add-watch)) + (auto-revert-handler))) + ;; Remove dead buffer from `auto-revert-buffer-list'. + (auto-revert-remove-current-buffer)))) (setq bufs (cdr bufs))) (setq auto-revert-remaining-buffers bufs) ;; Check if we should cancel the timer. diff --git a/lisp/avoid.el b/lisp/avoid.el index a4935c48895..1a471983fc8 100644 --- a/lisp/avoid.el +++ b/lisp/avoid.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/battery.el b/lisp/battery.el index b1834f06ff8..570cee140b1 100644 --- a/lisp/battery.el +++ b/lisp/battery.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/bindings.el b/lisp/bindings.el index be44b45136e..2bad90351c4 100644 --- a/lisp/bindings.el +++ b/lisp/bindings.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -689,6 +689,7 @@ okay. See `mode-line-format'.") ;; `kill-all-local-variables', because they have no default value. ;; For consistency, we give them the `permanent-local' property, even ;; though `kill-all-local-variables' does not actually consult it. +;; See init_buffer_once in buffer.c for the origins of this list. (mapc (lambda (sym) (put sym 'permanent-local t)) '(buffer-file-name default-directory buffer-backed-up @@ -697,7 +698,8 @@ okay. See `mode-line-format'.") point-before-scroll buffer-file-truename buffer-file-format buffer-auto-save-file-format buffer-display-count buffer-display-time - enable-multibyte-characters)) + enable-multibyte-characters + buffer-file-coding-system truncate-lines)) ;; We have base64, md5 and sha1 functions built in now. (provide 'base64) diff --git a/lisp/bookmark.el b/lisp/bookmark.el index 5b8ded7b22a..1c8ff3df23e 100644 --- a/lisp/bookmark.el +++ b/lisp/bookmark.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/bs.el b/lisp/bs.el index c626698faf9..07d23e465e3 100644 --- a/lisp/bs.el +++ b/lisp/bs.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/buff-menu.el b/lisp/buff-menu.el index 83d6bb6b0e9..cb107548280 100644 --- a/lisp/buff-menu.el +++ b/lisp/buff-menu.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/button.el b/lisp/button.el index 99c03d9d687..32cd995f5e4 100644 --- a/lisp/button.el +++ b/lisp/button.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -36,7 +36,7 @@ ;; represented by the overlay itself, or text-properties, in which case ;; the button is represented by a marker or buffer-position pointing ;; somewhere in the button. In the latter case, no markers into the -;; buffer are retained, which is important for speed if there are are +;; buffer are retained, which is important for speed if there are ;; extremely large numbers of buttons. Note however that if there is ;; an existing face text-property at the site of the button, the ;; button face may not be visible. Using overlays avoids this. @@ -232,7 +232,7 @@ property instead of `action'; if the button has no `mouse-action', the value of `action' is used instead. The action can either be a marker or a function. If it's a -marker then goto it. Otherwise it it is a function then it is +marker then goto it. Otherwise if it is a function then it is called with BUTTON as only argument. BUTTON is either an overlay, a buffer position, or (for buttons in the mode-line or header-line) a string." diff --git a/lisp/calc/calc-aent.el b/lisp/calc/calc-aent.el index 4e074d6b241..997ac3d583f 100644 --- a/lisp/calc/calc-aent.el +++ b/lisp/calc/calc-aent.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -728,7 +728,9 @@ in Calc algebraic input.") math-exp-str (1- math-exp-pos)) (1- math-exp-pos)))))) (or (and (memq calc-language calc-lang-c-type-hex) - (string-match "0[xX][0-9a-fA-F]+" math-exp-str math-exp-pos)) + (eq (string-match "0[xX][0-9a-fA-F]+" math-exp-str + math-exp-pos) + math-exp-pos)) (string-match "_?\\([0-9]+.?0*@ *\\)?\\([0-9]+.?0*' *\\)?\\(0*\\([2-9]\\|1[0-4]\\)\\(#[#]?\\|\\^\\^\\)[0-9a-dA-D.]+[eE][-+_]?[0-9]+\\|0*\\([2-9]\\|[0-2][0-9]\\|3[0-6]\\)\\(#[#]?\\|\\^\\^\\)[0-9a-zA-Zα-ωΑ-Ω:.]+\\|[0-9]+:[0-9:]+\\|[0-9.]+\\([eE][-+_]?[0-9]+\\)?\"?\\)?" math-exp-str math-exp-pos)) (setq math-exp-token 'number diff --git a/lisp/calc/calc-alg.el b/lisp/calc/calc-alg.el index 9db901a9753..e64308bad67 100644 --- a/lisp/calc/calc-alg.el +++ b/lisp/calc/calc-alg.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -544,7 +544,7 @@ (if (and (eq (car-safe nn) 'frac) (eq (nth 1 nn) 1) (not n)) (unless (and (eq (car-safe math-simplify-expr) 'calcFunc-eq) (eq (car-safe (nth 1 math-simplify-expr)) 'var) - (not (math-expr-contains (nth 2 math-simplify-expr) + (not (math-expr-contains (nth 2 math-simplify-expr) (nth 1 math-simplify-expr)))) (setcar (cdr math-simplify-expr) (math-mul (nth 2 nn) (nth 1 math-simplify-expr))) diff --git a/lisp/calc/calc-arith.el b/lisp/calc/calc-arith.el index ec08ea4dd36..008d5480dd3 100644 --- a/lisp/calc/calc-arith.el +++ b/lisp/calc/calc-arith.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -448,7 +448,7 @@ ((Math-negp a) 1) ((Math-zerop a) 2) ((eq (car a) 'intv) - (cond + (cond ((math-known-posp (nth 2 a)) 4) ((math-known-negp (nth 3 a)) 1) ((Math-zerop (nth 2 a)) 6) @@ -1436,12 +1436,12 @@ (and (math-identity-matrix-p a t) (or (and (eq (car-safe b) 'calcFunc-idn) (= (length b) 2) - (list 'calcFunc-idn (math-mul + (list 'calcFunc-idn (math-mul (nth 1 (nth 1 a)) (nth 1 b)) (1- (length a)))) (and (math-known-scalarp b) - (list 'calcFunc-idn (math-mul + (list 'calcFunc-idn (math-mul (nth 1 (nth 1 a)) b) (1- (length a)))) (and (math-known-matrixp b) @@ -1449,11 +1449,11 @@ (and (math-identity-matrix-p b t) (or (and (eq (car-safe a) 'calcFunc-idn) (= (length a) 2) - (list 'calcFunc-idn (math-mul (nth 1 a) + (list 'calcFunc-idn (math-mul (nth 1 a) (nth 1 (nth 1 b))) (1- (length b)))) (and (math-known-scalarp a) - (list 'calcFunc-idn (math-mul a (nth 1 (nth 1 b))) + (list 'calcFunc-idn (math-mul a (nth 1 (nth 1 b))) (1- (length b)))) (and (math-known-matrixp a) (math-mul a (nth 1 (nth 1 b)))))) @@ -1717,7 +1717,7 @@ (defun math-div-new-non-trig (ntr) (if math-div-non-trig - (setq math-div-non-trig + (setq math-div-non-trig (list '* ntr math-div-non-trig)) (setq math-div-non-trig ntr))) @@ -1958,7 +1958,7 @@ (not (equal a math-simplify-only))) (list '^ a b)) ((and (eq (car-safe a) '*) - (or + (or (and (math-known-matrixp (nth 1 a)) (math-known-matrixp (nth 2 a))) @@ -1970,7 +1970,7 @@ (if (and (= b -1) (math-known-square-matrixp (nth 1 a)) (math-known-square-matrixp (nth 2 a))) - (math-mul (math-pow-fancy (nth 2 a) -1) + (math-mul (math-pow-fancy (nth 2 a) -1) (math-pow-fancy (nth 1 a) -1)) (list '^ a b))) ((and (eq (car-safe a) '*) @@ -2358,7 +2358,7 @@ (defalias 'calcFunc-float 'math-float) -;; The variable math-trunc-prec is local to math-trunc in calc-misc.el, +;; The variable math-trunc-prec is local to math-trunc in calc-misc.el, ;; but used by math-trunc-fancy which is called by math-trunc. (defvar math-trunc-prec) diff --git a/lisp/calc/calc-bin.el b/lisp/calc/calc-bin.el index ffca7c37e61..3a7807bae5e 100644 --- a/lisp/calc/calc-bin.el +++ b/lisp/calc/calc-bin.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -32,7 +32,7 @@ (defconst math-bignum-logb-digit-size (logb math-bignum-digit-size) "The logb of the size of a bignum digit. -This is the largest value of B such that 2^B is less than +This is the largest value of B such that 2^B is less than the size of a Calc bignum digit.") (defconst math-bignum-digit-power-of-two @@ -171,7 +171,7 @@ the size of a Calc bignum digit.") (calc-wrapper (if (and (>= n 2) (<= n 36)) (progn - (calc-change-mode + (calc-change-mode (list 'calc-number-radix 'calc-twos-complement-mode) (list n (or arg (calc-is-option))) t) ;; also change global value so minibuffer sees it @@ -424,7 +424,7 @@ the size of a Calc bignum digit.") (math-mul-bignum-digit (math-not-bignum (math-norm-bignum (car q)) (- w math-bignum-logb-digit-size)) math-bignum-digit-power-of-two - (logxor (cdr q) + (logxor (cdr q) (1- math-bignum-digit-power-of-two)))))) (defun calcFunc-lsh (a &optional n w) ; [I I] [Public] @@ -845,7 +845,7 @@ the size of a Calc bignum digit.") (setq num (concat (make-string (- digs len) ?0) num)))) (when calc-group-digits (setq num (math-group-float num))) - (concat + (concat (number-to-string calc-number-radix) "##" num))) diff --git a/lisp/calc/calc-comb.el b/lisp/calc/calc-comb.el index 91fbb7b2b8a..20b24060fc1 100644 --- a/lisp/calc/calc-comb.el +++ b/lisp/calc/calc-comb.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calc/calc-cplx.el b/lisp/calc/calc-cplx.el index a00adc00992..06c9dc9d108 100644 --- a/lisp/calc/calc-cplx.el +++ b/lisp/calc/calc-cplx.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calc/calc-embed.el b/lisp/calc/calc-embed.el index 14ab97fbed8..bc88401752a 100644 --- a/lisp/calc/calc-embed.el +++ b/lisp/calc/calc-embed.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calc/calc-ext.el b/lisp/calc/calc-ext.el index d93a86ac06f..7973fc182bd 100644 --- a/lisp/calc/calc-ext.el +++ b/lisp/calc/calc-ext.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calc/calc-fin.el b/lisp/calc/calc-fin.el index 647574684e4..92ef8f3a440 100644 --- a/lisp/calc/calc-fin.el +++ b/lisp/calc/calc-fin.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calc/calc-forms.el b/lisp/calc/calc-forms.el index 6aa421ec205..338967159d6 100644 --- a/lisp/calc/calc-forms.el +++ b/lisp/calc/calc-forms.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calc/calc-frac.el b/lisp/calc/calc-frac.el index d98cdda4ea4..3aa9eb8b97e 100644 --- a/lisp/calc/calc-frac.el +++ b/lisp/calc/calc-frac.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -206,7 +206,7 @@ (defun calcFunc-fdiv (a b) ; [R I I] [Public] (cond ((Math-num-integerp a) - (cond + (cond ((Math-num-integerp b) (if (Math-zerop b) (math-reject-arg a "*Division by zero") @@ -217,7 +217,7 @@ (math-make-frac (math-mul (math-trunc a) (nth 2 b)) (nth 1 b)))) (t (math-reject-arg b 'integerp)))) ((eq (car-safe a) 'frac) - (cond + (cond ((Math-num-integerp b) (if (Math-zerop b) (math-reject-arg a "*Division by zero") @@ -227,7 +227,7 @@ (math-reject-arg a "*Division by zero") (math-make-frac (math-mul (nth 1 a) (nth 2 b)) (math-mul (nth 2 a) (nth 1 b))))) (t (math-reject-arg b 'integerp)))) - (t + (t (math-reject-arg a 'integerp)))) (provide 'calc-frac) diff --git a/lisp/calc/calc-funcs.el b/lisp/calc/calc-funcs.el index 2bb460df3c9..1dde2ede878 100644 --- a/lisp/calc/calc-funcs.el +++ b/lisp/calc/calc-funcs.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -564,7 +564,7 @@ ((Math-lessp '(float 8 0) (math-abs-approx x)) (let* ((z (math-div '(float 8 0) x)) (y (math-sqr z)) - (xx (math-add x + (xx (math-add x (math-read-number-simple "-0.785398164"))) (a1 (math-poly-eval y (list @@ -633,7 +633,7 @@ (setq sc (cons (math-neg (cdr sc)) (car sc))) (if (math-negp x) (setq sc (cons (math-neg (car sc)) (math-neg (cdr sc)))))) - (math-mul (math-sqrt (math-div + (math-mul (math-sqrt (math-div (math-read-number-simple "0.636619722") x)) (math-sub (math-mul (cdr sc) a1) @@ -813,39 +813,39 @@ (defvar math-bernoulli-b-cache (list - (list 'frac + (list 'frac -174611 (math-read-number-simple "802857662698291200000")) - (list 'frac - 43867 + (list 'frac + 43867 (math-read-number-simple "5109094217170944000")) - (list 'frac - -3617 + (list 'frac + -3617 (math-read-number-simple "10670622842880000")) - (list 'frac - 1 + (list 'frac + 1 (math-read-number-simple "74724249600")) - (list 'frac - -691 + (list 'frac + -691 (math-read-number-simple "1307674368000")) - (list 'frac - 1 + (list 'frac + 1 (math-read-number-simple "47900160")) - (list 'frac - -1 + (list 'frac + -1 (math-read-number-simple "1209600")) - (list 'frac - 1 - 30240) - (list 'frac - -1 + (list 'frac + 1 + 30240) + (list 'frac + -1 720) - (list 'frac - 1 - 12) + (list 'frac + 1 + 12) 1 )) -(defvar math-bernoulli-B-cache +(defvar math-bernoulli-B-cache '((frac -174611 330) (frac 43867 798) (frac -3617 510) (frac 7 6) (frac -691 2730) (frac 5 66) (frac -1 30) (frac 1 42) diff --git a/lisp/calc/calc-graph.el b/lisp/calc/calc-graph.el index bc05ffe427e..c0598e6015a 100644 --- a/lisp/calc/calc-graph.el +++ b/lisp/calc/calc-graph.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calc/calc-help.el b/lisp/calc/calc-help.el index 356e571c99c..3f957992842 100644 --- a/lisp/calc/calc-help.el +++ b/lisp/calc/calc-help.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calc/calc-incom.el b/lisp/calc/calc-incom.el index 59b591510dd..1ff50e20446 100644 --- a/lisp/calc/calc-incom.el +++ b/lisp/calc/calc-incom.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calc/calc-keypd.el b/lisp/calc/calc-keypd.el index dc49f2888cf..cc3bfcf2cd0 100644 --- a/lisp/calc/calc-keypd.el +++ b/lisp/calc/calc-keypd.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calc/calc-lang.el b/lisp/calc/calc-lang.el index ce1ddb56956..50a7eec1dae 100644 --- a/lisp/calc/calc-lang.el +++ b/lisp/calc/calc-lang.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calc/calc-macs.el b/lisp/calc/calc-macs.el index b2cd580c2ee..394c2e298e7 100644 --- a/lisp/calc/calc-macs.el +++ b/lisp/calc/calc-macs.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calc/calc-map.el b/lisp/calc/calc-map.el index 76a58f4e9c1..6f60d2eca77 100644 --- a/lisp/calc/calc-map.el +++ b/lisp/calc/calc-map.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -795,8 +795,8 @@ ;;; Do substitutions in parallel to avoid crosstalk. -;; The variables math-ms-temp and math-ms-args are local to -;; math-multi-subst, but are used by math-multi-subst-rec, which +;; The variables math-ms-temp and math-ms-args are local to +;; math-multi-subst, but are used by math-multi-subst-rec, which ;; is called by math-multi-subst. (defvar math-ms-temp) (defvar math-ms-args) @@ -811,7 +811,7 @@ (math-multi-subst-rec expr))) (defun math-multi-subst-rec (expr) - (cond ((setq math-ms-temp (assoc expr math-ms-args)) + (cond ((setq math-ms-temp (assoc expr math-ms-args)) (cdr math-ms-temp)) ((Math-primp expr) expr) ((and (eq (car expr) 'calcFunc-lambda) (> (length expr) 2)) @@ -820,7 +820,7 @@ (while (cdr (setq expr (cdr expr))) (setq new (cons (car expr) new)) (if (assoc (car expr) math-ms-args) - (setq math-ms-args (cons (cons (car expr) (car expr)) + (setq math-ms-args (cons (cons (car expr) (car expr)) math-ms-args)))) (nreverse (cons (math-multi-subst-rec (car expr)) new)))) (t diff --git a/lisp/calc/calc-math.el b/lisp/calc/calc-math.el index 2590761d539..6d51536ac7a 100644 --- a/lisp/calc/calc-math.el +++ b/lisp/calc/calc-math.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -44,15 +44,15 @@ (1- n)) "The number of digits in an Emacs float.") -;;; Find the largest power of 10 which is an Emacs float, -;;; then back off by one so that any float d.dddd...eN +;;; Find the largest power of 10 which is an Emacs float, +;;; then back off by one so that any float d.dddd...eN ;;; is an Emacs float, for acceptable d.dddd.... (defvar math-largest-emacs-expt (let ((x 1) (pow 1e2)) - ;; The following loop is for efficiency; it should stop when - ;; 10^(2x) is too large. This could be indicated by a range + ;; The following loop is for efficiency; it should stop when + ;; 10^(2x) is too large. This could be indicated by a range ;; error when computing 10^(2x) or an infinite value for 10^(2x). (while (and pow @@ -102,9 +102,9 @@ If this can't be done, return NIL." (condition-case nil (math-read-number (number-to-string - (funcall fn - (string-to-number - (let + (funcall fn + (string-to-number + (let ((calc-number-radix 10) (calc-twos-complement-mode nil) (calc-float-format (list 'float calc-internal-prec)) @@ -948,7 +948,7 @@ If this can't be done, return NIL." (math-mul xs (car sc)) (math-sqr (cdr sc))))))) (math-make-sdev (calcFunc-sec (nth 1 x)) - (math-div + (math-div (math-mul (nth 2 x) (calcFunc-sin (nth 1 x))) (math-sqr (calcFunc-cos (nth 1 x))))))) @@ -1010,7 +1010,7 @@ If this can't be done, return NIL." (math-mul xs (cdr sc)) (math-sqr (car sc))))))) (math-make-sdev (calcFunc-csc (nth 1 x)) - (math-div + (math-div (math-mul (nth 2 x) (calcFunc-cos (nth 1 x))) (math-sqr (calcFunc-sin (nth 1 x))))))) @@ -1114,7 +1114,7 @@ If this can't be done, return NIL." (sh (math-mul-float (math-sub-float expx expmx) '(float 5 -1))) (ch (math-mul-float (math-add-float expx expmx) '(float 5 -1))) (sc (math-sin-cos-raw (nth 1 x))) - (d (math-add-float + (d (math-add-float (math-mul-float (math-sqr (car sc)) (math-sqr sh)) (math-mul-float (math-sqr (cdr sc)) @@ -1139,7 +1139,7 @@ If this can't be done, return NIL." (sh (math-mul-float (math-sub-float expx expmx) '(float 5 -1))) (ch (math-mul-float (math-add-float expx expmx) '(float 5 -1))) (sc (math-sin-cos-raw (nth 1 x))) - (d (math-add-float + (d (math-add-float (math-mul-float (math-sqr (car sc)) (math-sqr ch)) (math-mul-float (math-sqr (cdr sc)) @@ -1164,17 +1164,17 @@ If this can't be done, return NIL." (sh (math-mul-float (math-sub-float expx expmx) '(float 5 -1))) (ch (math-mul-float (math-add-float expx expmx) '(float 5 -1))) (sc (math-sin-cos-raw (nth 1 x))) - (d (math-add-float + (d (math-add-float (math-sqr (car sc)) (math-sqr sh)))) (and (not (eq (nth 1 d) 0)) (list 'cplx - (math-div-float + (math-div-float (math-mul-float (car sc) (cdr sc)) d) (math-neg - (math-div-float - (math-mul-float sh ch) + (math-div-float + (math-mul-float sh ch) d)))))) ((eq (car x) 'polar) (math-polar (math-cot-raw (math-complex x)))) @@ -1223,7 +1223,7 @@ If this can't be done, return NIL." (math-cos-raw-2 xmpo2 orgx)) ((math-lessp-float x (math-neg (math-pi-over-4))) (math-neg (math-cos-raw-2 (math-add (math-pi-over-2) x) orgx))) - ((math-with-extra-prec -1 (math-nearly-zerop-float x orgx)) + ((math-with-extra-prec -1 (math-nearly-zerop-float x orgx)) '(float 0 0)) ((math-use-emacs-fn 'sin x)) (calc-symbolic-mode (signal 'inexact-result nil)) @@ -1765,7 +1765,7 @@ If this can't be done, return NIL." '(float 0 0)) (calc-symbolic-mode (signal 'inexact-result nil)) ((math-posp (nth 1 x)) ; positive and real - (cond + (cond ((math-use-emacs-fn 'log x)) (t (let ((xdigs (1- (math-numdigs (nth 1 x))))) @@ -1818,7 +1818,7 @@ If this can't be done, return NIL." (defconst math-approx-ln-10 (math-read-number-simple "2.302585092994045684018") "An approximation for ln(10).") - + (math-defcache math-ln-10 math-approx-ln-10 (math-ln-raw-2 '(float 1 1))) @@ -1963,7 +1963,7 @@ If this can't be done, return NIL." (math-div '(float 2 0) (math-add expx (math-div -1 expx)))))) ((eq (car-safe x) 'sdev) (math-make-sdev (calcFunc-csch (nth 1 x)) - (math-mul (nth 2 x) + (math-mul (nth 2 x) (math-mul (calcFunc-csch (nth 1 x)) (calcFunc-coth (nth 1 x)))))) ((eq (car x) 'intv) diff --git a/lisp/calc/calc-menu.el b/lisp/calc/calc-menu.el index e0305e36e24..546e65091fc 100644 --- a/lisp/calc/calc-menu.el +++ b/lisp/calc/calc-menu.el @@ -15,7 +15,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calc/calc-misc.el b/lisp/calc/calc-misc.el index e6af0920639..a3e41cae8a6 100644 --- a/lisp/calc/calc-misc.el +++ b/lisp/calc/calc-misc.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calc/calc-mode.el b/lisp/calc/calc-mode.el index 3b378815992..4a87281a39a 100644 --- a/lisp/calc/calc-mode.el +++ b/lisp/calc/calc-mode.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calc/calc-mtx.el b/lisp/calc/calc-mtx.el index dc97c45766f..77769e47daf 100644 --- a/lisp/calc/calc-mtx.el +++ b/lisp/calc/calc-mtx.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calc/calc-nlfit.el b/lisp/calc/calc-nlfit.el index 684d0f17b79..b3335bbb007 100644 --- a/lisp/calc/calc-nlfit.el +++ b/lisp/calc/calc-nlfit.el @@ -15,7 +15,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calc/calc-poly.el b/lisp/calc/calc-poly.el index a36213077f4..b2f69bc2331 100644 --- a/lisp/calc/calc-poly.el +++ b/lisp/calc/calc-poly.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calc/calc-prog.el b/lisp/calc/calc-prog.el index 6e9322fc04c..610e4dc5ba9 100644 --- a/lisp/calc/calc-prog.el +++ b/lisp/calc/calc-prog.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calc/calc-rewr.el b/lisp/calc/calc-rewr.el index e50f8e1566e..4f0d71a2760 100644 --- a/lisp/calc/calc-rewr.el +++ b/lisp/calc/calc-rewr.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -30,7 +30,7 @@ (defvar math-rewrite-default-iters 100) -;; The variable calc-rewr-sel is local to calc-rewrite-selection and +;; The variable calc-rewr-sel is local to calc-rewrite-selection and ;; calc-rewrite, but is used by calc-locate-selection-marker. (defvar calc-rewr-sel) @@ -219,7 +219,7 @@ (not (equal math-rewrite-whole-expr save-expr)))) (if (symbolp (car sched)) (progn - (setq math-rewrite-whole-expr + (setq math-rewrite-whole-expr (math-normalize (list (car sched) math-rewrite-whole-expr))) (if trace-buffer (let ((fmt (math-format-stack-value @@ -490,13 +490,13 @@ ;; The variable math-import-list is local to part of math-compile-rewrites, ;; but is also used in a different part, and so the local version could -;; be affected by the non-local version when math-compile-rewrites calls itself. +;; be affected by the non-local version when math-compile-rewrites calls itself. (defvar math-import-list nil) -;; The variables math-regs, math-num-regs, math-prog-last, math-bound-vars, +;; The variables math-regs, math-num-regs, math-prog-last, math-bound-vars, ;; math-conds, math-copy-neg, math-rhs, math-pattern, math-remembering and -;; math-aliased-vars are local to math-compile-rewrites, -;; but are used by many functions math-rwcomp-*, which are called by +;; math-aliased-vars are local to math-compile-rewrites, +;; but are used by many functions math-rwcomp-*, which are called by ;; math-compile-rewrites. (defvar math-regs) (defvar math-num-regs) @@ -753,8 +753,8 @@ (list expr))) ;; The variables math-rewrite-heads-heads (i.e.; heads for math-rewrite-heads) -;; math-rewrite-heads-blanks and math-rewrite-heads-skips are local to -;; math-rewrite-heads, but used by math-rewrite-heads-rec, which is called by +;; math-rewrite-heads-blanks and math-rewrite-heads-skips are local to +;; math-rewrite-heads, but used by math-rewrite-heads-rec, which is called by ;; math-rewrite-heads. (defvar math-rewrite-heads-heads) (defvar math-rewrite-heads-skips) @@ -844,7 +844,7 @@ (cond ((equal expr math-rwcomp-subst-old) math-rwcomp-subst-new) ((Math-primp expr) expr) (t (if (eq (car expr) math-rwcomp-subst-old-func) - (math-build-call math-rwcomp-subst-new-func + (math-build-call math-rwcomp-subst-new-func (mapcar 'math-rwcomp-subst-rec (cdr expr))) (cons (car expr) @@ -1489,12 +1489,12 @@ (progn (terpri) (princ (car pc)) (if (and (natnump (nth 1 (car pc))) (< (nth 1 (car pc)) (length math-apply-rw-regs))) - (princ + (princ (format "\n part = %s" (aref math-apply-rw-regs (nth 1 (car pc)))))))) (cond ((eq (setq op (car (setq inst (car pc)))) 'func) - (if (and (consp + (if (and (consp (setq part (aref math-apply-rw-regs (car (cdr inst))))) (eq (car part) (car (setq inst (cdr (cdr inst))))) @@ -1533,7 +1533,7 @@ (aset mark 2 0)) ((eq op 'try) - (if (and (consp (setq part + (if (and (consp (setq part (aref math-apply-rw-regs (car (cdr inst))))) (memq (car part) (nth 2 inst)) (= (length part) 3) @@ -1658,7 +1658,7 @@ op (aref mark 2)) (cond ((eq op 0) (if (setq op (cdr (aref mark 1))) - (aset math-apply-rw-regs (nth 4 inst) + (aset math-apply-rw-regs (nth 4 inst) (car (aset mark 1 op))) (if (nth 5 inst) (progn @@ -1668,7 +1668,7 @@ (math-rwfail t)))) ((eq op 1) (if (setq op (cdr (aref mark 1))) - (aset math-apply-rw-regs (nth 4 inst) + (aset math-apply-rw-regs (nth 4 inst) (car (aset mark 1 op))) (if (= (aref mark 3) 1) (if (nth 5 inst) @@ -1725,7 +1725,7 @@ (t (math-rwfail t)))) ((eq op 'integer) - (if (Math-integerp (setq part + (if (Math-integerp (setq part (aref math-apply-rw-regs (nth 1 inst)))) (setq pc (cdr pc)) (if (Math-primp part) @@ -1756,7 +1756,7 @@ (math-rwfail))))) ((eq op 'negative) - (if (math-looks-negp (setq part + (if (math-looks-negp (setq part (aref math-apply-rw-regs (nth 1 inst)))) (setq pc (cdr pc)) (if (Math-primp part) @@ -1774,7 +1774,7 @@ (setq part (math-rweval (math-simplify (calcFunc-sign - (math-sub + (math-sub (aref math-apply-rw-regs (nth 1 inst)) (aref math-apply-rw-regs (nth 3 inst)))))))) (if (cond ((eq op 'calcFunc-eq) @@ -1793,7 +1793,7 @@ (math-rwfail))) ((eq op 'func-def) - (if (and + (if (and (consp (setq part (aref math-apply-rw-regs (car (cdr inst))))) (eq (car part) (car (setq inst (cdr (cdr inst)))))) @@ -1815,8 +1815,8 @@ (math-rwfail))) ((eq op 'func-opt) - (if (or (not - (and + (if (or (not + (and (consp (setq part (aref math-apply-rw-regs (car (cdr inst))))) (eq (car part) (nth 2 inst)))) @@ -1833,7 +1833,7 @@ (setq pc (cdr pc)))) ((eq op 'mod) - (if (if (Math-zerop + (if (if (Math-zerop (setq part (aref math-apply-rw-regs (nth 1 inst)))) (Math-zerop (nth 3 inst)) (and (not (Math-zerop (nth 2 inst))) @@ -1847,7 +1847,7 @@ (math-rwfail))) ((eq op 'apply) - (if (and (consp + (if (and (consp (setq part (aref math-apply-rw-regs (car (cdr inst))))) (not (Math-objvecp part)) (not (eq (car part) 'var))) @@ -1860,19 +1860,19 @@ (math-rwfail))) ((eq op 'cons) - (if (and (consp + (if (and (consp (setq part (aref math-apply-rw-regs (car (cdr inst))))) (eq (car part) 'vec) (cdr part)) (progn (aset math-apply-rw-regs (nth 2 inst) (nth 1 part)) - (aset math-apply-rw-regs (nth 3 inst) + (aset math-apply-rw-regs (nth 3 inst) (cons 'vec (cdr (cdr part)))) (setq pc (cdr pc))) (math-rwfail))) ((eq op 'rcons) - (if (and (consp + (if (and (consp (setq part (aref math-apply-rw-regs (car (cdr inst))))) (eq (car part) 'vec) (cdr part)) @@ -1898,7 +1898,7 @@ (setq pc (cdr pc))) ((eq op 'copy) - (aset math-apply-rw-regs (nth 2 inst) + (aset math-apply-rw-regs (nth 2 inst) (aref math-apply-rw-regs (nth 1 inst))) (setq pc (cdr pc))) diff --git a/lisp/calc/calc-rules.el b/lisp/calc/calc-rules.el index bcace468168..b29e5bf349b 100644 --- a/lisp/calc/calc-rules.el +++ b/lisp/calc/calc-rules.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calc/calc-sel.el b/lisp/calc/calc-sel.el index d7f87f49108..a363469450f 100644 --- a/lisp/calc/calc-sel.el +++ b/lisp/calc/calc-sel.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -140,8 +140,8 @@ (calc-change-current-selection sel) (error "%d is not a valid sub-formula index" num))))) -;; The variables calc-fnp-op and calc-fnp-num are local to -;; calc-find-nth-part (and calc-select-previous) but used by +;; The variables calc-fnp-op and calc-fnp-num are local to +;; calc-find-nth-part (and calc-select-previous) but used by ;; calc-find-nth-part-rec, which is called by them. (defvar calc-fnp-op) (defvar calc-fnp-num) @@ -650,7 +650,7 @@ alg) (let ((calc-dollar-values (list sel)) (calc-dollar-used 0)) - (setq alg (calc-do-alg-entry "" "Replace selection with: " nil + (setq alg (calc-do-alg-entry "" "Replace selection with: " nil 'calc-selection-history)) (and alg (progn diff --git a/lisp/calc/calc-stat.el b/lisp/calc/calc-stat.el index 2b79712f301..d70d4cd40ef 100644 --- a/lisp/calc/calc-stat.el +++ b/lisp/calc/calc-stat.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calc/calc-store.el b/lisp/calc/calc-store.el index 67931a74472..16d35f28ec0 100644 --- a/lisp/calc/calc-store.el +++ b/lisp/calc/calc-store.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calc/calc-stuff.el b/lisp/calc/calc-stuff.el index 48e3a3404d3..afdeac1b6f6 100644 --- a/lisp/calc/calc-stuff.el +++ b/lisp/calc/calc-stuff.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calc/calc-trail.el b/lisp/calc/calc-trail.el index a9e294354bc..9f949675b2e 100644 --- a/lisp/calc/calc-trail.el +++ b/lisp/calc/calc-trail.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calc/calc-undo.el b/lisp/calc/calc-undo.el index 06181f8c5c2..17e1633c989 100644 --- a/lisp/calc/calc-undo.el +++ b/lisp/calc/calc-undo.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -75,7 +75,7 @@ (let ((v (intern (nth 1 action)))) (calc-record-undo (list 'store (nth 1 action) (and (boundp v) (symbol-value v)))) - (if (y-or-n-p (format "Un-store variable %s? " + (if (y-or-n-p (format "Un-store variable %s? " (calc-var-name (nth 1 action)))) (progn (if (nth 2 action) diff --git a/lisp/calc/calc-units.el b/lisp/calc/calc-units.el index 0e3715eb4cf..b7b43acefcc 100644 --- a/lisp/calc/calc-units.el +++ b/lisp/calc/calc-units.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -825,21 +825,18 @@ If COMP or STD is non-nil, put that in the units table instead." (forward-char -1)) (insert ";;; Custom units stored by Calc on " (current-time-string) "\n") (if math-additional-units - (progn + (let (expr) (insert "(setq math-additional-units '(\n") - (let ((list math-additional-units)) - (while list - (insert " (" (symbol-name (car (car list))) " " - (if (nth 1 (car list)) - (if (stringp (nth 1 (car list))) - (prin1-to-string (nth 1 (car list))) - (prin1-to-string (math-format-flat-expr - (nth 1 (car list)) 0))) - "nil") - " " - (prin1-to-string (nth 2 (car list))) - ")\n") - (setq list (cdr list)))) + (dolist (u math-additional-units) + (insert " (" (symbol-name (car u)) " " + (if (setq expr (nth 1 u)) + (if (stringp expr) + (prin1-to-string expr) + (prin1-to-string (math-format-flat-expr expr 0))) + "nil") + " " + (prin1-to-string (nth 2 u)) + ")\n")) (insert "))\n")) (insert ";;; (no custom units defined)\n")) (insert ";;; End of custom units\n") @@ -916,15 +913,13 @@ If COMP or STD is non-nil, put that in the units table instead." (defun math-find-base-units-rec (expr pow) (let ((u (math-check-unit-name expr))) (cond (u - (let ((ulist (math-find-base-units u))) - (while ulist - (let ((p (* (cdr (car ulist)) pow)) - (old (assq (car (car ulist)) math-fbu-base))) - (if old - (setcdr old (+ (cdr old) p)) - (setq math-fbu-base - (cons (cons (car (car ulist)) p) math-fbu-base)))) - (setq ulist (cdr ulist))))) + (dolist (x (math-find-base-units u)) + (let ((p (* (cdr x) pow)) + (old (assq (car x) math-fbu-base))) + (if old + (setcdr old (+ (cdr old) p)) + (setq math-fbu-base + (cons (cons (car x) p) math-fbu-base)))))) ((math-scalarp expr)) ((and (eq (car expr) '^) (integerp (nth 2 expr))) @@ -1377,20 +1372,15 @@ If COMP or STD is non-nil, put that in the units table instead." (if (eq pow1 1) (math-to-standard-units (list '/ n d) nil) (list '^ (math-to-standard-units (list '/ n d) nil) pow1)) - (let (ud1) - (setq un (nth 4 un) - ud (nth 4 ud)) - (while un - (setq ud1 ud) - (while ud1 - (and (eq (car (car un)) (car (car ud1))) - (setq math-try-cancel-units - (+ math-try-cancel-units - (- (* (cdr (car un)) pow1) - (* (cdr (car ud)) pow2))))) - (setq ud1 (cdr ud1))) - (setq un (cdr un))) - nil)))))) + (setq un (nth 4 un) + ud (nth 4 ud)) + (dolist (x un) + (dolist (y ud) + (when (eq (car x) (car y)) + (setq math-try-cancel-units + (+ math-try-cancel-units + (- (* (cdr x) pow1) + (* (cdr (car ud)) pow2)))))))))))) (math-defsimplify ^ (and math-simplifying-units @@ -1578,9 +1568,8 @@ If COMP or STD is non-nil, put that in the units table instead." (insert "Calculator Units Table:\n\n") (insert "(All definitions are exact unless marked with an asterisk (*).)\n\n") (insert "Unit Type Definition Description\n\n") - (while uptr - (setq u (car uptr) - name (nth 2 u)) + (dolist (u uptr) + (setq name (nth 2 u)) (when (eq (car u) 'm) (setq std t)) (setq shadowed (and std (assq (car u) math-additional-units))) @@ -1618,8 +1607,7 @@ If COMP or STD is non-nil, put that in the units table instead." (insert " (redefined above)") (unless (nth 1 u) (insert " (base unit)"))) - (insert "\n") - (setq uptr (cdr uptr))) + (insert "\n")) (insert "\n\nUnit Prefix Table:\n\n") (setq uptr math-unit-prefixes) (while uptr diff --git a/lisp/calc/calc-vec.el b/lisp/calc/calc-vec.el index 0ce0d422f2f..c049933eeb5 100644 --- a/lisp/calc/calc-vec.el +++ b/lisp/calc/calc-vec.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calc/calc-yank.el b/lisp/calc/calc-yank.el index e97d8789414..fec2512266b 100644 --- a/lisp/calc/calc-yank.el +++ b/lisp/calc/calc-yank.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calc/calc.el b/lisp/calc/calc.el index 6a9af44181d..d9e8cff16a5 100644 --- a/lisp/calc/calc.el +++ b/lisp/calc/calc.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calc/calcalg2.el b/lisp/calc/calcalg2.el index bb4c30e1235..48446c3c4c5 100644 --- a/lisp/calc/calcalg2.el +++ b/lisp/calc/calcalg2.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -2354,7 +2354,7 @@ ;; The variables math-solve-lhs, math-solve-rhs and math-try-solve-sign ;; are local to math-try-solve-for, but are used by math-try-solve-prod. -;; (math-solve-lhs and math-solve-rhs are is also local to +;; (math-solve-lhs and math-solve-rhs are also local to ;; math-decompose-poly, but used by math-solve-poly-funny-powers.) (defvar math-solve-lhs) (defvar math-solve-rhs) diff --git a/lisp/calc/calcalg3.el b/lisp/calc/calcalg3.el index dc6ac93e20a..11e6342be28 100644 --- a/lisp/calc/calcalg3.el +++ b/lisp/calc/calcalg3.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -120,7 +120,7 @@ (defvar calc-curve-fit-history nil "History for calc-curve-fit.") -(defun calc-curve-fit (arg &optional calc-curve-model +(defun calc-curve-fit (arg &optional calc-curve-model calc-curve-coefnames calc-curve-varnames) (interactive "P") (calc-slow-wrapper @@ -148,7 +148,7 @@ "P prefix = plot result" "' = alg entry, $ = stack, u = Model1, U = Model2"))) (while (not calc-curve-model) - (message + (message "Fit to model: %s:%s%s" (nth which msgs) (if plot "P" " ") @@ -194,27 +194,27 @@ calc-curve-varnames nil) nil)) ((= key ?1) ; linear or multilinear - (calc-get-fit-variables calc-curve-nvars + (calc-get-fit-variables calc-curve-nvars (1+ calc-curve-nvars) (and homog 0)) - (setq calc-curve-model + (setq calc-curve-model (math-mul calc-curve-coefnames (cons 'vec (cons 1 (cdr calc-curve-varnames)))))) ((and (>= key ?2) (<= key ?9)) ; polynomial (calc-get-fit-variables 1 (- key ?0 -1) (and homog 0)) - (setq calc-curve-model + (setq calc-curve-model (math-build-polynomial-expr (cdr calc-curve-coefnames) (nth 1 calc-curve-varnames)))) ((= key ?i) ; exact polynomial (calc-get-fit-variables 1 (1- (length (nth 1 data))) (and homog 0)) - (setq calc-curve-model + (setq calc-curve-model (math-build-polynomial-expr (cdr calc-curve-coefnames) (nth 1 calc-curve-varnames)))) ((= key ?p) ; power law - (calc-get-fit-variables calc-curve-nvars + (calc-get-fit-variables calc-curve-nvars (1+ calc-curve-nvars) (and homog 1)) - (setq calc-curve-model - (math-mul + (setq calc-curve-model + (math-mul (nth 1 calc-curve-coefnames) (calcFunc-reduce '(var mul var-mul) @@ -223,9 +223,9 @@ calc-curve-varnames (cons 'vec (cdr (cdr calc-curve-coefnames)))))))) ((= key ?^) ; exponential law - (calc-get-fit-variables calc-curve-nvars + (calc-get-fit-variables calc-curve-nvars (1+ calc-curve-nvars) (and homog 1)) - (setq calc-curve-model + (setq calc-curve-model (math-mul (nth 1 calc-curve-coefnames) (calcFunc-reduce '(var mul var-mul) @@ -258,9 +258,9 @@ (cdr (nth 1 plot))))))) (calc-fit-hubbert-linear-curve func)) ((memq key '(?e ?E)) - (calc-get-fit-variables calc-curve-nvars + (calc-get-fit-variables calc-curve-nvars (1+ calc-curve-nvars) (and homog 1)) - (setq calc-curve-model + (setq calc-curve-model (math-mul (nth 1 calc-curve-coefnames) (calcFunc-reduce '(var mul var-mul) @@ -275,18 +275,18 @@ (cons 'vec (cdr (cdr calc-curve-coefnames))) calc-curve-varnames)))))) ((memq key '(?x ?X)) - (calc-get-fit-variables calc-curve-nvars + (calc-get-fit-variables calc-curve-nvars (1+ calc-curve-nvars) (and homog 0)) - (setq calc-curve-model + (setq calc-curve-model (math-mul calc-curve-coefnames (cons 'vec (cons 1 (cdr calc-curve-varnames))))) (setq calc-curve-model (if (eq key ?x) (list 'calcFunc-exp calc-curve-model) (list '^ 10 calc-curve-model)))) ((memq key '(?l ?L)) - (calc-get-fit-variables calc-curve-nvars + (calc-get-fit-variables calc-curve-nvars (1+ calc-curve-nvars) (and homog 0)) - (setq calc-curve-model + (setq calc-curve-model (math-mul calc-curve-coefnames (cons 'vec (cons 1 (cdr (calcFunc-map @@ -296,7 +296,7 @@ var-log10)) calc-curve-varnames))))))) ((= key ?q) - (calc-get-fit-variables calc-curve-nvars + (calc-get-fit-variables calc-curve-nvars (1+ (* 2 calc-curve-nvars)) (and homog 0)) (let ((c calc-curve-coefnames) (v calc-curve-varnames)) @@ -310,15 +310,15 @@ (list '- (car v) (nth 1 c)) 2))))))) ((= key ?g) - (setq - calc-curve-model - (math-read-expr + (setq + calc-curve-model + (math-read-expr "(AFit / BFit sqrt(2 pi)) exp(-0.5 * ((XFit - CFit) / BFit)^2)") calc-curve-varnames '(vec (var XFit var-XFit)) calc-curve-coefnames '(vec (var AFit var-AFit) (var BFit var-BFit) (var CFit var-CFit))) - (calc-get-fit-variables 1 (1- (length calc-curve-coefnames)) + (calc-get-fit-variables 1 (1- (length calc-curve-coefnames)) (and homog 1))) ((memq key '(?\$ ?\' ?u ?U)) (let* ((defvars nil) @@ -327,7 +327,7 @@ (let* ((calc-dollar-values calc-arg-values) (calc-dollar-used 0) (calc-hashes-used 0)) - (setq calc-curve-model + (setq calc-curve-model (calc-do-alg-entry "" "Model formula: " nil 'calc-curve-fit-history)) (if (/= (length calc-curve-model) 1) @@ -358,19 +358,19 @@ (or (null (nth 3 calc-curve-model)) (math-vectorp (nth 3 calc-curve-model)))) (setq calc-curve-varnames (nth 2 calc-curve-model) - calc-curve-coefnames + calc-curve-coefnames (or (nth 3 calc-curve-model) (cons 'vec (math-all-vars-but - calc-curve-model + calc-curve-model calc-curve-varnames))) calc-curve-model (nth 1 calc-curve-model)) (error "Incorrect model specifier"))))) (or calc-curve-varnames - (let ((with-y + (let ((with-y (eq (car-safe calc-curve-model) 'calcFunc-eq))) (if calc-curve-coefnames - (calc-get-fit-variables + (calc-get-fit-variables (if with-y (1+ calc-curve-nvars) calc-curve-nvars) (1- (length calc-curve-coefnames)) (math-all-vars-but @@ -378,9 +378,9 @@ nil with-y) (let* ((coefs (math-all-vars-but calc-curve-model nil)) (vars nil) - (n (- - (length coefs) - calc-curve-nvars + (n (- + (length coefs) + calc-curve-nvars (if with-y 2 1))) p) (if (< n 0) @@ -388,12 +388,12 @@ (setq p (nthcdr n coefs)) (setq vars (cdr p)) (setcdr p nil) - (calc-get-fit-variables + (calc-get-fit-variables (if with-y (1+ calc-curve-nvars) calc-curve-nvars) (length coefs) vars coefs with-y))))) (if record-entry - (calc-record (list 'vec calc-curve-model + (calc-record (list 'vec calc-curve-model calc-curve-varnames calc-curve-coefnames) "modl")))) (t (beep)))) @@ -422,7 +422,7 @@ (calc-graph-set-styles nil nil) (calc-graph-point-style nil)) (setq plot (cdr (nth 1 plot))) - (setq plot + (setq plot (list 'intv 3 (math-sub @@ -1446,7 +1446,7 @@ ;;; Open Romberg method; "qromo" in section 4.4. ;; The variable math-ninteg-temp is local to math-ninteg-romberg, -;; but is used by math-ninteg-midpoint, which is used by +;; but is used by math-ninteg-midpoint, which is used by ;; math-ninteg-romberg. (defvar math-ninteg-temp) @@ -1564,7 +1564,7 @@ ;; The variables math-fit-first-var, math-fit-first-coef and ;; math-fit-new-coefs are local to math-general-fit, but are used by -;; calcFunc-fitvar, calcFunc-fitparam and calcFunc-fitdummy +;; calcFunc-fitvar, calcFunc-fitparam and calcFunc-fitdummy ;; (respectively), which are used by math-general-fit. (defvar math-fit-first-var) (defvar math-fit-first-coef) @@ -1903,7 +1903,7 @@ (function (lambda (x y) (string< (nth 1 x) (nth 1 y))))))) ;; The variables math-all-vars-vars (the vars for math-all-vars) and -;; math-all-vars-found are local to math-all-vars-in, but are used by +;; math-all-vars-found are local to math-all-vars-in, but are used by ;; math-all-vars-rec which is called by math-all-vars-in. (defvar math-all-vars-vars) (defvar math-all-vars-found) diff --git a/lisp/calc/calccomp.el b/lisp/calc/calccomp.el index 7c8013aa907..fe0a882cfb9 100644 --- a/lisp/calc/calccomp.el +++ b/lisp/calc/calccomp.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calc/calcsel2.el b/lisp/calc/calcsel2.el index 73497f049f1..2299cd3da2a 100644 --- a/lisp/calc/calcsel2.el +++ b/lisp/calc/calcsel2.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calculator.el b/lisp/calculator.el index 5366a9b9596..e5488b8ae1e 100644 --- a/lisp/calculator.el +++ b/lisp/calculator.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;;===================================================================== ;;; Commentary: diff --git a/lisp/calendar/appt.el b/lisp/calendar/appt.el index 08f1bf49788..2fc5040a756 100644 --- a/lisp/calendar/appt.el +++ b/lisp/calendar/appt.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calendar/cal-bahai.el b/lisp/calendar/cal-bahai.el index 6e624542cbf..e0b7f4a3c22 100644 --- a/lisp/calendar/cal-bahai.el +++ b/lisp/calendar/cal-bahai.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calendar/cal-china.el b/lisp/calendar/cal-china.el index 72e7675a78c..5761e576817 100644 --- a/lisp/calendar/cal-china.el +++ b/lisp/calendar/cal-china.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calendar/cal-coptic.el b/lisp/calendar/cal-coptic.el index 920ec7d5ce0..bff0ade6547 100644 --- a/lisp/calendar/cal-coptic.el +++ b/lisp/calendar/cal-coptic.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calendar/cal-dst.el b/lisp/calendar/cal-dst.el index be709f5e1c9..6b55ea479f1 100644 --- a/lisp/calendar/cal-dst.el +++ b/lisp/calendar/cal-dst.el @@ -1,4 +1,4 @@ -;;; cal-dst.el --- calendar functions for daylight saving rules +;;; cal-dst.el --- calendar functions for daylight saving rules -*- lexical-binding:t -*- ;; Copyright (C) 1993-1996, 2001-2017 Free Software Foundation, Inc. @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -220,35 +220,36 @@ The result has the proper form for `calendar-daylight-savings-starts'." '((calendar-gregorian-from-absolute (calendar-persian-to-absolute `(7 1 ,(- year 621)))))))) (prevday-sec (- -1 utc-diff)) ; last sec of previous local day - (year (1+ y)) new-rules) - ;; Scan through the next few years until only one rule remains. - (while (cdr candidate-rules) - (dolist (rule candidate-rules) - ;; The rule we return should give a Gregorian date, but here - ;; we require an absolute date. The following is for efficiency. - (setq date (cond ((eq (car rule) 'calendar-nth-named-day) - (eval (cons 'calendar-nth-named-absday (cdr rule)))) - ((eq (car rule) 'calendar-gregorian-from-absolute) - (eval (cadr rule))) - (t (calendar-absolute-from-gregorian (eval rule))))) - (or (equal (current-time-zone - (calendar-time-from-absolute date prevday-sec)) - (current-time-zone - (calendar-time-from-absolute (1+ date) prevday-sec))) - (setq new-rules (cons rule new-rules)))) - ;; If no rules remain, just use the first candidate rule; - ;; it's wrong in general, but it's right for at least one year. - (setq candidate-rules (if new-rules (nreverse new-rules) - (list (car candidate-rules))) - new-rules nil - year (1+ year))) + (calendar-dlet* ((year (1+ y))) + ;; Scan through the next few years until only one rule remains. + (while (cdr candidate-rules) + (dolist (rule candidate-rules) + ;; The rule we return should give a Gregorian date, but here + ;; we require an absolute date. The following is for efficiency. + (setq date (cond ((eq (car rule) #'calendar-nth-named-day) + (eval (cons #'calendar-nth-named-absday + (cdr rule)))) + ((eq (car rule) #'calendar-gregorian-from-absolute) + (eval (cadr rule))) + (t (calendar-absolute-from-gregorian (eval rule))))) + (or (equal (current-time-zone + (calendar-time-from-absolute date prevday-sec)) + (current-time-zone + (calendar-time-from-absolute (1+ date) prevday-sec))) + (setq new-rules (cons rule new-rules)))) + ;; If no rules remain, just use the first candidate rule; + ;; it's wrong in general, but it's right for at least one year. + (setq candidate-rules (if new-rules (nreverse new-rules) + (list (car candidate-rules))) + new-rules nil + year (1+ year)))) (car candidate-rules))) ;; TODO it might be better to extract this information directly from ;; the system timezone database. But cross-platform...? ;; See thread -;; http://lists.gnu.org/archive/html/emacs-pretest-bug/2006-11/msg00060.html +;; https://lists.gnu.org/r/emacs-pretest-bug/2006-11/msg00060.html (defun calendar-dst-find-data (&optional time) "Find data on the first daylight saving time transitions after TIME. TIME defaults to `current-time'. Return value is as described @@ -405,8 +406,9 @@ This function respects the value of `calendar-dst-check-each-year-flag'." (or (let ((expr (if calendar-dst-check-each-year-flag (cadr (calendar-dst-find-startend year)) (nth 4 calendar-current-time-zone-cache)))) - (if expr (eval expr))) - ;; New US rules commencing 2007. ftp://elsie.nci.nih.gov/pub/. + (calendar-dlet* ((year year)) + (if expr (eval expr)))) + ;; New US rules commencing 2007. https://www.iana.org/time-zones (and (not (zerop calendar-daylight-time-offset)) (calendar-nth-named-day 2 0 3 year)))) @@ -416,8 +418,9 @@ This function respects the value of `calendar-dst-check-each-year-flag'." (or (let ((expr (if calendar-dst-check-each-year-flag (nth 2 (calendar-dst-find-startend year)) (nth 5 calendar-current-time-zone-cache)))) - (if expr (eval expr))) - ;; New US rules commencing 2007. ftp://elsie.nci.nih.gov/pub/. + (calendar-dlet* ((year year)) + (if expr (eval expr)))) + ;; New US rules commencing 2007. https://www.iana.org/time-zones (and (not (zerop calendar-daylight-time-offset)) (calendar-nth-named-day 1 0 11 year)))) @@ -425,25 +428,25 @@ This function respects the value of `calendar-dst-check-each-year-flag'." (defun dst-in-effect (date) "True if on absolute DATE daylight saving time is in effect. Fractional part of DATE is local standard time of day." - (let* ((year (calendar-extract-year - (calendar-gregorian-from-absolute (floor date)))) - (dst-starts-gregorian (eval calendar-daylight-savings-starts)) - (dst-ends-gregorian (eval calendar-daylight-savings-ends)) - (dst-starts (and dst-starts-gregorian + (calendar-dlet* ((year (calendar-extract-year + (calendar-gregorian-from-absolute (floor date))))) + (let* ((dst-starts-gregorian (eval calendar-daylight-savings-starts)) + (dst-ends-gregorian (eval calendar-daylight-savings-ends)) + (dst-starts (and dst-starts-gregorian + (+ (calendar-absolute-from-gregorian + dst-starts-gregorian) + (/ calendar-daylight-savings-starts-time + 60.0 24.0)))) + (dst-ends (and dst-ends-gregorian (+ (calendar-absolute-from-gregorian - dst-starts-gregorian) - (/ calendar-daylight-savings-starts-time - 60.0 24.0)))) - (dst-ends (and dst-ends-gregorian - (+ (calendar-absolute-from-gregorian - dst-ends-gregorian) - (/ (- calendar-daylight-savings-ends-time - calendar-daylight-time-offset) - 60.0 24.0))))) - (and dst-starts dst-ends - (if (< dst-starts dst-ends) - (and (<= dst-starts date) (< date dst-ends)) - (or (<= dst-starts date) (< date dst-ends)))))) + dst-ends-gregorian) + (/ (- calendar-daylight-savings-ends-time + calendar-daylight-time-offset) + 60.0 24.0))))) + (and dst-starts dst-ends + (if (< dst-starts dst-ends) + (and (<= dst-starts date) (< date dst-ends)) + (or (<= dst-starts date) (< date dst-ends))))))) ;; used by calc, lunar, solar. (defun dst-adjust-time (date time) diff --git a/lisp/calendar/cal-french.el b/lisp/calendar/cal-french.el index 3ecd90a86e0..ede38217ee6 100644 --- a/lisp/calendar/cal-french.el +++ b/lisp/calendar/cal-french.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calendar/cal-hebrew.el b/lisp/calendar/cal-hebrew.el index 15de7cde032..ba18b92ff9d 100644 --- a/lisp/calendar/cal-hebrew.el +++ b/lisp/calendar/cal-hebrew.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calendar/cal-html.el b/lisp/calendar/cal-html.el index f002133900e..41463cfc94a 100644 --- a/lisp/calendar/cal-html.el +++ b/lisp/calendar/cal-html.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calendar/cal-islam.el b/lisp/calendar/cal-islam.el index c6478937800..3650db493cf 100644 --- a/lisp/calendar/cal-islam.el +++ b/lisp/calendar/cal-islam.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calendar/cal-iso.el b/lisp/calendar/cal-iso.el index c802c848e01..427fc22b8e5 100644 --- a/lisp/calendar/cal-iso.el +++ b/lisp/calendar/cal-iso.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calendar/cal-julian.el b/lisp/calendar/cal-julian.el index 40887b41712..2ad3017d625 100644 --- a/lisp/calendar/cal-julian.el +++ b/lisp/calendar/cal-julian.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calendar/cal-mayan.el b/lisp/calendar/cal-mayan.el index 2363cf535b6..8f3a4a4a5a5 100644 --- a/lisp/calendar/cal-mayan.el +++ b/lisp/calendar/cal-mayan.el @@ -23,7 +23,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calendar/cal-menu.el b/lisp/calendar/cal-menu.el index 1039b49591e..b2079797b6c 100644 --- a/lisp/calendar/cal-menu.el +++ b/lisp/calendar/cal-menu.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -100,7 +100,7 @@ ;; Show 11 years--5 before, 5 after year of middle month. ;; We used to use :suffix rather than :label and bumped into ;; an easymenu bug: - ;; http://lists.gnu.org/archive/html/emacs-devel/2007-11/msg01813.html + ;; https://lists.gnu.org/r/emacs-devel/2007-11/msg01813.html ;; The bug has since been fixed. (dotimes (i 11) (push (vector (format "hol-year-%d" i) diff --git a/lisp/calendar/cal-move.el b/lisp/calendar/cal-move.el index a845348b964..0ed5dc0bfb5 100644 --- a/lisp/calendar/cal-move.el +++ b/lisp/calendar/cal-move.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calendar/cal-persia.el b/lisp/calendar/cal-persia.el index 571b397828b..3365ae71a00 100644 --- a/lisp/calendar/cal-persia.el +++ b/lisp/calendar/cal-persia.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calendar/cal-tex.el b/lisp/calendar/cal-tex.el index 1ea10bf9d70..1d295606f23 100644 --- a/lisp/calendar/cal-tex.el +++ b/lisp/calendar/cal-tex.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -259,12 +259,33 @@ This definition is the heart of the calendar!") (defun cal-tex-preamble (&optional args) "Insert the LaTeX calendar preamble into `cal-tex-buffer'. Preamble includes initial definitions for various LaTeX commands. -Optional string ARGS are included as options for the article document class." +Optional string ARGS are included as options for the article +document class with inclusion of default values \"12pt\" for +size, and \"a4paper\" for paper unless size or paper are already +specified in ARGS. When ARGS is omitted, by default the option +\"12pt,a4paper\" is passed. When ARGS has any other value, then +no option is passed to the class. + +Insert the \"\\usepackage{geometry}\" directive when ARGS +contains the \"landscape\" string." (set-buffer (generate-new-buffer cal-tex-buffer)) - (insert (format "\\documentclass%s{article}\n" - (if (stringp args) - (format "[%s]" args) - ""))) + (save-match-data + (insert (format "\\documentclass%s{article}\n" + (cond + ((stringp args) + ;; set default size + (unless (string-match "\\(^\\|,\\) *[0-9]+pt *\\(,\\|$\\)" args) + (setq args (concat args ",12pt"))) + ;; set default paper + (unless (string-match "\\(^\\|,\\) *\\([ab][4-5]\\|le\\(tter\\|gal\\)\\|executive\\)paper *\\(,\\|$\\)" args) + (setq args (concat args ",a4paper"))) + (when (string= (substring args 0 1) ",") + (setq args (substring args 1))) + (if (string= args "") "" (format "[%s]" args))) + ((null args) "[12pt]") + (t "")))) + (if (and (stringp args) (string-match "\\<landscape\\>" args)) + (insert "\\usepackage{geometry}\n"))) (if (stringp cal-tex-preamble-extra) (insert cal-tex-preamble-extra "\n")) ;; FIXME boxwidth and boxheight unused? @@ -320,7 +341,7 @@ Optional EVENT indicates a buffer position to use instead of point." There are four rows of three months each, unless optional LANDSCAPE is non-nil, in which case the calendar is printed in landscape mode with three rows of four months each." - (cal-tex-insert-preamble 1 landscape "12pt") + (cal-tex-insert-preamble 1 (and landscape "landscape")) (if landscape (cal-tex-vspace "-.6cm") (cal-tex-vspace "-3.1cm")) @@ -476,7 +497,7 @@ Optional EVENT indicates a buffer position to use instead of point." (diary-list (if cal-tex-diary (cal-tex-list-diary-entries d1 d2))) (holidays (if cal-tex-holidays (holiday-in-range d1 d2))) other-month other-year small-months-at-start) - (cal-tex-insert-preamble (cal-tex-number-weeks month year 1) t "12pt") + (cal-tex-insert-preamble (cal-tex-number-weeks month year 1) "landscape") (cal-tex-cmd cal-tex-cal-one-month) (dotimes (i n) (setq other-month month @@ -515,7 +536,7 @@ Optional EVENT indicates a buffer position to use instead of point." (calendar-increment-month month year 1) (cal-tex-vspace "-2cm") (cal-tex-insert-preamble - (cal-tex-number-weeks month year 1) t "12pt" t)))) + (cal-tex-number-weeks month year 1) "landscape" t)))) (cal-tex-end-document) (run-hooks 'cal-tex-hook)) @@ -545,7 +566,7 @@ indicates a buffer position to use instead of point." end-year)))) (diary-list (if cal-tex-diary (cal-tex-list-diary-entries d1 d2))) (holidays (if cal-tex-holidays (holiday-in-range d1 d2)))) - (cal-tex-insert-preamble (cal-tex-number-weeks month year n) nil "12pt") + (cal-tex-insert-preamble (cal-tex-number-weeks month year n)) (if (> n 1) (cal-tex-cmd cal-tex-cal-multi-month) (cal-tex-cmd cal-tex-cal-one-month)) @@ -1615,24 +1636,27 @@ informative header, and run HOOK." \t\tM-x tex-buffer RET \t\tM-x tex-print RET"))) -(defun cal-tex-insert-preamble (weeks landscape size &optional append) +(defun cal-tex-insert-preamble (weeks &optional class-options append) "Initialize the output LaTeX calendar buffer, `cal-tex-buffer'. Select the output buffer, and insert the preamble for a calendar -of WEEKS weeks. Insert code for landscape mode if LANDSCAPE is -non-nil. Use point-size SIZE. Optional argument APPEND, if -non-nil, means add to end of buffer without erasing current contents." - (let ((width "18cm") +of WEEKS weeks. Pass string CLASS-OPTIONS as options for the +article document class. If it contains \"landscape\", use the +geometry package to produce landscape format. Optional argument +APPEND, if non-nil, means add to end of buffer without erasing +current contents." + (let ((landscape (and class-options + (string-match "\\<landscape\\>" class-options))) + (width "18cm") (height "24cm")) (when landscape - (setq width "24cm" - height "18cm")) + (let ((swap width)) + (setq width height height swap))) (unless append - (cal-tex-preamble size) + (cal-tex-preamble class-options) (if (not landscape) (progn (cal-tex-cmd "\\oddsidemargin -1.75cm") (cal-tex-cmd "\\def\\holidaymult" ".06")) - (cal-tex-cmd "\\special" "landscape") (cal-tex-cmd "\\textwidth 9.5in") (cal-tex-cmd "\\textheight 7in") (cal-tex-comment) diff --git a/lisp/calendar/cal-x.el b/lisp/calendar/cal-x.el index f7ca3695a07..aca9d1c510e 100644 --- a/lisp/calendar/cal-x.el +++ b/lisp/calendar/cal-x.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calendar/calendar.el b/lisp/calendar/calendar.el index 84282209ddd..76b077ba95c 100644 --- a/lisp/calendar/calendar.el +++ b/lisp/calendar/calendar.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -115,6 +115,37 @@ (load "cal-loaddefs" nil t) +;; Calendar has historically relied heavily on dynamic scoping. +;; Concretely, this manifests in the use of references to let-bound variables +;; in Custom vars as well as code in diary files. +;; `eval` is hence the core of the culprit. It's used on: +;; - calendar-date-display-form +;; - calendar-time-display-form +;; - calendar-chinese-time-zone +;; - in cal-dst's there are various calls to `eval' but they seem not to refer +;; to let-bound variables, surprisingly. +;; - calendar-date-echo-text +;; - calendar-mode-line-format +;; - cal-tex-daily-string +;; - diary-date-forms +;; - diary-remind-message +;; - calendar-holidays +;; - calendar-location-name +;; - whatever is passed to calendar-string-spread +;; - whatever is passed to calendar-insert-at-column +;; - whatever is passed to diary-sexp-entry +;; - whatever is passed to diary-remind + +(defmacro calendar-dlet* (binders &rest body) + "Like `let*' but using dynamic scoping." + (declare (indent 1) (debug let)) + `(progn + (with-no-warnings ;Silence "lacks a prefix" warnings! + ,@(mapcar (lambda (binder) + `(defvar ,(if (consp binder) (car binder) binder))) + binders)) + (let* ,binders ,@body))) + ;; Avoid recursive load of calendar when loading cal-menu. Yuck. (provide 'calendar) (require 'cal-menu) @@ -835,7 +866,7 @@ For examples of three common styles, see `diary-american-date-forms', diary-american-date-forms) :initialize 'custom-initialize-default :set (lambda (symbol value) - (unless (equal value (eval symbol)) + (unless (equal value (default-value symbol)) (custom-set-default symbol value) (setq diary-font-lock-keywords (diary-font-lock-keywords)) ;; Need to redraw not just to get new font-locking, but also diff --git a/lisp/calendar/diary-lib.el b/lisp/calendar/diary-lib.el index 4ee6719d326..4e7cbb313db 100644 --- a/lisp/calendar/diary-lib.el +++ b/lisp/calendar/diary-lib.el @@ -1,4 +1,4 @@ -;;; diary-lib.el --- diary functions +;;; diary-lib.el --- diary functions -*- lexical-binding:t -*- ;; Copyright (C) 1989-1990, 1992-1995, 2001-2017 Free Software ;; Foundation, Inc. @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -119,7 +119,7 @@ are: `string', `symbol', `int', `tnil', `stringtnil.'" :type 'boolean :group 'diary) -(defcustom diary-file-name-prefix-function 'identity +(defcustom diary-file-name-prefix-function #'identity "The function that will take a diary file name and return the desired prefix." :type 'function :group 'diary) @@ -156,7 +156,7 @@ Used for example by the appointment package - see `appt-activate'." :type 'hook :group 'diary) -(defcustom diary-display-function 'diary-fancy-display +(defcustom diary-display-function #'diary-fancy-display "Function used to display the diary. The two standard options are `diary-fancy-display' and `diary-simple-display'. @@ -185,9 +185,9 @@ diary buffer to be displayed with diary entries from various included files, each day's entries sorted into lexicographic order, add the following to your init file: - (setq diary-display-function \\='diary-fancy-display) - (add-hook \\='diary-list-entries-hook \\='diary-include-other-diary-files) - (add-hook \\='diary-list-entries-hook \\='diary-sort-entries t) + (setq diary-display-function #\\='diary-fancy-display) + (add-hook \\='diary-list-entries-hook #\\='diary-include-other-diary-files) + (add-hook \\='diary-list-entries-hook #\\='diary-sort-entries t) Note how the sort function is placed last, so that it can sort the entries included from other files. @@ -251,7 +251,7 @@ use `diary-mark-entries-hook', which runs only for the main diary file." diary-islamic-mark-entries) :group 'diary) -(defcustom diary-print-entries-hook 'lpr-buffer +(defcustom diary-print-entries-hook #'lpr-buffer "Run by `diary-print-entries' after preparing a temporary diary buffer. The buffer shows only the diary entries currently visible in the diary buffer. The default just does the printing. Other uses @@ -328,7 +328,8 @@ Returns a string using match elements 1-5, where: ;; use the standard function calendar-date-string. (concat (if month (calendar-date-string (list month (string-to-number day) - (string-to-number year)) nil t) + (string-to-number year)) + nil t) (cond ((eq calendar-date-style 'iso) "\\3 \\1 \\2") ; YMD ((eq calendar-date-style 'european) "\\2 \\1 \\3") ; DMY (t "\\1 \\2 \\3"))) ; MDY @@ -425,7 +426,7 @@ Only used if `diary-header-line-flag' is non-nil." ;; display does not create the fancy buffer, nor does it set ;; diary-selective-display in the diary buffer. This means some ;; customizations will not take effect, eg: -;; http://lists.gnu.org/archive/html/emacs-pretest-bug/2007-03/msg00466.html +;; https://lists.gnu.org/r/emacs-pretest-bug/2007-03/msg00466.html ;; So the check for diary-selective-display was dropped. This means the ;; diary will be displayed if one customizes a diary variable while ;; just visiting the diary-file. This is i) unlikely, and ii) no great loss. @@ -552,42 +553,40 @@ If ENTRY is a string, search for matches in that string, and remove them. Returns a list of ENTRY followed by (ATTRIBUTE VALUE) pairs. When ENTRY is non-nil, FILEGLOBATTRS forms the start of the (ATTRIBUTE VALUE) pairs." - (let (regexp regnum attrname attrname attrvalue type ret-attr) + (let (ret-attr) (if (null entry) (save-excursion (dolist (attr diary-face-attrs) ;; FIXME inefficient searching. (goto-char (point-min)) - (setq regexp (concat diary-glob-file-regexp-prefix (car attr)) - regnum (cadr attr) - attrname (nth 2 attr) - type (nth 3 attr) - attrvalue (if (re-search-forward regexp nil t) - (match-string-no-properties regnum))) - (and attrvalue - (setq attrvalue (diary-attrtype-convert attrvalue type)) - (setq ret-attr (append ret-attr - (list attrname attrvalue)))))) + (let* ((regexp (concat diary-glob-file-regexp-prefix (car attr))) + (regnum (cadr attr)) + (attrname (nth 2 attr)) + (type (nth 3 attr)) + (attrvalue (if (re-search-forward regexp nil t) + (match-string-no-properties regnum)))) + (and attrvalue + (setq attrvalue (diary-attrtype-convert attrvalue type)) + (setq ret-attr (append ret-attr + (list attrname attrvalue))))))) (setq ret-attr fileglobattrs) (dolist (attr diary-face-attrs) - (setq regexp (car attr) - regnum (cadr attr) - attrname (nth 2 attr) - type (nth 3 attr) - attrvalue nil) - ;; If multiple matches, replace all, use the last (which may - ;; be the first instance in the line, if the regexp is - ;; anchored with $). - (while (string-match regexp entry) - (setq attrvalue (match-string-no-properties regnum entry) - entry (replace-match "" t t entry))) - (and attrvalue - (setq attrvalue (diary-attrtype-convert attrvalue type)) - (setq ret-attr (append ret-attr (list attrname attrvalue)))))) + (let ((regexp (car attr)) + (regnum (cadr attr)) + (attrname (nth 2 attr)) + (type (nth 3 attr)) + (attrvalue nil)) + ;; If multiple matches, replace all, use the last (which may + ;; be the first instance in the line, if the regexp is + ;; anchored with $). + (while (string-match regexp entry) + (setq attrvalue (match-string-no-properties regnum entry) + entry (replace-match "" t t entry))) + (and attrvalue + (setq attrvalue (diary-attrtype-convert attrvalue type)) + (setq ret-attr (append ret-attr (list attrname attrvalue))))))) (list entry ret-attr))) - - (defvar diary-modify-entry-list-string-function nil "Function applied to entry string before putting it into the entries list. Can be used by programs integrating a diary list into other buffers (e.g. @@ -656,9 +655,12 @@ any entries were found." (let* ((month (calendar-extract-month date)) (day (calendar-extract-day date)) (year (calendar-extract-year date)) - (dayname (format "%s\\|%s\\.?" (calendar-day-name date) - (calendar-day-name date 'abbrev))) (calendar-month-name-array (or months calendar-month-name-array)) + (case-fold-search t) + entry-found) + (calendar-dlet* + ((dayname (format "%s\\|%s\\.?" (calendar-day-name date) + (calendar-day-name date 'abbrev))) (monthname (format "\\*\\|%s%s" (calendar-month-name month) (if months "" (format "\\|%s\\.?" @@ -668,61 +670,60 @@ any entries were found." (year (format "\\*\\|0*%d%s" year (if diary-abbreviated-year-flag (format "\\|%02d" (% year 100)) - ""))) - (case-fold-search t) - entry-found) - (dolist (date-form diary-date-forms) - (let ((backup (when (eq (car date-form) 'backup) - (setq date-form (cdr date-form)) - t)) - ;; date-form uses day etc as set above. - (regexp (format "^%s?%s\\(%s\\)" (regexp-quote mark) - (if symbol (regexp-quote symbol) "") - (mapconcat 'eval date-form "\\)\\(?:"))) - entry-start date-start temp) - (goto-char (point-min)) - (while (re-search-forward regexp nil t) - (if backup (re-search-backward "\\<" nil t)) - ;; regexp moves us past the end of date, onto the next line. - ;; Trailing whitespace after date not allowed (see diary-file). - (if (and (bolp) (not (looking-at "[ \t]"))) - ;; Diary entry that consists only of date. - (backward-char 1) - ;; Found a nonempty diary entry--make it - ;; visible and add it to the list. - (setq date-start (line-end-position 0)) - ;; Actual entry starts on the next-line? - (if (looking-at "[ \t]*\n[ \t]") (forward-line 1)) - (setq entry-found t - entry-start (point)) - (forward-line 1) - (while (looking-at "[ \t]") ; continued entry - (forward-line 1)) - (unless (and (eobp) (not (bolp))) - (backward-char 1)) - (unless list-only - (remove-overlays date-start (point) 'invisible 'diary)) - (setq temp (diary-pull-attrs - (buffer-substring-no-properties - entry-start (point)) globattr)) - (diary-add-to-list - (or gdate date) (car temp) - (buffer-substring-no-properties (1+ date-start) (1- entry-start)) - (copy-marker entry-start) (cadr temp)))))) - entry-found)) + "")))) + (dolist (date-form diary-date-forms) + (let ((backup (when (eq (car date-form) 'backup) + (setq date-form (cdr date-form)) + t)) + ;; date-form uses day etc as set above. + (regexp (format "^%s?%s\\(%s\\)" (regexp-quote mark) + (if symbol (regexp-quote symbol) "") + (mapconcat #'eval date-form "\\)\\(?:"))) + entry-start date-start temp) + (goto-char (point-min)) + (while (re-search-forward regexp nil t) + (if backup (re-search-backward "\\<" nil t)) + ;; regexp moves us past the end of date, onto the next line. + ;; Trailing whitespace after date not allowed (see diary-file). + (if (and (bolp) (not (looking-at "[ \t]"))) + ;; Diary entry that consists only of date. + (backward-char 1) + ;; Found a nonempty diary entry--make it + ;; visible and add it to the list. + (setq date-start (line-end-position 0)) + ;; Actual entry starts on the next-line? + (if (looking-at "[ \t]*\n[ \t]") (forward-line 1)) + (setq entry-found t + entry-start (point)) + (forward-line 1) + (while (looking-at "[ \t]") ; continued entry + (forward-line 1)) + (unless (and (eobp) (not (bolp))) + (backward-char 1)) + (unless list-only + (remove-overlays date-start (point) 'invisible 'diary)) + (setq temp (diary-pull-attrs + (buffer-substring-no-properties + entry-start (point)) + globattr)) + (diary-add-to-list + (or gdate date) (car temp) + (buffer-substring-no-properties + (1+ date-start) (1- entry-start)) + (copy-marker entry-start) (cadr temp)))))) + entry-found))) (defvar original-date) ; from diary-list-entries (defvar file-glob-attrs) -(defvar list-only) -(defvar number) (defun diary-list-entries-1 (months symbol absfunc) "List diary entries of a certain type. MONTHS is an array of month names. SYMBOL marks diary entries of the type in question. ABSFUNC is a function that converts absolute dates to dates of the appropriate type." + (with-no-warnings (defvar number) (defvar list-only)) (let ((gdate original-date)) - (dotimes (_idummy number) + (dotimes (_ number) (diary-list-entries-2 (funcall absfunc (calendar-absolute-from-gregorian gdate)) diary-nonmarking-symbol file-glob-attrs list-only months symbol gdate) @@ -735,6 +736,10 @@ of the appropriate type." "List of any diary files included in the last call to `diary-list-entries'. Or to `diary-mark-entries'.") +(defvar diary-saved-point) ; bound in diary-list-entries +(defvar diary-including) +(defvar date-string) ; bound in diary-list-entries + (defun diary-list-entries (date number &optional list-only) "Create and display a buffer containing the relevant lines in `diary-file'. Selects entries for NUMBER days starting with date DATE. Hides any @@ -814,8 +819,8 @@ LIST-ONLY is non-nil, in which case it just returns the list." ;; diary-header-line-flag after diary has been displayed ;; take effect. Unconditionally calling (diary-mode) ;; clobbers file local variables. - ;; http://lists.gnu.org/archive/html/emacs-pretest-bug/2007-03/msg00363.html - ;; http://lists.gnu.org/archive/html/emacs-pretest-bug/2007-04/msg00404.html + ;; https://lists.gnu.org/r/emacs-pretest-bug/2007-03/msg00363.html + ;; https://lists.gnu.org/r/emacs-pretest-bug/2007-04/msg00404.html (if (eq major-mode 'diary-mode) (setq header-line-format (and diary-header-line-flag diary-header-line-format))))) @@ -832,7 +837,7 @@ LIST-ONLY is non-nil, in which case it just returns the list." (set (make-local-variable 'diary-selective-display) t) (overlay-put ol 'invisible 'diary) (overlay-put ol 'evaporate t))) - (dotimes (_idummy number) + (dotimes (_ number) (let ((sexp-found (diary-list-sexp-entries date)) (entry-found (diary-list-entries-2 date diary-nonmarking-symbol @@ -848,8 +853,10 @@ LIST-ONLY is non-nil, in which case it just returns the list." ;; every time, diary-include-other-diary-files ;; binds it to nil (essentially) when it runs ;; in included files. - (run-hooks 'diary-nongregorian-listing-hook - 'diary-list-entries-hook) + (calendar-dlet* ((number number) + (list-only list-only)) + (run-hooks 'diary-nongregorian-listing-hook + 'diary-list-entries-hook)) ;; We could make this explicit: ;;; (run-hooks 'diary-nongregorian-listing-hook) ;;; (if d-incp @@ -878,8 +885,6 @@ LIST-ONLY is non-nil, in which case it just returns the list." (remove-overlays (point-min) (point-max) 'invisible 'diary)) (kill-local-variable 'mode-line-format)) -(defvar original-date) ; bound in diary-list-entries -;(defvar number) ; already declared above (defun diary-include-files (&optional mark) "Process diary entries from included diary files. @@ -894,8 +899,8 @@ This is recursive; that is, included files may include other files." (format "^%s \"\\([^\"]*\\)\"" (regexp-quote diary-include-string)) nil t) (let ((diary-file (match-string-no-properties 1)) - (diary-mark-entries-hook 'diary-mark-included-diary-files) - (diary-list-entries-hook 'diary-include-other-diary-files) + (diary-mark-entries-hook #'diary-mark-included-diary-files) + (diary-list-entries-hook #'diary-include-other-diary-files) (diary-including t) diary-hook diary-list-include-blanks efile) (if (file-exists-p diary-file) @@ -907,6 +912,13 @@ This is recursive; that is, included files may include other files." (append diary-included-files (list efile))) (if mark (diary-mark-entries) + ;; FIXME: `diary-include-files' can be run from + ;; diary-mark-entries-hook (via + ;; diary-mark-included-diary-files) or from + ;; diary-list-entries-hook (via + ;; diary-include-other-diary-files). In the "list" case, + ;; `number' is dynamically bound, but not in the "mark" case! + (with-no-warnings (defvar number)) (setq diary-entries-list (append diary-entries-list (diary-list-entries original-date number t))))) @@ -929,8 +941,6 @@ For details, see `diary-include-files'. See also `diary-mark-included-diary-files'." (diary-include-files)) -(defvar date-string) ; bound in diary-list-entries - (defun diary-display-no-entries () "Common subroutine of `diary-simple-display' and `diary-fancy-display'. Handles the case where there are no diary entries. @@ -940,7 +950,7 @@ Returns a cons (NOENTRIES . HOLIDAY-STRING)." (hol-string (format "%s%s%s" date-string (if holiday-list ": " "") - (mapconcat 'identity holiday-list "; "))) + (mapconcat #'identity holiday-list "; "))) (msg (format "No diary entries for %s" hol-string)) ;; Empty list, or single item with no text. ;; FIXME multiple items with no text? @@ -957,13 +967,11 @@ Returns a cons (NOENTRIES . HOLIDAY-STRING)." ;; holiday-list which is too wide for a message gets a buffer. (calendar-in-read-only-buffer holiday-buffer (calendar-set-mode-line (format "Holidays for %s" date-string)) - (insert (mapconcat 'identity holiday-list "\n"))) + (insert (mapconcat #'identity holiday-list "\n"))) (message "No diary entries for %s" date-string))) (cons noentries hol-string))) -(defvar diary-saved-point) ; bound in diary-list-entries - (defun diary-simple-display () "Display the diary buffer if there are any relevant entries or holidays. Entries that do not apply are made invisible. Holidays are shown @@ -987,7 +995,7 @@ in the mode line. This is an option for `diary-display-function'." (set-window-point window diary-saved-point) (set-window-start window (point-min))))))) -(defvar diary-goto-entry-function 'diary-goto-entry +(defvar diary-goto-entry-function #'diary-goto-entry "Function called to jump to a diary entry. Modes that require special handling of the included file containing the diary entry can assign a suitable function to this @@ -1022,6 +1030,9 @@ variable.") (goto-char (match-beginning 1))))) (message "Unable to locate this diary entry"))))) +(defvar displayed-year) ; bound in calendar-generate +(defvar displayed-month) + (defun diary-fancy-display () "Prepare a diary buffer with relevant entries in a fancy, noneditable form. Holidays are shown unless `diary-show-holidays-flag' is nil. @@ -1204,7 +1215,7 @@ ensure that all relevant variables are set. (interactive "P") (if (string-equal diary-mail-addr "") (user-error "You must set `diary-mail-addr' to use this command") - (let ((diary-display-function 'diary-fancy-display)) + (let ((diary-display-function #'diary-fancy-display)) (diary-list-entries (calendar-current-date) (or ndays diary-mail-days))) (compose-mail diary-mail-addr (concat "Diary entries generated " @@ -1242,109 +1253,111 @@ MARKFUNC is a function that marks entries of the appropriate type matching a given date pattern. MONTHS is an array of month names. SYMBOL marks diary entries of the type in question. ABSFUNC is a function that converts absolute dates to dates of the appropriate type. " - (let ((dayname (diary-name-pattern calendar-day-name-array - calendar-day-abbrev-array)) - (monthname (format "%s\\|\\*" - (if months - (diary-name-pattern months) - (diary-name-pattern calendar-month-name-array - calendar-month-abbrev-array)))) - (month "[0-9]+\\|\\*") - (day "[0-9]+\\|\\*") - (year "[0-9]+\\|\\*") - (case-fold-search t) - marks) - (dolist (date-form diary-date-forms) - (if (eq (car date-form) 'backup) ; ignore 'backup directive - (setq date-form (cdr date-form))) - (let* ((l (length date-form)) - (d-name-pos (- l (length (memq 'dayname date-form)))) - (d-name-pos (if (/= l d-name-pos) (1+ d-name-pos))) - (m-name-pos (- l (length (memq 'monthname date-form)))) - (m-name-pos (if (/= l m-name-pos) (1+ m-name-pos))) - (d-pos (- l (length (memq 'day date-form)))) - (d-pos (if (/= l d-pos) (1+ d-pos))) - (m-pos (- l (length (memq 'month date-form)))) - (m-pos (if (/= l m-pos) (1+ m-pos))) - (y-pos (- l (length (memq 'year date-form)))) - (y-pos (if (/= l y-pos) (1+ y-pos))) - (regexp (format "^%s\\(%s\\)" - (if symbol (regexp-quote symbol) "") - (mapconcat 'eval date-form "\\)\\(")))) - (goto-char (point-min)) - (while (re-search-forward regexp nil t) - (let* ((dd-name - (if d-name-pos - (match-string-no-properties d-name-pos))) - (mm-name - (if m-name-pos - (match-string-no-properties m-name-pos))) - (mm (string-to-number - (if m-pos - (match-string-no-properties m-pos) - ""))) - (dd (string-to-number - (if d-pos - (match-string-no-properties d-pos) - ""))) - (y-str (if y-pos - (match-string-no-properties y-pos))) - (yy (if (not y-str) - 0 - (if (and (= (length y-str) 2) - diary-abbreviated-year-flag) - (let* ((current-y - (calendar-extract-year - (if absfunc - (funcall - absfunc - (calendar-absolute-from-gregorian - (calendar-current-date))) - (calendar-current-date)))) - (y (+ (string-to-number y-str) - ;; Current century, eg 2000. - (* 100 (/ current-y 100)))) - (offset (- y current-y))) - ;; Add 2-digit year to current century. - ;; If more than 50 years in the future, - ;; assume last century. If more than 50 - ;; years in the past, assume next century. - (if (> offset 50) - (- y 100) - (if (< offset -50) - (+ y 100) - y))) - (string-to-number y-str))))) - (setq marks (cadr (diary-pull-attrs - (buffer-substring-no-properties - (point) (line-end-position)) - file-glob-attrs))) - ;; Only mark all days of a given name if the pattern - ;; contains no more specific elements. - (if (and dd-name (not (or d-pos m-pos y-pos))) - (calendar-mark-days-named - (cdr (assoc-string dd-name + (calendar-dlet* + ((dayname (diary-name-pattern calendar-day-name-array + calendar-day-abbrev-array)) + (monthname (format "%s\\|\\*" + (if months + (diary-name-pattern months) + (diary-name-pattern calendar-month-name-array + calendar-month-abbrev-array)))) + (month "[0-9]+\\|\\*") + (day "[0-9]+\\|\\*") + (year "[0-9]+\\|\\*")) + (let* ((case-fold-search t) + marks) + (dolist (date-form diary-date-forms) + (if (eq (car date-form) 'backup) ; ignore 'backup directive + (setq date-form (cdr date-form))) + (let* ((l (length date-form)) + (d-name-pos (- l (length (memq 'dayname date-form)))) + (d-name-pos (if (/= l d-name-pos) (1+ d-name-pos))) + (m-name-pos (- l (length (memq 'monthname date-form)))) + (m-name-pos (if (/= l m-name-pos) (1+ m-name-pos))) + (d-pos (- l (length (memq 'day date-form)))) + (d-pos (if (/= l d-pos) (1+ d-pos))) + (m-pos (- l (length (memq 'month date-form)))) + (m-pos (if (/= l m-pos) (1+ m-pos))) + (y-pos (- l (length (memq 'year date-form)))) + (y-pos (if (/= l y-pos) (1+ y-pos))) + (regexp (format "^%s\\(%s\\)" + (if symbol (regexp-quote symbol) "") + (mapconcat #'eval date-form "\\)\\(")))) + (goto-char (point-min)) + (while (re-search-forward regexp nil t) + (let* ((dd-name + (if d-name-pos + (match-string-no-properties d-name-pos))) + (mm-name + (if m-name-pos + (match-string-no-properties m-name-pos))) + (mm (string-to-number + (if m-pos + (match-string-no-properties m-pos) + ""))) + (dd (string-to-number + (if d-pos + (match-string-no-properties d-pos) + ""))) + (y-str (if y-pos + (match-string-no-properties y-pos))) + (yy (if (not y-str) + 0 + (if (and (= (length y-str) 2) + diary-abbreviated-year-flag) + (let* ((current-y + (calendar-extract-year + (if absfunc + (funcall + absfunc + (calendar-absolute-from-gregorian + (calendar-current-date))) + (calendar-current-date)))) + (y (+ (string-to-number y-str) + ;; Current century, eg 2000. + (* 100 (/ current-y 100)))) + (offset (- y current-y))) + ;; Add 2-digit year to current century. + ;; If more than 50 years in the future, + ;; assume last century. If more than 50 + ;; years in the past, assume next century. + (if (> offset 50) + (- y 100) + (if (< offset -50) + (+ y 100) + y))) + (string-to-number y-str))))) + (setq marks (cadr (diary-pull-attrs + (buffer-substring-no-properties + (point) (line-end-position)) + file-glob-attrs))) + ;; Only mark all days of a given name if the pattern + ;; contains no more specific elements. + (if (and dd-name (not (or d-pos m-pos y-pos))) + (calendar-mark-days-named + (cdr (assoc-string dd-name + (calendar-make-alist + calendar-day-name-array + 0 nil calendar-day-abbrev-array + (mapcar (lambda (e) + (format "%s." e)) + calendar-day-abbrev-array)) + t)) + marks) + (if mm-name + (setq mm + (if (string-equal mm-name "*") 0 + (cdr (assoc-string + mm-name + (if months (calendar-make-alist months) (calendar-make-alist - calendar-day-name-array - 0 nil calendar-day-abbrev-array + calendar-month-name-array + 1 nil calendar-month-abbrev-array (mapcar (lambda (e) (format "%s." e)) - calendar-day-abbrev-array)) - t)) marks) - (if mm-name - (setq mm - (if (string-equal mm-name "*") 0 - (cdr (assoc-string - mm-name - (if months (calendar-make-alist months) - (calendar-make-alist - calendar-month-name-array - 1 nil calendar-month-abbrev-array - (mapcar (lambda (e) - (format "%s." e)) - calendar-month-abbrev-array))) - t))))) - (funcall markfunc mm dd yy marks)))))))) + calendar-month-abbrev-array))) + t))))) + (funcall markfunc mm dd yy marks))))))))) ;;;###cal-autoload (defun diary-mark-entries (&optional redraw) @@ -1406,30 +1419,30 @@ marks. This is intended to deal with deleted diary entries." (defun diary-sexp-entry (sexp entry date) "Process a SEXP diary ENTRY for DATE." - (let ((result (if calendar-debug-sexp - (let ((debug-on-error t)) - (eval (car (read-from-string sexp)))) - (let (err) - (condition-case err - (eval (car (read-from-string sexp))) - (error - (display-warning - 'diary - (format "Bad diary sexp at line %d in %s:\n%s\n\ -Error: %s\n" - (count-lines (point-min) (point)) - diary-file sexp err) - :error) - nil)))))) + (let ((result + (calendar-dlet* ((date date) + (entry entry)) + (if calendar-debug-sexp + (let ((debug-on-error t)) + (eval (car (read-from-string sexp)))) + (condition-case err + (eval (car (read-from-string sexp))) + (error + (display-warning + 'diary + (format "Bad diary sexp at line %d in %s:\n%s\n\ +Error: %S\n" + (count-lines (point-min) (point)) + diary-file sexp err) + :error) + nil)))))) (cond ((stringp result) result) ((and (consp result) - (stringp (cdr result))) result) + (stringp (cdr result))) + result) (result entry) (t nil)))) -(defvar displayed-year) ; bound in calendar-generate -(defvar displayed-month) - (defun diary-mark-sexp-entries () "Mark days in the calendar window that have sexp diary entries. Each entry in the diary file (or included files) visible in the calendar window @@ -1532,7 +1545,7 @@ passed to `calendar-mark-visible-date' as MARK." (let ((m displayed-month) (y displayed-year)) (calendar-increment-month m y -1) - (dotimes (_idummy 3) + (dotimes (_ 3) (calendar-mark-month m y month day year color) (calendar-increment-month m y 1))))) @@ -1814,9 +1827,6 @@ form used internally by the calendar and diary." ;;; Sexp diary functions. -(defvar date) -(defvar entry) - ;; To be called from diary-sexp-entry, where DATE, ENTRY are bound. (defun diary-date (month day year &optional mark) "Specific date(s) diary entry. @@ -1827,6 +1837,7 @@ of the input parameters changes according to `calendar-date-style' An optional parameter MARK specifies a face or single-character string to use when highlighting the day in the calendar." + (with-no-warnings (defvar date) (defvar entry)) (let* ((ddate (diary-make-date month day year)) (dd (calendar-extract-day ddate)) (mm (calendar-extract-month ddate)) @@ -1855,6 +1866,7 @@ of the input parameters changes according to `calendar-date-style' An optional parameter MARK specifies a face or single-character string to use when highlighting the day in the calendar." + (with-no-warnings (defvar date) (defvar entry)) (let ((date1 (calendar-absolute-from-gregorian (diary-make-date m1 d1 y1))) (date2 (calendar-absolute-from-gregorian @@ -1873,6 +1885,7 @@ DAY defaults to 1 if N>0, and MONTH's last day otherwise. MONTH can be a list of months, an integer, or t (meaning all months). Optional MARK specifies a face or single-character string to use when highlighting the day in the calendar." + (with-no-warnings (defvar date) (defvar entry)) ;; This is messy because the diary entry may apply, but the date on which it ;; is based can be in a different month/year. For example, asking for the ;; first Monday after December 30. For large values of |n| the problem is @@ -1951,6 +1964,7 @@ is considered to be March 1 in non-leap years. An optional parameter MARK specifies a face or single-character string to use when highlighting the day in the calendar." + (with-no-warnings (defvar date) (defvar entry)) (let* ((ddate (diary-make-date month day year)) (dd (calendar-extract-day ddate)) (mm (calendar-extract-month ddate)) @@ -1975,6 +1989,7 @@ and %s by the ordinal ending of that number (that is, `st', `nd', An optional parameter MARK specifies a face or single-character string to use when highlighting the day in the calendar." + (with-no-warnings (defvar date) (defvar entry)) (or (> n 0) (user-error "Day count must be positive")) (let* ((diff (- (calendar-absolute-from-gregorian date) @@ -1986,6 +2001,7 @@ string to use when highlighting the day in the calendar." (defun diary-day-of-year () "Day of year and number of days remaining in the year of date diary entry." + (with-no-warnings (defvar date)) (calendar-day-of-year-string date)) (defun diary-remind (sexp days &optional marking) @@ -2007,11 +2023,12 @@ whether the entry itself is a marking or nonmarking; if optional parameter MARKING is non-nil then the reminders are marked on the calendar." ;; `date' has a value at this point, from diary-sexp-entry. + (with-no-warnings (defvar date)) ;; Convert a negative number to a list of days. (and (integerp days) (< days 0) (setq days (number-sequence 1 (- days)))) - (let ((diary-entry (eval sexp))) + (calendar-dlet* ((diary-entry (eval sexp))) (cond ;; Diary entry applies on date. ((and diary-entry @@ -2027,7 +2044,7 @@ calendar." (when (setq diary-entry (eval sexp)) ;; Discard any mark portion from diary-anniversary, etc. (if (consp diary-entry) (setq diary-entry (cdr diary-entry))) - (mapconcat 'eval diary-remind-message "")))) + (mapconcat #'eval diary-remind-message "")))) ;; Diary entry may apply to one of a list of days before date. ((and (listp days) days) (or (diary-remind sexp (car days) marking) @@ -2224,18 +2241,19 @@ If given, optional SYMBOL must be a prefix to entries. If optional ABBREV-ARRAY is present, also matches the abbreviations from this array (with or without a final `.'), in addition to the full month names." - (let ((dayname (diary-name-pattern calendar-day-name-array - calendar-day-abbrev-array t)) - (monthname (format "\\(%s\\|\\*\\)" - (diary-name-pattern month-array abbrev-array))) - (month "\\([0-9]+\\|\\*\\)") - (day "\\([0-9]+\\|\\*\\)") - (year "-?\\([0-9]+\\|\\*\\)")) + (calendar-dlet* + ((dayname (diary-name-pattern calendar-day-name-array + calendar-day-abbrev-array t)) + (monthname (format "\\(%s\\|\\*\\)" + (diary-name-pattern month-array abbrev-array))) + (month "\\([0-9]+\\|\\*\\)") + (day "\\([0-9]+\\|\\*\\)") + (year "-?\\([0-9]+\\|\\*\\)")) (mapcar (lambda (x) (cons (concat "^" (regexp-quote diary-nonmarking-symbol) "?" (if symbol (regexp-quote symbol) "") "\\(" - (mapconcat 'eval + (mapconcat #'eval ;; If backup, omit first item (backup) ;; and last item (not part of date). (if (equal (car x) 'backup) @@ -2312,7 +2330,7 @@ return a font-lock pattern matching array of MONTHS and marking SYMBOL." 'font-lock-constant-face) (cons (format "^%s?%s" (regexp-quote diary-nonmarking-symbol) - (regexp-opt (mapcar 'regexp-quote + (regexp-opt (mapcar #'regexp-quote (list diary-hebrew-entry-symbol diary-islamic-entry-symbol diary-bahai-entry-symbol @@ -2345,10 +2363,10 @@ return a font-lock pattern matching array of MONTHS and marking SYMBOL." (set (make-local-variable 'comment-start) diary-comment-start) (set (make-local-variable 'comment-end) diary-comment-end) (add-to-invisibility-spec '(diary . nil)) - (add-hook 'after-save-hook 'diary-redraw-calendar nil t) + (add-hook 'after-save-hook #'diary-redraw-calendar nil t) ;; In case the file was modified externally, refresh the calendar ;; after refreshing the diary buffer. - (add-hook 'after-revert-hook 'diary-redraw-calendar nil t) + (add-hook 'after-revert-hook #'diary-redraw-calendar nil t) (if diary-header-line-flag (setq header-line-format diary-header-line-format))) @@ -2359,18 +2377,19 @@ return a font-lock pattern matching array of MONTHS and marking SYMBOL." "Return a regexp matching the first line of a fancy diary date header. This depends on the calendar date style." (concat - (let ((dayname (diary-name-pattern calendar-day-name-array nil t)) - (monthname (diary-name-pattern calendar-month-name-array nil t)) - (day "1") - (month "2") - ;; FIXME? This used to be "-?[0-9]+" - what was the "-?" for? - (year "3")) + (calendar-dlet* + ((dayname (diary-name-pattern calendar-day-name-array nil t)) + (monthname (diary-name-pattern calendar-month-name-array nil t)) + (day "1") + (month "2") + ;; FIXME? This used to be "-?[0-9]+" - what was the "-?" for? + (year "3")) ;; This is ugly. c-d-d-form expects `day' etc to be "numbers in ;; string form"; eg the iso version calls string-to-number on some. ;; Therefore we cannot eg just let day = "[0-9]+". (Bug#8583). ;; Assumes no integers in c-day/month-name-array. (replace-regexp-in-string "[0-9]+" "[0-9]+" - (mapconcat 'eval calendar-date-display-form "") + (mapconcat #'eval calendar-date-display-form "") nil t)) ;; Optional ": holiday name" after the date. "\\(: .*\\)?")) @@ -2391,7 +2410,8 @@ This depends on the calendar date style." ("^Day.*omer.*$" . font-lock-builtin-face) ("^Parashat.*$" . font-lock-comment-face) (,(format "\\(^\\|\\s-\\)%s\\(-%s\\)?" diary-time-regexp - diary-time-regexp) . 'diary-time)) + diary-time-regexp) + . 'diary-time)) "Keywords to highlight in fancy diary display.") ;; If region looks like it might start or end in the middle of a diff --git a/lisp/calendar/holidays.el b/lisp/calendar/holidays.el index 5b51b16d223..0a80b79f442 100644 --- a/lisp/calendar/holidays.el +++ b/lisp/calendar/holidays.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calendar/icalendar.el b/lisp/calendar/icalendar.el index aa092b233ef..129cd6d9ad3 100644 --- a/lisp/calendar/icalendar.el +++ b/lisp/calendar/icalendar.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calendar/lunar.el b/lisp/calendar/lunar.el index 4d39b15aa03..9f7fad99f46 100644 --- a/lisp/calendar/lunar.el +++ b/lisp/calendar/lunar.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calendar/parse-time.el b/lisp/calendar/parse-time.el index b781cb0eb48..dc405b9d972 100644 --- a/lisp/calendar/parse-time.el +++ b/lisp/calendar/parse-time.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calendar/solar.el b/lisp/calendar/solar.el index f5cde3feac4..84e8bb3d259 100644 --- a/lisp/calendar/solar.el +++ b/lisp/calendar/solar.el @@ -1,4 +1,4 @@ -;;; solar.el --- calendar functions for solar events +;;; solar.el --- calendar functions for solar events -*- lexical-binding:t -*- ;; Copyright (C) 1992-1993, 1995, 1997, 2001-2017 Free Software ;; Foundation, Inc. @@ -23,7 +23,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -552,12 +552,14 @@ degrees to find out if polar regions have 24 hours of sun or only night." "Printable form for decimal fraction TIME in TIME-ZONE. Format used is given by `calendar-time-display-form'." (let* ((time (round (* 60 time))) - (24-hours (/ time 60)) + (24-hours (/ time 60))) + (calendar-dlet* + ((time-zone time-zone) (minutes (format "%02d" (% time 60))) (12-hours (format "%d" (1+ (% (+ 24-hours 11) 12)))) (am-pm (if (>= 24-hours 12) "pm" "am")) (24-hours (format "%02d" 24-hours))) - (mapconcat 'eval calendar-time-display-form ""))) + (mapconcat #'eval calendar-time-display-form "")))) (defun solar-daylight (time) "Printable form for TIME expressed in hours." @@ -661,10 +663,10 @@ Optional NOLOCATION non-nil means do not print the location." (format "%s, %s%s (%s hrs daylight)" (if (car l) - (concat "Sunrise " (apply 'solar-time-string (car l))) + (concat "Sunrise " (apply #'solar-time-string (car l))) "No sunrise") (if (cadr l) - (concat "sunset " (apply 'solar-time-string (cadr l))) + (concat "sunset " (apply #'solar-time-string (cadr l))) "no sunset") (if nolocation "" (format " at %s" (eval calendar-location-name))) @@ -749,7 +751,7 @@ The values of `calendar-daylight-savings-starts', (+ 4.9353929 (* 62833.1961680 U) (* 0.0000001 - (apply '+ + (apply #'+ (mapcar (lambda (x) (* (car x) (sin (mod @@ -889,13 +891,12 @@ Accurate to a few seconds." (insert (format "%s %2d: " (calendar-month-name month t) (1+ i)) (solar-sunrise-sunset-string date t) "\n"))))) -(defvar date) - -;; To be called from diary-list-sexp-entries, where DATE is bound. ;;;###diary-autoload (defun diary-sunrise-sunset () "Local time of sunrise and sunset as a diary entry. Accurate to a few seconds." + ;; To be called from diary-list-sexp-entries, where DATE is bound. + (with-no-warnings (defvar date)) (or (and calendar-latitude calendar-longitude calendar-time-zone) (solar-setup)) (solar-sunrise-sunset-string date)) @@ -938,7 +939,7 @@ Accurate to within a minute between 1951 and 2050." (W (- (* 35999.373 T) 2.47)) (Delta-lambda (+ 1 (* 0.0334 (solar-cosine-degrees W)) (* 0.0007 (solar-cosine-degrees (* 2 W))))) - (S (apply '+ (mapcar (lambda(x) + (S (apply #'+ (mapcar (lambda(x) (* (car x) (solar-cosine-degrees (+ (* (nth 2 x) T) (cadr x))))) solar-seasons-data))) diff --git a/lisp/calendar/time-date.el b/lisp/calendar/time-date.el index d9986231fdd..61722f61ea0 100644 --- a/lisp/calendar/time-date.el +++ b/lisp/calendar/time-date.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calendar/timeclock.el b/lisp/calendar/timeclock.el index a4709c3b4b5..a70e3ee416c 100644 --- a/lisp/calendar/timeclock.el +++ b/lisp/calendar/timeclock.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/calendar/todo-mode.el b/lisp/calendar/todo-mode.el index 7b27e7049d1..df3953f7a70 100644 --- a/lisp/calendar/todo-mode.el +++ b/lisp/calendar/todo-mode.el @@ -1,4 +1,4 @@ -;;; todo-mode.el --- facilities for making and maintaining todo lists +;;; todo-mode.el --- facilities for making and maintaining todo lists -*- lexical-binding:t -*- ;; Copyright (C) 1997, 1999, 2001-2017 Free Software Foundation, Inc. @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -72,14 +72,14 @@ file truenames in `todo-directory' with the extension \".todo\". With non-nil ARCHIVES return the list of archive file truenames (those with the extension \".toda\")." (let ((files (if (file-exists-p todo-directory) - (mapcar 'file-truename + (mapcar #'file-truename (directory-files todo-directory t - (if archives "\\.toda$" "\\.todo$") t))))) + (if archives "\\.toda\\'" "\\.todo\\'") t))))) (sort files (lambda (s1 s2) (let ((cis1 (upcase s1)) (cis2 (upcase s2))) (string< cis1 cis2)))))) -(defcustom todo-files-function 'todo-files +(defcustom todo-files-function #'todo-files "Function returning the value of the variable `todo-files'. This function should take an optional argument that, if non-nil, makes it return the value of the variable `todo-archives'." @@ -191,14 +191,15 @@ The final element is \"*\", indicating an unspecified month.") (defconst todo-date-pattern (let ((dayname (diary-name-pattern calendar-day-name-array nil t))) (concat "\\(?4:\\(?5:" dayname "\\)\\|" - (let ((dayname) - (monthname (format "\\(?6:%s\\)" (diary-name-pattern - todo-month-name-array - todo-month-abbrev-array))) - (month "\\(?7:[0-9]+\\|\\*\\)") - (day "\\(?8:[0-9]+\\|\\*\\)") - (year "-?\\(?9:[0-9]+\\|\\*\\)")) - (mapconcat 'eval calendar-date-display-form "")) + (calendar-dlet* + ((dayname) + (monthname (format "\\(?6:%s\\)" (diary-name-pattern + todo-month-name-array + todo-month-abbrev-array))) + (month "\\(?7:[0-9]+\\|\\*\\)") + (day "\\(?8:[0-9]+\\|\\*\\)") + (year "-?\\(?9:[0-9]+\\|\\*\\)")) + (mapconcat #'eval calendar-date-display-form "")) "\\)")) "Regular expression matching a todo item date header.") @@ -260,7 +261,7 @@ This function is the value of the user variable (let ((file (todo-short-file-name todo-current-todo-file))) (format "%s category %d: %s" file todo-category-number cat))) -(defcustom todo-mode-line-function 'todo-mode-line-control +(defcustom todo-mode-line-function #'todo-mode-line-control "Function that returns a mode line control for Todo mode buffers. The function expects one argument holding the name of the current todo category. The resulting control becomes the local value of @@ -555,13 +556,15 @@ This lacks the extension and directory components." (when (stringp file) (file-name-sans-extension (file-name-nondirectory file)))) +(defun todo--files-type-list () + (mapcar (lambda (f) (list 'const (todo-short-file-name f))) + (funcall todo-files-function))) + (defcustom todo-default-todo-file (todo-short-file-name (car (funcall todo-files-function))) "Todo file visited by first session invocation of `todo-show'." :type (when todo-files - `(radio ,@(mapcar (lambda (f) (list 'const f)) - (mapcar 'todo-short-file-name - (funcall todo-files-function))))) + `(radio ,@(todo--files-type-list))) :group 'todo) (defcustom todo-show-current-file t @@ -598,9 +601,7 @@ Otherwise, `todo-show' always visits `todo-default-todo-file'." (defcustom todo-category-completions-files nil "List of files for building `todo-read-category' completions." - :type `(set ,@(mapcar (lambda (f) (list 'const f)) - (mapcar 'todo-short-file-name - (funcall todo-files-function)))) + :type `(set ,@(todo--files-type-list)) :group 'todo) (defcustom todo-completion-ignore-case nil @@ -692,7 +693,8 @@ and done items are always shown on visiting a category." ;; We just initialized the first todo file, so make it the default. (setq todo-default-todo-file (todo-short-file-name file) first-file t) - (todo-reevaluate-default-file-defcustom)) + (put 'todo-default-todo-file 'custom-type + `(radio ,@(todo--files-type-list)))) (unless (member file todo-visited) ;; Can't setq t-c-t-f here, otherwise wrong file shown when ;; todo-show is called from todo-show-categories-table. @@ -707,11 +709,12 @@ and done items are always shown on visiting a category." (let ((rxfiles (directory-files todo-directory t ".*\\.todr$" t))) (when (and rxfiles (> (length rxfiles) 1)) - (let ((rxf (mapcar 'todo-short-file-name rxfiles))) + (let ((rxf (mapcar #'todo-short-file-name rxfiles))) (setq fi-file (todo-absolute-file-name (completing-read "Choose a regexp items file: " - rxf) 'regexp)))))) + rxf) + 'regexp)))))) (if (file-exists-p fi-file) (progn (set-window-buffer @@ -770,7 +773,8 @@ and done items are always shown on visiting a category." (when first-file (setq todo-default-todo-file nil todo-current-todo-file nil) - (todo-reevaluate-default-file-defcustom)) + (put 'todo-default-todo-file 'custom-type + `(radio ,@(todo--files-type-list)))) (kill-buffer) (keyboard-quit))))) (save-excursion (todo-category-select)) @@ -823,7 +827,7 @@ buries it and restores state as needed." (when (buffer-live-p buf) (kill-buffer buf))) ((eq major-mode 'todo-mode) (todo-save) - (bury-buffer))))) + (quit-window))))) ;; ----------------------------------------------------------------------------- ;;; Navigation between and within categories @@ -857,7 +861,7 @@ category is the first)." (zerop (todo-get-count 'done)) (not (zerop (todo-get-count 'archived)))) (setq todo-category-number - (apply (if back '1- '1+) (list todo-category-number))))) + (funcall (if back #'1- #'1+) todo-category-number)))) (todo-category-select) (goto-char (point-min))) @@ -944,7 +948,7 @@ called with a prefix argument only moves point to a lower item, e.g., with point on the last todo item and called with prefix 1, it moves point to the first done item; but if called with point on the last todo item without a prefix argument, it moves point -the the empty line above the done items separator." +to the empty line above the done items separator." (interactive "p") ;; It's not worth the trouble to allow prefix arg value < 1, since ;; we have the corresponding command. @@ -964,7 +968,7 @@ If the category's done items are visible, this command called with a prefix argument only moves point to a higher item, e.g., with point on the first done item and called with prefix 1, it moves to the last todo item; but if called with point on the -first done item without a prefix argument, it moves point the the +first done item without a prefix argument, it moves point to the empty line above the done items separator." (interactive "p") ;; Avoid moving to bob if on the first item but not at bob. @@ -1034,29 +1038,41 @@ empty line above the done items separator." (hl-line-mode -1) (hl-line-mode 1)))) +(defvar todo--item-headers-hidden nil + "Non-nil if item date-time headers in current buffer are hidden.") + (defun todo-toggle-item-header () "Hide or show item date-time headers in the current file. With done items, this hides only the done date-time string, not -the the original date-time string." +the original date-time string." (interactive) - (save-excursion - (save-restriction - (goto-char (point-min)) - (let ((ov (todo-get-overlay 'header))) - (if ov - (remove-overlays 1 (1+ (buffer-size)) 'todo 'header) - (widen) - (goto-char (point-min)) - (while (not (eobp)) - (when (re-search-forward - (concat todo-item-start - "\\( " diary-time-regexp "\\)?" - (regexp-quote todo-nondiary-end) "? ") - nil t) - (setq ov (make-overlay (match-beginning 0) (match-end 0) nil t)) - (overlay-put ov 'todo 'header) - (overlay-put ov 'display "")) - (todo-forward-item))))))) + (unless (catch 'nonempty + (dolist (type '(todo done)) + (dolist (c todo-categories) + (let ((count (todo-get-count type (car c)))) + (unless (zerop count) + (throw 'nonempty t)))))) + (user-error "This file has no items")) + (if todo--item-headers-hidden + (progn + (remove-overlays 1 (1+ (buffer-size)) 'todo 'header) + (setq todo--item-headers-hidden nil)) + (save-excursion + (save-restriction + (widen) + (goto-char (point-min)) + (let (ov) + (while (not (eobp)) + (when (re-search-forward + (concat todo-item-start + "\\( " diary-time-regexp "\\)?" + (regexp-quote todo-nondiary-end) "? ") + nil t) + (setq ov (make-overlay (match-beginning 0) (match-end 0) nil t)) + (overlay-put ov 'todo 'header) + (overlay-put ov 'display "")) + (forward-line))) + (setq todo--item-headers-hidden t))))) ;; ----------------------------------------------------------------------------- ;;; File and category editing @@ -1080,7 +1096,7 @@ Noninteractively, return the name of the new file." (write-region (point-min) (point-max) file nil 'nomessage nil t) (kill-buffer file)) (setq todo-files (funcall todo-files-function)) - (todo-reevaluate-filelist-defcustoms) + (todo-update-filelist-defcustoms) (if (called-interactively-p 'any) (progn (set-window-buffer (selected-window) @@ -1105,7 +1121,8 @@ these files, also rename them accordingly." (snname (todo-short-file-name nname)) (files (directory-files todo-directory t (concat ".*" (regexp-quote soname) - ".*\\.tod[aorty]$") t))) + ".*\\.tod[aorty]$") + t))) (dolist (f files) (let* ((sfname (todo-short-file-name f)) (fext (file-name-extension f t)) @@ -1133,7 +1150,7 @@ these files, also rename them accordingly." (setq todo-default-todo-file snname)) (when (string= todo-global-current-todo-file oname) (setq todo-global-current-todo-file nname)) - (todo-reevaluate-filelist-defcustoms))) + (todo-update-filelist-defcustoms))) (defun todo-delete-file () "Delete the current todo, archive or filtered items file. @@ -1194,7 +1211,7 @@ visiting the deleted files." (when (or (string= file1 todo-global-current-todo-file) (and delete2 (string= file2 todo-global-current-todo-file))) (setq todo-global-current-todo-file nil)) - (todo-reevaluate-filelist-defcustoms) + (todo-update-filelist-defcustoms) (message (concat (cond (todo "Todo") (archive "Archive")) " file \"%s\" " (when delete2 (concat "and its " @@ -1351,10 +1368,12 @@ todo or done items." (let ((buffer-read-only) (beg (re-search-backward (concat "^" (regexp-quote (concat todo-category-beg cat)) - "\n") nil t)) + "\n") + nil t)) (end (if (re-search-forward (concat "\n\\(" (regexp-quote todo-category-beg) - ".*\n\\)") nil t) + ".*\n\\)") + nil t) (match-beginning 1) (point-max)))) (remove-overlays beg end) @@ -1362,7 +1381,7 @@ todo or done items." (if (= (length todo-categories) 1) ;; If deleted category was the only one, delete the file. (progn - (todo-reevaluate-filelist-defcustoms) + (todo-update-filelist-defcustoms) ;; Skip confirming killing the archive buffer if it has been ;; modified and not saved. (set-buffer-modified-p nil) @@ -1405,7 +1424,7 @@ the archive of the file moved to, creating it if it does not exist." (write-region (point-min) (point-max) nfile nil 'nomessage nil t) (kill-buffer nfile)) (setq todo-files (funcall todo-files-function)) - (todo-reevaluate-filelist-defcustoms)) + (todo-update-filelist-defcustoms)) (dolist (buf buffers) ;; Make sure archive file is in Todo Archive mode so that ;; todo-categories has correct value. @@ -1463,7 +1482,8 @@ the archive of the file moved to, creating it if it does not exist." (goto-char (point-max)) (re-search-backward (concat "^" (regexp-quote todo-category-beg) - "\\(" (regexp-quote cat) "\\)$") nil t) + "\\(" (regexp-quote cat) "\\)$") + nil t) (replace-match new nil nil nil 1)) (setq todo-categories (append todo-categories (list (cons (or new cat) counts)))) @@ -1498,7 +1518,7 @@ the archive of the file moved to, creating it if it does not exist." (delete-file todo-current-todo-file) (kill-buffer) (when (member todo-current-todo-file todo-files) - (todo-reevaluate-filelist-defcustoms))) + (todo-update-filelist-defcustoms))) (setq todo-categories (delete (assoc cat todo-categories) todo-categories)) (todo-update-categories-sexp) @@ -1728,49 +1748,52 @@ means prompt user and omit comment only on confirmation." With positive numerical prefix argument N, change the marking of the next N items in the current category. If both the todo and done items sections are visible, the sequence of N items can -consist of the the last todo items and the first done items." +consist of the last todo items and the first done items." (interactive "p") (when (todo-item-string) - (unless (> n 1) (setq n 1)) - (catch 'end - (dotimes (i n) - (let* ((cat (todo-current-category)) - (marks (assoc cat todo-categories-with-marks)) - (ov (progn - (unless (looking-at todo-item-start) - (todo-item-start)) - (todo-get-overlay 'prefix))) - (pref (overlay-get ov 'before-string))) - (if (todo-marked-item-p) - (progn - (overlay-put ov 'before-string (substring pref 1)) - (if (= (cdr marks) 1) ; Deleted last mark in this category. - (setq todo-categories-with-marks - (assq-delete-all cat todo-categories-with-marks)) - (setcdr marks (1- (cdr marks))))) - (overlay-put ov 'before-string (concat todo-item-mark pref)) - (if marks - (setcdr marks (1+ (cdr marks))) - (push (cons cat 1) todo-categories-with-marks)))) - (todo-forward-item) - ;; Don't try to mark the empty lines at the end of the todo - ;; and done items sections. - (when (looking-at "^$") - (if (eobp) - (throw 'end nil) - (todo-forward-item))))))) + (let ((cat (todo-current-category))) + (unless (> n 1) (setq n 1)) + (catch 'end + (dotimes (_ n) + (let* ((marks (assoc cat todo-categories-with-marks)) + (ov (progn + (unless (looking-at todo-item-start) + (todo-item-start)) + (todo-get-overlay 'prefix))) + (pref (overlay-get ov 'before-string))) + (if (todo-marked-item-p) + (progn + (overlay-put ov 'before-string (substring pref 1)) + (if (= (cdr marks) 1) ; Deleted last mark in this category. + (setq todo-categories-with-marks + (assq-delete-all cat todo-categories-with-marks)) + (setcdr marks (1- (cdr marks))))) + (overlay-put ov 'before-string (concat todo-item-mark pref)) + (if marks + (setcdr marks (1+ (cdr marks))) + (push (cons cat 1) todo-categories-with-marks)))) + (todo-forward-item) + ;; Don't try to mark the empty lines at the end of the todo + ;; and done items sections. + (when (looking-at "^$") + (if (eobp) + (throw 'end nil) + (todo-forward-item)))))))) (defun todo-mark-category () "Mark all visible items in this category with `todo-item-mark'." (interactive) - (let* ((cat (todo-current-category)) - (marks (assoc cat todo-categories-with-marks))) + (let ((cat (todo-current-category))) (save-excursion (goto-char (point-min)) (while (not (eobp)) - (let* ((ov (todo-get-overlay 'prefix)) - (pref (overlay-get ov 'before-string))) - (unless (todo-marked-item-p) + (let* ((marks (assoc cat todo-categories-with-marks)) + (ov (todo-get-overlay 'prefix)) + ;; When done items are shown and there are no todo items, the + ;; loop starts on the empty line in the todo items sections, + ;; which has no overlay, so don't try to get it. + (pref (when ov (overlay-get ov 'before-string)))) + (unless (or (todo-marked-item-p) (not ov)) (overlay-put ov 'before-string (concat todo-item-mark pref)) (if marks (setcdr marks (1+ (cdr marks))) @@ -1791,7 +1814,7 @@ consist of the the last todo items and the first done items." (goto-char (point-min)) (while (not (eobp)) (let* ((ov (todo-get-overlay 'prefix)) - ;; No overlay on empty line between todo and done items. + ;; See comment above in `todo-mark-category'. (pref (when ov (overlay-get ov 'before-string)))) (when (todo-marked-item-p) (overlay-put ov 'before-string (substring pref 1))) @@ -2119,7 +2142,8 @@ the item at point." (todo-item-start) (re-search-forward (concat " \\[" (regexp-quote todo-comment-string) - ": \\([^]]+\\)\\]") end t))) + ": \\([^]]+\\)\\]") + end t))) (prompt (if comment "Edit comment: " "Enter a comment: ")) (buffer-read-only nil)) ;; When there are marked items, user can invoke todo-edit-item @@ -2135,7 +2159,8 @@ the item at point." (todo-item-start) (if (re-search-forward (concat " \\[" (regexp-quote todo-comment-string) - ": \\([^]]+\\)\\]") end t) + ": \\([^]]+\\)\\]") + end t) (if comment-delete (when (todo-y-or-n-p "Delete comment? ") (delete-region (match-beginning 0) (match-end 0))) @@ -2167,7 +2192,8 @@ the item at point." (cons item 0)))))) (when include-header (while (not (string-match (concat todo-date-string-start - todo-date-pattern) new)) + todo-date-pattern) + new)) (setq new (read-from-minibuffer "Item must start with a date: " new)))) ;; Ensure lines following hard newlines are indented. @@ -2196,7 +2222,8 @@ made in the number or names of categories." (regex "\\(\n\\)[^[:blank:]]") (buf (buffer-base-buffer))) (while (not (string-match (concat todo-date-string-start - todo-date-pattern) item)) + todo-date-pattern) + item)) (setq item (read-from-minibuffer "Item must start with a date: " item))) ;; Ensure lines following hard newlines are indented. @@ -2239,8 +2266,8 @@ made in the number or names of categories." ;; `todo-edit-item' as e.g. `-' or `C-u'. (inc (prefix-numeric-value inc)) (buffer-read-only nil) - ndate ntime year monthname month day - dayname) ; Needed by calendar-date-display-form. + ndate ntime + year monthname month day dayname) (when marked (todo--user-error-if-marked-done-item)) (save-excursion (or (and marked (goto-char (point-min))) (todo-item-start)) @@ -2255,8 +2282,7 @@ made in the number or names of categories." "\\)\\(?2: " diary-time-regexp "\\)?" (regexp-quote todo-nondiary-end) "?") (line-end-position) t) - (let* ((odate (match-string-no-properties 1)) - (otime (match-string-no-properties 2)) + (let* ((otime (match-string-no-properties 2)) (odayname (match-string-no-properties 5)) (omonthname (match-string-no-properties 6)) (omonth (match-string-no-properties 7)) @@ -2367,7 +2393,8 @@ made in the number or names of categories." (calendar-current-date)))) (date (calendar-gregorian-from-absolute (+ (calendar-absolute-from-gregorian - (list mm dd yy)) inc))) + (list mm dd yy)) + inc))) (adjmm (nth 0 date))) ;; Set year and month(name) to adjusted values. (unless (string= year "*") @@ -2381,7 +2408,15 @@ made in the number or names of categories." ;; If year, month or day date string components were ;; changed, rebuild the date string. (when (memq what '(year month day)) - (setq ndate (mapconcat 'eval calendar-date-display-form "")))) + (setq ndate + (calendar-dlet* + ;; Needed by calendar-date-display-form. + ((year year) + (monthname monthname) + (month month) + (day day) + (dayname dayname)) + (mapconcat #'eval calendar-date-display-form ""))))) (when ndate (replace-match ndate nil nil nil 1)) ;; Add new time string to the header, if it was supplied. (when ntime @@ -2408,7 +2443,7 @@ made in the number or names of categories." (when marked (goto-char (point-min))) (while (not (eobp)) (unless (and marked (not (todo-marked-item-p))) - (let* ((beg (todo-item-start)) + (let* ((_beg (todo-item-start)) (lim (save-excursion (todo-item-end))) (end (save-excursion (or (todo-time-string-matcher lim) @@ -2455,7 +2490,7 @@ items." (while (not (eobp)) (if (todo-done-item-p) ; We've gone too far. (throw 'stop nil) - (let* ((beg (todo-item-start)) + (let* ((_beg (todo-item-start)) (lim (save-excursion (todo-item-end))) (end (save-excursion (or (todo-time-string-matcher lim) @@ -2513,7 +2548,7 @@ numerical prefix argument, or noninteractively by argument ARG, whose value can be either of the symbols `raise' or `lower', meaning to raise or lower the item's priority by one." (interactive) - (unless (and (called-interactively-p 'any) + (unless (and (or (called-interactively-p 'any) (memq arg '(raise lower))) (or (todo-done-item-p) (looking-at "^$"))) (let* ((item (or item (todo-item-string))) (marked (todo-marked-item-p)) @@ -2530,7 +2565,7 @@ meaning to raise or lower the item's priority by one." (re-search-forward regexp1 nil t) (match-string-no-properties 1))))))) curnum - (todo (cond ((or (eq arg 'raise) (eq arg 'lower) + (todo (cond ((or (memq arg '(raise lower)) (eq major-mode 'todo-filtered-items-mode)) (save-excursion (let ((curstart (todo-item-start)) @@ -2667,10 +2702,8 @@ section in the category moved to." (not marked)) (let* ((buffer-read-only) (file1 todo-current-todo-file) - (num todo-category-number) (item (todo-item-string)) - (diary-item (todo-diary-item-p)) - (done-item (and (todo-done-item-p) (concat item "\n"))) + (done-item (and (todo-done-item-p) item)) (omark (save-excursion (todo-item-start) (point-marker))) (todo 0) (diary 0) @@ -2700,43 +2733,51 @@ section in the category moved to." (while (not (eobp)) (when (todo-marked-item-p) (if (todo-done-item-p) - (setq done-items (concat done-items - (todo-item-string) "\n") - done (1+ done)) - (setq todo-items (concat todo-items - (todo-item-string) "\n") - todo (1+ todo)) + (progn + (push (todo-item-string) done-items) + (setq done (1+ done))) + (push (todo-item-string) todo-items) + (setq todo (1+ todo)) (when (todo-diary-item-p) (setq diary (1+ diary))))) (todo-forward-item)) - ;; Chop off last newline of multiple todo item string, - ;; since it will be reinserted when setting priority - ;; (but with done items priority is not set, so keep - ;; last newline). - (and todo-items - (setq todo-items (substring todo-items 0 -1)))) + (setq todo-items (nreverse todo-items)) + (setq done-items (nreverse done-items))) (if (todo-done-item-p) - (setq done 1) - (setq todo 1) + (progn + (push done-item done-items) + (setq done 1)) + (push item todo-items) + (setq todo 1) (when (todo-diary-item-p) (setq diary 1)))) (set-window-buffer (selected-window) (set-buffer (find-file-noselect file2 'nowarn))) (unwind-protect - (progn - (when (or todo-items (and item (not done-item))) - (todo-set-item-priority (or todo-items item) cat2 t)) + (let (here) + (when todo-items + (todo-set-item-priority (pop todo-items) cat2 t) + (setq here (point)) + (while todo-items + (todo-forward-item) + (todo-insert-with-overlays (pop todo-items)))) ;; Move done items en bloc to top of done items section. - (when (or done-items done-item) + (when done-items (todo-category-number cat2) (widen) (goto-char (point-min)) (re-search-forward - (concat "^" (regexp-quote (concat todo-category-beg cat2)) - "$") nil t) + (concat "^" (regexp-quote (concat todo-category-beg cat2)) "$") + nil t) (re-search-forward (concat "^" (regexp-quote todo-category-done)) nil t) (forward-line) - (insert (or done-items done-item))) + (unless here (setq here (point))) + (while done-items + (todo-insert-with-overlays (pop done-items)) + (todo-forward-item))) + ;; If only done items were moved, move point to the top + ;; one, otherwise, move point to the top moved todo item. + (goto-char here) (setq moved t)) (cond ;; Move succeeded, so remove item from starting category, @@ -2761,10 +2802,13 @@ section in the category moved to." (forward-line) (setq beg (point)) (setq end (if (re-search-forward - (concat "^" (regexp-quote - todo-category-beg)) nil t) - (match-beginning 0) - (point-max))) + (concat "^" + (regexp-quote todo-category-beg)) + nil t) + (progn + (goto-char (match-beginning 0)) + (point-marker)) + (point-max-marker))) (goto-char beg) (while (< (point) end) (if (todo-marked-item-p) @@ -2781,7 +2825,7 @@ section in the category moved to." (set-window-buffer (selected-window) (set-buffer (find-file-noselect file2 'nowarn))) (setq todo-category-number (todo-category-number cat2)) - (let ((todo-show-with-done (or done-items done-item))) + (let ((todo-show-with-done (> done 0))) (todo-category-select)) (goto-char nmark) ;; If item is moved to end of (just first?) category, make @@ -2830,12 +2874,13 @@ visible." (goto-char (point-min)) (re-search-forward todo-done-string-start nil t))) (buffer-read-only nil) - item done-item + header item done-items (opoint (point))) ;; Don't add empty comment to done item. (setq comment (unless (zerop (length comment)) (concat " [" todo-comment-string ": " comment "]"))) (and marked (goto-char (point-min))) + (setq header (todo-get-overlay 'header)) (catch 'done ;; Stop looping when we hit the empty line below the last ;; todo item (this is eobp if only done items are hidden). @@ -2843,17 +2888,15 @@ visible." (if (or (not marked) (and marked (todo-marked-item-p))) (progn (setq item (todo-item-string)) - (setq done-item (concat done-item done-prefix item - comment (and marked "\n"))) + (push (concat done-prefix item comment) done-items) (setq item-count (1+ item-count)) (when (todo-diary-item-p) (setq diary-count (1+ diary-count))) (todo-remove-item) (unless marked (throw 'done nil))) (todo-forward-item)))) + (setq done-items (nreverse done-items)) (when marked - ;; Chop off last newline of done item string. - (setq done-item (substring done-item 0 -1)) (setq todo-categories-with-marks (assq-delete-all cat todo-categories-with-marks))) (save-excursion @@ -2862,7 +2905,17 @@ visible." (concat "^" (regexp-quote todo-category-done)) nil t) (forward-char) (when show-done (setq opoint (point))) - (insert done-item "\n")) + (while done-items + (insert (pop done-items) "\n") + (when header (let ((copy (copy-overlay header))) + (re-search-backward + (concat todo-item-start + "\\( " diary-time-regexp "\\)?" + (regexp-quote todo-nondiary-end) "? ") + nil t) + (move-overlay copy (match-beginning 0) (match-end 0))) + (todo-item-end) + (forward-char)))) (todo-update-count 'todo (- item-count)) (todo-update-count 'done item-count) (todo-update-count 'diary (- diary-count)) @@ -2921,7 +2974,8 @@ comments without asking." ;; affirmed, omit subsequent comments without asking. (when (re-search-forward (concat " \\[" (regexp-quote todo-comment-string) - ": [^]]+\\]") end t) + ": [^]]+\\]") + end t) (unwind-protect (if (eq first 'first) (setq first @@ -3089,7 +3143,9 @@ this category does not exist in the archive, it is created." (throw 'end (message "Only done items can be archived")) (with-current-buffer archive (unless (derived-mode-p 'todo-archive-mode) (todo-archive-mode)) - (let (buffer-read-only) + (let ((headers-hidden todo--item-headers-hidden) + buffer-read-only) + (if headers-hidden (todo-toggle-item-header)) (widen) (goto-char (point-min)) (if (and (re-search-forward @@ -3115,7 +3171,8 @@ this category does not exist in the archive, it is created." (unless (nth 7 (file-attributes afile)) (write-region nil nil afile t t) (setq todo-archives (funcall todo-files-function t)) - (todo-archive-mode)))) + (todo-archive-mode)) + (if headers-hidden (todo-toggle-item-header)))) (with-current-buffer tbuf (cond (all @@ -3178,7 +3235,8 @@ the only category in the archive, the archive file is deleted." (let* ((cat (todo-current-category)) (tbuf (find-file-noselect (concat (file-name-sans-extension todo-current-todo-file) - ".todo") t)) + ".todo") + t)) (marked (assoc cat todo-categories-with-marks)) (item (concat (todo-item-string) "\n")) (marked-count 0) @@ -3194,14 +3252,17 @@ the only category in the archive, the archive file is deleted." (todo-forward-item)))) ;; Restore items to top of category's done section and update counts. (with-current-buffer tbuf - (let (buffer-read-only newcat) + (let ((headers-hidden todo--item-headers-hidden) + buffer-read-only newcat) + (if headers-hidden (todo-toggle-item-header)) (widen) (goto-char (point-min)) ;; Find the corresponding todo category, or if there isn't ;; one, add it. (unless (re-search-forward (concat "^" (regexp-quote (concat todo-category-beg cat)) - "$") nil t) + "$") + nil t) (todo-add-category nil cat) (setq newcat t)) ;; Go to top of category's done section. @@ -3218,6 +3279,7 @@ the only category in the archive, the archive file is deleted." (todo-update-count 'done 1 cat) (unless newcat ; Newly added category has no archive. (todo-update-count 'archived -1 cat)))) + (if headers-hidden (todo-toggle-item-header)) (todo-update-categories-sexp))) ;; Delete restored items from archive. (when marked @@ -3263,6 +3325,10 @@ the only category in the archive, the archive file is deleted." (set-buffer (find-file-noselect tfile))) (todo-category-number cat) (todo-category-select) + ;; Selecting the category leaves point at the end of the done + ;; items separator string, so move it to the (first) restored + ;; done item. + (forward-line) (message "Items unarchived."))))) (defun todo-jump-to-archive-category (&optional file) @@ -3404,9 +3470,9 @@ decreasing or increasing its number." (unless prompt (setq priority candidate))) (let* ((lower (< curnum priority)) ; Priority is being lowered. (head (butlast todo-categories - (apply (if lower 'identity '1+) - (list (- maxnum priority))))) - (tail (nthcdr (apply (if lower 'identity '1-) (list priority)) + (funcall (if lower #'identity #'1+) + (- maxnum priority)))) + (tail (nthcdr (funcall (if lower #'identity #'1-) priority) todo-categories)) ;; Category's name and items counts list. (catcons (nth (1- curnum) todo-categories)) @@ -3492,7 +3558,7 @@ decreasing or increasing its number." "Return adjusted length of category label button. The adjustment ensures proper tabular alignment in Todo Categories mode." - (let* ((categories (mapcar 'car todo-categories)) + (let* ((categories (mapcar #'car todo-categories)) (longest (todo-longest-category-name-length categories)) (catlablen (length todo-categories-category-label)) (lc-diff (- longest catlablen))) @@ -3578,24 +3644,24 @@ LABEL determines which type of count is sorted." ov) (insert-button str 'face nil 'action - `(lambda (button) - (let ((key (todo-label-to-key ,label))) - (if (and (member key todo-descending-counts) - (eq key 'alpha)) - (progn - ;; If display is alphabetical, switch back to - ;; category priority order. - (todo-display-sorted nil) - (setq todo-descending-counts - (delete key todo-descending-counts))) - (todo-display-sorted key))))) + (lambda (_button) + (let ((key (todo-label-to-key label))) + (if (and (member key todo-descending-counts) + (eq key 'alpha)) + (progn + ;; If display is alphabetical, switch back to + ;; category priority order. + (todo-display-sorted nil) + (setq todo-descending-counts + (delete key todo-descending-counts))) + (todo-display-sorted key))))) (setq ov (make-overlay beg end)) (overlay-put ov 'face 'todo-button))) (defun todo-total-item-counts () "Return a list of total item counts for the current file." - (mapcar (lambda (i) (apply '+ (mapcar (lambda (l) (aref l i)) - (mapcar 'cdr todo-categories)))) + (mapcar (lambda (i) (apply #'+ (mapcar (lambda (x) (aref (cdr x) i)) + todo-categories))) (list 0 1 2 3))) (defvar todo-categories-category-number 0 @@ -3640,9 +3706,10 @@ which is the value of the user option (not (zerop (todo-get-count 'archived cat)))) 'todo-archived-only nil) - 'action `(lambda (button) (let ((buf (current-buffer))) - (todo-jump-to-category nil ,cat) - (kill-buffer buf)))) + 'action (lambda (_button) + (let ((buf (current-buffer))) + (todo-jump-to-category nil cat) + (kill-buffer buf)))) ;; Highlight the sorted count column. (let* ((beg (+ opoint 7 (length str))) end ovl) @@ -3721,8 +3788,8 @@ which is the value of the user option (delete-region (point) (point-max)) ;; Fill in the table with buttonized lines, each showing a category and ;; its item counts. - (mapc (lambda (cat) (todo-insert-category-line cat sortkey)) - (mapcar 'car cats)) + (dolist (cat cats) + (todo-insert-category-line (car cat) sortkey)) (newline) ;; Add a line showing item count totals. (insert (make-string (+ 4 (length todo-categories-number-separator)) 32) @@ -3778,7 +3845,8 @@ face." (when (looking-at todo-done-string-start) (setq in-done t)) (re-search-backward (concat "^" (regexp-quote todo-category-beg) - "\\(.*\\)\n") nil t) + "\\(.*\\)\n") + nil t) (setq cat (match-string-no-properties 1)) (todo-category-number cat) (todo-category-select) @@ -3840,9 +3908,7 @@ This variable should be set interactively by (defcustom todo-filter-files nil "List of default files for multifile item filtering." - :type `(set ,@(mapcar (lambda (f) (list 'const f)) - (mapcar 'todo-short-file-name - (funcall todo-files-function)))) + :type `(set ,@(todo--files-type-list)) :group 'todo-filtered) (defcustom todo-filter-done-items nil @@ -4022,19 +4088,17 @@ regexp items." (widget-insert "Select files for generating the top priorities list.\n\n") (setq todo-multiple-filter-files-widget (widget-create - `(set ,@(mapcar (lambda (x) (list 'const x)) - (mapcar 'todo-short-file-name - (funcall todo-files-function)))))) + `(set ,@(todo--files-type-list)))) (widget-insert "\n") (widget-create 'push-button - :notify (lambda (widget &rest ignore) + :notify (lambda (&rest _) (setq todo-multiple-filter-files 'quit) (quit-window t) (exit-recursive-edit)) "Cancel") (widget-insert " ") (widget-create 'push-button - :notify (lambda (&rest ignore) + :notify (lambda (&rest _) (setq todo-multiple-filter-files (mapcar (lambda (f) (file-truename @@ -4092,7 +4156,7 @@ multifile commands for further details." ;; Pressed `cancel' in t-m-f-f file selection dialog. (keyboard-quit) (concat todo-directory - (mapconcat 'todo-short-file-name flist "-") + (mapconcat #'todo-short-file-name flist "-") (cond (top ".todt") (diary ".tody") (regexp ".todr"))))) @@ -4105,10 +4169,11 @@ multifile commands for further details." (todo-filter-items-1 (cons 'top new) flist)) ((and (not new) file-exists) (when (and rxfiles (> (length rxfiles) 1)) - (let ((rxf (mapcar 'todo-short-file-name rxfiles))) + (let ((rxf (mapcar #'todo-short-file-name rxfiles))) (setq fname (todo-absolute-file-name (completing-read "Choose a regexp items file: " - rxf) 'regexp)))) + rxf) + 'regexp)))) (find-file fname) (unless (derived-mode-p 'todo-filtered-items-mode) (todo-filtered-items-mode)) @@ -4119,12 +4184,13 @@ multifile commands for further details." (dolist (s (split-string (todo-short-file-name fname) "-")) (setq bufname (if bufname (concat bufname (if (member s (mapcar - 'todo-short-file-name + #'todo-short-file-name todo-files)) - ", " "-") s) + ", " "-") + s) s))) - (rename-buffer (format (concat "%s for file" (if multi "s" "") - " \"%s\"") buf bufname)))) + (rename-buffer (format (concat "%s for file" (if multi "s" "") " \"%s\"") + buf bufname)))) (defun todo-filter-items-1 (filter file-list) "Build a list of items by applying FILTER to FILE-LIST. @@ -4190,7 +4256,8 @@ the values of FILTER and FILE-LIST." todo-top-priorities))) (while (re-search-forward (concat "^" (regexp-quote todo-category-beg) - "\\(.+\\)\n") nil t) + "\\(.+\\)\n") + nil t) (setq cat (match-string 1)) (let (cnum) ;; Unless the number of top priorities to show was @@ -4344,7 +4411,8 @@ its priority has changed, and `same' otherwise." "\\]" (regexp-quote todo-nondiary-end)) "?" "\\(?4: \\[\\(?3:(archive) \\)?\\(?2:.*:\\)?" - "\\(?1:.*\\)\\]\\).*$") str) + "\\(?1:.*\\)\\]\\).*$") + str) (let ((cat (match-string 1 str)) (file (match-string 2 str)) (archive (string= (match-string 3 str) "(archive) ")) @@ -4459,8 +4527,11 @@ If the file already exists, overwrite it only on confirmation." ;;; Printing Todo mode buffers ;; ----------------------------------------------------------------------------- -(defcustom todo-print-buffer-function 'ps-print-buffer-with-faces - "Function called by the command `todo-print-buffer'." +(defcustom todo-print-buffer-function #'ps-print-buffer-with-faces + "Function called by `todo-print-buffer' to print Todo mode buffers. +Called with one argument which can either be: +- a string, naming a file to save the print image to. +- nil, to send the image to the printer." :type 'symbol :group 'todo) @@ -4486,8 +4557,7 @@ otherwise, send it to the default printer." 'face 'todo-prefix-string)) (num 0) (fill-prefix (make-string todo-indent-to-here 32)) - (content (buffer-string)) - file) + (content (buffer-string))) (with-current-buffer (get-buffer-create buf) (insert content) (goto-char (point-min)) @@ -4511,10 +4581,9 @@ otherwise, send it to the default printer." (goto-char (point-min)) (insert header) (newline 2) - (if to-file - (let ((file (read-file-name "Print to file: "))) - (funcall todo-print-buffer-function file)) - (funcall todo-print-buffer-function))) + (funcall todo-print-buffer-function + (if to-file nil + (read-file-name "Print to file: ")))) (kill-buffer buf))) (defun todo-print-buffer-to-file () @@ -4544,14 +4613,15 @@ strings built using the default value of (defun todo-convert-legacy-date-time () "Return converted date-time string. Helper function for `todo-convert-legacy-files'." - (let* ((year (match-string 1)) - (month (match-string 2)) - (monthname (calendar-month-name (string-to-number month) t)) - (day (match-string 3)) - (time (match-string 4)) - dayname) + (calendar-dlet* + ((year (match-string 1)) + (month (match-string 2)) + (monthname (calendar-month-name (string-to-number month) t)) + (day (match-string 3)) + (time (match-string 4)) + dayname) (replace-match "") - (insert (mapconcat 'eval calendar-date-display-form "") + (insert (mapconcat #'eval calendar-date-display-form "") (when time (concat " " time))))) (defun todo-convert-legacy-files () @@ -4675,7 +4745,8 @@ name in `todo-directory'. See also the documentation string of (unless (save-excursion (re-search-backward (concat "^" (regexp-quote todo-category-beg) - "\\(.*\\)$") nil t) + "\\(.*\\)$") + nil t) (string= (match-string 1) cat)) ;; Else move it to its category. (setq item (buffer-substring-no-properties beg end)) @@ -4689,7 +4760,8 @@ name in `todo-directory'. See also the documentation string of (forward-line) (if (re-search-forward (concat "^" (regexp-quote todo-category-beg) - "\\(.*\\)$") nil t) + "\\(.*\\)$") + nil t) (progn (goto-char (match-beginning 0)) (newline) (forward-line -1)) @@ -4730,7 +4802,7 @@ name in `todo-directory'. See also the documentation string of (prin1 sexp (current-buffer))) (write-region (point-min) (point-max) file nil 'nomessage)) (setq todo-archives (funcall todo-files-function t))) - (todo-reevaluate-filelist-defcustoms) + (todo-update-filelist-defcustoms) (when (y-or-n-p (concat "Format conversion done; do you want to " "visit the converted file now? ")) (setq todo-current-todo-file file) @@ -4783,10 +4855,7 @@ buffer, clean up the state and return nil." (setq todo-files (funcall todo-files-function)) (setq todo-archives (funcall todo-files-function t)) t) - (let* ((files (append todo-files todo-archives)) - (tctf todo-current-todo-file) - (tgctf todo-global-current-todo-file) - (tdtf (todo-absolute-file-name todo-default-todo-file))) + (let* ((files (append todo-files todo-archives))) (unless (or (not todo-current-todo-file) (member todo-current-todo-file files)) (setq todo-current-todo-file nil)) @@ -4797,7 +4866,7 @@ buffer, clean up the state and return nil." (member todo-default-todo-file files)) (setq todo-default-todo-file (todo-short-file-name (car todo-files)))) - (todo-reevaluate-filelist-defcustoms) + (todo-update-filelist-defcustoms) (when buf (kill-buffer buf)) nil))))) @@ -4805,7 +4874,7 @@ buffer, clean up the state and return nil." "Return the number of category CAT in this todo file. The buffer-local variable `todo-category-number' holds this number as its value." - (let ((categories (mapcar 'car todo-categories))) + (let ((categories (mapcar #'car todo-categories))) (setq todo-category-number ;; Increment by one, so that the number of the first ;; category is one rather than zero. @@ -4835,7 +4904,8 @@ number as its value." (todo-prefix-overlays) (goto-char (point-min)) (if (re-search-forward (concat "\n\\(" (regexp-quote todo-category-done) - "\\)") nil t) + "\\)") + nil t) (progn (setq done-start (match-beginning 0)) (setq done-sep-start (match-beginning 1)) @@ -5141,12 +5211,22 @@ If the category's done items are visible, this command called with a prefix argument only moves point to a higher item, e.g., with point on the first done item and called with prefix 1, it moves to the last todo item; but if called with point on the -first done item without a prefix argument, it moves point the the +first done item without a prefix argument, it moves point to the empty line above the done items separator." (let* ((done (todo-done-item-p))) (todo-item-start) (unless (bobp) - (re-search-backward todo-item-start nil t (or count 1))) + (re-search-backward (concat todo-item-start + "\\( " diary-time-regexp "\\)?" + (regexp-quote todo-nondiary-end) "? ") + nil t (or count 1)) + ;; If the item date-time header is hidden, the display engine + ;; moves point to the next earlier displayable position, which + ;; is the end of the next item above, so we move it to the start + ;; of the current item's text (that's what the display engine + ;; does with todo-forward-item in this case.) + ;; FIXME: would it be better to use cursor-sensor-functions? + (when todo--item-headers-hidden (goto-char (match-end 0)))) ;; Unless this is a regexp filtered items buffer (which can contain ;; intermixed todo and done items), if points advances by one from a ;; done to a todo item, go back to the space above @@ -5162,10 +5242,12 @@ empty line above the done items separator." (defun todo-remove-item () "Internal function called in editing, deleting or moving items." - (let* ((end (progn (todo-item-end) (1+ (point)))) - (beg (todo-item-start)) - (ov (todo-get-overlay 'prefix))) - (when ov (delete-overlay ov)) + (let ((end (progn (todo-item-end) (1+ (point)))) + (beg (todo-item-start)) + ovs) + (push (todo-get-overlay 'prefix) ovs) + (push (todo-get-overlay 'header) ovs) + (dolist (ov ovs) (when ov (delete-overlay ov))) (delete-region beg end))) (defun todo-diary-item-p () @@ -5207,7 +5289,8 @@ Overrides `diary-goto-entry'." (when (eq major-mode 'todo-mode) (let ((opoint (point))) (re-search-backward (concat "^" (regexp-quote todo-category-beg) - "\\(.*\\)\n") nil t) + "\\(.*\\)\n") + nil t) (todo-category-number (match-string 1)) (todo-category-select) (goto-char opoint)))))) @@ -5221,7 +5304,10 @@ Also preserve category display, if applicable." (let ((revert-buffer-function nil)) (revert-buffer ignore-auto noconfirm 'preserve-modes) (when (memq major-mode '(todo-mode todo-archive-mode)) - (todo-category-select)))) + (save-excursion (todo-category-select)) + ;; revert-buffer--default calls after-find-file, which makes + ;; buffer writable. + (setq buffer-read-only t)))) (defun todo-desktop-save-buffer (_dir) `((catnum . ,(todo-category-number (todo-current-category))))) @@ -5296,15 +5382,21 @@ marked) not done todo items." (defun todo-get-overlay (val) "Return the overlay at point whose `todo' property has value VAL." - ;; Use overlays-in to find prefix overlays and check over two - ;; positions to find done separator overlay. - (let ((ovs (overlays-in (point) (1+ (point)))) - ov) - (catch 'done - (while ovs - (setq ov (pop ovs)) - (when (eq (overlay-get ov 'todo) val) - (throw 'done ov)))))) + (save-excursion + ;; When headers are hidden, the display engine makes item's start + ;; inaccessible to commands, so then we have to go there + ;; non-interactively to check for prefix and header overlays. + (when (memq val '(prefix header)) + (unless (looking-at todo-item-start) (todo-item-start))) + ;; Use overlays-in to find prefix overlays and check over two + ;; positions to find done separator overlay. + (let ((ovs (overlays-in (point) (1+ (point)))) + ov) + (catch 'done + (while ovs + (setq ov (pop ovs)) + (when (eq (overlay-get ov 'todo) val) + (throw 'done ov))))))) (defun todo-marked-item-p () "Non-nil if this item begins with `todo-item-mark'. @@ -5320,16 +5412,26 @@ In that case, return the item's prefix overlay." (when marked ov))) (defun todo-insert-with-overlays (item) - "Insert ITEM at point and update prefix/priority number overlays." + "Insert ITEM at point and update prefix and header overlays." (todo-item-start) - ;; Insertion pushes item down but not its prefix overlay. When the - ;; overlay includes a mark, this would now mark the inserted ITEM, - ;; so move it to the pushed down item. (let ((ov (todo-get-overlay 'prefix)) (marked (todo-marked-item-p))) (insert item "\n") - (when marked (move-overlay ov (point) (point)))) - (todo-backward-item) + ;; Insertion pushes item down but not its prefix overlay. When + ;; the overlay includes a mark, this would now mark the inserted + ;; ITEM, so move it to the pushed down item. + (when marked (move-overlay ov (point) (point))) + (todo-backward-item) + ;; With hidden headers, todo-backward-item puts point on first + ;; visible character after header, so we have to search backward. + (when todo--item-headers-hidden + (re-search-backward (concat todo-item-start + "\\( " diary-time-regexp "\\)?" + (regexp-quote todo-nondiary-end) "? ") + nil t) + (setq ov (make-overlay (match-beginning 0) (match-end 0) nil t)) + (overlay-put ov 'todo 'header) + (overlay-put ov 'display ""))) (todo-prefix-overlays)) (defun todo-prefix-overlays () @@ -5572,8 +5674,7 @@ already entered and those still available." (defvar todo-edit-item--prompt "Press a key (so far `e'): ") (defun todo-edit-item--next-key (params &optional arg) - (let* ((map (make-sparse-keymap)) - (p->k (mapconcat (lambda (elt) + (let* ((p->k (mapconcat (lambda (elt) (format "%s=>%s" (propertize (cdr elt) 'face 'todo-key-prompt) @@ -5652,7 +5753,8 @@ have been removed." " been deleted and removed from\n" "the list of category completion files") names)) - (todo-reevaluate-category-completions-files-defcustom) + (put 'todo-category-completions-files 'custom-type + `(set ,@(todo--files-type-list))) (custom-set-default 'todo-category-completions-files (symbol-value 'todo-category-completions-files)) (sleep-for 1.5))) @@ -5661,14 +5763,14 @@ have been removed." todo-global-current-todo-file) (todo-absolute-file-name todo-default-todo-file))) (files (or (unless archive - (mapcar 'todo-absolute-file-name + (mapcar #'todo-absolute-file-name todo-category-completions-files)) (list curfile))) listall listf) ;; If file was just added, it has no category completions. (unless (zerop (buffer-size (find-buffer-visiting curfile))) (unless (member curfile todo-archives) - (add-to-list 'files curfile)) + (cl-pushnew curfile files :test #'equal)) (dolist (f files listall) (with-current-buffer (find-file-noselect f 'nowarn) (if archive @@ -5708,7 +5810,7 @@ return the absolute truename of a todo archive file. With non-nil MUSTMATCH the name of an existing file must be chosen; otherwise, a new file name is allowed." (let* ((completion-ignore-case todo-completion-ignore-case) - (files (mapcar 'todo-short-file-name + (files (mapcar #'todo-short-file-name ;; (funcall todo-files-function archive))) (if archive todo-archives todo-files))) (file (completing-read prompt files nil mustmatch nil nil @@ -5757,7 +5859,8 @@ categories from `todo-category-completions-files'." (todo-read-file-name (concat "Choose a" (if archive "n archive" " todo") - " file: ") archive t))) + " file: ") + archive t))) (completions (unless file0 (todo-category-completions archive))) (categories (cond (file0 (with-current-buffer @@ -5798,7 +5901,7 @@ categories from `todo-category-completions-files'." (if (atom catfil) catfil (todo-absolute-file-name - (let ((files (mapcar 'todo-short-file-name catfil))) + (let ((files (mapcar #'todo-short-file-name catfil))) (completing-read (format str cat) files))))))) ;; Default to the current file. (unless file0 (setq file0 todo-current-todo-file)) @@ -5832,7 +5935,7 @@ categories from `todo-category-completions-files'." "Prompt for new NAME for TYPE until it is valid, then return it. TYPE can be either of the symbols `file' or `category'." (let ((categories todo-categories) - (files (mapcar 'todo-short-file-name todo-files)) + (files (mapcar #'todo-short-file-name todo-files)) prompt) (while (and @@ -5888,8 +5991,8 @@ indicating an unspecified month, day, or year. When ARG is `day', non-nil arguments MO and YR determine the number of the last the day of the month." - (let (year monthname month day - dayname) ; Needed by calendar-date-display-form. + (calendar-dlet* + (year monthname month day dayname) ;Needed by calendar-date-display-form. (when (or (not arg) (eq arg 'year)) (while (if (natnump year) (< year 1) (not (eq year '*))) (setq year (read-from-minibuffer @@ -5906,8 +6009,8 @@ number of the last the day of the month." (setq monthname (completing-read "Month name (RET for current month, * for any month): " mlist nil t nil nil - (calendar-month-name (calendar-extract-month - (calendar-current-date)) t)) + (calendar-month-name + (calendar-extract-month (calendar-current-date)) t)) month (1+ (- (length mlist) (length (or (member monthname mlist) (member monthname mablist)))))) @@ -5948,7 +6051,7 @@ number of the last the day of the month." (if (memq 'month calendar-date-display-form) month monthname))) - (mapconcat 'eval calendar-date-display-form "")))) + (mapconcat #'eval calendar-date-display-form "")))) (defun todo-read-dayname () "Choose name of a day of the week with completion and return it." @@ -6013,8 +6116,8 @@ the empty string (i.e., no time string)." "The :set function for user option `todo-show-current-file'." (custom-set-default symbol value) (if value - (add-hook 'pre-command-hook 'todo-show-current-file nil t) - (remove-hook 'pre-command-hook 'todo-show-current-file t))) + (add-hook 'pre-command-hook #'todo-show-current-file nil t) + (remove-hook 'pre-command-hook #'todo-show-current-file t))) (defun todo-reset-prefix (symbol value) "The :set function for `todo-prefix' and `todo-number-prefix'." @@ -6151,57 +6254,12 @@ the empty string (i.e., no time string)." (hl-line-mode 1) (hl-line-mode -1))))))))) -(defun todo-reevaluate-filelist-defcustoms () - "Reevaluate defcustoms that provide choice list of todo files." - (custom-set-default 'todo-default-todo-file - (symbol-value 'todo-default-todo-file)) - (todo-reevaluate-default-file-defcustom) - (custom-set-default 'todo-filter-files (symbol-value 'todo-filter-files)) - (todo-reevaluate-filter-files-defcustom) - (custom-set-default 'todo-category-completions-files - (symbol-value 'todo-category-completions-files)) - (todo-reevaluate-category-completions-files-defcustom)) - -(defun todo-reevaluate-default-file-defcustom () - "Reevaluate defcustom of `todo-default-todo-file'. -Called after adding or deleting a todo file. If the value of -`todo-default-todo-file' before calling this function was -associated with an existing file, keep that value." - ;; (let ((curval todo-default-todo-file)) - (eval - (defcustom todo-default-todo-file (todo-short-file-name - (car (funcall todo-files-function))) - "Todo file visited by first session invocation of `todo-show'." - :type (when todo-files - `(radio ,@(mapcar (lambda (f) (list 'const f)) - (mapcar 'todo-short-file-name - (funcall todo-files-function))))) - :group 'todo)) - ;; (when (and curval (file-exists-p (todo-absolute-file-name curval))) - ;; (custom-set-default 'todo-default-todo-file curval) - ;; ;; (custom-reevaluate-setting 'todo-default-todo-file) - ;; ))) - ) - -(defun todo-reevaluate-category-completions-files-defcustom () - "Reevaluate defcustom of `todo-category-completions-files'. -Called after adding or deleting a todo file." - (eval (defcustom todo-category-completions-files nil - "List of files for building `todo-read-category' completions." - :type `(set ,@(mapcar (lambda (f) (list 'const f)) - (mapcar 'todo-short-file-name - (funcall todo-files-function)))) - :group 'todo))) - -(defun todo-reevaluate-filter-files-defcustom () - "Reevaluate defcustom of `todo-filter-files'. -Called after adding or deleting a todo file." - (eval (defcustom todo-filter-files nil - "List of files for multifile item filtering." - :type `(set ,@(mapcar (lambda (f) (list 'const f)) - (mapcar 'todo-short-file-name - (funcall todo-files-function)))) - :group 'todo))) +(defun todo-update-filelist-defcustoms () + "Update defcustoms that provide choice list of todo files." + (put 'todo-default-todo-file 'custom-type `(radio ,@(todo--files-type-list))) + (put 'todo-category-completions-files 'custom-type + `(set ,@(todo--files-type-list))) + (put 'todo-filter-files 'custom-type `(set ,@(todo--files-type-list)))) ;; ----------------------------------------------------------------------------- ;;; Font locking @@ -6217,7 +6275,8 @@ Called after adding or deleting a todo file." (defun todo-diary-nonmarking-matcher (lim) "Search for diary nonmarking symbol within LIM for font-locking." (re-search-forward (concat "^\\(?1:" (regexp-quote diary-nonmarking-symbol) - "\\)" todo-date-pattern) lim t)) + "\\)" todo-date-pattern) + lim t)) (defun todo-date-string-matcher (lim) "Search for todo item date string within LIM for font-locking." @@ -6227,14 +6286,16 @@ Called after adding or deleting a todo file." (defun todo-time-string-matcher (lim) "Search for todo item time string within LIM for font-locking." (re-search-forward (concat todo-date-string-start todo-date-pattern - " \\(?1:" diary-time-regexp "\\)") lim t)) + " \\(?1:" diary-time-regexp "\\)") + lim t)) (defun todo-diary-expired-matcher (lim) "Search for expired diary item date within LIM for font-locking." (when (re-search-forward (concat "^\\(?:" (regexp-quote diary-nonmarking-symbol) "\\)?\\(?1:" todo-date-pattern "\\) \\(?2:" - diary-time-regexp "\\)?") lim t) + diary-time-regexp "\\)?") + lim t) (let* ((date (match-string-no-properties 1)) (time (match-string-no-properties 2)) ;; Function days-between requires a non-empty time string. @@ -6389,8 +6450,6 @@ Filtered Items mode following todo (not done) items." (defvar todo-mode-map (let ((map (make-keymap))) - ;; Don't suppress digit keys, so they can supply prefix arguments. - (suppress-keymap map) (dolist (kb todo-key-bindings-t) (define-key map (nth 0 kb) (nth 1 kb))) (dolist (kb todo-key-bindings-t+a+f) @@ -6404,7 +6463,6 @@ Filtered Items mode following todo (not done) items." (defvar todo-archive-mode-map (let ((map (make-sparse-keymap))) - (suppress-keymap map) (dolist (kb todo-key-bindings-t+a+f) (define-key map (nth 0 kb) (nth 1 kb))) (dolist (kb todo-key-bindings-t+a) @@ -6423,7 +6481,6 @@ Filtered Items mode following todo (not done) items." (defvar todo-categories-mode-map (let ((map (make-sparse-keymap))) - (suppress-keymap map) (define-key map "c" 'todo-sort-categories-alphabetically-or-numerically) (define-key map "t" 'todo-sort-categories-by-todo) (define-key map "y" 'todo-sort-categories-by-diary) @@ -6442,7 +6499,6 @@ Filtered Items mode following todo (not done) items." (defvar todo-filtered-items-mode-map (let ((map (make-sparse-keymap))) - (suppress-keymap map) (dolist (kb todo-key-bindings-t+a+f) (define-key map (nth 0 kb) (nth 1 kb))) (dolist (kb todo-key-bindings-t+f) @@ -6576,9 +6632,9 @@ Added to `window-configuration-change-hook' in Todo mode." (defun todo-modes-set-1 () "Make some settings that apply to multiple Todo modes." (setq-local font-lock-defaults '(todo-font-lock-keywords t)) - (setq-local revert-buffer-function 'todo-revert-buffer) + (setq-local revert-buffer-function #'todo-revert-buffer) (setq-local tab-width todo-indent-to-here) - (setq-local indent-line-function 'todo-indent) + (setq-local indent-line-function #'todo-indent) (when todo-wrap-lines (visual-line-mode) (setq wrap-prefix (make-string todo-indent-to-here 32)))) @@ -6594,14 +6650,15 @@ Added to `window-configuration-change-hook' in Todo mode." "Make some settings that apply to multiple Todo modes." (add-to-invisibility-spec 'todo) (setq buffer-read-only t) + (setq-local todo--item-headers-hidden nil) (setq-local desktop-save-buffer 'todo-desktop-save-buffer) - (setq-local hl-line-range-function 'todo-hl-line-range)) + (setq-local hl-line-range-function #'todo-hl-line-range)) (defun todo-modes-set-3 () "Make some settings that apply to multiple Todo modes." (setq-local todo-categories (todo-set-categories)) (setq-local todo-category-number 1) - ;; (add-hook 'find-file-hook 'todo-display-as-todo-file nil t) + ;; (add-hook 'find-file-hook #'todo-display-as-todo-file nil t) ) (put 'todo-mode 'mode-class 'special) @@ -6624,13 +6681,13 @@ Added to `window-configuration-change-hook' in Todo mode." (setq-local todo-current-todo-file (file-truename (buffer-file-name)))) (setq-local todo-show-done-only nil) (setq-local todo-categories-with-marks nil) - ;; (add-hook 'find-file-hook 'todo-add-to-buffer-list nil t) - (add-hook 'post-command-hook 'todo-update-buffer-list nil t) + ;; (add-hook 'find-file-hook #'todo-add-to-buffer-list nil t) + (add-hook 'post-command-hook #'todo-update-buffer-list nil t) (when todo-show-current-file - (add-hook 'pre-command-hook 'todo-show-current-file nil t)) + (add-hook 'pre-command-hook #'todo-show-current-file nil t)) (add-hook 'window-configuration-change-hook - 'todo-reset-and-enable-done-separator nil t) - (add-hook 'kill-buffer-hook 'todo-reset-global-current-todo-file nil t))) + #'todo-reset-and-enable-done-separator nil t) + (add-hook 'kill-buffer-hook #'todo-reset-global-current-todo-file nil t))) (put 'todo-archive-mode 'mode-class 'special) diff --git a/lisp/case-table.el b/lisp/case-table.el index 271bb0a4786..174e3f0afc0 100644 --- a/lisp/case-table.el +++ b/lisp/case-table.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/cdl.el b/lisp/cdl.el index 16ba7e7d527..80ef76ace14 100644 --- a/lisp/cdl.el +++ b/lisp/cdl.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/cedet/ChangeLog.1 b/lisp/cedet/ChangeLog.1 index c0223cbc78a..35cdf80e4b3 100644 --- a/lisp/cedet/ChangeLog.1 +++ b/lisp/cedet/ChangeLog.1 @@ -457,7 +457,7 @@ complete local variables. * semantic/scope.el (semantic-analyze-scoped-types-default): If we - cannot find a type in the typecache, also look into the the types + cannot find a type in the typecache, also look into the types we already found. This is necessary since in C++, a 'using namespace' can be dependend on a previous one. (semantic-completable-tags-from-type): When creating the list of @@ -3475,4 +3475,4 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. diff --git a/lisp/cedet/cedet-cscope.el b/lisp/cedet/cedet-cscope.el index 913f960b2a6..faee7feeb25 100644 --- a/lisp/cedet/cedet-cscope.el +++ b/lisp/cedet/cedet-cscope.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/cedet-files.el b/lisp/cedet/cedet-files.el index 0798e7c0c5b..e18e66a12dd 100644 --- a/lisp/cedet/cedet-files.el +++ b/lisp/cedet/cedet-files.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/cedet-global.el b/lisp/cedet/cedet-global.el index ee2265bec6d..871fd94aebd 100644 --- a/lisp/cedet/cedet-global.el +++ b/lisp/cedet/cedet-global.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/cedet-idutils.el b/lisp/cedet/cedet-idutils.el index dbcce2d99b3..48e1b2d09b1 100644 --- a/lisp/cedet/cedet-idutils.el +++ b/lisp/cedet/cedet-idutils.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -98,7 +98,7 @@ Return the created buffer with program output." (defun cedet-idutils-lid-call (flags) "Call ID Utils lid with the list of FLAGS. -Return the created buffer with with program output." +Return the created buffer with program output." (let ((b (get-buffer-create "*CEDET lid*")) (cd default-directory) ) diff --git a/lisp/cedet/cedet.el b/lisp/cedet/cedet.el index a0b06f2820d..bedbd98df3e 100644 --- a/lisp/cedet/cedet.el +++ b/lisp/cedet/cedet.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/cedet/data-debug.el b/lisp/cedet/data-debug.el index b12e2a378f7..5325bf52b57 100644 --- a/lisp/cedet/data-debug.el +++ b/lisp/cedet/data-debug.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/ede.el b/lisp/cedet/ede.el index 87d73b2e42b..1dcafc453f4 100644 --- a/lisp/cedet/ede.el +++ b/lisp/cedet/ede.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -998,7 +998,7 @@ Argument PROMPT is the prompt to use when querying the user for a target." (project-add-file this file)) (cl-defmethod project-add-file ((ot ede-target) _file) - "Add the current buffer into project project target OT. + "Add the current buffer into project target OT. Argument FILE is the file to add." (error "add-file not supported by %s" (eieio-object-name ot))) diff --git a/lisp/cedet/ede/auto.el b/lisp/cedet/ede/auto.el index 6c0e5885cf5..75f2d6bd7a9 100644 --- a/lisp/cedet/ede/auto.el +++ b/lisp/cedet/ede/auto.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/ede/autoconf-edit.el b/lisp/cedet/ede/autoconf-edit.el index c9783cae8b6..e7481aad267 100644 --- a/lisp/cedet/ede/autoconf-edit.el +++ b/lisp/cedet/ede/autoconf-edit.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/ede/base.el b/lisp/cedet/ede/base.el index 13d721a5f9a..bfb5834d622 100644 --- a/lisp/cedet/ede/base.el +++ b/lisp/cedet/ede/base.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/ede/config.el b/lisp/cedet/ede/config.el index a517ed18e02..64170fa1d0c 100644 --- a/lisp/cedet/ede/config.el +++ b/lisp/cedet/ede/config.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/ede/cpp-root.el b/lisp/cedet/ede/cpp-root.el index 319854e07c4..9643578fa3c 100644 --- a/lisp/cedet/ede/cpp-root.el +++ b/lisp/cedet/ede/cpp-root.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/ede/custom.el b/lisp/cedet/ede/custom.el index 1c4e849d2df..55d4b4a5a9d 100644 --- a/lisp/cedet/ede/custom.el +++ b/lisp/cedet/ede/custom.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/ede/detect.el b/lisp/cedet/ede/detect.el index 54d48a20500..25426dfeba6 100644 --- a/lisp/cedet/ede/detect.el +++ b/lisp/cedet/ede/detect.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -195,11 +195,10 @@ Return a cons cell: "Run a quick test for autodetecting on BUFFER." (interactive) (let ((start (current-time)) - (ans (ede-detect-directory-for-project default-directory)) - (end (current-time))) + (ans (ede-detect-directory-for-project default-directory))) (if ans (message "Project found in %d sec @ %s of type %s" - (float-time (time-subtract end start)) + (float-time (time-subtract nil start)) (car ans) (eieio-object-name-string (cdr ans))) (message "No Project found.") ))) diff --git a/lisp/cedet/ede/dired.el b/lisp/cedet/ede/dired.el index 2555fab3a37..361881855f8 100644 --- a/lisp/cedet/ede/dired.el +++ b/lisp/cedet/ede/dired.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/ede/emacs.el b/lisp/cedet/ede/emacs.el index cac66fa7348..f3ba4c3e1ef 100644 --- a/lisp/cedet/ede/emacs.el +++ b/lisp/cedet/ede/emacs.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/ede/files.el b/lisp/cedet/ede/files.el index 889cac8d954..4ba4ab19178 100644 --- a/lisp/cedet/ede/files.el +++ b/lisp/cedet/ede/files.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/ede/generic.el b/lisp/cedet/ede/generic.el index f7f98e618f3..cf91c33f1f7 100644 --- a/lisp/cedet/ede/generic.el +++ b/lisp/cedet/ede/generic.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/ede/linux.el b/lisp/cedet/ede/linux.el index 22f5c3ed218..3a183b317ef 100644 --- a/lisp/cedet/ede/linux.el +++ b/lisp/cedet/ede/linux.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/ede/locate.el b/lisp/cedet/ede/locate.el index 845a491b882..f61ce34ba92 100644 --- a/lisp/cedet/ede/locate.el +++ b/lisp/cedet/ede/locate.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/ede/make.el b/lisp/cedet/ede/make.el index 13591f6dc57..8fcaf52a96f 100644 --- a/lisp/cedet/ede/make.el +++ b/lisp/cedet/ede/make.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/ede/makefile-edit.el b/lisp/cedet/ede/makefile-edit.el index 90d48fc7639..e82577f4d35 100644 --- a/lisp/cedet/ede/makefile-edit.el +++ b/lisp/cedet/ede/makefile-edit.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/ede/pconf.el b/lisp/cedet/ede/pconf.el index f8d9e0b746f..8dc7f689ee8 100644 --- a/lisp/cedet/ede/pconf.el +++ b/lisp/cedet/ede/pconf.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/ede/pmake.el b/lisp/cedet/ede/pmake.el index 6feb9600e03..b836eafa8ce 100644 --- a/lisp/cedet/ede/pmake.el +++ b/lisp/cedet/ede/pmake.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -277,7 +277,7 @@ Execute BODY in a location where a value can be placed." ;;; SOURCE VARIABLE NAME CONSTRUCTION (defsubst ede-pmake-varname (obj) - "Convert OBJ into a variable name name. + "Convert OBJ into a variable name. Change . to _ in the variable name." (let ((name (oref obj name))) (while (string-match "\\." name) diff --git a/lisp/cedet/ede/proj-archive.el b/lisp/cedet/ede/proj-archive.el index 460df69f415..a9f3c708c0c 100644 --- a/lisp/cedet/ede/proj-archive.el +++ b/lisp/cedet/ede/proj-archive.el @@ -16,7 +16,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/ede/proj-aux.el b/lisp/cedet/ede/proj-aux.el index 3b60eea7c2e..8c5dfa7cf77 100644 --- a/lisp/cedet/ede/proj-aux.el +++ b/lisp/cedet/ede/proj-aux.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/ede/proj-comp.el b/lisp/cedet/ede/proj-comp.el index 80950ca7042..0537946bed4 100644 --- a/lisp/cedet/ede/proj-comp.el +++ b/lisp/cedet/ede/proj-comp.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/ede/proj-elisp.el b/lisp/cedet/ede/proj-elisp.el index 9f4e69f01f9..d48311548e4 100644 --- a/lisp/cedet/ede/proj-elisp.el +++ b/lisp/cedet/ede/proj-elisp.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/ede/proj-info.el b/lisp/cedet/ede/proj-info.el index d430e089c6f..9ec73924254 100644 --- a/lisp/cedet/ede/proj-info.el +++ b/lisp/cedet/ede/proj-info.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/ede/proj-misc.el b/lisp/cedet/ede/proj-misc.el index 6d1070a7f73..75e409bd74e 100644 --- a/lisp/cedet/ede/proj-misc.el +++ b/lisp/cedet/ede/proj-misc.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/ede/proj-obj.el b/lisp/cedet/ede/proj-obj.el index e7fa7730bd8..9fb94124c61 100644 --- a/lisp/cedet/ede/proj-obj.el +++ b/lisp/cedet/ede/proj-obj.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/ede/proj-prog.el b/lisp/cedet/ede/proj-prog.el index 3a149072718..2a9ea1a5131 100644 --- a/lisp/cedet/ede/proj-prog.el +++ b/lisp/cedet/ede/proj-prog.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/ede/proj-scheme.el b/lisp/cedet/ede/proj-scheme.el index 5ac2efa557a..0c6f602fb07 100644 --- a/lisp/cedet/ede/proj-scheme.el +++ b/lisp/cedet/ede/proj-scheme.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/ede/proj-shared.el b/lisp/cedet/ede/proj-shared.el index 6c17504a02b..f4c8e7b7944 100644 --- a/lisp/cedet/ede/proj-shared.el +++ b/lisp/cedet/ede/proj-shared.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/ede/proj.el b/lisp/cedet/ede/proj.el index a7f64ac5f3d..daedd37a25c 100644 --- a/lisp/cedet/ede/proj.el +++ b/lisp/cedet/ede/proj.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/ede/project-am.el b/lisp/cedet/ede/project-am.el index 86b707a99f5..de99b2939f9 100644 --- a/lisp/cedet/ede/project-am.el +++ b/lisp/cedet/ede/project-am.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/ede/shell.el b/lisp/cedet/ede/shell.el index 37beea0b427..dc31840ca62 100644 --- a/lisp/cedet/ede/shell.el +++ b/lisp/cedet/ede/shell.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/ede/simple.el b/lisp/cedet/ede/simple.el index d618b938e64..8f084754f0c 100644 --- a/lisp/cedet/ede/simple.el +++ b/lisp/cedet/ede/simple.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/ede/source.el b/lisp/cedet/ede/source.el index d72d0db3935..b2d7680e3ca 100644 --- a/lisp/cedet/ede/source.el +++ b/lisp/cedet/ede/source.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/cedet/ede/speedbar.el b/lisp/cedet/ede/speedbar.el index f938f209a46..4012fdadf71 100644 --- a/lisp/cedet/ede/speedbar.el +++ b/lisp/cedet/ede/speedbar.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/ede/srecode.el b/lisp/cedet/ede/srecode.el index 4193684dcf6..0658491f445 100644 --- a/lisp/cedet/ede/srecode.el +++ b/lisp/cedet/ede/srecode.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/ede/system.el b/lisp/cedet/ede/system.el index 42172ce5dc0..f5ac3e39803 100644 --- a/lisp/cedet/ede/system.el +++ b/lisp/cedet/ede/system.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/ede/util.el b/lisp/cedet/ede/util.el index eb364d7eafb..5535eff1e1b 100644 --- a/lisp/cedet/ede/util.el +++ b/lisp/cedet/ede/util.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/inversion.el b/lisp/cedet/inversion.el index ec54276af16..253336f973f 100644 --- a/lisp/cedet/inversion.el +++ b/lisp/cedet/inversion.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/mode-local.el b/lisp/cedet/mode-local.el index 88ee4001414..964f5c2db0f 100644 --- a/lisp/cedet/mode-local.el +++ b/lisp/cedet/mode-local.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/pulse.el b/lisp/cedet/pulse.el index 913c183a7e6..3554ee242b8 100644 --- a/lisp/cedet/pulse.el +++ b/lisp/cedet/pulse.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -196,11 +196,11 @@ Optional argument FACE specifies the face to do the highlighting." (pulse-reset-face face) (setq pulse-momentary-timer (run-with-timer 0 pulse-delay #'pulse-tick - (time-add (current-time) + (time-add nil (* pulse-delay pulse-iterations))))))) (defun pulse-tick (stop-time) - (if (time-less-p (current-time) stop-time) + (if (time-less-p nil stop-time) (pulse-lighten-highlight) (pulse-momentary-unhighlight))) diff --git a/lisp/cedet/semantic.el b/lisp/cedet/semantic.el index c38afed3964..cae6e049f44 100644 --- a/lisp/cedet/semantic.el +++ b/lisp/cedet/semantic.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -389,10 +389,9 @@ the output buffer." (if clear (semantic-clear-toplevel-cache)) (if (eq clear '-) (setq clear -1)) (let* ((start (current-time)) - (out (semantic-fetch-tags)) - (end (current-time))) + (out (semantic-fetch-tags))) (message "Retrieving tags took %.2f seconds." - (semantic-elapsed-time start end)) + (semantic-elapsed-time start nil)) (when (or (null clear) (not (listp clear)) (and (numberp clear) (< 0 clear))) (pop-to-buffer "*Parser Output*") diff --git a/lisp/cedet/semantic/analyze.el b/lisp/cedet/semantic/analyze.el index c7062fb24cd..b528487887a 100644 --- a/lisp/cedet/semantic/analyze.el +++ b/lisp/cedet/semantic/analyze.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -440,12 +440,11 @@ to provide a large number of non-cached analysis for filtering symbols." (defun semantic-analyze-current-symbol-default (analyzehookfcn position) "Call ANALYZEHOOKFCN on the analyzed symbol at POSITION." (let* ((semantic-analyze-error-stack nil) - (LLstart (current-time)) + ;; (LLstart (current-time)) (prefixandbounds (semantic-ctxt-current-symbol-and-bounds (or position (point)))) (prefix (car prefixandbounds)) (bounds (nth 2 prefixandbounds)) (scope (semantic-calculate-scope position)) - (end nil) ) ;; Only do work if we have bounds (meaning a prefix to complete) (when bounds @@ -464,15 +463,13 @@ to provide a large number of non-cached analysis for filtering symbols." prefix scope 'prefixtypes)) (error (semantic-analyze-push-error err)))) - (setq end (current-time)) - ;;(message "Analysis took %.2f sec" (semantic-elapsed-time LLstart end)) + ;;(message "Analysis took %.2f sec" (semantic-elapsed-time LLstart nil)) ) (when prefix (prog1 (funcall analyzehookfcn (car bounds) (cdr bounds) prefix) - ;;(setq end (current-time)) - ;;(message "hookfcn took %.5f sec" (semantic-elapsed-time LLstart end)) + ;;(message "hookfcn took %.5f sec" (semantic-elapsed-time LLstart nil)) ) ))) @@ -723,12 +720,11 @@ Optional argument CTXT is the context to show." (interactive) (require 'data-debug) (let ((start (current-time)) - (ctxt (or ctxt (semantic-analyze-current-context))) - (end (current-time))) + (ctxt (or ctxt (semantic-analyze-current-context)))) (if (not ctxt) (message "No Analyzer Results") (message "Analysis took %.2f seconds." - (semantic-elapsed-time start end)) + (semantic-elapsed-time start nil)) (semantic-analyze-pulse ctxt) (if ctxt (progn diff --git a/lisp/cedet/semantic/analyze/complete.el b/lisp/cedet/semantic/analyze/complete.el index 1ddbe131e6a..1a450683701 100644 --- a/lisp/cedet/semantic/analyze/complete.el +++ b/lisp/cedet/semantic/analyze/complete.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/analyze/debug.el b/lisp/cedet/semantic/analyze/debug.el index fd218b67827..8e68e3b856b 100644 --- a/lisp/cedet/semantic/analyze/debug.el +++ b/lisp/cedet/semantic/analyze/debug.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/analyze/fcn.el b/lisp/cedet/semantic/analyze/fcn.el index 1abbca5158e..29a1ac9165b 100644 --- a/lisp/cedet/semantic/analyze/fcn.el +++ b/lisp/cedet/semantic/analyze/fcn.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/analyze/refs.el b/lisp/cedet/semantic/analyze/refs.el index 7fbaa2ce974..84c60e2dae8 100644 --- a/lisp/cedet/semantic/analyze/refs.el +++ b/lisp/cedet/semantic/analyze/refs.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -317,9 +317,8 @@ Only works for tags in the global namespace." (let* ((tag (semantic-current-tag)) (start (current-time)) (sac (semantic-analyze-tag-references tag)) - (end (current-time)) ) - (message "Analysis took %.2f seconds." (semantic-elapsed-time start end)) + (message "Analysis took %.2f seconds." (semantic-elapsed-time start nil)) (if sac (progn (require 'eieio-datadebug) @@ -348,7 +347,7 @@ Only works for tags in the global namespace." (push-mark) (semantic-go-to-tag target) - (switch-to-buffer (current-buffer)) + (pop-to-buffer-same-window (current-buffer)) (semantic-momentary-highlight-tag target)) ) diff --git a/lisp/cedet/semantic/bovine.el b/lisp/cedet/semantic/bovine.el index 3c33eebb493..a3776b8d64f 100644 --- a/lisp/cedet/semantic/bovine.el +++ b/lisp/cedet/semantic/bovine.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/bovine/c.el b/lisp/cedet/semantic/bovine/c.el index 3200a5c1435..8dc04886158 100644 --- a/lisp/cedet/semantic/bovine/c.el +++ b/lisp/cedet/semantic/bovine/c.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/bovine/debug.el b/lisp/cedet/semantic/bovine/debug.el index f7bc20687e4..79aa400180f 100644 --- a/lisp/cedet/semantic/bovine/debug.el +++ b/lisp/cedet/semantic/bovine/debug.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/bovine/el.el b/lisp/cedet/semantic/bovine/el.el index ba6b05d7600..f5931e4f2cc 100644 --- a/lisp/cedet/semantic/bovine/el.el +++ b/lisp/cedet/semantic/bovine/el.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/bovine/gcc.el b/lisp/cedet/semantic/bovine/gcc.el index e4864bc6ca5..36f09354490 100644 --- a/lisp/cedet/semantic/bovine/gcc.el +++ b/lisp/cedet/semantic/bovine/gcc.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/bovine/grammar.el b/lisp/cedet/semantic/bovine/grammar.el index d34850f8032..28af05d95ef 100644 --- a/lisp/cedet/semantic/bovine/grammar.el +++ b/lisp/cedet/semantic/bovine/grammar.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -505,7 +505,7 @@ Menu items are appended to the common grammar menu.") ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/bovine/make.el b/lisp/cedet/semantic/bovine/make.el index 2a224bd99be..691ac0e85a0 100644 --- a/lisp/cedet/semantic/bovine/make.el +++ b/lisp/cedet/semantic/bovine/make.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/bovine/scm.el b/lisp/cedet/semantic/bovine/scm.el index 2e87993d0fe..547ca7a5fcd 100644 --- a/lisp/cedet/semantic/bovine/scm.el +++ b/lisp/cedet/semantic/bovine/scm.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/chart.el b/lisp/cedet/semantic/chart.el index 4f8ae245bd4..8063f2cbb15 100644 --- a/lisp/cedet/semantic/chart.el +++ b/lisp/cedet/semantic/chart.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/complete.el b/lisp/cedet/semantic/complete.el index 5bd76f018a1..325ca1f4414 100644 --- a/lisp/cedet/semantic/complete.el +++ b/lisp/cedet/semantic/complete.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -2120,7 +2120,7 @@ completion works." (when (semantic-tag-p tag) (push-mark) (semantic-go-to-tag tag) - (switch-to-buffer (current-buffer)) + (pop-to-buffer-same-window (current-buffer)) (semantic-momentary-highlight-tag tag) (message "%S: %s " (semantic-tag-class tag) diff --git a/lisp/cedet/semantic/ctxt.el b/lisp/cedet/semantic/ctxt.el index 01e156267af..13bea302658 100644 --- a/lisp/cedet/semantic/ctxt.el +++ b/lisp/cedet/semantic/ctxt.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/db-debug.el b/lisp/cedet/semantic/db-debug.el index 9e6d725f4e6..8595cceeca2 100644 --- a/lisp/cedet/semantic/db-debug.el +++ b/lisp/cedet/semantic/db-debug.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/db-ebrowse.el b/lisp/cedet/semantic/db-ebrowse.el index 187f72242d5..5b4e7eba27d 100644 --- a/lisp/cedet/semantic/db-ebrowse.el +++ b/lisp/cedet/semantic/db-ebrowse.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/db-el.el b/lisp/cedet/semantic/db-el.el index 89bbd1c0c29..768af034c62 100644 --- a/lisp/cedet/semantic/db-el.el +++ b/lisp/cedet/semantic/db-el.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/db-file.el b/lisp/cedet/semantic/db-file.el index ed8d7bb144b..1e398c5a283 100644 --- a/lisp/cedet/semantic/db-file.el +++ b/lisp/cedet/semantic/db-file.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/db-find.el b/lisp/cedet/semantic/db-find.el index c09af59ea70..1f5de71c53d 100644 --- a/lisp/cedet/semantic/db-find.el +++ b/lisp/cedet/semantic/db-find.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -930,7 +930,7 @@ but should be good enough for debugging assertions." (length result)))) (defun semanticdb-find-result-with-nil-p (resultp) - "Non-nil of RESULTP is in the form of a semanticdb search result. + "Non-nil if RESULTP is in the form of a semanticdb search result. The value nil is valid where a TABLE usually is, but only if the TAG results include overlays. This query only really tests the first entry in the list that is RESULTP, diff --git a/lisp/cedet/semantic/db-global.el b/lisp/cedet/semantic/db-global.el index 0afa6619d25..38fec0203a5 100644 --- a/lisp/cedet/semantic/db-global.el +++ b/lisp/cedet/semantic/db-global.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/db-javascript.el b/lisp/cedet/semantic/db-javascript.el index e8a3edcaf02..348512a212f 100644 --- a/lisp/cedet/semantic/db-javascript.el +++ b/lisp/cedet/semantic/db-javascript.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/db-mode.el b/lisp/cedet/semantic/db-mode.el index 3bd991b368a..8072ca9e69c 100644 --- a/lisp/cedet/semantic/db-mode.el +++ b/lisp/cedet/semantic/db-mode.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/db-ref.el b/lisp/cedet/semantic/db-ref.el index a75a73ce103..049420ee746 100644 --- a/lisp/cedet/semantic/db-ref.el +++ b/lisp/cedet/semantic/db-ref.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/db-typecache.el b/lisp/cedet/semantic/db-typecache.el index 8c8cf15eaf2..68f9e200ede 100644 --- a/lisp/cedet/semantic/db-typecache.el +++ b/lisp/cedet/semantic/db-typecache.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/db.el b/lisp/cedet/semantic/db.el index 0ba9f2f9c68..4d9daaf54ef 100644 --- a/lisp/cedet/semantic/db.el +++ b/lisp/cedet/semantic/db.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -595,7 +595,7 @@ This will call `semantic-fetch-tags' if that file is in memory." (kill-buffer buff)))))) (cl-defmethod semanticdb-needs-refresh-p ((obj semanticdb-table)) - "Return non-nil of OBJ's tag list is out of date. + "Return non-nil if OBJ's tag list is out of date. The file associated with OBJ does not need to be in a buffer." (let* ((ff (semanticdb-full-filename obj)) (buff (semanticdb-in-buffer-p obj)) diff --git a/lisp/cedet/semantic/debug.el b/lisp/cedet/semantic/debug.el index 5c793e44aa9..c0a5fcb5e25 100644 --- a/lisp/cedet/semantic/debug.el +++ b/lisp/cedet/semantic/debug.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/decorate.el b/lisp/cedet/semantic/decorate.el index fc00a527bf3..ad866e9fe0f 100644 --- a/lisp/cedet/semantic/decorate.el +++ b/lisp/cedet/semantic/decorate.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/decorate/include.el b/lisp/cedet/semantic/decorate/include.el index c7b5eb55ef1..975ba343469 100644 --- a/lisp/cedet/semantic/decorate/include.el +++ b/lisp/cedet/semantic/decorate/include.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -467,7 +467,7 @@ its contents. (error "Could not location include %s" (semantic-tag-name tag))) ((get-file-buffer file) - (switch-to-buffer (get-file-buffer file))) + (pop-to-buffer-same-window (get-file-buffer file))) ((stringp file) (find-file file)) )))) diff --git a/lisp/cedet/semantic/decorate/mode.el b/lisp/cedet/semantic/decorate/mode.el index a749fca9ccd..fb05a35cce9 100644 --- a/lisp/cedet/semantic/decorate/mode.el +++ b/lisp/cedet/semantic/decorate/mode.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/dep.el b/lisp/cedet/semantic/dep.el index 8c3ec0e06f3..f8d830bc29e 100644 --- a/lisp/cedet/semantic/dep.el +++ b/lisp/cedet/semantic/dep.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/doc.el b/lisp/cedet/semantic/doc.el index c8be665727c..d2b075655da 100644 --- a/lisp/cedet/semantic/doc.el +++ b/lisp/cedet/semantic/doc.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/ede-grammar.el b/lisp/cedet/semantic/ede-grammar.el index d982b6e258d..967af0bc359 100644 --- a/lisp/cedet/semantic/ede-grammar.el +++ b/lisp/cedet/semantic/ede-grammar.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/edit.el b/lisp/cedet/semantic/edit.el index 9c7ae69081f..fc0a05a6a6e 100644 --- a/lisp/cedet/semantic/edit.el +++ b/lisp/cedet/semantic/edit.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -221,7 +221,7 @@ Argument START, END, and LENGTH specify the bounds of the change." ) (defun semantic-edits-change-in-one-tag-p (change hits) - "Return non-nil of the overlay CHANGE exists solely in one leaf tag. + "Return non-nil if the overlay CHANGE exists solely in one leaf tag. HITS is the list of tags that CHANGE is in. It can have more than one tag in it if the leaf tag is within a parent tag." (and (< (semantic-tag-start (car hits)) diff --git a/lisp/cedet/semantic/find.el b/lisp/cedet/semantic/find.el index bf8eb9df116..0959dfc7255 100644 --- a/lisp/cedet/semantic/find.el +++ b/lisp/cedet/semantic/find.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/format.el b/lisp/cedet/semantic/format.el index b724429850a..1ec8e68c372 100644 --- a/lisp/cedet/semantic/format.el +++ b/lisp/cedet/semantic/format.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/fw.el b/lisp/cedet/semantic/fw.el index 3527f3e6af8..ea3fc2a2d6e 100644 --- a/lisp/cedet/semantic/fw.el +++ b/lisp/cedet/semantic/fw.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/grammar-wy.el b/lisp/cedet/semantic/grammar-wy.el index 9b5370815e1..6e7a1ad398f 100644 --- a/lisp/cedet/semantic/grammar-wy.el +++ b/lisp/cedet/semantic/grammar-wy.el @@ -15,7 +15,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/grammar.el b/lisp/cedet/semantic/grammar.el index f57c54a25bb..61266bcc608 100644 --- a/lisp/cedet/semantic/grammar.el +++ b/lisp/cedet/semantic/grammar.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -583,7 +583,7 @@ Typically a DEFINE expression should look like this: ;; 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 <http://www.gnu.org/licenses/>. +;; along with this program. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/html.el b/lisp/cedet/semantic/html.el index 7901d6aec2d..4485a1f44c9 100644 --- a/lisp/cedet/semantic/html.el +++ b/lisp/cedet/semantic/html.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/ia-sb.el b/lisp/cedet/semantic/ia-sb.el index 7901dd53ddb..d7e1acae93b 100644 --- a/lisp/cedet/semantic/ia-sb.el +++ b/lisp/cedet/semantic/ia-sb.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/ia.el b/lisp/cedet/semantic/ia.el index 4696388a9c0..625c3ae9757 100644 --- a/lisp/cedet/semantic/ia.el +++ b/lisp/cedet/semantic/ia.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -163,7 +163,7 @@ Completion options are calculated with `semantic-analyze-possible-completions'." (if (not syms) (progn (message "No smart completions found.") - ;; Disabled - see http://debbugs.gnu.org/14522 + ;; Disabled - see https://debbugs.gnu.org/14522 ;; (message "No smart completions found. Trying Senator.") ;; (when (semantic-analyze-context-p a) ;; ;; This is a quick way of getting a nice completion list @@ -322,7 +322,7 @@ This helper manages the mark, buffer switching, and pulsing." (semantic-go-to-tag dest) ;; 3) go-to-tag doesn't switch the buffer in the current window, ;; so it is like find-file-noselect. Bring it forward. - (switch-to-buffer (current-buffer)) + (pop-to-buffer-same-window (current-buffer)) ;; 4) Fancy pulsing. (pulse-momentary-highlight-one-line (point)) ) diff --git a/lisp/cedet/semantic/idle.el b/lisp/cedet/semantic/idle.el index 787748692e1..a106725f86c 100644 --- a/lisp/cedet/semantic/idle.el +++ b/lisp/cedet/semantic/idle.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/imenu.el b/lisp/cedet/semantic/imenu.el index a521f313f99..5018e039d03 100644 --- a/lisp/cedet/semantic/imenu.el +++ b/lisp/cedet/semantic/imenu.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -488,7 +488,7 @@ Clears all imenu menus that may be depending on the database." ;;; Which function support ;; ;; The which-function library will display the current function in the -;; mode line. It tries do do this through imenu. With a semantic parsed +;; mode line. It tries to do this through imenu. With a semantic parsed ;; buffer, there is a much more efficient way of doing this. ;; Advise `which-function' so that we optionally use semantic tags ;; instead, and get better stuff. diff --git a/lisp/cedet/semantic/java.el b/lisp/cedet/semantic/java.el index 00f9ee783b5..3c81b7ae65f 100644 --- a/lisp/cedet/semantic/java.el +++ b/lisp/cedet/semantic/java.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/lex-spp.el b/lisp/cedet/semantic/lex-spp.el index cb33e483a6b..35d77a8f87a 100644 --- a/lisp/cedet/semantic/lex-spp.el +++ b/lisp/cedet/semantic/lex-spp.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/lex.el b/lisp/cedet/semantic/lex.el index b2a63cdcc3c..835888db2ad 100644 --- a/lisp/cedet/semantic/lex.el +++ b/lisp/cedet/semantic/lex.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -657,10 +657,9 @@ If universal argument ARG, then try the whole buffer." (let* ((start (current-time)) (result (semantic-lex (if arg (point-min) (point)) - (point-max))) - (end (current-time))) + (point-max)))) (message "Elapsed Time: %.2f seconds." - (semantic-elapsed-time start end)) + (semantic-elapsed-time start nil)) (pop-to-buffer "*Lexer Output*") (require 'pp) (erase-buffer) @@ -810,7 +809,7 @@ analyzer which might mistake a number for as a symbol." tmp-start (car semantic-lex-token-stream))) (setq tmp-start semantic-lex-end-point) (goto-char semantic-lex-end-point) - ;;(when (> (semantic-elapsed-time starttime (current-time)) + ;;(when (> (semantic-elapsed-time starttime nil) ;; semantic-lex-timeout) ;; (error "Timeout during lex at char %d" (point))) (semantic-throw-on-input 'lex) diff --git a/lisp/cedet/semantic/mru-bookmark.el b/lisp/cedet/semantic/mru-bookmark.el index 067439d4772..24863de01b1 100644 --- a/lisp/cedet/semantic/mru-bookmark.el +++ b/lisp/cedet/semantic/mru-bookmark.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -113,7 +113,7 @@ Uses `semantic-go-to-tag' and highlighting." (forward-char o)) (error nil)) ;; make it visible - (switch-to-buffer (current-buffer)) + (pop-to-buffer-same-window (current-buffer)) (semantic-momentary-highlight-tag tag) )) diff --git a/lisp/cedet/semantic/sb.el b/lisp/cedet/semantic/sb.el index 41fe8857ccf..fbec9f2b019 100644 --- a/lisp/cedet/semantic/sb.el +++ b/lisp/cedet/semantic/sb.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/scope.el b/lisp/cedet/semantic/scope.el index 9bade569659..717c2e30119 100644 --- a/lisp/cedet/semantic/scope.el +++ b/lisp/cedet/semantic/scope.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/senator.el b/lisp/cedet/semantic/senator.el index f1918c40918..ea796dd19f9 100644 --- a/lisp/cedet/semantic/senator.el +++ b/lisp/cedet/semantic/senator.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -530,11 +530,11 @@ Some tags such as includes have other reference features." ;; A tag ((semantic-tag-p result) (semantic-go-to-tag result) - (switch-to-buffer (current-buffer)) + (pop-to-buffer-same-window (current-buffer)) (semantic-momentary-highlight-tag result)) ;; Buffers ((bufferp result) - (switch-to-buffer result) + (pop-to-buffer-same-window result) (pulse-momentary-highlight-one-line (point))) ;; Files ((and (stringp result) (file-exists-p result)) diff --git a/lisp/cedet/semantic/sort.el b/lisp/cedet/semantic/sort.el index e77b64f7bab..32e39d7454f 100644 --- a/lisp/cedet/semantic/sort.el +++ b/lisp/cedet/semantic/sort.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/symref.el b/lisp/cedet/semantic/symref.el index a16672e39de..b9fe63d684b 100644 --- a/lisp/cedet/semantic/symref.el +++ b/lisp/cedet/semantic/symref.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/symref/cscope.el b/lisp/cedet/semantic/symref/cscope.el index 181e3997681..502c3ef9f3b 100644 --- a/lisp/cedet/semantic/symref/cscope.el +++ b/lisp/cedet/semantic/symref/cscope.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/symref/filter.el b/lisp/cedet/semantic/symref/filter.el index e3860333194..d5766af9b6e 100644 --- a/lisp/cedet/semantic/symref/filter.el +++ b/lisp/cedet/semantic/symref/filter.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -103,7 +103,7 @@ tag that contains point, and return that." (when (called-interactively-p 'interactive) (message "Found %d occurrences of %s in %.2f seconds" Lcount (semantic-tag-name target) - (semantic-elapsed-time start (current-time)))) + (semantic-elapsed-time start nil))) Lcount))) (defun semantic-symref-rename-local-variable () diff --git a/lisp/cedet/semantic/symref/global.el b/lisp/cedet/semantic/symref/global.el index e91ecf07bcc..35f6a249d99 100644 --- a/lisp/cedet/semantic/symref/global.el +++ b/lisp/cedet/semantic/symref/global.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/symref/grep.el b/lisp/cedet/semantic/symref/grep.el index 42dc40cce04..0b263d8cc2d 100644 --- a/lisp/cedet/semantic/symref/grep.el +++ b/lisp/cedet/semantic/symref/grep.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -38,16 +38,22 @@ ( ) "A symref tool implementation using grep. -This tool uses EDE to find he root of the project, then executes -find-grep in the project. The output is parsed for hits -and those hits returned.") +This tool uses EDE to find the root of the project, then executes +find-grep in the project. The output is parsed for hits and +those hits returned.") (defvar semantic-symref-filepattern-alist '((c-mode "*.[ch]") (c++-mode "*.[chCH]" "*.[ch]pp" "*.cc" "*.hh") - (html-mode "*.s?html" "*.php") + (html-mode "*.html" "*.shtml" "*.php") + (mhtml-mode "*.html" "*.shtml" "*.php") ; FIXME: remove + ; duplication of + ; HTML-related patterns. + ; Maybe they belong in the + ; major mode definition? (ruby-mode "*.r[bu]" "*.rake" "*.gemspec" "*.erb" "*.haml" "Rakefile" "Thorfile" "Capfile" "Guardfile" "Vagrantfile") + (python-mode "*.py" "*.pyi" "*.pyw") (perl-mode "*.pl" "*.PL") (cperl-mode "*.pl" "*.PL") (lisp-interaction-mode "*.el" "*.ede" ".emacs" "_emacs") @@ -58,7 +64,7 @@ See find -name man page for format.") (defun semantic-symref-derive-find-filepatterns (&optional mode) ;; FIXME: This should be moved to grep.el, where it could be used ;; for "C-u M-x grep" as well. - "Derive a list of file patterns for the current buffer. + "Derive a list of file (glob) patterns for the current buffer. Looks first in `semantic-symref-filepattern-alist'. If it is not there, it then looks in `auto-mode-alist', and attempts to derive something from that. @@ -78,23 +84,20 @@ Optional argument MODE specifies the `major-mode' to test." (error "Customize `semantic-symref-filepattern-alist' for %S" major-mode) (let ((args `("-name" ,(car pat)))) - (if (null (cdr args)) + (if (null (cdr pat)) args `("(" ,@args ,@(mapcan (lambda (s) `("-o" "-name" ,s)) pat) ")")))))) -(defvar grepflags) -(defvar greppattern) +(defvar semantic-symref-grep-flags) (defvar semantic-symref-grep-expand-keywords (condition-case nil (let* ((kw (copy-alist grep-expand-keywords)) - (C (assoc "<C>" kw)) - (R (assoc "<R>" kw))) - (setcdr C 'grepflags) - (setcdr R 'greppattern) - kw) + (C (assoc "<C>" kw))) + (setcdr C 'semantic-symref-grep-flags) + kw) (error nil)) "Grep expand keywords used when expanding templates for symref.") @@ -102,15 +105,15 @@ Optional argument MODE specifies the `major-mode' to test." "Use the grep template expand feature to create a grep command. ROOTDIR is the root location to run the `find' from. FILEPATTERN is a string representing find flags for searching file patterns. -GREPFLAGS are flags passed to grep, such as -n or -l. -GREPPATTERN is the pattern used by grep." +FLAGS are flags passed to Grep, such as -n or -l. +PATTERN is the pattern used by Grep." ;; We have grep-compute-defaults. Let's use it. (grep-compute-defaults) - (let* ((grepflags flags) - (greppattern pattern) + (let* ((semantic-symref-grep-flags flags) (grep-expand-keywords semantic-symref-grep-expand-keywords) (cmd (grep-expand-template (if (memq system-type '(windows-nt ms-dos)) + ;; FIXME: Is this still needed? ;; grep-find uses '--color=always' on MS-Windows ;; because it wants the colorized output, to show ;; it to the user. By contrast, here we don't show @@ -119,13 +122,9 @@ GREPPATTERN is the pattern used by grep." (replace-regexp-in-string "--color=always" "" grep-find-template t t) grep-find-template) - greppattern + pattern filepattern rootdir))) - ;; http://debbugs.gnu.org/20719 - (when (string-match "find \\(\\.\\)" cmd) - (setq cmd (replace-match rootdir t t cmd 1))) - ;;(message "New command: %s" cmd) cmd)) (defcustom semantic-symref-grep-shell shell-file-name @@ -137,7 +136,7 @@ This shell should support pipe redirect syntax." (cl-defmethod semantic-symref-perform-search ((tool semantic-symref-tool-grep)) "Perform a search with Grep." ;; Grep doesn't support some types of searches. - (let ((st (oref tool :searchtype))) + (let ((st (oref tool searchtype))) (when (not (memq st '(symbol regexp))) (error "Symref impl GREP does not support searchtype of %s" st)) ) @@ -147,20 +146,19 @@ This shell should support pipe redirect syntax." (filepatterns (semantic-symref-derive-find-filepatterns)) (filepattern (mapconcat #'shell-quote-argument filepatterns " ")) ;; Grep based flags. - (grepflags (cond ((eq (oref tool :resulttype) 'file) + (grepflags (cond ((eq (oref tool resulttype) 'file) "-l ") - ((eq (oref tool :searchtype) 'regexp) + ((eq (oref tool searchtype) 'regexp) "-nE ") (t "-n "))) - (greppat (shell-quote-argument - (cond ((eq (oref tool :searchtype) 'regexp) - (oref tool searchfor)) - (t - ;; Can't use the word boundaries: Grep - ;; doesn't always agrees with the language - ;; syntax on those. - (format "\\(^\\|\\W\\)%s\\(\\W\\|$\\)" - (oref tool searchfor)))))) + (greppat (cond ((eq (oref tool searchtype) 'regexp) + (oref tool searchfor)) + (t + ;; Can't use the word boundaries: Grep + ;; doesn't always agree with the language + ;; syntax on those. + (format "\\(^\\|\\W\\)%s\\(\\W\\|$\\)" + (oref tool searchfor))))) ;; Misc (b (get-buffer-create "*Semantic SymRef*")) (ans nil) @@ -189,26 +187,25 @@ This shell should support pipe redirect syntax." ;; Return the answer ans)) -(defconst semantic-symref-grep--line-re - "^\\(\\(?:[a-zA-Z]:\\)?[^:\n]+\\):\\([0-9]+\\):") - (cl-defmethod semantic-symref-parse-tool-output-one-line ((tool semantic-symref-tool-grep)) "Parse one line of grep output, and return it as a match list. Moves cursor to end of the match." - (cond ((eq (oref tool :resulttype) 'file) - ;; Search for files - (when (re-search-forward "^\\([^\n]+\\)$" nil t) - (match-string 1))) - ((eq (oref tool :resulttype) 'line-and-text) - (when (re-search-forward semantic-symref-grep--line-re nil t) - (list (string-to-number (match-string 2)) - (match-string 1) - (buffer-substring-no-properties (point) (line-end-position))))) - (t - (when (re-search-forward semantic-symref-grep--line-re nil t) - (cons (string-to-number (match-string 2)) - (match-string 1)) - )))) + (pcase-let + ((`(,grep-re ,file-group ,line-group . ,_) (car grep-regexp-alist))) + (cond ((eq (oref tool resulttype) 'file) + ;; Search for files + (when (re-search-forward "^\\([^\n]+\\)$" nil t) + (match-string 1))) + ((eq (oref tool resulttype) 'line-and-text) + (when (re-search-forward grep-re nil t) + (list (string-to-number (match-string line-group)) + (match-string file-group) + (buffer-substring-no-properties (point) (line-end-position))))) + (t + (when (re-search-forward grep-re nil t) + (cons (string-to-number (match-string line-group)) + (match-string file-group)) + ))))) (provide 'semantic/symref/grep) diff --git a/lisp/cedet/semantic/symref/idutils.el b/lisp/cedet/semantic/symref/idutils.el index 3c94f01c6d9..290bed12245 100644 --- a/lisp/cedet/semantic/symref/idutils.el +++ b/lisp/cedet/semantic/symref/idutils.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/symref/list.el b/lisp/cedet/semantic/symref/list.el index e1a789d673a..d0ad23934d9 100644 --- a/lisp/cedet/semantic/symref/list.el +++ b/lisp/cedet/semantic/symref/list.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/tag-file.el b/lisp/cedet/semantic/tag-file.el index ac11dbeb44c..65d9e2cae53 100644 --- a/lisp/cedet/semantic/tag-file.el +++ b/lisp/cedet/semantic/tag-file.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/tag-ls.el b/lisp/cedet/semantic/tag-ls.el index 3a66fc7df5c..aa9b4b97142 100644 --- a/lisp/cedet/semantic/tag-ls.el +++ b/lisp/cedet/semantic/tag-ls.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/tag-write.el b/lisp/cedet/semantic/tag-write.el index 76a1d79e10d..6ce77edf102 100644 --- a/lisp/cedet/semantic/tag-write.el +++ b/lisp/cedet/semantic/tag-write.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/tag.el b/lisp/cedet/semantic/tag.el index 6b2a49558d6..59788c774e9 100644 --- a/lisp/cedet/semantic/tag.el +++ b/lisp/cedet/semantic/tag.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/texi.el b/lisp/cedet/semantic/texi.el index 79f879899d3..e9bc3415e33 100644 --- a/lisp/cedet/semantic/texi.el +++ b/lisp/cedet/semantic/texi.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/util-modes.el b/lisp/cedet/semantic/util-modes.el index 70f3a343343..b31fd07f3c3 100644 --- a/lisp/cedet/semantic/util-modes.el +++ b/lisp/cedet/semantic/util-modes.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/util.el b/lisp/cedet/semantic/util.el index 31562bc16ab..f5d9054bdc3 100644 --- a/lisp/cedet/semantic/util.el +++ b/lisp/cedet/semantic/util.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/wisent.el b/lisp/cedet/semantic/wisent.el index 90a863bd3c1..235f83821d5 100644 --- a/lisp/cedet/semantic/wisent.el +++ b/lisp/cedet/semantic/wisent.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/wisent/comp.el b/lisp/cedet/semantic/wisent/comp.el index cb19b1b861f..0ed9ba32597 100644 --- a/lisp/cedet/semantic/wisent/comp.el +++ b/lisp/cedet/semantic/wisent/comp.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/wisent/grammar.el b/lisp/cedet/semantic/wisent/grammar.el index d0dc3e7b39a..29106da5f9f 100644 --- a/lisp/cedet/semantic/wisent/grammar.el +++ b/lisp/cedet/semantic/wisent/grammar.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -370,7 +370,7 @@ Menu items are appended to the common grammar menu.") ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.") +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.") (defvar wisent-make-parsers--python-license ";; It is derived in part from the Python grammar, used under the diff --git a/lisp/cedet/semantic/wisent/java-tags.el b/lisp/cedet/semantic/wisent/java-tags.el index 9deb997435f..479fc7fbe87 100644 --- a/lisp/cedet/semantic/wisent/java-tags.el +++ b/lisp/cedet/semantic/wisent/java-tags.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -75,7 +75,7 @@ This function override `get-local-variables'." ;; Add 'this' if in a fcn (when (semantic-tag-of-class-p ct 'function) ;; Append a new tag THIS into our space. - (setq vars (cons (semantic-tag-new-variable + (setq vars (cons (semantic-tag-new-variable "this" (semantic-tag-name (semantic-current-tag-parent)) nil) vars))) diff --git a/lisp/cedet/semantic/wisent/javascript.el b/lisp/cedet/semantic/wisent/javascript.el index cf1911b46c4..b73cb01819a 100644 --- a/lisp/cedet/semantic/wisent/javascript.el +++ b/lisp/cedet/semantic/wisent/javascript.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/wisent/python.el b/lisp/cedet/semantic/wisent/python.el index 4e7ee3d0cf5..591895d5aa4 100644 --- a/lisp/cedet/semantic/wisent/python.el +++ b/lisp/cedet/semantic/wisent/python.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/semantic/wisent/wisent.el b/lisp/cedet/semantic/wisent/wisent.el index 08cad524aed..d4d2b3d2ace 100644 --- a/lisp/cedet/semantic/wisent/wisent.el +++ b/lisp/cedet/semantic/wisent/wisent.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/srecode.el b/lisp/cedet/srecode.el index e824062f7be..c8eee15bae4 100644 --- a/lisp/cedet/srecode.el +++ b/lisp/cedet/srecode.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/srecode/args.el b/lisp/cedet/srecode/args.el index facf96e9afb..c4a15a2d6ae 100644 --- a/lisp/cedet/srecode/args.el +++ b/lisp/cedet/srecode/args.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/srecode/compile.el b/lisp/cedet/srecode/compile.el index 1b6cd704095..21ab9b8f2e7 100644 --- a/lisp/cedet/srecode/compile.el +++ b/lisp/cedet/srecode/compile.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/srecode/cpp.el b/lisp/cedet/srecode/cpp.el index 8f9c0832844..fe1dd77ae92 100644 --- a/lisp/cedet/srecode/cpp.el +++ b/lisp/cedet/srecode/cpp.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/srecode/ctxt.el b/lisp/cedet/srecode/ctxt.el index 28dbd367399..664e06d73e7 100644 --- a/lisp/cedet/srecode/ctxt.el +++ b/lisp/cedet/srecode/ctxt.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/srecode/dictionary.el b/lisp/cedet/srecode/dictionary.el index 2844c1b52da..6c8fd655d7b 100644 --- a/lisp/cedet/srecode/dictionary.el +++ b/lisp/cedet/srecode/dictionary.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -612,10 +612,9 @@ STATE is the current compiler state." (srecode-get-mode-table modesym)) (error "No table found for mode %S" modesym))) (dict (srecode-create-dictionary (current-buffer))) - (end (current-time)) ) (message "Creating a dictionary took %.2f seconds." - (semantic-elapsed-time start end)) + (semantic-elapsed-time start nil)) (data-debug-new-buffer "*SRECODE ADEBUG*") (data-debug-insert-object-slots dict "*"))) diff --git a/lisp/cedet/srecode/document.el b/lisp/cedet/srecode/document.el index babd177c9bd..f8fcdef5840 100644 --- a/lisp/cedet/srecode/document.el +++ b/lisp/cedet/srecode/document.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/srecode/el.el b/lisp/cedet/srecode/el.el index 526a2a21070..e725074b7a6 100644 --- a/lisp/cedet/srecode/el.el +++ b/lisp/cedet/srecode/el.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/srecode/expandproto.el b/lisp/cedet/srecode/expandproto.el index 74742f66d7e..87bcdb3b944 100644 --- a/lisp/cedet/srecode/expandproto.el +++ b/lisp/cedet/srecode/expandproto.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/srecode/extract.el b/lisp/cedet/srecode/extract.el index 34771859d69..bbde255b413 100644 --- a/lisp/cedet/srecode/extract.el +++ b/lisp/cedet/srecode/extract.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/srecode/fields.el b/lisp/cedet/srecode/fields.el index 0bef8545ebe..7818a66a576 100644 --- a/lisp/cedet/srecode/fields.el +++ b/lisp/cedet/srecode/fields.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/srecode/filters.el b/lisp/cedet/srecode/filters.el index 7b9b9798f5d..1be451be281 100644 --- a/lisp/cedet/srecode/filters.el +++ b/lisp/cedet/srecode/filters.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -54,4 +54,3 @@ (provide 'srecode/filters) ;;; srecode/filters.el ends here - diff --git a/lisp/cedet/srecode/find.el b/lisp/cedet/srecode/find.el index 913013c259c..35b3753c915 100644 --- a/lisp/cedet/srecode/find.el +++ b/lisp/cedet/srecode/find.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/srecode/getset.el b/lisp/cedet/srecode/getset.el index 0b32af2351d..b23ae8ecebe 100644 --- a/lisp/cedet/srecode/getset.el +++ b/lisp/cedet/srecode/getset.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/srecode/insert.el b/lisp/cedet/srecode/insert.el index c582e328b2b..1e2cbc84e6a 100644 --- a/lisp/cedet/srecode/insert.el +++ b/lisp/cedet/srecode/insert.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/srecode/java.el b/lisp/cedet/srecode/java.el index 30734f2b9e3..0ede5d28b07 100644 --- a/lisp/cedet/srecode/java.el +++ b/lisp/cedet/srecode/java.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/srecode/map.el b/lisp/cedet/srecode/map.el index d5b4c5ffc8c..5b5d1fdd47d 100644 --- a/lisp/cedet/srecode/map.el +++ b/lisp/cedet/srecode/map.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -224,10 +224,9 @@ Optional argument RESET forces a reset of the current map." (require 'data-debug) (let ((start (current-time)) (p (srecode-get-maps t)) ;; Time the reset. - (end (current-time)) ) (message "Updating the map took %.2f seconds." - (semantic-elapsed-time start end)) + (semantic-elapsed-time start nil)) (data-debug-new-buffer "*SRECODE ADEBUG*") (data-debug-insert-stuff-list p "*"))) diff --git a/lisp/cedet/srecode/mode.el b/lisp/cedet/srecode/mode.el index 566ab5d366a..ddbce0a63c5 100644 --- a/lisp/cedet/srecode/mode.el +++ b/lisp/cedet/srecode/mode.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/srecode/semantic.el b/lisp/cedet/srecode/semantic.el index 7e24a320483..44c5248ad96 100644 --- a/lisp/cedet/srecode/semantic.el +++ b/lisp/cedet/srecode/semantic.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/srecode/srt-mode.el b/lisp/cedet/srecode/srt-mode.el index d3ce72aef80..602a1ce843f 100644 --- a/lisp/cedet/srecode/srt-mode.el +++ b/lisp/cedet/srecode/srt-mode.el @@ -15,7 +15,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/cedet/srecode/srt.el b/lisp/cedet/srecode/srt.el index 070261c47c2..4c885fe9abf 100644 --- a/lisp/cedet/srecode/srt.el +++ b/lisp/cedet/srecode/srt.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/srecode/table.el b/lisp/cedet/srecode/table.el index 98e0c2d1d14..f85a88165ff 100644 --- a/lisp/cedet/srecode/table.el +++ b/lisp/cedet/srecode/table.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -288,4 +288,3 @@ Use PREDICATE is the same as for the `sort' function." (provide 'srecode/table) ;;; srecode/table.el ends here - diff --git a/lisp/cedet/srecode/template.el b/lisp/cedet/srecode/template.el index 95510772ca4..7da896989f0 100644 --- a/lisp/cedet/srecode/template.el +++ b/lisp/cedet/srecode/template.el @@ -15,7 +15,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cedet/srecode/texi.el b/lisp/cedet/srecode/texi.el index 5cc57bebee5..9bf52e10f60 100644 --- a/lisp/cedet/srecode/texi.el +++ b/lisp/cedet/srecode/texi.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/char-fold.el b/lisp/char-fold.el index ea4486353a7..b24363530f3 100644 --- a/lisp/char-fold.el +++ b/lisp/char-fold.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: @@ -196,7 +196,7 @@ from which to start." ;;; If N suffixes match, we "branch" out into N+1 executions for the ;;; length of the longest match. This means "fix" will match "fix" but ;;; not "fⅸ", but it's necessary to keep the regexp size from scaling -;;; exponentially. See https://lists.gnu.org/archive/html/emacs-devel/2015-11/msg02562.html +;;; exponentially. See https://lists.gnu.org/r/emacs-devel/2015-11/msg02562.html (let ((subs (substring string (1+ i) (+ i 1 max-length)))) ;; `i' is still going to inc by 1 below. (setq i (+ i max-length)) diff --git a/lisp/chistory.el b/lisp/chistory.el index 8b6f3d1525e..c270bffe115 100644 --- a/lisp/chistory.el +++ b/lisp/chistory.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/cmuscheme.el b/lisp/cmuscheme.el index 0a41a401af1..1bf79f3c1ae 100644 --- a/lisp/cmuscheme.el +++ b/lisp/cmuscheme.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/color.el b/lisp/color.el index 6dbf3d55cbc..2db01a53c84 100644 --- a/lisp/color.el +++ b/lisp/color.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -42,7 +42,7 @@ (defun color-name-to-rgb (color &optional frame) "Convert COLOR string to a list of normalized RGB components. COLOR should be a color name (e.g. \"white\") or an RGB triplet -string (e.g. \"#ff12ec\"). +string (e.g. \"#ffff1122eecc\"). Normally the return value is a list of three floating-point numbers, (RED GREEN BLUE), each between 0.0 and 1.0 inclusive. @@ -68,7 +68,8 @@ or 2; use the latter if you need a 24-bit specification of a color." (defun color-complement (color-name) "Return the color that is the complement of COLOR-NAME. COLOR-NAME should be a string naming a color (e.g. \"white\"), or -a string specifying a color's RGB components (e.g. \"#ff12ec\")." +a string specifying a color's RGB +components (e.g. \"#ffff1212ecec\")." (let ((color (color-name-to-rgb color-name))) (list (- 1.0 (nth 0 color)) (- 1.0 (nth 1 color)) @@ -82,9 +83,10 @@ resulting list." (let* ((r (nth 0 start)) (g (nth 1 start)) (b (nth 2 start)) - (r-step (/ (- (nth 0 stop) r) (1+ step-number))) - (g-step (/ (- (nth 1 stop) g) (1+ step-number))) - (b-step (/ (- (nth 2 stop) b) (1+ step-number))) + (interval (float (1+ step-number))) + (r-step (/ (- (nth 0 stop) r) interval)) + (g-step (/ (- (nth 1 stop) g) interval)) + (b-step (/ (- (nth 2 stop) b) interval)) result) (dotimes (_ step-number) (push (list (setq r (+ r r-step)) @@ -177,7 +179,8 @@ each element is between 0.0 and 1.0, inclusive." ((= r max) (- bc gc)) ((= g max) (+ 2.0 rc (- bc))) (t (+ 4.0 gc (- rc)))) - 6.0) 1.0))) + 6.0) + 1.0))) (list h s l))))) (defun color-srgb-to-xyz (red green blue) @@ -211,9 +214,18 @@ RED, GREEN and BLUE should be between 0.0 and 1.0, inclusive." (* 12.92 b) (- (* 1.055 (expt b (/ 2.4))) 0.055))))) +(defconst color-d75-xyz '(0.9497 1.0 1.2264) + "D75 white point in CIE XYZ.") + (defconst color-d65-xyz '(0.950455 1.0 1.088753) "D65 white point in CIE XYZ.") +(defconst color-d55-xyz '(0.9568 1.0 0.9215) + "D55 white point in CIE XYZ.") + +(defconst color-d50-xyz '(0.9642 1.0 0.8249) + "D50 white point in CIE XYZ.") + (defconst color-cie-ε (/ 216 24389.0)) (defconst color-cie-κ (/ 24389 27.0)) @@ -268,6 +280,24 @@ conversion. If omitted or nil, use `color-d65-xyz'." "Convert CIE L*a*b* to RGB." (apply 'color-xyz-to-srgb (color-lab-to-xyz L a b))) +(defun color-xyz-to-xyy (X Y Z) + "Convert CIE XYZ to xyY." + (let ((d (float (+ X Y Z)))) + (list (/ X d) (/ Y d) Y))) + +(defun color-xyy-to-xyz (x y Y) + "Convert CIE xyY to XYZ." + (let ((y (float y))) + (list (/ (* Y x) y) Y (/ (* Y (- 1 x y)) y)))) + +(defun color-lab-to-lch (L a b) + "Convert CIE L*a*b* to L*C*h*" + (list L (sqrt (+ (* a a) (* b b))) (atan b a))) + +(defun color-lch-to-lab (L C h) + "Convert CIE L*a*b* to L*C*h*" + (list L (* C (cos h)) (* C (sin h)))) + (defun color-cie-de2000 (color1 color2 &optional kL kC kH) "Return the CIEDE2000 color distance between COLOR1 and COLOR2. Both COLOR1 and COLOR2 should be in CIE L*a*b* format, as diff --git a/lisp/comint.el b/lisp/comint.el index 51b659167d5..aa7dab28f32 100644 --- a/lisp/comint.el +++ b/lisp/comint.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -678,7 +678,7 @@ Entry to this mode runs the hooks on `comint-mode-hook'." ;; comint-scroll-show-maximum-output is nil, and no-one can remember ;; what the original problem was. If there are problems with point ;; not going to the end, consider re-enabling this. - ;; http://lists.gnu.org/archive/html/emacs-devel/2007-08/msg00827.html + ;; https://lists.gnu.org/r/emacs-devel/2007-08/msg00827.html ;; ;; This makes it really work to keep point at the bottom. ;; (make-local-variable 'scroll-conservatively) diff --git a/lisp/completion.el b/lisp/completion.el index d56ea93ad1b..42366acbf7a 100644 --- a/lisp/completion.el +++ b/lisp/completion.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/composite.el b/lisp/composite.el index a3e00013466..29fc753d5ae 100644 --- a/lisp/composite.el +++ b/lisp/composite.el @@ -25,7 +25,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -337,8 +337,9 @@ When Automatic Composition mode is on, this function also finds a chunk of text that is automatically composed. If such a chunk is found closer to POS than the position that has `composition' property, the value is a list of FROM, TO, and a glyph-string -that specifies how the chunk is to be composed. See the function -`composition-get-gstring' for the format of the glyph-string." +that specifies how the chunk is to be composed; DETAIL-P is +ignored in this case. See the function `composition-get-gstring' +for the format of the glyph-string." (let ((result (find-composition-internal pos limit string detail-p))) (if (and detail-p (> (length result) 3) (nth 2 result) (not (nth 3 result))) ;; This is a valid rule-base composition. @@ -442,8 +443,10 @@ after a sequence of character events." (defsubst lglyph-set-adjustment (glyph &optional xoff yoff wadjust) (aset glyph 9 (vector (or xoff 0) (or yoff 0) (or wadjust 0)))) +;; Return the shallow Copy of GLYPH. (defsubst lglyph-copy (glyph) (copy-sequence glyph)) +;; Insert GLYPH at the index IDX of GSTRING. (defun lgstring-insert-glyph (gstring idx glyph) (let ((nglyphs (lgstring-glyph-len gstring)) (i idx)) @@ -459,6 +462,18 @@ after a sequence of character events." (lgstring-set-glyph gstring i glyph) gstring)) +;; Remove glyph at IDX from GSTRING. +(defun lgstring-remove-glyph (gstring idx) + (setq gstring (copy-sequence gstring)) + (lgstring-set-id gstring nil) + (let ((len (length gstring))) + (setq idx (+ idx 3)) + (while (< idx len) + (aset gstring (1- idx) (aref gstring idx)) + (setq idx (1+ idx))) + (aset gstring (1- len) nil)) + gstring) + (defun compose-glyph-string (gstring from to) (let ((glyph (lgstring-glyph gstring from)) from-pos to-pos) diff --git a/lisp/cus-dep.el b/lisp/cus-dep.el index ecdda4e7023..6c513640bb3 100644 --- a/lisp/cus-dep.el +++ b/lisp/cus-dep.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/cus-edit.el b/lisp/cus-edit.el index ca6b8a38d99..4965adfd56c 100644 --- a/lisp/cus-edit.el +++ b/lisp/cus-edit.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -986,7 +986,7 @@ If given a prefix (or a COMMENT argument), also prompt for a comment." current-prefix-arg)) (custom-load-symbol variable) (custom-push-theme 'theme-value variable 'user 'set (custom-quote value)) - (funcall (or (get variable 'custom-set) 'set-default) variable value) + (funcall (or (get variable 'custom-set) #'set-default) variable value) (put variable 'customized-value (list (custom-quote value))) (cond ((string= comment "") (put variable 'variable-comment nil) @@ -1159,7 +1159,7 @@ Show the buffer in another window, but don't select it." (unless (eq symbol basevar) (message "`%s' is an alias for `%s'" symbol basevar)))) -(defvar customize-changed-options-previous-release "24.5" +(defvar customize-changed-options-previous-release "25.3" "Version for `customize-changed-options' to refer back to by default.") ;; Packages will update this variable, so make it available. @@ -2518,7 +2518,10 @@ try matching its doc string against `custom-guess-doc-alist'." (copy-sequence type) (list type)))) (when options - (widget-put tmp :options options)) + ;; This used to use widget-put, but with strict plists that + ;; fails when type is an even-length list, eg (repeat character). + ;; Passing our result through widget-convert makes it a valid widget. + (setcdr tmp (append (list :options options) (cdr tmp)))) tmp)) (defun custom-variable-value-create (widget) @@ -2796,7 +2799,7 @@ If STATE is nil, the value is computed by `custom-variable-state'." ;; init-file-user rather than user-init-file. This is in case ;; cus-edit is loaded by something in site-start.el, because ;; user-init-file is not set at that stage. - ;; http://lists.gnu.org/archive/html/emacs-devel/2007-10/msg00310.html + ;; https://lists.gnu.org/r/emacs-devel/2007-10/msg00310.html ,@(when (or custom-file init-file-user) '(("Save for Future Sessions" custom-variable-save (lambda (widget) diff --git a/lisp/cus-face.el b/lisp/cus-face.el index aa5ecd2e223..0fc084e69b3 100644 --- a/lisp/cus-face.el +++ b/lisp/cus-face.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/cus-start.el b/lisp/cus-start.el index 744fe7f69ee..a5ec223fe51 100644 --- a/lisp/cus-start.el +++ b/lisp/cus-start.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -223,6 +223,14 @@ Leaving \"Default\" unchecked is equivalent with specifying a default of (visible-bell display boolean) (no-redraw-on-reenter display boolean) + ;; doc.c + (text-quoting-style display + (choice + (const :tag "Prefer \\=‘curved\\=’ quotes, if possible" nil) + (const :tag "\\=‘Curved\\=’ quotes" curved) + (const :tag "\\='Straight\\=' quotes" straight) + (const :tag "\\=`Grave\\=' quotes (no translation)" grave))) + ;; dosfns.c (dos-display-scancodes display boolean) (dos-hyper-key keyboard integer) @@ -319,6 +327,13 @@ Leaving \"Default\" unchecked is equivalent with specifying a default of (const :tag "Always" t) (repeat (symbol :tag "Parameter"))) "25.1") + (iconify-child-frame frames + (choice + (const :tag "Do nothing" nil) + (const :tag "Iconify top level frame instead" iconify-top-level) + (const :tag "Make frame invisible instead" make-invisible) + (const :tag "Iconify" t)) + "26.1") (tooltip-reuse-hidden-frame tooltip boolean "26.1") ;; fringe.c (overflow-newline-into-fringe fringe boolean) @@ -584,6 +599,38 @@ since it could result in memory overflow and make Emacs crash." (const :tag "Grow only" :value grow-only)) "25.1") (display-raw-bytes-as-hex display boolean "26.1") + (display-line-numbers display-line-numbers + (choice + (const :tag "Off (nil)" :value nil) + (const :tag "Absolute line numbers" + :value t) + (const :tag "Relative line numbers" + :value relative) + (const :tag "Visually relative line numbers" + :value visual)) + "26.1") + (display-line-numbers-width display-line-numbers + (choice + (const :tag "Dynamically computed" + :value nil) + (integer :menu-tag "Fixed number of columns" + :value 2 + :format "%v")) + "26.1") + (display-line-numbers-current-absolute display-line-numbers + (choice + (const :tag "Display actual number of current line" + :value t) + (const :tag "Display zero as number of current line" + :value nil)) + "26.1") + (display-line-numbers-widen display-line-numbers + (choice + (const :tag "Disregard narrowing when calculating line numbers" + :value t) + (const :tag "Count lines from beginning of narrowed region" + :value nil)) + "26.1") ;; xfaces.c (scalable-fonts-allowed display boolean "22.1") ;; xfns.c diff --git a/lisp/cus-theme.el b/lisp/cus-theme.el index d2ee14d8bdf..1aac7bf631d 100644 --- a/lisp/cus-theme.el +++ b/lisp/cus-theme.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: diff --git a/lisp/custom.el b/lisp/custom.el index ecfa34db5bb..352fc6bd530 100644 --- a/lisp/custom.el +++ b/lisp/custom.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/dabbrev.el b/lisp/dabbrev.el index 86eb4e737df..4bdfffe864a 100644 --- a/lisp/dabbrev.el +++ b/lisp/dabbrev.el @@ -24,7 +24,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/delim-col.el b/lisp/delim-col.el index db89206f32f..175bf375162 100644 --- a/lisp/delim-col.el +++ b/lisp/delim-col.el @@ -2,8 +2,8 @@ ;; Copyright (C) 1999-2017 Free Software Foundation, Inc. -;; Author: Vinicius Jose Latorre <viniciusjl@ig.com.br> -;; Maintainer: Vinicius Jose Latorre <viniciusjl@ig.com.br> +;; Author: Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> +;; Maintainer: Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> ;; Version: 2.1 ;; Keywords: internal ;; X-URL: http://www.emacswiki.org/cgi-bin/wiki/ViniciusJoseLatorre @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/delsel.el b/lisp/delsel.el index d5f4736fddb..65b2cb85cea 100644 --- a/lisp/delsel.el +++ b/lisp/delsel.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -70,9 +70,12 @@ Value must be the register (key) to use.") ;;;###autoload (define-minor-mode delete-selection-mode "Toggle Delete Selection mode. -With a prefix argument ARG, enable Delete Selection mode if ARG -is positive, and disable it otherwise. If called from Lisp, -enable the mode if ARG is omitted or nil. +Interactively, with a prefix argument, enable +Delete Selection mode if the prefix argument is positive, +and disable it otherwise. If called from Lisp, toggle +the mode if ARG is `toggle', disable the mode if ARG is +a non-positive integer, and enable the mode otherwise +\(including if ARG is omitted or nil or a positive integer). When Delete Selection mode is enabled, typed text replaces the selection if the selection is active. Otherwise, typed text is just inserted at @@ -253,12 +256,18 @@ See `delete-selection-helper'." (get this-command 'delete-selection))))) (defun delete-selection-uses-region-p () - "Return t when the current command will be using the region -rather than having `delete-selection' delete it, nil otherwise. + "Return t when `delete-selection-mode' should not delete the region. + +The `self-insert-command' could be the current command or may be +called by the current command. If this function returns nil, +then `delete-selection' is allowed to delete the region. This function is intended for use as the value of the `delete-selection' property of a command, and shouldn't be used -for anything else." +for anything else. In particular, `self-insert-command' has this +function as its `delete-selection' property, so that \"electric\" +self-insert commands that act on the region could adapt themselves +to `delete-selection-mode'." (not (run-hook-with-args-until-success 'self-insert-uses-region-functions))) diff --git a/lisp/descr-text.el b/lisp/descr-text.el index 6a6a8ea4479..12d0016de38 100644 --- a/lisp/descr-text.el +++ b/lisp/descr-text.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -413,12 +413,11 @@ relevant to POS." (multibyte-p enable-multibyte-characters) (overlays (mapcar (lambda (o) (overlay-properties o)) (overlays-at pos))) - (char-description (if (not multibyte-p) + (char-description (if (< char 128) (single-key-description char) - (if (< char 128) - (single-key-description char) - (string-to-multibyte - (char-to-string char))))) + (string (if (not multibyte-p) + (decode-char 'eight-bit char) + char)))) (text-props-desc (let ((tmp-buf (generate-new-buffer " *text-props*"))) (unwind-protect @@ -618,16 +617,16 @@ relevant to POS." (list (let* ((names (ucs-names)) (name - (or (when (= char 7) + (or (when (= char ?\a) ;; Special case for "BELL" which is ;; apparently the only char which ;; doesn't have a new name and whose ;; old-name is shadowed by a newer char ;; with that name (bug#25641). - (car (rassoc char names))) + "BELL (BEL)") (get-char-code-property char 'name) (get-char-code-property char 'old-name)))) - (if (and name (assoc-string name names)) + (if (and name (gethash name names)) (format "type \"C-x 8 RET %x\" or \"C-x 8 RET %s\"" char name) @@ -635,7 +634,9 @@ relevant to POS." ("buffer code" ,(if multibyte-p (encoded-string-description - (string-as-unibyte (char-to-string char)) nil) + (encode-coding-string (char-to-string char) + 'emacs-internal) + nil) (format "#x%02X" char))) ("file code" ,@(if multibyte-p @@ -704,7 +705,6 @@ relevant to POS." (called-interactively-p 'interactive)) (with-help-window (help-buffer) (with-current-buffer standard-output - (set-buffer-multibyte multibyte-p) (let ((formatter (format "%%%ds:" max-width))) (dolist (elt item-list) (when (cadr elt) diff --git a/lisp/desktop.el b/lisp/desktop.el index 540d0e3b11d..5257c609dde 100644 --- a/lisp/desktop.el +++ b/lisp/desktop.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -216,8 +216,9 @@ determine where the desktop is saved." :version "22.1") (defcustom desktop-auto-save-timeout auto-save-timeout - "Number of seconds idle time before auto-save of the desktop. -The idle timer activates auto-saving only when window configuration changes. + "Number of seconds of idle time before auto-saving the desktop. +The desktop will be auto-saved when this amount of idle time have +passed after some change in the window configuration. This applies to an existing desktop file when `desktop-save-mode' is enabled. Zero or nil means disable auto-saving due to idleness." :type '(choice (const :tag "Off" nil) @@ -709,8 +710,8 @@ if different)." (setq desktop-io-file-version nil) (dolist (var desktop-globals-to-clear) (if (symbolp var) - (eval `(setq-default ,var nil)) - (eval `(setq-default ,(car var) ,(cdr var))))) + (set-default var nil) + (set-default var (eval (cdr var))))) (let ((preserve-regexp (concat "^\\(" (mapconcat (lambda (regexp) (concat "\\(" regexp "\\)")) @@ -1046,7 +1047,8 @@ without further confirmation." (or (not new-modtime) ; nothing to overwrite (equal desktop-file-modtime new-modtime) (yes-or-no-p (if desktop-file-modtime - (if (> (float-time new-modtime) (float-time desktop-file-modtime)) + (if (time-less-p desktop-file-modtime + new-modtime) "Desktop file is more recent than the one loaded. Save anyway? " "Desktop file isn't the one loaded. Overwrite it? ") "Current desktop was not loaded from a file. Overwrite this desktop file? ")) @@ -1238,7 +1240,13 @@ Using it may cause conflicts. Use it anyway? " owner))))) ;; disabled when loading the desktop fails with errors, ;; thus not overwriting the desktop with broken contents. (setq desktop-autosave-was-enabled - (memq 'desktop-auto-save-set-timer window-configuration-change-hook)) + (memq 'desktop-auto-save-set-timer + ;; Use the toplevel value of the hook, in case some + ;; feature makes window-configuration-change-hook + ;; buffer-local, and puts there stuff which + ;; doesn't include our timer. + (default-toplevel-value + 'window-configuration-change-hook))) (desktop-auto-save-disable) ;; Evaluate desktop buffer and remember when it was modified. (setq desktop-file-modtime (nth 5 (file-attributes (desktop-full-file-name)))) @@ -1361,10 +1369,11 @@ Called by the timer created in `desktop-auto-save-set-timer'." (desktop-save desktop-dirname nil t))) (defun desktop-auto-save-set-timer () - "Set the auto-save timer. + "Set the desktop auto-save timer. Cancel any previous timer. When `desktop-auto-save-timeout' is a positive -integer, start a new idle timer to call `desktop-auto-save' repeatedly -after that many seconds of idle time." +integer, start a new idle timer to call `desktop-auto-save' after that many +seconds of idle time. +This function is called from `window-configuration-change-hook'." (desktop-auto-save-cancel-timer) (when (and (integerp desktop-auto-save-timeout) (> desktop-auto-save-timeout 0)) @@ -1554,8 +1563,7 @@ and try to load that." (setq buffer-display-time (if buffer-display-time (time-add buffer-display-time - (time-subtract (current-time) - desktop-file-modtime)) + (time-subtract nil desktop-file-modtime)) (current-time))) (unless (< desktop-file-version 208) ; Don't misinterpret any old custom args (dolist (record compacted-vars) diff --git a/lisp/dframe.el b/lisp/dframe.el index f60fffe7a79..7f77d8991ff 100644 --- a/lisp/dframe.el +++ b/lisp/dframe.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/dired-aux.el b/lisp/dired-aux.el index ec07f9bf735..f1f7cf0b0ef 100644 --- a/lisp/dired-aux.el +++ b/lisp/dired-aux.el @@ -1,4 +1,4 @@ -;;; dired-aux.el --- less commonly used parts of dired +;;; dired-aux.el --- less commonly used parts of dired -*- lexical-binding: t -*- ;; Copyright (C) 1985-1986, 1992, 1994, 1998, 2000-2017 Free Software ;; Foundation, Inc. @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -51,6 +51,33 @@ into this list; they also should call `dired-log' to log the errors.") (defconst dired-star-subst-regexp "\\(^\\|[ \t]\\)\\*\\([ \t]\\|$\\)") (defconst dired-quark-subst-regexp "\\(^\\|[ \t]\\)\\?\\([ \t]\\|$\\)") +(make-obsolete-variable 'dired-star-subst-regexp nil "26.1") +(make-obsolete-variable 'dired-quark-subst-regexp nil "26.1") + +(defun dired-isolated-string-re (string) + "Return a regexp to match STRING isolated. +Isolated means that STRING is surrounded by spaces or at the beginning/end +of a string followed/prefixed with an space. +The regexp capture the preceding blank, STRING and the following blank as +the groups 1, 2 and 3 respectively." + (format "\\(\\`\\|[ \t]\\)\\(%s\\)\\([ \t]\\|\\'\\)" string)) + +(defun dired--star-or-qmark-p (string match &optional keep) + "Return non-nil if STRING contains isolated MATCH or `\\=`?\\=`'. +MATCH should be the strings \"?\", `\\=`?\\=`', \"*\" or nil. The latter +means STRING contains either \"?\" or `\\=`?\\=`' or \"*\". +If optional arg KEEP is non-nil, then preserve the match data. Otherwise, +this function changes it and saves MATCH as the second match group. + +Isolated means that MATCH is surrounded by spaces or at the beginning/end +of STRING followed/prefixed with an space. A match to `\\=`?\\=`', +isolated or not, is also valid." + (let ((regexps (list (dired-isolated-string-re (if match (regexp-quote match) "[*?]"))))) + (when (or (null match) (equal match "?")) + (setq regexps (append (list "\\(\\)\\(`\\?`\\)\\(\\)") regexps))) + (cl-some (lambda (x) + (funcall (if keep #'string-match-p #'string-match) x string)) + regexps))) ;;;###autoload (defun dired-diff (file &optional switches) @@ -308,7 +335,7 @@ List has a form of (file-name full-file-name (attribute-list))." failures) (setq failures (dired-bunch-files 10000 - (function dired-check-process) + #'dired-check-process (append (list operation program) (unless (or (string-equal new-attribute "") @@ -512,7 +539,7 @@ with a prefix argument." ;; If the file has numeric backup versions, ;; put on dired-file-version-alist an element of the form ;; (FILENAME . VERSION-NUMBER-LIST) - (dired-map-dired-file-lines (function dired-collect-file-versions)) + (dired-map-dired-file-lines #'dired-collect-file-versions) ;; Sort each VERSION-NUMBER-LIST, ;; and remove the versions not to be deleted. (let ((fval dired-file-version-alist)) @@ -528,7 +555,7 @@ with a prefix argument." (setq fval (cdr fval)))) ;; Look at each file. If it is a numeric backup file, ;; find it in a VERSION-NUMBER-LIST and maybe flag it for deletion. - (dired-map-dired-file-lines (function dired-trample-file-versions)) + (dired-map-dired-file-lines #'dired-trample-file-versions) (message "Cleaning numerical backups...done"))) ;;; Subroutines of dired-clean-directory. @@ -592,8 +619,9 @@ with a prefix argument." This function is used to add all related commands retrieved by `mailcap' to the end of the list of defaults just after the default value." (interactive) - (let ((commands (and (boundp 'files) (require 'mailcap nil t) - (mailcap-file-default-commands files)))) + (let* ((files minibuffer-completion-table) + (commands (and (require 'mailcap nil t) + (mailcap-file-default-commands files)))) (if (listp minibuffer-default) (append minibuffer-default commands) (cons minibuffer-default commands)))) @@ -611,6 +639,7 @@ This normally reads using `read-shell-command', but if the offer a smarter default choice of shell command." (minibuffer-with-setup-hook (lambda () + (set (make-local-variable 'minibuffer-completion-table) files) (set (make-local-variable 'minibuffer-default-add-function) 'minibuffer-default-add-dired-shell-commands)) (setq prompt (format prompt (dired-mark-prompt arg files))) @@ -658,13 +687,13 @@ If there is a `*' in COMMAND, surrounded by whitespace, this runs COMMAND just once with the entire file list substituted there. If there is no `*', but there is a `?' in COMMAND, surrounded by -whitespace, this runs COMMAND on each file individually with the -file name substituted for `?'. +whitespace, or a `\\=`?\\=`' this runs COMMAND on each file +individually with the file name substituted for `?' or `\\=`?\\=`'. Otherwise, this runs COMMAND on each file individually with the file name added at the end of COMMAND (separated by a space). -`*' and `?' when not surrounded by whitespace have no special +`*' and `?' when not surrounded by whitespace nor `\\=`' have no special significance for `dired-do-shell-command', and are passed through normally to the shell, but you must confirm first. @@ -704,32 +733,38 @@ can be produced by `dired-get-marked-files', for example." (dired-read-shell-command "! on %s: " current-prefix-arg files) current-prefix-arg files))) - (let* ((on-each (not (string-match-p dired-star-subst-regexp command))) - (no-subst (not (string-match-p dired-quark-subst-regexp command))) - (star (string-match-p "\\*" command)) - (qmark (string-match-p "\\?" command))) - ;; Get confirmation for wildcards that may have been meant - ;; to control substitution of a file name or the file name list. - (if (cond ((not (or on-each no-subst)) - (error "You can not combine `*' and `?' substitution marks")) - ((and star on-each) - (y-or-n-p (format-message - "Confirm--do you mean to use `*' as a wildcard? "))) - ((and qmark no-subst) - (y-or-n-p (format-message - "Confirm--do you mean to use `?' as a wildcard? "))) - (t)) - (if on-each - (dired-bunch-files - (- 10000 (length command)) - (function (lambda (&rest files) - (dired-run-shell-command - (dired-shell-stuff-it command files t arg)))) - nil - file-list) - ;; execute the shell command - (dired-run-shell-command - (dired-shell-stuff-it command file-list nil arg)))))) + (cl-flet ((need-confirm-p + (cmd str) + (let ((res cmd) + (regexp (regexp-quote str))) + ;; Drop all ? and * surrounded by spaces and `?`. + (while (and (string-match regexp res) + (dired--star-or-qmark-p res str)) + (setq res (replace-match "" t t res 2))) + (string-match regexp res)))) + (let* ((on-each (not (dired--star-or-qmark-p command "*" 'keep))) + (no-subst (not (dired--star-or-qmark-p command "?" 'keep))) + ;; Get confirmation for wildcards that may have been meant + ;; to control substitution of a file name or the file name list. + (ok (cond ((not (or on-each no-subst)) + (error "You can not combine `*' and `?' substitution marks")) + ((need-confirm-p command "*") + (y-or-n-p (format-message + "Confirm--do you mean to use `*' as a wildcard? "))) + ((need-confirm-p command "?") + (y-or-n-p (format-message + "Confirm--do you mean to use `?' as a wildcard? "))) + (t)))) + (when ok + (if on-each + (dired-bunch-files (- 10000 (length command)) + (lambda (&rest files) + (dired-run-shell-command + (dired-shell-stuff-it command files t arg))) + nil file-list) + ;; execute the shell command + (dired-run-shell-command + (dired-shell-stuff-it command file-list nil arg))))))) ;; Might use {,} for bash or csh: (defvar dired-mark-prefix "" @@ -769,12 +804,10 @@ can be produced by `dired-get-marked-files', for example." ";" "&")) (stuff-it - (if (or (string-match-p dired-star-subst-regexp command) - (string-match-p dired-quark-subst-regexp command)) + (if (dired--star-or-qmark-p command nil 'keep) (lambda (x) (let ((retval (concat cmd-prefix command))) - (while (string-match - "\\(^\\|[ \t]\\)\\([*?]\\)\\([ \t]\\|$\\)" retval) + (while (dired--star-or-qmark-p retval nil) (setq retval (replace-match x t t retval 2))) retval)) (lambda (x) (concat cmd-prefix command dired-mark-separator x))))) @@ -981,7 +1014,7 @@ ARGS are command switches passed to PROGRAM.") "Control the compression shell command for `dired-do-compress-to'. Each element is (REGEXP . CMD), where REGEXP is the name of the -archive to which you want to compress, and CMD the the +archive to which you want to compress, and CMD is the corresponding command. Within CMD, %i denotes the input file(s), and %o denotes the @@ -1122,7 +1155,7 @@ Return nil if no change in files." (let ((files (dired-get-marked-files t arg nil t)) (string (if (eq op-symbol 'compress) "Compress or uncompress" (capitalize (symbol-name op-symbol))))) - (dired-mark-pop-up nil op-symbol files (function y-or-n-p) + (dired-mark-pop-up nil op-symbol files #'y-or-n-p (concat string " " (dired-mark-prompt arg files) "? "))))) @@ -1190,7 +1223,7 @@ return t; if SYM is q or ESC, return nil." (defun dired-do-compress (&optional arg) "Compress or uncompress marked (or next ARG) files." (interactive "P") - (dired-map-over-marks-check (function dired-compress) arg 'compress t)) + (dired-map-over-marks-check #'dired-compress arg 'compress t)) ;; Commands for Emacs Lisp files - load and byte compile @@ -1218,7 +1251,7 @@ return t; if SYM is q or ESC, return nil." (defun dired-do-byte-compile (&optional arg) "Byte compile marked (or next ARG) Emacs Lisp files." (interactive "P") - (dired-map-over-marks-check (function dired-byte-compile) arg 'byte-compile t)) + (dired-map-over-marks-check #'dired-byte-compile arg 'byte-compile t)) (defun dired-load () ;; Return nil for success, offending file name else. @@ -1235,7 +1268,7 @@ return t; if SYM is q or ESC, return nil." (defun dired-do-load (&optional arg) "Load the marked (or next ARG) Emacs Lisp files." (interactive "P") - (dired-map-over-marks-check (function dired-load) arg 'load t)) + (dired-map-over-marks-check #'dired-load arg 'load t)) ;;;###autoload (defun dired-do-redisplay (&optional arg test-for-subdir) @@ -1308,7 +1341,7 @@ See Info node `(emacs)Subdir switches' for more details." (defun dired-add-file (filename &optional marker-char) (dired-fun-in-all-buffers (file-name-directory filename) (file-name-nondirectory filename) - (function dired-add-entry) filename marker-char)) + #'dired-add-entry filename marker-char)) (defvar dired-omit-mode) (declare-function dired-omit-regexp "dired-x" ()) @@ -1366,7 +1399,7 @@ files matching `dired-omit-regexp'." ;; else try to find correct place to insert (if (dired-goto-subdir directory) (progn ;; unhide if necessary - (if (looking-at-p "\r") + (if (= (following-char) ?\r) ;; Point is at end of subdir line. (dired-unhide-subdir)) ;; found - skip subdir and `total' line @@ -1445,7 +1478,7 @@ files matching `dired-omit-regexp'." (defun dired-remove-file (file) (dired-fun-in-all-buffers (file-name-directory file) (file-name-nondirectory file) - (function dired-remove-entry) file)) + #'dired-remove-entry file)) (defun dired-remove-entry (file) (save-excursion @@ -1459,7 +1492,7 @@ files matching `dired-omit-regexp'." "Create or update the line for FILE in all Dired buffers it would belong in." (dired-fun-in-all-buffers (file-name-directory file) (file-name-nondirectory file) - (function dired-relist-entry) file)) + #'dired-relist-entry file)) (defun dired-relist-entry (file) ;; Relist the line for FILE, or just add it if it did not exist. @@ -1515,6 +1548,24 @@ Special value `always' suppresses confirmation." (declare-function make-symbolic-link "fileio.c") +(defcustom dired-create-destination-dirs nil + "Whether Dired should create destination dirs when copying/removing files. +If nil, don't create them. +If `always', create them without ask. +If `ask', ask for user confirmation." + :type '(choice (const :tag "Never create non-existent dirs" nil) + (const :tag "Always create non-existent dirs" always) + (const :tag "Ask for user confirmation" ask)) + :group 'dired + :version "27.1") + +(defun dired-maybe-create-dirs (dir) + "Create DIR if doesn't exist according to `dired-create-destination-dirs'." + (when (and dired-create-destination-dirs (not (file-exists-p dir))) + (if (or (eq dired-create-destination-dirs 'always) + (yes-or-no-p (format "Create destination dir `%s'? " dir))) + (dired-create-directory dir)))) + (defun dired-copy-file-recursive (from to ok-flag &optional preserve-time top recursive) (when (and (eq t (car (file-attributes from))) @@ -1531,6 +1582,7 @@ Special value `always' suppresses confirmation." (if (stringp (car attrs)) ;; It is a symlink (make-symbolic-link (car attrs) to ok-flag) + (dired-maybe-create-dirs (file-name-directory to)) (copy-file from to ok-flag preserve-time)) (file-date-error (push (dired-make-relative from) @@ -1540,6 +1592,7 @@ Special value `always' suppresses confirmation." ;;;###autoload (defun dired-rename-file (file newname ok-if-already-exists) (dired-handle-overwrite newname) + (dired-maybe-create-dirs (file-name-directory newname)) (rename-file file newname ok-if-already-exists) ; error is caught in -create-files ;; Silently rename the visited file of any buffer visiting this file. (and (get-file-buffer file) @@ -1553,7 +1606,7 @@ Special value `always' suppresses confirmation." (setq from-dir (file-name-as-directory from-dir) to-dir (file-name-as-directory to-dir)) (dired-fun-in-all-buffers from-dir nil - (function dired-rename-subdir-1) from-dir to-dir) + #'dired-rename-subdir-1 from-dir to-dir) ;; Update visited file name of all affected buffers (let ((expanded-from-dir (expand-file-name from-dir)) (blist (buffer-list))) @@ -1590,10 +1643,14 @@ Special value `always' suppresses confirmation." (setq default-directory to dired-directory (expand-file-name;; this is correct ;; with and without wildcards - (file-name-nondirectory dired-directory) + (file-name-nondirectory (if (stringp dired-directory) + dired-directory + (car dired-directory))) to)) (let ((new-name (file-name-nondirectory - (directory-file-name dired-directory)))) + (directory-file-name (if (stringp dired-directory) + dired-directory + (car dired-directory)))))) ;; try to rename buffer, but just leave old name if new ;; name would already exist (don't try appending "<%d>") (or (get-buffer new-name) @@ -1788,7 +1845,7 @@ Optional arg HOW-TO determines how to treat the target. For any other return value, TARGET is treated as a directory." (or op1 (setq op1 operation)) (let* ((fn-list (dired-get-marked-files nil arg)) - (rfn-list (mapcar (function dired-make-relative) fn-list)) + (rfn-list (mapcar #'dired-make-relative fn-list)) (dired-one-file ; fluid variable inside dired-create-files (and (consp fn-list) (null (cdr fn-list)) (car fn-list))) (target-dir (dired-dwim-target-directory)) @@ -1838,10 +1895,9 @@ Optional arg HOW-TO determines how to treat the target. (if into-dir ; target is a directory ;; This function uses fluid variable target when called ;; inside dired-create-files: - (function - (lambda (from) - (expand-file-name (file-name-nondirectory from) target))) - (function (lambda (_from) target))) + (lambda (from) + (expand-file-name (file-name-nondirectory from) target)) + (lambda (_from) target)) marker-char)))) ;; Read arguments for a marked-files command that wants a file name, @@ -1857,7 +1913,7 @@ Optional arg HOW-TO determines how to treat the target. &optional default) (dired-mark-pop-up nil op-symbol files - (function read-file-name) + #'read-file-name (format prompt (dired-mark-prompt arg files)) dir default)) (defun dired-dwim-target-directory () @@ -1929,6 +1985,7 @@ Optional arg HOW-TO determines how to treat the target. ;;;###autoload (defun dired-create-directory (directory) "Create a directory called DIRECTORY. +Parent directories of DIRECTORY are created as needed. If DIRECTORY already exists, signal an error." (interactive (list (read-file-name "Create directory: " (dired-current-directory)))) @@ -1985,7 +2042,7 @@ This command copies symbolic links by creating new ones, similar to the \"-d\" option for the \"cp\" shell command." (interactive "P") (let ((dired-recursive-copies dired-recursive-copies)) - (dired-do-create-files 'copy (function dired-copy-file) + (dired-do-create-files 'copy #'dired-copy-file "Copy" arg dired-keep-marker-copy nil dired-copy-how-to-fn))) @@ -2002,7 +2059,7 @@ suggested for the target directory depends on the value of For relative symlinks, use \\[dired-do-relsymlink]." (interactive "P") - (dired-do-create-files 'symlink (function make-symbolic-link) + (dired-do-create-files 'symlink #'make-symbolic-link "Symlink" arg dired-keep-marker-symlink)) ;;;###autoload @@ -2015,7 +2072,7 @@ with the same names that the files currently have. The default suggested for the target directory depends on the value of `dired-dwim-target', which see." (interactive "P") - (dired-do-create-files 'hardlink (function dired-hardlink) + (dired-do-create-files 'hardlink #'dired-hardlink "Hardlink" arg dired-keep-marker-hardlink)) (defun dired-hardlink (file newname &optional ok-if-already-exists) @@ -2034,7 +2091,7 @@ This command also renames any buffers that are visiting the files. The default suggested for the target directory depends on the value of `dired-dwim-target', which see." (interactive "P") - (dired-do-create-files 'move (function dired-rename-file) + (dired-do-create-files 'move #'dired-rename-file "Move" arg dired-keep-marker-rename "Rename")) ;;;###end dired-cp.el @@ -2062,37 +2119,35 @@ Type SPC or `y' to %s one match, DEL or `n' to skip to next, (regexp-name-constructor ;; Function to construct new filename using REGEXP and NEWNAME: (if whole-name ; easy (but rare) case - (function - (lambda (from) - (let ((to (dired-string-replace-match regexp from newname)) - ;; must bind help-form directly around call to - ;; dired-query - (help-form rename-regexp-help-form)) - (if to - (and (dired-query 'rename-regexp-query - operation-prompt - from - to) - to) - (dired-log "%s: %s did not match regexp %s\n" - operation from regexp))))) - ;; not whole-name, replace non-directory part only - (function - (lambda (from) - (let* ((new (dired-string-replace-match - regexp (file-name-nondirectory from) newname)) - (to (and new ; nil means there was no match - (expand-file-name new - (file-name-directory from)))) + (lambda (from) + (let ((to (dired-string-replace-match regexp from newname)) + ;; must bind help-form directly around call to + ;; dired-query (help-form rename-regexp-help-form)) - (if to - (and (dired-query 'rename-regexp-query - operation-prompt - (dired-make-relative from) - (dired-make-relative to)) - to) - (dired-log "%s: %s did not match regexp %s\n" - operation (file-name-nondirectory from) regexp))))))) + (if to + (and (dired-query 'rename-regexp-query + operation-prompt + from + to) + to) + (dired-log "%s: %s did not match regexp %s\n" + operation from regexp)))) + ;; not whole-name, replace non-directory part only + (lambda (from) + (let* ((new (dired-string-replace-match + regexp (file-name-nondirectory from) newname)) + (to (and new ; nil means there was no match + (expand-file-name new + (file-name-directory from)))) + (help-form rename-regexp-help-form)) + (if to + (and (dired-query 'rename-regexp-query + operation-prompt + (dired-make-relative from) + (dired-make-relative to)) + to) + (dired-log "%s: %s did not match regexp %s\n" + operation (file-name-nondirectory from) regexp)))))) rename-regexp-query) (dired-create-files file-creator operation fn-list regexp-name-constructor marker-char))) @@ -2130,7 +2185,7 @@ With a zero prefix arg, renaming by regexp affects the absolute file name. Normally, only the non-directory part of the file name is used and changed." (interactive (dired-mark-read-regexp "Rename")) (dired-do-create-files-regexp - (function dired-rename-file) + #'dired-rename-file "Rename" arg regexp newname whole-name dired-keep-marker-rename)) ;;;###autoload @@ -2140,7 +2195,7 @@ See function `dired-do-rename-regexp' for more info." (interactive (dired-mark-read-regexp "Copy")) (let ((dired-recursive-copies nil)) ; No recursive copies. (dired-do-create-files-regexp - (function dired-copy-file) + #'dired-copy-file (if dired-copy-preserve-time "Copy [-p]" "Copy") arg regexp newname whole-name dired-keep-marker-copy))) @@ -2150,7 +2205,7 @@ See function `dired-do-rename-regexp' for more info." See function `dired-do-rename-regexp' for more info." (interactive (dired-mark-read-regexp "HardLink")) (dired-do-create-files-regexp - (function add-name-to-file) + #'add-name-to-file "HardLink" arg regexp newname whole-name dired-keep-marker-hardlink)) ;;;###autoload @@ -2159,7 +2214,7 @@ See function `dired-do-rename-regexp' for more info." See function `dired-do-rename-regexp' for more info." (interactive (dired-mark-read-regexp "SymLink")) (dired-do-create-files-regexp - (function make-symbolic-link) + #'make-symbolic-link "SymLink" arg regexp newname whole-name dired-keep-marker-symlink)) (defvar rename-non-directory-query) @@ -2174,39 +2229,38 @@ See function `dired-do-rename-regexp' for more info." file-creator operation (dired-get-marked-files nil arg) - (function - (lambda (from) - (let ((to (concat (file-name-directory from) - (funcall basename-constructor - (file-name-nondirectory from))))) - (and (let ((help-form (format-message "\ + (lambda (from) + (let ((to (concat (file-name-directory from) + (funcall basename-constructor + (file-name-nondirectory from))))) + (and (let ((help-form (format-message "\ Type SPC or `y' to %s one file, DEL or `n' to skip to next, `!' to %s all remaining matches with no more questions." - (downcase operation) - (downcase operation)))) - (dired-query 'rename-non-directory-query - (concat operation " `%s' to `%s'") - (dired-make-relative from) - (dired-make-relative to))) - to)))) + (downcase operation) + (downcase operation)))) + (dired-query 'rename-non-directory-query + (concat operation " `%s' to `%s'") + (dired-make-relative from) + (dired-make-relative to))) + to))) dired-keep-marker-rename))) (defun dired-rename-non-directory (basename-constructor operation arg) (dired-create-files-non-directory - (function dired-rename-file) + #'dired-rename-file basename-constructor operation arg)) ;;;###autoload (defun dired-upcase (&optional arg) "Rename all marked (or next ARG) files to upper case." (interactive "P") - (dired-rename-non-directory (function upcase) "Rename upcase" arg)) + (dired-rename-non-directory #'upcase "Rename upcase" arg)) ;;;###autoload (defun dired-downcase (&optional arg) "Rename all marked (or next ARG) files to lower case." (interactive "P") - (dired-rename-non-directory (function downcase) "Rename downcase" arg)) + (dired-rename-non-directory #'downcase "Rename downcase" arg)) ;;;###end dired-re.el @@ -2316,12 +2370,11 @@ This function takes some pains to conform to `ls -lR' output." (when real-switches (let (case-fold-search) (mapcar - (function - (lambda (x) - (or (eq (null (string-match-p x real-switches)) - (null (string-match-p x dired-actual-switches))) - (error - "Can't have dirs with and without -%s switches together" x)))) + (lambda (x) + (or (eq (null (string-match-p x real-switches)) + (null (string-match-p x dired-actual-switches))) + (error + "Can't have dirs with and without -%s switches together" x))) ;; all switches that make a difference to dired-get-filename: '("F" "b")))))) @@ -2334,9 +2387,9 @@ This function takes some pains to conform to `ls -lR' output." ;; Keep the alist sorted on buffer position. (setq dired-subdir-alist (sort dired-subdir-alist - (function (lambda (elt1 elt2) - (> (dired-get-subdir-min elt1) - (dired-get-subdir-min elt2))))))) + (lambda (elt1 elt2) + (> (dired-get-subdir-min elt1) + (dired-get-subdir-min elt2)))))) (defun dired-kill-tree (dirname &optional remember-marks kill-root) "Kill all proper subdirs of DIRNAME, excluding DIRNAME itself. @@ -2611,7 +2664,7 @@ Lower levels are unaffected." (and selective-display (save-excursion (dired-goto-subdir dir) - (looking-at-p "\r")))) + (= (following-char) ?\r)))) ;;;###autoload (defun dired-hide-subdir (arg) @@ -2715,9 +2768,9 @@ Intended to be added to `isearch-mode-hook'." (remove-hook 'isearch-mode-end-hook 'dired-isearch-filenames-end t)) (defun dired-isearch-filter-filenames (beg end) - "Test whether the current search hit is a file name. -Return non-nil if the text from BEG to END is part of a file -name (has the text property `dired-filename')." + "Test whether some part of the current search match is inside a file name. +This function returns non-nil if some part of the text between BEG and END +is part of a file name (i.e., has the text property `dired-filename')." (text-property-not-all (min beg end) (max beg end) 'dired-filename nil)) diff --git a/lisp/dired-x.el b/lisp/dired-x.el index 527685acf37..5fa28d3e3e8 100644 --- a/lisp/dired-x.el +++ b/lisp/dired-x.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -57,7 +57,7 @@ (defcustom dired-bind-vm nil "Non-nil means \"V\" runs `dired-vm', otherwise \"V\" runs `dired-rmail'. -RMAIL files in the old Babyl format (used before before Emacs 23.1) +RMAIL files in the old Babyl format (used before Emacs 23.1) contain \"-*- rmail -*-\" at the top, so `dired-find-file' will run `rmail' on these files. New RMAIL files use the standard mbox format, and so cannot be distinguished in this way." @@ -243,6 +243,12 @@ to nil: a pipe using `zcat' or `gunzip -c' will be used." :type 'boolean :group 'dired-x) +(defcustom dired-clean-confirm-killing-deleted-buffers t + "If nil, don't ask whether to kill buffers visiting deleted files." + :version "26.1" + :type 'boolean + :group 'dired-x) + ;;; KEY BINDINGS. (define-key dired-mode-map "\C-x\M-o" 'dired-omit-mode) @@ -546,7 +552,9 @@ Should never be used as marker by the user or other packages.") (interactive) (let ((dired-omit-mode nil)) (revert-buffer)) ;; Show omitted files (dired-mark-unmarked-files (dired-omit-regexp) nil nil dired-omit-localp - (dired-omit-case-fold-p dired-directory))) + (dired-omit-case-fold-p (if (stringp dired-directory) + dired-directory + (car dired-directory))))) (defcustom dired-omit-extensions (append completion-ignored-extensions @@ -591,7 +599,9 @@ This functions works by temporarily binding `dired-marker-char' to (let ((dired-marker-char dired-omit-marker-char)) (when dired-omit-verbose (message "Omitting...")) (if (dired-mark-unmarked-files omit-re nil nil dired-omit-localp - (dired-omit-case-fold-p dired-directory)) + (dired-omit-case-fold-p (if (stringp dired-directory) + dired-directory + (car dired-directory)))) (progn (setq count (dired-do-kill-lines nil @@ -634,7 +644,7 @@ Optional fifth argument CASE-FOLD-P specifies the value of (dired-mark-if (and ;; not already marked - (looking-at-p " ") + (= (following-char) ?\s) ;; uninteresting (let ((fn (dired-get-filename localp t)) ;; Match patterns case-insensitively on case-insensitive @@ -1530,7 +1540,7 @@ refer at all to the underlying file system. Contrast this with (setq mode (buffer-substring (point) (+ mode-len (point)))) (forward-char mode-len) ;; Skip any extended attributes marker ("." or "+"). - (or (looking-at " ") + (or (= (following-char) ?\s) (forward-char 1)) (setq nlink (read (current-buffer))) ;; Karsten Wenger <kw@cis.uni-muenchen.de> fixed uid. @@ -1625,10 +1635,11 @@ Binding direction based on `dired-x-hands-off-my-keys'." (if (called-interactively-p 'interactive) (setq dired-x-hands-off-my-keys (not (y-or-n-p "Bind dired-x-find-file over find-file? ")))) - (define-key (current-global-map) [remap find-file] - (if (not dired-x-hands-off-my-keys) 'dired-x-find-file)) - (define-key (current-global-map) [remap find-file-other-window] - (if (not dired-x-hands-off-my-keys) 'dired-x-find-file-other-window))) + (unless dired-x-hands-off-my-keys + (define-key (current-global-map) [remap find-file] + 'dired-x-find-file) + (define-key (current-global-map) [remap find-file-other-window] + 'dired-x-find-file-other-window))) ;; Now call it so binding is correct. This could go in the :initialize ;; slot, but then dired-x-bind-find-file has to be defined before the diff --git a/lisp/dired.el b/lisp/dired.el index 909735a3b54..ba762277ae7 100644 --- a/lisp/dired.el +++ b/lisp/dired.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -34,6 +34,7 @@ ;;; Code: +(eval-when-compile (require 'subr-x)) ;; When bootstrapping dired-loaddefs has not been generated. (require 'dired-loaddefs nil t) @@ -60,7 +61,7 @@ May contain all other options that don't contradict `-l'; may contain even `F', `b', `i' and `s'. See also the variable `dired-ls-F-marks-symlinks' concerning the `F' switch. Options that include embedded whitespace must be quoted -like this: \\\"--option=value with spaces\\\"; you can use +like this: \"--option=value with spaces\"; you can use `combine-and-quote-strings' to produce the correct quoting of each option. On systems such as MS-DOS and MS-Windows, which use `ls' emulation in Lisp, @@ -133,7 +134,7 @@ always set this variable to t." :type 'boolean :group 'dired-mark) -(defcustom dired-trivial-filenames (purecopy "^\\.\\.?$\\|^#") +(defcustom dired-trivial-filenames (purecopy "\\`\\.\\.?\\'\\|\\`#") "Regexp of files to skip when finding first file of a directory. A value of nil means move to the subdir line. A value of t means move to first file." @@ -197,8 +198,10 @@ The target is used in the prompt for file copy, rename etc." ; These variables were deleted and the replacements are on files.el. ; We leave aliases behind for back-compatibility. -(defvaralias 'dired-free-space-program 'directory-free-space-program) -(defvaralias 'dired-free-space-args 'directory-free-space-args) +(define-obsolete-variable-alias 'dired-free-space-program + 'directory-free-space-program "27.1") +(define-obsolete-variable-alias 'dired-free-space-args + 'directory-free-space-args "27.1") ;;; Hook variables @@ -335,9 +338,8 @@ The directory name must be absolute, but need not be fully expanded.") (defvar dired-re-dir (concat dired-re-maybe-mark dired-re-inode-size "d[^:]")) (defvar dired-re-sym (concat dired-re-maybe-mark dired-re-inode-size "l[^:]")) (defvar dired-re-exe;; match ls permission string of an executable file - (mapconcat (function - (lambda (x) - (concat dired-re-maybe-mark dired-re-inode-size x))) + (mapconcat (lambda (x) + (concat dired-re-maybe-mark dired-re-inode-size x)) '("-[-r][-w][xs][-r][-w].[-r][-w]." "-[-r][-w].[-r][-w][xs][-r][-w]." "-[-r][-w].[-r][-w].[-r][-w][xst]") @@ -607,9 +609,9 @@ marked file, return (t FILENAME) instead of (FILENAME)." (progn ;; no save-excursion, want to move point. (dired-repeat-over-lines ,arg - (function (lambda () - (if ,show-progress (sit-for 0)) - (setq results (cons ,body results))))) + (lambda () + (if ,show-progress (sit-for 0)) + (setq results (cons ,body results)))) (if (< ,arg 0) (nreverse results) results)) @@ -786,7 +788,7 @@ Type \\[describe-mode] after entering Dired for more info. If DIRNAME is already in a Dired buffer, that buffer is used without refresh." ;; Cannot use (interactive "D") because of wildcards. (interactive (dired-read-dir-and-switches "")) - (switch-to-buffer (dired-noselect dirname switches))) + (pop-to-buffer-same-window (dired-noselect dirname switches))) ;;;###autoload (define-key ctl-x-4-map "d" 'dired-other-window) ;;;###autoload @@ -872,14 +874,56 @@ periodically reverts at specified time intervals." :group 'dired :version "23.2") +(defun dired--need-align-p () + "Return non-nil if some file names are misaligned. +The return value is the target column for the file names." + (save-excursion + (goto-char (point-min)) + (dired-goto-next-file) + ;; Use point difference instead of `current-column', because + ;; the former works when `dired-hide-details-mode' is enabled. + (let* ((first (- (point) (point-at-bol))) + (target first)) + (while (and (not (eobp)) + (progn + (forward-line) + (dired-move-to-filename))) + (when-let* ((distance (- (point) (point-at-bol))) + (higher (> distance target))) + (setq target distance))) + (and (/= first target) target)))) + +(defun dired--align-all-files () + "Align all files adding spaces in front of the size column." + (let ((target (dired--need-align-p)) + (regexp directory-listing-before-filename-regexp)) + (when target + (save-excursion + (goto-char (point-min)) + (dired-goto-next-file) + (while (dired-move-to-filename) + ;; Use point difference instead of `current-column', because + ;; the former works when `dired-hide-details-mode' is enabled. + (let ((distance (- target (- (point) (point-at-bol)))) + (inhibit-read-only t)) + (unless (zerop distance) + (re-search-backward regexp nil t) + (goto-char (match-beginning 0)) + (search-backward-regexp "[[:space:]]" nil t) + (skip-chars-forward "[:space:]") + (insert-char ?\s distance 'inherit)) + (forward-line))))))) + (defun dired-internal-noselect (dir-or-list &optional switches mode) - ;; If there is an existing dired buffer for DIRNAME, just leave - ;; buffer as it is (don't even call dired-revert). + ;; If DIR-OR-LIST is a string and there is an existing dired buffer + ;; for it, just leave buffer as it is (don't even call dired-revert). ;; This saves time especially for deep trees or with ange-ftp. ;; The user can type `g' easily, and it is more consistent with find-file. ;; But if SWITCHES are given they are probably different from the ;; buffer's old value, so call dired-sort-other, which does ;; revert the buffer. + ;; Revert the buffer if DIR-OR-LIST is a cons or `dired-directory' + ;; is a cons and DIR-OR-LIST is a string. ;; A pity we can't possibly do "Directory has changed - refresh? " ;; like find-file does. ;; Optional argument MODE is passed to dired-find-buffer-nocreate, @@ -899,6 +943,11 @@ periodically reverts at specified time intervals." (setq dired-directory dir-or-list) ;; this calls dired-revert (dired-sort-other switches)) + ;; Always revert when `dir-or-list' is a cons. Also revert + ;; if `dired-directory' is a cons but `dir-or-list' is not. + ((or (consp dir-or-list) (consp dired-directory)) + (setq dired-directory dir-or-list) + (revert-buffer)) ;; Always revert regardless of whether it has changed or not. ((eq dired-auto-revert-buffer t) (revert-buffer)) @@ -914,11 +963,12 @@ periodically reverts at specified time intervals." "Directory has changed on disk; type \\[revert-buffer] to update Dired"))))) ;; Else a new buffer (setq default-directory - ;; We can do this unconditionally - ;; because dired-noselect ensures that the name - ;; is passed in directory name syntax - ;; if it was the name of a directory at all. - (file-name-directory dirname)) + (or (car-safe (insert-directory-wildcard-in-dir-p dirname)) + ;; We can do this unconditionally + ;; because dired-noselect ensures that the name + ;; is passed in directory name syntax + ;; if it was the name of a directory at all. + (file-name-directory dirname))) (or switches (setq switches dired-listing-switches)) (if mode (funcall mode) (dired-mode dir-or-list switches)) @@ -933,6 +983,8 @@ periodically reverts at specified time intervals." (if failed (kill-buffer buffer)))) (goto-char (point-min)) (dired-initial-position dirname)) + (when (consp dired-directory) + (dired--align-all-files)) (set-buffer old-buf) buffer)) @@ -996,7 +1048,7 @@ wildcards, erases the buffer, and builds the subdir-alist anew ;; default-directory and dired-actual-switches must be buffer-local ;; and initialized by now. (let (dirname - ;; This makes readin much much faster. + ;; This makes read-in much faster. ;; In particular, it prevents the font lock hook from running ;; until the directory is all read in. (inhibit-modification-hooks t)) @@ -1050,13 +1102,14 @@ wildcards, erases the buffer, and builds the subdir-alist anew (not file-list)) ;; If we are reading a whole single directory... (dired-insert-directory dir dired-actual-switches nil nil t) - (if (not (file-readable-p - (directory-file-name (file-name-directory dir)))) - (error "Directory %s inaccessible or nonexistent" dir) - ;; Else treat it as a wildcard spec - ;; unless we have an explicit list of files. - (dired-insert-directory dir dired-actual-switches - file-list (not file-list) t))))) + (if (and (not (insert-directory-wildcard-in-dir-p dir)) + (not (file-readable-p + (directory-file-name (file-name-directory dir))))) + (error "Directory %s inaccessible or nonexistent" dir)) + ;; Else treat it as a wildcard spec + ;; unless we have an explicit list of files. + (dired-insert-directory dir dired-actual-switches + file-list (not file-list) t)))) (defun dired-align-file (beg end) "Align the fields of a file to the ones of surrounding lines. @@ -1154,7 +1207,7 @@ BEG..END is the line where the file info is located." (setq file-col (+ spaces file-col)) (if (> file-col other-col) (setq spaces (- spaces (- file-col other-col)))) - (insert-char ?\s spaces) + (insert-char ?\s spaces 'inherit) ;; Let's just make really sure we did not mess up. (unless (save-excursion (eq (dired-move-to-filename) (marker-position file))) @@ -1201,29 +1254,56 @@ If HDR is non-nil, insert a header line with the directory name." ;; as indicated by `ls-lisp-use-insert-directory-program'. (not (and (featurep 'ls-lisp) (null ls-lisp-use-insert-directory-program))) - (or (if (eq dired-use-ls-dired 'unspecified) + (not (and (featurep 'eshell) + (bound-and-true-p eshell-ls-use-in-dired))) + (or (file-remote-p dir) + (if (eq dired-use-ls-dired 'unspecified) ;; Check whether "ls --dired" gives exit code 0, and ;; save the answer in `dired-use-ls-dired'. (or (setq dired-use-ls-dired (eq 0 (call-process insert-directory-program - nil nil nil "--dired"))) + nil nil nil "--dired"))) (progn (message "ls does not support --dired; \ see `dired-use-ls-dired' for more details.") nil)) - dired-use-ls-dired) - (file-remote-p dir))) + dired-use-ls-dired))) (setq switches (concat "--dired " switches))) - ;; We used to specify the C locale here, to force English month names; - ;; but this should not be necessary any more, - ;; with the new value of `directory-listing-before-filename-regexp'. - (if file-list - (dolist (f file-list) - (let ((beg (point))) - (insert-directory f switches nil nil) - ;; Re-align fields, if necessary. - (dired-align-file beg (point)))) - (insert-directory dir switches wildcard (not wildcard))) + ;; Expand directory wildcards and fill file-list. + (let ((dir-wildcard (insert-directory-wildcard-in-dir-p dir))) + (cond (dir-wildcard + (setq switches (concat "-d " switches)) + ;; We don't know whether the remote ls supports + ;; "--dired", so we cannot add it to the `process-file' + ;; call for wildcards. + (when (file-remote-p dir) + (setq switches (dired-replace-in-string "--dired" "" switches))) + (let* ((default-directory (car dir-wildcard)) + (script (format "ls %s %s" switches (cdr dir-wildcard))) + (remotep (file-remote-p dir)) + (sh (or (and remotep "/bin/sh") + (and (bound-and-true-p explicit-shell-file-name) + (executable-find explicit-shell-file-name)) + (executable-find "sh"))) + (switch (if remotep "-c" shell-command-switch))) + (unless + (zerop + (process-file sh nil (current-buffer) nil switch script)) + (user-error + "%s: No files matching wildcard" (cdr dir-wildcard))) + (insert-directory-clean (point) switches))) + (t + ;; We used to specify the C locale here, to force English + ;; month names; but this should not be necessary any + ;; more, with the new value of + ;; `directory-listing-before-filename-regexp'. + (if file-list + (dolist (f file-list) + (let ((beg (point))) + (insert-directory f switches nil nil) + ;; Re-align fields, if necessary. + (dired-align-file beg (point)))) + (insert-directory dir switches wildcard (not wildcard)))))) ;; Quote certain characters, unless ls quoted them for us. (if (not (dired-switches-escape-p dired-actual-switches)) (save-excursion @@ -1273,11 +1353,14 @@ see `dired-use-ls-dired' for more details.") ;; Note that dired-build-subdir-alist will replace the name ;; by its expansion, so it does not matter whether what we insert ;; here is fully expanded, but it should be absolute. - (insert " " (directory-file-name (file-name-directory dir)) ":\n") + (insert " " (or (car-safe (insert-directory-wildcard-in-dir-p dir)) + (directory-file-name (file-name-directory dir))) ":\n") (setq content-point (point))) (when wildcard ;; Insert "wildcard" line where "total" line would be for a full dir. - (insert " wildcard " (file-name-nondirectory dir) "\n"))) + (insert " wildcard " (or (cdr-safe (insert-directory-wildcard-in-dir-p dir)) + (file-name-nondirectory dir)) + "\n"))) (dired-insert-set-properties content-point (point))))) (defun dired-insert-set-properties (beg end) @@ -1363,18 +1446,22 @@ ARG and NOCONFIRM, passed from `revert-buffer', are ignored." The positions have the form (BUFFER-POSITION WINDOW-POSITIONS). BUFFER-POSITION is the point position in the current Dired buffer. -It has the form (BUFFER DIRED-FILENAME BUFFER-POINT). +It has the form (BUFFER DIRED-FILENAME BUFFER-LINE-NUMBER). WINDOW-POSITIONS are current positions in all windows displaying this dired buffer. The window positions have the form (WINDOW -DIRED-FILENAME WINDOW-POINT)." +DIRED-FILENAME WINDOW-LINE-NUMBER). + +We store line numbers instead of point positions because the header +lines might change as well: when this happen the line number doesn't +change; the point does." (list - (list (current-buffer) (dired-get-filename nil t) (point)) + (list (current-buffer) (dired-get-filename nil t) (line-number-at-pos)) (mapcar (lambda (w) - (list w - (with-selected-window w - (dired-get-filename nil t)) - (window-point w))) + (with-selected-window w + (list w + (dired-get-filename nil t) + (line-number-at-pos (window-point w))))) (get-buffer-window-list nil 0 t)))) (defun dired-restore-positions (positions) @@ -1383,7 +1470,8 @@ DIRED-FILENAME WINDOW-POINT)." (buffer (nth 0 buf-file-pos))) (unless (and (nth 1 buf-file-pos) (dired-goto-file (nth 1 buf-file-pos))) - (goto-char (nth 2 buf-file-pos)) + (goto-char (point-min)) + (forward-line (1- (nth 2 buf-file-pos))) (dired-move-to-filename)) (dolist (win-file-pos (nth 1 positions)) ;; Ensure that window still displays the original buffer. @@ -1391,7 +1479,8 @@ DIRED-FILENAME WINDOW-POINT)." (with-selected-window (nth 0 win-file-pos) (unless (and (nth 1 win-file-pos) (dired-goto-file (nth 1 win-file-pos))) - (goto-char (nth 2 win-file-pos)) + (goto-char (point-min)) + (forward-line (1- (nth 2 win-file-pos))) (dired-move-to-filename))))))) (defun dired-remember-marks (beg end) @@ -1995,8 +2084,8 @@ Keybindings: ;; Ignore dired-hide-details-* value of invisible text property by default. (when (eq buffer-invisibility-spec t) (setq buffer-invisibility-spec (list t))) - (setq-local revert-buffer-function (function dired-revert)) - (setq-local buffer-stale-function (function dired-buffer-stale-p)) + (setq-local revert-buffer-function #'dired-revert) + (setq-local buffer-stale-function #'dired-buffer-stale-p) (setq-local page-delimiter "\n\n") (setq-local dired-directory (or dirname default-directory)) ;; list-buffers uses this to display the dir being edited in this buffer. @@ -2139,16 +2228,23 @@ directory in another window." (find-file (dired-get-file-for-visit)))) (defun dired-find-alternate-file () - "In Dired, visit this file or directory instead of the Dired buffer." + "In Dired, visit file or directory on current line via `find-alternate-file'. +This kills the Dired buffer, then visits the current line's file or directory." (interactive) (set-buffer-modified-p nil) (find-alternate-file (dired-get-file-for-visit))) ;; Don't override the setting from .emacs. ;;;###autoload (put 'dired-find-alternate-file 'disabled t) -(defun dired-mouse-find-file-other-window (event) - "In Dired, visit the file or directory name you click on." +(defun dired-mouse-find-file (event &optional find-file-func find-dir-func) + "In Dired, visit the file or directory name you click on. +The optional arguments FIND-FILE-FUNC and FIND-DIR-FUNC specify +functions to visit the file and directory, respectively. If +omitted or nil, these arguments default to `find-file' and `dired', +respectively." (interactive "e") + (or find-file-func (setq find-file-func 'find-file)) + (or find-dir-func (setq find-dir-func 'dired)) (let (window pos file) (save-excursion (setq window (posn-window (event-end event)) @@ -2163,9 +2259,19 @@ directory in another window." (dired-goto-subdir file)) (progn (select-window window) - (dired-other-window file))) + (funcall find-dir-func file))) (select-window window) - (find-file-other-window (file-name-sans-versions file t))))) + (funcall find-file-func (file-name-sans-versions file t))))) + +(defun dired-mouse-find-file-other-window (event) + "In Dired, visit the file or directory name you click on in another window." + (interactive "e") + (dired-mouse-find-file event 'find-file-other-window 'dired-other-window)) + +(defun dired-mouse-find-file-other-frame (event) + "In Dired, visit the file or directory name you click on in another frame." + (interactive "e") + (dired-mouse-find-file event 'find-file-other-frame 'dired-other-frame)) (defun dired-view-file () "In Dired, examine a file in view mode, returning to Dired when done. @@ -2245,10 +2351,7 @@ Otherwise, an error occurs in these cases." (if (and enable-multibyte-characters (not (multibyte-string-p file))) (setq file (string-to-multibyte file))))) - (and file (file-name-absolute-p file) - ;; A relative file name can start with ~. - ;; Don't treat it as absolute in this context. - (not (eq (aref file 0) ?~)) + (and file (files--name-absolute-system-p file) (setq already-absolute t)) (cond ((null file) @@ -2469,7 +2572,7 @@ You can then feed the file name(s) to other commands with \\[yank]." (interactive "P") (let ((string (or (dired-get-subdir) - (mapconcat (function identity) + (mapconcat #'identity (if arg (cond ((zerop (prefix-numeric-value arg)) (dired-get-marked-files)) @@ -2894,6 +2997,37 @@ Any other value means to ask for each directory." ;; Match anything but `.' and `..'. (defvar dired-re-no-dot "^\\([^.]\\|\\.\\([^.]\\|\\..\\)\\).*") +(defconst dired-delete-help + "Type: +`yes' to delete recursively the current directory, +`no' to skip to next, +`all' to delete all remaining directories with no more questions, +`quit' to exit, +`help' to show this help message.") + +(defun dired--yes-no-all-quit-help (prompt &optional help-msg) + "Ask a question with valid answers: yes, no, all, quit, help. +PROMPT must end with '? ', for instance, 'Delete it? '. +If optional arg HELP-MSG is non-nil, then is a message to show when +the user answers 'help'. Otherwise, default to `dired-delete-help'." + (let ((valid-answers (list "yes" "no" "all" "quit")) + (answer "") + (input-fn (lambda () + (read-string + (format "%s [yes, no, all, quit, help] " prompt))))) + (setq answer (funcall input-fn)) + (when (string= answer "help") + (with-help-window "*Help*" + (with-current-buffer "*Help*" + (insert (or help-msg dired-delete-help))))) + (while (not (member answer valid-answers)) + (unless (string= answer "help") + (beep) + (message "Please answer `yes' or `no' or `all' or `quit'") + (sleep-for 2)) + (setq answer (funcall input-fn))) + answer)) + ;; Delete file, possibly delete a directory and all its files. ;; This function is useful outside of dired. One could change its name ;; to e.g. recursive-delete-file and put it somewhere else. @@ -2909,23 +3043,27 @@ its possible values is: TRASH non-nil means to trash the file instead of deleting, provided `delete-by-moving-to-trash' (which see) is non-nil." - ;; This test is equivalent to - ;; (and (file-directory-p fn) (not (file-symlink-p fn))) - ;; but more efficient - (if (not (eq t (car (file-attributes file)))) - (delete-file file trash) - (if (and recursive - (directory-files file t dired-re-no-dot) ; Not empty. - (or (eq recursive 'always) - (yes-or-no-p (format "Recursively %s %s? " - (if (and trash - delete-by-moving-to-trash) - "trash" - "delete") - (dired-make-relative file))))) - (if (eq recursive 'top) (setq recursive 'always)) ; Don't ask again. - (setq recursive nil)) - (delete-directory file recursive trash))) + ;; This test is equivalent to + ;; (and (file-directory-p fn) (not (file-symlink-p fn))) + ;; but more efficient + (if (not (eq t (car (file-attributes file)))) + (delete-file file trash) + (let* ((empty-dir-p (null (directory-files file t dired-re-no-dot)))) + (if (and recursive (not empty-dir-p)) + (unless (eq recursive 'always) + (let ((prompt + (format "Recursively %s %s? " + (if (and trash delete-by-moving-to-trash) + "trash" + "delete") + (dired-make-relative file)))) + (pcase (dired--yes-no-all-quit-help prompt) ; Prompt user. + ('"all" (setq recursive 'always dired-recursive-deletes recursive)) + ('"yes" (if (eq recursive 'top) (setq recursive 'always))) + ('"no" (setq recursive nil)) + ('"quit" (keyboard-quit))))) + (setq recursive nil)) ; Empty dir or recursive is nil. + (delete-directory file recursive trash)))) (defun dired-do-flagged-delete (&optional nomessage) "In Dired, delete the files flagged for deletion. @@ -2940,9 +3078,10 @@ non-empty directories is allowed." (if (save-excursion (goto-char (point-min)) (re-search-forward regexp nil t)) (dired-internal-do-deletions - ;; this can't move point since ARG is nil - (dired-map-over-marks (cons (dired-get-filename) (point)) - nil) + (nreverse + ;; this can't move point since ARG is nil + (dired-map-over-marks (cons (dired-get-filename) (point)) + nil)) nil t) (or nomessage (message "(No deletions requested)"))))) @@ -2955,9 +3094,10 @@ non-empty directories is allowed." ;; dired-do-flagged-delete. (interactive "P") (dired-internal-do-deletions - ;; this may move point if ARG is an integer - (dired-map-over-marks (cons (dired-get-filename) (point)) - arg) + (nreverse + ;; this may move point if ARG is an integer + (dired-map-over-marks (cons (dired-get-filename) (point)) + arg)) arg t)) (defvar dired-deletion-confirmer 'yes-or-no-p) ; or y-or-n-p? @@ -2971,18 +3111,22 @@ non-empty directories is allowed." ;; lines still to be changed, so the (point) values in L stay valid. ;; Also, for subdirs in natural order, a subdir's files are deleted ;; before the subdir itself - the other way around would not work. - (let* ((files (mapcar (function car) l)) + (let* ((files (mapcar #'car l)) (count (length l)) (succ 0) + ;; Bind `dired-recursive-deletes' so that we can change it + ;; locally according with the user answer within `dired-delete-file'. + (dired-recursive-deletes dired-recursive-deletes) (trashing (and trash delete-by-moving-to-trash))) ;; canonicalize file list for pop up - (setq files (nreverse (mapcar (function dired-make-relative) files))) + (setq files (nreverse (mapcar #'dired-make-relative files))) (if (dired-mark-pop-up " *Deletions*" 'delete files dired-deletion-confirmer (format "%s %s " (if trashing "Trash" "Delete") (dired-mark-prompt arg files))) (save-excursion + (catch '--delete-cancel (let ((progress-reporter (make-progress-reporter (if trashing "Trashing..." "Deleting...") @@ -2999,9 +3143,10 @@ non-empty directories is allowed." (progress-reporter-update progress-reporter succ) (dired-fun-in-all-buffers (file-name-directory fn) (file-name-nondirectory fn) - (function dired-delete-entry) fn)) + #'dired-delete-entry fn)) + (quit (throw '--delete-cancel (message "OK, canceled"))) (error ;; catch errors from failed deletions - (dired-log "%s\n" err) + (dired-log "%s: %s\n" (car err) (error-message-string err)) (setq failures (cons (car (car l)) failures))))) (setq l (cdr l))) (if (not failures) @@ -3010,7 +3155,7 @@ non-empty directories is allowed." (format "%d of %d deletion%s failed" (length failures) count (dired-plural-s count)) - failures)))) + failures))))) (message "(No deletions performed)"))) (dired-move-to-filename)) @@ -3038,12 +3183,15 @@ non-empty directories is allowed." (dired-clean-up-after-deletion file)) (defvar dired-clean-up-buffers-too) +(defvar dired-clean-confirm-killing-deleted-buffers) (defun dired-clean-up-after-deletion (fn) "Clean up after a deleted file or directory FN. -Removes any expanded subdirectory of deleted directory. -If `dired-x' is loaded and `dired-clean-up-buffers-too' is non-nil, -also offers to kill buffers visiting deleted files and directories." +Removes any expanded subdirectory of deleted directory. If +`dired-x' is loaded and `dired-clean-up-buffers-too' is non-nil, +kill any buffers visiting those files, prompting for +confirmation. To disable the confirmation, see +`dired-clean-confirm-killing-deleted-buffers'." (save-excursion (and (cdr dired-subdir-alist) (dired-goto-subdir fn) (dired-kill-subdir))) @@ -3051,15 +3199,17 @@ also offers to kill buffers visiting deleted files and directories." (when (and (featurep 'dired-x) dired-clean-up-buffers-too) (let ((buf (get-file-buffer fn))) (and buf - (funcall #'y-or-n-p - (format "Kill buffer of %s, too? " - (file-name-nondirectory fn))) + (and dired-clean-confirm-killing-deleted-buffers + (funcall #'y-or-n-p + (format "Kill buffer of %s, too? " + (file-name-nondirectory fn)))) (kill-buffer buf))) (let ((buf-list (dired-buffers-for-dir (expand-file-name fn)))) (and buf-list - (y-or-n-p (format "Kill Dired buffer%s of %s, too? " - (dired-plural-s (length buf-list)) - (file-name-nondirectory fn))) + (and dired-clean-confirm-killing-deleted-buffers + (y-or-n-p (format "Kill Dired buffer%s of %s, too? " + (dired-plural-s (length buf-list)) + (file-name-nondirectory fn)))) (dolist (buf buf-list) (kill-buffer buf)))))) @@ -3215,9 +3365,14 @@ argument or confirmation)." (save-excursion (not (dired-move-to-filename)))) (defun dired-next-marked-file (arg &optional wrap opoint) - "Move to the next marked file. -If WRAP is non-nil, wrap around to the beginning of the buffer if -we reach the end." + "Move to the ARGth next marked file. +ARG is the numeric prefix argument and defaults to 1. +If WRAP is non-nil, which happens interactively, wrap around +to the beginning of the buffer and search from there, if no +marked file is found after this line. +Optional argument OPOINT specifies the buffer position to +return to if no ARGth marked file is found; it defaults to +the position where this command was invoked." (interactive "p\np") (or opoint (setq opoint (point)));; return to where interactively started (if (if (> arg 0) @@ -3234,9 +3389,11 @@ we reach the end." (dired-next-marked-file arg nil opoint)))) (defun dired-prev-marked-file (arg &optional wrap) - "Move to the previous marked file. -If WRAP is non-nil, wrap around to the end of the buffer if we -reach the beginning of the buffer." + "Move to the ARGth previous marked file. +ARG is the numeric prefix argument and defaults to 1. +If WRAP is non-nil, which happens interactively, wrap around +to the end of the buffer and search backwards from there, if +no ARGth marked file is found before this line." (interactive "p\np") (dired-next-marked-file (- arg) wrap)) @@ -3293,7 +3450,7 @@ this subdir." (let ((inhibit-read-only t)) (dired-repeat-over-lines (prefix-numeric-value arg) - (function (lambda () (delete-char 1) (insert dired-marker-char)))))))) + (lambda () (delete-char 1) (insert dired-marker-char))))))) (defun dired-unmark (arg &optional interactive) "Unmark the file at point in the Dired buffer. @@ -3928,7 +4085,7 @@ Ask means pop up a menu for the user to select one of copy, move or link." (cdr (nreverse (mapcar - (function (lambda (f) (desktop-file-name (car f) dirname))) + (lambda (f) (desktop-file-name (car f) dirname)) dired-subdir-alist))))) (defun dired-restore-desktop-buffer (_file-name diff --git a/lisp/dirtrack.el b/lisp/dirtrack.el index 6004c7c7ca2..137a0cbfa50 100644 --- a/lisp/dirtrack.el +++ b/lisp/dirtrack.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/disp-table.el b/lisp/disp-table.el index 59cc8d61ee2..1410e273298 100644 --- a/lisp/disp-table.el +++ b/lisp/disp-table.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/display-line-numbers.el b/lisp/display-line-numbers.el new file mode 100644 index 00000000000..15e04279156 --- /dev/null +++ b/lisp/display-line-numbers.el @@ -0,0 +1,106 @@ +;;; display-line-numbers.el --- interface for display-line-numbers -*- lexical-binding: t -*- + +;; Copyright (C) 2017 Free Software Foundation, Inc. + +;; Maintainer: emacs-devel@gnu.org +;; Keywords: convenience + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Provides a minor mode interface for `display-line-numbers'. +;; +;; Toggle display of line numbers with M-x display-line-numbers-mode. +;; To enable line numbering in all buffers, use M-x +;; global-display-line-numbers-mode. To change the default type of +;; line numbers displayed, customize display-line-numbers-type. + +;; NOTE: Customization variables for `display-line-numbers' itself are +;; defined in cus-start.el. + +;;; Code: + +(defgroup display-line-numbers nil + "Display line numbers in the buffer." + :group 'convenience + :group 'display) + +(defcustom display-line-numbers-type t + "The default type of line numbers to use in `display-line-numbers-mode'. +See `display-line-numbers' for value options." + :group 'display-line-numbers + :type '(choice (const :tag "Relative line numbers" relative) + (const :tag "Relative visual line numbers" visual) + (other :tag "Absolute line numbers" t)) + :version "26.1") + +(defcustom display-line-numbers-grow-only nil + "If non-nil, do not shrink line number width." + :group 'display-line-numbers + :type 'boolean + :version "26.1") + +(defcustom display-line-numbers-width-start nil + "If non-nil, count number of lines to use for line number width. +When `display-line-numbers-mode' is turned on, +`display-line-numbers-width' is set to the minimum width necessary +to display all line numbers in the buffer." + :group 'display-line-numbers + :type 'boolean + :version "26.1") + +(defun display-line-numbers-update-width () + "Prevent the line number width from shrinking." + (let ((width (line-number-display-width))) + (when (> width (or display-line-numbers-width 1)) + (setq display-line-numbers-width width)))) + +;;;###autoload +(define-minor-mode display-line-numbers-mode + "Toggle display of line numbers in the buffer. +This uses `display-line-numbers' internally. + +To change the type of line numbers displayed by default, +customize `display-line-numbers-type'. To change the type while +the mode is on, set `display-line-numbers' directly." + :lighter nil + (if display-line-numbers-mode + (progn + (when display-line-numbers-width-start + (setq display-line-numbers-width + (length (number-to-string + (count-lines (point-min) (point-max)))))) + (when display-line-numbers-grow-only + (add-hook 'pre-command-hook #'display-line-numbers-update-width nil t)) + (setq display-line-numbers display-line-numbers-type)) + (remove-hook 'pre-command-hook #'display-line-numbers-update-width t) + (setq display-line-numbers nil))) + +(defun display-line-numbers--turn-on () + "Turn on `display-line-numbers-mode'." + (unless (or (minibufferp) + ;; taken from linum.el + (and (daemonp) (null (frame-parameter nil 'client)))) + (display-line-numbers-mode))) + +;;;###autoload +(define-globalized-minor-mode global-display-line-numbers-mode + display-line-numbers-mode display-line-numbers--turn-on) + +(provide 'display-line-numbers) + +;;; display-line-numbers.el ends here diff --git a/lisp/dnd.el b/lisp/dnd.el index 3ae5e4f8945..c5ee8975ac6 100644 --- a/lisp/dnd.el +++ b/lisp/dnd.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -144,7 +144,7 @@ Return nil if URI is not a local file." str)) uri t t)) -;; http://lists.gnu.org/archive/html/emacs-devel/2006-05/msg01060.html +;; https://lists.gnu.org/r/emacs-devel/2006-05/msg01060.html (defun dnd-get-local-file-name (uri &optional must-exist) "Return file name converted from file:/// or file: syntax. URI is the uri for the file. If MUST-EXIST is given and non-nil, diff --git a/lisp/doc-view.el b/lisp/doc-view.el index 2eb555821d9..7213ea2ff6b 100644 --- a/lisp/doc-view.el +++ b/lisp/doc-view.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Requirements: @@ -451,7 +451,7 @@ Typically \"page-%s.png\".") (if (and (eq 'pdf doc-view-doc-type) (executable-find "pdfinfo")) ;; We don't want to revert if the PDF file is corrupted which - ;; might happen when it it currently recompiled from a tex + ;; might happen when it is currently recompiled from a tex ;; file. (TODO: We'd like to have something like that also ;; for other types, at least PS, but I don't know a good way ;; to test if a PS file is complete.) diff --git a/lisp/dom.el b/lisp/dom.el index 4d0d4233db3..70938f539be 100644 --- a/lisp/dom.el +++ b/lisp/dom.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -162,7 +162,7 @@ ATTRIBUTE would typically be `class', `id' or the like." (defun dom-previous-sibling (dom node) "Return the previous sibling of NODE in DOM." - (when-let (parent (dom-parent dom node)) + (when-let* ((parent (dom-parent dom node))) (let ((siblings (dom-children parent)) (previous nil)) (while siblings diff --git a/lisp/dos-fns.el b/lisp/dos-fns.el index 1d48371912f..f69335d2c21 100644 --- a/lisp/dos-fns.el +++ b/lisp/dos-fns.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/dos-vars.el b/lisp/dos-vars.el index d552d518a01..90052ce0282 100644 --- a/lisp/dos-vars.el +++ b/lisp/dos-vars.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/dos-w32.el b/lisp/dos-w32.el index ff5310e1fb3..affadee2fe7 100644 --- a/lisp/dos-w32.el +++ b/lisp/dos-w32.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/double.el b/lisp/double.el index ab9e23b301f..91dc095fed9 100644 --- a/lisp/double.el +++ b/lisp/double.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/dynamic-setting.el b/lisp/dynamic-setting.el index 3d80f9dd9af..41667e61880 100644 --- a/lisp/dynamic-setting.el +++ b/lisp/dynamic-setting.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -92,4 +92,3 @@ Changes can be (define-key special-event-map [config-changed-event] 'dynamic-setting-handle-config-changed-event) - diff --git a/lisp/ebuff-menu.el b/lisp/ebuff-menu.el index b399be5d303..51c33c64be4 100644 --- a/lisp/ebuff-menu.el +++ b/lisp/ebuff-menu.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/echistory.el b/lisp/echistory.el index 2146faae1d7..588f60521dd 100644 --- a/lisp/echistory.el +++ b/lisp/echistory.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/ecomplete.el b/lisp/ecomplete.el index 70277facb0a..014b4b21122 100644 --- a/lisp/ecomplete.el +++ b/lisp/ecomplete.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -55,7 +55,7 @@ (defun ecomplete-add-item (type key text) (let ((elems (assq type ecomplete-database)) - (now (string-to-number (format "%.0f" (float-time)))) + (now (string-to-number (format-time-string "%s"))) entry) (unless elems (push (setq elems (list type)) ecomplete-database)) diff --git a/lisp/edmacro.el b/lisp/edmacro.el index 5fefc3102d0..dc840ef1f19 100644 --- a/lisp/edmacro.el +++ b/lisp/edmacro.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -88,20 +88,26 @@ Default nil means to write characters above \\177 in octal notation." (defun edit-kbd-macro (keys &optional prefix finish-hook store-hook) "Edit a keyboard macro. At the prompt, type any key sequence which is bound to a keyboard macro. -Or, type `C-x e' or RET to edit the last keyboard macro, `C-h l' to edit -the last 300 keystrokes as a keyboard macro, or `\\[execute-extended-command]' to edit a macro by -its command name. +Or, type `\\[kmacro-end-and-call-macro]' or RET to edit the last +keyboard macro, `\\[view-lossage]' to edit the last 300 +keystrokes as a keyboard macro, or `\\[execute-extended-command]' +to edit a macro by its command name. With a prefix argument, format the macro in a more concise way." - (interactive "kKeyboard macro to edit (C-x e, M-x, C-h l, or keys): \nP") + (interactive + (list (read-key-sequence (substitute-command-keys "Keyboard macro to edit \ +\(\\[kmacro-end-and-call-macro], \\[execute-extended-command], \\[view-lossage],\ + or keys): ")) + current-prefix-arg)) (when keys (let ((cmd (if (arrayp keys) (key-binding keys) keys)) + (cmd-noremap (when (arrayp keys) (key-binding keys nil t))) (mac nil) (mac-counter nil) (mac-format nil) kmacro) (cond (store-hook (setq mac keys) (setq cmd nil)) - ((or (memq cmd '(call-last-kbd-macro kmacro-call-macro - kmacro-end-or-call-macro kmacro-end-and-call-macro)) + ((or (memq cmd '(call-last-kbd-macro kmacro-call-macro kmacro-end-or-call-macro kmacro-end-and-call-macro)) + (memq cmd-noremap '(call-last-kbd-macro kmacro-call-macro kmacro-end-or-call-macro kmacro-end-and-call-macro)) (member keys '("\r" [return]))) (or last-kbd-macro (y-or-n-p "No keyboard macro defined. Create one? ") @@ -109,13 +115,14 @@ With a prefix argument, format the macro in a more concise way." (setq mac (or last-kbd-macro "")) (setq keys nil) (setq cmd 'last-kbd-macro)) - ((eq cmd 'execute-extended-command) + ((memq 'execute-extended-command (list cmd cmd-noremap)) (setq cmd (read-command "Name of keyboard macro to edit: ")) (if (string-equal cmd "") (error "No command name given")) (setq keys nil) (setq mac (symbol-function cmd))) - ((memq cmd '(view-lossage electric-view-lossage)) + ((or (memq cmd '(view-lossage electric-view-lossage)) + (memq cmd-noremap '(view-lossage electric-view-lossage))) (setq mac (recent-keys)) (setq keys nil) (setq cmd 'last-kbd-macro)) diff --git a/lisp/ehelp.el b/lisp/ehelp.el index a3719f63915..1e89f84313c 100644 --- a/lisp/ehelp.el +++ b/lisp/ehelp.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/elec-pair.el b/lisp/elec-pair.el index 87e82e24fb1..7f523d1df45 100644 --- a/lisp/elec-pair.el +++ b/lisp/elec-pair.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -28,9 +28,9 @@ ;;; Electric pairing. (defcustom electric-pair-pairs - '((?\" . ?\") - ((nth 0 electric-quote-chars) . (nth 1 electric-quote-chars)) - ((nth 2 electric-quote-chars) . (nth 3 electric-quote-chars))) + `((?\" . ?\") + (,(nth 0 electric-quote-chars) . ,(nth 1 electric-quote-chars)) + (,(nth 2 electric-quote-chars) . ,(nth 3 electric-quote-chars))) "Alist of pairs that should be used regardless of major mode. Pairs of delimiters in this list are a fallback in case they have @@ -42,11 +42,10 @@ See also the variable `electric-pair-text-pairs'." :group 'electricity :type '(repeat (cons character character))) -;;;###autoload (defcustom electric-pair-text-pairs - '((?\" . ?\" ) - ((nth 0 electric-quote-chars) . (nth 1 electric-quote-chars)) - ((nth 2 electric-quote-chars) . (nth 3 electric-quote-chars))) + `((?\" . ?\") + (,(nth 0 electric-quote-chars) . ,(nth 1 electric-quote-chars)) + (,(nth 2 electric-quote-chars) . ,(nth 3 electric-quote-chars))) "Alist of pairs that should always be used in comments and strings. Pairs of delimiters in this list are a fallback in case they have diff --git a/lisp/electric.el b/lisp/electric.el index 4078ef8193e..cee35621397 100644 --- a/lisp/electric.el +++ b/lisp/electric.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -227,7 +227,7 @@ Python does not lend itself to fully automatic indentation.") haskell-indentation-indent-line haskell-indent-cycle haskell-simple-indent yaml-indent-line) "List of indent functions that can't reindent. -If `line-indent-function' is one of those, then `electric-indent-mode' will +If `indent-line-function' is one of those, then `electric-indent-mode' will not try to reindent lines. It is normally better to make the major mode set `electric-indent-inhibit', but this can be used as a workaround.") @@ -443,49 +443,95 @@ quote, left double quote, and right double quote, respectively." :version "25.1" :type 'boolean :safe 'booleanp :group 'electricity) +(defcustom electric-quote-context-sensitive nil + "Non-nil means to replace \\=' with an electric quote depending on context. +If `electric-quote-context-sensitive' is non-nil, Emacs replaces +\\=' and \\='\\=' with an opening quote after a line break, +whitespace, opening parenthesis, or quote and leaves \\=` alone." + :version "26.1" + :type 'boolean :safe #'booleanp :group 'electricity) + +(defcustom electric-quote-replace-double nil + "Non-nil means to replace \" with an electric double quote. +Emacs replaces \" with an opening double quote after a line +break, whitespace, opening parenthesis, or quote, and with a +closing double quote otherwise." + :version "26.1" + :type 'boolean :safe #'booleanp :group 'electricity) + +(defvar electric-quote-inhibit-functions () + "List of functions that should inhibit electric quoting. +When the variable `electric-quote-mode' is non-nil, Emacs will +call these functions in order after the user has typed an \\=` or +\\=' character. If one of them returns non-nil, electric quote +substitution is inhibited. The functions are called after the +\\=` or \\=' character has been inserted with point directly +after the inserted character. The functions in this hook should +not move point or change the current buffer.") + +(defvar electric-pair-text-pairs) + (defun electric-quote-post-self-insert-function () "Function that `electric-quote-mode' adds to `post-self-insert-hook'. This requotes when a quoting key is typed." (when (and electric-quote-mode - (memq last-command-event '(?\' ?\`))) - (let ((start - (if (and comment-start comment-use-syntax) - (when (or electric-quote-comment electric-quote-string) - (let* ((syntax (syntax-ppss)) - (beg (nth 8 syntax))) - (and beg - (or (and electric-quote-comment (nth 4 syntax)) - (and electric-quote-string (nth 3 syntax))) - ;; Do not requote a quote that starts or ends - ;; a comment or string. - (eq beg (nth 8 (save-excursion - (syntax-ppss (1- (point))))))))) - (and electric-quote-paragraph - (derived-mode-p 'text-mode) - (or (eq last-command-event ?\`) - (save-excursion (backward-paragraph) (point))))))) - (pcase electric-quote-chars - (`(,q< ,q> ,q<< ,q>>) - (when start - (save-excursion - (if (eq last-command-event ?\`) - (cond ((search-backward (string q< ?`) (- (point) 2) t) - (replace-match (string q<<)) - (when (and electric-pair-mode - (eq (cdr-safe - (assq q< electric-pair-text-pairs)) - (char-after))) - (delete-char 1)) - (setq last-command-event q<<)) - ((search-backward "`" (1- (point)) t) - (replace-match (string q<)) - (setq last-command-event q<))) - (cond ((search-backward (string q> ?') (- (point) 2) t) - (replace-match (string q>>)) - (setq last-command-event q>>)) - ((search-backward "'" (1- (point)) t) - (replace-match (string q>)) - (setq last-command-event q>))))))))))) + (or (eq last-command-event ?\') + (and (not electric-quote-context-sensitive) + (eq last-command-event ?\`)) + (and electric-quote-replace-double + (eq last-command-event ?\"))) + (not (run-hook-with-args-until-success + 'electric-quote-inhibit-functions)) + (if (derived-mode-p 'text-mode) + electric-quote-paragraph + (and comment-start comment-use-syntax + (or electric-quote-comment electric-quote-string) + (let* ((syntax (syntax-ppss)) + (beg (nth 8 syntax))) + (and beg + (or (and electric-quote-comment (nth 4 syntax)) + (and electric-quote-string (nth 3 syntax))) + ;; Do not requote a quote that starts or ends + ;; a comment or string. + (eq beg (nth 8 (save-excursion + (syntax-ppss (1- (point))))))))))) + (pcase electric-quote-chars + (`(,q< ,q> ,q<< ,q>>) + (save-excursion + (let ((backtick ?\`)) + (if (or (eq last-command-event ?\`) + (and (or electric-quote-context-sensitive + electric-quote-replace-double) + (save-excursion + (backward-char) + (or (bobp) (bolp) + (memq (char-before) (list q< q<<)) + (memq (char-syntax (char-before)) + '(?\s ?\()))) + (setq backtick ?\'))) + (cond ((search-backward (string q< backtick) (- (point) 2) t) + (replace-match (string q<<)) + (when (and electric-pair-mode + (eq (cdr-safe + (assq q< electric-pair-text-pairs)) + (char-after))) + (delete-char 1)) + (setq last-command-event q<<)) + ((search-backward (string backtick) (1- (point)) t) + (replace-match (string q<)) + (setq last-command-event q<)) + ((search-backward "\"" (1- (point)) t) + (replace-match (string q<<)) + (setq last-command-event q<<))) + (cond ((search-backward (string q> ?') (- (point) 2) t) + (replace-match (string q>>)) + (setq last-command-event q>>)) + ((search-backward "'" (1- (point)) t) + (replace-match (string q>)) + (setq last-command-event q>)) + ((search-backward "\"" (1- (point)) t) + (replace-match (string q>>)) + (setq last-command-event q>>)))))))))) (put 'electric-quote-post-self-insert-function 'priority 10) diff --git a/lisp/elide-head.el b/lisp/elide-head.el index 3904edd7f64..c6d8c9009b9 100644 --- a/lisp/elide-head.el +++ b/lisp/elide-head.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -57,7 +57,7 @@ If not, see <http://www\\.gnu\\.org/licenses/>\\)\\.") "SUCH DAMAGE\\.") ; BSD ("Permission is hereby granted, free of charge" . ; X11 "authorization from the X Consortium\\.")) - "Alist of regexps defining start end end of text to elide. + "Alist of regexps defining start and end of text to elide. The cars of elements of the list are searched for in order. Text is elided with an invisible overlay from the end of the line where the diff --git a/lisp/emacs-lisp/advice.el b/lisp/emacs-lisp/advice.el index 3342bea209a..82867667756 100644 --- a/lisp/emacs-lisp/advice.el +++ b/lisp/emacs-lisp/advice.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; LCD Archive Entry: ;; advice|Hans Chalupsky|hans@cs.buffalo.edu| @@ -502,7 +502,7 @@ ;; important advantage is that it allows the implementation of forward advice. ;; Advice information for a certain function accumulates as the value of the ;; `advice-info' property of the function symbol. This accumulation is -;; completely independent of the fact that that function might not yet be +;; completely independent of the fact that the function might not yet be ;; defined. The macros `defun' and `defmacro' check whether the ;; function/macro they defined had advice information ;; associated with it. If so and forward advice is enabled, the original diff --git a/lisp/emacs-lisp/autoload.el b/lisp/emacs-lisp/autoload.el index 8fe94013700..71fc51e27b0 100644 --- a/lisp/emacs-lisp/autoload.el +++ b/lisp/emacs-lisp/autoload.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -497,6 +497,7 @@ Return non-nil in the case where no autoloads were added at point." Standard prefixes won't be registered anyway. I.e. if a file \"foo.el\" defines variables or functions that use \"foo-\" as prefix, that will not be registered. But all other prefixes will be included.") +(put 'autoload-compute-prefixes 'safe #'booleanp) (defconst autoload-def-prefixes-max-entries 5 "Target length of the list of definition prefixes per file. @@ -761,6 +762,7 @@ FILE's modification time." "def-edebug-spec" ;; Hmm... this is getting ugly: "define-widget" + "define-erc-module" "define-erc-response-handler" "defun-rcirc-command")))) (push (match-string 2) defs)) @@ -873,18 +875,24 @@ FILE's modification time." ;; For parallel builds, to stop another process reading a half-written file. (defun autoload--save-buffer () "Save current buffer to its file, atomically." - ;; Copied from byte-compile-file. + ;; Similar to byte-compile-file. (let* ((version-control 'never) - (tempfile (make-temp-name buffer-file-name)) + (tempfile (make-temp-file buffer-file-name)) + (default-modes (default-file-modes)) + (temp-modes (logand default-modes #o600)) + (desired-modes (logand default-modes + (or (file-modes buffer-file-name) #o666))) (kill-emacs-hook (cons (lambda () (ignore-errors (delete-file tempfile))) kill-emacs-hook))) + (unless (= temp-modes desired-modes) + (set-file-modes tempfile desired-modes)) (write-region (point-min) (point-max) tempfile nil 1) (backup-buffer) - (rename-file tempfile buffer-file-name t) - (set-buffer-modified-p nil) - (set-visited-file-modtime) - (or noninteractive (message "Wrote %s" buffer-file-name)))) + (rename-file tempfile buffer-file-name t)) + (set-buffer-modified-p nil) + (set-visited-file-modtime) + (or noninteractive (message "Wrote %s" buffer-file-name))) (defun autoload-save-buffers () (while autoload-modified-buffers @@ -892,7 +900,7 @@ FILE's modification time." (autoload--save-buffer)))) ;; FIXME This command should be deprecated. -;; See http://debbugs.gnu.org/22213#41 +;; See https://debbugs.gnu.org/22213#41 ;;;###autoload (defun update-file-autoloads (file &optional save-after outfile) "Update the autoloads for FILE. @@ -911,7 +919,7 @@ Return FILE if there was no autoload cookie in it, else nil." (let* ((generated-autoload-file (or outfile generated-autoload-file)) (autoload-modified-buffers nil) ;; We need this only if the output file handles more than one input. - ;; See http://debbugs.gnu.org/22213#38 and subsequent. + ;; See https://debbugs.gnu.org/22213#38 and subsequent. (autoload-timestamps t) (no-autoloads (autoload-generate-file-autoloads file))) (if autoload-modified-buffers diff --git a/lisp/emacs-lisp/avl-tree.el b/lisp/emacs-lisp/avl-tree.el index 17f1ffa9f61..8435b29b04a 100644 --- a/lisp/emacs-lisp/avl-tree.el +++ b/lisp/emacs-lisp/avl-tree.el @@ -23,7 +23,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -52,7 +52,7 @@ ;;; Code: (eval-when-compile (require 'cl-lib)) - +(require 'generator) ;; ================================================================ @@ -670,6 +670,21 @@ a null element stored in the AVL tree.)" (null (avl-tree--stack-store avl-tree-stack))) +(iter-defun avl-tree-iter (tree &optional reverse) + "Return an AVL tree iterator object. + +Calling `iter-next' on this object will retrieve the next element +from TREE. If REVERSE is non-nil, elements are returned in +reverse order. + +Note that any modification to TREE *immediately* invalidates all +iterators created from TREE before the modification (in +particular, calling `iter-next' will give unpredictable results)." + (let ((stack (avl-tree-stack tree reverse))) + (while (not (avl-tree-stack-empty-p stack)) + (iter-yield (avl-tree-stack-pop stack))))) + + (provide 'avl-tree) ;;; avl-tree.el ends here diff --git a/lisp/emacs-lisp/backquote.el b/lisp/emacs-lisp/backquote.el index bb877dd2c97..4649cf343c4 100644 --- a/lisp/emacs-lisp/backquote.el +++ b/lisp/emacs-lisp/backquote.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emacs-lisp/benchmark.el b/lisp/emacs-lisp/benchmark.el index a2217d20953..02db21a7e53 100644 --- a/lisp/emacs-lisp/benchmark.el +++ b/lisp/emacs-lisp/benchmark.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -34,13 +34,11 @@ (defmacro benchmark-elapse (&rest forms) "Return the time in seconds elapsed for execution of FORMS." (declare (indent 0) (debug t)) - (let ((t1 (make-symbol "t1")) - (t2 (make-symbol "t2"))) - `(let (,t1 ,t2) + (let ((t1 (make-symbol "t1"))) + `(let (,t1) (setq ,t1 (current-time)) ,@forms - (setq ,t2 (current-time)) - (float-time (time-subtract ,t2 ,t1))))) + (float-time (time-subtract nil ,t1))))) ;;;###autoload (defmacro benchmark-run (&optional repetitions &rest forms) diff --git a/lisp/emacs-lisp/bindat.el b/lisp/emacs-lisp/bindat.el index d345151907b..0f86923518c 100644 --- a/lisp/emacs-lisp/bindat.el +++ b/lisp/emacs-lisp/bindat.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index 962a7ae5cde..623985f44f9 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -1281,7 +1281,10 @@ ;; errors to compile time. (let ((pure-fns - '(concat symbol-name regexp-opt regexp-quote string-to-syntax))) + '(concat symbol-name regexp-opt regexp-quote string-to-syntax + string-to-char + ash lsh logb lognot logior logxor + ceiling floor))) (while pure-fns (put (car pure-fns) 'pure t) (setq pure-fns (cdr pure-fns))) diff --git a/lisp/emacs-lisp/byte-run.el b/lisp/emacs-lisp/byte-run.el index 4fa31dd4c27..d6c43ecf462 100644 --- a/lisp/emacs-lisp/byte-run.el +++ b/lisp/emacs-lisp/byte-run.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -285,9 +285,13 @@ The return value is undefined. def)))) -;; Redefined in byte-optimize.el. -;; This is not documented--it's not clear that we should promote it. -(fset 'inline 'progn) +;; Redefined in byte-opt.el. +;; This was undocumented and unused for decades. +(defalias 'inline 'progn + "Like `progn', but when compiled inline top-level function calls in body. +You don't need this. (See bytecomp.el commentary for more details.) + +\(fn BODY...)") ;;; Interface to inline functions. @@ -318,6 +322,7 @@ The return value is undefined. (defmacro defsubst (name arglist &rest body) "Define an inline function. The syntax is just like that of `defun'. + \(fn NAME ARGLIST &optional DOCSTRING DECL &rest BODY)" (declare (debug defun) (doc-string 3)) (or (memq (get name 'byte-optimizer) diff --git a/lisp/emacs-lisp/bytecomp.el b/lisp/emacs-lisp/bytecomp.el index e5b9b47b1d0..f69ac7f342a 100644 --- a/lisp/emacs-lisp/bytecomp.el +++ b/lisp/emacs-lisp/bytecomp.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -144,14 +144,20 @@ (defcustom emacs-lisp-file-regexp "\\.el\\'" "Regexp which matches Emacs Lisp source files. -If you change this, you might want to set `byte-compile-dest-file-function'." +If you change this, you might want to set `byte-compile-dest-file-function'. +\(Note that the assumption of a \".elc\" suffix for compiled files +is hard-coded in various places in Emacs.)" + ;; Eg is_elc in Fload. :group 'bytecomp :type 'regexp) (defcustom byte-compile-dest-file-function nil "Function for the function `byte-compile-dest-file' to call. It should take one argument, the name of an Emacs Lisp source -file name, and return the name of the compiled file." +file name, and return the name of the compiled file. +\(Note that the assumption that the source and compiled files +are found in the same directory is hard-coded in various places in Emacs.)" + ;; Eg load-prefer-newer, documentation lookup IIRC. :group 'bytecomp :type '(choice (const nil) function) :version "23.2") @@ -166,12 +172,19 @@ file name, and return the name of the compiled file." (funcall handler 'byte-compiler-base-file-name filename) filename))) +;; Sadly automake relies on this misfeature up to at least version 1.15.1. +(if (fboundp 'byte-compile-dest-file) + (or (featurep 'bytecomp) + (display-warning 'bytecomp (format-message "\ +Changing `byte-compile-dest-file' is obsolete (as of 23.2); +set `byte-compile-dest-file-function' instead."))) (defun byte-compile-dest-file (filename) "Convert an Emacs Lisp source file name to a compiled file name. If `byte-compile-dest-file-function' is non-nil, uses that function to do the work. Otherwise, if FILENAME matches -`emacs-lisp-file-regexp' (by default, files with the extension `.el'), -adds `c' to it; otherwise adds `.elc'." +`emacs-lisp-file-regexp' (by default, files with the extension \".el\"), +replaces the matching part (and anything after it) with \".elc\"; +otherwise adds \".elc\"." (if byte-compile-dest-file-function (funcall byte-compile-dest-file-function filename) (setq filename (file-name-sans-versions @@ -179,6 +192,7 @@ adds `c' to it; otherwise adds `.elc'." (cond ((string-match emacs-lisp-file-regexp filename) (concat (substring filename 0 (match-beginning 0)) ".elc")) (t (concat filename ".elc"))))) +) ;; This can be the 'byte-compile property of any symbol. (autoload 'byte-compile-inline-expand "byte-opt") @@ -1183,7 +1197,29 @@ Each function's symbol gets added to `byte-compile-noruntime-functions'." (compilation-forget-errors) pt)))) +(defvar byte-compile-log-warning-function + #'byte-compile--log-warning-for-byte-compile + "Function called when encountering a warning or error. +Called with arguments (STRING POSITION FILL LEVEL). STRING is a +message describing the problem. POSITION is a buffer position +where the problem was detected. FILL is a prefix as in +`warning-fill-prefix'. LEVEL is the level of the +problem (`:warning' or `:error'). POSITION, FILL and LEVEL may be +nil.") + (defun byte-compile-log-warning (string &optional fill level) + "Log a byte-compilation warning. +STRING, FILL and LEVEL are as described in +`byte-compile-log-warning-function', which see." + (funcall byte-compile-log-warning-function + string byte-compile-last-position + fill + level)) + +(defun byte-compile--log-warning-for-byte-compile (string &optional + _position + fill + level) "Log a message STRING in `byte-compile-log-buffer'. Also log the current function and file if not already done. If FILL is non-nil, set `warning-fill-prefix' to four spaces. LEVEL @@ -1263,12 +1299,6 @@ when printing the error message." (defun byte-compile-arglist-signature (arglist) (cond - ;; New style byte-code arglist. - ((integerp arglist) - (cons (logand arglist 127) ;Mandatory. - (if (zerop (logand arglist 128)) ;No &rest. - (lsh arglist -8)))) ;Nonrest. - ;; Old style byte-code, or interpreted function. ((listp arglist) (let ((args 0) opts @@ -1289,6 +1319,19 @@ when printing the error message." ;; Unknown arglist. (t '(0)))) +(defun byte-compile--function-signature (f) + ;; Similar to help-function-arglist, except that it returns the info + ;; in a different format. + (and (eq 'macro (car-safe f)) (setq f (cdr f))) + ;; Advice wrappers have "catch all" args, so fetch the actual underlying + ;; function to find the real arguments. + (while (advice--p f) (setq f (advice--cdr f))) + (if (eq (car-safe f) 'declared) + (byte-compile-arglist-signature (nth 1 f)) + (condition-case nil + (let ((sig (func-arity f))) + (if (numberp (cdr sig)) sig (list (car sig)))) + (error '(0))))) (defun byte-compile-arglist-signatures-congruent-p (old new) (not (or @@ -1330,19 +1373,7 @@ when printing the error message." (defun byte-compile-callargs-warn (form) (let* ((def (or (byte-compile-fdefinition (car form) nil) (byte-compile-fdefinition (car form) t))) - (sig (if (and def (not (eq def t))) - (progn - (and (eq (car-safe def) 'macro) - (eq (car-safe (cdr-safe def)) 'lambda) - (setq def (cdr def))) - (byte-compile-arglist-signature - (if (memq (car-safe def) '(declared lambda)) - (nth 1 def) - (if (byte-code-function-p def) - (aref def 0) - '(&rest def))))) - (if (subrp (symbol-function (car form))) - (subr-arity (symbol-function (car form)))))) + (sig (byte-compile--function-signature def)) (ncall (length (cdr form)))) ;; Check many or unevalled from subr-arity. (if (and (cdr-safe sig) @@ -1461,15 +1492,7 @@ extra args." (and initial (symbolp initial) (setq old (byte-compile-fdefinition initial nil))) (when (and old (not (eq old t))) - (and (eq 'macro (car-safe old)) - (eq 'lambda (car-safe (cdr-safe old))) - (setq old (cdr old))) - (let ((sig1 (byte-compile-arglist-signature - (pcase old - (`(lambda ,args . ,_) args) - (`(closure ,_ ,args . ,_) args) - ((pred byte-code-function-p) (aref old 0)) - (_ '(&rest def))))) + (let ((sig1 (byte-compile--function-signature old)) (sig2 (byte-compile-arglist-signature arglist))) (unless (byte-compile-arglist-signatures-congruent-p sig1 sig2) (byte-compile-set-symbol-position name) @@ -1585,6 +1608,7 @@ extra args." ;; macroenvironment. (copy-alist byte-compile-initial-macro-environment)) (byte-compile--outbuffer nil) + (overriding-plist-environment nil) (byte-compile-function-environment nil) (byte-compile-bound-variables nil) (byte-compile-lexical-variables nil) @@ -1901,25 +1925,33 @@ The value is non-nil if there were no errors, nil if errors." (insert "\n") ; aaah, unix. (if (file-writable-p target-file) ;; We must disable any code conversion here. - (let* ((coding-system-for-write 'no-conversion) - ;; Write to a tempfile so that if another Emacs - ;; process is trying to load target-file (eg in a - ;; parallel bootstrap), it does not risk getting a - ;; half-finished file. (Bug#4196) - (tempfile (make-temp-name target-file)) - (kill-emacs-hook - (cons (lambda () (ignore-errors (delete-file tempfile))) - kill-emacs-hook))) - (write-region (point-min) (point-max) tempfile nil 1) - ;; This has the intentional side effect that any - ;; hard-links to target-file continue to - ;; point to the old file (this makes it possible - ;; for installed files to share disk space with - ;; the build tree, without causing problems when - ;; emacs-lisp files in the build tree are - ;; recompiled). Previously this was accomplished by - ;; deleting target-file before writing it. - (rename-file tempfile target-file t) + (progn + (let* ((coding-system-for-write 'no-conversion) + ;; Write to a tempfile so that if another Emacs + ;; process is trying to load target-file (eg in a + ;; parallel bootstrap), it does not risk getting a + ;; half-finished file. (Bug#4196) + (tempfile + (make-temp-file (file-name-nondirectory target-file))) + (default-modes (default-file-modes)) + (temp-modes (logand default-modes #o600)) + (desired-modes (logand default-modes #o666)) + (kill-emacs-hook + (cons (lambda () (ignore-errors + (delete-file tempfile))) + kill-emacs-hook))) + (unless (= temp-modes desired-modes) + (set-file-modes tempfile desired-modes)) + (write-region (point-min) (point-max) tempfile nil 1) + ;; This has the intentional side effect that any + ;; hard-links to target-file continue to + ;; point to the old file (this makes it possible + ;; for installed files to share disk space with + ;; the build tree, without causing problems when + ;; emacs-lisp files in the build tree are + ;; recompiled). Previously this was accomplished by + ;; deleting target-file before writing it. + (rename-file tempfile target-file t)) (or noninteractive (message "Wrote %s" target-file))) ;; This is just to give a better error message than write-region (let ((exists (file-exists-p target-file))) @@ -2574,7 +2606,7 @@ not to take responsibility for the actual compilation of the code." (let ((index ;; If there's no doc string, provide -1 as the "doc string ;; index" so that no element will be treated as a doc string. - (if (not (stringp (car body))) -1 4))) + (if (not (stringp (documentation code t))) -1 4))) ;; Output the form by hand, that's much simpler than having ;; b-c-output-file-form analyze the defalias. (byte-compile-output-docform @@ -3347,15 +3379,14 @@ for symbols generated by the byte compiler itself." (defun byte-compile-constant (const) (if byte-compile--for-effect (setq byte-compile--for-effect nil) - (when (symbolp const) - (byte-compile-set-symbol-position const)) - (byte-compile-out 'byte-constant (byte-compile-get-constant const)))) + (inline (byte-compile-push-constant const)))) ;; Use this for a constant that is not the value of its containing form. ;; This ignores byte-compile--for-effect. (defun byte-compile-push-constant (const) - (let ((byte-compile--for-effect nil)) - (inline (byte-compile-constant const)))) + (when (symbolp const) + (byte-compile-set-symbol-position const)) + (byte-compile-out 'byte-constant (byte-compile-get-constant const))) ;; Compile those primitive ordinary functions ;; which have special byte codes just for speed. @@ -4725,6 +4756,35 @@ binding slots have been popped." 'byte-hunk-handler 'byte-compile-form-make-variable-buffer-local) (defun byte-compile-form-make-variable-buffer-local (form) (byte-compile-keep-pending form 'byte-compile-normal-call)) + +(put 'function-put 'byte-hunk-handler 'byte-compile-define-symbol-prop) +(put 'define-symbol-prop 'byte-hunk-handler 'byte-compile-define-symbol-prop) +(defun byte-compile-define-symbol-prop (form) + (pcase form + ((and `(,op ,fun ,prop ,val) + (guard (and (macroexp-const-p fun) + (macroexp-const-p prop) + (or (macroexp-const-p val) + ;; Also accept anonymous functions, since + ;; we're at top-level which implies they're + ;; also constants. + (pcase val (`(function (lambda . ,_)) t)))))) + (byte-compile-push-constant op) + (byte-compile-form fun) + (byte-compile-form prop) + (let* ((fun (eval fun)) + (prop (eval prop)) + (val (if (macroexp-const-p val) + (eval val) + (byte-compile-lambda (cadr val))))) + (push `(,fun + . (,prop ,val ,@(alist-get fun overriding-plist-environment))) + overriding-plist-environment) + (byte-compile-push-constant val) + (byte-compile-out 'byte-call 3) + nil)) + + (_ (byte-compile-keep-pending form)))) ;;; tags diff --git a/lisp/emacs-lisp/cconv.el b/lisp/emacs-lisp/cconv.el index 4507af7a59b..fe92288d548 100644 --- a/lisp/emacs-lisp/cconv.el +++ b/lisp/emacs-lisp/cconv.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emacs-lisp/chart.el b/lisp/emacs-lisp/chart.el index dc108f956c2..2c37923353c 100644 --- a/lisp/emacs-lisp/chart.el +++ b/lisp/emacs-lisp/chart.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/emacs-lisp/check-declare.el b/lisp/emacs-lisp/check-declare.el index c46426cd366..6d503bae2df 100644 --- a/lisp/emacs-lisp/check-declare.el +++ b/lisp/emacs-lisp/check-declare.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emacs-lisp/checkdoc.el b/lisp/emacs-lisp/checkdoc.el index 1d6fdfa4e87..fe6cd4160ed 100644 --- a/lisp/emacs-lisp/checkdoc.el +++ b/lisp/emacs-lisp/checkdoc.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -171,6 +171,7 @@ (defvar checkdoc-version "0.6.1" "Release version of checkdoc you are currently running.") +(eval-when-compile (require 'cl-lib)) (require 'help-mode) ;; for help-xref-info-regexp (require 'thingatpt) ;; for handy thing-at-point-looking-at @@ -258,12 +259,13 @@ Any more than this and a warning is generated suggesting that the construct \\ {keymap} be used instead." :type 'integer) -(defcustom checkdoc-arguments-in-order-flag t +(defcustom checkdoc-arguments-in-order-flag nil "Non-nil means warn if arguments appear out of order. Setting this to nil will mean only checking that all the arguments appear in the proper form in the documentation, not that they are in the same order as they appear in the argument list. No mention is made in the style guide relating to order." + :version "26.1" :type 'boolean) ;;;###autoload(put 'checkdoc-arguments-in-order-flag 'safe-local-variable #'booleanp) @@ -435,23 +437,6 @@ be re-created.") st) "Syntax table used by checkdoc in document strings.") -;;; Compatibility -;; -(defalias 'checkdoc-make-overlay - (if (featurep 'xemacs) #'make-extent #'make-overlay)) -(defalias 'checkdoc-overlay-put - (if (featurep 'xemacs) #'set-extent-property #'overlay-put)) -(defalias 'checkdoc-delete-overlay - (if (featurep 'xemacs) #'delete-extent #'delete-overlay)) -(defalias 'checkdoc-overlay-start - (if (featurep 'xemacs) #'extent-start #'overlay-start)) -(defalias 'checkdoc-overlay-end - (if (featurep 'xemacs) #'extent-end #'overlay-end)) -(defalias 'checkdoc-mode-line-update - (if (featurep 'xemacs) #'redraw-modeline #'force-mode-line-update)) -(defalias 'checkdoc-char= - (if (featurep 'xemacs) #'char= #'=)) - ;;; User level commands ;; ;;;###autoload @@ -474,32 +459,31 @@ the users will view as each check is completed." tmp) (checkdoc-display-status-buffer status) ;; check the comments - (if (not buffer-file-name) - (setcar status "Not checked") - (if (checkdoc-file-comments-engine) - (setcar status "Errors") - (setcar status "Ok"))) - (setcar (cdr status) "Checking...") + (setf (nth 0 status) + (cond + ((not buffer-file-name) "Not checked") + ((checkdoc-file-comments-engine) "Errors") + (t "Ok"))) + (setf (nth 1 status) "Checking...") (checkdoc-display-status-buffer status) ;; Check the documentation (setq tmp (checkdoc-interactive nil t)) - (if tmp - (setcar (cdr status) (format "%d Errors" (length tmp))) - (setcar (cdr status) "Ok")) - (setcar (cdr (cdr status)) "Checking...") + (setf (nth 1 status) + (if tmp (format "%d Errors" (length tmp)) "Ok")) + (setf (nth 2 status) "Checking...") (checkdoc-display-status-buffer status) ;; Check the message text - (if (setq tmp (checkdoc-message-interactive nil t)) - (setcar (cdr (cdr status)) (format "%d Errors" (length tmp))) - (setcar (cdr (cdr status)) "Ok")) - (setcar (cdr (cdr (cdr status))) "Checking...") + (setf (nth 2 status) + (if (setq tmp (checkdoc-message-interactive nil t)) + (format "%d Errors" (length tmp)) + "Ok")) + (setf (nth 3 status) "Checking...") (checkdoc-display-status-buffer status) ;; Rogue spacing - (if (condition-case nil - (checkdoc-rogue-spaces nil t) - (error t)) - (setcar (cdr (cdr (cdr status))) "Errors") - (setcar (cdr (cdr (cdr status))) "Ok")) + (setf (nth 3 status) + (if (ignore-errors (checkdoc-rogue-spaces nil t)) + "Errors" + "Ok")) (checkdoc-display-status-buffer status))) (defun checkdoc-display-status-buffer (check) @@ -591,16 +575,16 @@ style." (while err-list (goto-char (cdr (car err-list))) ;; The cursor should be just in front of the offending doc string - (if (stringp (car (car err-list))) - (setq cdo (save-excursion (checkdoc-make-overlay + (setq cdo (if (stringp (car (car err-list))) + (save-excursion (make-overlay (point) (progn (forward-sexp 1) - (point))))) - (setq cdo (checkdoc-make-overlay + (point)))) + (make-overlay (checkdoc-error-start (car (car err-list))) (checkdoc-error-end (car (car err-list)))))) (unwind-protect (progn - (checkdoc-overlay-put cdo 'face 'highlight) + (overlay-put cdo 'face 'highlight) ;; Make sure the whole doc string is visible if possible. (sit-for 0) (if (and (= (following-char) ?\") @@ -626,10 +610,10 @@ style." (if (not (integerp c)) (setq c ??)) (cond ;; Exit condition - ((checkdoc-char= c ?\C-g) (signal 'quit nil)) + ((eq c ?\C-g) (signal 'quit nil)) ;; Request an auto-fix - ((or (checkdoc-char= c ?y) (checkdoc-char= c ?f)) - (checkdoc-delete-overlay cdo) + ((memq c '(?y ?f)) + (delete-overlay cdo) (setq cdo nil) (goto-char (cdr (car err-list))) ;; `automatic-then-never' tells the autofix function @@ -658,7 +642,7 @@ style." "No Additional style errors. Continuing...") (sit-for 2)))))) ;; Move to the next error (if available) - ((or (checkdoc-char= c ?n) (checkdoc-char= c ?\s)) + ((memq c '(?n ?\s)) (let ((ne (funcall findfunc nil))) (if (not ne) (if showstatus @@ -670,7 +654,7 @@ style." (sit-for 2)) (setq err-list (cons ne err-list))))) ;; Go backwards in the list of errors - ((or (checkdoc-char= c ?p) (checkdoc-char= c ?\C-?)) + ((memq c '(?p ?\C-?)) (if (/= (length err-list) 1) (progn (setq err-list (cdr err-list)) @@ -679,10 +663,10 @@ style." (message "No Previous Errors.") (sit-for 2))) ;; Edit the buffer recursively. - ((checkdoc-char= c ?e) + ((eq c ?e) (checkdoc-recursive-edit (checkdoc-error-text (car (car err-list)))) - (checkdoc-delete-overlay cdo) + (delete-overlay cdo) (setq err-list (cdr err-list)) ;back up the error found. (beginning-of-defun) (let ((ne (funcall findfunc nil))) @@ -694,7 +678,7 @@ style." (sit-for 2)) (setq err-list (cons ne err-list))))) ;; Quit checkdoc - ((checkdoc-char= c ?q) + ((eq c ?q) (setq returnme err-list err-list nil begin (point))) @@ -722,7 +706,7 @@ style." "C-h - Toggle this help buffer."))) (shrink-window-if-larger-than-buffer (get-buffer-window "*Checkdoc Help*")))))) - (if cdo (checkdoc-delete-overlay cdo))))) + (if cdo (delete-overlay cdo))))) (goto-char begin) (if (get-buffer "*Checkdoc Help*") (kill-buffer "*Checkdoc Help*")) (message "Checkdoc: Done.") @@ -1146,38 +1130,40 @@ Prefix argument is the same as for `checkdoc-defun'" ;; features and behaviors, so we need some ways of specifying ;; them, and making them easier to use in the wacked-out interfaces ;; people are requesting -(defun checkdoc-create-error (text start end &optional unfixable) - "Used to create the return error text returned from all engines. + +(cl-defstruct (checkdoc-error + (:constructor nil) + (:constructor checkdoc--create-error (text start end &optional unfixable))) + (text nil :read-only t) + (start nil :read-only t) + (end nil :read-only t) + (unfixable nil :read-only t)) + +(defvar checkdoc-create-error-function #'checkdoc--create-error-for-checkdoc + "Function called when Checkdoc encounters an error. +Should accept as arguments (TEXT START END &optional UNFIXABLE). + TEXT is the descriptive text of the error. START and END define the region it is sensible to highlight when describing the problem. Optional argument UNFIXABLE means that the error has no auto-fix available. -A list of the form (TEXT START END UNFIXABLE) is returned if we are not -generating a buffered list of errors." +An object of type `checkdoc-error' is returned if we are not +generating a buffered list of errors.") + +(defun checkdoc-create-error (text start end &optional unfixable) + "Used to create the return error text returned from all engines. +TEXT, START, END and UNFIXABLE conform to +`checkdoc-create-error-function', which see." + (funcall checkdoc-create-error-function text start end unfixable)) + +(defun checkdoc--create-error-for-checkdoc (text start end &optional unfixable) + "Create an error for Checkdoc. +TEXT, START, END and UNFIXABLE conform to +`checkdoc-create-error-function', which see." (if checkdoc-generate-compile-warnings-flag (progn (checkdoc-error start text) nil) - (list text start end unfixable))) - -(defun checkdoc-error-text (err) - "Return the text specified in the checkdoc ERR." - ;; string-p part is for backwards compatibility - (if (stringp err) err (car err))) - -(defun checkdoc-error-start (err) - "Return the start point specified in the checkdoc ERR." - ;; string-p part is for backwards compatibility - (if (stringp err) nil (nth 1 err))) - -(defun checkdoc-error-end (err) - "Return the end point specified in the checkdoc ERR." - ;; string-p part is for backwards compatibility - (if (stringp err) nil (nth 2 err))) - -(defun checkdoc-error-unfixable (err) - "Return the t if we cannot autofix the error specified in the checkdoc ERR." - ;; string-p part is for backwards compatibility - (if (stringp err) nil (nth 3 err))) + (checkdoc--create-error text start end unfixable))) ;;; Minor Mode specification ;; @@ -1328,7 +1314,7 @@ See the style guide in the Emacs Lisp manual for more details." (if (and (not (nth 1 fp)) ; not a variable (or (nth 2 fp) ; is interactive checkdoc-force-docstrings-flag) ;or we always complain - (not (checkdoc-char= (following-char) ?\"))) ; no doc string + (not (eq (following-char) ?\"))) ; no doc string ;; Sometimes old code has comments where the documentation should ;; be. Let's see if we can find the comment, and offer to turn it ;; into documentation for them. @@ -1457,9 +1443,9 @@ regexp short cuts work. FP is the function defun information." (if (> (point) e) (goto-char e)) ;of the form (defun n () "doc" nil) (forward-char -1) (cond - ((and (checkdoc-char= (following-char) ?\") + ((and (eq (following-char) ?\") ;; A backslashed double quote at the end of a sentence - (not (checkdoc-char= (preceding-char) ?\\))) + (not (eq (preceding-char) ?\\))) ;; We might have to add a period in this case (forward-char -1) (if (looking-at "[.!?]") @@ -1782,7 +1768,7 @@ function,command,variable,option or symbol." ms1)))))) (let ((lim (save-excursion (end-of-line) ;; check string-continuation - (if (checkdoc-char= (preceding-char) ?\\) + (if (eq (preceding-char) ?\\) (line-end-position 2) (point)))) (rs nil) replace original (case-fold-search t)) @@ -2579,12 +2565,12 @@ This function returns non-nil if the text was replaced. This function will not modify `match-data'." (if (and checkdoc-autofix-flag (not (eq checkdoc-autofix-flag 'never))) - (let ((o (checkdoc-make-overlay start end)) + (let ((o (make-overlay start end)) (ret nil) (md (match-data))) (unwind-protect (progn - (checkdoc-overlay-put o 'face 'highlight) + (overlay-put o 'face 'highlight) (if (or (eq checkdoc-autofix-flag 'automatic) (eq checkdoc-autofix-flag 'automatic-then-never) (and (eq checkdoc-autofix-flag 'semiautomatic) @@ -2601,9 +2587,9 @@ This function will not modify `match-data'." (insert replacewith) (if checkdoc-bouncy-flag (sit-for 0)) (setq ret t))) - (checkdoc-delete-overlay o) + (delete-overlay o) (set-match-data md)) - (checkdoc-delete-overlay o) + (delete-overlay o) (set-match-data md)) (if (eq checkdoc-autofix-flag 'automatic-then-never) (setq checkdoc-autofix-flag 'never)) diff --git a/lisp/emacs-lisp/cl-extra.el b/lisp/emacs-lisp/cl-extra.el index 3852ceb6c31..214adbc5817 100644 --- a/lisp/emacs-lisp/cl-extra.el +++ b/lisp/emacs-lisp/cl-extra.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -437,22 +437,38 @@ as an integer unless JUNK-ALLOWED is non-nil." ;; Random numbers. +(defun cl--random-time () + (let* ((time (copy-sequence (current-time-string))) (i (length time)) (v 0)) + (while (>= (cl-decf i) 0) (setq v (+ (* v 3) (aref time i)))) + v)) + +;;;###autoload (autoload 'cl-random-state-p "cl-extra") +(cl-defstruct (cl--random-state + (:copier nil) + (:predicate cl-random-state-p) + (:constructor nil) + (:constructor cl--make-random-state (vec))) + (i -1) (j 30) vec) + +(defvar cl--random-state (cl--make-random-state (cl--random-time))) + ;;;###autoload (defun cl-random (lim &optional state) "Return a random nonnegative number less than LIM, an integer or float. Optional second arg STATE is a random-state object." (or state (setq state cl--random-state)) ;; Inspired by "ran3" from Numerical Recipes. Additive congruential method. - (let ((vec (aref state 3))) + (let ((vec (cl--random-state-vec state))) (if (integerp vec) (let ((i 0) (j (- 1357335 (abs (% vec 1357333)))) (k 1)) - (aset state 3 (setq vec (make-vector 55 nil))) + (setf (cl--random-state-vec state) + (setq vec (make-vector 55 nil))) (aset vec 0 j) (while (> (setq i (% (+ i 21) 55)) 0) (aset vec i (setq j (prog1 k (setq k (- j k)))))) (while (< (setq i (1+ i)) 200) (cl-random 2 state)))) - (let* ((i (aset state 1 (% (1+ (aref state 1)) 55))) - (j (aset state 2 (% (1+ (aref state 2)) 55))) + (let* ((i (cl-callf (lambda (x) (% (1+ x) 55)) (cl--random-state-i state))) + (j (cl-callf (lambda (x) (% (1+ x) 55)) (cl--random-state-j state))) (n (logand 8388607 (aset vec i (- (aref vec i) (aref vec j)))))) (if (integerp lim) (if (<= lim 512) (% n lim) @@ -466,17 +482,10 @@ Optional second arg STATE is a random-state object." (defun cl-make-random-state (&optional state) "Return a copy of random-state STATE, or of the internal state if omitted. If STATE is t, return a new state object seeded from the time of day." - (cond ((null state) (cl-make-random-state cl--random-state)) - ((vectorp state) (copy-tree state t)) - ((integerp state) (vector 'cl--random-state-tag -1 30 state)) - (t (cl-make-random-state (cl--random-time))))) - -;;;###autoload -(defun cl-random-state-p (object) - "Return t if OBJECT is a random-state object." - (and (vectorp object) (= (length object) 4) - (eq (aref object 0) 'cl--random-state-tag))) - + (unless state (setq state cl--random-state)) + (if (cl-random-state-p state) + (copy-tree state t) + (cl--make-random-state (if (integerp state) state (cl--random-time))))) ;; Implementation limits. diff --git a/lisp/emacs-lisp/cl-generic.el b/lisp/emacs-lisp/cl-generic.el index c64376b940f..00278996792 100644 --- a/lisp/emacs-lisp/cl-generic.el +++ b/lisp/emacs-lisp/cl-generic.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -166,6 +166,10 @@ SPECIALIZERS-FUNCTION takes as first argument a tag value TAG (defmacro cl--generic (name) `(get ,name 'cl--generic)) +(defun cl-generic-p (f) + "Return non-nil if F is a generic function." + (and (symbolp f) (cl--generic f))) + (defun cl-generic-ensure-function (name &optional noerror) (let (generic (origname name)) @@ -182,8 +186,7 @@ SPECIALIZERS-FUNCTION takes as first argument a tag value TAG origname)) (if generic (cl-assert (eq name (cl--generic-name generic))) - (setf (cl--generic name) (setq generic (cl--generic-make name))) - (defalias name (cl--generic-make-function generic))) + (setf (cl--generic name) (setq generic (cl--generic-make name)))) generic)) ;;;###autoload @@ -201,7 +204,17 @@ OPTIONS-AND-METHODS currently understands: DEFAULT-BODY, if present, is used as the body of a default method. \(fn NAME ARGS [DOC-STRING] [OPTIONS-AND-METHODS...] &rest DEFAULT-BODY)" - (declare (indent 2) (doc-string 3)) + (declare (indent 2) (doc-string 3) + (debug + (&define [&or name ("setf" name :name setf)] listp + lambda-doc + [&rest [&or + ("declare" &rest sexp) + (":argument-precedence-order" &rest sexp) + (&define ":method" [&rest atom] + cl-generic-method-args lambda-doc + def-body)]] + def-body))) (let* ((doc (if (stringp (car-safe options-and-methods)) (pop options-and-methods))) (declarations nil) @@ -410,7 +423,7 @@ The set of acceptable TYPEs (also called \"specializers\") is defined \(and can be extended) by the various methods of `cl-generic-generalizers'. \(fn NAME [QUALIFIER] ARGS &rest [DOCSTRING] BODY)" - (declare (doc-string 3) (indent 2) + (declare (doc-string 3) (indent defun) (debug (&define ; this means we are defining something [&or name ("setf" name :name setf)] @@ -419,7 +432,7 @@ The set of acceptable TYPEs (also called \"specializers\") is defined ; Like in CLOS spec, we support ; any non-list values. cl-generic-method-args ; arguments - [ &optional stringp ] ; documentation string + lambda-doc ; documentation string def-body))) ; part to be debugged (let ((qualifiers nil)) (while (not (listp args)) @@ -501,25 +514,26 @@ The set of acceptable TYPEs (also called \"specializers\") is defined (cons method mt) ;; Keep the ordering; important for methods with :extra qualifiers. (mapcar (lambda (x) (if (eq x (car me)) method x)) mt))) - (cl-pushnew `(cl-defmethod . ,(cl--generic-load-hist-format - (cl--generic-name generic) - qualifiers specializers)) - current-load-list :test #'equal) - ;; FIXME: Try to avoid re-constructing a new function if the old one - ;; is still valid (e.g. still empty method cache)? - (let ((gfun (cl--generic-make-function generic)) - ;; Prevent `defalias' from recording this as the definition site of - ;; the generic function. - current-load-list) - ;; For aliases, cl--generic-name gives us the actual name. - (let ((purify-flag - ;; BEWARE! Don't purify this function definition, since that leads - ;; to memory corruption if the hash-tables it holds are modified - ;; (the GC doesn't trace those pointers). - nil)) + (let ((sym (cl--generic-name generic))) ; Actual name (for aliases). + (unless (symbol-function sym) + (defalias sym 'dummy)) ;Record definition into load-history. + (cl-pushnew `(cl-defmethod . ,(cl--generic-load-hist-format + (cl--generic-name generic) + qualifiers specializers)) + current-load-list :test #'equal) + ;; FIXME: Try to avoid re-constructing a new function if the old one + ;; is still valid (e.g. still empty method cache)? + (let ((gfun (cl--generic-make-function generic)) + ;; Prevent `defalias' from recording this as the definition site of + ;; the generic function. + current-load-list + ;; BEWARE! Don't purify this function definition, since that leads + ;; to memory corruption if the hash-tables it holds are modified + ;; (the GC doesn't trace those pointers). + (purify-flag nil)) ;; But do use `defalias', so that it interacts properly with nadvice, ;; e.g. for tracing/debug-on-entry. - (defalias (cl--generic-name generic) gfun))))) + (defalias sym gfun))))) (defmacro cl--generic-with-memoization (place &rest code) (declare (indent 1) (debug t)) @@ -1023,6 +1037,20 @@ The value returned is a list of elements of the form (push (cl--generic-method-info method) docs)))) docs)) +(defun cl--generic-method-files (method) + "Return a list of files where METHOD is defined by `cl-defmethod'. +The list will have entries of the form (FILE . (METHOD ...)) +where (METHOD ...) contains the qualifiers and specializers of +the method and is a suitable argument for +`find-function-search-for-symbol'. Filenames are absolute." + (let (result) + (pcase-dolist (`(,file . ,defs) load-history) + (dolist (def defs) + (when (and (eq (car-safe def) 'cl-defmethod) + (eq (cadr def) method)) + (push (cons file (cdr def)) result)))) + result)) + ;;; Support for (head <val>) specializers. ;; For both the `eql' and the `head' specializers, the dispatch @@ -1210,5 +1238,18 @@ Used internally for the (major-mode MODE) context specializers." (progn (cl-assert (null modes)) mode) `(derived-mode ,mode . ,modes)))) +;;; Support for unloading. + +(cl-defmethod loadhist-unload-element ((x (head cl-defmethod))) + (pcase-let* + ((`(,name ,qualifiers . ,specializers) (cdr x)) + (generic (cl-generic-ensure-function name 'noerror))) + (when generic + (let* ((mt (cl--generic-method-table generic)) + (me (cl--generic-member-method specializers qualifiers mt))) + (when me + (setf (cl--generic-method-table generic) (delq (car me) mt))))))) + + (provide 'cl-generic) ;;; cl-generic.el ends here diff --git a/lisp/emacs-lisp/cl-indent.el b/lisp/emacs-lisp/cl-indent.el index df0e0a88583..17e2434f589 100644 --- a/lisp/emacs-lisp/cl-indent.el +++ b/lisp/emacs-lisp/cl-indent.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emacs-lisp/cl-lib.el b/lisp/emacs-lisp/cl-lib.el index 936c852526c..da7176f662d 100644 --- a/lisp/emacs-lisp/cl-lib.el +++ b/lisp/emacs-lisp/cl-lib.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -288,14 +288,6 @@ If true return the decimal value of digit CHAR in RADIX." (let ((n (aref cl-digit-char-table char))) (and n (< n (or radix 10)) n))) -(defun cl--random-time () - (let* ((time (copy-sequence (current-time-string))) (i (length time)) (v 0)) - (while (>= (cl-decf i) 0) (setq v (+ (* v 3) (aref time i)))) - v)) - -(defvar cl--random-state - (vector 'cl--random-state-tag -1 30 (cl--random-time))) - (defconst cl-most-positive-float nil "The largest value that a Lisp float can hold. If your system supports infinities, this is the largest finite value. @@ -639,7 +631,7 @@ If ALIST is non-nil, the new pairs are prepended to it." (require 'cl-seq)) (defun cl--old-struct-type-of (orig-fun object) - (or (and (vectorp object) + (or (and (vectorp object) (> (length object) 0) (let ((tag (aref object 0))) (when (and (symbolp tag) (string-prefix-p "cl-struct-" (symbol-name tag))) diff --git a/lisp/emacs-lisp/cl-macs.el b/lisp/emacs-lisp/cl-macs.el index b1ada00f4a4..f5311041cce 100644 --- a/lisp/emacs-lisp/cl-macs.el +++ b/lisp/emacs-lisp/cl-macs.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -172,14 +172,15 @@ The name is made by appending a number to PREFIX, default \"G\"." (setq cl--gensym-counter (1+ cl--gensym-counter)))))) (make-symbol (format "%s%d" pfix num)))) +(defvar cl--gentemp-counter 0) ;;;###autoload (defun cl-gentemp (&optional prefix) "Generate a new interned symbol with a unique name. -The name is made by appending a number to PREFIX, default \"G\"." - (let ((pfix (if (stringp prefix) prefix "G")) +The name is made by appending a number to PREFIX, default \"T\"." + (let ((pfix (if (stringp prefix) prefix "T")) name) - (while (intern-soft (setq name (format "%s%d" pfix cl--gensym-counter))) - (setq cl--gensym-counter (1+ cl--gensym-counter))) + (while (intern-soft (setq name (format "%s%d" pfix cl--gentemp-counter))) + (setq cl--gentemp-counter (1+ cl--gentemp-counter))) (intern name))) @@ -189,23 +190,37 @@ The name is made by appending a number to PREFIX, default \"G\"." (&rest ("cl-declare" &rest sexp))) (def-edebug-spec cl-declarations-or-string - (&or stringp cl-declarations)) + (&or lambda-doc cl-declarations)) (def-edebug-spec cl-lambda-list - (([&rest arg] + (([&rest cl-lambda-arg] [&optional ["&optional" cl-&optional-arg &rest cl-&optional-arg]] - [&optional ["&rest" arg]] + [&optional ["&rest" cl-lambda-arg]] [&optional ["&key" [cl-&key-arg &rest cl-&key-arg] &optional "&allow-other-keys"]] [&optional ["&aux" &rest &or (symbolp &optional def-form) symbolp]] - ))) + . [&or arg nil]))) (def-edebug-spec cl-&optional-arg - (&or (arg &optional def-form arg) arg)) + (&or (cl-lambda-arg &optional def-form arg) arg)) (def-edebug-spec cl-&key-arg - (&or ([&or (symbolp arg) arg] &optional def-form arg) arg)) + (&or ([&or (symbolp cl-lambda-arg) arg] &optional def-form arg) arg)) + +(def-edebug-spec cl-lambda-arg + (&or arg cl-lambda-list1)) + +(def-edebug-spec cl-lambda-list1 + (([&optional ["&whole" arg]] ;; only allowed at lower levels + [&rest cl-lambda-arg] + [&optional ["&optional" cl-&optional-arg &rest cl-&optional-arg]] + [&optional ["&rest" cl-lambda-arg]] + [&optional ["&key" cl-&key-arg &rest cl-&key-arg + &optional "&allow-other-keys"]] + [&optional ["&aux" &rest + &or (symbolp &optional def-form) symbolp]] + . [&or arg nil]))) (def-edebug-spec cl-type-spec sexp) @@ -335,8 +350,8 @@ The full form of a Common Lisp function argument list is [&key (([KEYWORD] VAR) [INITFORM [SVAR]])... [&allow-other-keys]] [&aux (VAR [INITFORM])...]) -VAR maybe be replaced recursively with an argument list for -destructing, `&whole' is supported within these sublists. If +VAR may be replaced recursively with an argument list for +destructuring, `&whole' is supported within these sublists. If SVAR, INITFORM, and KEYWORD are all omitted, then `(VAR)' may be written simply `VAR'. See the Info node `(cl)Argument Lists' for more details. @@ -429,8 +444,8 @@ The full form of a Common Lisp macro argument list is [&aux (VAR [INITFORM])...] [&environment VAR]) -VAR maybe be replaced recursively with an argument list for -destructing, `&whole' is supported within these sublists. If +VAR may be replaced recursively with an argument list for +destructuring, `&whole' is supported within these sublists. If SVAR, INITFORM, and KEYWORD are all omitted, then `(VAR)' may be written simply `VAR'. See the Info node `(cl)Argument Lists' for more details. @@ -446,8 +461,8 @@ more details. (def-edebug-spec cl-lambda-expr (&define ("lambda" cl-lambda-list - ;;cl-declarations-or-string - ;;[&optional ("interactive" interactive)] + cl-declarations-or-string + [&optional ("interactive" interactive)] def-body))) ;; Redefine function-form to also match cl-function @@ -540,7 +555,7 @@ its argument list allows full Common Lisp conventions." (if (memq '&environment args) (error "&environment used incorrectly")) (let ((restarg (memq '&rest args)) (safety (if (cl--compiling-file) cl--optimize-safety 3)) - (keys nil) + (keys t) (laterarg nil) (exactarg nil) minarg) (or num (setq num 0)) (setq restarg (if (listp (cadr restarg)) @@ -595,6 +610,7 @@ its argument list allows full Common Lisp conventions." (+ ,num (length ,restarg))))) cl--bind-forms))) (while (and (eq (car args) '&key) (pop args)) + (unless (listp keys) (setq keys nil)) (while (and args (not (memq (car args) cl--lambda-list-keywords))) (let ((arg (pop args))) (or (consp arg) (setq arg (list arg))) @@ -633,23 +649,32 @@ its argument list allows full Common Lisp conventions." `'(nil ,(cl--const-expr-val def)) `(list nil ,def)))))))) (push karg keys))))) - (setq keys (nreverse keys)) + (when (consp keys) (setq keys (nreverse keys))) (or (and (eq (car args) '&allow-other-keys) (pop args)) - (null keys) (= safety 0) - (let* ((var (make-symbol "--cl-keys--")) - (allow '(:allow-other-keys)) - (check `(while ,var - (cond - ((memq (car ,var) ',(append keys allow)) - (setq ,var (cdr (cdr ,var)))) - ((car (cdr (memq (quote ,@allow) ,restarg))) - (setq ,var nil)) - (t - (error - ,(format "Keyword argument %%s not one of %s" - keys) - (car ,var))))))) - (push `(let ((,var ,restarg)) ,check) cl--bind-forms))) + (= safety 0) + (cond + ((eq keys t) nil) ;No &keys at all + ((null keys) ;A &key but no actual keys specified. + (push `(when ,restarg + (error ,(format "Keyword argument %%s not one of %s" + keys) + (car ,restarg))) + cl--bind-forms)) + (t + (let* ((var (make-symbol "--cl-keys--")) + (allow '(:allow-other-keys)) + (check `(while ,var + (cond + ((memq (car ,var) ',(append keys allow)) + (setq ,var (cdr (cdr ,var)))) + ((car (cdr (memq (quote ,@allow) ,restarg))) + (setq ,var nil)) + (t + (error + ,(format "Keyword argument %%s not one of %s" + keys) + (car ,var))))))) + (push `(let ((,var ,restarg)) ,check) cl--bind-forms))))) (cl--do-&aux args) nil))) @@ -669,7 +694,7 @@ its argument list allows full Common Lisp conventions." (defmacro cl-destructuring-bind (args expr &rest body) "Bind the variables in ARGS to the result of EXPR and execute BODY." (declare (indent 2) - (debug (&define cl-macro-list def-form cl-declarations def-body))) + (debug (&define cl-macro-list1 def-form cl-declarations def-body))) (let* ((cl--bind-lets nil) (cl--bind-forms nil) (cl--bind-defs nil) (cl--bind-block 'cl-none) (cl--bind-enquote nil)) (cl--do-arglist (or args '(&aux)) expr) @@ -2073,60 +2098,65 @@ except that it additionally expands symbol macros." (setq exp (cons 'setq args)) ;; Don't loop further. nil))) - (`(,(or `let `let*) . ,(or `(,bindings . ,body) dontcare)) - ;; CL's symbol-macrolet treats re-bindings as candidates for - ;; expansion (turning the let into a letf if needed), contrary to - ;; Common-Lisp where such re-bindings hide the symbol-macro. - (let ((letf nil) (found nil) (nbs ())) - (dolist (binding bindings) - (let* ((var (if (symbolp binding) binding (car binding))) - (sm (assq var venv))) - (push (if (not (cdr sm)) - binding - (let ((nexp (cadr sm))) - (setq found t) - (unless (symbolp nexp) (setq letf t)) - (cons nexp (cdr-safe binding)))) - nbs))) - (when found - (setq exp `(,(if letf - (if (eq (car exp) 'let) 'cl-letf 'cl-letf*) - (car exp)) - ,(nreverse nbs) - ,@body))))) - ;; FIXME: The behavior of CL made sense in a dynamically scoped - ;; language, but for lexical scoping, Common-Lisp's behavior might - ;; make more sense (and indeed, CL behaves like Common-Lisp w.r.t - ;; lexical-let), so maybe we should adjust the behavior based on - ;; the use of lexical-binding. + ;; CL's symbol-macrolet used to treat re-bindings as candidates for + ;; expansion (turning the let into a letf if needed), contrary to + ;; Common-Lisp where such re-bindings hide the symbol-macro. + ;; Not sure if there actually is code out there which depends + ;; on this behavior (haven't found any yet). + ;; Such code should explicitly use `cl-letf' instead, I think. + ;; ;; (`(,(or `let `let*) . ,(or `(,bindings . ,body) dontcare)) - ;; (let ((nbs ()) (found nil)) + ;; (let ((letf nil) (found nil) (nbs ())) ;; (dolist (binding bindings) ;; (let* ((var (if (symbolp binding) binding (car binding))) - ;; (name (symbol-name var)) - ;; (val (and found (consp binding) (eq 'let* (car exp)) - ;; (list (macroexpand-all (cadr binding) - ;; env))))) - ;; (push (if (assq name env) - ;; ;; This binding should hide its symbol-macro, - ;; ;; but given the way macroexpand-all works, we - ;; ;; can't prevent application of `env' to the - ;; ;; sub-expressions, so we need to α-rename this - ;; ;; variable instead. - ;; (let ((nvar (make-symbol - ;; (copy-sequence name)))) - ;; (setq found t) - ;; (push (list name nvar) env) - ;; (cons nvar (or val (cdr-safe binding)))) - ;; (if val (cons var val) binding)) + ;; (sm (assq var venv))) + ;; (push (if (not (cdr sm)) + ;; binding + ;; (let ((nexp (cadr sm))) + ;; (setq found t) + ;; (unless (symbolp nexp) (setq letf t)) + ;; (cons nexp (cdr-safe binding)))) ;; nbs))) ;; (when found - ;; (setq exp `(,(car exp) + ;; (setq exp `(,(if letf + ;; (if (eq (car exp) 'let) 'cl-letf 'cl-letf*) + ;; (car exp)) ;; ,(nreverse nbs) - ;; ,@(macroexp-unprogn - ;; (macroexpand-all (macroexp-progn body) - ;; env))))) - ;; nil)) + ;; ,@body))))) + ;; + ;; We implement the Common-Lisp behavior, instead (see bug#26073): + ;; The behavior of CL made sense in a dynamically scoped + ;; language, but nowadays, lexical scoping semantics is more often + ;; expected. + (`(,(or `let `let*) . ,(or `(,bindings . ,body) dontcare)) + (let ((nbs ()) (found nil)) + (dolist (binding bindings) + (let* ((var (if (symbolp binding) binding (car binding))) + (val (and found (consp binding) (eq 'let* (car exp)) + (list (macroexpand-all (cadr binding) + env))))) + (push (if (assq var venv) + ;; This binding should hide its symbol-macro, + ;; but given the way macroexpand-all works + ;; (i.e. the `env' we receive as input will be + ;; (re)applied to the code we return), we can't + ;; prevent application of `env' to the + ;; sub-expressions, so we need to α-rename this + ;; variable instead. + (let ((nvar (make-symbol (symbol-name var)))) + (setq found t) + (push (list var nvar) venv) + (push (cons :cl-symbol-macros venv) env) + (cons nvar (or val (cdr-safe binding)))) + (if val (cons var val) binding)) + nbs))) + (when found + (setq exp `(,(car exp) + ,(nreverse nbs) + ,@(macroexp-unprogn + (macroexpand-all (macroexp-progn body) + env))))) + nil)) ))) exp)) @@ -2410,10 +2440,11 @@ Each PLACE may be a symbol, or any generalized variable allowed by `setf'. (pcase-let ((`(,vold ,_getter ,setter ,_vnew) x)) (funcall setter vold))) binds)))) - (let ((binding (car bindings))) - (gv-letplace (getter setter) (car binding) + (let* ((binding (car bindings)) + (place (macroexpand (car binding) macroexpand-all-environment))) + (gv-letplace (getter setter) place (macroexp-let2 nil vnew (cadr binding) - (if (symbolp (car binding)) + (if (symbolp place) ;; Special-case for simple variables. (cl--letf (cdr bindings) (cons `(,getter ,(if (cdr binding) vnew getter)) @@ -2437,8 +2468,12 @@ As a special case, if `(PLACE)' is used instead of `(PLACE VALUE)', the PLACE is not modified before executing BODY. \(fn ((PLACE VALUE) ...) BODY...)" - (declare (indent 1) (debug ((&rest (gate gv-place &optional form)) body))) - (if (and (not (cdr bindings)) (cdar bindings) (symbolp (caar bindings))) + (declare (indent 1) (debug ((&rest [&or (symbolp form) + (gate gv-place &optional form)]) + body))) + (if (and (not (cdr bindings)) (cdar bindings) (symbolp (caar bindings)) + (not (assq (caar bindings) + (alist-get :cl-symbol-macros macroexpand-all-environment)))) `(let ,bindings ,@body) (cl--letf bindings () () body))) @@ -2503,8 +2538,9 @@ The function's arguments should be treated as immutable. ,(if (memq '&key args) `(&whole cl-whole &cl-quote ,@args) (cons '&cl-quote args)) + ,(format "compiler-macro for inlining `%s'." name) (cl--defsubst-expand - ',argns '(cl-block ,name ,@body) + ',argns '(cl-block ,name ,@(cdr (macroexp-parse-body body))) ;; We used to pass `simple' as ;; (not (or unsafe (cl-expr-access-order pbody argns))) ;; But this is much too simplistic since it diff --git a/lisp/emacs-lisp/cl-preloaded.el b/lisp/emacs-lisp/cl-preloaded.el index ab6354de7cd..e550f5a095f 100644 --- a/lisp/emacs-lisp/cl-preloaded.el +++ b/lisp/emacs-lisp/cl-preloaded.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emacs-lisp/cl-print.el b/lisp/emacs-lisp/cl-print.el index 89a71d1b6c5..4fc178c29aa 100644 --- a/lisp/emacs-lisp/cl-print.el +++ b/lisp/emacs-lisp/cl-print.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -78,6 +78,16 @@ call other entry points instead, such as `cl-prin1'." (cl-print-object (aref object i) stream)) (princ "]" stream)) +(cl-defmethod cl-print-object ((object hash-table) stream) + (princ "#<hash-table " stream) + (princ (hash-table-test object) stream) + (princ " " stream) + (princ (hash-table-count object) stream) + (princ "/" stream) + (princ (hash-table-size object) stream) + (princ (format " 0x%x" (sxhash object)) stream) + (princ ">" stream)) + (define-button-type 'help-byte-code 'follow-link t 'action (lambda (button) @@ -85,12 +95,13 @@ call other entry points instead, such as `cl-prin1'." 'help-echo (purecopy "mouse-2, RET: disassemble this function")) (defvar cl-print-compiled nil - "Control how to print byte-compiled functions. Can be: + "Control how to print byte-compiled functions. +Acceptable values include: - `static' to print the vector of constants. - `disassemble' to print the disassembly of the code. - nil to skip printing any details about the code.") -(defvar cl-print-compiled-button nil +(defvar cl-print-compiled-button t "Control how to print byte-compiled functions into buffers. When the stream is a buffer, make the bytecode part of the output into a button whose action shows the function's disassembly.") @@ -105,10 +116,11 @@ into a button whose action shows the function's disassembly.") (if args (prin1 args stream) (princ "()" stream))) - (let ((doc (documentation object 'raw))) - (when doc - (princ " " stream) - (prin1 doc stream))) + (pcase (help-split-fundoc (documentation object 'raw) object) + ;; Drop args which `help-function-arglist' already printed. + (`(,_usage . ,(and doc (guard (stringp doc)))) + (princ " " stream) + (prin1 doc stream))) (let ((inter (interactive-form object))) (when inter (princ " " stream) @@ -130,7 +142,7 @@ into a button whose action shows the function's disassembly.") (let ((button-start (and cl-print-compiled-button (bufferp stream) (with-current-buffer stream (point))))) - (princ "#<bytecode>" stream) + (princ (format "#<bytecode 0x%x>" (sxhash object)) stream) (when (eq cl-print-compiled 'static) (princ " " stream) (cl-print-object (aref object 2) stream)) @@ -252,6 +264,11 @@ into a button whose action shows the function's disassembly.") ;;;###autoload (defun cl-prin1 (object &optional stream) + "Print OBJECT on STREAM according to its type. +Output is further controlled by the variables +`cl-print-readably', `cl-print-compiled', along with output +variables for the standard printing functions. See Info +node `(elisp)Output Variables'." (cond (cl-print-readably (prin1 object stream)) ((not print-circle) (cl-print-object object stream)) @@ -261,6 +278,7 @@ into a button whose action shows the function's disassembly.") ;;;###autoload (defun cl-prin1-to-string (object) + "Return a string containing the `cl-prin1'-printed representation of OBJECT." (with-temp-buffer (cl-prin1 object (current-buffer)) (buffer-string))) diff --git a/lisp/emacs-lisp/cl-seq.el b/lisp/emacs-lisp/cl-seq.el index 67ff1a00bd3..6a21936ebcf 100644 --- a/lisp/emacs-lisp/cl-seq.el +++ b/lisp/emacs-lisp/cl-seq.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emacs-lisp/cl.el b/lisp/emacs-lisp/cl.el index 73eb9a4e866..5ac40234f0f 100644 --- a/lisp/emacs-lisp/cl.el +++ b/lisp/emacs-lisp/cl.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -250,7 +250,6 @@ eval-when destructuring-bind gentemp - gensym pairlis acons subst diff --git a/lisp/emacs-lisp/copyright.el b/lisp/emacs-lisp/copyright.el index b6936131fc7..25dc77c7258 100644 --- a/lisp/emacs-lisp/copyright.el +++ b/lisp/emacs-lisp/copyright.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -186,9 +186,10 @@ skips to the end of all the years." (substring copyright-current-year -2)) (if (or noquery (save-window-excursion - (switch-to-buffer (current-buffer)) - ;; Fixes some point-moving oddness (bug#2209). + ;; switch-to-buffer might move point when + ;; switch-to-buffer-preserve-window-point is non-nil. (save-excursion + (switch-to-buffer (current-buffer)) (y-or-n-p (if replace (concat "Replace copyright year(s) by " copyright-current-year "? ") diff --git a/lisp/emacs-lisp/crm.el b/lisp/emacs-lisp/crm.el index 0fad27cafe9..2a417f1758b 100644 --- a/lisp/emacs-lisp/crm.el +++ b/lisp/emacs-lisp/crm.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emacs-lisp/cursor-sensor.el b/lisp/emacs-lisp/cursor-sensor.el index e68b429258d..b6e25b9684f 100644 --- a/lisp/emacs-lisp/cursor-sensor.el +++ b/lisp/emacs-lisp/cursor-sensor.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emacs-lisp/debug.el b/lisp/emacs-lisp/debug.el index 83456fc31a2..1ebbc0e0086 100644 --- a/lisp/emacs-lisp/debug.el +++ b/lisp/emacs-lisp/debug.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -49,6 +49,13 @@ the middle is discarded, and just the beginning and end are displayed." :group 'debugger :version "21.1") +(defcustom debugger-print-function #'cl-prin1 + "Function used to print values in the debugger backtraces." + :type '(choice (const cl-prin1) + (const prin1) + function) + :version "26.1") + (defcustom debugger-bury-or-kill 'bury "What to do with the debugger buffer when exiting `debug'. The value affects the behavior of operations on any window @@ -247,7 +254,9 @@ first will be printed into the backtrace buffer." ;; Unshow debugger-buffer. (quit-restore-window debugger-window debugger-bury-or-kill) ;; Restore current buffer (Bug#12502). - (set-buffer debugger-old-buffer)))) + (set-buffer debugger-old-buffer))) + ;; Forget debugger window, it won't be back (Bug#17882). + (setq debugger-previous-window nil)) ;; Restore previous state of debugger-buffer in case we were ;; in a recursive invocation of the debugger, otherwise just ;; erase the buffer and put it into fundamental mode. @@ -264,6 +273,46 @@ first will be printed into the backtrace buffer." (setq debug-on-next-call debugger-step-after-exit) debugger-value))) +(defun debugger--print (obj &optional stream) + (condition-case err + (funcall debugger-print-function obj stream) + (error + (message "Error in debug printer: %S" err) + (prin1 obj stream)))) + +(defun debugger-insert-backtrace (frames do-xrefs) + "Format and insert the backtrace FRAMES at point. +Make functions into cross-reference buttons if DO-XREFS is non-nil." + (let ((standard-output (current-buffer)) + (eval-buffers eval-buffer-list)) + (require 'help-mode) ; Define `help-function-def' button type. + (pcase-dolist (`(,evald ,fun ,args ,flags) frames) + (insert (if (plist-get flags :debug-on-exit) + "* " " ")) + (let ((fun-file (and do-xrefs (symbol-file fun 'defun))) + (fun-pt (point))) + (cond + ((and evald (not debugger-stack-frame-as-list)) + (debugger--print fun) + (if args (debugger--print args) (princ "()"))) + (t + (debugger--print (cons fun args)) + (cl-incf fun-pt))) + (when fun-file + (make-text-button fun-pt (+ fun-pt (length (symbol-name fun))) + :type 'help-function-def + 'help-args (list fun fun-file)))) + ;; After any frame that uses eval-buffer, insert a line that + ;; states the buffer position it's reading at. + (when (and eval-buffers (memq fun '(eval-buffer eval-region))) + (insert (format " ; Reading at buffer position %d" + ;; This will get the wrong result if there are + ;; two nested eval-region calls for the same + ;; buffer. That's not a very useful case. + (with-current-buffer (pop eval-buffers) + (point))))) + (insert "\n")))) + (defun debugger-setup-buffer (args) "Initialize the `*Backtrace*' buffer for entry to the debugger. That buffer should be current already." @@ -271,27 +320,20 @@ That buffer should be current already." (erase-buffer) (set-buffer-multibyte t) ;Why was it nil ? -stef (setq buffer-undo-list t) - (let ((standard-output (current-buffer)) - (print-escape-newlines t) - (print-level 8) - (print-length 50)) - ;; FIXME the debugger could pass a custom callback to mapbacktrace - ;; instead of manipulating printed results. - (mapbacktrace #'backtrace--print-frame 'debug)) - (goto-char (point-min)) - (delete-region (point) - (progn - (forward-line (if (eq (car args) 'debug) - ;; Remove debug--implement-debug-on-entry - ;; and the advice's `apply' frame. - 3 - 1)) - (point))) (insert "Debugger entered") - ;; lambda is for debug-on-call when a function call is next. - ;; debug is for debug-on-entry function called. - (let ((pos (point))) + (let ((frames (nthcdr + ;; Remove debug--implement-debug-on-entry and the + ;; advice's `apply' frame. + (if (eq (car args) 'debug) 3 1) + (backtrace-frames 'debug))) + (print-escape-newlines t) + (print-escape-control-characters t) + (print-level 8) + (print-length 50) + (pos (point))) (pcase (car args) + ;; lambda is for debug-on-call when a function call is next. + ;; debug is for debug-on-entry function called. ((or `lambda `debug) (insert "--entering a function:\n") (setq pos (1- (point)))) @@ -300,11 +342,9 @@ That buffer should be current already." (insert "--returning value: ") (setq pos (point)) (setq debugger-value (nth 1 args)) - (prin1 debugger-value (current-buffer)) - (insert ?\n) - (delete-char 1) - (insert ? ) - (beginning-of-line)) + (debugger--print debugger-value (current-buffer)) + (setf (cl-getf (nth 3 (car frames)) :debug-on-exit) nil) + (insert ?\n)) ;; Watchpoint triggered. ((and `watchpoint (let `(,symbol ,newval . ,details) (cdr args))) (insert @@ -327,7 +367,7 @@ That buffer should be current already." (`error (insert "--Lisp error: ") (setq pos (point)) - (prin1 (nth 1 args) (current-buffer)) + (debugger--print (nth 1 args) (current-buffer)) (insert ?\n)) ;; debug-on-call, when the next thing is an eval. (`t @@ -337,98 +377,15 @@ That buffer should be current already." (_ (insert ": ") (setq pos (point)) - (prin1 (if (eq (car args) 'nil) - (cdr args) args) - (current-buffer)) + (debugger--print + (if (eq (car args) 'nil) + (cdr args) args) + (current-buffer)) (insert ?\n))) + (debugger-insert-backtrace frames t) ;; Place point on "stack frame 0" (bug#15101). - (goto-char pos)) - ;; After any frame that uses eval-buffer, - ;; insert a line that states the buffer position it's reading at. - (save-excursion - (let ((tem eval-buffer-list)) - (while (and tem - (re-search-forward "^ eval-\\(buffer\\|region\\)(" nil t)) - (end-of-line) - (insert (format " ; Reading at buffer position %d" - ;; This will get the wrong result - ;; if there are two nested eval-region calls - ;; for the same buffer. That's not a very useful case. - (with-current-buffer (car tem) - (point)))) - (pop tem)))) - (debugger-make-xrefs)) - -(defun debugger-make-xrefs (&optional buffer) - "Attach cross-references to function names in the `*Backtrace*' buffer." - (interactive "b") - (with-current-buffer (or buffer (current-buffer)) - (save-excursion - (setq buffer (current-buffer)) - (let ((inhibit-read-only t) - (old-end (point-min)) (new-end (point-min))) - ;; If we saved an old backtrace, find the common part - ;; between the new and the old. - ;; Compare line by line, starting from the end, - ;; because that's the part that is likely to be unchanged. - (if debugger-previous-backtrace - (let (old-start new-start (all-match t)) - (goto-char (point-max)) - (with-temp-buffer - (insert debugger-previous-backtrace) - (while (and all-match (not (bobp))) - (setq old-end (point)) - (forward-line -1) - (setq old-start (point)) - (with-current-buffer buffer - (setq new-end (point)) - (forward-line -1) - (setq new-start (point))) - (if (not (zerop - (let ((case-fold-search nil)) - (compare-buffer-substrings - (current-buffer) old-start old-end - buffer new-start new-end)))) - (setq all-match nil)))) - ;; Now new-end is the position of the start of the - ;; unchanged part in the current buffer, and old-end is - ;; the position of that same text in the saved old - ;; backtrace. But we must subtract (point-min) since strings are - ;; indexed in origin 0. - - ;; Replace the unchanged part of the backtrace - ;; with the text from debugger-previous-backtrace, - ;; since that already has the proper xrefs. - ;; With this optimization, we only need to scan - ;; the changed part of the backtrace. - (delete-region new-end (point-max)) - (goto-char (point-max)) - (insert (substring debugger-previous-backtrace - (- old-end (point-min)))) - ;; Make the unchanged part of the backtrace inaccessible - ;; so it won't be scanned. - (narrow-to-region (point-min) new-end))) - - ;; Scan the new part of the backtrace, inserting xrefs. - (goto-char (point-min)) - (while (progn - (goto-char (+ (point) 2)) - (skip-syntax-forward "^w_") - (not (eobp))) - (let* ((beg (point)) - (end (progn (skip-syntax-forward "w_") (point))) - (sym (intern-soft (buffer-substring-no-properties - beg end))) - (file (and sym (symbol-file sym 'defun)))) - (when file - (goto-char beg) - ;; help-xref-button needs to operate on something matched - ;; by a regexp, so set that up for it. - (re-search-forward "\\(\\sw\\|\\s_\\)+") - (help-xref-button 0 'help-function-def sym file))) - (forward-line 1)) - (widen)) - (setq debugger-previous-backtrace (buffer-string))))) + (goto-char pos))) + (defun debugger-step-through () "Proceed, stepping through subexpressions of this expression. @@ -466,7 +423,7 @@ will be used, such as in a debug on exit from a frame." "from an error" "at function entrance"))) (setq debugger-value val) (princ "Returning " t) - (prin1 debugger-value) + (debugger--print debugger-value) (save-excursion ;; Check to see if we've flagged some frame for debug-on-exit, in which ;; case we'll probably come back to the debugger soon. @@ -581,7 +538,7 @@ The environment used is the one when entering the activation frame at point." (debugger-env-macro (let ((val (backtrace-eval exp nframe base))) (prog1 - (prin1 val t) + (debugger--print val t) (let ((str (eval-expression-print-format val))) (if str (princ str t)))))))) @@ -603,7 +560,7 @@ The environment used is the one when entering the activation frame at point." (insert "\n ") (prin1 symbol (current-buffer)) (insert " = ") - (prin1 value (current-buffer)))))))) + (debugger--print value (current-buffer)))))))) (defun debugger--show-locals () "For the frame at point, insert locals and add text properties." @@ -866,9 +823,13 @@ To specify a nil argument interactively, exit with an empty minibuffer." 'type 'help-function 'help-args (list fun)) (terpri)) - (terpri) - (princ "Note: if you have redefined a function, then it may no longer\n") - (princ "be set to debug on entry, even if it is in the list.")))))) + ;; Now that debug--function-list uses advice-member-p, its + ;; output should be reliable (except for bugs and the exceptional + ;; case where some other advice ends up overriding ours). + ;;(terpri) + ;;(princ "Note: if you have redefined a function, then it may no longer\n") + ;;(princ "be set to debug on entry, even if it is in the list.") + ))))) (defun debug--implement-debug-watch (symbol newval op where) "Conditionally call the debugger. diff --git a/lisp/emacs-lisp/derived.el b/lisp/emacs-lisp/derived.el index fffe972460c..751291afa88 100644 --- a/lisp/emacs-lisp/derived.el +++ b/lisp/emacs-lisp/derived.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -203,11 +203,13 @@ See Info node `(elisp)Derived Modes' for more details." parent child docstring syntax abbrev)) `(progn - (defvar ,hook nil - ,(format "Hook run after entering %s mode. + (defvar ,hook nil) + (unless (get ',hook 'variable-documentation) + (put ',hook 'variable-documentation + ,(format "Hook run after entering %s mode. No problems result if this variable is not bound. `add-hook' automatically binds it. (This is true for all hook variables.)" - name)) + name))) (unless (boundp ',map) (put ',map 'definition-name ',child)) (with-no-warnings (defvar ,map (make-sparse-keymap))) diff --git a/lisp/emacs-lisp/disass.el b/lisp/emacs-lisp/disass.el index 66673b4d26c..90d5001c841 100644 --- a/lisp/emacs-lisp/disass.el +++ b/lisp/emacs-lisp/disass.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emacs-lisp/easy-mmode.el b/lisp/emacs-lisp/easy-mmode.el index 158b9212fbd..6293d71470d 100644 --- a/lisp/emacs-lisp/easy-mmode.el +++ b/lisp/emacs-lisp/easy-mmode.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -309,11 +309,13 @@ the mode if ARG is omitted or nil, and toggle it if ARG is `toggle'. ;; up-to-here. :autoload-end - (defvar ,hook nil - ,(format "Hook run after entering or leaving `%s'. + (defvar ,hook nil) + (unless (get ',hook 'variable-documentation) + (put ',hook 'variable-documentation + ,(format "Hook run after entering or leaving `%s'. No problems result if this variable is not bound. `add-hook' automatically binds it. (This is true for all hook variables.)" - modefun)) + modefun))) ;; Define the minor-mode keymap. ,(unless (symbolp keymap) ;nil is also a symbol. @@ -543,6 +545,7 @@ Valid keywords and arguments are: "Define a constant M whose value is the result of `easy-mmode-define-keymap'. The M, BS, and ARGS arguments are as per that function. DOC is the constant's documentation." + (declare (indent 1)) `(defconst ,m (easy-mmode-define-keymap ,bs nil (if (boundp ',m) ,m) ,(cons 'list args)) ,doc)) @@ -569,6 +572,7 @@ the constant's documentation." (defmacro easy-mmode-defsyntax (st css doc &rest args) "Define variable ST as a syntax-table. CSS contains a list of syntax specifications of the form (CHAR . SYNTAX)." + (declare (indent 1)) `(progn (autoload 'easy-mmode-define-syntax "easy-mmode") (defconst ,st (easy-mmode-define-syntax ,css ,(cons 'list args)) ,doc))) diff --git a/lisp/emacs-lisp/easymenu.el b/lisp/emacs-lisp/easymenu.el index 4fc9a783a5e..35b2af1a3f7 100644 --- a/lisp/emacs-lisp/easymenu.el +++ b/lisp/emacs-lisp/easymenu.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emacs-lisp/edebug.el b/lisp/emacs-lisp/edebug.el index 65e30f86778..dec986ae3e3 100644 --- a/lisp/emacs-lisp/edebug.el +++ b/lisp/emacs-lisp/edebug.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -906,7 +906,7 @@ circular objects. Let `read' read everything else." ;; with the object itself, wherever it occurs. (forward-char 1) (let ((obj (edebug-read-storing-offsets stream))) - (substitute-object-in-subtree obj placeholder) + (lread--substitute-object-in-subtree obj placeholder t) (throw 'return (setf (cdr elem) obj))))) ((eq ?# (following-char)) ;; #n# returns a previously read object. @@ -950,7 +950,8 @@ circular objects. Let `read' read everything else." ;;; Cursors for traversal of list and vector elements with offsets. -(defvar edebug-dotted-spec nil) +(defvar edebug-dotted-spec nil + "Set to t when matching after the dot in a dotted spec list.") (defun edebug-new-cursor (expressions offsets) ;; Return a new cursor for EXPRESSIONS with OFFSETS. @@ -1065,6 +1066,32 @@ circular objects. Let `read' read everything else." (defvar edebug-error-point nil) (defvar edebug-best-error nil) +;; Functions which may be used to extend Edebug's functionality. See +;; Testcover for an example. +(defvar edebug-after-instrumentation-function #'identity + "Function to run on code after instrumentation for debugging. +The function is called with one argument, a FORM which has just +been instrumented for Edebugging, and it should return either FORM +or a replacement form to use in its place.") + +(defvar edebug-new-definition-function #'edebug-new-definition + "Function to call after Edebug wraps a new definition. +After Edebug has initialized its own data, this function is +called with one argument, the symbol associated with the +definition, which may be the actual symbol defined or one +generated by Edebug.") + +(defvar edebug-behavior-alist + '((edebug edebug-default-enter edebug-slow-before edebug-slow-after)) + "Alist describing the runtime behavior of Edebug's instrumented code. +Each definition instrumented by Edebug will have a +`edebug-behavior' property which is a key to this alist. When +the instrumented code is running, Edebug will look here for the +implementations of `edebug-enter', `edebug-before', and +`edebug-after'. Edebug's instrumentation may be used for a new +purpose by adding an entry to this alist, and setting +`edebug-new-definition-function' to a function which sets +`edebug-behavior' for the definition.") (defun edebug-read-and-maybe-wrap-form () ;; Read a form and wrap it with edebug calls, if the conditions are right. @@ -1124,47 +1151,47 @@ circular objects. Let `read' read everything else." (eq 'symbol (edebug-next-token-class))) (read (current-buffer)))))) ;;;(message "all defs: %s all forms: %s" edebug-all-defs edebug-all-forms) - (cond - (defining-form-p - (if (or edebug-all-defs edebug-all-forms) - ;; If it is a defining form and we are edebugging defs, - ;; then let edebug-list-form start it. - (let ((cursor (edebug-new-cursor - (list (edebug-read-storing-offsets (current-buffer))) - (list edebug-offsets)))) - (car - (edebug-make-form-wrapper - cursor - (edebug-before-offset cursor) - (1- (edebug-after-offset cursor)) - (list (cons (symbol-name def-kind) (cdr spec)))))) - - ;; Not edebugging this form, so reset the symbol's edebug - ;; property to be just a marker at the definition's source code. - ;; This only works for defs with simple names. - (put def-name 'edebug (point-marker)) - ;; Also nil out dependent defs. - '(mapcar (function - (lambda (def) - (put def-name 'edebug nil))) - (get def-name 'edebug-dependents)) - (edebug-read-sexp))) - - ;; If all forms are being edebugged, explicitly wrap it. - (edebug-all-forms - (let ((cursor (edebug-new-cursor - (list (edebug-read-storing-offsets (current-buffer))) - (list edebug-offsets)))) - (edebug-make-form-wrapper - cursor - (edebug-before-offset cursor) - (edebug-after-offset cursor) - nil))) - - ;; Not a defining form, and not edebugging. - (t (edebug-read-sexp))) - )) - + (let ((result + (cond + (defining-form-p + (if (or edebug-all-defs edebug-all-forms) + ;; If it is a defining form and we are edebugging defs, + ;; then let edebug-list-form start it. + (let ((cursor (edebug-new-cursor + (list (edebug-read-storing-offsets (current-buffer))) + (list edebug-offsets)))) + (car + (edebug-make-form-wrapper + cursor + (edebug-before-offset cursor) + (1- (edebug-after-offset cursor)) + (list (cons (symbol-name def-kind) (cdr spec)))))) + + ;; Not edebugging this form, so reset the symbol's edebug + ;; property to be just a marker at the definition's source code. + ;; This only works for defs with simple names. + (put def-name 'edebug (point-marker)) + ;; Also nil out dependent defs. + '(mapcar (function + (lambda (def) + (put def-name 'edebug nil))) + (get def-name 'edebug-dependents)) + (edebug-read-sexp))) + + ;; If all forms are being edebugged, explicitly wrap it. + (edebug-all-forms + (let ((cursor (edebug-new-cursor + (list (edebug-read-storing-offsets (current-buffer))) + (list edebug-offsets)))) + (edebug-make-form-wrapper + cursor + (edebug-before-offset cursor) + (edebug-after-offset cursor) + nil))) + + ;; Not a defining form, and not edebugging. + (t (edebug-read-sexp))))) + (funcall edebug-after-instrumentation-function result)))) (defvar edebug-def-args) ; args of defining form. (defvar edebug-def-interactive) ; is it an emacs interactive function? @@ -1193,7 +1220,7 @@ circular objects. Let `read' read everything else." ;; Uses the dynamically bound vars edebug-def-name and edebug-def-args. ;; Do this after parsing since that may find a name. (setq edebug-def-name - (or edebug-def-name edebug-old-def-name (cl-gensym "edebug-anon"))) + (or edebug-def-name edebug-old-def-name (gensym "edebug-anon"))) `(edebug-enter (quote ,edebug-def-name) ,(if edebug-inside-func @@ -1332,7 +1359,6 @@ expressions; a `progn' form will be returned enclosing these forms." ;; (message "defining: %s" edebug-def-name) (sit-for 2) (edebug-make-top-form-data-entry form-data-entry) - (message "Edebug: %s" edebug-def-name) ;;(debug edebug-def-name) ;; Destructively reverse edebug-offset-list and make vector from it. @@ -1358,9 +1384,16 @@ expressions; a `progn' form will be returned enclosing these forms." edebug-offset-list edebug-top-window-data )) + + (funcall edebug-new-definition-function edebug-def-name) result ))) +(defun edebug-new-definition (def-name) + "Set up DEF-NAME to use Edebug's instrumentation functions." + (put def-name 'edebug-behavior 'edebug) + (message "Edebug: %s" def-name)) + (defun edebug-clear-frequency-count (name) ;; Create initial frequency count vector. @@ -1494,8 +1527,6 @@ expressions; a `progn' form will be returned enclosing these forms." ;;; Matching of specs. -(defvar edebug-after-dotted-spec nil) - (defvar edebug-matching-depth 0) ;; initial value @@ -1556,36 +1587,48 @@ expressions; a `progn' form will be returned enclosing these forms." (let ((edebug-dotted-spec t));; Containing spec list was dotted. (edebug-match-specs cursor (list specs) remainder-handler))) - ;; Is the form dotted? - ((not (listp (edebug-cursor-expressions cursor)));; allow nil + ;; The reason for processing here &optional, &rest, and vectors + ;; which might contain them even when the form is dotted is to + ;; allow them to match nothing, so we can advance to the dotted + ;; part of the spec. + ((or (listp (edebug-cursor-expressions cursor)) + (vectorp (car specs)) + (memq (car specs) '(&optional &rest))) ; Process normally. + ;; (message "%scursor=%s specs=%s" + ;; (make-string edebug-matching-depth ?|) cursor (car specs)) + (let* ((spec (car specs)) + (rest) + (first-char (and (symbolp spec) (aref (symbol-name spec) 0))) + (match (cond + ((eq ?& first-char);; "&" symbols take all following specs. + (funcall (get-edebug-spec spec) cursor (cdr specs))) + ((eq ?: first-char);; ":" symbols take one following spec. + (setq rest (cdr (cdr specs))) + (funcall (get-edebug-spec spec) cursor (car (cdr specs)))) + (t;; Any other normal spec. + (setq rest (cdr specs)) + (edebug-match-one-spec cursor spec))))) + ;; The first match result may not be a list, which can happen + ;; when matching the tail of a dotted list. In that case + ;; there is no remainder. + (if (listp match) + (nconc match + (funcall remainder-handler cursor rest remainder-handler)) + match))) + + ;; Must be a dotted form, with no remaining &rest or &optional specs to + ;; match. + (t (if (not edebug-dotted-spec) (edebug-no-match cursor "Dotted spec required.")) ;; Cancel dotted spec and dotted form. (let ((edebug-dotted-spec) - (this-form (edebug-cursor-expressions cursor)) - (this-offset (edebug-cursor-offsets cursor))) - ;; Wrap the form in a list, (by changing the cursor??)... + (this-form (edebug-cursor-expressions cursor)) + (this-offset (edebug-cursor-offsets cursor))) + ;; Wrap the form in a list, by changing the cursor. (edebug-set-cursor cursor (list this-form) this-offset) - ;; and process normally, then unwrap the result. - (car (edebug-match-specs cursor specs remainder-handler)))) - - (t;; Process normally. - (let* ((spec (car specs)) - (rest) - (first-char (and (symbolp spec) (aref (symbol-name spec) 0)))) - ;;(message "spec = %s first char = %s" spec first-char) (sit-for 1) - (nconc - (cond - ((eq ?& first-char);; "&" symbols take all following specs. - (funcall (get-edebug-spec spec) cursor (cdr specs))) - ((eq ?: first-char);; ":" symbols take one following spec. - (setq rest (cdr (cdr specs))) - (funcall (get-edebug-spec spec) cursor (car (cdr specs)))) - (t;; Any other normal spec. - (setq rest (cdr specs)) - (edebug-match-one-spec cursor spec))) - (funcall remainder-handler cursor rest remainder-handler))))))) - + ;; Process normally, then unwrap the result. + (car (edebug-match-specs cursor specs remainder-handler))))))) ;; Define specs for all the symbol specs with functions used to process them. ;; Perhaps we shouldn't be doing this with edebug-form-specs since the @@ -1986,15 +2029,14 @@ expressions; a `progn' form will be returned enclosing these forms." (def-edebug-spec defvar (symbolp &optional form stringp)) (def-edebug-spec defun - (&define name lambda-list - [&optional stringp] + (&define name lambda-list lambda-doc [&optional ("declare" &rest sexp)] [&optional ("interactive" interactive)] def-body)) (def-edebug-spec defmacro ;; FIXME: Improve `declare' so we can Edebug gv-expander and ;; gv-setter declarations. - (&define name lambda-list [&optional stringp] + (&define name lambda-list lambda-doc [&optional ("declare" &rest sexp)] def-body)) (def-edebug-spec arglist lambda-list) ;; deprecated - use lambda-list. @@ -2005,6 +2047,10 @@ expressions; a `progn' form will be returned enclosing these forms." &optional ["&rest" arg] ))) +(def-edebug-spec lambda-doc + (&optional [&or stringp + (&define ":documentation" def-form)])) + (def-edebug-spec interactive (&optional &or stringp def-form)) @@ -2167,7 +2213,21 @@ error is signaled again." ;;; Entering Edebug -(defun edebug-enter (function args body) +(defun edebug-enter (func args body) + "Enter Edebug for a function. +FUNC should be the symbol with the Edebug information, ARGS is +the list of arguments and BODY is the code. + +Look up the `edebug-behavior' for FUNC in `edebug-behavior-alist' +and run its entry function, and set up `edebug-before' and +`edebug-after'." + (cl-letf* ((behavior (get func 'edebug-behavior)) + (functions (cdr (assoc behavior edebug-behavior-alist))) + ((symbol-function #'edebug-before) (nth 1 functions)) + ((symbol-function #'edebug-after) (nth 2 functions))) + (funcall (nth 0 functions) func args body))) + +(defun edebug-default-enter (function args body) ;; Entering FUNC. The arguments are ARGS, and the body is BODY. ;; Setup edebug variables and evaluate BODY. This function is called ;; when a function evaluated with edebug-eval-top-level-form is entered. @@ -2198,7 +2258,7 @@ error is signaled again." edebug-initial-mode edebug-execution-mode) edebug-next-execution-mode nil) - (edebug-enter function args body)))) + (edebug-default-enter function args body)))) (let* ((edebug-data (get function 'edebug)) (edebug-def-mark (car edebug-data)) ; mark at def start @@ -2317,22 +2377,27 @@ MSG is printed after `::::} '." value (edebug-debugger after-index 'after value) ))) - (defun edebug-fast-after (_before-index _after-index value) ;; Do nothing but return the value. value) (defun edebug-run-slow () - (defalias 'edebug-before 'edebug-slow-before) - (defalias 'edebug-after 'edebug-slow-after)) + "Set up Edebug's normal behavior." + (setf (cdr (assq 'edebug edebug-behavior-alist)) + '(edebug-default-enter edebug-slow-before edebug-slow-after))) ;; This is not used, yet. (defun edebug-run-fast () - (defalias 'edebug-before 'edebug-fast-before) - (defalias 'edebug-after 'edebug-fast-after)) - -(edebug-run-slow) + "Disable Edebug without de-instrumenting code." + (setf (cdr (assq 'edebug edebug-behavior-alist)) + '(edebug-default-enter edebug-fast-before edebug-fast-after))) +(defalias 'edebug-before nil + "Function called by Edebug before a form is evaluated. +See `edebug-behavior-alist' for implementations.") +(defalias 'edebug-after nil + "Function called by Edebug after a form is evaluated. +See `edebug-behavior-alist' for implementations.") (defun edebug--update-coverage (after-index value) (let ((old-result (aref edebug-coverage after-index))) @@ -3204,17 +3269,8 @@ generated symbols for methods. If a function or method to instrument cannot be found, signal an error." (let ((func-marker (get func 'edebug))) (cond - ((and (markerp func-marker) (marker-buffer func-marker)) - ;; It is uninstrumented, so instrument it. - (with-current-buffer (marker-buffer func-marker) - (goto-char func-marker) - (edebug-eval-top-level-form) - (list func))) - ((consp func-marker) - (message "%s is already instrumented." func) - (list func)) - ((get func 'cl--generic) - (let ((method-defs (method-files func)) + ((cl-generic-p func) + (let ((method-defs (cl--generic-method-files func)) symbols) (unless method-defs (error "Could not find any method definitions for %s" func)) @@ -3227,6 +3283,15 @@ instrument cannot be found, signal an error." (edebug-eval-top-level-form) (push (edebug-form-data-symbol) symbols)))) symbols)) + ((and (markerp func-marker) (marker-buffer func-marker)) + ;; It is uninstrumented, so instrument it. + (with-current-buffer (marker-buffer func-marker) + (goto-char func-marker) + (edebug-eval-top-level-form) + (list func))) + ((consp func-marker) + (message "%s is already instrumented." func) + (list func)) (t (let ((loc (find-function-noselect func t))) (unless (cdr loc) diff --git a/lisp/emacs-lisp/eieio-base.el b/lisp/emacs-lisp/eieio-base.el index 33c71ec5807..58dcd09d7ea 100644 --- a/lisp/emacs-lisp/eieio-base.el +++ b/lisp/emacs-lisp/eieio-base.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -31,6 +31,7 @@ ;;; Code: (require 'eieio) +(require 'seq) (eval-when-compile (require 'cl-lib)) ;;; eieio-instance-inheritor @@ -255,8 +256,11 @@ malicious code. Note: This function recurses when a slot of :type of some object is identified, and needing more object creation." (let* ((objclass (nth 0 inputlist)) - ;; (objname (nth 1 inputlist)) - (slots (nthcdr 2 inputlist)) + ;; Earlier versions of `object-write' added a string name for + ;; the object, now obsolete. + (slots (nthcdr + (if (stringp (nth 1 inputlist)) 2 1) + inputlist)) (createslots nil) (class (progn @@ -308,14 +312,6 @@ Second, any text properties will be stripped from strings." (= (length proposed-value) 1)) nil) - ;; We have a slot with a single object that can be - ;; saved here. Recurse and evaluate that - ;; sub-object. - ((and classtype (class-p classtype) - (child-of-class-p (car proposed-value) classtype)) - (eieio-persistent-convert-list-to-object - proposed-value)) - ;; List of object constructors. ((and (eq (car proposed-value) 'list) ;; 2nd item is a list. @@ -346,6 +342,16 @@ Second, any text properties will be stripped from strings." objlist)) ;; return the list of objects ... reversed. (nreverse objlist))) + ;; We have a slot with a single object that can be + ;; saved here. Recurse and evaluate that + ;; sub-object. + ((and classtype + (seq-some + (lambda (elt) + (child-of-class-p (car proposed-value) elt)) + classtype)) + (eieio-persistent-convert-list-to-object + proposed-value)) (t proposed-value)))) @@ -402,13 +408,9 @@ If no class is referenced there, then return nil." type)) ((eq (car-safe type) 'or) - ;; If type is a list, and is an or, it is possibly something - ;; like (or null myclass), so check for that. - (let ((ans nil)) - (dolist (subtype (cdr type)) - (setq ans (eieio-persistent-slot-type-is-class-p - subtype))) - ans)) + ;; If type is a list, and is an `or', return all valid class + ;; types within the `or' statement. + (seq-filter #'eieio-persistent-slot-type-is-class-p (cdr type))) (t ;; No match, not a class. @@ -465,7 +467,7 @@ instance." (cl-defmethod eieio-object-name-string ((obj eieio-named)) "Return a string which is OBJ's name." (or (slot-value obj 'object-name) - (symbol-name (eieio-object-class obj)))) + (cl-call-next-method))) (cl-defmethod eieio-object-set-name-string ((obj eieio-named) name) "Set the string which is OBJ's NAME." diff --git a/lisp/emacs-lisp/eieio-compat.el b/lisp/emacs-lisp/eieio-compat.el index e6e6d118709..bf0bc857358 100644 --- a/lisp/emacs-lisp/eieio-compat.el +++ b/lisp/emacs-lisp/eieio-compat.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -165,7 +165,8 @@ Summary: (if (memq method '(no-next-method no-applicable-method)) (symbol-function method) (let ((generic (cl-generic-ensure-function method))) - (symbol-function (cl--generic-name generic))))) + (or (symbol-function (cl--generic-name generic)) + (cl--generic-make-function generic))))) ;;;###autoload (defun eieio--defmethod (method kind argclass code) diff --git a/lisp/emacs-lisp/eieio-core.el b/lisp/emacs-lisp/eieio-core.el index dfe1c06bfaf..22bf812fcb9 100644 --- a/lisp/emacs-lisp/eieio-core.el +++ b/lisp/emacs-lisp/eieio-core.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -84,7 +84,7 @@ Currently under control of this var: (progn ;; Arrange for field access not to bother checking if the access is indeed ;; made to an eieio--class object. - (cl-declaim (optimize (safety 0))) + (eval-when-compile (cl-declaim (optimize (safety 0)))) (cl-defstruct (eieio--class (:constructor nil) @@ -103,8 +103,12 @@ Currently under control of this var: options ;; storage location of tagged class option ; Stored outright without modifications or stripping ) - ;; Set it back to the default value. - (cl-declaim (optimize (safety 1)))) + ;; Set it back to the default value. NOTE: Using the default + ;; `safety' value does NOT give the default + ;; `byte-compile-delete-errors' value. Therefore limit this (and + ;; the above `cl-declaim') to compile time so that we don't affect + ;; code which only loads this library. + (eval-when-compile (cl-declaim (optimize (safety 1))))) (eval-and-compile diff --git a/lisp/emacs-lisp/eieio-custom.el b/lisp/emacs-lisp/eieio-custom.el index e82eaa2b01f..745bd89f062 100644 --- a/lisp/emacs-lisp/eieio-custom.el +++ b/lisp/emacs-lisp/eieio-custom.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/emacs-lisp/eieio-datadebug.el b/lisp/emacs-lisp/eieio-datadebug.el index 8ef92df513e..da8d9a017bb 100644 --- a/lisp/emacs-lisp/eieio-datadebug.el +++ b/lisp/emacs-lisp/eieio-datadebug.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/emacs-lisp/eieio-opt.el b/lisp/emacs-lisp/eieio-opt.el index ba4331f126b..f464d024670 100644 --- a/lisp/emacs-lisp/eieio-opt.el +++ b/lisp/emacs-lisp/eieio-opt.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/emacs-lisp/eieio-speedbar.el b/lisp/emacs-lisp/eieio-speedbar.el index 36ab2c165cf..fb57453f39e 100644 --- a/lisp/emacs-lisp/eieio-speedbar.el +++ b/lisp/emacs-lisp/eieio-speedbar.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/emacs-lisp/eieio.el b/lisp/emacs-lisp/eieio.el index 1a7de55fcef..d0d2ff5145c 100644 --- a/lisp/emacs-lisp/eieio.el +++ b/lisp/emacs-lisp/eieio.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -246,7 +246,7 @@ This method is obsolete." ;; test, so we can let typep have the CLOS documented behavior ;; while keeping our above predicate clean. - (put ',name 'cl-deftype-satisfies #',testsym2) + (define-symbol-prop ',name 'cl-deftype-satisfies #',testsym2) (eieio-defclass-internal ',name ',superclasses ',slots ',options-and-doc) @@ -377,9 +377,21 @@ is a shorthand for (NAME NAME)." (define-obsolete-function-alias 'object-class-fast #'eieio-object-class "24.4") +;; In the past, every EIEIO object had a `name' field, so we had the +;; two methods `eieio-object-name-string' and +;; `eieio-object-set-name-string' "for free". Since this field is +;; very rarely used, we got rid of it and instead we keep it in a weak +;; hash-tables, for those very rare objects that use it. +;; Really, those rare objects should inherit from `eieio-named' instead! +(defconst eieio--object-names (make-hash-table :test #'eq :weakness 'key)) + (cl-defgeneric eieio-object-name-string (obj) "Return a string which is OBJ's name." - (declare (obsolete eieio-named "25.1"))) + (or (gethash obj eieio--object-names) + (format "%s-%x" (eieio-object-class obj) (sxhash-eq obj)))) + +(define-obsolete-function-alias + 'object-name-string #'eieio-object-name-string "24.4") (defun eieio-object-name (obj &optional extra) "Return a printed representation for object OBJ. @@ -389,21 +401,9 @@ If EXTRA, include that in the string returned to represent the symbol." (eieio-object-name-string obj) (or extra ""))) (define-obsolete-function-alias 'object-name #'eieio-object-name "24.4") -(defconst eieio--object-names (make-hash-table :test #'eq :weakness 'key)) - -;; In the past, every EIEIO object had a `name' field, so we had the two method -;; below "for free". Since this field is very rarely used, we got rid of it -;; and instead we keep it in a weak hash-tables, for those very rare objects -;; that use it. -(cl-defmethod eieio-object-name-string (obj) - (or (gethash obj eieio--object-names) - (symbol-name (eieio-object-class obj)))) -(define-obsolete-function-alias - 'object-name-string #'eieio-object-name-string "24.4") - -(cl-defmethod eieio-object-set-name-string (obj name) +(cl-defgeneric eieio-object-set-name-string (obj name) "Set the string which is OBJ's NAME." - (declare (obsolete eieio-named "25.1")) + (declare (obsolete "inherit from `eieio-named' and use (setf (slot-value OBJ 'object-name) NAME) instead" "25.1")) (cl-check-type name string) (setf (gethash obj eieio--object-names) name)) (define-obsolete-function-alias @@ -847,7 +847,16 @@ to prepend a space." (princ (object-print object) stream)) (defvar eieio-print-depth 0 - "When printing, keep track of the current indentation depth.") + "The current indentation depth while printing. +Ignored if `eieio-print-indentation' is nil.") + +(defvar eieio-print-indentation t + "When non-nil, indent contents of printed objects.") + +(defvar eieio-print-object-name t + "When non-nil write the object name in `object-write'. +Does not affect objects subclassing `eieio-named'. Note that +Emacs<26 requires that object names be present.") (cl-defgeneric object-write (this &optional comment) "Write out object THIS to the current stream. @@ -859,10 +868,11 @@ This writes out the vector version of this object. Complex and recursive object are discouraged from being written. If optional COMMENT is non-nil, include comments when outputting this object." - (when comment + (when (and comment eieio-print-object-name) (princ ";; Object ") (princ (eieio-object-name-string this)) - (princ "\n") + (princ "\n")) + (when comment (princ comment) (princ "\n")) (let* ((cl (eieio-object-class this)) @@ -871,12 +881,14 @@ this object." ;; It should look like this: ;; (<constructor> <name> <slot> <slot> ... ) ;; Each slot's slot is writen using its :writer. - (princ (make-string (* eieio-print-depth 2) ? )) + (when eieio-print-indentation + (princ (make-string (* eieio-print-depth 2) ? ))) (princ "(") (princ (symbol-name (eieio--class-constructor (eieio-object-class this)))) - (princ " ") - (prin1 (eieio-object-name-string this)) - (princ "\n") + (when eieio-print-object-name + (princ " ") + (prin1 (eieio-object-name-string this)) + (princ "\n")) ;; Loop over all the public slots (let ((slots (eieio--class-slots cv)) (eieio-print-depth (1+ eieio-print-depth))) @@ -889,7 +901,8 @@ this object." (unless (or (not i) (equal v (cl--slot-descriptor-initform slot))) (unless (bolp) (princ "\n")) - (princ (make-string (* eieio-print-depth 2) ? )) + (when eieio-print-indentation + (princ (make-string (* eieio-print-depth 2) ? ))) (princ (symbol-name i)) (if (alist-get :printer (cl--slot-descriptor-props slot)) ;; Use our public printer @@ -904,7 +917,7 @@ this object." "\n" " ")) (eieio-override-prin1 v)))))))) (princ ")") - (when (= eieio-print-depth 0) + (when (zerop eieio-print-depth) (princ "\n")))) (defun eieio-override-prin1 (thing) @@ -923,14 +936,16 @@ this object." (progn (princ "'") (prin1 list)) - (princ (make-string (* eieio-print-depth 2) ? )) + (when eieio-print-indentation + (princ (make-string (* eieio-print-depth 2) ? ))) (princ "(list") (let ((eieio-print-depth (1+ eieio-print-depth))) (while list (princ "\n") (if (eieio-object-p (car list)) (object-write (car list)) - (princ (make-string (* eieio-print-depth 2) ? )) + (when eieio-print-indentation + (princ (make-string (* eieio-print-depth) ? ))) (eieio-override-prin1 (car list))) (setq list (cdr list)))) (princ ")"))) diff --git a/lisp/emacs-lisp/eldoc.el b/lisp/emacs-lisp/eldoc.el index a05bd7cc4d4..ad08977b81a 100644 --- a/lisp/emacs-lisp/eldoc.el +++ b/lisp/emacs-lisp/eldoc.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -160,6 +160,10 @@ This is used to determine if `eldoc-idle-delay' is changed by the user.") It should receive the same arguments as `message'.") (defun eldoc-edit-message-commands () + "Return an obarray containing common editing commands. + +When `eldoc-print-after-edit' is non-nil, ElDoc messages are only +printed after commands contained in this obarray." (let ((cmds (make-vector 31 0)) (re (regexp-opt '("delete" "insert" "edit" "electric" "newline")))) (mapatoms (lambda (s) @@ -211,16 +215,21 @@ expression point is on." ;;;###autoload (defun turn-on-eldoc-mode () - "Turn on `eldoc-mode' if the buffer has eldoc support enabled. + "Turn on `eldoc-mode' if the buffer has ElDoc support enabled. See `eldoc-documentation-function' for more detail." (when (eldoc--supported-p) (eldoc-mode 1))) (defun eldoc--supported-p () + "Non-nil if an ElDoc function is set for this buffer." (not (memq eldoc-documentation-function '(nil ignore)))) (defun eldoc-schedule-timer () + "Ensure `eldoc-timer' is running. + +If the user has changed `eldoc-idle-delay', update the timer to +reflect the change." (or (and eldoc-timer (memq eldoc-timer timer-idle-list)) ;FIXME: Why? (setq eldoc-timer @@ -229,8 +238,7 @@ See `eldoc-documentation-function' for more detail." (lambda () (when (or eldoc-mode (and global-eldoc-mode - (not (memq eldoc-documentation-function - '(nil ignore))))) + (eldoc--supported-p))) (eldoc-print-current-symbol-info)))))) ;; If user has changed the idle delay, update the timer. @@ -248,7 +256,7 @@ Otherwise work like `message'." (progn (add-hook 'minibuffer-exit-hook (lambda () (setq eldoc-mode-line-string nil - ;; http://debbugs.gnu.org/16920 + ;; https://debbugs.gnu.org/16920 eldoc-last-message nil)) nil t) (with-current-buffer @@ -256,28 +264,25 @@ Otherwise work like `message'." (or (window-in-direction 'above (minibuffer-window)) (minibuffer-selected-window) (get-largest-window))) + (when mode-line-format (unless (and (listp mode-line-format) (assq 'eldoc-mode-line-string mode-line-format)) (setq mode-line-format (list "" '(eldoc-mode-line-string (" " eldoc-mode-line-string " ")) - mode-line-format))) + mode-line-format)))) (setq eldoc-mode-line-string (when (stringp format-string) (apply #'format-message format-string args))) (force-mode-line-update))) (apply 'message format-string args))) -(defun eldoc-message (&rest args) +(defun eldoc-message (&optional string) + "Display STRING as an ElDoc message if it's non-nil. + +Also store it in `eldoc-last-message' and return that value." (let ((omessage eldoc-last-message)) - (setq eldoc-last-message - (cond ((eq (car args) eldoc-last-message) eldoc-last-message) - ((null (car args)) nil) - ;; If only one arg, no formatting to do, so put it in - ;; eldoc-last-message so eq test above might succeed on - ;; subsequent calls. - ((null (cdr args)) (car args)) - (t (apply #'format-message args)))) + (setq eldoc-last-message string) ;; In emacs 19.29 and later, and XEmacs 19.13 and later, all messages ;; are recorded in a log. Do not put eldoc messages in that log since ;; they are Legion. @@ -289,6 +294,7 @@ Otherwise work like `message'." eldoc-last-message) (defun eldoc--message-command-p (command) + "Return non-nil if COMMAND is in `eldoc-message-commands'." (and (symbolp command) (intern-soft (symbol-name command) eldoc-message-commands))) @@ -299,6 +305,7 @@ Otherwise work like `message'." ;; before the next command executes, which does away with the flicker. ;; This doesn't seem to be required for Emacs 19.28 and earlier. (defun eldoc-pre-command-refresh-echo-area () + "Reprint `eldoc-last-message' in the echo area." (and eldoc-last-message (not (minibufferp)) ;We don't use the echo area when in minibuffer. (if (and (eldoc-display-message-no-interference-p) @@ -310,6 +317,7 @@ Otherwise work like `message'." ;; Decide whether now is a good time to display a message. (defun eldoc-display-message-p () + "Return non-nil when it is appropriate to display an ElDoc message." (and (eldoc-display-message-no-interference-p) ;; If this-command is non-nil while running via an idle ;; timer, we're still in the middle of executing a command, @@ -322,6 +330,7 @@ Otherwise work like `message'." ;; Check various conditions about the current environment that might make ;; it undesirable to print eldoc messages right this instant. (defun eldoc-display-message-no-interference-p () + "Return nil if displaying a message would cause interference." (not (or executing-kbd-macro (bound-and-true-p edebug-active)))) @@ -347,6 +356,7 @@ variable) is taken into account if the major mode specific function does not return any documentation.") (defun eldoc-print-current-symbol-info () + "Print the text produced by `eldoc-documentation-function'." ;; This is run from post-command-hook or some idle timer thing, ;; so we need to be careful that errors aren't ignored. (with-demoted-errors "eldoc error: %s" @@ -361,6 +371,13 @@ return any documentation.") ;; truncated or eliminated entirely from the output to make room for the ;; description. (defun eldoc-docstring-format-sym-doc (prefix doc &optional face) + "Combine PREFIX and DOC, and shorten the result to fit in the echo area. + +When PREFIX is a symbol, propertize its symbol name with FACE +before combining it with DOC. If FACE is not provided, just +apply the nil face. + +See also: `eldoc-echo-area-use-multiline-p'." (when (symbolp prefix) (setq prefix (concat (propertize (symbol-name prefix) 'face face) ": "))) (let* ((ea-multi eldoc-echo-area-use-multiline-p) @@ -390,22 +407,26 @@ return any documentation.") ;; These functions do display-command table management. (defun eldoc-add-command (&rest cmds) + "Add each of CMDS to the obarray `eldoc-message-commands'." (dolist (name cmds) (and (symbolp name) (setq name (symbol-name name))) (set (intern name eldoc-message-commands) t))) (defun eldoc-add-command-completions (&rest names) + "Pass every prefix completion of NAMES to `eldoc-add-command'." (dolist (name names) (apply #'eldoc-add-command (all-completions name obarray 'commandp)))) (defun eldoc-remove-command (&rest cmds) + "Remove each of CMDS from the obarray `eldoc-message-commands'." (dolist (name cmds) (and (symbolp name) (setq name (symbol-name name))) (unintern name eldoc-message-commands))) (defun eldoc-remove-command-completions (&rest names) + "Pass every prefix completion of NAMES to `eldoc-remove-command'." (dolist (name names) (apply #'eldoc-remove-command (all-completions name eldoc-message-commands)))) @@ -418,9 +439,9 @@ return any documentation.") "down-list" "end-of-" "exchange-point-and-mark" "forward-" "goto-" "handle-select-window" "indent-for-tab-command" "left-" "mark-page" "mark-paragraph" "mouse-set-point" "move-" "move-beginning-of-" - "move-end-of-" "newline" "next-" "other-window" "pop-global-mark" "previous-" - "recenter" "right-" "scroll-" "self-insert-command" "split-window-" - "up-list") + "move-end-of-" "newline" "next-" "other-window" "pop-global-mark" + "previous-" "recenter" "right-" "scroll-" "self-insert-command" + "split-window-" "up-list") (provide 'eldoc) diff --git a/lisp/emacs-lisp/elint.el b/lisp/emacs-lisp/elint.el index cce9553ff6a..643d7160dbb 100644 --- a/lisp/emacs-lisp/elint.el +++ b/lisp/emacs-lisp/elint.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -463,21 +463,9 @@ Return nil if there are no more forms, t otherwise." ;; Import variable definitions ((memq (car form) '(require cc-require cc-require-when-compile)) (let ((name (eval (cadr form))) - (file (eval (nth 2 form))) - (elint-doing-cl (bound-and-true-p elint-doing-cl))) + (file (eval (nth 2 form)))) (unless (memq name elint-features) (add-to-list 'elint-features name) - ;; cl loads cl-macs in an opaque manner. - ;; Since cl-macs requires cl, we can just process cl-macs. - ;; FIXME: AFAIK, `cl' now behaves properly and does not need any - ;; special treatment any more. Can someone who understands this - ;; code confirm? --Stef - (and (eq name 'cl) (not elint-doing-cl) - ;; We need cl if elint-form is to be able to expand cl macros. - (require 'cl) - (setq name 'cl-macs - file nil - elint-doing-cl t)) ; blech (setq elint-env (elint-add-required-env elint-env name file)))))) elint-env) diff --git a/lisp/emacs-lisp/elp.el b/lisp/emacs-lisp/elp.el index d4500f131a2..905718dad68 100644 --- a/lisp/emacs-lisp/elp.el +++ b/lisp/emacs-lisp/elp.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -382,14 +382,13 @@ original definition, use \\[elp-restore-function] or \\[elp-restore-all]." ;; and return the results. (setq result (apply func args)) ;; we are recording times - (let (enter-time exit-time) + (let (enter-time) ;; increment the call-counter (cl-incf (aref info 0)) (setq enter-time (current-time) - result (apply func args) - exit-time (current-time)) + result (apply func args)) ;; calculate total time in function - (cl-incf (aref info 1) (elp-elapsed-time enter-time exit-time)) + (cl-incf (aref info 1) (elp-elapsed-time enter-time nil)) )) ;; turn off recording if this is the master function (if (and elp-master @@ -583,6 +582,11 @@ displayed." (elp-restore-all) ;; continue standard unloading nil) + +(cl-defmethod loadhist-unload-element :before :extra "elp" ((x (head defun))) + "Un-instrument before unloading a function." + (elp-restore-function (cdr x))) + (provide 'elp) diff --git a/lisp/emacs-lisp/ert-x.el b/lisp/emacs-lisp/ert-x.el index 4cf9d9609e9..71d46c11077 100644 --- a/lisp/emacs-lisp/ert-x.el +++ b/lisp/emacs-lisp/ert-x.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -286,27 +286,60 @@ BUFFER defaults to current buffer. Does not modify BUFFER." (defmacro ert-with-message-capture (var &rest body) - "Execute BODY while collecting anything written with `message' in VAR. + "Execute BODY while collecting messages in VAR. -Capture all messages produced by `message' when it is called from -Lisp, and concatenate them separated by newlines into one string. +Capture messages issued by Lisp code and concatenate them +separated by newlines into one string. This includes messages +written by `message' as well as objects printed by `print', +`prin1' and `princ' to the echo area. Messages issued from C +code using the above mentioned functions will not be captured. This is useful for separating the issuance of messages by the code under test from the behavior of the *Messages* buffer." (declare (debug (symbolp body)) (indent 1)) - (let ((g-advice (cl-gensym))) + (let ((g-message-advice (gensym)) + (g-print-advice (gensym)) + (g-collector (gensym))) `(let* ((,var "") - (,g-advice (lambda (func &rest args) - (if (or (null args) (equal (car args) "")) - (apply func args) - (let ((msg (apply #'format-message args))) - (setq ,var (concat ,var msg "\n")) - (funcall func "%s" msg)))))) - (advice-add 'message :around ,g-advice) + (,g-collector (lambda (msg) (setq ,var (concat ,var msg)))) + (,g-message-advice (ert--make-message-advice ,g-collector)) + (,g-print-advice (ert--make-print-advice ,g-collector))) + (advice-add 'message :around ,g-message-advice) + (advice-add 'prin1 :around ,g-print-advice) + (advice-add 'princ :around ,g-print-advice) + (advice-add 'print :around ,g-print-advice) (unwind-protect (progn ,@body) - (advice-remove 'message ,g-advice))))) + (advice-remove 'print ,g-print-advice) + (advice-remove 'princ ,g-print-advice) + (advice-remove 'prin1 ,g-print-advice) + (advice-remove 'message ,g-message-advice))))) + +(defun ert--make-message-advice (collector) + "Create around advice for `message' for `ert-collect-messages'. +COLLECTOR will be called with the message before it is passed +to the real `message'." + (lambda (func &rest args) + (if (or (null args) (equal (car args) "")) + (apply func args) + (let ((msg (apply #'format-message args))) + (funcall collector (concat msg "\n")) + (funcall func "%s" msg))))) + +(defun ert--make-print-advice (collector) + "Create around advice for print functions for `ert-collect-messages'. +The created advice function will just call the original function +unless the output is going to the echo area (when PRINTCHARFUN is +t or PRINTCHARFUN is nil and `standard-output' is t). If the +output is destined for the echo area, the advice function will +convert it to a string and pass it to COLLECTOR first." + (lambda (func object &optional printcharfun) + (if (not (eq t (or printcharfun standard-output))) + (funcall func object printcharfun) + (funcall collector (with-output-to-string + (funcall func object))) + (funcall func object printcharfun)))) (provide 'ert-x) diff --git a/lisp/emacs-lisp/ert.el b/lisp/emacs-lisp/ert.el index 2c49a634e35..1d69af80639 100644 --- a/lisp/emacs-lisp/ert.el +++ b/lisp/emacs-lisp/ert.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -73,6 +73,11 @@ :prefix "ert-" :group 'lisp) +(defcustom ert-batch-backtrace-right-margin 70 + "Maximum length of lines in ERT backtraces in batch mode. +Use nil for no limit (caution: backtrace lines can be very long)." + :type '(choice (const :tag "No truncation" nil) integer)) + (defface ert-test-result-expected '((((class color) (background light)) :background "green1") (((class color) (background dark)) @@ -97,7 +102,7 @@ This is like `equal-including-properties' except that it compares the property values of text properties structurally (by recursing) rather than with `eq'. Perhaps this is what `equal-including-properties' should do in the first place; see -Emacs bug 6581 at URL `http://debbugs.gnu.org/cgi/bugreport.cgi?bug=6581'." +Emacs bug 6581 at URL `https://debbugs.gnu.org/cgi/bugreport.cgi?bug=6581'." ;; This implementation is inefficient. Rather than making it ;; efficient, let's hope bug 6581 gets fixed so that we can delete ;; it altogether. @@ -135,7 +140,7 @@ Emacs bug 6581 at URL `http://debbugs.gnu.org/cgi/bugreport.cgi?bug=6581'." ;; Note that nil is still a valid value for the `name' slot in ;; ert-test objects. It designates an anonymous test. (error "Attempt to define a test named nil")) - (put symbol 'ert--test definition) + (define-symbol-prop symbol 'ert--test definition) definition) (defun ert-make-test-unbound (symbol) @@ -214,12 +219,6 @@ description of valid values for RESULT-TYPE. ,@(when tags-supplied-p `(:tags ,tags)) :body (lambda () ,@body))) - ;; This hack allows `symbol-file' to associate `ert-deftest' - ;; forms with files, and therefore enables `find-function' to - ;; work with tests. However, it leads to warnings in - ;; `unload-feature', which doesn't know how to undefine tests - ;; and has no mechanism for extension. - (push '(ert-deftest . ,name) current-load-list) ',name)))) ;; We use these `put' forms in addition to the (declare (indent)) in @@ -266,6 +265,14 @@ DATA is displayed to the user and should state the reason for skipping." (when ert--should-execution-observer (funcall ert--should-execution-observer form-description))) +;; See Bug#24402 for why this exists +(defun ert--should-signal-hook (error-symbol data) + "Stupid hack to stop `condition-case' from catching ert signals. +It should only be stopped when ran from inside ert--run-test-internal." + (when (and (not (symbolp debugger)) ; only run on anonymous debugger + (memq error-symbol '(ert-test-failed ert-test-skipped))) + (funcall debugger 'error data))) + (defun ert--special-operator-p (thing) "Return non-nil if THING is a symbol naming a special operator." (and (symbolp thing) @@ -273,20 +280,26 @@ DATA is displayed to the user and should state the reason for skipping." (and (subrp definition) (eql (cdr (subr-arity definition)) 'unevalled))))) +;; FIXME: Code inside of here should probably be evaluated like it is +;; outside of tests, with the sole exception of error handling (defun ert--expand-should-1 (whole form inner-expander) "Helper function for the `should' macro and its variants." (let ((form - (macroexpand form (append (bound-and-true-p - byte-compile-macro-environment) - (cond - ((boundp 'macroexpand-all-environment) - macroexpand-all-environment) - ((boundp 'cl-macro-environment) - cl-macro-environment)))))) + ;; catch macroexpansion errors + (condition-case err + (macroexpand-all form + (append (bound-and-true-p + byte-compile-macro-environment) + (cond + ((boundp 'macroexpand-all-environment) + macroexpand-all-environment) + ((boundp 'cl-macro-environment) + cl-macro-environment)))) + (error `(signal ',(car err) ',(cdr err)))))) (cond ((or (atom form) (ert--special-operator-p (car form))) - (let ((value (cl-gensym "value-"))) - `(let ((,value (cl-gensym "ert-form-evaluation-aborted-"))) + (let ((value (gensym "value-"))) + `(let ((,value (gensym "ert-form-evaluation-aborted-"))) ,(funcall inner-expander `(setq ,value ,form) `(list ',whole :form ',form :value ,value) @@ -299,12 +312,17 @@ DATA is displayed to the user and should state the reason for skipping." (and (consp fn-name) (eql (car fn-name) 'lambda) (listp (cdr fn-name))))) - (let ((fn (cl-gensym "fn-")) - (args (cl-gensym "args-")) - (value (cl-gensym "value-")) - (default-value (cl-gensym "ert-form-evaluation-aborted-"))) - `(let ((,fn (function ,fn-name)) - (,args (list ,@arg-forms))) + (let ((fn (gensym "fn-")) + (args (gensym "args-")) + (value (gensym "value-")) + (default-value (gensym "ert-form-evaluation-aborted-"))) + `(let* ((,fn (function ,fn-name)) + (,args (condition-case err + (let ((signal-hook-function #'ert--should-signal-hook)) + (list ,@arg-forms)) + (error (progn (setq ,fn #'signal) + (list (car err) + (cdr err))))))) (let ((,value ',default-value)) ,(funcall inner-expander `(setq ,value (apply ,fn ,args)) @@ -339,7 +357,7 @@ FORM-DESCRIPTION-FORM before it has called INNER-FORM." (ert--expand-should-1 whole form (lambda (inner-form form-description-form value-var) - (let ((form-description (cl-gensym "form-description-"))) + (let ((form-description (gensym "form-description-"))) `(let (,form-description) ,(funcall inner-expander `(unwind-protect @@ -417,8 +435,8 @@ failed." `(should-error ,form ,@keys) form (lambda (inner-form form-description-form value-var) - (let ((errorp (cl-gensym "errorp")) - (form-description-fn (cl-gensym "form-description-fn-"))) + (let ((errorp (gensym "errorp")) + (form-description-fn (gensym "form-description-fn-"))) `(let ((,errorp nil) (,form-description-fn (lambda () ,form-description-form))) (condition-case -condition- @@ -670,48 +688,12 @@ and is displayed in front of the value of MESSAGE-FORM." (cl-defstruct (ert-test-aborted-with-non-local-exit (:include ert-test-result))) - -(defun ert--record-backtrace () - "Record the current backtrace (as a list) and return it." - ;; Since the backtrace is stored in the result object, result - ;; objects must only be printed with appropriate limits - ;; (`print-level' and `print-length') in place. For interactive - ;; use, the cost of ensuring this possibly outweighs the advantage - ;; of storing the backtrace for - ;; `ert-results-pop-to-backtrace-for-test-at-point' given that we - ;; already have `ert-results-rerun-test-debugging-errors-at-point'. - ;; For batch use, however, printing the backtrace may be useful. - (cl-loop - ;; 6 is the number of frames our own debugger adds (when - ;; compiled; more when interpreted). FIXME: Need to describe a - ;; procedure for determining this constant. - for i from 6 - for frame = (backtrace-frame i) - while frame - collect frame)) - -(defun ert--print-backtrace (backtrace) +(defun ert--print-backtrace (backtrace do-xrefs) "Format the backtrace BACKTRACE to the current buffer." - ;; This is essentially a reimplementation of Fbacktrace - ;; (src/eval.c), but for a saved backtrace, not the current one. (let ((print-escape-newlines t) (print-level 8) (print-length 50)) - (dolist (frame backtrace) - (pcase-exhaustive frame - (`(nil ,special-operator . ,arg-forms) - ;; Special operator. - (insert - (format " %S\n" (cons special-operator arg-forms)))) - (`(t ,fn . ,args) - ;; Function call. - (insert (format " %S(" fn)) - (cl-loop for firstp = t then nil - for arg in args do - (unless firstp - (insert " ")) - (insert (format "%S" arg))) - (insert ")\n")))))) + (debugger-insert-backtrace backtrace do-xrefs))) ;; A container for the state of the execution of a single test and ;; environment data needed during its execution. @@ -750,7 +732,18 @@ run. ARGS are the arguments to `debugger'." ((quit) 'quit) ((ert-test-skipped) 'skipped) (otherwise 'failed))) - (backtrace (ert--record-backtrace)) + ;; We store the backtrace in the result object for + ;; `ert-results-pop-to-backtrace-for-test-at-point'. + ;; This means we have to limit `print-level' and + ;; `print-length' when printing result objects. That + ;; might not be worth while when we can also use + ;; `ert-results-rerun-test-debugging-errors-at-point', + ;; (i.e., when running interactively) but having the + ;; backtrace ready for printing is important for batch + ;; use. + ;; + ;; Grab the frames above the debugger. + (backtrace (cdr (backtrace-frames debugger))) (infos (reverse ert--infos))) (setf (ert--test-execution-info-result info) (cl-ecase type @@ -790,6 +783,10 @@ This mainly sets up debugger-related bindings." ;; too expensive, we can remove it. (with-temp-buffer (save-window-excursion + ;; FIXME: Use `signal-hook-function' instead of `debugger' to + ;; handle ert errors. Once that's done, remove + ;; `ert--should-signal-hook'. See Bug#24402 and Bug#11218 for + ;; details. (let ((debugger (lambda (&rest args) (ert--run-test-debugger test-execution-info args))) @@ -1336,8 +1333,8 @@ RESULT must be an `ert-test-result-with-condition'." ;;; Running tests in batch mode. -(defvar ert-batch-backtrace-right-margin 70 - "The maximum line length for printing backtraces in `ert-run-tests-batch'.") +(defvar ert-quiet nil + "Non-nil makes ERT only print important information in batch mode.") ;;;###autoload (defun ert-run-tests-batch (&optional selector) @@ -1355,10 +1352,11 @@ Returns the stats object." (lambda (event-type &rest event-args) (cl-ecase event-type (run-started - (cl-destructuring-bind (stats) event-args - (message "Running %s tests (%s)" - (length (ert--stats-tests stats)) - (ert--format-time-iso8601 (ert--stats-start-time stats))))) + (unless ert-quiet + (cl-destructuring-bind (stats) event-args + (message "Running %s tests (%s)" + (length (ert--stats-tests stats)) + (ert--format-time-iso8601 (ert--stats-start-time stats)))))) (run-ended (cl-destructuring-bind (stats abortedp) event-args (let ((unexpected (ert-stats-completed-unexpected stats)) @@ -1409,17 +1407,23 @@ Returns the stats object." (ert-test-result-with-condition (message "Test %S backtrace:" (ert-test-name test)) (with-temp-buffer - (ert--print-backtrace (ert-test-result-with-condition-backtrace - result)) - (goto-char (point-min)) - (while (not (eobp)) - (let ((start (point)) - (end (progn (end-of-line) (point)))) - (setq end (min end - (+ start ert-batch-backtrace-right-margin))) - (message "%s" (buffer-substring-no-properties - start end))) - (forward-line 1))) + (ert--print-backtrace + (ert-test-result-with-condition-backtrace result) + nil) + (if (not ert-batch-backtrace-right-margin) + (message "%s" + (buffer-substring-no-properties (point-min) + (point-max))) + (goto-char (point-min)) + (while (not (eobp)) + (let ((start (point)) + (end (line-end-position))) + (setq end (min end + (+ start + ert-batch-backtrace-right-margin))) + (message "%s" (buffer-substring-no-properties + start end))) + (forward-line 1)))) (with-temp-buffer (ert--insert-infos result) (insert " ") @@ -1438,16 +1442,17 @@ Returns the stats object." (ert-test-name test))) (ert-test-quit (message "Quit during %S" (ert-test-name test))))) - (let* ((max (prin1-to-string (length (ert--stats-tests stats)))) - (format-string (concat "%9s %" - (prin1-to-string (length max)) - "s/" max " %S"))) - (message format-string - (ert-string-for-test-result result - (ert-test-result-expected-p - test result)) - (1+ (ert--stats-test-pos stats test)) - (ert-test-name test))))))) + (unless ert-quiet + (let* ((max (prin1-to-string (length (ert--stats-tests stats)))) + (format-string (concat "%9s %" + (prin1-to-string (length max)) + "s/" max " %S"))) + (message format-string + (ert-string-for-test-result result + (ert-test-result-expected-p + test result)) + (1+ (ert--stats-test-pos stats test)) + (ert-test-name test)))))))) nil)) ;;;###autoload @@ -1491,7 +1496,7 @@ this exits Emacs, with status as per `ert-run-tests-batch-and-exit'." (with-temp-buffer (while (setq logfile (pop command-line-args-left)) (erase-buffer) - (insert-file-contents logfile) + (when (file-readable-p logfile) (insert-file-contents logfile)) (if (not (re-search-forward "^Running \\([0-9]+\\) tests" nil t)) (push logfile notests) (setq ntests (+ ntests (string-to-number (match-string 1)))) @@ -1535,7 +1540,7 @@ Ran \\([0-9]+\\) tests, \\([0-9]+\\) results as expected\ (message "%d files contained unexpected results:" (length unexpected)) (mapc (lambda (l) (message " %s" l)) unexpected)) ;; More details on hydra, where the logs are harder to get to. - (when (and (getenv "NIX_STORE") + (when (and (getenv "EMACS_HYDRA_CI") (not (zerop (+ nunexpected nskipped)))) (message "\nDETAILS") (message "-------") @@ -1625,7 +1630,7 @@ default (if any)." (defun ert-find-test-other-window (test-name) "Find, in another window, the definition of TEST-NAME." (interactive (list (ert-read-test-name-at-point "Find test definition: "))) - (find-function-do-it test-name 'ert-deftest 'switch-to-buffer-other-window)) + (find-function-do-it test-name 'ert--test 'switch-to-buffer-other-window)) (defun ert-delete-test (test-name) "Make the test TEST-NAME unbound. @@ -1828,12 +1833,23 @@ EWOC and STATS are arguments for `ert--results-update-stats-display'." BEGIN and END specify a region in the current buffer." (save-excursion - (save-restriction - (narrow-to-region begin end) - ;; Inhibit optimization in `debugger-make-xrefs' that would - ;; sometimes insert unrelated backtrace info into our buffer. - (let ((debugger-previous-backtrace nil)) - (debugger-make-xrefs))))) + (goto-char begin) + (while (progn + (goto-char (+ (point) 2)) + (skip-syntax-forward "^w_") + (< (point) end)) + (let* ((beg (point)) + (end (progn (skip-syntax-forward "w_") (point))) + (sym (intern-soft (buffer-substring-no-properties + beg end))) + (file (and sym (symbol-file sym 'defun)))) + (when file + (goto-char beg) + ;; help-xref-button needs to operate on something matched + ;; by a regexp, so set that up for it. + (re-search-forward "\\(\\sw\\|\\s_\\)+") + (help-xref-button 0 'help-function-def sym file))) + (forward-line 1)))) (defun ert--string-first-line (s) "Return the first line of S, or S if it contains no newlines. @@ -2417,11 +2433,9 @@ To be used in the ERT results buffer." (buffer-disable-undo) (erase-buffer) (ert-simple-view-mode) - ;; Use unibyte because `debugger-setup-buffer' also does so. - (set-buffer-multibyte nil) + (set-buffer-multibyte t) ; mimic debugger-setup-buffer (setq truncate-lines t) - (ert--print-backtrace backtrace) - (debugger-make-xrefs) + (ert--print-backtrace backtrace t) (goto-char (point-min)) (insert (substitute-command-keys "Backtrace for test `")) (ert-insert-test-name-button (ert-test-name test)) @@ -2552,7 +2566,7 @@ To be used in the ERT results buffer." (insert (if test-name (format "%S" test-name) "<anonymous test>")) (insert " is a test") (let ((file-name (and test-name - (symbol-file test-name 'ert-deftest)))) + (symbol-file test-name 'ert--test)))) (when file-name (insert (format-message " defined in `%s'" (file-name-nondirectory file-name))) @@ -2585,7 +2599,7 @@ To be used in the ERT results buffer." ;;; Actions on load/unload. -(add-to-list 'find-function-regexp-alist '(ert-deftest . ert--find-test-regexp)) +(add-to-list 'find-function-regexp-alist '(ert--test . ert--find-test-regexp)) (add-to-list 'minor-mode-alist '(ert--current-run-stats (:eval (ert--tests-running-mode-line-indicator)))) diff --git a/lisp/emacs-lisp/ewoc.el b/lisp/emacs-lisp/ewoc.el index cc574568d50..e1b94a3ec90 100644 --- a/lisp/emacs-lisp/ewoc.el +++ b/lisp/emacs-lisp/ewoc.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emacs-lisp/faceup.el b/lisp/emacs-lisp/faceup.el new file mode 100644 index 00000000000..8d2818fbab8 --- /dev/null +++ b/lisp/emacs-lisp/faceup.el @@ -0,0 +1,1180 @@ +;;; faceup.el --- Markup language for faces and font-lock regression testing -*- lexical-binding: t -*- + +;; Copyright (C) 2013-2017 Free Software Foundation, Inc. + +;; Author: Anders Lindgren +;; Version: 0.0.6 +;; Created: 2013-01-21 +;; Keywords: faces languages +;; URL: https://github.com/Lindydancer/faceup + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Emacs is capable of highlighting buffers based on language-specific +;; `font-lock' rules. This package makes it possible to perform +;; regression test for packages that provide font-lock rules. +;; +;; The underlying idea is to convert text with highlights ("faces") +;; into a plain text representation using the Faceup markup +;; language. This language is semi-human readable, for example: +;; +;; «k:this» is a keyword +;; +;; By comparing the current highlight with a highlight performed with +;; stable versions of a package, it's possible to automatically find +;; problems that otherwise would have been hard to spot. +;; +;; This package is designed to be used in conjunction with Ert, the +;; standard Emacs regression test system. +;; +;; The Faceup markup language is a generic markup language, regression +;; testing is merely one way to use it. + +;; Regression test examples: +;; +;; This section describes the two typical ways regression testing with +;; this package is performed. +;; +;; +;; Full source file highlighting: +;; +;; The most straight-forward way to perform regression testing is to +;; collect a number of representative source files. From each source +;; file, say `alpha.mylang', you can use `M-x faceup-write-file RET' +;; to generate a Faceup file named `alpha.mylang.faceup', this file +;; use the Faceup markup language to represent the text with +;; highlights and is used as a reference in future tests. +;; +;; An Ert test case can be defined as follows: +;; +;; (require 'faceup) +;; +;; (defvar mylang-font-lock-test-dir (faceup-this-file-directory)) +;; +;; (defun mylang-font-lock-test-apps (file) +;; "Test that the mylang FILE is fontifies as the .faceup file describes." +;; (faceup-test-font-lock-file 'mylang-mode +;; (concat mylang-font-lock-test-dir file))) +;; (faceup-defexplainer mylang-font-lock-test-apps) +;; +;; (ert-deftest mylang-font-lock-file-test () +;; (should (mylang-font-lock-test-apps "apps/FirstApp/alpha.mylang")) +;; ;; ... Add more test files here ... +;; ) +;; +;; To execute the tests, run something like `M-x ert RET t RET'. +;; +;; +;; Source snippets: +;; +;; To test smaller snippets of code, you can use the +;; `faceup-test-font-lock-string'. It takes a major mode and a string +;; written using the Faceup markup language. The functions strips away +;; the Faceup markup, inserts the plain text into a temporary buffer, +;; highlights it, converts the result back into the Faceup markup +;; language, and finally compares the result with the original Faceup +;; string. +;; +;; For example: +;; +;; (defun mylang-font-lock-test (faceup) +;; (faceup-test-font-lock-string 'mylang-mode faceup)) +;; (faceup-defexplainer mylang-font-lock-test) +;; +;; (ert-deftest mylang-font-lock-test-simple () +;; "Simple MyLang font-lock tests." +;; (should (mylang-font-lock-test "«k:this» is a keyword")) +;; (should (mylang-font-lock-test "«k:function» «f:myfunc» («v:var»)"))) +;; + +;; Executing the tests: +;; +;; Once the tests have been defined, you can use `M-x ert RET t RET' +;; to execute them. Hopefully, you will be given the "all clear". +;; However, if there is a problem, you will be presented with +;; something like: +;; +;; F mylang-font-lock-file-test +;; (ert-test-failed +;; ((should +;; (mylang-font-lock-test-apps "apps/FirstApp/alpha.mylang")) +;; :form +;; (mylang-font-lock-test-apps "apps/FirstApp/alpha.mylang") +;; :value nil :explanation +;; ((on-line 2 +;; ("but_«k:this»_is_not_a_keyword") +;; ("but_this_is_not_a_keyword"))))) +;; +;; You should read this that on line 2, the old font-lock rules +;; highlighted `this' inside `but_this_is_not_a_keyword' (which is +;; clearly wrong), whereas the new doesn't. Of course, if this is the +;; desired result (for example, the result of a recent change) you can +;; simply regenerate the .faceup file and store it as the reference +;; file for the future. + +;; The Faceup markup language: +;; +;; The Faceup markup language is designed to be human-readable and +;; minimalistic. +;; +;; The two special characters `«' and `»' marks the start and end of a +;; range of a face. +;; +;; +;; Compact format for special faces: +;; +;; The compact format `«<LETTER>:text»' is used for a number of common +;; faces. For example, `«U:abc»' means that the text `abc' is +;; underlined. +;; +;; See `faceup-face-short-alist' for the known faces and the +;; corresponding letter. +;; +;; +;; Full format: +;; +;; The format `«:<NAME OF FACE>:text»' is used use to encode other +;; faces. +;; +;; For example `«:my-special-face:abc»' meanst that `abc' has the face +;; `my-special-face'. +;; +;; +;; Anonymous faces: +;; +;; An "anonymous face" is when the `face' property contains a property +;; list (plist) on the form `(:key value)'. This is represented using +;; a variant of the full format: `«:(:key value):text»'. +;; +;; For example, `«:(:background "red"):abc»' represent the text `abc' +;; with a red background. +;; +;; +;; Multiple properties: +;; +;; In case a text contains more than one face property, they are +;; represented using nested sections. +;; +;; For example: +;; +;; * `«B:abc«U:def»»' represent the text `abcdef' that is both *bold* +;; and *underlined*. +;; +;; * `«W:abc«U:def»ghi»' represent the text `abcdefghi' where the +;; entire text is in *warning* face and `def' is *underlined*. +;; +;; In case two faces partially overlap, the ranges will be split when +;; represented in Faceup. For example: +;; +;; * `«B:abc«U:def»»«U:ghi»' represent the text `abcdefghi' where +;; `abcdef' is bold and `defghi' is underlined. +;; +;; +;; Escaping start and end markers: +;; +;; Any occurrence of the start or end markers in the original text +;; will be escaped using the start marker in the Faceup +;; representation. In other words, the sequences `««' and `«»' +;; represent a start and end marker, respectively. +;; +;; +;; Other properties: +;; +;; In addition to representing the `face' property (or, more +;; correctly, the value of `faceup-default-property') other properties +;; can be encoded. The variable `faceup-properties' contains a list of +;; properties to track. If a property behaves like the `face' +;; property, it is encoded as described above, with the addition of +;; the property name placed in parentheses, for example: +;; `«(my-face)U:abd»'. +;; +;; The variable `faceup-face-like-properties' contains a list of +;; properties considered face-like. +;; +;; Properties that are not considered face-like are always encoded +;; using the full format and the don't nest. For example: +;; `«(my-fibonacci-property):(1 1 2 3 5 8):abd»'. +;; +;; Examples of properties that could be tracked are: +;; +;; * `font-lock-face' -- an alias to `face' when `font-lock-mode' is +;; enabled. +;; +;; * `syntax-table' -- used by a custom `syntax-propertize' to +;; override the default syntax table. +;; +;; * `help-echo' -- provides tooltip text displayed when the mouse is +;; held over a text. + +;; Reference section: +;; +;; Faceup commands and functions: +;; +;; `M-x faceup-write-file RET' - generate a Faceup file based on the +;; current buffer. +;; +;; `M-x faceup-view-file RET' - view the current buffer converted to +;; Faceup. +;; +;; `faceup-markup-{string,buffer}' - convert text with properties to +;; the Faceup markup language. +;; +;; `faceup-render-view-buffer' - convert buffer with Faceup markup to +;; a buffer with real text properties and display it. +;; +;; `faceup-render-string' - return string with real text properties +;; from a string with Faceup markup. +;; +;; `faceup-render-to-{buffer,string}' - convert buffer with Faceup +;; markup to a buffer/string with real text properties. +;; +;; `faceup-clean-{buffer,string}' - remove Faceup markup from buffer +;; or string. +;; +;; +;; Regression test support: +;; +;; The following functions can be used as Ert test functions, or can +;; be used to implement new Ert test functions. +;; +;; `faceup-test-equal' - Test function, work like Ert:s `equal', but +;; more ergonomically when reporting multi-line string errors. +;; Concretely, it breaks down multi-line strings into lines and +;; reports which line number the error occurred on and the content of +;; that line. +;; +;; `faceup-test-font-lock-buffer' - Test that a buffer is highlighted +;; according to a reference Faceup text, for a specific major mode. +;; +;; `faceup-test-font-lock-string' - Test that a text with Faceup +;; markup is refontified to match the original Faceup markup. +;; +;; `faceup-test-font-lock-file' - Test that a file is highlighted +;; according to a reference .faceup file. +;; +;; `faceup-defexplainer' - Macro, define an explainer function and set +;; the `ert-explainer' property on the original function, for +;; functions based on the above test functions. +;; +;; `faceup-this-file-directory' - Macro, the directory of the current +;; file. + +;; Real-world examples: +;; +;; The following are examples of real-world package that use faceup to +;; test their font-lock keywords. +;; +;; * [cmake-font-lock](https://github.com/Lindydancer/cmake-font-lock) +;; an advanced set of font-lock keywords for the CMake language +;; +;; * [objc-font-lock](https://github.com/Lindydancer/objc-font-lock) +;; highlight Objective-C function calls. +;; + +;; Other Font Lock Tools: +;; +;; This package is part of a suite of font-lock tools. The other +;; tools in the suite are: +;; +;; +;; Font Lock Studio: +;; +;; Interactive debugger for font-lock keywords (Emacs syntax +;; highlighting rules). +;; +;; Font Lock Studio lets you *single-step* Font Lock keywords -- +;; matchers, highlights, and anchored rules, so that you can see what +;; happens when a buffer is fontified. You can set *breakpoints* on +;; or inside rules and *run* until one has been hit. When inside a +;; rule, matches are *visualized* using a palette of background +;; colors. The *explainer* can describe a rule in plain-text English. +;; Tight integration with *Edebug* allows you to step into Lisp +;; expressions that are part of the Font Lock keywords. +;; +;; +;; Font Lock Profiler: +;; +;; A profiler for font-lock keywords. This package measures time and +;; counts the number of times each part of a font-lock keyword is +;; used. For matchers, it counts the total number and the number of +;; successful matches. +;; +;; The result is presented in table that can be sorted by count or +;; time. The table can be expanded to include each part of the +;; font-lock keyword. +;; +;; In addition, this package can generate a log of all font-lock +;; events. This can be used to verify font-lock implementations, +;; concretely, this is used for back-to-back tests of the real +;; font-lock engine and Font Lock Studio, an interactive debugger for +;; font-lock keywords. +;; +;; +;; Highlight Refontification: +;; +;; Minor mode that visualizes how font-lock refontifies a buffer. +;; This is useful when developing or debugging font-lock keywords, +;; especially for keywords that span multiple lines. +;; +;; The background of the buffer is painted in a rainbow of colors, +;; where each band in the rainbow represent a region of the buffer +;; that has been refontified. When the buffer is modified, the +;; rainbow is updated. +;; +;; +;; Face Explorer: +;; +;; Library and tools for faces and text properties. +;; +;; This library is useful for packages that convert syntax highlighted +;; buffers to other formats. The functions can be used to determine +;; how a face or a face text property looks, in terms of primitive +;; face attributes (e.g. foreground and background colors). Two sets +;; of functions are provided, one for existing frames and one for +;; fictitious displays, like 8 color tty. +;; +;; In addition, the following tools are provided: +;; +;; - `face-explorer-list-faces' -- list all available faces. Like +;; `list-faces-display' but with information on how a face is +;; defined. In addition, a sample for the selected frame and for a +;; fictitious display is shown. +;; +;; - `face-explorer-describe-face' -- Print detailed information on +;; how a face is defined, and list all underlying definitions. +;; +;; - `face-explorer-describe-face-prop' -- Describe the `face' text +;; property at the point in terms of primitive face attributes. +;; Also show how it would look on a fictitious display. +;; +;; - `face-explorer-list-display-features' -- Show which features a +;; display supports. Most graphical displays support all, or most, +;; features. However, many tty:s don't support, for example, +;; strike-through. Using specially constructed faces, the resulting +;; buffer will render differently in different displays, e.g. a +;; graphical frame and a tty connected using `emacsclient -nw'. +;; +;; - `face-explorer-list-face-prop-examples' -- Show a buffer with an +;; assortment of `face' text properties. A sample text is shown in +;; four variants: Native, a manually maintained reference vector, +;; the result of `face-explorer-face-prop-attributes' and +;; `face-explorer-face-prop-attributes-for-fictitious-display'. Any +;; package that convert a buffer to another format (like HTML, ANSI, +;; or LaTeX) could use this buffer to ensure that everything work as +;; intended. +;; +;; - `face-explorer-list-overlay-examples' -- Show a buffer with a +;; number of examples of overlays, some are mixed with `face' text +;; properties. Any package that convert a buffer to another format +;; (like HTML, ANSI, or LaTeX) could use this buffer to ensure that +;; everything work as intended. +;; +;; - `face-explorer-tooltip-mode' -- Minor mode that shows tooltips +;; containing text properties and overlays at the mouse pointer. +;; +;; - `face-explorer-simulate-display-mode' -- Minor mode for make a +;; buffer look like it would on a fictitious display. Using this +;; you can, for example, see how a theme would look in using dark or +;; light background, a 8 color tty, or on a grayscale graphical +;; monitor. +;; +;; +;; Font Lock Regression Suite: +;; +;; A collection of example source files for a large number of +;; programming languages, with ERT tests to ensure that syntax +;; highlighting does not accidentally change. +;; +;; For each source file, font-lock reference files are provided for +;; various Emacs versions. The reference files contains a plain-text +;; representation of source file with syntax highlighting, using the +;; format "faceup". +;; +;; Of course, the collection source file can be used for other kinds +;; of testing, not limited to font-lock regression testing. + +;;; Code: + + +(defvar faceup-default-property 'face + "The property that should be represented in Faceup without the (prop) part.") + +(defvar faceup-properties '(face) + "List of properties that should be converted to the Faceup format. + +Only face-like property use the short format. All other use the +non-nesting full format. (See `faceup-face-like-properties'.)" ) + + +(defvar faceup-face-like-properties '(face font-lock-face) + "List of properties that behave like `face'. + +The following properties are assumed about face-like properties: + +* Elements are either symbols or property lists, or lists thereof. + +* A plain element and a list containing the same element are + treated as equal + +* Property lists and sequences of property lists are considered + equal. For example: + + ((:underline t :foreground \"red\")) + + and + + ((:underline t) (:foreground \"red\")) + +Face-like properties are converted to faceup in a nesting fashion. + +For example, the string AAAXXXAAA (where the property `prop' has +the value `(a)' on the A:s and `(a b)' on the X:s) is converted +as follows, when treated as a face-like property: + + «(prop):a:AAA«(prop):b:XXX»AAAA» + +When treated as a non-face-like property: + + «(prop):(a):AAA»«(prop):(a b):XXX»«(prop):(a):AAA»") + + +(defvar faceup-markup-start-char ?«) +(defvar faceup-markup-end-char ?») + +(defvar faceup-face-short-alist + '(;; Generic faces (uppercase letters) + (bold . "B") + (bold-italic . "Q") + (default . "D") + (error . "E") + (highlight . "H") + (italic . "I") + (underline . "U") + (warning . "W") + ;; font-lock-specific faces (lowercase letters) + (font-lock-builtin-face . "b") + (font-lock-comment-delimiter-face . "m") + (font-lock-comment-face . "x") + (font-lock-constant-face . "c") + (font-lock-doc-face . "d") + (font-lock-function-name-face . "f") + (font-lock-keyword-face . "k") + (font-lock-negation-char-face . "n") + (font-lock-preprocessor-face . "p") + (font-lock-regexp-grouping-backslash . "h") + (font-lock-regexp-grouping-construct . "o") + (font-lock-string-face . "s") + (font-lock-type-face . "t") + (font-lock-variable-name-face . "v") + (font-lock-warning-face . "w")) + "Alist from faces to one-character representation.") + + +;; Plain: «W....» +;; Nested: «W...«W...»» + +;; Overlapping: xxxxxxxxxx +;; yyyyyyyyyyyy +;; «X..«Y..»»«Y...» + + +(defun faceup-markup-string (s) + "Return the faceup version of the string S." + (with-temp-buffer + (insert s) + (faceup-markup-buffer))) + + +;;;###autoload +(defun faceup-view-buffer () + "Display the faceup representation of the current buffer." + (interactive) + (let ((buffer (get-buffer-create "*FaceUp*"))) + (with-current-buffer buffer + (delete-region (point-min) (point-max))) + (faceup-markup-to-buffer buffer) + (display-buffer buffer))) + + +;;;###autoload +(defun faceup-write-file (&optional file-name confirm) + "Save the faceup representation of the current buffer to the file FILE-NAME. + +Unless a name is given, the file will be named xxx.faceup, where +xxx is the file name associated with the buffer. + +If optional second arg CONFIRM is non-nil, this function +asks for confirmation before overwriting an existing file. +Interactively, confirmation is required unless you supply a prefix argument." + (interactive + (let ((suggested-name (and (buffer-file-name) + (concat (buffer-file-name) + ".faceup")))) + (list (read-file-name "Write faceup file: " + default-directory + suggested-name + nil + (file-name-nondirectory suggested-name)) + (not current-prefix-arg)))) + (unless file-name + (setq file-name (concat (buffer-file-name) ".faceup"))) + (let ((buffer (current-buffer))) + (with-temp-buffer + (faceup-markup-to-buffer (current-buffer) buffer) + ;; Note: Must set `require-final-newline' inside + ;; `with-temp-buffer', otherwise the value will be overridden by + ;; the buffers local value. + ;; + ;; Clear `window-size-change-functions' as a workaround for + ;; Emacs bug#19576 (`write-file' saves the wrong buffer if a + ;; function in the list change current buffer). + (let ((require-final-newline nil) + (window-size-change-functions '())) + (write-file file-name confirm))))) + + +(defun faceup-markup-buffer () + "Return a string with the content of the buffer using faceup markup." + (let ((buf (current-buffer))) + (with-temp-buffer + (faceup-markup-to-buffer (current-buffer) buf) + (buffer-substring-no-properties (point-min) (point-max))))) + + +;; Idea: +;; +;; Typically, only one face is used. However, when two faces are used, +;; the one of top is typically shorter. Hence, the faceup variant +;; should treat the inner group of nested ranges the upper (i.e. the +;; one towards the front.) For example: +;; +;; «f:aaaaaaa«U:xxxx»aaaaaa» + +(defun faceup-copy-and-quote (start end to-buffer) + "Quote and insert the text between START and END into TO-BUFFER." + (let ((not-markup (concat "^" + (make-string 1 faceup-markup-start-char) + (make-string 1 faceup-markup-end-char)))) + (save-excursion + (goto-char start) + (while (< (point) end) + (let ((old (point))) + (skip-chars-forward not-markup end) + (let ((s (buffer-substring-no-properties old (point)))) + (with-current-buffer to-buffer + (insert s)))) + ;; Quote stray markup characters. + (unless (= (point) end) + (let ((next-char (following-char))) + (with-current-buffer to-buffer + (insert faceup-markup-start-char) + (insert next-char))) + (forward-char)))))) + + +;; A face (string or symbol) can be on the top level. +;; +;; A face text property can be a arbitrary deep lisp structure. Each +;; list in the tree structure contains faces (symbols or strings) up +;; to the first keyword, e.g. :foreground, thereafter the list is +;; considered a property list, regardless of the content. A special +;; case are `(foreground-color . COLOR)' and `(background-color +;; . COLOR)', old forms used to represent the foreground and +;; background colors, respectively. +;; +;; Some of this is undocumented, and took some effort to reverse +;; engineer. +(defun faceup-normalize-face-property (value) + "Normalize VALUES into a list of faces and (KEY VALUE) entries." + (cond ((null value) + '()) + ((symbolp value) + (list value)) + ((stringp value) + (list (intern value))) + ((consp value) + (cond ((eq (car value) 'foreground-color) + (list (list :foreground (cdr value)))) + ((eq (car value) 'background-color) + (list (list :background (cdr value)))) + (t + ;; A list + (if (keywordp (car value)) + ;; Once a keyword has been seen, the rest of the + ;; list is treated as a property list, regardless + ;; of what it contains. + (let ((res '())) + (while value + (let ((key (pop value)) + (val (pop value))) + (when (keywordp key) + (push (list key val) res)))) + res) + (append + (faceup-normalize-face-property (car value)) + (faceup-normalize-face-property (cdr value))))))) + (t + (error "Unexpected text property %s" value)))) + + +(defun faceup-get-text-properties (pos) + "Alist of properties and values at POS. + +Face-like properties are normalized -- value is a list of +faces (symbols) and short (KEY VALUE) lists. The list is +reversed to that later elements take precedence over earlier." + (let ((res '())) + (dolist (prop faceup-properties) + (let ((value (get-text-property pos prop))) + (when value + (when (memq prop faceup-face-like-properties) + ;; Normalize face-like properties. + (setq value (reverse (faceup-normalize-face-property value)))) + (push (cons prop value) res)))) + res)) + + +(defun faceup-markup-to-buffer (to-buffer &optional buffer) + "Convert content of BUFFER to faceup form and insert in TO-BUFFER." + (save-excursion + (if buffer + (set-buffer buffer)) + ;; Font-lock often only fontifies the visible sections. This + ;; ensures that the entire buffer is fontified before converting + ;; it. + (if (and font-lock-mode + ;; Prevent clearing out face attributes explicitly + ;; inserted by functions like `list-faces-display'. + ;; (Font-lock mode is enabled, for some reason, in those + ;; buffers.) + (not (and (eq major-mode 'help-mode) + (not font-lock-defaults)))) + (font-lock-fontify-region (point-min) (point-max))) + (let ((last-pos (point-min)) + (pos nil) + ;; List of (prop . value), representing open faceup blocks. + (state '())) + (while (setq pos (faceup-next-property-change pos)) + ;; Insert content. + (faceup-copy-and-quote last-pos pos to-buffer) + (setq last-pos pos) + (let ((prop-values (faceup-get-text-properties pos))) + (let ((next-state '())) + (setq state (reverse state)) + ;; Find all existing sequences that should continue. + (let ((cont t)) + (while (and state + prop-values + cont) + (let* ((prop (car (car state))) + (value (cdr (car state))) + (pair (assq prop prop-values))) + (if (memq prop faceup-face-like-properties) + ;; Element by element. + (if (equal value (car (cdr pair))) + (setcdr pair (cdr (cdr pair))) + (setq cont nil)) + ;; Full value. + ;; + ;; Note: Comparison is done by `eq', since (at + ;; least) the `display' property treats + ;; eq-identical values differently than when + ;; comparing using `equal'. See "Display Specs + ;; That Replace The Text" in the elisp manual. + (if (eq value (cdr pair)) + (setq prop-values (delq pair prop-values)) + (setq cont nil)))) + (when cont + (push (pop state) next-state)))) + ;; End values that should not be included in the next state. + (while state + (with-current-buffer to-buffer + (insert (make-string 1 faceup-markup-end-char))) + (pop state)) + ;; Start new ranges. + (with-current-buffer to-buffer + (while prop-values + (let ((pair (pop prop-values))) + (if (memq (car pair) faceup-face-like-properties) + ;; Face-like. + (dolist (element (cdr pair)) + (insert (make-string 1 faceup-markup-start-char)) + (unless (eq (car pair) faceup-default-property) + (insert "(") + (insert (symbol-name (car pair))) + (insert "):")) + (if (symbolp element) + (let ((short + (assq element faceup-face-short-alist))) + (if short + (insert (cdr short) ":") + (insert ":" (symbol-name element) ":"))) + (insert ":") + (prin1 element (current-buffer)) + (insert ":")) + (push (cons (car pair) element) next-state)) + ;; Not face-like. + (insert (make-string 1 faceup-markup-start-char)) + (insert "(") + (insert (symbol-name (car pair))) + (insert "):") + (prin1 (cdr pair) (current-buffer)) + (insert ":") + (push pair next-state))))) + ;; Insert content. + (setq state next-state)))) + ;; Insert whatever is left after the last face change. + (faceup-copy-and-quote last-pos (point-max) to-buffer)))) + + + +;; Some basic facts: +;; +;; (get-text-property (point-max) ...) always return nil. To check the +;; last character in the buffer, use (- (point-max) 1). +;; +;; If a text has more than one face, the first one in the list +;; takes precedence, when being viewed in Emacs. +;; +;; (let ((s "ABCDEF")) +;; (set-text-properties 1 4 +;; '(face (font-lock-warning-face font-lock-variable-name-face)) s) +;; (insert s)) +;; +;; => ABCDEF +;; +;; Where DEF is drawn in "warning" face. + + +(defun faceup-has-any-text-property (pos) + "True if any properties in `faceup-properties' are defined at POS." + (let ((res nil)) + (dolist (prop faceup-properties) + (when (get-text-property pos prop) + (setq res t))) + res)) + + +(defun faceup-next-single-property-change (pos) + "Next position a property in `faceup-properties' changes after POS, or nil." + (let ((res nil)) + (dolist (prop faceup-properties) + (let ((next (next-single-property-change pos prop))) + (when next + (setq res (if res + (min res next) + next))))) + res)) + + +(defun faceup-next-property-change (pos) + "Next position after POS where one of the tracked properties change. + +If POS is nil, also include `point-min' in the search. +If last character contains a tracked property, return `point-max'. + +See `faceup-properties' for a list of tracked properties." + (if (eq pos (point-max)) + ;; Last search returned `point-max'. There is no more to search + ;; for. + nil + (if (and (null pos) + (faceup-has-any-text-property (point-min))) + ;; `pos' is `nil' and the character at `point-min' contains a + ;; tracked property, return `point-min'. + (point-min) + (unless pos + ;; Start from the beginning. + (setq pos (point-min))) + ;; Do a normal search. Compensate for that + ;; `next-single-property-change' does not include the end of the + ;; buffer, even when a property reach it. + (let ((res (faceup-next-single-property-change pos))) + (if (and (not res) ; No more found. + (not (eq pos (point-max))) ; Not already at the end. + (not (eq (point-min) (point-max))) ; Not an empty buffer. + (faceup-has-any-text-property (- (point-max) 1))) + ;; If a property goes all the way to the end of the + ;; buffer, return `point-max'. + (point-max) + res))))) + + +;; ---------------------------------------------------------------------- +;; Renderer +;; + +;; Functions to convert from the faceup textual representation to text +;; with real properties. + +(defun faceup-render-string (faceup) + "Return string with properties from FACEUP written with Faceup markup." + (with-temp-buffer + (insert faceup) + (faceup-render-to-string))) + + +;;;###autoload +(defun faceup-render-view-buffer (&optional buffer) + "Convert BUFFER containing Faceup markup to a new buffer and display it." + (interactive) + (with-current-buffer (or buffer (current-buffer)) + (let ((dest-buffer (get-buffer-create "*FaceUp rendering*"))) + (with-current-buffer dest-buffer + (delete-region (point-min) (point-max))) + (faceup-render-to-buffer dest-buffer) + (display-buffer dest-buffer)))) + + +(defun faceup-render-to-string (&optional buffer) + "Convert BUFFER containing faceup markup to a string with faces." + (unless buffer + (setq buffer (current-buffer))) + (with-temp-buffer + (faceup-render-to-buffer (current-buffer) buffer) + (buffer-substring (point-min) (point-max)))) + + +(defun faceup-render-to-buffer (to-buffer &optional buffer) + "Convert BUFFER containing faceup markup into text with faces in TO-BUFFER." + (with-current-buffer (or buffer (current-buffer)) + (goto-char (point-min)) + (let ((last-point (point)) + (state '()) ; List of (prop . element) + (not-markup (concat + "^" + (make-string 1 faceup-markup-start-char) + (make-string 1 faceup-markup-end-char)))) + (while (progn + (skip-chars-forward not-markup) + (if (not (eq last-point (point))) + (let ((text (buffer-substring-no-properties + last-point (point))) + (prop-elements-alist '())) + ;; Accumulate all values for each property. + (dolist (prop-element state) + (let ((property (car prop-element)) + (element (cdr prop-element))) + (let ((pair (assq property prop-elements-alist))) + (unless pair + (setq pair (cons property '())) + (push pair prop-elements-alist)) + (push element (cdr pair))))) + ;; Apply all properties. + (dolist (pair prop-elements-alist) + (let ((property (car pair)) + (elements (reverse (cdr pair)))) + ;; Create one of: + ;; (property element) or + ;; (property (element element ...)) + (when (eq (length elements) 1) + ;; This ensures that non-face-like + ;; properties are restored to their + ;; original state. + (setq elements (car elements))) + (add-text-properties 0 (length text) + (list property elements) + text))) + (with-current-buffer to-buffer + (insert text)) + (setq last-point (point)))) + (not (eobp))) + (if (eq (following-char) faceup-markup-start-char) + ;; Start marker. + (progn + (forward-char) + (if (or (eq (following-char) faceup-markup-start-char) + (eq (following-char) faceup-markup-end-char)) + ;; Escaped markup character. + (progn + (setq last-point (point)) + (forward-char)) + ;; Markup sequence. + (let ((property faceup-default-property)) + (when (eq (following-char) ?\( ) + (forward-char) ; "(" + (let ((p (point))) + (forward-sexp) + (setq property (intern (buffer-substring p (point))))) + (forward-char)) ; ")" + (let ((element + (if (eq (following-char) ?:) + ;; :element: + (progn + (forward-char) + (prog1 + (let ((p (point))) + (forward-sexp) + ;; Note: (read (current-buffer)) + ;; doesn't work, as it reads more + ;; than a sexp. + (read (buffer-substring p (point)))) + (forward-char))) + ;; X: + (prog1 + (car (rassoc (buffer-substring-no-properties + (point) (+ (point) 1)) + faceup-face-short-alist)) + (forward-char 2))))) + (push (cons property element) state))) + (setq last-point (point)))) + ;; End marker. + (pop state) + (forward-char) + (setq last-point (point))))))) + +;; ---------------------------------------------------------------------- + +;;;###autoload +(defun faceup-clean-buffer () + "Remove faceup markup from buffer." + (interactive) + (goto-char (point-min)) + (let ((not-markup (concat + "^" + (make-string 1 faceup-markup-start-char) + (make-string 1 faceup-markup-end-char)))) + (while (progn (skip-chars-forward not-markup) + (not (eobp))) + (if (eq (following-char) faceup-markup-end-char) + ;; End markers are always on their own. + (delete-char 1) + ;; Start marker. + (delete-char 1) + (if (or (eq (following-char) faceup-markup-start-char) + (eq (following-char) faceup-markup-end-char)) + ;; Escaped markup character, delete the escape and skip + ;; the original character. + (forward-char) + ;; Property name (if present) + (if (eq (following-char) ?\( ) + (let ((p (point))) + (forward-sexp) + (delete-region p (point)))) + ;; Markup sequence. + (if (eq (following-char) ?:) + ;; :value: + (let ((p (point))) + (forward-char) + (forward-sexp) + (unless (eobp) + (forward-char)) + (delete-region p (point))) + ;; X: + (delete-char 1) ; The one-letter form. + (delete-char 1))))))) ; The colon. + + +(defun faceup-clean-string (s) + "Remove faceup markup from string S." + (with-temp-buffer + (insert s) + (faceup-clean-buffer) + (buffer-substring (point-min) (point-max)))) + + +;; ---------------------------------------------------------------------- +;; Regression test support +;; + +(defvar faceup-test-explain nil + "When non-nil, tester functions returns a text description on failure. + +Of course, this only work for test functions aware of this +variable, like `faceup-test-equal' and functions based on this +function. + +This is intended to be used to simplify `ert' explain functions, +which could be defined as: + + (defun my-test (args...) ...) + (defun my-test-explain (args...) + (let ((faceup-test-explain t)) + (the-test args...))) + (put 'my-test 'ert-explainer 'my-test-explain) + +Alternative, you can use the macro `faceup-defexplainer' as follows: + + (defun my-test (args...) ...) + (faceup-defexplainer my-test) + +Test functions, like `faceup-test-font-lock-buffer', built on top +of `faceup-test-equal', and other functions that adhere to this +variable, can easily define their own explainer functions.") + +;;;###autoload +(defmacro faceup-defexplainer (function) + "Define an Ert explainer function for FUNCTION. + +FUNCTION must return an explanation when the test fails and +`faceup-test-explain' is set." + (let ((name (intern (concat (symbol-name function) "-explainer")))) + `(progn + (defun ,name (&rest args) + (let ((faceup-test-explain t)) + (apply (quote ,function) args))) + (put (quote ,function) 'ert-explainer (quote ,name))))) + + +;; ------------------------------ +;; Multi-line string support. +;; + +(defun faceup-test-equal (lhs rhs) + "Compares two (multi-line) strings, LHS and RHS, for equality. + +This is intended to be used in Ert regression test rules. + +When `faceup-test-explain' is non-nil, instead of returning nil +on inequality, a list is returned with a explanation what +differs. Currently, this function reports 1) if the number of +lines in the strings differ. 2) the lines and the line numbers on +which the string differed. + +For example: + (let ((a \"ABC\\nDEF\\nGHI\") + (b \"ABC\\nXXX\\nGHI\\nZZZ\") + (faceup-test-explain t)) + (message \"%s\" (faceup-test-equal a b))) + + ==> (4 3 number-of-lines-differ (on-line 2 (DEF) (XXX))) + +When used in an `ert' rule, the output is as below: + + (ert-deftest faceup-test-equal-example () + (let ((a \"ABC\\nDEF\\nGHI\") + (b \"ABC\\nXXX\\nGHI\\nZZZ\")) + (should (faceup-test-equal a b)))) + + F faceup-test-equal-example + (ert-test-failed + ((should + (faceup-test-equal a b)) + :form + (faceup-test-equal \"ABC\\nDEF\\nGHI\" \"ABC\\nXXX\\nGHI\\nZZZ\") + :value nil :explanation + (4 3 number-of-lines-differ + (on-line 2 + (\"DEF\") + (\"XXX\")))))" + (if (equal lhs rhs) + t + (if faceup-test-explain + (let ((lhs-lines (split-string lhs "\n")) + (rhs-lines (split-string rhs "\n")) + (explanation '()) + (line 1)) + (unless (= (length lhs-lines) (length rhs-lines)) + (setq explanation (list 'number-of-lines-differ + (length lhs-lines) (length rhs-lines)))) + (while lhs-lines + (let ((one (pop lhs-lines)) + (two (pop rhs-lines))) + (unless (equal one two) + (setq explanation + (cons (list 'on-line line (list one) (list two)) + explanation))) + (setq line (+ line 1)))) + (nreverse explanation)) + nil))) + +(faceup-defexplainer faceup-test-equal) + + +;; ------------------------------ +;; Font-lock regression test support. +;; + +(defun faceup-test-font-lock-buffer (mode faceup &optional buffer) + "Verify that BUFFER is fontified as FACEUP for major mode MODE. + +If BUFFER is not specified the current buffer is used. + +Note that the major mode of the buffer is set to MODE and that +the buffer is fontified. + +If MODE is a list, the first element is the major mode, the +remaining are additional functions to call, e.g. minor modes." + (save-excursion + (if buffer + (set-buffer buffer)) + (if (listp mode) + (dolist (m mode) + (funcall m)) + (funcall mode)) + (font-lock-fontify-region (point-min) (point-max)) + (let ((result (faceup-markup-buffer))) + (faceup-test-equal faceup result)))) + +(faceup-defexplainer faceup-test-font-lock-buffer) + + +(defun faceup-test-font-lock-string (mode faceup) + "True if FACEUP is re-fontified as the faceup markup for major mode MODE. + +The string FACEUP is stripped from markup, inserted into a +buffer, the requested major mode activated, the buffer is +fontified, the result is again converted to the faceup form, and +compared with the original string." + (with-temp-buffer + (insert faceup) + (faceup-clean-buffer) + (faceup-test-font-lock-buffer mode faceup))) + +(faceup-defexplainer faceup-test-font-lock-string) + + +(defun faceup-test-font-lock-file (mode file &optional faceup-file) + "Verify that FILE is fontified as FACEUP-FILE for major mode MODE. + +If FACEUP-FILE is omitted, FILE.faceup is used." + (unless faceup-file + (setq faceup-file (concat file ".faceup"))) + (let ((faceup (with-temp-buffer + (insert-file-contents faceup-file) + (buffer-substring-no-properties (point-min) (point-max))))) + (with-temp-buffer + (insert-file-contents file) + (faceup-test-font-lock-buffer mode faceup)))) + +(faceup-defexplainer faceup-test-font-lock-file) + + +;; ------------------------------ +;; Get current file directory. Test cases can use this to locate test +;; files. +;; + +(defun faceup-this-file-directory () + "The directory of the file where the call to this function is located in. +Intended to be called when a file is loaded." + (expand-file-name + (if load-file-name + ;; File is being loaded. + (file-name-directory load-file-name) + ;; File is being evaluated using, for example, `eval-buffer'. + default-directory))) + + +;; ---------------------------------------------------------------------- +;; The end +;; + +(provide 'faceup) + +;;; faceup.el ends here diff --git a/lisp/emacs-lisp/find-func.el b/lisp/emacs-lisp/find-func.el index 9b98f05ae81..29c42f36938 100644 --- a/lisp/emacs-lisp/find-func.el +++ b/lisp/emacs-lisp/find-func.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/emacs-lisp/float-sup.el b/lisp/emacs-lisp/float-sup.el index a33937cd752..18ba834b91a 100644 --- a/lisp/emacs-lisp/float-sup.el +++ b/lisp/emacs-lisp/float-sup.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emacs-lisp/generator.el b/lisp/emacs-lisp/generator.el index c96b400809b..ef6cfba420c 100644 --- a/lisp/emacs-lisp/generator.el +++ b/lisp/emacs-lisp/generator.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -86,10 +86,7 @@ (defvar cps--cleanup-function nil) (defmacro cps--gensym (fmt &rest args) - ;; Change this function to use `cl-gensym' if you want the generated - ;; code to be easier to read and debug. - ;; (cl-gensym (apply #'format fmt args)) - `(progn (ignore ,@args) (make-symbol ,fmt))) + `(gensym (format ,fmt ,@args))) (defvar cps--dynamic-wrappers '(identity) "List of transformer functions to apply to atomic forms we @@ -145,8 +142,7 @@ the CPS state machinery. `(let ((,dynamic-var ,static-var)) (unwind-protect ; Update the static shadow after evaluation is done ,form - (setf ,static-var ,dynamic-var)) - ,form))) + (setf ,static-var ,dynamic-var))))) (defmacro cps--with-dynamic-binding (dynamic-var static-var &rest body) "Evaluate BODY such that generated atomic evaluations run with @@ -684,7 +680,8 @@ sub-iterator function returns via `iter-end-of-sequence'." When called as a function, NAME returns an iterator value that encapsulates the state of a computation that produces a sequence of values. Callers can retrieve each value using `iter-next'." - (declare (indent defun)) + (declare (indent defun) + (debug (&define name lambda-list lambda-doc def-body))) (cl-assert lexical-binding) (let* ((parsed-body (macroexp-parse-body body)) (declarations (car parsed-body)) @@ -696,7 +693,8 @@ of values. Callers can retrieve each value using `iter-next'." (defmacro iter-lambda (arglist &rest body) "Return a lambda generator. `iter-lambda' is to `iter-defun' as `lambda' is to `defun'." - (declare (indent defun)) + (declare (indent defun) + (debug (&define lambda-list lambda-doc def-body))) (cl-assert lexical-binding) `(lambda ,arglist ,(cps-generate-evaluator body))) @@ -720,7 +718,8 @@ is blocked." "Loop over values from an iterator. Evaluate BODY with VAR bound to each value from ITERATOR. Return the value with which ITERATOR finished iteration." - (declare (indent 1)) + (declare (indent 1) + (debug ((symbolp form) body))) (let ((done-symbol (cps--gensym "iter-do-iterator-done")) (condition-symbol (cps--gensym "iter-do-condition")) (it-symbol (cps--gensym "iter-do-iterator")) diff --git a/lisp/emacs-lisp/generic.el b/lisp/emacs-lisp/generic.el index 165b0d4507d..14208857bc4 100644 --- a/lisp/emacs-lisp/generic.el +++ b/lisp/emacs-lisp/generic.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emacs-lisp/gv.el b/lisp/emacs-lisp/gv.el index c5c12a6414c..777b955d90d 100644 --- a/lisp/emacs-lisp/gv.el +++ b/lisp/emacs-lisp/gv.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -146,12 +146,7 @@ NAME is a symbol: the name of a function, macro, or special form. HANDLER is a function which takes an argument DO followed by the same arguments as NAME. DO is a function as defined in `gv-get'." (declare (indent 1) (debug (sexp form))) - ;; Use eval-and-compile so the method can be used in the same file as it - ;; is defined. - ;; FIXME: Just like byte-compile-macro-environment, we should have something - ;; like byte-compile-symbolprop-environment so as to handle these things - ;; cleanly without affecting the running Emacs. - `(eval-and-compile (put ',name 'gv-expander ,handler))) + `(function-put ',name 'gv-expander ,handler)) ;;;###autoload (defun gv--defun-declaration (symbol name args handler &optional fix) @@ -308,7 +303,9 @@ The return value is the last VAL in the list. (lambda (do before index place) (gv-letplace (getter setter) place (funcall do `(edebug-after ,before ,index ,getter) - setter)))) + (lambda (store) + `(progn (edebug-after ,before ,index ,getter) + ,(funcall setter store))))))) ;;; The common generalized variables. @@ -377,10 +374,12 @@ The return value is the last VAL in the list. `(with-current-buffer ,buf (set (make-local-variable ,var) ,v)))) (gv-define-expander alist-get - (lambda (do key alist &optional default remove) + (lambda (do key alist &optional default remove testfn) (macroexp-let2 macroexp-copyable-p k key (gv-letplace (getter setter) alist - (macroexp-let2 nil p `(assq ,k ,getter) + (macroexp-let2 nil p `(if (and ,testfn (not (eq ,testfn 'eq))) + (assoc ,k ,getter ,testfn) + (assq ,k ,getter)) (funcall do (if (null default) `(cdr ,p) `(if ,p (cdr ,p) ,default)) (lambda (v) @@ -434,7 +433,7 @@ The return value is the last VAL in the list. ;; code is large, but otherwise results in more efficient code. `(if ,test ,(gv-get then do) ,@(macroexp-unprogn (gv-get (macroexp-progn else) do))) - (let ((v (make-symbol "v"))) + (let ((v (gensym "v"))) (macroexp-let2 nil gv `(if ,test ,(gv-letplace (getter setter) then `(cons (lambda () ,getter) @@ -459,7 +458,7 @@ The return value is the last VAL in the list. (gv-get (macroexp-progn (cdr branch)) do))) (gv-get (car branch) do))) branches)) - (let ((v (make-symbol "v"))) + (let ((v (gensym "v"))) (macroexp-let2 nil gv `(cond ,@(mapcar diff --git a/lisp/emacs-lisp/helper.el b/lisp/emacs-lisp/helper.el index 78611c661ab..9dc59467ffd 100644 --- a/lisp/emacs-lisp/helper.el +++ b/lisp/emacs-lisp/helper.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emacs-lisp/inline.el b/lisp/emacs-lisp/inline.el index ce46f66aef8..ff27158f836 100644 --- a/lisp/emacs-lisp/inline.el +++ b/lisp/emacs-lisp/inline.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -59,7 +59,7 @@ ;; and then M-: (macroexpand-all '(my-test1 y)) RET) ;; There is still one downside shared with the defmacro and cl-defsubst ;; approach: when the function is inlined, the scoping rules (dynamic or -;; lexical) will be inherited from the the call site. +;; lexical) will be inherited from the call site. ;; Of course, since define-inline defines a compiler macro, you can also do ;; call-site optimizations, just like you can with `defmacro', but not with @@ -218,7 +218,7 @@ After VARS is handled, BODY is evaluated in the new environment." `(let* ((,bsym ()) (,listvar (mapcar (lambda (e) (if (macroexp-copyable-p e) e - (let ((v (make-symbol "v"))) + (let ((v (gensym "v"))) (push (list v e) ,bsym) v))) ,listvar))) diff --git a/lisp/emacs-lisp/let-alist.el b/lisp/emacs-lisp/let-alist.el index cf82fe3ec63..70a58c4b1c6 100644 --- a/lisp/emacs-lisp/let-alist.el +++ b/lisp/emacs-lisp/let-alist.el @@ -25,7 +25,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emacs-lisp/lisp-mnt.el b/lisp/emacs-lisp/lisp-mnt.el index fc3caf3359a..4e4957faa1f 100644 --- a/lisp/emacs-lisp/lisp-mnt.el +++ b/lisp/emacs-lisp/lisp-mnt.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -326,12 +326,13 @@ Return argument is of the form (\"HOLDER\" \"YEAR1\" ... \"YEARN\")" (start (point)) (end (line-end-position))) ;; Cope with multi-line copyright `lines'. Assume the second - ;; line is indented (with the same commenting style). + ;; line is indented at least as much as the original, with the + ;; same commenting style. (save-excursion (beginning-of-line 2) - (let ((str (concat (match-string-no-properties 1) "[ \t]+"))) + (let ((str (match-string-no-properties 1))) (beginning-of-line) - (while (looking-at str) + (while (and (looking-at str) (not (looking-at lm-copyright-prefix))) (setq end (line-end-position)) (beginning-of-line 2)))) ;; Make a single line and parse that. diff --git a/lisp/emacs-lisp/lisp-mode.el b/lisp/emacs-lisp/lisp-mode.el index 59db00d5f96..7d38052fd40 100644 --- a/lisp/emacs-lisp/lisp-mode.el +++ b/lisp/emacs-lisp/lisp-mode.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -461,11 +461,6 @@ This will generate compile-time constants from BINDINGS." (throw 'found t))))))) (1 'font-lock-regexp-grouping-backslash prepend) (3 'font-lock-regexp-grouping-construct prepend)) - ;; This is too general -- rms. - ;; A user complained that he has functions whose names start with `do' - ;; and that they get the wrong color. - ;; ;; CL `with-' and `do-' constructs - ;;("(\\(\\(do-\\|with-\\)\\(\\s_\\|\\w\\)*\\)" 1 font-lock-keyword-face) (lisp--match-hidden-arg (0 '(face font-lock-warning-face help-echo "Hidden behind deeper element; move to another line?"))) @@ -491,6 +486,11 @@ This will generate compile-time constants from BINDINGS." (,(concat "[`‘]\\(\\(?:\\sw\\|\\s_\\|\\\\.\\)" lisp-mode-symbol-regexp "\\)['’]") (1 font-lock-constant-face prepend)) + ;; Uninterned symbols, e.g., (defpackage #:my-package ...) + ;; must come before keywords below to have effect + (,(concat "\\(#:\\)\\(" lisp-mode-symbol-regexp "\\)") + (1 font-lock-comment-delimiter-face) + (2 font-lock-doc-face)) ;; Constant values. (,(concat "\\_<:" lisp-mode-symbol-regexp "\\_>") (0 font-lock-builtin-face)) @@ -500,8 +500,10 @@ This will generate compile-time constants from BINDINGS." ;; This is too general -- rms. ;; A user complained that he has functions whose names start with `do' ;; and that they get the wrong color. - ;; ;; CL `with-' and `do-' constructs - ;;("(\\(\\(do-\\|with-\\)\\(\\s_\\|\\w\\)*\\)" 1 font-lock-keyword-face) + ;; That user has violated the http://www.cliki.net/Naming+conventions: + ;; CL (but not EL!) `with-' (context) and `do-' (iteration) + (,(concat "(\\(\\(do-\\|with-\\)" lisp-mode-symbol-regexp "\\)") + (1 font-lock-keyword-face)) (lisp--match-hidden-arg (0 '(face font-lock-warning-face help-echo "Hidden behind deeper element; move to another line?"))) @@ -602,6 +604,7 @@ font-lock keywords will not be case sensitive." ;;(set (make-local-variable 'adaptive-fill-mode) nil) (setq-local indent-line-function 'lisp-indent-line) (setq-local indent-region-function 'lisp-indent-region) + (setq-local comment-indent-function #'lisp-comment-indent) (setq-local outline-regexp ";;;\\(;* [^ \t\n]\\|###autoload\\)\\|(") (setq-local outline-level 'lisp-outline-level) (setq-local add-log-current-defun-function #'lisp-current-defun-name) @@ -735,9 +738,17 @@ or to switch back to an existing one." (autoload 'lisp-eval-defun "inf-lisp" nil t) -;; May still be used by some external Lisp-mode variant. -(define-obsolete-function-alias 'lisp-comment-indent - 'comment-indent-default "22.1") +(defun lisp-comment-indent () + "Like `comment-indent-default', but don't put space after open paren." + (or (when (looking-at "\\s<\\s<") + (let ((pt (point))) + (skip-syntax-backward " ") + (if (eq (preceding-char) ?\() + (cons (current-column) (current-column)) + (goto-char pt) + nil))) + (comment-indent-default))) + (define-obsolete-function-alias 'lisp-mode-auto-fill 'do-auto-fill "23.1") (defcustom lisp-indent-offset nil @@ -1258,7 +1269,8 @@ and initial semicolons." ;; case). The `;' and `:' stop the paragraph being filled at following ;; comment lines and at keywords (e.g., in `defcustom'). Left parens are ;; escaped to keep font-locking, filling, & paren matching in the source - ;; file happy. + ;; file happy. The `:' must be preceded by whitespace so that keywords + ;; inside of the docstring don't start new paragraphs (Bug#7751). ;; ;; `paragraph-separate': A clever regexp distinguishes the first line of ;; a docstring and identifies it as a paragraph separator, so that it @@ -1271,13 +1283,7 @@ and initial semicolons." ;; `emacs-lisp-docstring-fill-column' if that value is an integer. (let ((paragraph-start (concat paragraph-start - (format "\\|\\s-*\\([(;%s\"]\\|`(\\|#'(\\)" - ;; If we're inside a string (like the doc - ;; string), don't consider a colon to be - ;; a paragraph-start character. - (if (nth 3 (syntax-ppss)) - "" - ":")))) + "\\|\\s-*\\([(;\"]\\|\\s-:\\|`(\\|#'(\\)")) (paragraph-separate (concat paragraph-separate "\\|\\s-*\".*[,\\.]$")) (fill-column (if (and (integerp emacs-lisp-docstring-fill-column) diff --git a/lisp/emacs-lisp/lisp.el b/lisp/emacs-lisp/lisp.el index 0c1fe42fedb..6952ef4cf49 100644 --- a/lisp/emacs-lisp/lisp.el +++ b/lisp/emacs-lisp/lisp.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -402,7 +402,7 @@ is called as a function to find the defun's beginning." "Return non-nil if the point is in an \"emptyish\" line. This means a line that consists entirely of comments and/or whitespace." -;; See http://lists.gnu.org/archive/html/help-gnu-emacs/2016-08/msg00141.html +;; See https://lists.gnu.org/r/help-gnu-emacs/2016-08/msg00141.html (save-excursion (forward-line 0) (< (line-end-position) @@ -525,7 +525,7 @@ the one(s) already marked." (interactive "p") (setq arg (or arg 1)) ;; There is no `mark-defun-back' function - see - ;; https://lists.gnu.org/archive/html/bug-gnu-emacs/2016-11/msg00079.html + ;; https://lists.gnu.org/r/bug-gnu-emacs/2016-11/msg00079.html ;; for explanation (when (eq last-command 'mark-defun-back) (setq arg (- arg))) @@ -574,7 +574,7 @@ the one(s) already marked." (goto-char beg) (unless (= arg -1) ; beginning-of-defun behaves ; strange with zero arg - see - ; https://lists.gnu.org/archive/html/bug-gnu-emacs/2017-02/msg00196.html + ; https://lists.gnu.org/r/bug-gnu-emacs/2017-02/msg00196.html (beginning-of-defun (1- (- arg)))) (push-mark end nil t)))))) (skip-chars-backward "[:space:]\n") diff --git a/lisp/emacs-lisp/macroexp.el b/lisp/emacs-lisp/macroexp.el index 9bc194c478c..b7496d5a602 100644 --- a/lisp/emacs-lisp/macroexp.el +++ b/lisp/emacs-lisp/macroexp.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/emacs-lisp/map-ynp.el b/lisp/emacs-lisp/map-ynp.el index af7a9ee4abb..d055a54fb39 100644 --- a/lisp/emacs-lisp/map-ynp.el +++ b/lisp/emacs-lisp/map-ynp.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emacs-lisp/map.el b/lisp/emacs-lisp/map.el index a89457e877d..2a3e1d0a4b0 100644 --- a/lisp/emacs-lisp/map.el +++ b/lisp/emacs-lisp/map.el @@ -4,7 +4,7 @@ ;; Author: Nicolas Petton <nicolas@petton.fr> ;; Keywords: convenience, map, hash-table, alist, array -;; Version: 1.1 +;; Version: 1.2 ;; Package: map ;; Maintainer: emacs-devel@gnu.org @@ -22,7 +22,7 @@ ;; 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 <http://www.gnu.org/licenses/>. +;; along with this program. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -73,7 +73,8 @@ KEYS can also be a list of (KEY VARNAME) pairs, in which case KEY is an unquoted form. MAP can be a list, hash-table or array." - (declare (indent 2) (debug t)) + (declare (indent 2) + (debug ((&rest &or symbolp ([form symbolp])) form body))) `(pcase-let ((,(map--make-pcase-patterns keys) ,map)) ,@body)) @@ -93,11 +94,13 @@ Returns the result of evaluating the form associated with MAP-VAR's type." ((arrayp ,map-var) ,(plist-get args :array)) (t (error "Unsupported map: %s" ,map-var))))) -(defun map-elt (map key &optional default) +(defun map-elt (map key &optional default testfn) "Lookup KEY in MAP and return its associated value. If KEY is not found, return DEFAULT which defaults to nil. -If MAP is a list, `eql' is used to lookup KEY. +If MAP is a list, `eql' is used to lookup KEY. Optional argument +TESTFN, if non-nil, means use its function definition instead of +`eql'. MAP can be a list, hash-table or array." (declare @@ -106,30 +109,31 @@ MAP can be a list, hash-table or array." (gv-letplace (mgetter msetter) `(gv-delay-error ,map) (macroexp-let2* nil ;; Eval them once and for all in the right order. - ((key key) (default default)) + ((key key) (default default) (testfn testfn)) `(if (listp ,mgetter) ;; Special case the alist case, since it can't be handled by the ;; map--put function. ,(gv-get `(alist-get ,key (gv-synthetic-place ,mgetter ,msetter) - ,default) + ,default nil ,testfn) do) ,(funcall do `(map-elt ,mgetter ,key ,default) (lambda (v) `(map--put ,mgetter ,key ,v))))))))) (map--dispatch map - :list (alist-get key map default) + :list (alist-get key map default nil testfn) :hash-table (gethash key map default) :array (if (and (>= key 0) (< key (seq-length map))) (seq-elt map key) default))) -(defmacro map-put (map key value) +(defmacro map-put (map key value &optional testfn) "Associate KEY with VALUE in MAP and return VALUE. If KEY is already present in MAP, replace the associated value with VALUE. +When MAP is a list, test equality with TESTFN if non-nil, otherwise use `eql'. MAP can be a list, hash-table or array." - `(setf (map-elt ,map ,key) ,value)) + `(setf (map-elt ,map ,key nil ,testfn) ,value)) (defun map-delete (map key) "Delete KEY from MAP and return MAP. diff --git a/lisp/emacs-lisp/nadvice.el b/lisp/emacs-lisp/nadvice.el index fd1cd2c7aaf..c638d5df51c 100644 --- a/lisp/emacs-lisp/nadvice.el +++ b/lisp/emacs-lisp/nadvice.el @@ -17,7 +17,7 @@ ;; 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 <http://www.gnu.org/licenses/>. +;; along with this program. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -385,6 +385,18 @@ of the piece of advice." (defun advice--defalias-fset (fsetfun symbol newdef) (unless fsetfun (setq fsetfun #'fset)) + ;; `newdef' shouldn't include advice wrappers, since that's what *we* manage! + ;; So if `newdef' includes advice wrappers, it's usually because someone + ;; naively took (symbol-function F) and then passed that back to `defalias': + ;; let's strip them away. + (cond + ((advice--p newdef) (setq newdef (advice--cd*r newdef))) + ((and (eq 'macro (car-safe newdef)) + (advice--p (cdr newdef))) + (setq newdef `(macro . ,(advice--cd*r (cdr newdef)))))) + ;; The saved-rewrite is specific to the current value, so since we are about + ;; to overwrite that current value with new value, the old saved-rewrite is + ;; not relevant any more. (when (get symbol 'advice--saved-rewrite) (put symbol 'advice--saved-rewrite nil)) (setq newdef (advice--normalize symbol newdef)) diff --git a/lisp/emacs-lisp/package-x.el b/lisp/emacs-lisp/package-x.el index a3d90f4fb1b..923da4681a5 100644 --- a/lisp/emacs-lisp/package-x.el +++ b/lisp/emacs-lisp/package-x.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emacs-lisp/package.el b/lisp/emacs-lisp/package.el index bebfd18d7a6..f8b4cc888dd 100644 --- a/lisp/emacs-lisp/package.el +++ b/lisp/emacs-lisp/package.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -101,7 +101,7 @@ ;; Michael Olson <mwolson@member.fsf.org> ;; Sebastian Tennant <sebyte@smolny.plus.com> ;; Stefan Monnier <monnier@iro.umontreal.ca> -;; Vinicius Jose Latorre <viniciusjl@ig.com.br> +;; Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> ;; Phil Hagelberg <phil@hagelb.org> ;;; ToDo: @@ -708,24 +708,26 @@ correspond to previously loaded files (those returned by (unless pkg-dir (error "Internal error: unable to find directory for `%s'" (package-desc-full-name pkg-desc))) - ;; Activate its dependencies recursively. - ;; FIXME: This doesn't check whether the activated version is the - ;; required version. - (when deps - (dolist (req (package-desc-reqs pkg-desc)) - (unless (package-activate (car req)) - (error "Unable to activate package `%s'.\nRequired package `%s-%s' is unavailable" - name (car req) (package-version-join (cadr req)))))) - (package--load-files-for-activation pkg-desc reload) - ;; Add info node. - (when (file-exists-p (expand-file-name "dir" pkg-dir)) - ;; FIXME: not the friendliest, but simple. - (require 'info) - (info-initialize) - (push pkg-dir Info-directory-list)) - (push name package-activated-list) - ;; Don't return nil. - t)) + (catch 'exit + ;; Activate its dependencies recursively. + ;; FIXME: This doesn't check whether the activated version is the + ;; required version. + (when deps + (dolist (req (package-desc-reqs pkg-desc)) + (unless (package-activate (car req)) + (message "Unable to activate package `%s'.\nRequired package `%s-%s' is unavailable" + name (car req) (package-version-join (cadr req))) + (throw 'exit nil)))) + (package--load-files-for-activation pkg-desc reload) + ;; Add info node. + (when (file-exists-p (expand-file-name "dir" pkg-dir)) + ;; FIXME: not the friendliest, but simple. + (require 'info) + (info-initialize) + (push pkg-dir Info-directory-list)) + (push name package-activated-list) + ;; Don't return nil. + t))) (declare-function find-library-name "find-func" (library)) @@ -866,14 +868,14 @@ untar into a directory named DIR; otherwise, signal an error." ;; Activation has to be done before compilation, so that if we're ;; upgrading and macros have changed we load the new definitions ;; before compiling. - (package-activate-1 new-desc :reload :deps) - ;; FIXME: Compilation should be done as a separate, optional, step. - ;; E.g. for multi-package installs, we should first install all packages - ;; and then compile them. - (package--compile new-desc) - ;; After compilation, load again any files loaded by - ;; `activate-1', so that we use the byte-compiled definitions. - (package--load-files-for-activation new-desc :reload)) + (when (package-activate-1 new-desc :reload :deps) + ;; FIXME: Compilation should be done as a separate, optional, step. + ;; E.g. for multi-package installs, we should first install all packages + ;; and then compile them. + (package--compile new-desc) + ;; After compilation, load again any files loaded by + ;; `activate-1', so that we use the byte-compiled definitions. + (package--load-files-for-activation new-desc :reload))) pkg-dir)) (defun package-generate-description-file (pkg-desc pkg-file) @@ -959,17 +961,12 @@ This assumes that `pkg-desc' has already been activated with (defun package-read-from-string (str) "Read a Lisp expression from STR. Signal an error if the entire string was not used." - (let* ((read-data (read-from-string str)) - (more-left - (condition-case nil - ;; The call to `ignore' suppresses a compiler warning. - (progn (ignore (read-from-string - (substring str (cdr read-data)))) - t) - (end-of-file nil)))) - (if more-left - (error "Can't read whole string") - (car read-data)))) + (pcase-let ((`(,expr . ,offset) (read-from-string str))) + (condition-case () + ;; The call to `ignore' suppresses a compiler warning. + (progn (ignore (read-from-string str offset)) + (error "Can't read whole string")) + (end-of-file expr)))) (defun package--prepare-dependencies (deps) "Turn DEPS into an acceptable list of dependencies. @@ -1190,7 +1187,7 @@ errors signaled by ERROR-FORM or by BODY). (let ((,b-sym (current-buffer))) (require 'url-handlers) (unless-error ,body - (when-let ((er (plist-get status :error))) + (when-let* ((er (plist-get status :error))) (error "Error retrieving: %s %S" ,url-sym er)) (with-current-buffer ,b-sym (goto-char (point-min)) @@ -1463,7 +1460,11 @@ taken care of by `package-initialize'." (package-read-all-archive-contents) (unless no-activate (dolist (elt package-alist) - (package-activate (car elt)))) + (condition-case err + (package-activate (car elt)) + ;; Don't let failure of activation of a package arbitrarily stop + ;; activation of further packages. + (error (message "%s" (error-message-string err)))))) (setq package--initialized t) ;; This uses `package--mapc' so it must be called after ;; `package--initialized' is t. @@ -1764,8 +1765,8 @@ Only these packages will be in the return value an their cdrs are destructively set to nil in ONLY." (let ((out)) (dolist (dep (package-desc-reqs package)) - (when-let ((cell (assq (car dep) only)) - (dep-package (cdr-safe cell))) + (when-let* ((cell (assq (car dep) only)) + (dep-package (cdr-safe cell))) (setcdr cell nil) (setq out (append (package--sort-deps-in-alist dep-package only) out)))) @@ -1784,7 +1785,7 @@ if all the in-between dependencies are also in PACKAGE-LIST." (dolist (cell alist out-list) ;; `package--sort-deps-in-alist' destructively changes alist, so ;; some cells might already be empty. We check this here. - (when-let ((pkg-desc (cdr cell))) + (when-let* ((pkg-desc (cdr cell))) (setcdr cell nil) (setq out-list (append (package--sort-deps-in-alist pkg-desc alist) @@ -1841,7 +1842,7 @@ if all the in-between dependencies are also in PACKAGE-LIST." ;; Update the old pkg-desc which will be shown on the description buffer. (setf (package-desc-signed pkg-desc) t) ;; Update the new (activated) pkg-desc as well. - (when-let ((pkg-descs (cdr (assq (package-desc-name pkg-desc) package-alist)))) + (when-let* ((pkg-descs (cdr (assq (package-desc-name pkg-desc) package-alist)))) (setf (package-desc-signed (car pkg-descs)) t)))))))))) (defun package-installed-p (package &optional min-version) @@ -1964,12 +1965,12 @@ to install it but still mark it as selected." (unless (or dont-select (package--user-selected-p name)) (package--save-selected-packages (cons name package-selected-packages))) - (if-let ((transaction - (if (package-desc-p pkg) - (unless (package-installed-p pkg) - (package-compute-transaction (list pkg) - (package-desc-reqs pkg))) - (package-compute-transaction () (list (list pkg)))))) + (if-let* ((transaction + (if (package-desc-p pkg) + (unless (package-installed-p pkg) + (package-compute-transaction (list pkg) + (package-desc-reqs pkg))) + (package-compute-transaction () (list (list pkg)))))) (package-download-transaction transaction) (message "`%s' is already installed" name)))) @@ -2127,7 +2128,7 @@ If NOSAVE is non-nil, the package is not removed from (package-desc-name pkg-used-elsewhere-by))) (t (add-hook 'post-command-hook #'package-menu--post-refresh) - (delete-directory dir t t) + (delete-directory dir t) ;; Remove NAME-VERSION.signed and NAME-readme.txt files. (dolist (suffix '(".signed" "readme.txt")) (let* ((version (package-version-join (package-desc-version pkg-desc))) @@ -2254,6 +2255,7 @@ Otherwise no newline is inserted." (archive (if desc (package-desc-archive desc))) (extras (and desc (package-desc-extras desc))) (homepage (cdr (assoc :url extras))) + (commit (cdr (assoc :commit extras))) (keywords (if desc (package-desc--keywords desc))) (built-in (eq pkg-dir 'builtin)) (installable (and archive (not built-in))) @@ -2326,6 +2328,8 @@ Otherwise no newline is inserted." (and version (package--print-help-section "Version" (package-version-join version))) + (when commit + (package--print-help-section "Commit" commit)) (when desc (package--print-help-section "Summary" (package-desc-summary desc))) @@ -2751,6 +2755,7 @@ KEYWORDS should be nil or a list of keywords." (push pkg info-list)))))) ;; Print the result. + (tabulated-list-init-header) (setq tabulated-list-entries (mapcar #'package-menu--print-info-simple info-list)))) @@ -3274,7 +3279,7 @@ Optional argument NOQUERY non-nil means do not ask the user to confirm." (package--update-selected-packages .install .delete) (package-menu--perform-transaction install-list delete-list) (when package-selected-packages - (if-let ((removable (package--removable-packages))) + (if-let* ((removable (package--removable-packages))) (message "Package menu: Operation finished. %d packages %s" (length removable) (substitute-command-keys @@ -3346,7 +3351,7 @@ Store this list in `package-menu--new-package-list'." (defun package-menu--find-and-notify-upgrades () "Notify the user of upgradable packages." - (when-let ((upgrades (package-menu--find-upgrades))) + (when-let* ((upgrades (package-menu--find-upgrades))) (message "%d package%s can be upgraded; type `%s' to mark %s for upgrading." (length upgrades) (if (= (length upgrades) 1) "" "s") @@ -3387,7 +3392,9 @@ This function is called after `package-refresh-contents'." "Display a list of packages. This first fetches the updated list of packages before displaying, unless a prefix argument NO-FETCH is specified. -The list is displayed in a buffer named `*Packages*'." +The list is displayed in a buffer named `*Packages*', and +includes the package's version, availability status, and a +short description." (interactive "P") (require 'finder-inf nil t) ;; Initialize the package system if necessary. diff --git a/lisp/emacs-lisp/pcase.el b/lisp/emacs-lisp/pcase.el index 4a06ab25d3e..36af88423c8 100644 --- a/lisp/emacs-lisp/pcase.el +++ b/lisp/emacs-lisp/pcase.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -213,7 +213,7 @@ Emacs Lisp manual for more information and examples." (defmacro pcase-exhaustive (exp &rest cases) "The exhaustive version of `pcase' (which see)." (declare (indent 1) (debug pcase)) - (let* ((x (make-symbol "x")) + (let* ((x (gensym "x")) (pcase--dontwarn-upats (cons x pcase--dontwarn-upats))) (pcase--expand ;; FIXME: Could we add the FILE:LINE data in the error message? @@ -226,7 +226,7 @@ I.e. accepts the usual &optional and &rest keywords, but every formal argument can be any pattern accepted by `pcase' (a mere variable name being but a special case of it)." (declare (doc-string 2) (indent defun) - (debug ((&rest pcase-PAT) body))) + (debug (&define (&rest pcase-PAT) lambda-doc def-body))) (let* ((bindings ()) (parsed-body (macroexp-parse-body body)) (args (mapcar (lambda (pat) @@ -304,7 +304,7 @@ any kind of error." (declare (indent 1) (debug ((pcase-PAT form) body))) (if (pcase--trivial-upat-p (car spec)) `(dolist ,spec ,@body) - (let ((tmpvar (make-symbol "x"))) + (let ((tmpvar (gensym "x"))) `(dolist (,tmpvar ,@(cdr spec)) (pcase-let* ((,(car spec) ,tmpvar)) ,@body))))) @@ -418,8 +418,8 @@ to this macro." (when decl (setq body (remove decl body))) `(progn (defun ,fsym ,args ,@body) - (put ',fsym 'edebug-form-spec ',(cadr (assq 'debug decl))) - (put ',name 'pcase-macroexpander #',fsym)))) + (define-symbol-prop ',fsym 'edebug-form-spec ',(cadr (assq 'debug decl))) + (define-symbol-prop ',name 'pcase-macroexpander #',fsym)))) (defun pcase--match (val upat) "Build a MATCH structure, hoisting all `or's and `and's outside." @@ -715,7 +715,7 @@ MATCH is the pattern that needs to be matched, of the form: (call (progn (when (memq arg vs) ;; `arg' is shadowed by `env'. - (let ((newsym (make-symbol "x"))) + (let ((newsym (gensym "x"))) (push (list newsym arg) env) (setq arg newsym))) (if (functionp fun) @@ -842,7 +842,7 @@ Otherwise, it defers to REST which is a list of branches of the form ;; A upat of the form (app FUN PAT) (pcase--mark-used sym) (let* ((fun (nth 1 upat)) - (nsym (make-symbol "x")) + (nsym (gensym "x")) (body ;; We don't change `matches' to reuse the newly computed value, ;; because we assume there shouldn't be such redundancy in there. @@ -930,6 +930,5 @@ QPAT can take the following forms: ((or (stringp qpat) (integerp qpat) (symbolp qpat)) `',qpat) (t (error "Unknown QPAT: %S" qpat)))) - (provide 'pcase) ;;; pcase.el ends here diff --git a/lisp/emacs-lisp/pp.el b/lisp/emacs-lisp/pp.el index 7ef46a48bde..d9cd37e9ec3 100644 --- a/lisp/emacs-lisp/pp.el +++ b/lisp/emacs-lisp/pp.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emacs-lisp/radix-tree.el b/lisp/emacs-lisp/radix-tree.el index b5e7589b951..053dd452ea2 100644 --- a/lisp/emacs-lisp/radix-tree.el +++ b/lisp/emacs-lisp/radix-tree.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emacs-lisp/re-builder.el b/lisp/emacs-lisp/re-builder.el index f60d723a883..84925cb335c 100644 --- a/lisp/emacs-lisp/re-builder.el +++ b/lisp/emacs-lisp/re-builder.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -64,8 +64,8 @@ ;; syntax and string syntax are both delimited by `"'s and behave ;; according to their name. With the `string' syntax there's no need ;; to escape the backslashes and double quotes simplifying the editing -;; somewhat. The other three allow editing of symbolic regular -;; expressions supported by the packages of the same name. +;; somewhat. The `rx' syntax allows editing of symbolic regular +;; expressions supported by the package of the same name. ;; Editing symbolic expressions is done through a major mode derived ;; from `emacs-lisp-mode' so you'll get all the good stuff like diff --git a/lisp/emacs-lisp/regexp-opt.el b/lisp/emacs-lisp/regexp-opt.el index 5feaad88c7b..ef91eb4b979 100644 --- a/lisp/emacs-lisp/regexp-opt.el +++ b/lisp/emacs-lisp/regexp-opt.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emacs-lisp/regi.el b/lisp/emacs-lisp/regi.el index 351dba560f4..9f612a146a6 100644 --- a/lisp/emacs-lisp/regi.el +++ b/lisp/emacs-lisp/regi.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emacs-lisp/ring.el b/lisp/emacs-lisp/ring.el index b0ec3bcbe01..69754b05e23 100644 --- a/lisp/emacs-lisp/ring.el +++ b/lisp/emacs-lisp/ring.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emacs-lisp/rmc.el b/lisp/emacs-lisp/rmc.el new file mode 100644 index 00000000000..ca11c596638 --- /dev/null +++ b/lisp/emacs-lisp/rmc.el @@ -0,0 +1,201 @@ +;;; rmc.el --- read from a multiple choice question -*- lexical-binding: t -*- + +;; Copyright (C) 2016-2017 Free Software Foundation, Inc. + +;; Maintainer: emacs-devel@gnu.org + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;;; Code: + +(require 'seq) + +;;;###autoload +(defun read-multiple-choice (prompt choices) + "Ask user a multiple choice question. +PROMPT should be a string that will be displayed as the prompt. + +CHOICES is an alist where the first element in each entry is a +character to be entered, the second element is a short name for +the entry to be displayed while prompting (if there's room, it +might be shortened), and the third, optional entry is a longer +explanation that will be displayed in a help buffer if the user +requests more help. + +This function translates user input into responses by consulting +the bindings in `query-replace-map'; see the documentation of +that variable for more information. In this case, the useful +bindings are `recenter', `scroll-up', and `scroll-down'. If the +user enters `recenter', `scroll-up', or `scroll-down' responses, +perform the requested window recentering or scrolling and ask +again. + +When `use-dialog-box' is t (the default), this function can pop +up a dialog window to collect the user input. That functionality +requires `display-popup-menus-p' to return t. Otherwise, a text +dialog will be used. + +The return value is the matching entry from the CHOICES list. + +Usage example: + +\(read-multiple-choice \"Continue connecting?\" + \\='((?a \"always\") + (?s \"session only\") + (?n \"no\")))" + (let* ((altered-names nil) + (full-prompt + (format + "%s (%s): " + prompt + (mapconcat + (lambda (elem) + (let* ((name (cadr elem)) + (pos (seq-position name (car elem))) + (altered-name + (cond + ;; Not in the name string. + ((not pos) + (format "[%c] %s" (car elem) name)) + ;; The prompt character is in the name, so highlight + ;; it on graphical terminals... + ((display-supports-face-attributes-p + '(:underline t) (window-frame)) + (setq name (copy-sequence name)) + (put-text-property pos (1+ pos) + 'face 'read-multiple-choice-face + name) + name) + ;; And put it in [bracket] on non-graphical terminals. + (t + (concat + (substring name 0 pos) + "[" + (upcase (substring name pos (1+ pos))) + "]" + (substring name (1+ pos))))))) + (push (cons (car elem) altered-name) + altered-names) + altered-name)) + (append choices '((?? "?"))) + ", "))) + tchar buf wrong-char answer) + (save-window-excursion + (save-excursion + (while (not tchar) + (message "%s%s" + (if wrong-char + "Invalid choice. " + "") + full-prompt) + (setq tchar + (if (and (display-popup-menus-p) + last-input-event ; not during startup + (listp last-nonmenu-event) + use-dialog-box) + (x-popup-dialog + t + (cons prompt + (mapcar + (lambda (elem) + (cons (capitalize (cadr elem)) + (car elem))) + choices))) + (condition-case nil + (let ((cursor-in-echo-area t)) + (read-char)) + (error nil)))) + (setq answer (lookup-key query-replace-map (vector tchar) t)) + (setq tchar + (cond + ((eq answer 'recenter) + (recenter) t) + ((eq answer 'scroll-up) + (ignore-errors (scroll-up-command)) t) + ((eq answer 'scroll-down) + (ignore-errors (scroll-down-command)) t) + ((eq answer 'scroll-other-window) + (ignore-errors (scroll-other-window)) t) + ((eq answer 'scroll-other-window-down) + (ignore-errors (scroll-other-window-down)) t) + (t tchar))) + (when (eq tchar t) + (setq wrong-char nil + tchar nil)) + ;; The user has entered an invalid choice, so display the + ;; help messages. + (when (and (not (eq tchar nil)) + (not (assq tchar choices))) + (setq wrong-char (not (memq tchar '(?? ?\C-h))) + tchar nil) + (when wrong-char + (ding)) + (with-help-window (setq buf (get-buffer-create + "*Multiple Choice Help*")) + (with-current-buffer buf + (erase-buffer) + (pop-to-buffer buf) + (insert prompt "\n\n") + (let* ((columns (/ (window-width) 25)) + (fill-column 21) + (times 0) + (start (point))) + (dolist (elem choices) + (goto-char start) + (unless (zerop times) + (if (zerop (mod times columns)) + ;; Go to the next "line". + (goto-char (setq start (point-max))) + ;; Add padding. + (while (not (eobp)) + (end-of-line) + (insert (make-string (max (- (* (mod times columns) + (+ fill-column 4)) + (current-column)) + 0) + ?\s)) + (forward-line 1)))) + (setq times (1+ times)) + (let ((text + (with-temp-buffer + (insert (format + "%c: %s\n" + (car elem) + (cdr (assq (car elem) altered-names)))) + (fill-region (point-min) (point-max)) + (when (nth 2 elem) + (let ((start (point))) + (insert (nth 2 elem)) + (unless (bolp) + (insert "\n")) + (fill-region start (point-max)))) + (buffer-string)))) + (goto-char start) + (dolist (line (split-string text "\n")) + (end-of-line) + (if (bolp) + (insert line "\n") + (insert line)) + (forward-line 1))))))))))) + (when (buffer-live-p buf) + (kill-buffer buf)) + (assq tchar choices))) + +(provide 'rmc) + +;;; rmc.el ends here diff --git a/lisp/emacs-lisp/rx.el b/lisp/emacs-lisp/rx.el index 386232c6eef..54755a7dc12 100644 --- a/lisp/emacs-lisp/rx.el +++ b/lisp/emacs-lisp/rx.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -1169,6 +1169,62 @@ enclosed in `(and ...)'. (rx-to-string `(and ,@regexps) t)) (t (rx-to-string (car regexps) t)))) + + +(pcase-defmacro rx (&rest regexps) + "Build a `pcase' pattern matching `rx' regexps. +The REGEXPS are interpreted as by `rx'. The pattern matches if +the regular expression so constructed matches the object, as if +by `string-match'. + +In addition to the usual `rx' constructs, REGEXPS can contain the +following constructs: + + (let VAR FORM...) creates a new explicitly numbered submatch + that matches FORM and binds the match to + VAR. + (backref VAR) creates a backreference to the submatch + introduced by a previous (let VAR ...) + construct. + +The VARs are associated with explicitly numbered submatches +starting from 1. Multiple occurrences of the same VAR refer to +the same submatch. + +If a case matches, the match data is modified as usual so you can +use it in the case body, but you still have to pass the correct +string as argument to `match-string'." + (let* ((vars ()) + (rx-constituents + `((let + ,(lambda (form) + (rx-check form) + (let ((var (cadr form))) + (cl-check-type var symbol) + (let ((i (or (cl-position var vars :test #'eq) + (prog1 (length vars) + (setq vars `(,@vars ,var)))))) + (rx-form `(submatch-n ,(1+ i) ,@(cddr form)))))) + 1 nil) + (backref + ,(lambda (form) + (rx-check form) + (rx-backref + `(backref ,(let ((var (cadr form))) + (if (integerp var) var + (1+ (cl-position var vars :test #'eq))))))) + 1 1 + ,(lambda (var) + (cond ((integerp var) (rx-check-backref var)) + ((memq var vars) t) + (t (error "rx `backref' variable must be one of %s: %s" + vars var))))) + ,@rx-constituents)) + (regexp (rx-to-string `(seq ,@regexps) :no-group))) + `(and (pred (string-match ,regexp)) + ,@(cl-loop for i from 1 + for var in vars + collect `(app (match-string ,i) ,var))))) ;; ;; sregex.el replacement diff --git a/lisp/emacs-lisp/seq.el b/lisp/emacs-lisp/seq.el index 23e444fe241..2861ed75ce7 100644 --- a/lisp/emacs-lisp/seq.el +++ b/lisp/emacs-lisp/seq.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emacs-lisp/shadow.el b/lisp/emacs-lisp/shadow.el index df586486d32..103e131ea39 100644 --- a/lisp/emacs-lisp/shadow.el +++ b/lisp/emacs-lisp/shadow.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emacs-lisp/smie.el b/lisp/emacs-lisp/smie.el index 7baccbc7524..da1e12b1408 100644 --- a/lisp/emacs-lisp/smie.el +++ b/lisp/emacs-lisp/smie.el @@ -18,7 +18,7 @@ ;; 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 <http://www.gnu.org/licenses/>. +;; along with this program. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -1956,7 +1956,7 @@ E.g. provided via a file-local call to `smie-config-local'.") (defvar smie-config--modefuns nil) (defun smie-config--setter (var value) - (setq-default var value) + (set-default var value) (let ((old-modefuns smie-config--modefuns)) (setq smie-config--modefuns nil) (pcase-dolist (`(,mode . ,rules) value) @@ -1982,7 +1982,7 @@ value with which to replace it." ;; FIXME improve value-type. :type '(choice (const nil) (alist :key-type symbol)) - :initialize 'custom-initialize-default + :initialize 'custom-initialize-set :set #'smie-config--setter) (defun smie-config-local (rules) diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el index 849ac19d6a5..37bcfc2003d 100644 --- a/lisp/emacs-lisp/subr-x.el +++ b/lisp/emacs-lisp/subr-x.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -28,7 +28,7 @@ ;; in subr.el. ;; Do not document these functions in the lispref. -;; http://lists.gnu.org/archive/html/emacs-devel/2014-01/msg01006.html +;; https://lists.gnu.org/r/emacs-devel/2014-01/msg01006.html ;; NB If you want to use this library, it's almost always correct to use: ;; (eval-when-compile (require 'subr-x)) @@ -83,10 +83,13 @@ threading." `(internal--thread-argument nil ,@forms)) (defsubst internal--listify (elt) - "Wrap ELT in a list if it is not one." - (if (not (listp elt)) - (list elt) - elt)) + "Wrap ELT in a list if it is not one. +If ELT is of the form ((EXPR)), listify (EXPR) with a dummy symbol." + (cond + ((symbolp elt) (list elt elt)) + ((null (cdr elt)) + (list (make-symbol "s") (car elt))) + (t elt))) (defsubst internal--check-binding (binding) "Check BINDING is properly formed." @@ -98,7 +101,8 @@ threading." (defsubst internal--build-binding-value-form (binding prev-var) "Build the conditional value form for BINDING using PREV-VAR." - `(,(car binding) (and ,prev-var ,(cadr binding)))) + (let ((var (car binding))) + `(,var (and ,prev-var ,(cadr binding))))) (defun internal--build-binding (binding prev-var) "Check and build a single BINDING with PREV-VAR." @@ -117,44 +121,71 @@ threading." binding)) bindings))) -(defmacro if-let* (bindings then &rest else) +(defmacro if-let* (varlist then &rest else) "Bind variables according to VARLIST and eval THEN or ELSE. -Each binding is evaluated in turn with `let*', and evaluation -stops if a binding value is nil. If all are non-nil, the value -of THEN is returned, or the last form in ELSE is returned. -Each element of VARLIST is a symbol (which is bound to nil) -or a list (SYMBOL VALUEFORM) (which binds SYMBOL to the value of VALUEFORM). -In the special case you only want to bind a single value, -VARLIST can just be a plain tuple. -\n(fn VARLIST THEN ELSE...)" +Each binding is evaluated in turn, and evaluation stops if a +binding value is nil. If all are non-nil, the value of THEN is +returned, or the last form in ELSE is returned. + +Each element of VARLIST is a list (SYMBOL VALUEFORM) which binds +SYMBOL to the value of VALUEFORM. An element can additionally +be of the form (VALUEFORM), which is evaluated and checked for +nil; i.e. SYMBOL can be omitted if only the test result is of +interest." (declare (indent 2) - (debug ([&or (&rest [&or symbolp (symbolp form)]) (symbolp form)] + (debug ((&rest [&or symbolp (symbolp form) (form)]) form body))) - (when (and (<= (length bindings) 2) - (not (listp (car bindings)))) - ;; Adjust the single binding case - (setq bindings (list bindings))) - `(let* ,(internal--build-bindings bindings) - (if ,(car (internal--listify (car (last bindings)))) - ,then - ,@else))) + (if varlist + `(let* ,(setq varlist (internal--build-bindings varlist)) + (if ,(caar (last varlist)) + ,then + ,@else)) + `(let* () ,then))) + +(defmacro when-let* (varlist &rest body) + "Bind variables according to VARLIST and conditionally eval BODY. +Each binding is evaluated in turn, and evaluation stops if a +binding value is nil. If all are non-nil, the value of the last +form in BODY is returned. -(defmacro when-let* (bindings &rest body) +VARLIST is the same as in `if-let*'." + (declare (indent 1) (debug if-let*)) + (list 'if-let* varlist (macroexp-progn body))) + +(defmacro and-let* (varlist &rest body) "Bind variables according to VARLIST and conditionally eval BODY. -Each binding is evaluated in turn with `let*', and evaluation -stops if a binding value is nil. If all are non-nil, the value -of the last form in BODY is returned. -Each element of VARLIST is a symbol (which is bound to nil) -or a list (SYMBOL VALUEFORM) (which binds SYMBOL to the value of VALUEFORM). -In the special case you only want to bind a single value, -VARLIST can just be a plain tuple. -\n(fn VARLIST BODY...)" - (declare (indent 1) (debug if-let)) - (list 'if-let bindings (macroexp-progn body))) - -(defalias 'if-let 'if-let*) -(defalias 'when-let 'when-let*) -(defalias 'and-let* 'when-let*) +Like `when-let*', except if BODY is empty and all the bindings +are non-nil, then the result is non-nil." + (declare (indent 1) + (debug ((&rest [&or symbolp (symbolp form) (form)]) + body))) + (let (res) + (if varlist + `(let* ,(setq varlist (internal--build-bindings varlist)) + (if ,(setq res (caar (last varlist))) + ,@(or body `(,res)))) + `(let* () ,@(or body '(t)))))) + +(defmacro if-let (spec then &rest else) + "Bind variables according to SPEC and eval THEN or ELSE. +Like `if-let*' except SPEC can have the form (SYMBOL VALUEFORM)." + (declare (indent 2) + (debug ([&or (&rest [&or symbolp (symbolp form) (form)]) + (symbolp form)] + form body)) + (obsolete "use `if-let*' instead." "26.1")) + (when (and (<= (length spec) 2) + (not (listp (car spec)))) + ;; Adjust the single binding case + (setq spec (list spec))) + (list 'if-let* spec then (macroexp-progn else))) + +(defmacro when-let (spec &rest body) + "Bind variables according to SPEC and conditionally eval BODY. +Like `when-let*' except SPEC can have the form (SYMBOL VALUEFORM)." + (declare (indent 1) (debug if-let) + (obsolete "use `when-let*' instead." "26.1")) + (list 'if-let spec (macroexp-progn body))) (defsubst hash-table-empty-p (hash-table) "Check whether HASH-TABLE is empty (has 0 elements)." @@ -216,176 +247,6 @@ TRIM-LEFT and TRIM-RIGHT default to \"[ \\t\\n\\r]+\"." (substring string 0 (- (length string) (length suffix))) string)) -(defun read-multiple-choice (prompt choices) - "Ask user a multiple choice question. -PROMPT should be a string that will be displayed as the prompt. - -CHOICES is an alist where the first element in each entry is a -character to be entered, the second element is a short name for -the entry to be displayed while prompting (if there's room, it -might be shortened), and the third, optional entry is a longer -explanation that will be displayed in a help buffer if the user -requests more help. - -This function translates user input into responses by consulting -the bindings in `query-replace-map'; see the documentation of -that variable for more information. In this case, the useful -bindings are `recenter', `scroll-up', and `scroll-down'. If the -user enters `recenter', `scroll-up', or `scroll-down' responses, -perform the requested window recentering or scrolling and ask -again. - -When `use-dialog-box' is t (the default), this function can pop -up a dialog window to collect the user input. That functionality -requires `display-popup-menus-p' to return t. Otherwise, a text -dialog will be used. - -The return value is the matching entry from the CHOICES list. - -Usage example: - -\(read-multiple-choice \"Continue connecting?\" - \\='((?a \"always\") - (?s \"session only\") - (?n \"no\")))" - (let* ((altered-names nil) - (full-prompt - (format - "%s (%s): " - prompt - (mapconcat - (lambda (elem) - (let* ((name (cadr elem)) - (pos (seq-position name (car elem))) - (altered-name - (cond - ;; Not in the name string. - ((not pos) - (format "[%c] %s" (car elem) name)) - ;; The prompt character is in the name, so highlight - ;; it on graphical terminals... - ((display-supports-face-attributes-p - '(:underline t) (window-frame)) - (setq name (copy-sequence name)) - (put-text-property pos (1+ pos) - 'face 'read-multiple-choice-face - name) - name) - ;; And put it in [bracket] on non-graphical terminals. - (t - (concat - (substring name 0 pos) - "[" - (upcase (substring name pos (1+ pos))) - "]" - (substring name (1+ pos))))))) - (push (cons (car elem) altered-name) - altered-names) - altered-name)) - (append choices '((?? "?"))) - ", "))) - tchar buf wrong-char answer) - (save-window-excursion - (save-excursion - (while (not tchar) - (message "%s%s" - (if wrong-char - "Invalid choice. " - "") - full-prompt) - (setq tchar - (if (and (display-popup-menus-p) - last-input-event ; not during startup - (listp last-nonmenu-event) - use-dialog-box) - (x-popup-dialog - t - (cons prompt - (mapcar - (lambda (elem) - (cons (capitalize (cadr elem)) - (car elem))) - choices))) - (condition-case nil - (let ((cursor-in-echo-area t)) - (read-char)) - (error nil)))) - (setq answer (lookup-key query-replace-map (vector tchar) t)) - (setq tchar - (cond - ((eq answer 'recenter) - (recenter) t) - ((eq answer 'scroll-up) - (ignore-errors (scroll-up-command)) t) - ((eq answer 'scroll-down) - (ignore-errors (scroll-down-command)) t) - ((eq answer 'scroll-other-window) - (ignore-errors (scroll-other-window)) t) - ((eq answer 'scroll-other-window-down) - (ignore-errors (scroll-other-window-down)) t) - (t tchar))) - (when (eq tchar t) - (setq wrong-char nil - tchar nil)) - ;; The user has entered an invalid choice, so display the - ;; help messages. - (when (and (not (eq tchar nil)) - (not (assq tchar choices))) - (setq wrong-char (not (memq tchar '(?? ?\C-h))) - tchar nil) - (when wrong-char - (ding)) - (with-help-window (setq buf (get-buffer-create - "*Multiple Choice Help*")) - (with-current-buffer buf - (erase-buffer) - (pop-to-buffer buf) - (insert prompt "\n\n") - (let* ((columns (/ (window-width) 25)) - (fill-column 21) - (times 0) - (start (point))) - (dolist (elem choices) - (goto-char start) - (unless (zerop times) - (if (zerop (mod times columns)) - ;; Go to the next "line". - (goto-char (setq start (point-max))) - ;; Add padding. - (while (not (eobp)) - (end-of-line) - (insert (make-string (max (- (* (mod times columns) - (+ fill-column 4)) - (current-column)) - 0) - ?\s)) - (forward-line 1)))) - (setq times (1+ times)) - (let ((text - (with-temp-buffer - (insert (format - "%c: %s\n" - (car elem) - (cdr (assq (car elem) altered-names)))) - (fill-region (point-min) (point-max)) - (when (nth 2 elem) - (let ((start (point))) - (insert (nth 2 elem)) - (unless (bolp) - (insert "\n")) - (fill-region start (point-max)))) - (buffer-string)))) - (goto-char start) - (dolist (line (split-string text "\n")) - (end-of-line) - (if (bolp) - (insert line "\n") - (insert line)) - (forward-line 1))))))))))) - (when (buffer-live-p buf) - (kill-buffer buf)) - (assq tchar choices))) - (provide 'subr-x) ;;; subr-x.el ends here diff --git a/lisp/emacs-lisp/syntax.el b/lisp/emacs-lisp/syntax.el index d1d5176944c..9eb6bde7454 100644 --- a/lisp/emacs-lisp/syntax.el +++ b/lisp/emacs-lisp/syntax.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -381,10 +381,26 @@ This function should move the cursor back to some syntactically safe point (where the PPSS is equivalent to nil).") (make-obsolete-variable 'syntax-begin-function nil "25.1") -(defvar-local syntax-ppss-cache nil - "List of (POS . PPSS) pairs, in decreasing POS order.") -(defvar-local syntax-ppss-last nil - "Cache of (LAST-POS . LAST-PPSS).") +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Several caches. +;; +;; Because `syntax-ppss' is equivalent to (parse-partial-sexp +;; (POINT-MIN) x), we need either to empty the cache when we narrow +;; the buffer, which is suboptimal, or we need to use several caches. +;; We use two of them, one for widened buffer, and one for narrowing. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defvar-local syntax-ppss-wide nil + "Cons of two elements (LAST . CACHE). +Where LAST is a pair (LAST-POS . LAST-PPS) caching the last invocation +and CACHE is a list of (POS . PPSS) pairs, in decreasing POS order. +These are valid when the buffer has no restriction.") + +(defvar-local syntax-ppss-narrow nil + "Same as `syntax-ppss-wide' but for a narrowed buffer.") + +(defvar-local syntax-ppss-narrow-start nil + "Start position of the narrowing for `syntax-ppss-narrow'.") (defalias 'syntax-ppss-after-change-function 'syntax-ppss-flush-cache) (defun syntax-ppss-flush-cache (beg &rest ignored) @@ -392,24 +408,29 @@ point (where the PPSS is equivalent to nil).") ;; Set syntax-propertize to refontify anything past beg. (setq syntax-propertize--done (min beg syntax-propertize--done)) ;; Flush invalid cache entries. - (while (and syntax-ppss-cache (> (caar syntax-ppss-cache) beg)) - (setq syntax-ppss-cache (cdr syntax-ppss-cache))) - ;; Throw away `last' value if made invalid. - (when (< beg (or (car syntax-ppss-last) 0)) - ;; If syntax-begin-function jumped to BEG, then the old state at BEG can - ;; depend on the text after BEG (which is presumably changed). So if - ;; BEG=(car (nth 10 syntax-ppss-last)) don't reuse that data because the - ;; assumed nil state at BEG may not be valid any more. - (if (<= beg (or (syntax-ppss-toplevel-pos (cdr syntax-ppss-last)) - (nth 3 syntax-ppss-last) - 0)) - (setq syntax-ppss-last nil) - (setcar syntax-ppss-last nil))) - ;; Unregister if there's no cache left. Sadly this doesn't work - ;; because `before-change-functions' is temporarily bound to nil here. - ;; (unless syntax-ppss-cache - ;; (remove-hook 'before-change-functions 'syntax-ppss-flush-cache t)) - ) + (dolist (cell (list syntax-ppss-wide syntax-ppss-narrow)) + (pcase cell + (`(,last . ,cache) + (while (and cache (> (caar cache) beg)) + (setq cache (cdr cache))) + ;; Throw away `last' value if made invalid. + (when (< beg (or (car last) 0)) + ;; If syntax-begin-function jumped to BEG, then the old state at BEG can + ;; depend on the text after BEG (which is presumably changed). So if + ;; BEG=(car (nth 10 syntax-ppss-last)) don't reuse that data because the + ;; assumed nil state at BEG may not be valid any more. + (if (<= beg (or (syntax-ppss-toplevel-pos (cdr last)) + (nth 3 last) + 0)) + (setq last nil) + (setcar last nil))) + ;; Unregister if there's no cache left. Sadly this doesn't work + ;; because `before-change-functions' is temporarily bound to nil here. + ;; (unless cache + ;; (remove-hook 'before-change-functions 'syntax-ppss-flush-cache t)) + (setcar cell last) + (setcdr cell cache))) + )) (defvar syntax-ppss-stats [(0 . 0.0) (0 . 0.0) (0 . 0.0) (0 . 0.0) (0 . 0.0) (1 . 2500.0)]) @@ -423,6 +444,17 @@ point (where the PPSS is equivalent to nil).") (defvar-local syntax-ppss-table nil "Syntax-table to use during `syntax-ppss', if any.") +(defun syntax-ppss--data () + (if (eq (point-min) 1) + (progn + (unless syntax-ppss-wide + (setq syntax-ppss-wide (cons nil nil))) + syntax-ppss-wide) + (unless (eq syntax-ppss-narrow-start (point-min)) + (setq syntax-ppss-narrow-start (point-min)) + (setq syntax-ppss-narrow (cons nil nil))) + syntax-ppss-narrow)) + (defun syntax-ppss (&optional pos) "Parse-Partial-Sexp State at POS, defaulting to point. The returned value is the same as that of `parse-partial-sexp' @@ -439,10 +471,13 @@ running the hook." (syntax-propertize pos) ;; (with-syntax-table (or syntax-ppss-table (syntax-table)) - (let ((old-ppss (cdr syntax-ppss-last)) - (old-pos (car syntax-ppss-last)) - (ppss nil) - (pt-min (point-min))) + (let* ((cell (syntax-ppss--data)) + (ppss-last (car cell)) + (ppss-cache (cdr cell)) + (old-ppss (cdr ppss-last)) + (old-pos (car ppss-last)) + (ppss nil) + (pt-min (point-min))) (if (and old-pos (> old-pos pos)) (setq old-pos nil)) ;; Use the OLD-POS if usable and close. Don't update the `last' cache. (condition-case nil @@ -475,7 +510,7 @@ running the hook." ;; The OLD-* data can't be used. Consult the cache. (t (let ((cache-pred nil) - (cache syntax-ppss-cache) + (cache ppss-cache) (pt-min (point-min)) ;; I differentiate between PT-MIN and PT-BEST because ;; I feel like it might be important to ensure that the @@ -491,7 +526,7 @@ running the hook." (if cache (setq pt-min (caar cache) ppss (cdar cache))) ;; Setup the before-change function if necessary. - (unless (or syntax-ppss-cache syntax-ppss-last) + (unless (or ppss-cache ppss-last) (add-hook 'before-change-functions 'syntax-ppss-flush-cache t t)) @@ -541,7 +576,7 @@ running the hook." pt-min (setq pt-min (/ (+ pt-min pos) 2)) nil nil ppss)) (push (cons pt-min ppss) - (if cache-pred (cdr cache-pred) syntax-ppss-cache))) + (if cache-pred (cdr cache-pred) ppss-cache))) ;; Compute the actual return value. (setq ppss (parse-partial-sexp pt-min pos nil nil ppss)) @@ -562,13 +597,15 @@ running the hook." (if (> (- (caar cache-pred) pos) syntax-ppss-max-span) (push pair (cdr cache-pred)) (setcar cache-pred pair)) - (if (or (null syntax-ppss-cache) - (> (- (caar syntax-ppss-cache) pos) + (if (or (null ppss-cache) + (> (- (caar ppss-cache) pos) syntax-ppss-max-span)) - (push pair syntax-ppss-cache) - (setcar syntax-ppss-cache pair))))))))) + (push pair ppss-cache) + (setcar ppss-cache pair))))))))) - (setq syntax-ppss-last (cons pos ppss)) + (setq ppss-last (cons pos ppss)) + (setcar cell ppss-last) + (setcdr cell ppss-cache) ppss) (args-out-of-range ;; If the buffer is more narrowed than when we built the cache, @@ -582,7 +619,7 @@ running the hook." (defun syntax-ppss-debug () (let ((pt nil) (min-diffs nil)) - (dolist (x (append syntax-ppss-cache (list (cons (point-min) nil)))) + (dolist (x (append (cdr (syntax-ppss--data)) (list (cons (point-min) nil)))) (when pt (push (- pt (car x)) min-diffs)) (setq pt (car x))) min-diffs)) diff --git a/lisp/emacs-lisp/tabulated-list.el b/lisp/emacs-lisp/tabulated-list.el index b6b49b1bfa2..3889ba8e587 100644 --- a/lisp/emacs-lisp/tabulated-list.el +++ b/lisp/emacs-lisp/tabulated-list.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -186,14 +186,29 @@ If ADVANCE is non-nil, move forward by one line afterwards." Populated by `tabulated-list-init-header'.") (defvar tabulated-list--header-overlay nil) +(defun tabulated-list-line-number-width () + "Return the width taken by display-line-numbers in the current buffer." + ;; line-number-display-width returns the value for the selected + ;; window, which might not be the window in which the current buffer + ;; is displayed. + (if (not display-line-numbers) + 0 + (let ((cbuf-window (get-buffer-window (current-buffer) t))) + (if (window-live-p cbuf-window) + (with-selected-window cbuf-window + (line-number-display-width 'columns)) + 4)))) + (defun tabulated-list-init-header () "Set up header line for the Tabulated List buffer." ;; FIXME: Should share code with tabulated-list-print-col! (let ((x (max tabulated-list-padding 0)) (button-props `(help-echo "Click to sort by column" - mouse-face highlight + mouse-face header-line-highlight keymap ,tabulated-list-sort-button-map)) (cols nil)) + (if display-line-numbers + (setq x (+ x (tabulated-list-line-number-width)))) (push (propertize " " 'display `(space :align-to ,x)) cols) (dotimes (n (length tabulated-list-format)) (let* ((col (aref tabulated-list-format n)) @@ -368,7 +383,7 @@ changing `tabulated-list-sort-key'." (equal entry-id id) (setq entry-id nil saved-pt (point))) - ;; If the buffer this empty, simply print each elt. + ;; If the buffer is empty, simply print each elt. (if (or (not update) (eobp)) (apply tabulated-list-printer elt) (while (let ((local-id (tabulated-list-get-id))) @@ -582,6 +597,23 @@ With a numeric prefix argument N, sort the Nth column." (tabulated-list-init-header) (tabulated-list-print t))) +(defvar tabulated-list--current-lnum-width nil) +(defun tabulated-list-watch-line-number-width (_window) + (if display-line-numbers + (let ((lnum-width (tabulated-list-line-number-width))) + (when (not (= tabulated-list--current-lnum-width lnum-width)) + (setq-local tabulated-list--current-lnum-width lnum-width) + (tabulated-list-init-header))))) + +(defun tabulated-list-window-scroll-function (window _start) + (if display-line-numbers + (let ((lnum-width + (with-selected-window window + (line-number-display-width 'columns)))) + (when (not (= tabulated-list--current-lnum-width lnum-width)) + (setq-local tabulated-list--current-lnum-width lnum-width) + (tabulated-list-init-header))))) + ;;; The mode definition: (define-derived-mode tabulated-list-mode special-mode "Tabulated" @@ -624,7 +656,16 @@ as the ewoc pretty-printer." (setq-local glyphless-char-display tabulated-list-glyphless-char-display) ;; Avoid messing up the entries' display just because the first ;; column of the first entry happens to begin with a R2L letter. - (setq bidi-paragraph-direction 'left-to-right)) + (setq bidi-paragraph-direction 'left-to-right) + ;; This is for if/when they turn on display-line-numbers + (add-hook 'display-line-numbers-mode-hook #'tabulated-list-revert nil t) + ;; This is for if/when they customize the line-number face or when + ;; the line-number width needs to change due to scrolling. + (setq-local tabulated-list--current-lnum-width 0) + (add-hook 'pre-redisplay-functions + #'tabulated-list-watch-line-number-width nil t) + (add-hook 'window-scroll-functions + #'tabulated-list-window-scroll-function nil t)) (put 'tabulated-list-mode 'mode-class 'special) diff --git a/lisp/emacs-lisp/tcover-ses.el b/lisp/emacs-lisp/tcover-ses.el index efcaeedd117..7e4beb6743e 100644 --- a/lisp/emacs-lisp/tcover-ses.el +++ b/lisp/emacs-lisp/tcover-ses.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. (require 'testcover) diff --git a/lisp/emacs-lisp/tcover-unsafep.el b/lisp/emacs-lisp/tcover-unsafep.el index f9bf9a4c734..69ae175eff7 100644 --- a/lisp/emacs-lisp/tcover-unsafep.el +++ b/lisp/emacs-lisp/tcover-unsafep.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. (require 'testcover) diff --git a/lisp/emacs-lisp/testcover.el b/lisp/emacs-lisp/testcover.el index 433ad38a147..797cc682171 100644 --- a/lisp/emacs-lisp/testcover.el +++ b/lisp/emacs-lisp/testcover.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -33,7 +33,9 @@ ;; that has a splotch. ;; * Basic algorithm: use `edebug' to mark up the function text with -;; instrumentation callbacks, then replace edebug's callbacks with ours. +;; instrumentation callbacks, walk the instrumented code looking for +;; forms which don't return or always return the same value, then use +;; Edebug's before and after hooks to replace its code coverage with ours. ;; * To show good coverage, we want to see two values for every form, except ;; functions that always return the same value and `defconst' variables ;; need show only one value for good coverage. To avoid the brown @@ -47,11 +49,10 @@ ;; function being called is capable of returning in other cases. ;; Problems: -;; * To detect different values, we store the form's result in a vector and -;; compare the next result using `equal'. We don't copy the form's -;; result, so if caller alters it (`setcar', etc.) we'll think the next -;; call has the same value! Also, equal thinks two strings are the same -;; if they differ only in properties. +;; * `equal', which is used to compare the results of repeatedly executing +;; a form, has a couple of shortcomings. It considers strings to be the same +;; if they only differ in properties, and it raises an error when asked to +;; compare circular lists. ;; * Because we have only a "1value" class and no "always nil" class, we have ;; to treat as potentially 1-valued any `and' whose last term is 1-valued, ;; in case the last term is always nil. Example: @@ -89,16 +90,14 @@ these. This list is quite incomplete!" buffer-disable-undo buffer-enable-undo current-global-map deactivate-mark delete-backward-char delete-char delete-region ding forward-char function* insert insert-and-inherit kill-all-local-variables - kill-line kill-paragraph kill-region kill-sexp lambda + kill-line kill-paragraph kill-region kill-sexp minibuffer-complete-and-exit narrow-to-region next-line push-mark put-text-property run-hooks set-match-data signal substitute-key-definition suppress-keymap undo use-local-map while widen yank) - "Functions that always return the same value. No brown splotch is shown -for these. This list is quite incomplete! Notes: Nobody ever changes the -current global map. The macro `lambda' is self-evaluating, hence always -returns the same value (the function it defines may return varying values -when called)." + "Functions that always return the same value, according to `equal'. +No brown splotch is shown for these. This list is quite +incomplete! Notes: Nobody ever changes the current global map." :group 'testcover :type '(repeat symbol)) @@ -111,7 +110,7 @@ them as having returned nil just before calling them." (defcustom testcover-compose-functions '(+ - * / = append length list make-keymap make-sparse-keymap - mapcar message propertize replace-regexp-in-string + message propertize replace-regexp-in-string run-with-idle-timer set-buffer-modified-p) "Functions that are 1-valued if all their args are either constants or calls to one of the `testcover-1value-functions', so if that's true then no @@ -186,19 +185,18 @@ call to one of the `testcover-1value-functions'." ;;;###autoload (defun testcover-start (filename &optional byte-compile) - "Uses edebug to instrument all macros and functions in FILENAME, then -changes the instrumentation from edebug to testcover--much faster, no -problems with type-ahead or post-command-hook, etc. If BYTE-COMPILE is -non-nil, byte-compiles each function after instrumenting." + "Use Edebug to instrument for coverage all macros and functions in FILENAME. +If BYTE-COMPILE is non-nil, byte compile each function after instrumenting." (interactive "fStart covering file: ") - (let ((buf (find-file filename)) - (load-read-function load-read-function)) - (add-function :around load-read-function - #'testcover--read) - (setq edebug-form-data nil - testcover-module-constants nil - testcover-module-1value-functions nil) - (eval-buffer buf)) + (let ((buf (find-file filename))) + (setq edebug-form-data nil + testcover-module-constants nil + testcover-module-1value-functions nil + testcover-module-potentially-1value-functions nil) + (let ((edebug-all-defs t) + (edebug-after-instrumentation-function #'testcover-after-instrumentation) + (edebug-new-definition-function #'testcover-init-definition)) + (eval-buffer buf))) (when byte-compile (dolist (x (reverse edebug-form-data)) (when (fboundp (car x)) @@ -209,229 +207,10 @@ non-nil, byte-compiles each function after instrumenting." (defun testcover-this-defun () "Start coverage on function under point." (interactive) - (let ((x (let ((edebug-all-defs t)) - (symbol-function (eval-defun nil))))) - (testcover-reinstrument x) - x)) - -(defun testcover--read (orig &optional stream) - "Read a form using edebug, changing edebug callbacks to testcover callbacks." - (or stream (setq stream standard-input)) - (if (eq stream (current-buffer)) - (let ((x (let ((edebug-all-defs t)) - (edebug-read-and-maybe-wrap-form)))) - (testcover-reinstrument x) - x) - (funcall (or orig #'read) stream))) - -(defun testcover-reinstrument (form) - "Reinstruments FORM to use testcover instead of edebug. This -function modifies the list that FORM points to. Result is nil if -FORM should return multiple values, t if should always return same -value, `maybe' if either is acceptable." - (let ((fun (car-safe form)) - id val) - (cond - ((not fun) ;Atom - (when (or (not (symbolp form)) - (memq form testcover-constants) - (memq form testcover-module-constants)) - t)) - ((consp fun) ;Embedded list - (testcover-reinstrument fun) - (testcover-reinstrument-list (cdr form)) - nil) - ((or (memq fun testcover-1value-functions) - (memq fun testcover-module-1value-functions)) - ;;Should always return same value - (testcover-reinstrument-list (cdr form)) - t) - ((or (memq fun testcover-potentially-1value-functions) - (memq fun testcover-module-potentially-1value-functions)) - ;;Might always return same value - (testcover-reinstrument-list (cdr form)) - 'maybe) - ((memq fun testcover-progn-functions) - ;;1-valued if last argument is - (testcover-reinstrument-list (cdr form))) - ((memq fun testcover-prog1-functions) - ;;1-valued if first argument is - (testcover-reinstrument-list (cddr form)) - (testcover-reinstrument (cadr form))) - ((memq fun testcover-compose-functions) - ;;1-valued if all arguments are. Potentially 1-valued if all - ;;arguments are either definitely or potentially. - (testcover-reinstrument-compose (cdr form) 'testcover-reinstrument)) - ((eq fun 'edebug-enter) - ;;(edebug-enter 'SYM ARGS #'(lambda nil FORMS)) - ;; => (testcover-enter 'SYM #'(lambda nil FORMS)) - (setcar form 'testcover-enter) - (setcdr (nthcdr 1 form) (nthcdr 3 form)) - (let ((testcover-vector (get (cadr (cadr form)) 'edebug-coverage))) - (testcover-reinstrument-list (nthcdr 2 (cadr (nth 2 form)))))) - ((eq fun 'edebug-after) - ;;(edebug-after (edebug-before XXX) YYY FORM) - ;; => (testcover-after YYY FORM), mark XXX as ok-coverage - (unless (eq (cadr form) 0) - (aset testcover-vector (cadr (cadr form)) 'ok-coverage)) - (setq id (nth 2 form)) - (setcdr form (nthcdr 2 form)) - (setq val (testcover-reinstrument (nth 2 form))) - (setcar form (if (eq val t) - 'testcover-1value - 'testcover-after)) - (when val - ;;1-valued or potentially 1-valued - (aset testcover-vector id '1value)) - (cond - ((memq (car-safe (nth 2 form)) testcover-noreturn-functions) - ;;This function won't return, so set the value in advance - ;;(edebug-after (edebug-before XXX) YYY FORM) - ;; => (progn (edebug-after YYY nil) FORM) - (setcar (cdr form) `(,(car form) ,id nil)) - (setcar form 'progn) - (aset testcover-vector id '1value) - (setq val t)) - ((eq (car-safe (nth 2 form)) '1value) - ;;This function is always supposed to return the same value - (setq val t) - (aset testcover-vector id '1value) - (setcar form 'testcover-1value))) - val) - ((eq fun 'defun) - (setq val (testcover-reinstrument-list (nthcdr 3 form))) - (when (eq val t) - (push (cadr form) testcover-module-1value-functions)) - (when (eq val 'maybe) - (push (cadr form) testcover-module-potentially-1value-functions))) - ((memq fun '(defconst defcustom)) - ;;Define this symbol as 1-valued - (push (cadr form) testcover-module-constants) - (testcover-reinstrument-list (cddr form))) - ((memq fun '(dotimes dolist)) - ;;Always returns third value from SPEC - (testcover-reinstrument-list (cddr form)) - (setq val (testcover-reinstrument-list (cadr form))) - (if (nth 2 (cadr form)) - val - ;;No third value, always returns nil - t)) - ((memq fun '(let let*)) - ;;Special parsing for second argument - (mapc 'testcover-reinstrument-list (cadr form)) - (testcover-reinstrument-list (cddr form))) - ((eq fun 'if) - ;;Potentially 1-valued if both THEN and ELSE clauses are - (testcover-reinstrument (cadr form)) - (let ((then (testcover-reinstrument (nth 2 form))) - (else (testcover-reinstrument-list (nthcdr 3 form)))) - (and then else 'maybe))) - ((eq fun 'cond) - ;;Potentially 1-valued if all clauses are - (when (testcover-reinstrument-compose (cdr form) - 'testcover-reinstrument-list) - 'maybe)) - ((eq fun 'condition-case) - ;;Potentially 1-valued if BODYFORM is and all HANDLERS are - (let ((body (testcover-reinstrument (nth 2 form))) - (errs (testcover-reinstrument-compose - (mapcar #'cdr (nthcdr 3 form)) - 'testcover-reinstrument-list))) - (and body errs 'maybe))) - ((eq fun 'quote) - ;;Don't reinstrument what's inside! - ;;This doesn't apply within a backquote - t) - ((eq fun '\`) - ;;Quotes are not special within backquotes - (let ((testcover-1value-functions - (cons 'quote testcover-1value-functions))) - (testcover-reinstrument (cadr form)))) - ((eq fun '\,) - ;;In commas inside backquotes, quotes are special again - (let ((testcover-1value-functions - (remq 'quote testcover-1value-functions))) - (testcover-reinstrument (cadr form)))) - ((eq fun '1value) - ;;Hack - pretend the arg is 1-valued here - (cond - ((symbolp (cadr form)) - ;;A pseudoconstant variable - t) - ((and (eq (car (cadr form)) 'edebug-after) - (symbolp (nth 3 (cadr form)))) - ;;Reference to pseudoconstant - (aset testcover-vector (nth 2 (cadr form)) '1value) - (setcar (cdr form) `(testcover-1value ,(nth 2 (cadr form)) - ,(nth 3 (cadr form)))) - t) - (t - (setq id (car (if (eq (car (cadr form)) 'edebug-after) - (nth 3 (cadr form)) - (cadr form)))) - (let ((testcover-1value-functions - (cons id testcover-1value-functions))) - (testcover-reinstrument (cadr form)))))) - ((eq fun 'noreturn) - ;;Hack - pretend the arg has no return - (cond - ((symbolp (cadr form)) - ;;A pseudoconstant variable - 'maybe) - ((and (eq (car (cadr form)) 'edebug-after) - (symbolp (nth 3 (cadr form)))) - ;;Reference to pseudoconstant - (aset testcover-vector (nth 2 (cadr form)) '1value) - (setcar (cdr form) `(progn (testcover-after ,(nth 2 (cadr form)) nil) - ,(nth 3 (cadr form)))) - 'maybe) - (t - (setq id (car (if (eq (car (cadr form)) 'edebug-after) - (nth 3 (cadr form)) - (cadr form)))) - (let ((testcover-noreturn-functions - (cons id testcover-noreturn-functions))) - (testcover-reinstrument (cadr form)))))) - ((and (eq fun 'apply) - (eq (car-safe (cadr form)) 'quote) - (symbolp (cadr (cadr form)))) - ;;Apply of a constant symbol. Process as 1value or noreturn - ;;depending on symbol. - (setq fun (cons (cadr (cadr form)) (cddr form)) - val (testcover-reinstrument fun)) - (setcdr (cdr form) (cdr fun)) - val) - (t ;Some other function or weird thing - (testcover-reinstrument-list (cdr form)) - nil)))) - -(defun testcover-reinstrument-list (list) - "Reinstruments each form in LIST to use testcover instead of edebug. -This function modifies the forms in LIST. Result is `testcover-reinstrument's -value for the last form in LIST. If the LIST is empty, its evaluation will -always be nil, so we return t for 1-valued." - (let ((result t)) - (while (consp list) - (setq result (testcover-reinstrument (pop list)))) - result)) - -(defun testcover-reinstrument-compose (list fun) - "For a compositional function, the result is 1-valued if all -arguments are, potentially 1-valued if all arguments are either -definitely or potentially 1-valued, and multi-valued otherwise. -FUN should be `testcover-reinstrument' for compositional functions, - `testcover-reinstrument-list' for clauses in a `cond'." - (let ((result t)) - (mapc #'(lambda (x) - (setq x (funcall fun x)) - (cond - ((eq result t) - (setq result x)) - ((eq result 'maybe) - (when (not x) - (setq result nil))))) - list) - result)) + (let ((edebug-all-defs t) + (edebug-after-instrumentation-function #'testcover-after-instrumentation) + (edebug-new-definition-function #'testcover-init-definition)) + (eval-defun nil))) (defun testcover-end (filename) "Turn off instrumentation of all macros and functions in FILENAME." @@ -444,42 +223,108 @@ FUN should be `testcover-reinstrument' for compositional functions, ;;; Accumulate coverage data ;;;========================================================================= -(defun testcover-enter (testcover-sym testcover-fun) - "Internal function for coverage testing. Invokes TESTCOVER-FUN while -binding `testcover-vector' to the code-coverage vector for TESTCOVER-SYM -\(the name of the current function)." - (let ((testcover-vector (get testcover-sym 'edebug-coverage))) - (funcall testcover-fun))) - -(defun testcover-after (idx val) - "Internal function for coverage testing. Returns VAL after installing it in -`testcover-vector' at offset IDX." - (declare (gv-expander (lambda (do) - (gv-letplace (getter setter) val - (funcall do getter - (lambda (store) - `(progn (testcover-after ,idx ,getter) - ,(funcall setter store)))))))) - (cond - ((eq (aref testcover-vector idx) 'unknown) - (aset testcover-vector idx val)) - ((not (equal (aref testcover-vector idx) val)) - (aset testcover-vector idx 'ok-coverage))) - val) - -(defun testcover-1value (idx val) - "Internal function for coverage testing. Returns VAL after installing it in -`testcover-vector' at offset IDX. Error if FORM does not always return the -same value during coverage testing." - (cond - ((eq (aref testcover-vector idx) '1value) - (aset testcover-vector idx (cons '1value val))) - ((not (and (eq (car-safe (aref testcover-vector idx)) '1value) - (equal (cdr (aref testcover-vector idx)) val))) - (error "Value of form marked with `1value' does vary: %s" val))) - val) - - +(defun testcover-after-instrumentation (form) + "Analyze FORM for code coverage." + (testcover-analyze-coverage form) + form) + +(defun testcover-init-definition (sym) + "Mark SYM as under test coverage." + (message "Testcover: %s" sym) + (put sym 'edebug-behavior 'testcover)) + +(defun testcover-enter (func _args body) + "Begin execution of a function under coverage testing. +Bind `testcover-vector' to the code-coverage vector for FUNC and +return the result of evaluating BODY." + (let ((testcover-vector (get func 'edebug-coverage))) + (funcall body))) + +(defun testcover-before (before-index) + "Update code coverage before a form is evaluated. +BEFORE-INDEX is the form's index into the code-coverage vector." + (let ((before-entry (aref testcover-vector before-index))) + (when (eq (car-safe before-entry) 'noreturn) + (let* ((after-index (cdr before-entry))) + (aset testcover-vector after-index 'ok-coverage))))) + +(defun testcover-after (_before-index after-index value) + "Update code coverage with the result of a form's evaluation. +AFTER-INDEX is the form's index into the code-coverage +vector. Return VALUE." + (let ((old-result (aref testcover-vector after-index))) + (cond + ((eq 'unknown old-result) + (aset testcover-vector after-index (testcover--copy-object value))) + ((eq 'maybe old-result) + (aset testcover-vector after-index 'ok-coverage)) + ((eq '1value old-result) + (aset testcover-vector after-index + (cons old-result (testcover--copy-object value)))) + ((and (eq (car-safe old-result) '1value) + (not (condition-case () + (equal (cdr old-result) value) + (circular-list t)))) + (error "Value of form expected to be constant does vary, from %s to %s" + old-result value)) + ;; Test if a different result. + ((not (condition-case () + (equal value old-result) + (circular-list nil))) + (aset testcover-vector after-index 'ok-coverage)))) + value) + +;; Add these behaviors to Edebug. +(unless (assoc 'testcover edebug-behavior-alist) + (push '(testcover testcover-enter testcover-before testcover-after) + edebug-behavior-alist)) + +(defun testcover--copy-object (obj) + "Make a copy of OBJ. +If OBJ is a cons cell, copy both its car and its cdr. +Contrast to `copy-tree' which does the same but fails on circular +structures, and `copy-sequence', which copies only along the +cdrs. Copy vectors as well as conses." + (let ((ht (make-hash-table :test 'eq))) + (testcover--copy-object1 obj t ht))) + +(defun testcover--copy-object1 (obj vecp hash-table) + "Make a copy of OBJ, using a HASH-TABLE of objects already copied. +If OBJ is a cons cell, this recursively copies its car and +iteratively copies its cdr. When VECP is non-nil, copy +vectors as well as conses." + (if (and (atom obj) (or (not vecp) (not (vectorp obj)))) + obj + (let ((copy (gethash obj hash-table nil))) + (unless copy + (cond + ((consp obj) + (let* ((rest obj) current) + (setq copy (cons nil nil) + current copy) + (while + (progn + (puthash rest current hash-table) + (setf (car current) + (testcover--copy-object1 (car rest) vecp hash-table)) + (setq rest (cdr rest)) + (cond + ((atom rest) + (setf (cdr current) + (testcover--copy-object1 rest vecp hash-table)) + nil) + ((gethash rest hash-table nil) + (setf (cdr current) (gethash rest hash-table nil)) + nil) + (t (setq current + (setf (cdr current) (cons nil nil))))))))) + (t ; (and vecp (vectorp obj)) is true due to test in if above. + (setq copy (copy-sequence obj)) + (puthash obj copy hash-table) + (dotimes (i (length copy)) + (aset copy i + (testcover--copy-object1 (aref copy i) vecp hash-table)))))) + copy))) ;;;========================================================================= ;;; Display the coverage data as color splotches on your code. @@ -511,12 +356,13 @@ eliminated by adding more test cases." (while (> len 0) (setq len (1- len) data (aref coverage len)) - (when (and (not (eq data 'ok-coverage)) - (not (eq (car-safe data) '1value)) - (setq j (+ def-mark (aref points len)))) + (when (and (not (eq data 'ok-coverage)) + (not (memq (car-safe data) + '(1value maybe noreturn))) + (setq j (+ def-mark (aref points len)))) (setq ov (make-overlay (1- j) j)) (overlay-put ov 'face - (if (memq data '(unknown 1value)) + (if (memq data '(unknown maybe 1value)) 'testcover-nohits 'testcover-1value)))) (set-buffer-modified-p changed)))) @@ -547,4 +393,284 @@ coverage tests. This function creates many overlays." (goto-char (next-overlay-change (point))) (end-of-line)) + +;;; Coverage Analysis + +;; The top level function for initializing code coverage is +;; `testcover-analyze-coverage', which recursively walks the form it is +;; passed, which should have already been instrumented by +;; edebug-read-and-maybe-wrap-form, and initializes the associated +;; code coverage vectors, which should have already been created by +;; `edebug-clear-coverage'. +;; +;; The purpose of the analysis is to identify forms which can only +;; ever return a single value. These forms can be considered to have +;; adequate code coverage even if only executed once. In addition, +;; forms which will never return, such as error signals, can be +;; identified and treated correctly. +;; +;; The code coverage vector entries for the beginnings of forms will +;; be changed to `ok-coverage.', except for the beginnings of forms +;; which should never return, which will be changed to +;; (noreturn . AFTER-INDEX) so that testcover-before can set the entry +;; for the end of the form just before it is executed. +;; +;; Entries for the ends of forms may be changed to `1value' if +;; analysis determines the form will only ever return a single value, +;; or `maybe' if the form could potentially only ever return a single +;; value. +;; +;; An example of a potentially 1-valued form is an `and' whose last +;; term is 1-valued, in case the last term is always nil. Example: +;; +;; (and (< (point) 1000) (forward-char 10)) +;; +;; This form always returns nil. Similarly, `or', `if', and `cond' +;; are treated as potentially 1-valued if all clauses are, in case +;; those values are always nil. Unlike truly 1-valued functions, it +;; is not an error if these "potentially" 1-valued forms actually +;; return differing values. + +(defun testcover-analyze-coverage (form) + "Analyze FORM and initialize coverage vectors for definitions found within. +Return 1value, maybe or nil depending on if the form is determined +to return only a single value, potentially return only a single value, +or return multiple values." + (pcase form + (`(edebug-enter ',sym ,_ (function (lambda nil . ,body))) + (let ((testcover-vector (get sym 'edebug-coverage))) + (testcover-analyze-coverage-progn body))) + + (`(edebug-after ,(and before-form + (or `(edebug-before ,before-id) before-id)) + ,after-id ,wrapped-form) + (testcover-analyze-coverage-edebug-after + form before-form before-id after-id wrapped-form)) + + (`(defconst ,sym . ,args) + (push sym testcover-module-constants) + (testcover-analyze-coverage-progn args) + '1value) + + (`(defun ,name ,_ . ,doc-and-body) + (let ((val (testcover-analyze-coverage-progn doc-and-body))) + (cl-case val + ((1value) (push name testcover-module-1value-functions)) + ((maybe) (push name testcover-module-potentially-1value-functions))) + nil)) + + (`(quote . ,_) + ;; A quoted form is 1value. Edebug could have instrumented + ;; something inside the form if an Edebug spec contained a quote. + ;; It's also possible that the quoted form is a circular object. + ;; To avoid infinite recursion, don't examine quoted objects. + ;; This will cause the coverage marks on an instrumented quoted + ;; form to look odd. See bug#25316. + '1value) + + (`(\` ,bq-form) + (testcover-analyze-coverage-backquote-form bq-form)) + + ((or 't 'nil (pred keywordp)) + '1value) + + ((pred vectorp) + (testcover-analyze-coverage-compose (append form nil) + #'testcover-analyze-coverage)) + + ((pred symbolp) + nil) + + ((pred atom) + '1value) + + (_ + ;; Whatever we have here, it's not wrapped, so treat it as a list of forms. + (testcover-analyze-coverage-compose form #'testcover-analyze-coverage)))) + +(defun testcover-analyze-coverage-progn (forms) + "Analyze FORMS, which should be a list of forms, for code coverage. +Analyze all the forms in FORMS and return 1value, maybe or nil +depending on the analysis of the last one. Find the coverage +vectors referenced by `edebug-enter' forms nested within FORMS and +update them with the results of the analysis." + (let ((result '1value)) + (while (consp forms) + (setq result (testcover-analyze-coverage (pop forms)))) + result)) + +(defun testcover-analyze-coverage-edebug-after (_form before-form before-id + after-id wrapped-form + &optional wrapper) + "Analyze a _FORM wrapped by `edebug-after' for code coverage. +_FORM should be either: + (edebug-after (edebug-before BEFORE-ID) AFTER-ID WRAPPED-FORM) +or: + (edebug-after 0 AFTER-ID WRAPPED-FORM) + +where BEFORE-FORM is bound to either (edebug-before BEFORE-ID) or +0. WRAPPER may be 1value or noreturn, and if so it forces the +form to be treated accordingly." + (let (val) + (unless (eql before-form 0) + (aset testcover-vector before-id 'ok-coverage)) + + (setq val (testcover-analyze-coverage-wrapped-form wrapped-form)) + (when (or (eq wrapper '1value) val) + ;; The form is 1-valued or potentially 1-valued. + (aset testcover-vector after-id (or val '1value))) + + (cond + ((or (eq wrapper 'noreturn) + (memq (car-safe wrapped-form) testcover-noreturn-functions)) + ;; This function won't return, so indicate to testcover-before that + ;; it should record coverage. + (aset testcover-vector before-id (cons 'noreturn after-id)) + (aset testcover-vector after-id '1value) + (setq val '1value)) + + ((eq (car-safe wrapped-form) '1value) + ;; This function is always supposed to return the same value. + (setq val '1value) + (aset testcover-vector after-id '1value))) + val)) + +(defun testcover-analyze-coverage-wrapped-form (form) + "Analyze a FORM for code coverage which was wrapped by `edebug-after'. +FORM is treated as if it will be evaluated." + (pcase form + ((pred keywordp) + '1value) + ((pred symbolp) + (when (or (memq form testcover-constants) + (memq form testcover-module-constants)) + '1value)) + ((pred atom) + '1value) + (`(\` ,bq-form) + (testcover-analyze-coverage-backquote-form bq-form)) + (`(defconst ,sym ,val . ,_) + (push sym testcover-module-constants) + (testcover-analyze-coverage val) + '1value) + (`(,(or 'dotimes 'dolist) (,_ ,expr . ,result) . ,body) + ;; These always return RESULT if provided. + (testcover-analyze-coverage expr) + (testcover-analyze-coverage-progn body) + (let ((val (testcover-analyze-coverage-progn result))) + ;; If the third value is not present, the loop always returns nil. + (if result val '1value))) + (`(,(or 'let 'let*) ,bindings . ,body) + (testcover-analyze-coverage-progn bindings) + (testcover-analyze-coverage-progn body)) + (`(if ,test ,then-form . ,else-body) + ;; `if' is potentially 1-valued if both THEN and ELSE clauses are. + (testcover-analyze-coverage test) + (let ((then (testcover-analyze-coverage then-form)) + (else (testcover-analyze-coverage else-body))) + (and then else 'maybe))) + (`(cond . ,clauses) + ;; `cond' is potentially 1-valued if all clauses are. + (when (testcover-analyze-coverage-compose clauses #'testcover-analyze-coverage-progn) + 'maybe)) + (`(condition-case ,_ ,body-form . ,handlers) + ;; `condition-case' is potentially 1-valued if BODY-FORM is and all + ;; HANDLERS are. + (let ((body (testcover-analyze-coverage body-form)) + (errs (testcover-analyze-coverage-compose + (mapcar #'cdr handlers) + #'testcover-analyze-coverage-progn))) + (and body errs 'maybe))) + (`(apply (quote ,(and func (pred symbolp))) . ,args) + ;; Process application of a constant symbol as 1value or noreturn + ;; depending on the symbol. + (let ((temp-form (cons func args))) + (testcover-analyze-coverage-wrapped-form temp-form))) + (`(,(and func (or '1value 'noreturn)) ,inner-form) + ;; 1value and noreturn change how the edebug-after they wrap is handled. + (let ((val (if (eq func '1value) '1value 'maybe))) + (pcase inner-form + (`(edebug-after ,(and before-form + (or `(edebug-before ,before-id) before-id)) + ,after-id ,wrapped-form) + (testcover-analyze-coverage-edebug-after inner-form before-form + before-id after-id + wrapped-form func)) + (_ (testcover-analyze-coverage inner-form))) + val)) + (`(,func . ,args) + (testcover-analyze-coverage-wrapped-application func args)))) + +(defun testcover-analyze-coverage-wrapped-application (func args) + "Analyze the application of FUNC to ARGS for code coverage." + (cond + ((eq func 'quote) '1value) + ((or (memq func testcover-1value-functions) + (memq func testcover-module-1value-functions)) + ;; The function should always return the same value. + (testcover-analyze-coverage-progn args) + '1value) + ((or (memq func testcover-potentially-1value-functions) + (memq func testcover-module-potentially-1value-functions)) + ;; The function might always return the same value. + (testcover-analyze-coverage-progn args) + 'maybe) + ((memq func testcover-progn-functions) + ;; The function is 1-valued if the last argument is. + (testcover-analyze-coverage-progn args)) + ((memq func testcover-prog1-functions) + ;; The function is 1-valued if first argument is. + (testcover-analyze-coverage-progn (cdr args)) + (testcover-analyze-coverage (car args))) + ((memq func testcover-compose-functions) + ;; The function is 1-valued if all arguments are, and potentially + ;; 1-valued if all arguments are either definitely or potentially. + (testcover-analyze-coverage-compose args #'testcover-analyze-coverage)) + (t (testcover-analyze-coverage-progn args) + nil))) + +(defun testcover-coverage-combine (result val) + "Combine RESULT with VAL and return the new result. +If either argument is nil, return nil, otherwise if either +argument is maybe, return maybe. Return 1value only if both arguments +are 1value." + (cl-case val + (1value result) + (maybe (and result 'maybe)) + (nil nil))) + +(defun testcover-analyze-coverage-compose (forms func) + "Analyze a list of FORMS for code coverage using FUNC. +The list is 1valued if all of its constituent elements are also 1valued." + (let ((result '1value)) + (dolist (form forms) + (let ((val (funcall func form))) + (setq result (testcover-coverage-combine result val)))) + result)) + +(defun testcover-analyze-coverage-backquote (bq-list) + "Analyze BQ-LIST, the body of a backquoted list, for code coverage." + (let ((result '1value)) + (while (consp bq-list) + (let ((form (car bq-list)) + val) + (if (memq form (list '\, '\,@)) + ;; Correctly handle `(foo bar . ,(baz). + (progn + (setq val (testcover-analyze-coverage (cdr bq-list))) + (setq bq-list nil)) + (setq val (testcover-analyze-coverage-backquote-form form)) + (setq bq-list (cdr bq-list))) + (setq result (testcover-coverage-combine result val)))) + result)) + +(defun testcover-analyze-coverage-backquote-form (form) + "Analyze a single FORM from a backquoted list for code coverage." + (cond + ((vectorp form) (testcover-analyze-coverage-backquote (append form nil))) + ((atom form) '1value) + ((memq (car form) (list '\, '\,@)) + (testcover-analyze-coverage (cadr form))) + (t (testcover-analyze-coverage-backquote form)))) + ;; testcover.el ends here. diff --git a/lisp/emacs-lisp/thunk.el b/lisp/emacs-lisp/thunk.el index f4c075d22ce..895fa86722d 100644 --- a/lisp/emacs-lisp/thunk.el +++ b/lisp/emacs-lisp/thunk.el @@ -22,16 +22,16 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; ;; Thunk provides functions and macros to delay the evaluation of ;; forms. ;; -;; Use `thunk-delay' to delay the evaluation of a form, and -;; `thunk-force' to evaluate it. The result of the evaluation is -;; cached, and only happens once. +;; Use `thunk-delay' to delay the evaluation of a form (requires +;; lexical-binding), and `thunk-force' to evaluate it. The result of +;; the evaluation is cached, and only happens once. ;; ;; Here is an example of a form which evaluation is delayed: ;; @@ -41,12 +41,19 @@ ;; following: ;; ;; (thunk-force delayed) +;; +;; This file also defines macros `thunk-let' and `thunk-let*' that are +;; analogous to `let' and `let*' but provide lazy evaluation of +;; bindings by using thunks implicitly (i.e. in the expansion). ;;; Code: +(eval-when-compile (require 'cl-macs)) + (defmacro thunk-delay (&rest body) "Delay the evaluation of BODY." (declare (debug t)) + (cl-assert lexical-binding) (let ((forced (make-symbol "forced")) (val (make-symbol "val"))) `(let (,forced ,val) @@ -68,5 +75,60 @@ with the same DELAYED argument." "Return non-nil if DELAYED has been evaluated." (funcall delayed t)) +(defmacro thunk-let (bindings &rest body) + "Like `let' but create lazy bindings. + +BINDINGS is a list of elements of the form (SYMBOL EXPRESSION). +Any binding EXPRESSION is not evaluated before the variable +SYMBOL is used for the first time when evaluating the BODY. + +It is not allowed to set `thunk-let' or `thunk-let*' bound +variables. + +Using `thunk-let' and `thunk-let*' requires `lexical-binding'." + (declare (indent 1) (debug let)) + (cl-callf2 mapcar + (lambda (binding) + (pcase binding + (`(,(pred symbolp) ,_) binding) + (_ (signal 'error (cons "Bad binding in thunk-let" + (list binding)))))) + bindings) + (cl-callf2 mapcar + (pcase-lambda (`(,var ,binding)) + (list (make-symbol (concat (symbol-name var) "-thunk")) + var binding)) + bindings) + `(let ,(mapcar + (pcase-lambda (`(,thunk-var ,_var ,binding)) + `(,thunk-var (thunk-delay ,binding))) + bindings) + (cl-symbol-macrolet + ,(mapcar (pcase-lambda (`(,thunk-var ,var ,_binding)) + `(,var (thunk-force ,thunk-var))) + bindings) + ,@body))) + +(defmacro thunk-let* (bindings &rest body) + "Like `let*' but create lazy bindings. + +BINDINGS is a list of elements of the form (SYMBOL EXPRESSION). +Any binding EXPRESSION is not evaluated before the variable +SYMBOL is used for the first time when evaluating the BODY. + +It is not allowed to set `thunk-let' or `thunk-let*' bound +variables. + +Using `thunk-let' and `thunk-let*' requires `lexical-binding'." + (declare (indent 1) (debug let)) + (cl-reduce + (lambda (expr binding) `(thunk-let (,binding) ,expr)) + (nreverse bindings) + :initial-value (macroexp-progn body))) + +;; (defalias 'lazy-let #'thunk-let) +;; (defalias 'lazy-let* #'thunk-let*) + + (provide 'thunk) ;;; thunk.el ends here diff --git a/lisp/emacs-lisp/timer-list.el b/lisp/emacs-lisp/timer-list.el index 1a38254bcba..69c67419835 100644 --- a/lisp/emacs-lisp/timer-list.el +++ b/lisp/emacs-lisp/timer-list.el @@ -18,14 +18,14 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;;; Code: ;;;###autoload -(defun timer-list (&optional _ignore-auto _nonconfirm) +(defun list-timers (&optional _ignore-auto _nonconfirm) "List all timers in a buffer." (interactive) (pop-to-buffer-same-window (get-buffer-create "*timer-list*")) @@ -35,9 +35,7 @@ (dolist (timer (append timer-list timer-idle-list)) (insert (format "%4s %10s %8s %s" ;; Idle. - (if (aref timer 7) - "*" - " ") + (if (aref timer 7) "*" " ") ;; Next time. (let ((time (float-time (list (aref timer 1) (aref timer 2) @@ -59,16 +57,9 @@ (t (format "%s" repeat)))) ;; Function. - (let ((function (aref timer 5))) - (replace-regexp-in-string - "\n" " " - (cond - ((byte-code-function-p function) - (replace-regexp-in-string - "[^-A-Za-z0-9 ]" "" - (format "%s" function))) - (t - (format "%s" function))))))) + (let ((cl-print-compiled 'static) + (cl-print-compiled-button nil)) + (cl-prin1-to-string (aref timer 5))))) (put-text-property (line-beginning-position) (1+ (line-beginning-position)) 'timer timer) @@ -76,21 +67,24 @@ (goto-char (point-min))) ;; This command can be destructive if they don't know what they are ;; doing. Kids, don't try this at home! -;;;###autoload (put 'timer-list 'disabled "Beware: manually canceling timers can ruin your Emacs session.") +;;;###autoload (put 'list-timers 'disabled "Beware: manually canceling timers can ruin your Emacs session.") (defvar timer-list-mode-map (let ((map (make-sparse-keymap))) (define-key map "c" 'timer-list-cancel) + (define-key map "n" 'next-line) + (define-key map "p" 'previous-line) (easy-menu-define nil map "" '("Timers" ["Cancel" timer-list-cancel t])) map)) -(define-derived-mode timer-list-mode special-mode "timer-list" +(define-derived-mode timer-list-mode special-mode "Timer-List" "Mode for listing and controlling timers." + (setq bidi-paragraph-direction 'left-to-right) (setq truncate-lines t) (buffer-disable-undo) - (setq-local revert-buffer-function 'timer-list) + (setq-local revert-buffer-function #'list-timers) (setq buffer-read-only t) (setq header-line-format (format "%4s %10s %8s %s" diff --git a/lisp/emacs-lisp/timer.el b/lisp/emacs-lisp/timer.el index d872256dad4..1de3043cd94 100644 --- a/lisp/emacs-lisp/timer.el +++ b/lisp/emacs-lisp/timer.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emacs-lisp/tq.el b/lisp/emacs-lisp/tq.el index 3f5d78df31c..31bb9d1b965 100644 --- a/lisp/emacs-lisp/tq.el +++ b/lisp/emacs-lisp/tq.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emacs-lisp/trace.el b/lisp/emacs-lisp/trace.el index 1c57d7363c2..4a83937acb3 100644 --- a/lisp/emacs-lisp/trace.el +++ b/lisp/emacs-lisp/trace.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; LCD Archive Entry: ;; trace|Hans Chalupsky|hans@cs.buffalo.edu| diff --git a/lisp/emacs-lisp/unsafep.el b/lisp/emacs-lisp/unsafep.el index 1ab65a044e0..88f053d9f73 100644 --- a/lisp/emacs-lisp/unsafep.el +++ b/lisp/emacs-lisp/unsafep.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emacs-lisp/warnings.el b/lisp/emacs-lisp/warnings.el index 671d2795c37..2765877f3ef 100644 --- a/lisp/emacs-lisp/warnings.el +++ b/lisp/emacs-lisp/warnings.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emacs-lock.el b/lisp/emacs-lock.el index dbf6ac88443..6624c99cdb5 100644 --- a/lisp/emacs-lock.el +++ b/lisp/emacs-lock.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emulation/cua-base.el b/lisp/emulation/cua-base.el index 63b8e9bf934..9d97fee4e3d 100644 --- a/lisp/emulation/cua-base.el +++ b/lisp/emulation/cua-base.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emulation/cua-gmrk.el b/lisp/emulation/cua-gmrk.el index baa430e5b79..9d51f4a7171 100644 --- a/lisp/emulation/cua-gmrk.el +++ b/lisp/emulation/cua-gmrk.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emulation/cua-rect.el b/lisp/emulation/cua-rect.el index 3538181dfad..339203414ee 100644 --- a/lisp/emulation/cua-rect.el +++ b/lisp/emulation/cua-rect.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Acknowledgments diff --git a/lisp/emulation/edt-lk201.el b/lisp/emulation/edt-lk201.el index 9afb25ca099..354d2889853 100644 --- a/lisp/emulation/edt-lk201.el +++ b/lisp/emulation/edt-lk201.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emulation/edt-mapper.el b/lisp/emulation/edt-mapper.el index 457ad55dd6c..963da2ba59e 100644 --- a/lisp/emulation/edt-mapper.el +++ b/lisp/emulation/edt-mapper.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/emulation/edt-pc.el b/lisp/emulation/edt-pc.el index 3ea249fe79a..c59ad9799da 100644 --- a/lisp/emulation/edt-pc.el +++ b/lisp/emulation/edt-pc.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emulation/edt-vt100.el b/lisp/emulation/edt-vt100.el index a8c186b166b..98f51dabc10 100644 --- a/lisp/emulation/edt-vt100.el +++ b/lisp/emulation/edt-vt100.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emulation/edt.el b/lisp/emulation/edt.el index a6b2d785ac5..bdb606c69ed 100644 --- a/lisp/emulation/edt.el +++ b/lisp/emulation/edt.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. diff --git a/lisp/emulation/keypad.el b/lisp/emulation/keypad.el index 0c7135e78b9..aea2440627d 100644 --- a/lisp/emulation/keypad.el +++ b/lisp/emulation/keypad.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emulation/viper-cmd.el b/lisp/emulation/viper-cmd.el index a697aa7d032..21200ae02a7 100644 --- a/lisp/emulation/viper-cmd.el +++ b/lisp/emulation/viper-cmd.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emulation/viper-ex.el b/lisp/emulation/viper-ex.el index ca067033e63..7d52d5a3a1c 100644 --- a/lisp/emulation/viper-ex.el +++ b/lisp/emulation/viper-ex.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -401,13 +401,14 @@ reversed." (setq viper-ex-work-buf (get-buffer-create viper-ex-work-buf-name)) (set-buffer viper-ex-work-buf) (skip-chars-forward " \t|") - (let ((case-fold-search t)) - (cond ((looking-at "#") + (let ((case-fold-search t) + (char (following-char))) + (cond ((= char ?#) (setq ex-token-type 'command) - (setq ex-token (char-to-string (following-char))) + (setq ex-token (char-to-string char)) (forward-char 1)) ((looking-at "[a-z]") (viper-get-ex-com-subr)) - ((looking-at "\\.") + ((= char ?.) (forward-char 1) (setq ex-token-type 'dot)) ((looking-at "[0-9]") @@ -419,13 +420,13 @@ reversed." (t 'abs-number))) (setq ex-token (string-to-number (buffer-substring (point) (mark t))))) - ((looking-at "\\$") + ((= char ?$) (forward-char 1) (setq ex-token-type 'end)) - ((looking-at "%") + ((= char ?%) (forward-char 1) (setq ex-token-type 'whole)) - ((looking-at "+") + ((= char ?+) (cond ((or (looking-at "+[-+]") (looking-at "+[\n|]")) (forward-char 1) (insert "1") @@ -436,7 +437,7 @@ reversed." (setq ex-token-type 'plus)) (t (error viper-BadAddress)))) - ((looking-at "-") + ((= char ?-) (cond ((or (looking-at "-[-+]") (looking-at "-[\n|]")) (forward-char 1) (insert "1") @@ -447,7 +448,7 @@ reversed." (setq ex-token-type 'minus)) (t (error viper-BadAddress)))) - ((looking-at "/") + ((= char ?/) (forward-char 1) (set-mark (point)) (let ((cont t)) @@ -459,9 +460,9 @@ reversed." (setq cont nil)))) (backward-char 1) (setq ex-token (buffer-substring (point) (mark t))) - (if (looking-at "/") (forward-char 1)) + (when (= (following-char) ?/) (forward-char 1)) (setq ex-token-type 'search-forward)) - ((looking-at "\\?") + ((= char ??) (forward-char 1) (set-mark (point)) (let ((cont t)) @@ -472,27 +473,27 @@ reversed." (line-beginning-position 0))) (setq cont nil)) (backward-char 1) - (if (not (looking-at "\n")) (forward-char 1)))) + (when (/= (following-char) ?\n) (forward-char 1)))) (setq ex-token-type 'search-backward) (setq ex-token (buffer-substring (1- (point)) (mark t)))) - ((looking-at ",") + ((= char ?,) (forward-char 1) (setq ex-token-type 'comma)) - ((looking-at ";") + ((= char ?\;) (forward-char 1) (setq ex-token-type 'semi-colon)) ((looking-at "[!=><&~]") (setq ex-token-type 'command) - (setq ex-token (char-to-string (following-char))) + (setq ex-token (char-to-string char)) (forward-char 1)) - ((looking-at "'") + ((= char ?\') (setq ex-token-type 'goto-mark) (forward-char 1) - (cond ((looking-at "'") (setq ex-token nil)) + (cond ((= (following-char) ?\') (setq ex-token nil)) ((looking-at "[a-z]") (setq ex-token (following-char))) (t (error "%s" "Marks are ' and a-z"))) (forward-char 1)) - ((looking-at "\n") + ((= char ?\n) (setq ex-token-type 'end-mark) (setq ex-token "goto")) (t @@ -687,9 +688,9 @@ reversed." (get-buffer-create viper-ex-work-buf-name)) (set-buffer viper-ex-work-buf) (skip-chars-forward " \t") - (cond ((looking-at "|") + (cond ((= (following-char) ?|) (forward-char 1)) - ((looking-at "\n") + ((= (following-char) ?\n) (setq cont nil)) (t (error "`%s': %s" ex-token viper-SpuriousText))) @@ -994,33 +995,31 @@ reversed." (with-current-buffer (setq viper-ex-work-buf (get-buffer-create viper-ex-work-buf-name)) (skip-chars-forward " \t") - (if (looking-at "!") - (if (and (not (looking-back "[ \t]" (1- (point)))) - ;; read doesn't have a corresponding :r! form, so ! is - ;; immediately interpreted as a shell command. - (not (string= ex-token "read"))) - (progn - (setq ex-variant t) - (forward-char 1) - (skip-chars-forward " \t")) - (setq ex-cmdfile t) - (forward-char 1) - (skip-chars-forward " \t"))) - (if (looking-at ">>") - (progn - (setq ex-append t - ex-variant t) - (forward-char 2) - (skip-chars-forward " \t"))) - (if (looking-at "+") - (progn - (forward-char 1) - (set-mark (point)) - (re-search-forward "[ \t\n]") - (backward-char 1) - (setq ex-offset (buffer-substring (point) (mark t))) - (forward-char 1) - (skip-chars-forward " \t"))) + (when (= (following-char) ?!) + (if (and (not (memq (preceding-char) '(?\s ?\t))) + ;; read doesn't have a corresponding :r! form, so ! is + ;; immediately interpreted as a shell command. + (not (string= ex-token "read"))) + (progn + (setq ex-variant t) + (forward-char 1) + (skip-chars-forward " \t")) + (setq ex-cmdfile t) + (forward-char 1) + (skip-chars-forward " \t"))) + (when (looking-at ">>") + (setq ex-append t + ex-variant t) + (forward-char 2) + (skip-chars-forward " \t")) + (when (= (following-char) ?+) + (forward-char 1) + (set-mark (point)) + (re-search-forward "[ \t\n]") + (backward-char 1) + (setq ex-offset (buffer-substring (point) (mark t))) + (forward-char 1) + (skip-chars-forward " \t")) ;; this takes care of :r, :w, etc., when they get file names ;; from the history list (if (member ex-token '("read" "write" "edit" "visual" "next")) @@ -1602,7 +1601,7 @@ reversed." ;; skip "!", if it is q!. In Viper q!, w!, etc., behave as q, w, etc. (with-current-buffer (setq viper-ex-work-buf (get-buffer-create viper-ex-work-buf-name)) - (if (looking-at "!") (forward-char 1))) + (when (= (following-char) ?!) (forward-char 1))) (if (< viper-expert-level 3) (save-buffers-kill-emacs) (kill-buffer (current-buffer)))) @@ -2322,8 +2321,4 @@ Type `mak ' (including the space) to run make with no args." (with-output-to-temp-buffer " *viper-info*" (princ lines)))))) - - - - ;;; viper-ex.el ends here diff --git a/lisp/emulation/viper-init.el b/lisp/emulation/viper-init.el index 0d478011238..3fd492b3dd3 100644 --- a/lisp/emulation/viper-init.el +++ b/lisp/emulation/viper-init.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -344,9 +344,7 @@ Use `\\[viper-set-expert-level]' to change this.") (quail-delete-overlays)) (setq describe-current-input-method-function nil) (setq current-input-method nil) - (run-hooks - 'input-method-inactivate-hook ; for backward compatibility - 'input-method-deactivate-hook) + (run-hooks 'input-method-deactivate-hook) (force-mode-line-update)) )) (defun viper-activate-input-method () diff --git a/lisp/emulation/viper-keym.el b/lisp/emulation/viper-keym.el index a18833d2502..6227e33417a 100644 --- a/lisp/emulation/viper-keym.el +++ b/lisp/emulation/viper-keym.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emulation/viper-macs.el b/lisp/emulation/viper-macs.el index 1353f7e1772..d79fa454f37 100644 --- a/lisp/emulation/viper-macs.el +++ b/lisp/emulation/viper-macs.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emulation/viper-mous.el b/lisp/emulation/viper-mous.el index 2600c503224..d36f57352f5 100644 --- a/lisp/emulation/viper-mous.el +++ b/lisp/emulation/viper-mous.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emulation/viper-util.el b/lisp/emulation/viper-util.el index e32b41f5750..e09a2bb9a65 100644 --- a/lisp/emulation/viper-util.el +++ b/lisp/emulation/viper-util.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/emulation/viper.el b/lisp/emulation/viper.el index bded174b0d3..2a66262f6cf 100644 --- a/lisp/emulation/viper.el +++ b/lisp/emulation/viper.el @@ -30,7 +30,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/env.el b/lisp/env.el index 859f2808023..5f8c4f5e5c0 100644 --- a/lisp/env.el +++ b/lisp/env.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/epa-dired.el b/lisp/epa-dired.el index 5eb6ca50a34..cbf8b974d8e 100644 --- a/lisp/epa-dired.el +++ b/lisp/epa-dired.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: diff --git a/lisp/epa-file.el b/lisp/epa-file.el index c97acb837aa..7b5ad38f70a 100644 --- a/lisp/epa-file.el +++ b/lisp/epa-file.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: diff --git a/lisp/epa-hook.el b/lisp/epa-hook.el index c6577c81eb5..5f12a153362 100644 --- a/lisp/epa-hook.el +++ b/lisp/epa-hook.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: diff --git a/lisp/epa-mail.el b/lisp/epa-mail.el index 7069273afa1..1eb73e31327 100644 --- a/lisp/epa-mail.el +++ b/lisp/epa-mail.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: diff --git a/lisp/epa.el b/lisp/epa.el index 52963b6d3cd..aca9aaa7d22 100644 --- a/lisp/epa.el +++ b/lisp/epa.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: @@ -561,7 +561,7 @@ If SECRET is non-nil, list secret keys instead of public keys." (epg-sub-key-creation-time (car pointer))) (error "????-??-??")) (if (epg-sub-key-expiration-time (car pointer)) - (format (if (time-less-p (current-time) + (format (if (time-less-p nil (epg-sub-key-expiration-time (car pointer))) "\n\tExpires: %s" diff --git a/lisp/epg-config.el b/lisp/epg-config.el index 7b963add881..dff5e99a8de 100644 --- a/lisp/epg-config.el +++ b/lisp/epg-config.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: @@ -210,34 +210,16 @@ version requirement is met." (declare (obsolete epg-find-configuration "25.1")) (epg-config--make-gpg-configuration epg-gpg-program)) -(defun epg-config--parse-version (string) - (let ((index 0) - version) - (while (eq index (string-match "\\([0-9]+\\)\\.?" string index)) - (setq version (cons (string-to-number (match-string 1 string)) - version) - index (match-end 0))) - (nreverse version))) - -(defun epg-config--compare-version (v1 v2) - (while (and v1 v2 (= (car v1) (car v2))) - (setq v1 (cdr v1) v2 (cdr v2))) - (- (or (car v1) 0) (or (car v2) 0))) - ;;;###autoload (defun epg-check-configuration (config &optional minimum-version) "Verify that a sufficient version of GnuPG is installed." - (let ((entry (assq 'version config)) - version) - (unless (and entry - (stringp (cdr entry))) - (error "Undetermined version: %S" entry)) - (setq version (epg-config--parse-version (cdr entry)) - minimum-version (epg-config--parse-version - (or minimum-version - epg-gpg-minimum-version))) - (unless (>= (epg-config--compare-version version minimum-version) 0) - (error "Unsupported version: %s" (cdr entry))))) + (let ((version (alist-get 'version config))) + (unless (stringp version) + (error "Undetermined version: %S" version)) + (unless (version<= (or minimum-version + epg-gpg-minimum-version) + version) + (error "Unsupported version: %s" version)))) ;;;###autoload (defun epg-expand-group (config group) diff --git a/lisp/epg.el b/lisp/epg.el index 587271b0003..b2d80023f0f 100644 --- a/lisp/epg.el +++ b/lisp/epg.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: @@ -551,8 +551,6 @@ callback data (if any)." (defun epg-errors-to-string (errors) (mapconcat #'epg-error-to-string errors "; ")) -(declare-function pinentry-start "pinentry" (&optional quiet)) - (defun epg--start (context args) "Start `epg-gpg-program' in a subprocess with given ARGS." (if (and (epg-context-process context) @@ -604,30 +602,13 @@ callback data (if any)." (setq process-environment (cons (concat "GPG_TTY=" terminal-name) (cons "TERM=xterm" process-environment)))) - ;; Automatically start the Emacs Pinentry server if appropriate. - (when (and (fboundp 'pinentry-start) - ;; Emacs Pinentry is useless if Emacs has no interactive session. - (not noninteractive) - ;; Prefer pinentry-mode over Emacs Pinentry. - (null (epg-context-pinentry-mode context)) - ;; Check if the allow-emacs-pinentry option is set. - (executable-find epg-gpgconf-program) - (with-temp-buffer - (when (= (call-process epg-gpgconf-program nil t nil - "--list-options" "gpg-agent") - 0) - (goto-char (point-min)) - (re-search-forward - "^allow-emacs-pinentry:\\(?:.*:\\)\\{8\\}1" - nil t)))) - (pinentry-start 'quiet)) (setq process-environment (cons (format "INSIDE_EMACS=%s,epg" emacs-version) process-environment)) ;; Record modified time of gpg-agent socket to restore the Emacs ;; frame on text terminal in `epg-wait-for-completion'. ;; See - ;; <http://lists.gnu.org/archive/html/emacs-devel/2007-02/msg00755.html> + ;; <https://lists.gnu.org/r/emacs-devel/2007-02/msg00755.html> ;; for more details. (when (and agent-info (string-match "\\(.*\\):[0-9]+:[0-9]+" agent-info)) (setq agent-file (match-string 1 agent-info) @@ -757,9 +738,8 @@ callback data (if any)." ;; Restore Emacs frame on text terminal, when pinentry-curses has terminated. (if (with-current-buffer (process-buffer (epg-context-process context)) (and epg-agent-file - (> (float-time (or (nth 5 (file-attributes epg-agent-file)) - '(0 0 0 0))) - (float-time epg-agent-mtime)))) + (time-less-p epg-agent-mtime + (or (nth 5 (file-attributes epg-agent-file)) 0)))) (redraw-frame)) (epg-context-set-result-for context 'error @@ -1047,7 +1027,7 @@ callback data (if any)." (defun epg--status-TRUST_MARGINAL (context _string) (let ((signature (car (epg-context-result-for context 'verify)))) (if (and signature - (eq (epg-signature-status signature) 'marginal)) + (eq (epg-signature-status signature) 'good)) (setf (epg-signature-validity signature) 'marginal)))) (defun epg--status-TRUST_FULLY (context _string) diff --git a/lisp/erc/ChangeLog.1 b/lisp/erc/ChangeLog.1 index 2d5403fdc17..eefbbe924bf 100644 --- a/lisp/erc/ChangeLog.1 +++ b/lisp/erc/ChangeLog.1 @@ -11717,7 +11717,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; Local Variables: ;; coding: utf-8 diff --git a/lisp/erc/ChangeLog.2 b/lisp/erc/ChangeLog.2 index d961fbfd08a..36b01e235c7 100644 --- a/lisp/erc/ChangeLog.2 +++ b/lisp/erc/ChangeLog.2 @@ -120,7 +120,7 @@ 2014-09-25 Kelvin White <kwhite@gnu.org> - * erc.el: Follow Emacs version instead of tracking it seperately. + * erc.el: Follow Emacs version instead of tracking it separately. (erc-quit/part-reason-default) : Clean up quit/part message functions by abstracting repetitive code, change version string. (erc-quit-reason-various, erc-quit-reason-normal, erc-quit-reason-zippy) @@ -772,7 +772,7 @@ See ChangeLog.1 for earlier changes. GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; Local Variables: ;; coding: utf-8 diff --git a/lisp/erc/erc-autoaway.el b/lisp/erc/erc-autoaway.el index 827527966ca..86ca5610abc 100644 --- a/lisp/erc/erc-autoaway.el +++ b/lisp/erc/erc-autoaway.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -82,7 +82,7 @@ This is used when `erc-autoaway-idle-method' is 'user." (unless (erc-autoaway-some-server-buffer) (remove-hook 'post-command-hook 'erc-autoaway-reset-idle-user))) -;;;###autoload (autoload 'erc-autoaway-mode "erc-autoaway") +;;;###autoload(autoload 'erc-autoaway-mode "erc-autoaway") (define-erc-module autoaway nil "In ERC autoaway mode, you can be set away automatically. If `erc-auto-set-away' is set, then you will be set away after @@ -282,6 +282,7 @@ active server buffer available." ;;; erc-autoaway.el ends here ;; ;; Local Variables: +;; generated-autoload-file: "erc-loaddefs.el" ;; indent-tabs-mode: t ;; tab-width: 8 ;; End: diff --git a/lisp/erc/erc-backend.el b/lisp/erc/erc-backend.el index 3368d6701ae..68b30a9216d 100644 --- a/lisp/erc/erc-backend.el +++ b/lisp/erc/erc-backend.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -644,22 +644,24 @@ Make sure you are in an ERC buffer when running this." (erc-log-irc-protocol line nil) (erc-parse-server-response process line))))))) -(defsubst erc-server-reconnect-p (event) +(define-inline erc-server-reconnect-p (event) "Return non-nil if ERC should attempt to reconnect automatically. EVENT is the message received from the closed connection process." - (or erc-server-reconnecting - (and erc-server-auto-reconnect - (not erc-server-banned) - ;; make sure we don't infinitely try to reconnect, unless the - ;; user wants that - (or (eq erc-server-reconnect-attempts t) - (and (integerp erc-server-reconnect-attempts) - (< erc-server-reconnect-count - erc-server-reconnect-attempts))) - (or erc-server-timed-out - (not (string-match "^deleted" event))) - ;; open-network-stream-nowait error for connection refused - (if (string-match "^failed with code 111" event) 'nonblocking t)))) + (inline-letevals (event) + (inline-quote + (or erc-server-reconnecting + (and erc-server-auto-reconnect + (not erc-server-banned) + ;; make sure we don't infinitely try to reconnect, unless the + ;; user wants that + (or (eq erc-server-reconnect-attempts t) + (and (integerp erc-server-reconnect-attempts) + (< erc-server-reconnect-count + erc-server-reconnect-attempts))) + (or erc-server-timed-out + (not (string-match "^deleted" ,event))) + ;; open-network-stream-nowait error for connection refused + (if (string-match "^failed with code 111" ,event) 'nonblocking t)))))) (defun erc-process-sentinel-2 (event buffer) "Called when `erc-process-sentinel-1' has detected an unexpected disconnect." diff --git a/lisp/erc/erc-button.el b/lisp/erc/erc-button.el index ee5d6fe09ee..04f3096650c 100644 --- a/lisp/erc/erc-button.el +++ b/lisp/erc/erc-button.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -49,7 +49,7 @@ "Define how text can be turned into clickable buttons." :group 'erc) -;;;###autoload (autoload 'erc-button-mode "erc-button" nil t) +;;;###autoload(autoload 'erc-button-mode "erc-button" nil t) (define-erc-module button nil "This mode buttonizes all messages according to `erc-button-alist'." ((add-hook 'erc-insert-modify-hook 'erc-button-add-buttons 'append) @@ -545,5 +545,6 @@ and `apropos' for other symbols." ;;; erc-button.el ends here ;; Local Variables: +;; generated-autoload-file: "erc-loaddefs.el" ;; indent-tabs-mode: nil ;; End: diff --git a/lisp/erc/erc-capab.el b/lisp/erc/erc-capab.el index 0d3b23701c4..1830cca40ed 100644 --- a/lisp/erc/erc-capab.el +++ b/lisp/erc/erc-capab.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -90,7 +90,7 @@ character not found in IRC nicknames to avoid confusion." ;;; Define module: -;;;###autoload (autoload 'erc-capab-identify-mode "erc-capab" nil t) +;;;###autoload(autoload 'erc-capab-identify-mode "erc-capab" nil t) (define-erc-module capab-identify nil "Handle dancer-ircd's CAPAB IDENTIFY-MSG and IDENTIFY-CTCP." ;; append so that `erc-server-parameters' is already set by `erc-server-005' @@ -207,3 +207,7 @@ PARSED is an `erc-parsed' response struct." (provide 'erc-capab) ;;; erc-capab.el ends here + +;; Local Variables: +;; generated-autoload-file: "erc-loaddefs.el" +;; End: diff --git a/lisp/erc/erc-compat.el b/lisp/erc/erc-compat.el index 1ad66802fec..d4e07029107 100644 --- a/lisp/erc/erc-compat.el +++ b/lisp/erc/erc-compat.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -29,7 +29,7 @@ (require 'format-spec) -;;;###autoload (autoload 'erc-define-minor-mode "erc-compat") +;;;###autoload(autoload 'erc-define-minor-mode "erc-compat") (defalias 'erc-define-minor-mode 'define-minor-mode) (put 'erc-define-minor-mode 'edebug-form-spec 'define-minor-mode) @@ -161,6 +161,7 @@ If START or END is negative, it counts from the end." ;;; erc-compat.el ends here ;; ;; Local Variables: +;; generated-autoload-file: "erc-loaddefs.el" ;; indent-tabs-mode: t ;; tab-width: 8 ;; End: diff --git a/lisp/erc/erc-dcc.el b/lisp/erc/erc-dcc.el index 542e1909cb6..0ab3a30364a 100644 --- a/lisp/erc/erc-dcc.el +++ b/lisp/erc/erc-dcc.el @@ -23,7 +23,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -56,7 +56,7 @@ (require 'erc) (eval-when-compile (require 'pcomplete)) -;;;###autoload (autoload 'erc-dcc-mode "erc-dcc") +;;;###autoload(autoload 'erc-dcc-mode "erc-dcc") (define-erc-module dcc nil "Provide Direct Client-to-Client support for ERC." ((add-hook 'erc-server-401-functions 'erc-dcc-no-such-nick)) @@ -649,9 +649,10 @@ that subcommand." "\"\\(\\(.*?\\(\\\\\"\\)?\\)+?\\)\"\\|\\([^ ]+\\)" "\\) \\([0-9]+\\) \\([0-9]+\\) *\\([0-9]*\\)")) -(defsubst erc-dcc-unquote-filename (filename) - (erc-replace-regexp-in-string "\\\\\\\\" "\\" - (erc-replace-regexp-in-string "\\\\\"" "\"" filename t t) t t)) +(define-inline erc-dcc-unquote-filename (filename) + (inline-quote + (erc-replace-regexp-in-string "\\\\\\\\" "\\" + (erc-replace-regexp-in-string "\\\\\"" "\"" ,filename t t) t t))) (defun erc-dcc-handle-ctcp-send (proc query nick login host to) "This is called if a CTCP DCC SEND subcommand is sent to the client. @@ -780,8 +781,8 @@ unconfirmed." :group 'erc-dcc :type '(choice (const nil) integer)) -(defsubst erc-dcc-get-parent (proc) - (plist-get (erc-dcc-member :peer proc) :parent)) +(define-inline erc-dcc-get-parent (proc) + (inline-quote (plist-get (erc-dcc-member :peer ,proc) :parent))) (defun erc-dcc-send-block (proc) "Send one block of data. @@ -1257,5 +1258,6 @@ other client." ;;; erc-dcc.el ends here ;; ;; Local Variables: +;; generated-autoload-file: "erc-loaddefs.el" ;; indent-tabs-mode: nil ;; End: diff --git a/lisp/erc/erc-desktop-notifications.el b/lisp/erc/erc-desktop-notifications.el index 113f1cffa60..b35b713fc65 100644 --- a/lisp/erc/erc-desktop-notifications.el +++ b/lisp/erc/erc-desktop-notifications.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -98,3 +98,7 @@ This will replace the last notification sent with this function." (provide 'erc-desktop-notifications) ;;; erc-desktop-notifications.el ends here + +;; Local Variables: +;; generated-autoload-file: "erc-loaddefs.el" +;; End: diff --git a/lisp/erc/erc-ezbounce.el b/lisp/erc/erc-ezbounce.el index 94735787e20..45baa1d1a4c 100644 --- a/lisp/erc/erc-ezbounce.el +++ b/lisp/erc/erc-ezbounce.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -175,3 +175,7 @@ in the alist is nil, prompt for the appropriate values." (provide 'erc-ezbounce) ;;; erc-ezbounce.el ends here + +;; Local Variables: +;; generated-autoload-file: "erc-loaddefs.el" +;; End: diff --git a/lisp/erc/erc-fill.el b/lisp/erc/erc-fill.el index d58ccfa9a9f..35571ab362a 100644 --- a/lisp/erc/erc-fill.el +++ b/lisp/erc/erc-fill.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -37,7 +37,7 @@ "Filling means to reformat long lines in different ways." :group 'erc) -;;;###autoload (autoload 'erc-fill-mode "erc-fill" nil t) +;;;###autoload(autoload 'erc-fill-mode "erc-fill" nil t) (erc-define-minor-mode erc-fill-mode "Toggle ERC fill mode. With a prefix argument ARG, enable ERC fill mode if ARG is @@ -193,5 +193,6 @@ You can put this on `erc-insert-modify-hook' and/or `erc-send-modify-hook'." ;;; erc-fill.el ends here ;; Local Variables: +;; generated-autoload-file: "erc-loaddefs.el" ;; indent-tabs-mode: nil ;; End: diff --git a/lisp/erc/erc-goodies.el b/lisp/erc/erc-goodies.el index 1f27036f40e..8906da1e47d 100644 --- a/lisp/erc/erc-goodies.el +++ b/lisp/erc/erc-goodies.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -147,7 +147,19 @@ Put this function on `erc-insert-post-hook' and/or `erc-send-post-hook'." (>= (point) erc-insert-marker)) (deactivate-mark) (goto-char (erc-beg-of-input-line)) - (forward-line -1))) + (forward-line -1) + ;; if `switch-to-buffer-preserve-window-point' is set, + ;; we cannot rely on point being saved, and must commit + ;; it to window-prev-buffers. + (when switch-to-buffer-preserve-window-point + (dolist (frame (frame-list)) + (walk-window-tree + (lambda (window) + (let ((prev (assq (current-buffer) + (window-prev-buffers window)))) + (when prev + (setf (nth 2 prev) (point-marker))))) + frame nil 'nominibuf))))) ;;; Distinguish non-commands (defvar erc-noncommands-list '(erc-cmd-ME diff --git a/lisp/erc/erc-ibuffer.el b/lisp/erc/erc-ibuffer.el index cb9c21fc3c9..03d51d9879c 100644 --- a/lisp/erc/erc-ibuffer.el +++ b/lisp/erc/erc-ibuffer.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -189,4 +189,3 @@ ;; indent-tabs-mode: t ;; tab-width: 8 ;; End: - diff --git a/lisp/erc/erc-identd.el b/lisp/erc/erc-identd.el index 61360f40f5c..42133110a23 100644 --- a/lisp/erc/erc-identd.el +++ b/lisp/erc/erc-identd.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -55,7 +55,7 @@ This can be either a string or a number." (integer :tag "Port number") (string :tag "Port string"))) -;;;###autoload (autoload 'erc-identd-mode "erc-identd") +;;;###autoload(autoload 'erc-identd-mode "erc-identd") (define-erc-module identd nil "This mode launches an identd server on port 8113." ((add-hook 'erc-connect-pre-hook 'erc-identd-quickstart) @@ -115,7 +115,7 @@ The default port is specified by `erc-identd-port'." ;;; erc-identd.el ends here ;; ;; Local Variables: +;; generated-autoload-file: "erc-loaddefs.el" ;; indent-tabs-mode: t ;; tab-width: 8 ;; End: - diff --git a/lisp/erc/erc-imenu.el b/lisp/erc/erc-imenu.el index 95c2b35c699..f74674b8f98 100644 --- a/lisp/erc/erc-imenu.el +++ b/lisp/erc/erc-imenu.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -131,7 +131,7 @@ Don't rely on this function, read it first!" ;;; erc-imenu.el ends here ;; ;; Local Variables: +;; generated-autoload-file: "erc-loaddefs.el" ;; indent-tabs-mode: t ;; tab-width: 8 ;; End: - diff --git a/lisp/erc/erc-join.el b/lisp/erc/erc-join.el index aa83ffe92ac..c4d80f0ada5 100644 --- a/lisp/erc/erc-join.el +++ b/lisp/erc/erc-join.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -39,7 +39,7 @@ "Enable autojoining." :group 'erc) -;;;###autoload (autoload 'erc-autojoin-mode "erc-join" nil t) +;;;###autoload(autoload 'erc-autojoin-mode "erc-join" nil t) (define-erc-module autojoin nil "Makes ERC autojoin on connects and reconnects." ((add-hook 'erc-after-connect 'erc-autojoin-channels) @@ -215,7 +215,7 @@ This function is run from `erc-nickserv-identified-hook'." ;;; erc-join.el ends here ;; ;; Local Variables: +;; generated-autoload-file: "erc-loaddefs.el" ;; indent-tabs-mode: t ;; tab-width: 8 ;; End: - diff --git a/lisp/erc/erc-lang.el b/lisp/erc/erc-lang.el index 4aa31529dae..7551b1d2e13 100644 --- a/lisp/erc/erc-lang.el +++ b/lisp/erc/erc-lang.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -180,7 +180,7 @@ This is based on the technical contents of ISO 639:1988 (E/F) \"Code for the representation of names of languages\". Typed by Keld.Simonsen@dkuug.dk 1990-11-30 - <ftp://dkuug.dk/i18n/ISO_639> + <ftp://std.dkuug.dk/i18n/iso_639> Minor corrections, 1992-09-08 by Keld Simonsen Sundanese corrected, 1992-11-11 by Keld Simonsen Telugu corrected, 1995-08-24 by Keld Simonsen diff --git a/lisp/erc/erc-list.el b/lisp/erc/erc-list.el index 5110239f61e..7a6ba821134 100644 --- a/lisp/erc/erc-list.el +++ b/lisp/erc/erc-list.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -55,7 +55,7 @@ (defvar erc-list-server-buffer nil) ;; Define module: -;;;###autoload (autoload 'erc-list-mode "erc-list") +;;;###autoload(autoload 'erc-list-mode "erc-list") (define-erc-module list nil "List channels nicely in a separate buffer." ((remove-hook 'erc-server-321-functions 'erc-server-321-message) @@ -145,7 +145,7 @@ (erc-propertize title 'column-number column 'help-echo "mouse-1: sort by column" - 'mouse-face 'highlight + 'mouse-face 'header-line-highlight 'keymap erc-list-menu-sort-button-map)) (define-derived-mode erc-list-menu-mode special-mode "ERC-List" @@ -225,7 +225,7 @@ to RFC and send the LIST header (#321) at start of list transmission." ;;; erc-list.el ends here ;; ;; Local Variables: +;; generated-autoload-file: "erc-loaddefs.el" ;; indent-tabs-mode: t ;; tab-width: 8 ;; End: - diff --git a/lisp/erc/erc-log.el b/lisp/erc/erc-log.el index 09cffdcd84c..e48d5ff80ee 100644 --- a/lisp/erc/erc-log.el +++ b/lisp/erc/erc-log.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -215,7 +215,7 @@ The function should take one argument, which is the text to filter." (const :tag "No filtering" nil))) -;;;###autoload (autoload 'erc-log-mode "erc-log" nil t) +;;;###autoload(autoload 'erc-log-mode "erc-log" nil t) (define-erc-module log nil "Automatically logs things you receive on IRC into files. Files are stored in `erc-log-channels-directory'; file name @@ -455,6 +455,7 @@ You can save every individual message by putting this function on ;;; erc-log.el ends here ;; ;; Local Variables: +;; generated-autoload-file: "erc-loaddefs.el" ;; indent-tabs-mode: t ;; tab-width: 8 ;; End: diff --git a/lisp/erc/erc-match.el b/lisp/erc/erc-match.el index 3f6b1e546a9..b13b6f7d44a 100644 --- a/lisp/erc/erc-match.el +++ b/lisp/erc/erc-match.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -44,7 +44,7 @@ Group containing all things concerning pattern matching in ERC messages." :group 'erc) -;;;###autoload (autoload 'erc-match-mode "erc-match") +;;;###autoload(autoload 'erc-match-mode "erc-match") (define-erc-module match nil "This mode checks whether messages match certain patterns. If so, they are hidden or highlighted. This is controlled via the variables @@ -648,6 +648,7 @@ This function is meant to be called from `erc-text-matched-hook'." ;;; erc-match.el ends here ;; ;; Local Variables: +;; generated-autoload-file: "erc-loaddefs.el" ;; indent-tabs-mode: t ;; tab-width: 8 ;; End: diff --git a/lisp/erc/erc-menu.el b/lisp/erc/erc-menu.el index 9db1e754351..3702886aa3c 100644 --- a/lisp/erc/erc-menu.el +++ b/lisp/erc/erc-menu.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -107,7 +107,7 @@ "Internal variable used to keep track of whether we've defined the ERC menu yet.") -;;;###autoload (autoload 'erc-menu-mode "erc-menu" nil t) +;;;###autoload(autoload 'erc-menu-mode "erc-menu" nil t) (define-erc-module menu nil "Enable a menu in ERC buffers." ((unless erc-menu-defined @@ -148,7 +148,7 @@ ERC menu yet.") ;;; erc-menu.el ends here ;; ;; Local Variables: +;; generated-autoload-file: "erc-loaddefs.el" ;; indent-tabs-mode: t ;; tab-width: 8 ;; End: - diff --git a/lisp/erc/erc-netsplit.el b/lisp/erc/erc-netsplit.el index 583e071c677..52b671c6114 100644 --- a/lisp/erc/erc-netsplit.el +++ b/lisp/erc/erc-netsplit.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -38,7 +38,7 @@ netsplit happens, and filters the QUIT messages. It also keeps track of netsplits, so that it can filter the JOIN messages on a netjoin too." :group 'erc) -;;;###autoload (autoload 'erc-netsplit-mode "erc-netsplit") +;;;###autoload(autoload 'erc-netsplit-mode "erc-netsplit") (define-erc-module netsplit nil "This mode hides quit/join messages if a netsplit occurs." ((erc-netsplit-install-message-catalogs) @@ -151,7 +151,7 @@ join from that split has been detected or not.") (when (nth 2 ass) ;; There was already a netjoin for this netsplit, it ;; seems like the old one didn't get finished... - (erc-display-message + (erc-display-message parsed 'notice (process-buffer proc) 'netsplit ?s split) (setcar (nthcdr 2 ass) t) @@ -205,7 +205,7 @@ join from that split has been detected or not.") ;;; erc-netsplit.el ends here ;; ;; Local Variables: +;; generated-autoload-file: "erc-loaddefs.el" ;; indent-tabs-mode: t ;; tab-width: 8 ;; End: - diff --git a/lisp/erc/erc-networks.el b/lisp/erc/erc-networks.el index 59a9356c2ae..bf964bc6ba1 100644 --- a/lisp/erc/erc-networks.el +++ b/lisp/erc/erc-networks.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/erc/erc-notify.el b/lisp/erc/erc-notify.el index 54c8bebab30..3f235d3d452 100644 --- a/lisp/erc/erc-notify.el +++ b/lisp/erc/erc-notify.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -92,7 +92,7 @@ strings." (notify_on . "Detected %n on IRC network %m") (notify_off . "%n has left IRC network %m")))) -;;;###autoload (autoload 'erc-notify-mode "erc-notify" nil t) +;;;###autoload(autoload 'erc-notify-mode "erc-notify" nil t) (define-erc-module notify nil "Periodically check for the online status of certain users and report changes." @@ -253,6 +253,7 @@ with args, toggle notify status of people." ;;; erc-notify.el ends here ;; ;; Local Variables: +;; generated-autoload-file: "erc-loaddefs.el" ;; indent-tabs-mode: t ;; tab-width: 8 ;; End: diff --git a/lisp/erc/erc-page.el b/lisp/erc/erc-page.el index d441b099bb7..20811407e32 100644 --- a/lisp/erc/erc-page.el +++ b/lisp/erc/erc-page.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -30,7 +30,7 @@ (require 'erc) -;;;###autoload (autoload 'erc-page-mode "erc-page") +;;;###autoload(autoload 'erc-page-mode "erc-page") (define-erc-module page ctcp-page "Process CTCP PAGE requests from IRC." nil nil) @@ -107,7 +107,7 @@ receive pages if `erc-page-mode' is on." ;;; erc-page.el ends here ;; ;; Local Variables: +;; generated-autoload-file: "erc-loaddefs.el" ;; indent-tabs-mode: t ;; tab-width: 8 ;; End: - diff --git a/lisp/erc/erc-pcomplete.el b/lisp/erc/erc-pcomplete.el index 6dfe0a77862..bf20f5c3472 100644 --- a/lisp/erc/erc-pcomplete.el +++ b/lisp/erc/erc-pcomplete.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -60,7 +60,7 @@ the most recent speakers are listed first." :group 'erc-pcomplete :type 'boolean) -;;;###autoload (autoload 'erc-completion-mode "erc-pcomplete" nil t) +;;;###autoload(autoload 'erc-completion-mode "erc-pcomplete" nil t) (define-erc-module pcomplete Completion "In ERC Completion mode, the TAB key does completion whenever possible." ((add-hook 'erc-mode-hook 'pcomplete-erc-setup) @@ -225,7 +225,7 @@ If optional argument IGNORE-SELF is non-nil, don't return the current nick." (erc-get-channel-user-list))) (nicks nil)) (dolist (user users) - (unless (or (not user) + (unless (or (not user) (and ignore-self (string= (erc-server-user-nickname (car user)) (erc-current-nick)))) @@ -284,6 +284,6 @@ up to where point is right now." ;;; erc-pcomplete.el ends here ;; ;; Local Variables: +;; generated-autoload-file: "erc-loaddefs.el" ;; indent-tabs-mode: nil ;; End: - diff --git a/lisp/erc/erc-replace.el b/lisp/erc/erc-replace.el index ec443ec0224..35be533939c 100644 --- a/lisp/erc/erc-replace.el +++ b/lisp/erc/erc-replace.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -77,7 +77,7 @@ It replaces text according to `erc-replace-alist'." (eval to)))))) erc-replace-alist)) -;;;###autoload (autoload 'erc-replace-mode "erc-replace") +;;;###autoload(autoload 'erc-replace-mode "erc-replace") (define-erc-module replace nil "This mode replaces incoming text according to `erc-replace-alist'." ((add-hook 'erc-insert-modify-hook @@ -90,7 +90,7 @@ It replaces text according to `erc-replace-alist'." ;;; erc-replace.el ends here ;; ;; Local Variables: +;; generated-autoload-file: "erc-loaddefs.el" ;; indent-tabs-mode: t ;; tab-width: 8 ;; End: - diff --git a/lisp/erc/erc-ring.el b/lisp/erc/erc-ring.el index 4e31ec20a67..2d6152ccea8 100644 --- a/lisp/erc/erc-ring.el +++ b/lisp/erc/erc-ring.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -42,7 +42,7 @@ "An input ring for ERC." :group 'erc) -;;;###autoload (autoload 'erc-ring-mode "erc-ring" nil t) +;;;###autoload(autoload 'erc-ring-mode "erc-ring" nil t) (define-erc-module ring nil "Stores input in a ring so that previous commands and messages can be recalled using M-p and M-n." @@ -146,5 +146,6 @@ containing a password." ;;; erc-ring.el ends here ;; Local Variables: +;; generated-autoload-file: "erc-loaddefs.el" ;; indent-tabs-mode: nil ;; End: diff --git a/lisp/erc/erc-services.el b/lisp/erc/erc-services.el index 6e7c918316a..f18b9d29b5b 100644 --- a/lisp/erc/erc-services.el +++ b/lisp/erc/erc-services.el @@ -1,4 +1,4 @@ -;;; erc-services.el --- Identify to NickServ +;;; erc-services.el --- Identify to NickServ -*- lexical-binding:t -*- ;; Copyright (C) 2002-2004, 2006-2017 Free Software Foundation, Inc. @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -89,7 +89,7 @@ Possible settings are:. latter. nil - Disables automatic Nickserv identification. -You can also use M-x erc-nickserv-identify-mode to change modes." +You can also use \\[erc-nickserv-identify-mode] to change modes." :group 'erc-services :type '(choice (const autodetect) (const nick-change) @@ -101,7 +101,7 @@ You can also use M-x erc-nickserv-identify-mode to change modes." (when (featurep 'erc-services) (erc-nickserv-identify-mode val)))) -;;;###autoload (autoload 'erc-services-mode "erc-services" nil t) +;;;###autoload(autoload 'erc-services-mode "erc-services" nil t) (define-erc-module services nickserv "This mode automates communication with services." ((erc-nickserv-identify-mode erc-nickserv-identify-mode)) @@ -312,26 +312,33 @@ The last two elements are optional." (const :tag "Do not try to detect success" nil))))) -(defsubst erc-nickserv-alist-sender (network &optional entry) - (nth 1 (or entry (assoc network erc-nickserv-alist)))) +(define-inline erc-nickserv-alist-sender (network &optional entry) + (inline-letevals (network entry) + (inline-quote (nth 1 (or ,entry (assoc ,network erc-nickserv-alist)))))) -(defsubst erc-nickserv-alist-regexp (network &optional entry) - (nth 2 (or entry (assoc network erc-nickserv-alist)))) +(define-inline erc-nickserv-alist-regexp (network &optional entry) + (inline-letevals (network entry) + (inline-quote (nth 2 (or ,entry (assoc ,network erc-nickserv-alist)))))) -(defsubst erc-nickserv-alist-nickserv (network &optional entry) - (nth 3 (or entry (assoc network erc-nickserv-alist)))) +(define-inline erc-nickserv-alist-nickserv (network &optional entry) + (inline-letevals (network entry) + (inline-quote (nth 3 (or ,entry (assoc ,network erc-nickserv-alist)))))) -(defsubst erc-nickserv-alist-ident-keyword (network &optional entry) - (nth 4 (or entry (assoc network erc-nickserv-alist)))) +(define-inline erc-nickserv-alist-ident-keyword (network &optional entry) + (inline-letevals (network entry) + (inline-quote (nth 4 (or ,entry (assoc ,network erc-nickserv-alist)))))) -(defsubst erc-nickserv-alist-use-nick-p (network &optional entry) - (nth 5 (or entry (assoc network erc-nickserv-alist)))) +(define-inline erc-nickserv-alist-use-nick-p (network &optional entry) + (inline-letevals (network entry) + (inline-quote (nth 5 (or ,entry (assoc ,network erc-nickserv-alist)))))) -(defsubst erc-nickserv-alist-ident-command (network &optional entry) - (nth 6 (or entry (assoc network erc-nickserv-alist)))) +(define-inline erc-nickserv-alist-ident-command (network &optional entry) + (inline-letevals (network entry) + (inline-quote (nth 6 (or ,entry (assoc ,network erc-nickserv-alist)))))) -(defsubst erc-nickserv-alist-identified-regexp (network &optional entry) - (nth 7 (or entry (assoc network erc-nickserv-alist)))) +(define-inline erc-nickserv-alist-identified-regexp (network &optional entry) + (inline-letevals (network entry) + (inline-quote (nth 7 (or ,entry (assoc ,network erc-nickserv-alist)))))) ;; Functions: @@ -341,7 +348,7 @@ Hooks are called with arguments (NETWORK NICK)." :group 'erc-services :type 'hook) -(defun erc-nickserv-identification-autodetect (proc parsed) +(defun erc-nickserv-identification-autodetect (_proc parsed) "Check for NickServ's successful identification notice. Make sure it is the real NickServ for this network and that it has specifically confirmed a successful identification attempt. @@ -361,7 +368,7 @@ If this is the case, run `erc-nickserv-identified-hook'." (run-hook-with-args 'erc-nickserv-identified-hook network nick) nil))) -(defun erc-nickserv-identify-autodetect (proc parsed) +(defun erc-nickserv-identify-autodetect (_proc parsed) "Identify to NickServ when an identify request is received. Make sure it is the real NickServ for this network. If `erc-prompt-for-nickserv-password' is non-nil, prompt the user for the @@ -383,7 +390,7 @@ password for this nickname, otherwise try to send it automatically." (erc-nickserv-call-identify-function nick) nil)))) -(defun erc-nickserv-identify-on-connect (server nick) +(defun erc-nickserv-identify-on-connect (_server nick) "Identify to Nickserv after the connection to the server is established." (unless (or (and (null erc-nickserv-passwords) (null erc-prompt-for-nickserv-password)) @@ -391,7 +398,7 @@ password for this nickname, otherwise try to send it automatically." (erc-nickserv-alist-regexp (erc-network)))) (erc-nickserv-call-identify-function nick))) -(defun erc-nickserv-identify-on-nick-change (nick old-nick) +(defun erc-nickserv-identify-on-nick-change (nick _old-nick) "Identify to Nickserv whenever your nick changes." (unless (or (and (null erc-nickserv-passwords) (null erc-prompt-for-nickserv-password)) @@ -400,9 +407,9 @@ password for this nickname, otherwise try to send it automatically." (erc-nickserv-call-identify-function nick))) (defun erc-nickserv-call-identify-function (nickname) - "Call `erc-nickserv-identify' interactively or run it with NICKNAME's -password. -The action is determined by the value of `erc-prompt-for-nickserv-password'." + "Call `erc-nickserv-identify'. +Either call it interactively or run it with NICKNAME's password, +depending on the value of `erc-prompt-for-nickserv-password'." (if erc-prompt-for-nickserv-password (call-interactively 'erc-nickserv-identify) (when erc-nickserv-passwords @@ -411,6 +418,8 @@ The action is determined by the value of `erc-prompt-for-nickserv-password'." (nth 1 (assoc (erc-network) erc-nickserv-passwords)))))))) +(defvar erc-auto-discard-away) + ;;;###autoload (defun erc-nickserv-identify (password) "Send an \"identify <PASSWORD>\" message to NickServ. @@ -444,6 +453,7 @@ When called interactively, read the password using `read-passwd'." ;;; erc-services.el ends here ;; ;; Local Variables: +;; generated-autoload-file: "erc-loaddefs.el" ;; indent-tabs-mode: t ;; tab-width: 8 ;; End: diff --git a/lisp/erc/erc-sound.el b/lisp/erc/erc-sound.el index 4ca7a59bbba..ddf32b6dd7a 100644 --- a/lisp/erc/erc-sound.el +++ b/lisp/erc/erc-sound.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -46,7 +46,7 @@ (require 'erc) -;;;###autoload (autoload 'erc-sound-mode "erc-sound") +;;;###autoload(autoload 'erc-sound-mode "erc-sound") (define-erc-module sound ctcp-sound "In ERC sound mode, the client will respond to CTCP SOUND requests and play sound files as requested." @@ -145,7 +145,7 @@ See also `play-sound-file'." ;;; erc-sound.el ends here ;; ;; Local Variables: +;; generated-autoload-file: "erc-loaddefs.el" ;; indent-tabs-mode: t ;; tab-width: 8 ;; End: - diff --git a/lisp/erc/erc-speedbar.el b/lisp/erc/erc-speedbar.el index 4f44f415fdd..5b052f0686c 100644 --- a/lisp/erc/erc-speedbar.el +++ b/lisp/erc/erc-speedbar.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -361,6 +361,7 @@ The INDENT level is ignored." ;;; erc-speedbar.el ends here ;; ;; Local Variables: +;; generated-autoload-file: "erc-loaddefs.el" ;; indent-tabs-mode: t ;; tab-width: 8 ;; End: diff --git a/lisp/erc/erc-spelling.el b/lisp/erc/erc-spelling.el index 9b0e5faaf64..25c5450161b 100644 --- a/lisp/erc/erc-spelling.el +++ b/lisp/erc/erc-spelling.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -33,7 +33,7 @@ (require 'erc) (require 'flyspell) -;;;###autoload (autoload 'erc-spelling-mode "erc-spelling" nil t) +;;;###autoload(autoload 'erc-spelling-mode "erc-spelling" nil t) (define-erc-module spelling nil "Enable flyspell mode in ERC buffers." ;; Use erc-connect-pre-hook instead of erc-mode-hook as pre-hook is @@ -109,3 +109,7 @@ The cadr is the beginning and the caddr is the end." (provide 'erc-spelling) ;;; erc-spelling.el ends here + +;; Local Variables: +;; generated-autoload-file: "erc-loaddefs.el" +;; End: diff --git a/lisp/erc/erc-stamp.el b/lisp/erc/erc-stamp.el index 7ce22b380db..9f1b7fc968a 100644 --- a/lisp/erc/erc-stamp.el +++ b/lisp/erc/erc-stamp.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -158,7 +158,7 @@ from entering them and instead jump over them." "ERC timestamp face." :group 'erc-faces) -;;;###autoload (autoload 'erc-timestamp-mode "erc-stamp" nil t) +;;;###autoload(autoload 'erc-timestamp-mode "erc-stamp" nil t) (define-erc-module stamp timestamp "This mode timestamps messages in the channel buffers." ((add-hook 'erc-mode-hook #'erc-munge-invisibility-spec) @@ -417,7 +417,7 @@ enabled when the message was inserted." ;;; erc-stamp.el ends here ;; ;; Local Variables: +;; generated-autoload-file: "erc-loaddefs.el" ;; indent-tabs-mode: t ;; tab-width: 8 ;; End: - diff --git a/lisp/erc/erc-track.el b/lisp/erc/erc-track.el index c49971e872a..b94b6de8833 100644 --- a/lisp/erc/erc-track.el +++ b/lisp/erc/erc-track.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -542,7 +542,7 @@ keybindings will not do anything useful." ;;; Module -;;;###autoload (autoload 'erc-track-mode "erc-track" nil t) +;;;###autoload(autoload 'erc-track-mode "erc-track" nil t) (define-erc-module track nil "This mode tracks ERC channel buffers with activity." ;; Enable: @@ -974,6 +974,7 @@ switch back to the last non-ERC buffer visited. Next is defined by ;;; erc-track.el ends here ;; ;; Local Variables: +;; generated-autoload-file: "erc-loaddefs.el" ;; indent-tabs-mode: t ;; tab-width: 8 ;; End: diff --git a/lisp/erc/erc-truncate.el b/lisp/erc/erc-truncate.el index 7f5bb326b7f..acd8f63bb16 100644 --- a/lisp/erc/erc-truncate.el +++ b/lisp/erc/erc-truncate.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -43,7 +43,7 @@ Used only when auto-truncation is enabled. :group 'erc-truncate :type 'integer) -;;;###autoload (autoload 'erc-truncate-mode "erc-truncate" nil t) +;;;###autoload(autoload 'erc-truncate-mode "erc-truncate" nil t) (define-erc-module truncate nil "Truncate a query buffer if it gets too large. This prevents the query buffer from getting too large, which can @@ -112,7 +112,7 @@ Meant to be used in hooks, like `erc-insert-post-hook'." ;;; erc-truncate.el ends here ;; ;; Local Variables: +;; generated-autoload-file: "erc-loaddefs.el" ;; indent-tabs-mode: t ;; tab-width: 8 ;; End: - diff --git a/lisp/erc/erc-xdcc.el b/lisp/erc/erc-xdcc.el index 4b0b7b9afa2..6732c9cdc6e 100644 --- a/lisp/erc/erc-xdcc.el +++ b/lisp/erc/erc-xdcc.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -61,7 +61,7 @@ being evaluated and should return strings." :group 'erc-dcc :type '(repeat (repeat :tag "Message" (choice string sexp)))) -;;;###autoload (autoload 'erc-xdcc-mode "erc-xdcc") +;;;###autoload(autoload 'erc-xdcc-mode "erc-xdcc") (define-erc-module xdcc nil "Act as an XDCC file-server." nil nil) @@ -133,7 +133,7 @@ being evaluated and should return strings." ;;; erc-xdcc.el ends here ;; ;; Local Variables: +;; generated-autoload-file: "erc-loaddefs.el" ;; indent-tabs-mode: t ;; tab-width: 8 ;; End: - diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index 8547821f080..eee79464a9a 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -28,20 +28,20 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; ERC is a powerful, modular, and extensible IRC client for Emacs. ;; For more information, see the following URLs: -;; * http://sv.gnu.org/projects/erc/ +;; * https://sv.gnu.org/projects/erc/ ;; * http://www.emacswiki.org/cgi-bin/wiki/ERC ;; As of 2006-06-13, ERC development is now hosted on Savannah -;; (http://sv.gnu.org/projects/erc). I invite everyone who wants to +;; (https://sv.gnu.org/projects/erc). I invite everyone who wants to ;; hack on it to contact me <mwolson@gnu.org> in order to get write ;; access to the shared Arch archive. @@ -67,6 +67,8 @@ ;;; Code: +(load "erc-loaddefs" nil t) + (eval-when-compile (require 'cl-lib)) (require 'font-lock) (require 'pp) @@ -75,7 +77,7 @@ (require 'erc-compat) (defvar erc-official-location - "http://emacswiki.org/cgi-bin/wiki/ERC (mailing list: erc-discuss@gnu.org)" + "https://emacswiki.org/cgi-bin/wiki/ERC (mailing list: erc-discuss@gnu.org)" "Location of the ERC client on the Internet.") (defgroup erc nil @@ -399,25 +401,28 @@ If no server buffer exists, return nil." ;; This is useful for ordered name completion. (last-message-time nil)) -(defsubst erc-get-channel-user (nick) +(define-inline erc-get-channel-user (nick) "Find the (USER . CHANNEL-DATA) element corresponding to NICK in the current buffer's `erc-channel-users' hash table." - (gethash (erc-downcase nick) erc-channel-users)) + (inline-quote (gethash (erc-downcase ,nick) erc-channel-users))) -(defsubst erc-get-server-user (nick) +(define-inline erc-get-server-user (nick) "Find the USER corresponding to NICK in the current server's `erc-server-users' hash table." - (erc-with-server-buffer - (gethash (erc-downcase nick) erc-server-users))) + (inline-letevals (nick) + (inline-quote (erc-with-server-buffer + (gethash (erc-downcase ,nick) erc-server-users))))) -(defsubst erc-add-server-user (nick user) +(define-inline erc-add-server-user (nick user) "This function is for internal use only. Adds USER with nickname NICK to the `erc-server-users' hash table." - (erc-with-server-buffer - (puthash (erc-downcase nick) user erc-server-users))) + (inline-letevals (nick user) + (inline-quote + (erc-with-server-buffer + (puthash (erc-downcase ,nick) ,user erc-server-users))))) -(defsubst erc-remove-server-user (nick) +(define-inline erc-remove-server-user (nick) "This function is for internal use only. Removes the user with nickname NICK from the `erc-server-users' @@ -425,8 +430,10 @@ hash table. This user is not removed from the `erc-channel-users' lists of other buffers. See also: `erc-remove-user'." - (erc-with-server-buffer - (remhash (erc-downcase nick) erc-server-users))) + (inline-letevals (nick) + (inline-quote + (erc-with-server-buffer + (remhash (erc-downcase ,nick) erc-server-users))))) (defun erc-change-user-nickname (user new-nick) "This function is for internal use only. @@ -497,45 +504,55 @@ Removes all users in the current channel. This is called by erc-channel-users) (clrhash erc-channel-users))) -(defsubst erc-channel-user-owner-p (nick) +(define-inline erc-channel-user-owner-p (nick) "Return non-nil if NICK is an owner of the current channel." - (and nick - (hash-table-p erc-channel-users) - (let ((cdata (erc-get-channel-user nick))) - (and cdata (cdr cdata) - (erc-channel-user-owner (cdr cdata)))))) - -(defsubst erc-channel-user-admin-p (nick) + (inline-letevals (nick) + (inline-quote + (and ,nick + (hash-table-p erc-channel-users) + (let ((cdata (erc-get-channel-user ,nick))) + (and cdata (cdr cdata) + (erc-channel-user-owner (cdr cdata)))))))) + +(define-inline erc-channel-user-admin-p (nick) "Return non-nil if NICK is an admin in the current channel." - (and nick + (inline-letevals (nick) + (inline-quote + (and ,nick (hash-table-p erc-channel-users) - (let ((cdata (erc-get-channel-user nick))) + (let ((cdata (erc-get-channel-user ,nick))) (and cdata (cdr cdata) - (erc-channel-user-admin (cdr cdata)))))) + (erc-channel-user-admin (cdr cdata)))))))) -(defsubst erc-channel-user-op-p (nick) +(define-inline erc-channel-user-op-p (nick) "Return non-nil if NICK is an operator in the current channel." - (and nick + (inline-letevals (nick) + (inline-quote + (and ,nick (hash-table-p erc-channel-users) - (let ((cdata (erc-get-channel-user nick))) + (let ((cdata (erc-get-channel-user ,nick))) (and cdata (cdr cdata) - (erc-channel-user-op (cdr cdata)))))) + (erc-channel-user-op (cdr cdata)))))))) -(defsubst erc-channel-user-halfop-p (nick) +(define-inline erc-channel-user-halfop-p (nick) "Return non-nil if NICK is a half-operator in the current channel." - (and nick + (inline-letevals (nick) + (inline-quote + (and ,nick (hash-table-p erc-channel-users) - (let ((cdata (erc-get-channel-user nick))) + (let ((cdata (erc-get-channel-user ,nick))) (and cdata (cdr cdata) - (erc-channel-user-halfop (cdr cdata)))))) + (erc-channel-user-halfop (cdr cdata)))))))) -(defsubst erc-channel-user-voice-p (nick) +(define-inline erc-channel-user-voice-p (nick) "Return non-nil if NICK has voice in the current channel." - (and nick + (inline-letevals (nick) + (inline-quote + (and ,nick (hash-table-p erc-channel-users) - (let ((cdata (erc-get-channel-user nick))) + (let ((cdata (erc-get-channel-user ,nick))) (and cdata (cdr cdata) - (erc-channel-user-voice (cdr cdata)))))) + (erc-channel-user-voice (cdr cdata)))))))) (defun erc-get-channel-user-list () "Return a list of users in the current channel. Each element @@ -1260,7 +1277,7 @@ erc-NAME-enable, and erc-NAME-disable. Example: - ;;;###autoload (autoload \\='erc-replace-mode \"erc-replace\") + ;;;###autoload(autoload \\='erc-replace-mode \"erc-replace\") (define-erc-module replace nil \"This mode replaces incoming text according to `erc-replace-alist'.\" ((add-hook \\='erc-insert-modify-hook @@ -1343,10 +1360,11 @@ capabilities." (add-hook hook fun nil t) fun)) -(defsubst erc-log (string) +(define-inline erc-log (string) "Logs STRING if logging is on (see `erc-log-p')." - (when erc-log-p - (erc-log-aux string))) + (inline-quote + (when erc-log-p + (erc-log-aux ,string)))) (defun erc-server-buffer () "Return the server buffer for the current buffer's process. @@ -2548,9 +2566,7 @@ consumption for long-lived IRC or Emacs sessions." (maphash (lambda (nick last-PRIVMSG-time) (when - (> (float-time (time-subtract - (current-time) - last-PRIVMSG-time)) + (> (float-time (time-subtract nil last-PRIVMSG-time)) erc-lurker-threshold-time) (remhash nick hash))) hash) @@ -2617,7 +2633,7 @@ server within `erc-lurker-threshold-time'. See also (gethash server erc-lurker-state (make-hash-table))))) (or (null last-PRIVMSG-time) (> (float-time - (time-subtract (current-time) last-PRIVMSG-time)) + (time-subtract nil last-PRIVMSG-time)) erc-lurker-threshold-time)))) (defcustom erc-common-server-suffixes @@ -2650,9 +2666,9 @@ otherwise `erc-server-announced-name'. SERVER is matched against "Predicate indicating whether the parsed ERC response PARSED should be hidden. Messages are always hidden if the message type of PARSED appears in -`erc-hide-list'. Message types that appear in `erc-network-hide-list' -or `erc-channel-hide-list' are are only hidden if the target matches -the network or channel in the list. In addition, messages whose type +`erc-hide-list'. Message types that appear in `erc-network-hide-list' +or `erc-channel-hide-list' are only hidden if the target matches +the network or channel in the list. In addition, messages whose type is a member of `erc-lurker-hide-list' are hidden if `erc-lurker-p' returns non-nil." (let* ((command (erc-response.command parsed)) diff --git a/lisp/eshell/em-alias.el b/lisp/eshell/em-alias.el index 5bf80b2310a..742234574f3 100644 --- a/lisp/eshell/em-alias.el +++ b/lisp/eshell/em-alias.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -214,8 +214,8 @@ file named by `eshell-aliases-file'.") (defvar eshell-prevent-alias-expansion nil) -(defun eshell-maybe-replace-by-alias (command args) - "If COMMAND has an alias definition, call that instead using ARGS." +(defun eshell-maybe-replace-by-alias (command _args) + "Call COMMAND's alias definition, if it exists." (unless (and eshell-prevent-alias-expansion (member command eshell-prevent-alias-expansion)) (let ((alias (eshell-lookup-alias command))) diff --git a/lisp/eshell/em-banner.el b/lisp/eshell/em-banner.el index aee7daa49f3..268b4289f4a 100644 --- a/lisp/eshell/em-banner.el +++ b/lisp/eshell/em-banner.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/eshell/em-basic.el b/lisp/eshell/em-basic.el index c570d7cca89..33ce3b5e93a 100644 --- a/lisp/eshell/em-basic.el +++ b/lisp/eshell/em-basic.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -50,12 +50,6 @@ ;; ;; The umask command changes the default file permissions for newly ;; created files. It uses the same syntax as bash. -;; -;;;_* `version' -;; -;; This command reports the version number for Eshell and all its -;; dependent module, including the date when those modules were last -;; modified. ;;; Code: diff --git a/lisp/eshell/em-cmpl.el b/lisp/eshell/em-cmpl.el index 571348620bf..89826bebb76 100644 --- a/lisp/eshell/em-cmpl.el +++ b/lisp/eshell/em-cmpl.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -136,75 +136,70 @@ to writing a completion function." :type '(repeat (cons string regexp)) :group 'eshell-cmpl) +(defun eshell-cmpl--custom-variable-docstring (pcomplete-var) + "Generate the docstring of a variable derived from a pcomplete-* variable." + (format "%s\n\nIts value is assigned to `%s' locally after eshell starts." + (documentation-property pcomplete-var + 'variable-documentation t) + (symbol-name pcomplete-var))) + (defcustom eshell-cmpl-file-ignore "~\\'" - (documentation-property 'pcomplete-file-ignore - 'variable-documentation) + (eshell-cmpl--custom-variable-docstring 'pcomplete-file-ignore) :type (get 'pcomplete-file-ignore 'custom-type) :group 'eshell-cmpl) (defcustom eshell-cmpl-dir-ignore "\\`\\(\\.\\.?\\|CVS\\)/\\'" - (documentation-property 'pcomplete-dir-ignore - 'variable-documentation) + (eshell-cmpl--custom-variable-docstring 'pcomplete-dir-ignore) :type (get 'pcomplete-dir-ignore 'custom-type) :group 'eshell-cmpl) (defcustom eshell-cmpl-ignore-case (eshell-under-windows-p) - (documentation-property 'pcomplete-ignore-case - 'variable-documentation) + (eshell-cmpl--custom-variable-docstring 'pcomplete-ignore-case) :type (get 'pcomplete-ignore-case 'custom-type) :group 'eshell-cmpl) (defcustom eshell-cmpl-autolist nil - (documentation-property 'pcomplete-autolist - 'variable-documentation) + (eshell-cmpl--custom-variable-docstring 'pcomplete-autolist) :type (get 'pcomplete-autolist 'custom-type) :group 'eshell-cmpl) (defcustom eshell-cmpl-suffix-list (list ?/ ?:) - (documentation-property 'pcomplete-suffix-list - 'variable-documentation) + (eshell-cmpl--custom-variable-docstring 'pcomplete-suffix-list) :type (get 'pcomplete-suffix-list 'custom-type) :group 'pcomplete) (defcustom eshell-cmpl-recexact nil - (documentation-property 'pcomplete-recexact - 'variable-documentation) + (eshell-cmpl--custom-variable-docstring 'pcomplete-recexact) :type (get 'pcomplete-recexact 'custom-type) :group 'eshell-cmpl) (defcustom eshell-cmpl-man-function 'man - (documentation-property 'pcomplete-man-function - 'variable-documentation) + (eshell-cmpl--custom-variable-docstring 'pcomplete-man-function) :type (get 'pcomplete-man-function 'custom-type) :group 'eshell-cmpl) (defcustom eshell-cmpl-compare-entry-function 'file-newer-than-file-p - (documentation-property 'pcomplete-compare-entry-function - 'variable-documentation) + (eshell-cmpl--custom-variable-docstring 'pcomplete-compare-entry-function) :type (get 'pcomplete-compare-entry-function 'custom-type) :group 'eshell-cmpl) (defcustom eshell-cmpl-expand-before-complete nil - (documentation-property 'pcomplete-expand-before-complete - 'variable-documentation) + (eshell-cmpl--custom-variable-docstring 'pcomplete-expand-before-complete) :type (get 'pcomplete-expand-before-complete 'custom-type) :group 'eshell-cmpl) (defcustom eshell-cmpl-cycle-completions t - (documentation-property 'pcomplete-cycle-completions - 'variable-documentation) + (eshell-cmpl--custom-variable-docstring 'pcomplete-cycle-completions) :type (get 'pcomplete-cycle-completions 'custom-type) :group 'eshell-cmpl) (defcustom eshell-cmpl-cycle-cutoff-length 5 - (documentation-property 'pcomplete-cycle-cutoff-length - 'variable-documentation) + (eshell-cmpl--custom-variable-docstring 'pcomplete-cycle-cutoff-length) :type (get 'pcomplete-cycle-cutoff-length 'custom-type) :group 'eshell-cmpl) (defcustom eshell-cmpl-restore-window-delay 1 - (documentation-property 'pcomplete-restore-window-delay - 'variable-documentation) + (eshell-cmpl--custom-variable-docstring 'pcomplete-restore-window-delay) :type (get 'pcomplete-restore-window-delay 'custom-type) :group 'eshell-cmpl) @@ -212,15 +207,13 @@ to writing a completion function." (function (lambda () (pcomplete-here (eshell-complete-commands-list)))) - (documentation-property 'pcomplete-command-completion-function - 'variable-documentation) + (eshell-cmpl--custom-variable-docstring 'pcomplete-command-completion-function) :type (get 'pcomplete-command-completion-function 'custom-type) :group 'eshell-cmpl) (defcustom eshell-cmpl-command-name-function 'eshell-completion-command-name - (documentation-property 'pcomplete-command-name-function - 'variable-documentation) + (eshell-cmpl--custom-variable-docstring 'pcomplete-command-name-function) :type (get 'pcomplete-command-name-function 'custom-type) :group 'eshell-cmpl) @@ -231,13 +224,12 @@ to writing a completion function." (pcomplete-dirs-or-entries (cdr (assoc (funcall eshell-cmpl-command-name-function) eshell-command-completions-alist))))))) - (documentation-property 'pcomplete-default-completion-function - 'variable-documentation) + (eshell-cmpl--custom-variable-docstring 'pcomplete-default-completion-function) :type (get 'pcomplete-default-completion-function 'custom-type) :group 'eshell-cmpl) (defcustom eshell-cmpl-use-paring t - (documentation-property 'pcomplete-use-paring 'variable-documentation) + (eshell-cmpl--custom-variable-docstring 'pcomplete-use-paring) :type (get 'pcomplete-use-paring 'custom-type) :group 'eshell-cmpl) diff --git a/lisp/eshell/em-dirs.el b/lisp/eshell/em-dirs.el index c10ff16ef26..0d87f2a599e 100644 --- a/lisp/eshell/em-dirs.el +++ b/lisp/eshell/em-dirs.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/eshell/em-glob.el b/lisp/eshell/em-glob.el index fee3ff20981..11d7ffcfc53 100644 --- a/lisp/eshell/em-glob.el +++ b/lisp/eshell/em-glob.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/eshell/em-hist.el b/lisp/eshell/em-hist.el index 5c6e6291209..df462a70587 100644 --- a/lisp/eshell/em-hist.el +++ b/lisp/eshell/em-hist.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -218,9 +218,6 @@ Returns nil if INPUT is prepended by blank space, otherwise non-nil." (defun eshell-hist-initialize () "Initialize the history management code for one Eshell buffer." - (add-hook 'eshell-expand-input-functions - 'eshell-expand-history-references nil t) - (when (eshell-using-module 'eshell-cmpl) (add-hook 'pcomplete-try-first-hook 'eshell-complete-history-reference nil t)) @@ -444,7 +441,6 @@ line, with the most recent command last. See also (ignore-dups eshell-hist-ignoredups)) (with-temp-buffer (insert-file-contents file) - ;; Save restriction in case file is already visited... ;; Watch for those date stamps in history files! (goto-char (point-max)) (while (and (< count size) @@ -488,7 +484,9 @@ See also `eshell-read-history'." (while (> index 0) (setq index (1- index)) (let ((start (point))) - (insert (ring-ref ring index) ?\n) + ;; Remove properties before inserting, to avoid trouble + ;; with read-only strings (Bug#28700). + (insert (substring-no-properties (ring-ref ring index)) ?\n) (subst-char-in-region start (1- (point)) ?\n ?\177))) (eshell-with-private-file-modes (write-region (point-min) (point-max) file append diff --git a/lisp/eshell/em-ls.el b/lisp/eshell/em-ls.el index 79799db30bc..bb087d2feba 100644 --- a/lisp/eshell/em-ls.el +++ b/lisp/eshell/em-ls.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -65,17 +65,19 @@ This is useful for enabling human-readable format (-h), for example." "If non-nil, use `eshell-ls' to read directories in Dired. Changing this without using customize has no effect." :set (lambda (symbol value) - (if value - (advice-add 'insert-directory :around - #'eshell-ls--insert-directory) - (advice-remove 'insert-directory - #'eshell-ls--insert-directory)) + (cond (value + (require 'dired) + (advice-add 'insert-directory :around + #'eshell-ls--insert-directory) + (advice-add 'dired :around #'eshell-ls--dired)) + (t + (advice-remove 'insert-directory + #'eshell-ls--insert-directory) + (advice-remove 'dired #'eshell-ls--dired))) (set symbol value)) :type 'boolean :require 'em-ls) -(add-hook 'eshell-ls-unload-hook - (lambda () (advice-remove 'insert-directory - #'eshell-ls--insert-directory))) +(add-hook 'eshell-ls-unload-hook #'eshell-ls-unload-function) (defcustom eshell-ls-default-blocksize 1024 @@ -241,6 +243,9 @@ scope during the evaluation of TEST-SEXP." ;;; Functions: +(declare-function eshell-extended-glob "em-glob" (glob)) +(defvar eshell-error-if-no-glob) + (defun eshell-ls--insert-directory (orig-fun file switches &optional wildcard full-directory-p) "Insert directory listing for FILE, formatted according to SWITCHES. @@ -273,11 +278,53 @@ instead." (set 'font-lock-buffers (delq (current-buffer) (symbol-value 'font-lock-buffers))))) - (let ((insert-func 'insert) - (error-func 'insert) - (flush-func 'ignore) - eshell-ls-dired-initial-args) - (eshell-do-ls (append switches (list file))))))))) + (require 'em-glob) + (let* ((insert-func 'insert) + (error-func 'insert) + (flush-func 'ignore) + (eshell-error-if-no-glob t) + (target ; Expand the shell wildcards if any. + (if (and (atom file) + (string-match "[[?*]" file) + (not (file-exists-p file))) + (mapcar #'file-relative-name (eshell-extended-glob file)) + (file-relative-name file))) + (switches + (append eshell-ls-dired-initial-args + (and (or (consp dired-directory) wildcard) (list "-d")) + switches))) + (eshell-do-ls (nconc switches (list target))))))))) + + +(declare-function eshell-extended-glob "em-glob" (glob)) +(declare-function dired-read-dir-and-switches "dired" (str)) +(declare-function dired-goto-next-file "dired" ()) + +(defun eshell-ls--dired (orig-fun dir-or-list &optional switches) + (interactive (dired-read-dir-and-switches "")) + (require 'em-glob) + (if (consp dir-or-list) + (funcall orig-fun dir-or-list switches) + (let ((dir-wildcard (insert-directory-wildcard-in-dir-p + (expand-file-name dir-or-list)))) + (if (not dir-wildcard) + (funcall orig-fun dir-or-list switches) + (let* ((default-directory (car dir-wildcard)) + (files (eshell-extended-glob (cdr dir-wildcard))) + (dir (car dir-wildcard))) + (if files + (let ((inhibit-read-only t) + (buf + (apply orig-fun + (nconc (list dir) files) + (and switches (list switches))))) + (with-current-buffer buf + (save-excursion + (goto-char (point-min)) + (dired-goto-next-file) + (forward-line 0) + (insert " wildcard " (cdr dir-wildcard) "\n")))) + (user-error "No files matching regexp"))))))) (defsubst eshell/ls (&rest args) "An alias version of `eshell-do-ls'." @@ -909,6 +956,11 @@ to use, and each member of which is the width of that column (car file))))) (car file)) +(defun eshell-ls-unload-function () + (advice-remove 'insert-directory #'eshell-ls--insert-directory) + (advice-remove 'dired #'eshell-ls--dired) + nil) + (provide 'em-ls) ;; Local Variables: diff --git a/lisp/eshell/em-pred.el b/lisp/eshell/em-pred.el index 9e6890ebc97..72a7bc4afcb 100644 --- a/lisp/eshell/em-pred.el +++ b/lisp/eshell/em-pred.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/eshell/em-prompt.el b/lisp/eshell/em-prompt.el index 53a83e6a67b..76dd13ff842 100644 --- a/lisp/eshell/em-prompt.el +++ b/lisp/eshell/em-prompt.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -117,6 +117,8 @@ arriving, or after." (defun eshell-emit-prompt () "Emit a prompt if eshell is being used interactively." + (when (boundp 'ansi-color-context-region) + (setq ansi-color-context-region nil)) (run-hooks 'eshell-before-prompt-hook) (if (not eshell-prompt-function) (set-marker eshell-last-output-end (point)) @@ -159,14 +161,25 @@ If N is negative, find the previous or Nth previous match." "Move to end of Nth next prompt in the buffer. See `eshell-prompt-regexp'." (interactive "p") - (forward-paragraph n) + (if eshell-highlight-prompt + (progn + (while (< n 0) + (while (and (re-search-backward eshell-prompt-regexp nil t) + (not (get-text-property (match-beginning 0) 'read-only)))) + (setq n (1+ n))) + (while (> n 0) + (while (and (re-search-forward eshell-prompt-regexp nil t) + (not (get-text-property (match-beginning 0) 'read-only)))) + (setq n (1- n)))) + (re-search-forward eshell-prompt-regexp nil t n)) (eshell-skip-prompt)) (defun eshell-previous-prompt (n) "Move to end of Nth previous prompt in the buffer. See `eshell-prompt-regexp'." (interactive "p") - (eshell-next-prompt (- (1+ n)))) + (beginning-of-line) ; Don't count prompt on current line. + (eshell-next-prompt (- n))) (defun eshell-skip-prompt () "Skip past the text matching regexp `eshell-prompt-regexp'. diff --git a/lisp/eshell/em-rebind.el b/lisp/eshell/em-rebind.el index a1f9054daed..07f4318e58c 100644 --- a/lisp/eshell/em-rebind.el +++ b/lisp/eshell/em-rebind.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/eshell/em-script.el b/lisp/eshell/em-script.el index b8333adf550..bbc2f9acf6b 100644 --- a/lisp/eshell/em-script.el +++ b/lisp/eshell/em-script.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/eshell/em-smart.el b/lisp/eshell/em-smart.el index 718198689f3..f79f46387b7 100644 --- a/lisp/eshell/em-smart.el +++ b/lisp/eshell/em-smart.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/eshell/em-term.el b/lisp/eshell/em-term.el index ea38f12124a..261a32e97cf 100644 --- a/lisp/eshell/em-term.el +++ b/lisp/eshell/em-term.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/eshell/em-tramp.el b/lisp/eshell/em-tramp.el index d2697227bc0..e322cea1e21 100644 --- a/lisp/eshell/em-tramp.el +++ b/lisp/eshell/em-tramp.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/eshell/em-unix.el b/lisp/eshell/em-unix.el index 86e0d829a14..e5c799ea167 100644 --- a/lisp/eshell/em-unix.el +++ b/lisp/eshell/em-unix.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -961,7 +961,7 @@ Show wall-clock time elapsed during execution of COMMAND.") ;; after setting (throw 'eshell-replace-command (eshell-parse-command (car time-args) -;;; http://lists.gnu.org/archive/html/bug-gnu-emacs/2007-08/msg00205.html +;;; https://lists.gnu.org/r/bug-gnu-emacs/2007-08/msg00205.html (eshell-stringify-list (eshell-flatten-list (cdr time-args)))))))) diff --git a/lisp/eshell/em-xtra.el b/lisp/eshell/em-xtra.el index fe839de03ac..7b80f64d629 100644 --- a/lisp/eshell/em-xtra.el +++ b/lisp/eshell/em-xtra.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/eshell/esh-arg.el b/lisp/eshell/esh-arg.el index 7843ca166be..b317f4e1d2a 100644 --- a/lisp/eshell/esh-arg.el +++ b/lisp/eshell/esh-arg.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/eshell/esh-cmd.el b/lisp/eshell/esh-cmd.el index 86e7b83c281..6c26af8999f 100644 --- a/lisp/eshell/esh-cmd.el +++ b/lisp/eshell/esh-cmd.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -1148,6 +1148,8 @@ be finished later after the completion of an asynchronous subprocess." ;; command invocation +(declare-function help-fns-function-description-header "help-fns") + (defun eshell/which (command &rest names) "Identify the COMMAND, and where it is located." (dolist (name (cons command names)) @@ -1164,25 +1166,17 @@ be finished later after the completion of an asynchronous subprocess." (concat name " is an alias, defined as \"" (cadr alias) "\""))) (unless program - (setq program (eshell-search-path name)) - (let* ((esym (eshell-find-alias-function name)) - (sym (or esym (intern-soft name)))) - (if (and (or esym (and sym (fboundp sym))) - (or eshell-prefer-lisp-functions (not direct))) - (let ((desc (let ((inhibit-redisplay t)) - (save-window-excursion - (prog1 - (describe-function sym) - (message nil)))))) - (setq desc (if desc (substring desc 0 - (1- (or (string-match "\n" desc) - (length desc)))) - ;; This should not happen. - (format "%s is defined, \ -but no documentation was found" name))) - (if (buffer-live-p (get-buffer "*Help*")) - (kill-buffer "*Help*")) - (setq program (or desc name)))))) + (setq program + (let* ((esym (eshell-find-alias-function name)) + (sym (or esym (intern-soft name)))) + (if (and (or esym (and sym (fboundp sym))) + (or eshell-prefer-lisp-functions (not direct))) + (or (with-output-to-string + (require 'help-fns) + (princ (format "%s is " sym)) + (help-fns-function-description-header sym)) + name) + (eshell-search-path name))))) (if (not program) (eshell-error (format "which: no %s in (%s)\n" name (getenv "PATH"))) diff --git a/lisp/eshell/esh-ext.el b/lisp/eshell/esh-ext.el index 0b292306ff1..14ae6b4ae1d 100644 --- a/lisp/eshell/esh-ext.el +++ b/lisp/eshell/esh-ext.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/eshell/esh-io.el b/lisp/eshell/esh-io.el index 97d48c1fd08..ca791982f56 100644 --- a/lisp/eshell/esh-io.el +++ b/lisp/eshell/esh-io.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/eshell/esh-mode.el b/lisp/eshell/esh-mode.el index 0fd0c183016..ea2fe1a6c26 100644 --- a/lisp/eshell/esh-mode.el +++ b/lisp/eshell/esh-mode.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -726,7 +726,9 @@ This is done after all necessary filtering has been done." (setq obeg (+ obeg nchars))) (if (<= (point) oend) (setq oend (+ oend nchars))) - (insert-before-markers string) + ;; Let the ansi-color overlay hooks run. + (let ((inhibit-modification-hooks nil)) + (insert-before-markers string)) (if (= (window-start) (point)) (set-window-start (selected-window) (- (point) nchars))) diff --git a/lisp/eshell/esh-module.el b/lisp/eshell/esh-module.el index cbff8c84115..fe4c88e1cfd 100644 --- a/lisp/eshell/esh-module.el +++ b/lisp/eshell/esh-module.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: diff --git a/lisp/eshell/esh-opt.el b/lisp/eshell/esh-opt.el index 59757ab6ebb..c141fe0bcea 100644 --- a/lisp/eshell/esh-opt.el +++ b/lisp/eshell/esh-opt.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/eshell/esh-proc.el b/lisp/eshell/esh-proc.el index ba5cb5c2db7..3e9ac281a10 100644 --- a/lisp/eshell/esh-proc.el +++ b/lisp/eshell/esh-proc.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/eshell/esh-util.el b/lisp/eshell/esh-util.el index d65839b72a0..8b24ec3c430 100644 --- a/lisp/eshell/esh-util.el +++ b/lisp/eshell/esh-util.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -142,7 +142,7 @@ function `string-to-number'." (defmacro eshell-condition-case (tag form &rest handlers) "If `eshell-handle-errors' is non-nil, this is `condition-case'. Otherwise, evaluates FORM with no error handling." - (declare (indent 2)) + (declare (indent 2) (debug (sexp form &rest form))) (if eshell-handle-errors `(condition-case-unless-debug ,tag ,form diff --git a/lisp/eshell/esh-var.el b/lisp/eshell/esh-var.el index cdd05bd7e9a..d038609d957 100644 --- a/lisp/eshell/esh-var.el +++ b/lisp/eshell/esh-var.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/eshell/eshell.el b/lisp/eshell/eshell.el index faf5f89d64f..f85f0e82b38 100644 --- a/lisp/eshell/eshell.el +++ b/lisp/eshell/eshell.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/expand.el b/lisp/expand.el index d06287e6f9b..7dab2051f11 100644 --- a/lisp/expand.el +++ b/lisp/expand.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/ezimage.el b/lisp/ezimage.el index 25e0ed306a7..115ebc5670c 100644 --- a/lisp/ezimage.el +++ b/lisp/ezimage.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/face-remap.el b/lisp/face-remap.el index 4e6ada8acd3..129b90301ba 100644 --- a/lisp/face-remap.el +++ b/lisp/face-remap.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; ;;; Commentary: diff --git a/lisp/facemenu.el b/lisp/facemenu.el index ae5865d7399..5db640ba254 100644 --- a/lisp/facemenu.el +++ b/lisp/facemenu.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/faces.el b/lisp/faces.el index 9a8a1344caf..d8ec454e626 100644 --- a/lisp/faces.el +++ b/lisp/faces.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -102,11 +102,18 @@ a font height that isn't optimal." ;; Monospace Serif is an Emacs invention, intended to work around ;; portability problems when using Courier. It should work well ;; when combined with Monospaced and with other standard fonts. + ;; One of its uses is for 'tex-verbatim' and 'Info-quoted' faces, + ;; so the result must be different from the default face's font, + ;; and must be monospaced. For 'tex-verbatim', it is desirable + ;; that the font really is a Serif font, so as to look like + ;; TeX's 'verbatim'. ("Monospace Serif" ;; This looks good on GNU/Linux. "Courier 10 Pitch" - ;; This looks good on MS-Windows and OS X. + ;; This looks good on MS-Windows and OS X. Note that this is + ;; actually a sans-serif font, but it's here for lack of a better + ;; alternative. "Consolas" ;; This looks good on macOS. "Courier" looks good too, but is ;; jagged on GNU/Linux and so is listed later as "courier". @@ -1447,7 +1454,7 @@ If FRAME is omitted or nil, use the selected frame." (setq face (list face))) (with-help-window (help-buffer) (with-current-buffer standard-output - (dolist (f face) + (dolist (f face (buffer-string)) (if (stringp f) (setq f (intern f))) ;; We may get called for anonymous faces (i.e., faces ;; expressed using prop-value plists). Those can't be @@ -2354,7 +2361,7 @@ If you set `term-file-prefix' to nil, this function does nothing." (defface variable-pitch '((((type w32)) ;; This is a workaround for an issue discussed in - ;; http://lists.gnu.org/archive/html/emacs-devel/2016-04/msg00746.html. + ;; https://lists.gnu.org/r/emacs-devel/2016-04/msg00746.html. ;; We need (a) the splash screen not to pick up bold-italics variant of ;; the font, and (b) still be able to request bold/italic/larger size ;; variants in the likes of EWW. @@ -2465,6 +2472,35 @@ If you set `term-file-prefix' to nil, this function does nothing." :version "21.1" :group 'basic-faces) +;; Definition stolen from linum.el. +(defface line-number + '((t :inherit (shadow default))) + "Face for displaying line numbers. +This face is used when `display-line-numbers' is non-nil. + +If you customize the font of this face, make sure it is a +monospaced font, otherwise line numbers will not line up, +and text lines might move horizontally as you move through +the buffer." + :version "26.1" + :group 'basic-faces + :group 'display-line-numbers) + +(defface line-number-current-line + '((t :inherit line-number)) + "Face for displaying the current line number. +This face is used when `display-line-numbers' is non-nil. + +If you customize the font of this face, make sure it is a +monospaced font, otherwise line numbers will not line up, +and text lines might move horizontally as you move through +the buffer. Similarly, making this face's font different +from that of the `line-number' face could produce such +unwanted effects." + :version "26.1" + :group 'basic-faces + :group 'display-line-numbers) + (defface escape-glyph '((((background dark)) :foreground "cyan") ;; See the comment in minibuffer-prompt for @@ -2594,6 +2630,11 @@ Use the face `mode-line-highlight' for features that can be selected." :version "21.1" :group 'basic-faces) +(defface header-line-highlight '((t :inherit highlight)) + "Basic header line face for highlighting." + :version "26.1" + :group 'basic-faces) + (defface vertical-border '((((type tty)) :inherit mode-line-inactive)) "Face used for vertical window dividers on ttys." @@ -2815,6 +2856,13 @@ It is used for characters of no fonts too." "Face used for a matching paren." :group 'paren-showing-faces) +(defface show-paren-match-expression + '((t :inherit show-paren-match)) + "Face used for a matching paren when highlighting the whole expression. +This face is used by `show-paren-mode'." + :group 'paren-showing-faces + :version "26.1") + (defface show-paren-mismatch '((((class color)) (:foreground "white" :background "purple")) (t (:inverse-video t))) diff --git a/lisp/ffap.el b/lisp/ffap.el index 87531110b86..a776668d109 100644 --- a/lisp/ffap.el +++ b/lisp/ffap.el @@ -6,7 +6,6 @@ ;; Maintainer: emacs-devel@gnu.org ;; Created: 29 Mar 1993 ;; Keywords: files, hypermedia, matching, mouse, convenience -;; X-URL: ftp://ftp.mathcs.emory.edu/pub/mic/emacs/ ;; This file is part of GNU Emacs. @@ -21,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -787,7 +786,7 @@ This uses `ffap-file-exists-string', which may try adding suffixes from ("\\`~/" . ffap-lcd) ; |~/misc/ffap.el.Z| ;; This used to have a blank, but ffap-string-at-point doesn't ;; handle blanks. - ;; http://lists.gnu.org/archive/html/emacs-devel/2008-01/msg01058.html + ;; https://lists.gnu.org/r/emacs-devel/2008-01/msg01058.html ("\\`[Rr][Ff][Cc][-#]?\\([0-9]+\\)" ; no $ . ffap-rfc) ; "100% RFC2100 compliant" (dired-mode . ffap-dired) ; maybe in a subdirectory @@ -1536,7 +1535,8 @@ If `ffap-url-regexp' is not nil, the FILENAME may also be an URL. With a prefix, this command behaves exactly like `ffap-file-finder'. If `ffap-require-prefix' is set, the prefix meaning is reversed. See also the variables `ffap-dired-wildcards', `ffap-newfile-prompt', -and the functions `ffap-file-at-point' and `ffap-url-at-point'." +`ffap-url-unwrap-local', `ffap-url-unwrap-remote', and the functions +`ffap-file-at-point' and `ffap-url-at-point'." (interactive) (if (and (called-interactively-p 'interactive) (if ffap-require-prefix (not current-prefix-arg) diff --git a/lisp/filecache.el b/lisp/filecache.el index 02b5f79c07a..ea7cbcb6f10 100644 --- a/lisp/filecache.el +++ b/lisp/filecache.el @@ -1,4 +1,4 @@ -;;; filecache.el --- find files using a pre-loaded cache +;;; filecache.el --- find files using a pre-loaded cache -*- lexical-binding:t -*- ;; Copyright (C) 1996, 2000-2017 Free Software Foundation, Inc. @@ -19,22 +19,22 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; ;; The file-cache package is an attempt to make it easy to locate files ;; by name, without having to remember exactly where they are located. -;; This is very handy when working with source trees. You can also add +;; This is very handy when working with source trees. You can also add ;; frequently used files to the cache to create a hotlist effect. ;; The cache can be used with any interactive command which takes a ;; filename as an argument. ;; ;; It is worth noting that this package works best when most of the files ;; in the cache have unique names, or (if they have the same name) exist in -;; only a few directories. The worst case is many files all with +;; only a few directories. The worst case is many files all with ;; the same name and in different directories, for example a big source tree -;; with a Makefile in each directory. In such a case, you should probably +;; with a Makefile in each directory. In such a case, you should probably ;; use an alternate strategy to find the files. ;; ;; ADDING FILES TO THE CACHE: @@ -49,11 +49,11 @@ ;; `file-cache-delete-regexps' to eliminate unwanted files: ;; ;; * `file-cache-add-directory': Adds the files in a directory to the -;; cache. You can also specify a regular expression to match the files +;; cache. You can also specify a regular expression to match the files ;; which should be added. ;; ;; * `file-cache-add-directory-list': Same as above, but acts on a list -;; of directories. You can use `load-path', `exec-path' and the like. +;; of directories. You can use `load-path', `exec-path' and the like. ;; ;; * `file-cache-add-directory-using-find': Uses the `find' command to ;; add a directory tree to the cache. @@ -65,7 +65,7 @@ ;; add all files matching a pattern to the cache. ;; ;; Use the function `file-cache-clear-cache' to remove all items from the -;; cache. There are a number of `file-cache-delete' functions provided +;; cache. There are a number of `file-cache-delete' functions provided ;; as well, but in general it is probably better to not worry too much ;; about extra files in the cache. ;; @@ -76,7 +76,7 @@ ;; FINDING FILES USING THE CACHE: ;; ;; You can use the file-cache with any function that expects a filename as -;; an argument. For example: +;; an argument. For example: ;; ;; 1) Invoke a function which expects a filename as an argument: ;; M-x find-file @@ -160,13 +160,11 @@ File names which match these expressions will not be added to the cache. Note that the functions `file-cache-add-file' and `file-cache-add-file-list' do not use this variable." :version "25.1" ; added "/\\.#" - :type '(repeat regexp) - :group 'file-cache) + :type '(repeat regexp)) (defcustom file-cache-find-command "find" "External program used by `file-cache-add-directory-using-find'." - :type 'string - :group 'file-cache) + :type 'string) (defcustom file-cache-find-command-posix-flag 'not-defined "Set to t, if `file-cache-find-command' handles wildcards POSIX style. @@ -178,30 +176,25 @@ Under Windows operating system where Cygwin is available, this value should be t." :type '(choice (const :tag "Yes" t) (const :tag "No" nil) - (const :tag "Unknown" not-defined)) - :group 'file-cache) + (const :tag "Unknown" not-defined))) (defcustom file-cache-locate-command "locate" "External program used by `file-cache-add-directory-using-locate'." - :type 'string - :group 'file-cache) + :type 'string) ;; Minibuffer messages (defcustom file-cache-no-match-message " [File Cache: No match]" "Message to display when there is no completion." - :type 'string - :group 'file-cache) + :type 'string) (defcustom file-cache-sole-match-message " [File Cache: sole completion]" "Message to display when there is only one completion." - :type 'string - :group 'file-cache) + :type 'string) (defcustom file-cache-non-unique-message " [File Cache: complete but not unique]" "Message to display when there is a non-unique completion." - :type 'string - :group 'file-cache) + :type 'string) (defcustom file-cache-completion-ignore-case (if (memq system-type '(ms-dos windows-nt cygwin)) @@ -209,8 +202,7 @@ should be t." completion-ignore-case) "If non-nil, file-cache completion should ignore case. Defaults to the value of `completion-ignore-case'." - :type 'boolean - :group 'file-cache) + :type 'boolean) (defcustom file-cache-case-fold-search (if (memq system-type '(ms-dos windows-nt cygwin)) @@ -218,15 +210,13 @@ Defaults to the value of `completion-ignore-case'." case-fold-search) "If non-nil, file-cache completion should ignore case. Defaults to the value of `case-fold-search'." - :type 'boolean - :group 'file-cache) + :type 'boolean) (defcustom file-cache-ignore-case (memq system-type '(ms-dos windows-nt cygwin)) "Non-nil means ignore case when checking completions in the file cache. Defaults to nil on DOS and Windows, and t on other systems." - :type 'boolean - :group 'file-cache) + :type 'boolean) (defvar file-cache-multiple-directory-message nil) @@ -235,18 +225,10 @@ Defaults to nil on DOS and Windows, and t on other systems." ;; switch-to-completions in simple.el expects (defcustom file-cache-completions-buffer "*Completions*" "Buffer to display completions when using the file cache." - :type 'string - :group 'file-cache) + :type 'string) -(defcustom file-cache-buffer "*File Cache*" - "Buffer to hold the cache of file names." - :type 'string - :group 'file-cache) - -(defcustom file-cache-buffer-default-regexp "^.+$" - "Regexp to match files in `file-cache-buffer'." - :type 'regexp - :group 'file-cache) +(defvar file-cache-buffer-default-regexp "^.+$" + "Regexp to match files in find and locate's output.") (defvar file-cache-last-completion nil) @@ -362,36 +344,31 @@ Find is run in DIRECTORY." (if (eq file-cache-find-command-posix-flag 'not-defined) (setq file-cache-find-command-posix-flag (executable-command-find-posix-p file-cache-find-command)))) - (set-buffer (get-buffer-create file-cache-buffer)) - (erase-buffer) - (call-process file-cache-find-command nil - (get-buffer file-cache-buffer) nil - dir "-name" - (if (memq system-type '(windows-nt cygwin)) - (if file-cache-find-command-posix-flag - "\\*" - "'*'") - "*") - "-print") - (file-cache-add-from-file-cache-buffer))) + (with-temp-buffer + (call-process file-cache-find-command nil t nil + dir "-name" + (if (memq system-type '(windows-nt cygwin)) + (if file-cache-find-command-posix-flag + "\\*" + "'*'") + "*") + "-print") + (file-cache--add-from-buffer)))) ;;;###autoload (defun file-cache-add-directory-using-locate (string) "Use the `locate' command to add files to the file cache. STRING is passed as an argument to the locate command." (interactive "sAdd files using locate string: ") - (set-buffer (get-buffer-create file-cache-buffer)) - (erase-buffer) - (call-process file-cache-locate-command nil - (get-buffer file-cache-buffer) nil - string) - (file-cache-add-from-file-cache-buffer)) + (with-temp-buffer + (call-process file-cache-locate-command nil t nil string) + (file-cache--add-from-buffer))) (autoload 'find-lisp-find-files "find-lisp") ;;;###autoload (defun file-cache-add-directory-recursively (dir &optional regexp) - "Adds DIR and any subdirectories to the file-cache. + "Add DIR and any subdirectories to the file-cache. This function does not use any external programs. If the optional REGEXP argument is non-nil, only files which match it will be added to the cache. Note that the REGEXP is applied to the @@ -408,22 +385,16 @@ files in each directory, not to the directory list itself." (file-cache-add-file file))) (find-lisp-find-files dir (or regexp "^")))) -(defun file-cache-add-from-file-cache-buffer (&optional regexp) - "Add any entries found in the file cache buffer. +(defun file-cache--add-from-buffer () + "Add any entries found in the current buffer. Each entry matches the regular expression `file-cache-buffer-default-regexp' or the optional REGEXP argument." - (set-buffer file-cache-buffer) (dolist (elt file-cache-filter-regexps) (goto-char (point-min)) (delete-matching-lines elt)) (goto-char (point-min)) - (let ((full-filename)) - (while (re-search-forward - (or regexp file-cache-buffer-default-regexp) - (point-max) t) - (setq full-filename (buffer-substring-no-properties - (match-beginning 0) (match-end 0))) - (file-cache-add-file full-filename)))) + (while (re-search-forward file-cache-buffer-default-regexp nil t) + (file-cache-add-file (match-string-no-properties 0)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Functions to delete from the cache @@ -566,68 +537,65 @@ the directories that the name is available in. With a prefix argument, the name is considered already unique; only the second substitution \(directories) is done." (interactive "P") - (let* - ( - (completion-ignore-case file-cache-completion-ignore-case) - (case-fold-search file-cache-case-fold-search) - (string (file-name-nondirectory (minibuffer-contents))) - (completion-string (try-completion string file-cache-alist)) - (completion-list) - (len) - (file-cache-string)) + (let* ((completion-ignore-case file-cache-completion-ignore-case) + (case-fold-search file-cache-case-fold-search) + (string (file-name-nondirectory (minibuffer-contents))) + (completion (completion-try-completion + string file-cache-alist nil 0))) (cond ;; If it's the only match, replace the original contents - ((or arg (eq completion-string t)) - (setq file-cache-string (file-cache-file-name string)) - (if (string= file-cache-string (minibuffer-contents)) - (minibuffer-message file-cache-sole-match-message) - (delete-minibuffer-contents) - (insert file-cache-string) - (if file-cache-multiple-directory-message - (minibuffer-message file-cache-multiple-directory-message)))) + ((or arg (eq completion t)) + (let ((file-name (file-cache-file-name string))) + (if (string= file-name (minibuffer-contents)) + (minibuffer-message file-cache-sole-match-message) + (delete-minibuffer-contents) + (insert file-name) + (if file-cache-multiple-directory-message + (minibuffer-message file-cache-multiple-directory-message))))) ;; If it's the longest match, insert it - ((stringp completion-string) - ;; If we've already inserted a unique string, see if the user - ;; wants to use that one - (if (and (string= string completion-string) - (assoc-string string file-cache-alist - file-cache-ignore-case)) - (if (and (eq last-command this-command) - (string= file-cache-last-completion completion-string)) - (progn - (delete-minibuffer-contents) - (insert (file-cache-file-name completion-string)) - (setq file-cache-last-completion nil)) - (minibuffer-message file-cache-non-unique-message) - (setq file-cache-last-completion string)) - (setq file-cache-last-completion string) - (setq completion-list (all-completions string file-cache-alist) - len (length completion-list)) - (if (> len 1) - (progn - (goto-char (point-max)) - (insert - (substring completion-string (length string))) - ;; Add our own setup function to the Completions Buffer - (let ((completion-setup-hook - (append completion-setup-hook - (list 'file-cache-completion-setup-function)))) - (with-output-to-temp-buffer file-cache-completions-buffer - (display-completion-list - (completion-hilit-commonality completion-list - (length string)))))) - (setq file-cache-string (file-cache-file-name completion-string)) - (if (string= file-cache-string (minibuffer-contents)) - (minibuffer-message file-cache-sole-match-message) - (delete-minibuffer-contents) - (insert file-cache-string) - (if file-cache-multiple-directory-message - (minibuffer-message file-cache-multiple-directory-message))) - ))) + ((consp completion) + (let ((newstring (car completion)) + (newpoint (cdr completion))) + ;; If we've already inserted a unique string, see if the user + ;; wants to use that one + (if (and (string= string newstring) + (assoc-string string file-cache-alist + file-cache-ignore-case)) + (if (and (eq last-command this-command) + (string= file-cache-last-completion newstring)) + (progn + (delete-minibuffer-contents) + (insert (file-cache-file-name newstring)) + (setq file-cache-last-completion nil)) + (minibuffer-message file-cache-non-unique-message) + (setq file-cache-last-completion string)) + (setq file-cache-last-completion string) + (let* ((completion-list (completion-all-completions + newstring file-cache-alist nil newpoint)) + (base-size (cdr (last completion-list)))) + (when base-size + (setcdr (last completion-list) nil)) + (if (> (length completion-list) 1) + (progn + (delete-region (- (point-max) (length string)) (point-max)) + (save-excursion (insert newstring)) + (forward-char newpoint) + (with-output-to-temp-buffer file-cache-completions-buffer + (display-completion-list completion-list) + ;; Add our own setup function to the Completions Buffer + (file-cache-completion-setup-function))) + (let ((file-name (file-cache-file-name newstring))) + (if (string= file-name (minibuffer-contents)) + (minibuffer-message file-cache-sole-match-message) + (delete-minibuffer-contents) + (insert file-name) + (if file-cache-multiple-directory-message + (minibuffer-message + file-cache-multiple-directory-message))))))))) ;; No match - ((eq completion-string nil) + ((eq completion nil) (minibuffer-message file-cache-no-match-message))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -647,7 +615,7 @@ the name is considered already unique; only the second substitution (file-cache-minibuffer-complete nil))) (define-obsolete-function-alias 'file-cache-mouse-choose-completion - 'file-cache-choose-completion "23.2") + #'file-cache-choose-completion "23.2") (defun file-cache-complete () "Complete the word at point, using the filecache." diff --git a/lisp/filenotify.el b/lisp/filenotify.el index 64cfab143ec..18c44ec3e1e 100644 --- a/lisp/filenotify.el +++ b/lisp/filenotify.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary @@ -71,7 +71,7 @@ struct.") "Remove DESCRIPTOR from `file-notify-descriptors'. DESCRIPTOR should be an object returned by `file-notify-add-watch'. If it is registered in `file-notify-descriptors', a stopped event is sent." - (when-let (watch (gethash descriptor file-notify-descriptors)) + (when-let* ((watch (gethash descriptor file-notify-descriptors))) ;; Send `stopped' event. (unwind-protect (funcall @@ -106,12 +106,12 @@ It is a form ((DESCRIPTOR ACTION FILE [FILE1-OR-COOKIE]) CALLBACK).") (defun file-notify--event-watched-file (event) "Return file or directory being watched. Could be different from the directory watched by the backend library." - (when-let (watch (gethash (car event) file-notify-descriptors)) + (when-let* ((watch (gethash (car event) file-notify-descriptors))) (file-notify--watch-absolute-filename watch))) (defun file-notify--event-file-name (event) "Return file name of file notification event, or nil." - (when-let (watch (gethash (car event) file-notify-descriptors)) + (when-let* ((watch (gethash (car event) file-notify-descriptors))) (directory-file-name (expand-file-name (or (and (stringp (nth 2 event)) (nth 2 event)) "") @@ -121,7 +121,7 @@ Could be different from the directory watched by the backend library." (defun file-notify--event-file1-name (event) "Return second file name of file notification event, or nil. This is available in case a file has been moved." - (when-let (watch (gethash (car event) file-notify-descriptors)) + (when-let* ((watch (gethash (car event) file-notify-descriptors))) (and (stringp (nth 3 event)) (directory-file-name (expand-file-name @@ -375,7 +375,7 @@ FILE is the name of the file whose event is being reported." (defun file-notify-rm-watch (descriptor) "Remove an existing watch specified by its DESCRIPTOR. DESCRIPTOR should be an object returned by `file-notify-add-watch'." - (when-let (watch (gethash descriptor file-notify-descriptors)) + (when-let* ((watch (gethash descriptor file-notify-descriptors))) (let ((handler (find-file-name-handler (file-notify--watch-directory watch) 'file-notify-rm-watch))) @@ -399,7 +399,7 @@ DESCRIPTOR should be an object returned by `file-notify-add-watch'." (defun file-notify-valid-p (descriptor) "Check a watch specified by its DESCRIPTOR. DESCRIPTOR should be an object returned by `file-notify-add-watch'." - (when-let (watch (gethash descriptor file-notify-descriptors)) + (when-let* ((watch (gethash descriptor file-notify-descriptors))) (let ((handler (find-file-name-handler (file-notify--watch-directory watch) 'file-notify-valid-p))) diff --git a/lisp/files-x.el b/lisp/files-x.el index b7c6f51e658..667737075ed 100644 --- a/lisp/files-x.el +++ b/lisp/files-x.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/files.el b/lisp/files.el index 8ac1993754e..a7ad40b76cd 100644 --- a/lisp/files.el +++ b/lisp/files.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -150,8 +150,13 @@ Called with an absolute file name as argument, it returns t to enable backup.") (defcustom buffer-offer-save nil "Non-nil in a buffer means always offer to save buffer on exit. Do so even if the buffer is not visiting a file. -Automatically local in all buffers." - :type 'boolean +Automatically local in all buffers. + +Set to the symbol `always' to offer to save buffer whenever +`save-some-buffers' is called." + :type '(choice (const :tag "Never" nil) + (const :tag "On Emacs exit" t) + (const :tag "Whenever save-some-buffers is called" always)) :group 'backup) (make-variable-buffer-local 'buffer-offer-save) (put 'buffer-offer-save 'permanent-local t) @@ -434,8 +439,11 @@ and toggle it if ARG is `toggle'." (not (and buffer-auto-save-file-name auto-save-visited-file-name))))))) +;; The 'set' part is so we don't get a warning for using this variable +;; above, while still catching code that _sets_ the variable to get +;; the same effect as the new auto-save-visited-mode. (make-obsolete-variable 'auto-save-visited-file-name 'auto-save-visited-mode - "Emacs 26.1") + "Emacs 26.1" 'set) (defcustom save-abbrevs t "Non-nil means save word abbrevs too when files are saved. @@ -514,10 +522,12 @@ updates before the buffer is saved, use `before-save-hook'.") 'write-contents-functions "22.1") (defvar write-contents-functions nil "List of functions to be called before writing out a buffer to a file. -Only used by `save-buffer'. -If one of them returns non-nil, the file is considered already written -and the rest are not called and neither are the functions in -`write-file-functions'. + +Only used by `save-buffer'. If one of them returns non-nil, the +file is considered already written and the rest are not called +and neither are the functions in `write-file-functions'. This +hook can thus be used to create save behavior for buffers that +are not visiting a file at all. This variable is meant to be used for hooks that pertain to the buffer's contents, not to the particular visited file; thus, @@ -591,13 +601,14 @@ settings being applied, but still respect file-local ones.") ;; ignore. So AFAICS the only reason this variable exists is for a ;; minor convenience feature for handling of an obsolete Rmail file format. (defvar local-enable-local-variables t - "Like `enable-local-variables' but meant for buffer-local bindings. + "Like `enable-local-variables', except for major mode in a -*- line. The meaningful values are nil and non-nil. The default is non-nil. -If a major mode sets this to nil, buffer-locally, then any local -variables list in a file visited in that mode will be ignored. +It should be set in a buffer-local fashion. -This variable does not affect the use of major modes specified -in a -*- line.") +Setting this to nil has the same effect as setting `enable-local-variables' +to nil, except that it does not ignore any mode: setting in a -*- line. +Unless this difference matters to you, you should set `enable-local-variables' +instead of this variable.") (defcustom enable-local-eval 'maybe "Control processing of the \"variable\" `eval' in a file's local variables. @@ -788,16 +799,6 @@ The path separator is colon in GNU and GNU-like systems." (lambda (f) (and (file-directory-p f) 'dir-ok))) (error "No such directory found via CDPATH environment variable")))) -(defsubst directory-name-p (name) - "Return non-nil if NAME ends with a directory separator character." - (let ((len (length name)) - (lastc ?.)) - (if (> len 0) - (setq lastc (aref name (1- len)))) - (or (= lastc ?/) - (and (memq system-type '(windows-nt ms-dos)) - (= lastc ?\\))))) - (defun directory-files-recursively (dir regexp &optional include-directories) "Return list of all files under DIR that have file names matching REGEXP. This function works recursively. Files are returned in \"depth first\" @@ -943,68 +944,23 @@ The default regexp prevents fruitless and time-consuming attempts to find special files in directories in which filenames are interpreted as hostnames, or mount points potentially requiring authentication as a different user.") -;; (defun locate-dominating-files (file regexp) -;; "Look up the directory hierarchy from FILE for a file matching REGEXP. -;; Stop at the first parent where a matching file is found and return the list -;; of files that that match in this directory." -;; (catch 'found -;; ;; `user' is not initialized yet because `file' may not exist, so we may -;; ;; have to walk up part of the hierarchy before we find the "initial UID". -;; (let ((user nil) -;; ;; Abbreviate, so as to stop when we cross ~/. -;; (dir (abbreviate-file-name (file-name-as-directory file))) -;; files) -;; (while (and dir -;; ;; As a heuristic, we stop looking up the hierarchy of -;; ;; directories as soon as we find a directory belonging to -;; ;; another user. This should save us from looking in -;; ;; things like /net and /afs. This assumes that all the -;; ;; files inside a project belong to the same user. -;; (let ((prev-user user)) -;; (setq user (nth 2 (file-attributes dir))) -;; (or (null prev-user) (equal user prev-user)))) -;; (if (setq files (condition-case nil -;; (directory-files dir 'full regexp 'nosort) -;; (error nil))) -;; (throw 'found files) -;; (if (equal dir -;; (setq dir (file-name-directory -;; (directory-file-name dir)))) -;; (setq dir nil)))) -;; nil))) - (defun locate-dominating-file (file name) - "Look up the directory hierarchy from FILE for a directory containing NAME. + "Starting at FILE, look up directory hierarchy for directory containing NAME. +FILE can be a file or a directory. If it's a file, its directory will +serve as the starting point for searching the hierarchy of directories. Stop at the first parent directory containing a file NAME, and return the directory. Return nil if not found. Instead of a string, NAME can also be a predicate taking one argument \(a directory) and returning a non-nil value if that directory is the one for -which we're looking." - ;; We used to use the above locate-dominating-files code, but the - ;; directory-files call is very costly, so we're much better off doing - ;; multiple calls using the code in here. - ;; +which we're looking. The predicate will be called with every file/directory +the function needs to examine, starting with FILE." ;; Represent /home/luser/foo as ~/foo so that we don't try to look for ;; `name' in /home or in /. (setq file (abbreviate-file-name (expand-file-name file))) (let ((root nil) - ;; `user' is not initialized outside the loop because - ;; `file' may not exist, so we may have to walk up part of the - ;; hierarchy before we find the "initial UID". Note: currently unused - ;; (user nil) try) (while (not (or root (null file) - ;; FIXME: Disabled this heuristic because it is sometimes - ;; inappropriate. - ;; As a heuristic, we stop looking up the hierarchy of - ;; directories as soon as we find a directory belonging - ;; to another user. This should save us from looking in - ;; things like /net and /afs. This assumes that all the - ;; files inside a project belong to the same user. - ;; (let ((prev-user user)) - ;; (setq user (nth 2 (file-attributes file))) - ;; (and prev-user (not (equal user prev-user)))) (string-match locate-dominating-stop-dir-regexp file))) (setq try (if (stringp name) (file-exists-p (expand-file-name name file)) @@ -1197,6 +1153,29 @@ accessible." (funcall handler 'file-local-copy file) nil))) +(defun files--name-absolute-system-p (file) + "Return non-nil if FILE is an absolute name to the operating system. +This is like `file-name-absolute-p', except that it returns nil for +names beginning with `~'." + (and (file-name-absolute-p file) + (not (eq (aref file 0) ?~)))) + +(defun files--splice-dirname-file (dirname file) + "Splice DIRNAME to FILE like the operating system would. +If FILE is relative, return DIRNAME concatenated to FILE. +Otherwise return FILE, quoted as needed if DIRNAME and FILE have +different handlers; although this quoting is dubious if DIRNAME +is magic, it is not clear what would be better. This function +differs from `expand-file-name' in that DIRNAME must be a +directory name and leading `~' and `/:' are not special in FILE." + (let ((unquoted (if (files--name-absolute-system-p file) + file + (concat dirname file)))) + (if (eq (find-file-name-handler dirname 'file-symlink-p) + (find-file-name-handler unquoted 'file-symlink-p)) + unquoted + (let (file-name-handler-alist) (file-name-quote unquoted))))) + (defun file-truename (filename &optional counter prev-dirs) "Return the truename of FILENAME. If FILENAME is not absolute, first expands it against `default-directory'. @@ -1297,10 +1276,7 @@ containing it, until no links are left at any level. ;; We can't safely use expand-file-name here ;; since target might look like foo/../bar where foo ;; is itself a link. Instead, we handle . and .. above. - (setq filename - (if (file-name-absolute-p target) - target - (concat dir target)) + (setq filename (files--splice-dirname-file dir target) done nil) ;; No, we are done! (setq done t)))))))) @@ -1335,7 +1311,8 @@ it means chase no more than that many links and then stop." (directory-file-name (file-name-directory newname)))) ;; Now find the parent of that dir. (setq newname (file-name-directory newname))) - (setq newname (expand-file-name tem (file-name-directory newname))) + (setq newname (files--splice-dirname-file (file-name-directory newname) + tem)) (setq count (1+ count)))) newname)) @@ -1400,35 +1377,46 @@ the variable `temporary-file-directory' is returned." default-directory temporary-file-directory)))) -(defun make-temp-file (prefix &optional dir-flag suffix) +(defun make-temp-file (prefix &optional dir-flag suffix text) "Create a temporary file. The returned file name (created by appending some random characters at the end of PREFIX, and expanding against `temporary-file-directory' if necessary), -is guaranteed to point to a newly created empty file. +is guaranteed to point to a newly created file. You can then use `write-region' to write new data into the file. If DIR-FLAG is non-nil, create a new empty directory instead of a file. -If SUFFIX is non-nil, add that at the end of the file name." +If SUFFIX is non-nil, add that at the end of the file name. + +If TEXT is a string, insert it into the new file; DIR-FLAG should be nil. +Otherwise the file will be empty." + (let ((absolute-prefix + (if (or (zerop (length prefix)) (member prefix '("." ".."))) + (concat (file-name-as-directory temporary-file-directory) prefix) + (expand-file-name prefix temporary-file-directory)))) + (if (find-file-name-handler absolute-prefix 'write-region) + (files--make-magic-temp-file absolute-prefix dir-flag suffix text) + (make-temp-file-internal absolute-prefix + (if dir-flag t) (or suffix "") text)))) + +(defun files--make-magic-temp-file (absolute-prefix + &optional dir-flag suffix text) + "Implement (make-temp-file ABSOLUTE-PREFIX DIR-FLAG SUFFIX TEXT). +This implementation works on magic file names." ;; Create temp files with strict access rights. It's easy to ;; loosen them later, whereas it's impossible to close the ;; time-window of loose permissions otherwise. (with-file-modes ?\700 - (let (file) + (let ((contents (if (stringp text) text "")) + file) (while (condition-case () (progn - (setq file - (make-temp-name - (if (zerop (length prefix)) - (file-name-as-directory - temporary-file-directory) - (expand-file-name prefix - temporary-file-directory)))) + (setq file (make-temp-name absolute-prefix)) (if suffix (setq file (concat file suffix))) (if dir-flag (make-directory file) - (write-region "" nil file nil 'silent nil 'excl)) + (write-region contents nil file nil 'silent nil 'excl)) nil) (file-already-exists t)) ;; the file was somehow created by someone else between @@ -1572,7 +1560,15 @@ Switch to a buffer visiting file FILENAME, creating one if none already exists. Interactively, the default if you just type RET is the current directory, but the visited file name is available through the minibuffer history: -type M-n to pull it into the minibuffer. +type \\[next-history-element] to pull it into the minibuffer. + +The first time \\[next-history-element] is used after Emacs prompts for +the file name, the result is affected by `file-name-at-point-functions', +which by default try to guess the file name by looking at point in the +current buffer. Customize the value of `file-name-at-point-functions' +or set it to nil, if you want only the visited file name and the +current directory to be available on first \\[next-history-element] +request. You can visit files on remote machines by specifying something like /ssh:SOME_REMOTE_MACHINE:FILE for the file name. You can @@ -1592,8 +1588,8 @@ automatically choosing a major mode, use \\[find-file-literally]." (confirm-nonexistent-file-or-buffer))) (let ((value (find-file-noselect filename nil nil wildcards))) (if (listp value) - (mapcar 'switch-to-buffer (nreverse value)) - (switch-to-buffer value)))) + (mapcar 'pop-to-buffer-same-window (nreverse value)) + (pop-to-buffer-same-window value)))) (defun find-file-other-window (filename &optional wildcards) "Edit file FILENAME, in another window. @@ -1603,7 +1599,15 @@ an existing one. See the function `display-buffer'. Interactively, the default if you just type RET is the current directory, but the visited file name is available through the minibuffer history: -type M-n to pull it into the minibuffer. +type \\[next-history-element] to pull it into the minibuffer. + +The first time \\[next-history-element] is used after Emacs prompts for +the file name, the result is affected by `file-name-at-point-functions', +which by default try to guess the file name by looking at point in the +current buffer. Customize the value of `file-name-at-point-functions' +or set it to nil, if you want only the visited file name and the +current directory to be available on first \\[next-history-element] +request. Interactively, or if WILDCARDS is non-nil in a call from Lisp, expand wildcards (if any) and visit multiple files." @@ -1627,7 +1631,15 @@ an existing one. See the function `display-buffer'. Interactively, the default if you just type RET is the current directory, but the visited file name is available through the minibuffer history: -type M-n to pull it into the minibuffer. +type \\[next-history-element] to pull it into the minibuffer. + +The first time \\[next-history-element] is used after Emacs prompts for +the file name, the result is affected by `file-name-at-point-functions', +which by default try to guess the file name by looking at point in the +current buffer. Customize the value of `file-name-at-point-functions' +or set it to nil, if you want only the visited file name and the +current directory to be available on first \\[next-history-element] +request. Interactively, or if WILDCARDS is non-nil in a call from Lisp, expand wildcards (if any) and visit multiple files." @@ -1789,7 +1801,11 @@ killed." (setq buffer-file-truename nil) ;; Likewise for dired buffers. (setq dired-directory nil) - (find-file filename wildcards)) + ;; Don't use `find-file' because it may end up using another window + ;; in some corner cases, e.g. when the selected window is + ;; softly-dedicated. + (let ((newbuf (find-file-noselect filename wildcards))) + (switch-to-buffer newbuf))) (when (eq obuf (current-buffer)) ;; This executes if find-file gets an error ;; and does not really find anything. @@ -1843,7 +1859,13 @@ The value includes abbreviation according to `directory-abbrev-alist'.") "Return a version of FILENAME shortened using `directory-abbrev-alist'. This also substitutes \"~\" for the user's home directory (unless the home directory is a root directory) and removes automounter prefixes -\(see the variable `automount-dir-prefix')." +\(see the variable `automount-dir-prefix'). + +When this function is first called, it caches the user's home +directory as a regexp in `abbreviated-home-dir', and reuses it +afterwards (so long as the home directory does not change; +if you want to permanently change your home directory after having +started Emacs, set `abbreviated-home-dir' to nil so it will be recalculated)." ;; Get rid of the prefixes added by the automounter. (save-match-data (if (and automount-dir-prefix @@ -1865,29 +1887,37 @@ home directory is a root directory) and removes automounter prefixes ;; give time for directory-abbrev-alist to be set properly. ;; We include a slash at the end, to avoid spurious matches ;; such as `/usr/foobar' when the home dir is `/usr/foo'. - (or abbreviated-home-dir - (setq abbreviated-home-dir - (let ((abbreviated-home-dir "$foo")) - (setq abbreviated-home-dir - (concat "\\`" - (abbreviate-file-name (expand-file-name "~")) - "\\(/\\|\\'\\)")) - ;; Depending on whether default-directory does or - ;; doesn't include non-ASCII characters, the value - ;; of abbreviated-home-dir could be multibyte or - ;; unibyte. In the latter case, we need to decode - ;; it. Note that this function is called for the - ;; first time (from startup.el) when - ;; locale-coding-system is already set up. - (if (multibyte-string-p abbreviated-home-dir) - abbreviated-home-dir - (decode-coding-string abbreviated-home-dir - (if (eq system-type 'windows-nt) - 'utf-8 - locale-coding-system)))))) + (unless abbreviated-home-dir + (put 'abbreviated-home-dir 'home (expand-file-name "~")) + (setq abbreviated-home-dir + (let ((abbreviated-home-dir "$foo")) + (setq abbreviated-home-dir + (concat "\\`" + (abbreviate-file-name + (get 'abbreviated-home-dir 'home)) + "\\(/\\|\\'\\)")) + ;; Depending on whether default-directory does or + ;; doesn't include non-ASCII characters, the value + ;; of abbreviated-home-dir could be multibyte or + ;; unibyte. In the latter case, we need to decode + ;; it. Note that this function is called for the + ;; first time (from startup.el) when + ;; locale-coding-system is already set up. + (if (multibyte-string-p abbreviated-home-dir) + abbreviated-home-dir + (decode-coding-string abbreviated-home-dir + (if (eq system-type 'windows-nt) + 'utf-8 + locale-coding-system)))))) ;; If FILENAME starts with the abbreviated homedir, + ;; and ~ hasn't changed since abbreviated-home-dir was set, ;; make it start with `~' instead. + ;; If ~ has changed, we ignore abbreviated-home-dir rather than + ;; invalidating it, on the assumption that a change in HOME + ;; is likely temporary (eg for testing). + ;; FIXME Is it even worth caching abbreviated-home-dir? + ;; Ref: https://debbugs.gnu.org/19657#20 (if (and (string-match abbreviated-home-dir filename) ;; If the home dir is just /, don't change it. (not (and (= (match-end 0) 1) @@ -1896,7 +1926,9 @@ home directory is a root directory) and removes automounter prefixes ;; Novell Netware allows drive letters beyond `Z:'. (not (and (memq system-type '(ms-dos windows-nt cygwin)) (save-match-data - (string-match "^[a-zA-`]:/$" filename))))) + (string-match "^[a-zA-`]:/$" filename)))) + (equal (get 'abbreviated-home-dir 'home) + (expand-file-name "~"))) (setq filename (concat "~" (match-string 1 filename) @@ -2539,7 +2571,7 @@ since only a single case-insensitive search through the alist is made." ("\\.[ckz]?sh\\'\\|\\.shar\\'\\|/\\.z?profile\\'" . sh-mode) ("\\.bash\\'" . sh-mode) ("\\(/\\|\\`\\)\\.\\(bash_\\(profile\\|history\\|log\\(in\\|out\\)\\)\\|z?log\\(in\\|out\\)\\)\\'" . sh-mode) - ("\\(/\\|\\`\\)\\.\\(shrc\\|[kz]shrc\\|bashrc\\|t?cshrc\\|esrc\\)\\'" . sh-mode) + ("\\(/\\|\\`\\)\\.\\(shrc\\|zshrc\\|m?kshrc\\|bashrc\\|t?cshrc\\|esrc\\)\\'" . sh-mode) ("\\(/\\|\\`\\)\\.\\([kz]shenv\\|xinitrc\\|startxrc\\|xsession\\)\\'" . sh-mode) ("\\.m?spec\\'" . sh-mode) ("\\.m[mes]\\'" . nroff-mode) @@ -2641,10 +2673,12 @@ ARC\\|ZIP\\|LZH\\|LHA\\|ZOO\\|[JEW]AR\\|XPI\\|RAR\\|CBR\\|7Z\\)\\'" . archive-mo ("/config\\.\\(?:bat\\|log\\)\\'" . fundamental-mode) ;; Windows candidates may be opened case sensitively on Unix ("\\.\\(?:[iI][nN][iI]\\|[lL][sS][tT]\\|[rR][eE][gG]\\|[sS][yY][sS]\\)\\'" . conf-mode) - ("\\.\\(?:desktop\\|la\\)\\'" . conf-unix-mode) + ("\\.la\\'" . conf-unix-mode) ("\\.ppd\\'" . conf-ppd-mode) ("java.+\\.conf\\'" . conf-javaprop-mode) ("\\.properties\\(?:\\.[a-zA-Z0-9._-]+\\)?\\'" . conf-javaprop-mode) + ("\\.toml\\'" . conf-toml-mode) + ("\\.desktop\\'" . conf-desktop-mode) ("\\`/etc/\\(?:DIR_COLORS\\|ethers\\|.?fstab\\|.*hosts\\|lesskey\\|login\\.?de\\(?:fs\\|vperm\\)\\|magic\\|mtab\\|pam\\.d/.*\\|permissions\\(?:\\.d/.+\\)?\\|protocols\\|rpc\\|services\\)\\'" . conf-space-mode) ("\\`/etc/\\(?:acpid?/.+\\|aliases\\(?:\\.d/.+\\)?\\|default/.+\\|group-?\\|hosts\\..+\\|inittab\\|ksysguarddrc\\|opera6rc\\|passwd-?\\|shadow-?\\|sysconfig/.+\\)\\'" . conf-mode) ;; ChangeLog.old etc. Other change-log-mode entries are above; @@ -3875,16 +3909,16 @@ VARIABLES list of the class. The list is processed in order. "File that contains directory-local variables. It has to be constant to enforce uniform values across different environments and users. -See also `dir-locals-file-2', whose values override this one's. -See Info node `(elisp)Directory Local Variables' for details.") -(defconst dir-locals-file-2 ".dir-locals-2.el" - "File that contains directory-local variables. -This essentially a second file that can be used like -`dir-locals-file', so that users can have specify their personal -dir-local variables even if the current directory already has a -`dir-locals-file' that is shared with other users (such as in a -git repository). +A second dir-locals file can be used by a user to specify their +personal dir-local variables even if the current directory +already has a `dir-locals-file' that is shared with other +users (such as in a git repository). The name of this second +file is derived by appending \"-2\" to the base name of +`dir-locals-file'. With the default value of `dir-locals-file', +a \".dir-locals-2.el\" file in the same directory will override +the \".dir-locals.el\". + See Info node `(elisp)Directory Local Variables' for details.") (defun dir-locals--all-files (directory) @@ -3957,11 +3991,12 @@ This function returns either: ;; The entry MTIME should match the most recent ;; MTIME among matching files. (and cached-files - (= (float-time (nth 2 dir-elt)) - (apply #'max (mapcar (lambda (f) - (float-time - (nth 5 (file-attributes f)))) - cached-files)))))) + (equal (nth 2 dir-elt) + (let ((latest 0)) + (dolist (f cached-files latest) + (let ((f-time (nth 5 (file-attributes f)))) + (if (time-less-p latest f-time) + (setq latest f-time))))))))) ;; This cache entry is OK. dir-elt ;; This cache entry is invalid; clear it. @@ -3983,10 +4018,15 @@ Return the new class name, which is a symbol named DIR." (let* ((class-name (intern dir)) (files (dir-locals--all-files dir)) (read-circle nil) - (success nil) + ;; If there was a problem, use the values we could get but + ;; don't let the cache prevent future reads. + (latest 0) (success 0) (variables)) (with-demoted-errors "Error reading dir-locals: %S" (dolist (file files) + (let ((file-time (nth 5 (file-attributes file)))) + (if (time-less-p latest file-time) + (setq latest file-time))) (with-temp-buffer (insert-file-contents file) (condition-case-unless-debug nil @@ -3995,18 +4035,9 @@ Return the new class name, which is a symbol named DIR." variables (read (current-buffer)))) (end-of-file nil)))) - (setq success t)) + (setq success latest)) (dir-locals-set-class-variables class-name variables) - (dir-locals-set-directory-class - dir class-name - (seconds-to-time - (if success - (apply #'max (mapcar (lambda (file) - (float-time (nth 5 (file-attributes file)))) - files)) - ;; If there was a problem, use the values we could get but - ;; don't let the cache prevent future reads. - 0))) + (dir-locals-set-directory-class dir class-name success) class-name)) (define-obsolete-function-alias 'dir-locals-read-from-file @@ -4229,10 +4260,10 @@ Interactively, confirmation is required unless you supply a prefix argument." (not current-prefix-arg))) (or (null filename) (string-equal filename "") (progn - ;; If arg is just a directory, + ;; If arg is a directory name, ;; use the default file name, but in that directory. - (if (file-directory-p filename) - (setq filename (concat (file-name-as-directory filename) + (if (directory-name-p filename) + (setq filename (concat filename (file-name-nondirectory (or buffer-file-name (buffer-name)))))) (and confirm @@ -4494,8 +4525,8 @@ extension, the value is \"\"." ""))))) (defun file-name-base (&optional filename) - "Return the base name of the FILENAME: no directory, no extension. -FILENAME defaults to `buffer-file-name'." + "Return the base name of the FILENAME: no directory, no extension." + (declare (advertised-calling-convention (filename) "27.1")) (file-name-sans-extension (file-name-nondirectory (or filename (buffer-file-name))))) @@ -4626,17 +4657,27 @@ The function `find-backup-file-name' also uses this." ;; "/drive_x". (or (file-name-absolute-p file) (setq file (expand-file-name file))) ; make defaults explicit - ;; Replace any invalid file-name characters (for the - ;; case of backing up remote files). - (setq file (expand-file-name (convert-standard-filename file))) - (if (eq (aref file 1) ?:) - (setq file (concat "/" - "drive_" - (char-to-string (downcase (aref file 0))) - (if (eq (aref file 2) ?/) - "" - "/") - (substring file 2))))) + (cond + ((file-remote-p file) + ;; Remove the leading slash, if any, to prevent + ;; convert-standard-filename from converting that to a + ;; backslash. + (and (memq (aref file 0) '(?/ ?\\)) + (setq file (substring file 1))) + ;; Replace any invalid file-name characters, then + ;; prepend the leading slash back. + (setq file (concat "/" (convert-standard-filename file)))) + (t + ;; Replace any invalid file-name characters. + (setq file (expand-file-name (convert-standard-filename file))) + (if (eq (aref file 1) ?:) + (setq file (concat "/" + "drive_" + (char-to-string (downcase (aref file 0))) + (if (eq (aref file 2) ?/) + "" + "/") + (substring file 2))))))) ;; Make the name unique by substituting directory ;; separators. It may not really be worth bothering about ;; doubling `!'s in the original name... @@ -4724,46 +4765,6 @@ Uses `backup-directory-alist' in the same way as "Return number of names file FILENAME has." (car (cdr (file-attributes filename)))) -;; (defun file-relative-name (filename &optional directory) -;; "Convert FILENAME to be relative to DIRECTORY (default: `default-directory'). -;; This function returns a relative file name which is equivalent to FILENAME -;; when used with that default directory as the default. -;; If this is impossible (which can happen on MSDOS and Windows -;; when the file name and directory use different drive names) -;; then it returns FILENAME." -;; (save-match-data -;; (let ((fname (expand-file-name filename))) -;; (setq directory (file-name-as-directory -;; (expand-file-name (or directory default-directory)))) -;; ;; On Microsoft OSes, if FILENAME and DIRECTORY have different -;; ;; drive names, they can't be relative, so return the absolute name. -;; (if (and (or (eq system-type 'ms-dos) -;; (eq system-type 'cygwin) -;; (eq system-type 'windows-nt)) -;; (not (string-equal (substring fname 0 2) -;; (substring directory 0 2)))) -;; filename -;; (let ((ancestor ".") -;; (fname-dir (file-name-as-directory fname))) -;; (while (and (not (string-match (concat "^" (regexp-quote directory)) fname-dir)) -;; (not (string-match (concat "^" (regexp-quote directory)) fname))) -;; (setq directory (file-name-directory (substring directory 0 -1)) -;; ancestor (if (equal ancestor ".") -;; ".." -;; (concat "../" ancestor)))) -;; ;; Now ancestor is empty, or .., or ../.., etc. -;; (if (string-match (concat "^" (regexp-quote directory)) fname) -;; ;; We matched within FNAME's directory part. -;; ;; Add the rest of FNAME onto ANCESTOR. -;; (let ((rest (substring fname (match-end 0)))) -;; (if (and (equal ancestor ".") -;; (not (equal rest ""))) -;; ;; But don't bother with ANCESTOR if it would give us `./'. -;; rest -;; (concat (file-name-as-directory ancestor) rest))) -;; ;; We matched FNAME's directory equivalent. -;; ancestor)))))) - (defun file-relative-name (filename &optional directory) "Convert FILENAME to be relative to DIRECTORY (default: `default-directory'). This function returns a relative file name which is equivalent to FILENAME @@ -4932,9 +4933,12 @@ in such cases.") (defun basic-save-buffer (&optional called-interactively) "Save the current buffer in its visited file, if it has been modified. -The hooks `write-contents-functions' and `write-file-functions' get a chance -to do the job of saving; if they do not, then the buffer is saved in -the visited file in the usual way. + +The hooks `write-contents-functions', `local-write-file-hooks' +and `write-file-functions' get a chance to do the job of saving; +if they do not, then the buffer is saved in the visited file in +the usual way. + Before and after saving the buffer, this function runs `before-save-hook' and `after-save-hook', respectively." (interactive '(called-interactively)) @@ -4943,29 +4947,14 @@ Before and after saving the buffer, this function runs (if (buffer-base-buffer) (set-buffer (buffer-base-buffer))) (if (or (buffer-modified-p) - ;; handle the case when no modification has been made but - ;; the file disappeared since visited + ;; Handle the case when no modification has been made but + ;; the file disappeared since visited. (and buffer-file-name (not (file-exists-p buffer-file-name)))) (let ((recent-save (recent-auto-save-p)) setmodes) - ;; If buffer has no file name, ask user for one. - (or buffer-file-name - (let ((filename - (expand-file-name - (read-file-name "File to save in: " - nil (expand-file-name (buffer-name)))))) - (if (file-exists-p filename) - (if (file-directory-p filename) - ;; Signal an error if the user specified the name of an - ;; existing directory. - (error "%s is a directory" filename) - (unless (y-or-n-p (format-message - "File `%s' exists; overwrite? " - filename)) - (error "Canceled")))) - (set-visited-file-name filename))) - (or (verify-visited-file-modtime (current-buffer)) + (or (null buffer-file-name) + (verify-visited-file-modtime (current-buffer)) (not (file-exists-p buffer-file-name)) (yes-or-no-p (format @@ -4977,6 +4966,7 @@ Before and after saving the buffer, this function runs (save-excursion (and (> (point-max) (point-min)) (not find-file-literally) + (null buffer-read-only) (/= (char-after (1- (point-max))) ?\n) (not (and (eq selective-display t) (= (char-after (1- (point-max))) ?\r))) @@ -4989,46 +4979,65 @@ Before and after saving the buffer, this function runs (save-excursion (goto-char (point-max)) (insert ?\n)))) - ;; Support VC version backups. - (vc-before-save) ;; Don't let errors prevent saving the buffer. (with-demoted-errors (run-hooks 'before-save-hook)) - (or (run-hook-with-args-until-success 'write-contents-functions) - (run-hook-with-args-until-success 'local-write-file-hooks) - (run-hook-with-args-until-success 'write-file-functions) - ;; If a hook returned t, file is already "written". - ;; Otherwise, write it the usual way now. - (let ((dir (file-name-directory - (expand-file-name buffer-file-name)))) - (unless (file-exists-p dir) - (if (y-or-n-p - (format-message - "Directory `%s' does not exist; create? " dir)) - (make-directory dir t) - (error "Canceled"))) - (setq setmodes (basic-save-buffer-1)))) + ;; Give `write-contents-functions' a chance to + ;; short-circuit the whole process. + (unless (run-hook-with-args-until-success 'write-contents-functions) + ;; If buffer has no file name, ask user for one. + (or buffer-file-name + (let ((filename + (expand-file-name + (read-file-name "File to save in: " + nil (expand-file-name (buffer-name)))))) + (if (file-exists-p filename) + (if (file-directory-p filename) + ;; Signal an error if the user specified the name of an + ;; existing directory. + (error "%s is a directory" filename) + (unless (y-or-n-p (format-message + "File `%s' exists; overwrite? " + filename)) + (error "Canceled")))) + (set-visited-file-name filename))) + ;; Support VC version backups. + (vc-before-save) + (or (run-hook-with-args-until-success 'local-write-file-hooks) + (run-hook-with-args-until-success 'write-file-functions) + ;; If a hook returned t, file is already "written". + ;; Otherwise, write it the usual way now. + (let ((dir (file-name-directory + (expand-file-name buffer-file-name)))) + (unless (file-exists-p dir) + (if (y-or-n-p + (format-message + "Directory `%s' does not exist; create? " dir)) + (make-directory dir t) + (error "Canceled"))) + (setq setmodes (basic-save-buffer-1))))) ;; Now we have saved the current buffer. Let's make sure ;; that buffer-file-coding-system is fixed to what ;; actually used for saving by binding it locally. - (if save-buffer-coding-system - (setq save-buffer-coding-system last-coding-system-used) - (setq buffer-file-coding-system last-coding-system-used)) - (setq buffer-file-number - (nthcdr 10 (file-attributes buffer-file-name))) - (if setmodes - (condition-case () - (progn - (unless - (with-demoted-errors - (set-file-modes buffer-file-name (car setmodes))) - (set-file-extended-attributes buffer-file-name - (nth 1 setmodes)))) - (error nil)))) - ;; If the auto-save file was recent before this command, - ;; delete it now. - (delete-auto-save-file-if-necessary recent-save) - ;; Support VC `implicit' locking. - (vc-after-save) + (when buffer-file-name + (if save-buffer-coding-system + (setq save-buffer-coding-system last-coding-system-used) + (setq buffer-file-coding-system last-coding-system-used)) + (setq buffer-file-number + (nthcdr 10 (file-attributes buffer-file-name))) + (if setmodes + (condition-case () + (progn + (unless + (with-demoted-errors + (set-file-modes buffer-file-name (car setmodes))) + (set-file-extended-attributes buffer-file-name + (nth 1 setmodes)))) + (error nil))) + ;; Support VC `implicit' locking. + (vc-after-save)) + ;; If the auto-save file was recent before this command, + ;; delete it now. + (delete-auto-save-file-if-necessary recent-save)) (run-hooks 'after-save-hook)) (or noninteractive (not called-interactively) @@ -5083,48 +5092,33 @@ Before and after saving the buffer, this function runs ;; This requires write access to the containing dir, ;; which is why we don't try it if we don't have that access. (let ((realname buffer-file-name) - tempname succeed - (umask (default-file-modes)) + tempname (old-modtime (visited-file-modtime))) ;; Create temp files with strict access rights. It's easy to ;; loosen them later, whereas it's impossible to close the ;; time-window of loose permissions otherwise. - (unwind-protect + (condition-case err (progn (clear-visited-file-modtime) - (set-default-file-modes ?\700) - ;; Try various temporary names. - ;; This code follows the example of make-temp-file, - ;; but it calls write-region in the appropriate way + ;; Call write-region in the appropriate way ;; for saving the buffer. - (while (condition-case () - (progn - (setq tempname - (make-temp-name - (expand-file-name "tmp" dir))) - ;; Pass in nil&nil rather than point-min&max - ;; cause we're saving the whole buffer. - ;; write-region-annotate-functions may use it. - (write-region nil nil - tempname nil realname - buffer-file-truename 'excl) - (when save-silently (message nil)) - nil) - (file-already-exists t)) - ;; The file was somehow created by someone else between - ;; `make-temp-name' and `write-region', let's try again. - nil) - (setq succeed t)) - ;; Reset the umask. - (set-default-file-modes umask) + (setq tempname + (make-temp-file + (expand-file-name "tmp" dir))) + ;; Pass in nil&nil rather than point-min&max + ;; cause we're saving the whole buffer. + ;; write-region-annotate-functions may use it. + (write-region nil nil tempname nil realname + buffer-file-truename) + (when save-silently (message nil))) ;; If we failed, restore the buffer's modtime. - (unless succeed - (set-visited-file-modtime old-modtime))) + (error (set-visited-file-modtime old-modtime) + (signal (car err) (cdr err)))) ;; Since we have created an entirely new file, ;; make sure it gets the right permission bits set. (setq setmodes (or setmodes (list (or (file-modes buffer-file-name) - (logand ?\666 umask)) + (logand ?\666 (default-file-modes))) (file-extended-attributes buffer-file-name) buffer-file-name))) ;; We succeeded in writing the temp file, @@ -5152,7 +5146,7 @@ Before and after saving the buffer, this function runs (progn ;; Pass in nil&nil rather than point-min&max to indicate ;; we're saving the buffer rather than just a region. - ;; write-region-annotate-functions may make us of it. + ;; write-region-annotate-functions may make use of it. (write-region nil nil buffer-file-name nil t buffer-file-truename) (when save-silently (message nil)) @@ -5252,10 +5246,9 @@ change the additional actions you can take on files." (not (buffer-base-buffer buffer)) (or (buffer-file-name buffer) - (and pred - (progn - (set-buffer buffer) - (and buffer-offer-save (> (buffer-size) 0))))) + (with-current-buffer buffer + (or (eq buffer-offer-save 'always) + (and pred buffer-offer-save (> (buffer-size) 0))))) (or (not (functionp pred)) (with-current-buffer buffer (funcall pred))) (if arg @@ -5392,6 +5385,14 @@ instance of such commands." (rename-buffer (generate-new-buffer-name base-name)) (force-mode-line-update)))) +(defun files--ensure-directory (dir) + "Make directory DIR if it is not already a directory. Return nil." + (condition-case err + (make-directory-internal dir) + (error + (unless (file-directory-p dir) + (signal (car err) (cdr err)))))) + (defun make-directory (dir &optional parents) "Create the directory DIR and optionally any nonexistent parent dirs. If DIR already exists as a directory, signal an error, unless @@ -5420,18 +5421,19 @@ raised." (if (not parents) (make-directory-internal dir) (let ((dir (directory-file-name (expand-file-name dir))) - create-list) - (while (and (not (file-exists-p dir)) - ;; If directory is its own parent, then we can't - ;; keep looping forever - (not (equal dir - (directory-file-name - (file-name-directory dir))))) + create-list parent) + (while (progn + (setq parent (directory-file-name + (file-name-directory dir))) + (condition-case () + (files--ensure-directory dir) + (file-missing + ;; Do not loop if root does not exist (Bug#2309). + (not (string= dir parent))))) (setq create-list (cons dir create-list) - dir (directory-file-name (file-name-directory dir)))) - (while create-list - (make-directory-internal (car create-list)) - (setq create-list (cdr create-list)))))))) + dir parent)) + (dolist (dir create-list) + (files--ensure-directory dir))))))) (defconst directory-files-no-dot-files-regexp "^\\([^.]\\|\\.\\([^.]\\|\\..\\)\\).*" @@ -5564,10 +5566,10 @@ Noninteractively, the last argument PARENTS says whether to create parent directories if they don't exist. Interactively, this happens by default. -If NEWNAME names an existing directory, copy DIRECTORY as a -subdirectory there. However, if called from Lisp with a non-nil -optional argument COPY-CONTENTS, copy the contents of DIRECTORY -directly into NEWNAME instead." +If NEWNAME is a directory name, copy DIRECTORY as a subdirectory +there. However, if called from Lisp with a non-nil optional +argument COPY-CONTENTS, copy the contents of DIRECTORY directly +into NEWNAME instead." (interactive (let ((dir (read-directory-name "Copy directory: " default-directory default-directory t nil))) @@ -5589,35 +5591,32 @@ directly into NEWNAME instead." ;; Compute target name. (setq directory (directory-file-name (expand-file-name directory)) - newname (directory-file-name (expand-file-name newname))) + newname (expand-file-name newname)) - (cond ((not (file-directory-p newname)) - ;; If NEWNAME is not an existing directory, create it; + (cond ((not (directory-name-p newname)) + ;; If NEWNAME is not a directory name, create it; ;; that is where we will copy the files of DIRECTORY. (make-directory newname parents)) - ;; If NEWNAME is an existing directory and COPY-CONTENTS - ;; is nil, copy into NEWNAME/[DIRECTORY-BASENAME]. - ((not copy-contents) - (setq newname (expand-file-name - (file-name-nondirectory - (directory-file-name directory)) - newname)) - (and (file-exists-p newname) - (not (file-directory-p newname)) - (error "Cannot overwrite non-directory %s with a directory" - newname)) - (make-directory newname t))) + ;; NEWNAME is a directory name. If COPY-CONTENTS is non-nil, + ;; create NEWNAME if it is not already a directory; + ;; otherwise, create NEWNAME/[DIRECTORY-BASENAME]. + ((if copy-contents + (or parents (not (file-directory-p newname))) + (setq newname (concat newname + (file-name-nondirectory directory)))) + (make-directory (directory-file-name newname) parents))) ;; Copy recursively. (dolist (file ;; We do not want to copy "." and "..". (directory-files directory 'full directory-files-no-dot-files-regexp)) - (let ((target (expand-file-name (file-name-nondirectory file) newname)) + (let ((target (concat (file-name-as-directory newname) + (file-name-nondirectory file))) (filetype (car (file-attributes file)))) (cond ((eq filetype t) ; Directory but not a symlink. - (copy-directory file newname keep-time parents)) + (copy-directory file target keep-time parents t)) ((stringp filetype) ; Symbolic link (make-symbolic-link filetype target t)) ((copy-file file target t keep-time))))) @@ -5906,7 +5905,11 @@ an auto-save file." (error "%s is an auto-save file" (abbreviate-file-name file))) (let ((file-name (let ((buffer-file-name file)) (make-auto-save-file-name)))) - (cond ((if (file-exists-p file) + (cond ((and (file-exists-p file) + (not (file-exists-p file-name))) + (error "Auto save file %s does not exist" + (abbreviate-file-name file-name))) + ((if (file-exists-p file) (not (file-newer-than-file-p file-name file)) (not (file-exists-p file-name))) (error "Auto-save file %s not current" @@ -6076,16 +6079,18 @@ specifies the list of buffers to kill, asking for approval for each one." (kill-buffer-ask buffer))) (setq list (cdr list)))) -(defun kill-matching-buffers (regexp &optional internal-too) +(defun kill-matching-buffers (regexp &optional internal-too no-ask) "Kill buffers whose name matches the specified REGEXP. -The optional second argument indicates whether to kill internal buffers too." +Ignores buffers whose name starts with a space, unless optional +prefix argument INTERNAL-TOO is non-nil. Asks before killing +each buffer, unless NO-ASK is non-nil." (interactive "sKill buffers matching this regular expression: \nP") (dolist (buffer (buffer-list)) (let ((name (buffer-name buffer))) (when (and name (not (string-equal name "")) (or internal-too (/= (aref name 0) ?\s)) (string-match regexp name)) - (kill-buffer-ask buffer))))) + (funcall (if no-ask 'kill-buffer 'kill-buffer-ask) buffer))))) (defun rename-auto-save-file () @@ -6436,58 +6441,31 @@ if you want to specify options, use `directory-free-space-args'. A value of nil disables this feature. -If the function `file-system-info' is defined, it is always used in -preference to the program given by this variable." +This variable is obsolete; Emacs no longer uses it." :type '(choice (string :tag "Program") (const :tag "None" nil)) :group 'dired) +(make-obsolete-variable 'directory-free-space-program + "ignored, as Emacs uses `file-system-info' instead" + "27.1") (defcustom directory-free-space-args (purecopy (if (eq system-type 'darwin) "-k" "-Pk")) "Options to use when running `directory-free-space-program'." :type 'string :group 'dired) +(make-obsolete-variable 'directory-free-space-args + "ignored, as Emacs uses `file-system-info' instead" + "27.1") (defun get-free-disk-space (dir) "Return the amount of free space on directory DIR's file system. The return value is a string describing the amount of free space (normally, the number of free 1KB blocks). -This function calls `file-system-info' if it is available, or -invokes the program specified by `directory-free-space-program' -and `directory-free-space-args'. If the system call or program -is unsuccessful, or if DIR is a remote directory, this function -returns nil." - (unless (file-remote-p (expand-file-name dir)) - ;; Try to find the number of free blocks. Non-Posix systems don't - ;; always have df, but might have an equivalent system call. - (if (fboundp 'file-system-info) - (let ((fsinfo (file-system-info dir))) - (if fsinfo - (format "%.0f" (/ (nth 2 fsinfo) 1024)))) - (setq dir (expand-file-name dir)) - (save-match-data - (with-temp-buffer - (when (and directory-free-space-program - ;; Avoid failure if the default directory does - ;; not exist (Bug#2631, Bug#3911). - (let ((default-directory - (locate-dominating-file dir 'file-directory-p))) - (eq (process-file directory-free-space-program - nil t nil - directory-free-space-args - (file-relative-name dir)) - 0))) - ;; Assume that the "available" column is before the - ;; "capacity" column. Find the "%" and scan backward. - (goto-char (point-min)) - (forward-line 1) - (when (re-search-forward - "[[:space:]]+[^[:space:]]+%[^%]*$" - (line-end-position) t) - (goto-char (match-beginning 0)) - (let ((endpt (point))) - (skip-chars-backward "^[:space:]") - (buffer-substring-no-properties (point) endpt))))))))) +If DIR's free space cannot be obtained, this function returns nil." + (let ((avail (nth 2 (file-system-info dir)))) + (if avail + (format "%.0f" (/ avail 1024))))) ;; The following expression replaces `dired-move-to-filename-regexp'. (defvar directory-listing-before-filename-regexp @@ -6546,6 +6524,75 @@ regardless of the language.") (defvar insert-directory-ls-version 'unknown) +(defun insert-directory-wildcard-in-dir-p (dir) + "Return non-nil if DIR contents a shell wildcard in the directory part. +The return value is a cons (DIR . WILDCARDS); DIR is the +`default-directory' in the Dired buffer, and WILDCARDS are the wildcards. + +Valid wildcards are '*', '?', '[abc]' and '[a-z]'." + (let ((wildcards "[?*")) + (when (and (or (not (featurep 'ls-lisp)) + ls-lisp-support-shell-wildcards) + (string-match (concat "[" wildcards "]") (file-name-directory dir)) + (not (file-exists-p dir))) ; Prefer an existing file to wildcards. + (let ((regexp (format "\\`\\([^%s]*/\\)\\([^%s]*[%s].*\\)" + wildcards wildcards wildcards))) + (string-match regexp dir) + (cons (match-string 1 dir) (match-string 2 dir)))))) + +(defun insert-directory-clean (beg switches) + (when (if (stringp switches) + (string-match "--dired\\>" switches) + (member "--dired" switches)) + ;; The following overshoots by one line for an empty + ;; directory listed with "--dired", but without "-a" + ;; switch, where the ls output contains a + ;; "//DIRED-OPTIONS//" line, but no "//DIRED//" line. + ;; We take care of that case later. + (forward-line -2) + (when (looking-at "//SUBDIRED//") + (delete-region (point) (progn (forward-line 1) (point))) + (forward-line -1)) + (if (looking-at "//DIRED//") + (let ((end (line-end-position)) + (linebeg (point)) + error-lines) + ;; Find all the lines that are error messages, + ;; and record the bounds of each one. + (goto-char beg) + (while (< (point) linebeg) + (or (eql (following-char) ?\s) + (push (list (point) (line-end-position)) error-lines)) + (forward-line 1)) + (setq error-lines (nreverse error-lines)) + ;; Now read the numeric positions of file names. + (goto-char linebeg) + (forward-word-strictly 1) + (forward-char 3) + (while (< (point) end) + (let ((start (insert-directory-adj-pos + (+ beg (read (current-buffer))) + error-lines)) + (end (insert-directory-adj-pos + (+ beg (read (current-buffer))) + error-lines))) + (if (memq (char-after end) '(?\n ?\s)) + ;; End is followed by \n or by " -> ". + (put-text-property start end 'dired-filename t) + ;; It seems that we can't trust ls's output as to + ;; byte positions of filenames. + (put-text-property beg (point) 'dired-filename nil) + (end-of-line)))) + (goto-char end) + (beginning-of-line) + (delete-region (point) (progn (forward-line 1) (point)))) + ;; Take care of the case where the ls output contains a + ;; "//DIRED-OPTIONS//"-line, but no "//DIRED//"-line + ;; and we went one line too far back (see above). + (forward-line 1)) + (if (looking-at "//DIRED-OPTIONS//") + (delete-region (point) (progn (forward-line 1) (point)))))) + ;; insert-directory ;; - must insert _exactly_one_line_ describing FILE if WILDCARD and ;; FULL-DIRECTORY-P is nil. @@ -6605,19 +6652,25 @@ normally equivalent short `-D' option is just passed on to default-file-name-coding-system)))) (setq result (if wildcard - ;; Run ls in the directory part of the file pattern - ;; using the last component as argument. - (let ((default-directory - (if (file-name-absolute-p file) - (file-name-directory file) - (file-name-directory (expand-file-name file)))) - (pattern (file-name-nondirectory file))) + ;; If the wildcard is just in the file part, then run ls in + ;; the directory part of the file pattern using the last + ;; component as argument. Otherwise, run ls in the longest + ;; subdirectory of the directory part free of wildcards; use + ;; the remaining of the file pattern as argument. + (let* ((dir-wildcard (insert-directory-wildcard-in-dir-p file)) + (default-directory + (cond (dir-wildcard (car dir-wildcard)) + (t + (if (file-name-absolute-p file) + (file-name-directory file) + (file-name-directory (expand-file-name file)))))) + (pattern (if dir-wildcard (cdr dir-wildcard) (file-name-nondirectory file)))) ;; NB since switches is passed to the shell, be ;; careful of malicious values, eg "-l;reboot". ;; See eg dired-safe-switches-p. (call-process shell-file-name nil t nil - "-c" + shell-command-switch (concat (if (memq system-type '(ms-dos windows-nt)) "" "\\") ; Disregard Unix shell aliases! @@ -6659,7 +6712,8 @@ normally equivalent short `-D' option is just passed on to (setq file (expand-file-name file))) (list (if full-directory-p - (concat (file-name-as-directory file) ".") + ;; (concat (file-name-as-directory file) ".") + file file)))))))) ;; If we got "//DIRED//" in the output, it means we got a real @@ -6730,59 +6784,7 @@ normally equivalent short `-D' option is just passed on to ;; Unix. Access the file to get a suitable error. (access-file file "Reading directory") (error "Listing directory failed but `access-file' worked"))) - - (when (if (stringp switches) - (string-match "--dired\\>" switches) - (member "--dired" switches)) - ;; The following overshoots by one line for an empty - ;; directory listed with "--dired", but without "-a" - ;; switch, where the ls output contains a - ;; "//DIRED-OPTIONS//" line, but no "//DIRED//" line. - ;; We take care of that case later. - (forward-line -2) - (when (looking-at "//SUBDIRED//") - (delete-region (point) (progn (forward-line 1) (point))) - (forward-line -1)) - (if (looking-at "//DIRED//") - (let ((end (line-end-position)) - (linebeg (point)) - error-lines) - ;; Find all the lines that are error messages, - ;; and record the bounds of each one. - (goto-char beg) - (while (< (point) linebeg) - (or (eql (following-char) ?\s) - (push (list (point) (line-end-position)) error-lines)) - (forward-line 1)) - (setq error-lines (nreverse error-lines)) - ;; Now read the numeric positions of file names. - (goto-char linebeg) - (forward-word-strictly 1) - (forward-char 3) - (while (< (point) end) - (let ((start (insert-directory-adj-pos - (+ beg (read (current-buffer))) - error-lines)) - (end (insert-directory-adj-pos - (+ beg (read (current-buffer))) - error-lines))) - (if (memq (char-after end) '(?\n ?\s)) - ;; End is followed by \n or by " -> ". - (put-text-property start end 'dired-filename t) - ;; It seems that we can't trust ls's output as to - ;; byte positions of filenames. - (put-text-property beg (point) 'dired-filename nil) - (end-of-line)))) - (goto-char end) - (beginning-of-line) - (delete-region (point) (progn (forward-line 1) (point)))) - ;; Take care of the case where the ls output contains a - ;; "//DIRED-OPTIONS//"-line, but no "//DIRED//"-line - ;; and we went one line too far back (see above). - (forward-line 1)) - (if (looking-at "//DIRED-OPTIONS//") - (delete-region (point) (progn (forward-line 1) (point))))) - + (insert-directory-clean beg switches) ;; Now decode what read if necessary. (let ((coding (or coding-system-for-read file-name-coding-system @@ -7023,7 +7025,7 @@ only these files will be asked to be saved." (setq file-arg-indices (cdr file-arg-indices)))) (pcase method (`identity (car arguments)) - (`add (concat "/:" (apply operation arguments))) + (`add (file-name-quote (apply operation arguments))) (`insert-file-contents (let ((visit (nth 1 arguments))) (unwind-protect @@ -7217,8 +7219,8 @@ Otherwise, trash FILENAME using the freedesktop.org conventions, ;; If `trash-directory' is non-nil, move the file there. (let* ((trash-dir (expand-file-name trash-directory)) (fn (directory-file-name (expand-file-name filename))) - (new-fn (expand-file-name (file-name-nondirectory fn) - trash-dir))) + (new-fn (concat (file-name-as-directory trash-dir) + (file-name-nondirectory fn)))) ;; We can't trash a parent directory of trash-directory. (if (string-prefix-p fn trash-dir) (error "Trash directory `%s' is a subdirectory of `%s'" @@ -7297,37 +7299,25 @@ Otherwise, trash FILENAME using the freedesktop.org conventions, (format-time-string "%Y-%m-%dT%T") "\n") - ;; Attempt to make .trashinfo file, trying up to 5 - ;; times. The .trashinfo file is opened with O_EXCL, - ;; as per trash-spec 0.7, even if that can be a problem - ;; on old NFS versions... - (let* ((tries 5) - (base-fn (expand-file-name - (file-name-nondirectory fn) - trash-files-dir)) - (new-fn base-fn) - success info-fn) - (while (> tries 0) - (setq info-fn (expand-file-name - (concat (file-name-nondirectory new-fn) - ".trashinfo") - trash-info-dir)) - (unless (condition-case nil - (progn - (write-region nil nil info-fn nil - 'quiet info-fn 'excl) - (setq tries 0 success t)) - (file-already-exists nil)) - (setq tries (1- tries)) - ;; Uniquify new-fn. (Some file managers do not - ;; like Emacs-style backup file names---e.g. bug - ;; 170956 in Konqueror bug tracker.) - (setq new-fn (make-temp-name (concat base-fn "_"))))) - (unless success - (error "Cannot move %s to trash: Lock failed" filename)) - + ;; Make a .trashinfo file. Use O_EXCL, as per trash-spec 1.0. + (let* ((files-base (file-name-nondirectory fn)) + (info-fn (expand-file-name + (concat files-base ".trashinfo") + trash-info-dir))) + (condition-case nil + (write-region nil nil info-fn nil 'quiet info-fn 'excl) + (file-already-exists + ;; Uniquify new-fn. Some file managers do not + ;; like Emacs-style backup file names. E.g.: + ;; https://bugs.kde.org/170956 + (setq info-fn (make-temp-file + (expand-file-name files-base trash-info-dir) + nil ".trashinfo")) + (setq files-base (file-name-nondirectory info-fn)) + (write-region nil nil info-fn nil 'quiet info-fn))) ;; Finally, try to move the file to the trashcan. - (let ((delete-by-moving-to-trash nil)) + (let ((delete-by-moving-to-trash nil) + (new-fn (expand-file-name files-base trash-files-dir))) (rename-file fn new-fn))))))))) (defsubst file-attribute-type (attributes) diff --git a/lisp/filesets.el b/lisp/filesets.el index 4542d6a5ef8..c2bdec0e6d7 100644 --- a/lisp/filesets.el +++ b/lisp/filesets.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: diff --git a/lisp/find-cmd.el b/lisp/find-cmd.el index 93abe02f14e..9801ee3afa3 100644 --- a/lisp/find-cmd.el +++ b/lisp/find-cmd.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/find-dired.el b/lisp/find-dired.el index a92d477e1e0..3b0613b2806 100644 --- a/lisp/find-dired.el +++ b/lisp/find-dired.el @@ -1,4 +1,4 @@ -;;; find-dired.el --- run a `find' command and dired the output +;;; find-dired.el --- run a `find' command and dired the output -*- lexical-binding: t -*- ;; Copyright (C) 1992, 1994-1995, 2000-2017 Free Software Foundation, ;; Inc. @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -255,14 +255,14 @@ See `find-name-arg' to customize the arguments." (defalias 'lookfor-dired 'find-grep-dired) ;;;###autoload (defun find-grep-dired (dir regexp) - "Find files in DIR matching a regexp REGEXP and start Dired on output. + "Find files in DIR that contain matches for REGEXP and start Dired on output. The command run (after changing into DIR) is find . \\( -type f -exec `grep-program' `find-grep-options' \\ -e REGEXP {} \\; \\) -ls -where the car of the variable `find-ls-option' specifies what to -use in place of \"-ls\" as the final argument." +where the first string in the value of the variable `find-ls-option' +specifies what to use in place of \"-ls\" as the final argument." ;; Doc used to say "Thus ARG can also contain additional grep options." ;; i) Presumably ARG == REGEXP? ;; ii) No it can't have options, since it gets shell-quoted. diff --git a/lisp/find-file.el b/lisp/find-file.el index d3691694d17..8b45c9d5bed 100644 --- a/lisp/find-file.el +++ b/lisp/find-file.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/find-lisp.el b/lisp/find-lisp.el index e9f844487bc..e079e15b0aa 100644 --- a/lisp/find-lisp.el +++ b/lisp/find-lisp.el @@ -1,4 +1,4 @@ -;;; find-lisp.el --- emulation of find in Emacs Lisp +;;; find-lisp.el --- emulation of find in Emacs Lisp -*- lexical-binding: t -*- ;; Author: Peter Breton ;; Created: Fri Mar 26 1999 @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/finder.el b/lisp/finder.el index 361572f7c2d..1cebad7b546 100644 --- a/lisp/finder.el +++ b/lisp/finder.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -129,7 +129,7 @@ Keywords and package names both should be symbols.") ;; Skip autogenerated files, because they will never contain anything ;; useful, and because in parallel builds of Emacs they may get ;; modified while we are trying to read them. -;; http://lists.gnu.org/archive/html/emacs-pretest-bug/2007-01/msg00469.html +;; https://lists.gnu.org/r/emacs-pretest-bug/2007-01/msg00469.html ;; ldefs-boot is not auto-generated, but has nothing useful. (defvar finder-no-scan-regexp "\\(^\\.#\\|\\(loaddefs\\|ldefs-boot\\|\ cus-load\\|finder-inf\\|esh-groups\\|subdirs\\|leim-list\\)\\.el$\\)" @@ -209,7 +209,7 @@ from; the default is `load-path'." ;; There are multiple files in the tree with the same basename. ;; So skipping files based on basename means you randomly (depending ;; on which order the files are traversed in) miss some packages. -;; http://debbugs.gnu.org/14010 +;; https://debbugs.gnu.org/14010 ;; You might think this could lead to two files providing the same package, ;; but it does not, because the duplicates are (at time of writing) ;; all due to files in cedet, which end up with package-override set. diff --git a/lisp/flow-ctrl.el b/lisp/flow-ctrl.el index c0609b0c3ab..5b16ee4214b 100644 --- a/lisp/flow-ctrl.el +++ b/lisp/flow-ctrl.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/foldout.el b/lisp/foldout.el index da69f8b259a..3f6485434d0 100644 --- a/lisp/foldout.el +++ b/lisp/foldout.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/follow.el b/lisp/follow.el index 5dd74f37a18..1ec6ff30f26 100644 --- a/lisp/follow.el +++ b/lisp/follow.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -416,6 +416,7 @@ This command runs the normal hook `follow-mode-hook'. Keys specific to Follow mode: \\{follow-mode-map}" + :lighter follow-mode-line-text :keymap follow-mode-map (if follow-mode (progn @@ -1117,7 +1118,7 @@ Otherwise, return nil." ;;; Redisplay ;; Redraw all the windows on the screen, starting with the top window. -;; The window used as as marker is WIN, or the selected window if WIN +;; The window used as marker is WIN, or the selected window if WIN ;; is nil. Start every window directly after the end of the previous ;; window, to make sure long lines are displayed correctly. diff --git a/lisp/font-core.el b/lisp/font-core.el index f64e1b646ae..06b36a23512 100644 --- a/lisp/font-core.el +++ b/lisp/font-core.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: diff --git a/lisp/font-lock.el b/lisp/font-lock.el index 5eedb7849a0..3c9660dc64a 100644 --- a/lisp/font-lock.el +++ b/lisp/font-lock.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/format-spec.el b/lisp/format-spec.el index 95ed000452c..fecf9d77b59 100644 --- a/lisp/format-spec.el +++ b/lisp/format-spec.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/format.el b/lisp/format.el index cbcba8250d4..8d3dd36fe5b 100644 --- a/lisp/format.el +++ b/lisp/format.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -84,7 +84,7 @@ iso-sgml2iso iso-iso2sgml t nil) (rot13 ,(purecopy "rot13") nil - ,(purecopy "tr a-mn-z n-za-m") ,(purecopy "tr a-mn-z n-za-m") t nil) + rot13-region rot13-region t nil) (duden ,(purecopy "Duden Ersatzdarstellung") nil ,(purecopy "diac") iso-iso2duden t nil) diff --git a/lisp/forms.el b/lisp/forms.el index e13dc170cb9..dacbd8c4671 100644 --- a/lisp/forms.el +++ b/lisp/forms.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/frame.el b/lisp/frame.el index b7a55169281..2e925325a9e 100644 --- a/lisp/frame.el +++ b/lisp/frame.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -604,11 +604,12 @@ new frame." (select-frame (make-frame)))) (defvar before-make-frame-hook nil - "Functions to run before a frame is created.") + "Functions to run before `make-frame' creates a new frame.") (defvar after-make-frame-functions nil - "Functions to run after a frame is created. -The functions are run with one arg, the newly created frame.") + "Functions to run after `make-frame' created a new frame. +The functions are run with one argument, the newly created +frame.") (defvar after-setting-font-hook nil "Functions to run after a frame's font has been changed.") @@ -617,7 +618,7 @@ The functions are run with one arg, the newly created frame.") (define-obsolete-function-alias 'new-frame 'make-frame "22.1") (defvar frame-inherited-parameters '() - "Parameters `make-frame' copies from the `selected-frame' to the new frame.") + "Parameters `make-frame' copies from the selected to the new frame.") (defvar x-display-name) @@ -632,9 +633,6 @@ form (NAME . VALUE), for example: (width . NUMBER) The frame should be NUMBER characters in width. (height . NUMBER) The frame should be NUMBER text lines high. -You cannot specify either `width' or `height', you must specify -neither or both. - (minibuffer . t) The frame should have a minibuffer. (minibuffer . nil) The frame should have no minibuffer. (minibuffer . only) The frame should contain only a minibuffer. @@ -650,10 +648,10 @@ neither or both. In addition, any parameter specified in `default-frame-alist', but not present in PARAMETERS, is applied. -Before creating the frame (via `frame-creation-function-alist'), -this function runs the hook `before-make-frame-hook'. After -creating the frame, it runs the hook `after-make-frame-functions' -with one arg, the newly created frame. +Before creating the frame (via `frame-creation-function'), this +function runs the hook `before-make-frame-hook'. After creating +the frame, it runs the hook `after-make-frame-functions' with one +argument, the newly created frame. If a display parameter is supplied and a window-system is not, guess the window-system from the display. @@ -894,7 +892,8 @@ Calls `suspend-emacs' if invoked from the controlling tty device, (defvar frame-name-history nil) (defun select-frame-by-name (name) - "Select the frame on the current terminal whose name is NAME and raise it. + "Select the frame whose name is NAME and raise it. +Frames on the current terminal are checked first. If there is no frame by that name, signal an error." (interactive (let* ((frame-names-alist (make-frame-names-alist)) @@ -905,11 +904,14 @@ If there is no frame by that name, signal an error." (if (= (length input) 0) (list default) (list input)))) - (let* ((frame-names-alist (make-frame-names-alist)) - (frame (cdr (assoc name frame-names-alist)))) - (if frame - (select-frame-set-input-focus frame) - (error "There is no frame named `%s'" name)))) + (select-frame-set-input-focus + ;; Prefer frames on the current display. + (or (cdr (assoc name (make-frame-names-alist))) + (catch 'done + (dolist (frame (frame-list)) + (when (equal (frame-parameter frame 'name) name) + (throw 'done frame)))) + (error "There is no frame named `%s'" name)))) ;;;; Background mode. @@ -1073,7 +1075,7 @@ is given and non-nil, the unwanted frames are iconified instead." (when mini (setq parms (delq mini parms))) ;; Leave name in iff it was set explicitly. ;; This should fix the behavior reported in - ;; http://lists.gnu.org/archive/html/emacs-devel/2007-08/msg01632.html + ;; https://lists.gnu.org/r/emacs-devel/2007-08/msg01632.html (when (and name (not explicit-name)) (setq parms (delq name parms))) parms)) @@ -1110,6 +1112,38 @@ differing font heights." If FRAME is omitted, describe the currently selected frame." (cdr (assq 'width (frame-parameters frame)))) +(defalias 'frame-border-width 'frame-internal-border-width) +(defalias 'frame-pixel-width 'frame-native-width) +(defalias 'frame-pixel-height 'frame-native-height) + +(defun frame-inner-width (&optional frame) + "Return inner width of FRAME in pixels. +FRAME defaults to the selected frame." + (setq frame (window-normalize-frame frame)) + (- (frame-native-width frame) + (* 2 (frame-internal-border-width frame)))) + +(defun frame-inner-height (&optional frame) + "Return inner height of FRAME in pixels. +FRAME defaults to the selected frame." + (setq frame (window-normalize-frame frame)) + (- (frame-native-height frame) + (* 2 (frame-internal-border-width frame)))) + +(defun frame-outer-width (&optional frame) + "Return outer width of FRAME in pixels. +FRAME defaults to the selected frame." + (setq frame (window-normalize-frame frame)) + (let ((edges (frame-edges frame 'outer-edges))) + (- (nth 2 edges) (nth 0 edges)))) + +(defun frame-outer-height (&optional frame) + "Return outer height of FRAME in pixels. +FRAME defaults to the selected frame." + (setq frame (window-normalize-frame frame)) + (let ((edges (frame-edges frame 'outer-edges))) + (- (nth 3 edges) (nth 1 edges)))) + (declare-function x-list-fonts "xfaces.c" (pattern &optional face frame maximum width)) @@ -1450,6 +1484,7 @@ FRAME." (declare-function w32-mouse-absolute-pixel-position "w32fns.c") (declare-function x-mouse-absolute-pixel-position "xfns.c") +(declare-function ns-mouse-absolute-pixel-position "nsfns.c") (defun mouse-absolute-pixel-position () "Return absolute position of mouse cursor in pixels. @@ -1462,6 +1497,8 @@ position (0, 0) of the selected frame's terminal." (x-mouse-absolute-pixel-position)) ((eq frame-type 'w32) (w32-mouse-absolute-pixel-position)) + ((eq frame-type 'ns) + (ns-mouse-absolute-pixel-position)) (t (cons 0 0))))) @@ -2123,7 +2160,7 @@ To adjust bottom dividers for frames individually, use the frame parameter `bottom-divider-width'." :type '(restricted-sexp :tag "Default width of bottom dividers" - :match-alternatives (frame-window-divider-width-valid-p)) + :match-alternatives (window-divider-width-valid-p)) :initialize 'custom-initialize-default :set (lambda (symbol value) (set-default symbol value) @@ -2140,7 +2177,7 @@ To adjust right dividers for frames individually, use the frame parameter `right-divider-width'." :type '(restricted-sexp :tag "Default width of right dividers" - :match-alternatives (frame-window-divider-width-valid-p)) + :match-alternatives (window-divider-width-valid-p)) :initialize 'custom-initialize-default :set (lambda (symbol value) (set-default symbol value) @@ -2401,7 +2438,11 @@ See also `toggle-frame-maximized'." (set-frame-parameter nil 'fullscreen fullscreen-restore) (set-frame-parameter nil 'fullscreen nil))) (modify-frame-parameters - nil `((fullscreen . fullboth) (fullscreen-restore . ,fullscreen)))))) + nil `((fullscreen . fullboth) (fullscreen-restore . ,fullscreen)))) + ;; Manipulating a frame without waiting for the fullscreen + ;; animation to complete can cause a crash, or other unexpected + ;; behaviour, on macOS (bug#28496). + (when (featurep 'cocoa) (sleep-for 0.5)))) ;;;; Key bindings @@ -2426,7 +2467,13 @@ See also `toggle-frame-maximized'." (make-obsolete-variable 'window-system-version "it does not give useful information." "24.3") -;; Variables which should trigger redisplay of the current buffer. +;; Variables whose change of value should trigger redisplay of the +;; current buffer. +;; To test whether a given variable needs to be added to this list, +;; write a simple interactive function that changes the variable's +;; value and bind that function to a simple key, like F5. If typing +;; F5 then produces the correct effect, the variable doesn't need +;; to be in this list; otherwise, it does. (mapc (lambda (var) (add-variable-watcher var (symbol-function 'set-buffer-redisplay))) '(line-spacing @@ -2434,6 +2481,10 @@ See also `toggle-frame-maximized'." line-prefix wrap-prefix truncate-lines + display-line-numbers + display-line-numbers-width + display-line-numbers-current-absolute + display-line-numbers-widen bidi-paragraph-direction bidi-display-reordering)) diff --git a/lisp/frameset.el b/lisp/frameset.el index ebf09d3ab5c..16940f814a9 100644 --- a/lisp/frameset.el +++ b/lisp/frameset.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -446,8 +446,12 @@ DO NOT MODIFY. See `frameset-filter-alist' for a full description.") (buffer-list . :never) (buffer-predicate . :never) (buried-buffer-list . :never) + ;; Don't save the 'client' parameter to avoid that a subsequent + ;; `save-buffers-kill-terminal' in a non-client session barks at + ;; the user (Bug#29067). + (client . :never) (delete-before . :never) - (font . frameset-filter-shelve-param) + (font . frameset-filter-font-param) (foreground-color . frameset-filter-sanitize-color) (fullscreen . frameset-filter-shelve-param) (GUI:font . frameset-filter-unshelve-param) @@ -631,6 +635,17 @@ see `frameset-filter-alist'." (setcdr found val) nil)))) +(defun frameset-filter-font-param (current filtered parameters saving + &optional prefix) + "When switching from a tty frame to a GUI frame, remove the FONT param. + +When switching from a GUI frame to a tty frame, behave +as `frameset-filter-shelve-param' does." + (or saving + (if (frameset-switch-to-tty-p parameters) + (frameset-filter-shelve-param current filtered parameters saving + prefix)))) + (defun frameset-filter-iconified (_current _filtered parameters saving) "Remove CURRENT when saving an iconified frame. This is used for positional parameters `left' and `top', which are @@ -1024,6 +1039,12 @@ Internal use only." (frameset--initial-params filtered-cfg)))) (puthash frame :created frameset--action-map)) + ;; Remove `border-width' from the list of parameters. If it has not + ;; been assigned via `make-frame-on-display', any attempt to assign + ;; it now via `modify-frame-parameters' may result in an error on X + ;; (Bug#28873). + (setq filtered-cfg (assq-delete-all 'border-width filtered-cfg)) + ;; Try to assign parent-frame right here - it will improve things ;; for minibuffer-less child frames. (let* ((frame-id (frame-parameter frame 'frameset--parent-frame)) diff --git a/lisp/fringe.el b/lisp/fringe.el index acd13b54b1f..3cb6f9d115b 100644 --- a/lisp/fringe.el +++ b/lisp/fringe.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/generic-x.el b/lisp/generic-x.el index 8823faac0ff..09a5488a178 100644 --- a/lisp/generic-x.el +++ b/lisp/generic-x.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/gnus/ChangeLog.1 b/lisp/gnus/ChangeLog.1 index 4cf5129dcd5..c21d59bf706 100644 --- a/lisp/gnus/ChangeLog.1 +++ b/lisp/gnus/ChangeLog.1 @@ -3717,7 +3717,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; Local Variables: ;; coding: utf-8 diff --git a/lisp/gnus/ChangeLog.2 b/lisp/gnus/ChangeLog.2 index d7ff3b6205e..f1633389246 100644 --- a/lisp/gnus/ChangeLog.2 +++ b/lisp/gnus/ChangeLog.2 @@ -18553,7 +18553,7 @@ See ChangeLog.1 for earlier changes. GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; Local Variables: ;; coding: utf-8 diff --git a/lisp/gnus/ChangeLog.3 b/lisp/gnus/ChangeLog.3 index a799f73f583..2d030b61b99 100644 --- a/lisp/gnus/ChangeLog.3 +++ b/lisp/gnus/ChangeLog.3 @@ -955,7 +955,7 @@ * gnus-sum.el (gnus-summary-read-group-1): Initialize the spam code if that's needed. - * spam.el (spam-initialize): Allow calling repeatedly, but only run the + * spam.el (spam-initialize): Allow calling repeatedly, but only run the code once (bug#9069). 2014-01-18 Steinar Bang <sb@dod.no> @@ -3819,7 +3819,7 @@ 2012-02-15 Paul Eggert <eggert@cs.ucla.edu> * shr.el (shr-rescale-image): Undo previous change; see - <http://lists.gnu.org/archive/html/emacs-devel/2012-02/msg00540.html>. + <https://lists.gnu.org/r/emacs-devel/2012-02/msg00540.html>. 2012-02-13 Lars Ingebrigtsen <larsi@gnus.org> @@ -9303,7 +9303,7 @@ * mail-source.el, message.el, mm-bodies.el, mm-decode.el, mm-extern.el: * mm-util.el, mm-view.el, mml-smime.el, mml.el, mml1991.el, mml2015.el: * nnfolder.el, nnheader.el, nnmail.el, nnmaildir.el, nnrss.el, nntp.el: - * rfc1843.el, sieve-manage.el, smime.el, spam.el: + * gnus-rfc1843.el, sieve-manage.el, smime.el, spam.el: Fix comment for declare-function. 2010-10-11 Lars Magne Ingebrigtsen <larsi@gnus.org> @@ -10470,7 +10470,7 @@ 2010-09-25 Julien Danjou <julien@danjou.info> - * rfc1843.el: Remove useless rfc1843-old-gnus-decode-header-function + * gnus-rfc1843.el: Remove useless rfc1843-old-gnus-decode-header-function variables. * nnheader.el: Remove useless variables news-reply-yank-from and @@ -14716,14 +14716,14 @@ * mml2015.el (gnus-buffer-live-p, gnus-get-buffer-create): * nnfolder.el (gnus-request-group): * nnheader.el (ietf-drums-unfold-fws): - * rfc1843.el (mail-header-parse-content-type, message-narrow-to-head): + * gnus-rfc1843.el (mail-header-parse-content-type, message-narrow-to-head): * smime.el (gnus-run-mode-hooks): * spam-stat.el (gnus-message): Autoload. * gnus-cache.el, gnus-fun.el, gnus-group.el, gnus.el, mail-source.el: * mm-bodies.el, mm-decode.el, mm-extern.el, mm-util.el: * mml-smime.el, mml.el, mml1991.el, mml2015.el, nndb.el, nnfolder.el: - * nnmail.el, nnmaildir.el, nnrss.el, rfc1843.el, spam.el: + * nnmail.el, nnmaildir.el, nnrss.el, gnus-rfc1843.el, spam.el: Add declare-function compatibility definition. * gnus-cache.el (nnvirtual-find-group-art): @@ -14753,7 +14753,7 @@ * nnmail.el (gnus-activate-group, gnus-group-mark-article-read): * nnmaildir.el (gnus-group-mark-article-read): * nnrss.el (w3-parse-buffer, gnus-group-make-rss-group): - * rfc1843.el (message-fetch-field): + * gnus-rfc1843.el (message-fetch-field): * spam.el (gnus-extract-address-components): Declare as functions. @@ -19139,7 +19139,7 @@ (mml-insert-parameter): Fold lines properly even if a parameter is segmented into two or more lines; change the max column to 76. - * rfc1843.el (rfc1843-decode-article-body): Don't use + * gnus-rfc1843.el (rfc1843-decode-article-body): Don't use ignore-errors when calling mail-header-parse-content-type. * rfc2231.el (rfc2231-parse-string): Return at least type if @@ -20525,7 +20525,7 @@ * mml1991.el (mc-pgp-always-sign): * mml2015.el (mc-pgp-always-sign): * nnheader.el (nnmail-extra-headers): - * rfc1843.el (gnus-decode-encoded-word-function) + * gnus-rfc1843.el (gnus-decode-encoded-word-function) (gnus-decode-header-function, gnus-newsgroup-name): * spam-stat.el (gnus-original-article-buffer): Add defvars. @@ -26340,7 +26340,7 @@ See ChangeLog.2 for earlier changes. GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; Local Variables: ;; coding: utf-8 diff --git a/lisp/gnus/canlock.el b/lisp/gnus/canlock.el index 5157256594d..bb666ff934f 100644 --- a/lisp/gnus/canlock.el +++ b/lisp/gnus/canlock.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/deuglify.el b/lisp/gnus/deuglify.el index af8ccf182e4..897ca7048ba 100644 --- a/lisp/gnus/deuglify.el +++ b/lisp/gnus/deuglify.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/gmm-utils.el b/lisp/gnus/gmm-utils.el index 45035646f76..4050046aab4 100644 --- a/lisp/gnus/gmm-utils.el +++ b/lisp/gnus/gmm-utils.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/gnus-agent.el b/lisp/gnus/gnus-agent.el index 93d86526af0..466da535605 100644 --- a/lisp/gnus/gnus-agent.el +++ b/lisp/gnus/gnus-agent.el @@ -16,7 +16,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -1108,7 +1108,7 @@ downloadable." gnus-newsgroup-cached) (setq articles (gnus-sorted-ndifference (gnus-sorted-ndifference - (gnus-copy-sequence articles) + (copy-tree articles) gnus-newsgroup-downloadable) gnus-newsgroup-cached))) @@ -1123,7 +1123,7 @@ downloadable." (when gnus-newsgroup-processable (setq gnus-newsgroup-downloadable (let* ((dl gnus-newsgroup-downloadable) - (processable (sort (gnus-copy-sequence gnus-newsgroup-processable) '<)) + (processable (sort (copy-tree gnus-newsgroup-processable) '<)) (gnus-newsgroup-downloadable processable)) (gnus-agent-summary-fetch-group) @@ -1513,7 +1513,7 @@ downloaded into the agent." (let* ((fetched-articles (list nil)) (tail-fetched-articles fetched-articles) (dir (gnus-agent-group-pathname group)) - (date (time-to-days (current-time))) + (date (time-to-days nil)) (case-fold-search t) pos crosses (file-name-coding-system nnmail-pathname-coding-system)) @@ -2833,7 +2833,7 @@ The following commands are available: "Copy the current category." (interactive (list (gnus-category-name) (intern (read-string "New name: ")))) (let ((info (assq category gnus-category-alist))) - (push (let ((newcat (gnus-copy-sequence info))) + (push (let ((newcat (copy-tree info))) (setf (gnus-agent-cat-name newcat) to) (setf (gnus-agent-cat-groups newcat) nil) newcat) @@ -3089,7 +3089,7 @@ FORCE is equivalent to setting the expiration predicates to true." (nov-entries-deleted 0) (info (gnus-get-info group)) (alist gnus-agent-article-alist) - (day (- (time-to-days (current-time)) + (day (- (time-to-days nil) (gnus-agent-find-parameter group 'agent-days-until-old))) (specials (if (and alist (not force)) @@ -3824,7 +3824,7 @@ has been fetched." ;; be expired later. (gnus-agent-load-alist group) (gnus-agent-save-alist group (list article) - (time-to-days (current-time)))))) + (time-to-days nil))))) (defun gnus-agent-regenerate-group (group &optional reread) "Regenerate GROUP. diff --git a/lisp/gnus/gnus-art.el b/lisp/gnus/gnus-art.el index 66c9fbea871..97aa878ab63 100644 --- a/lisp/gnus/gnus-art.el +++ b/lisp/gnus/gnus-art.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -3430,13 +3430,20 @@ possible values." (progn (goto-char date-position) (setq date (get-text-property (point) 'original-date)) + (beginning-of-line) (when (looking-at "[^:]+:[\t ]*") (setq bface (get-text-property (match-beginning 0) 'face) eface (get-text-property (match-end 0) 'face))) - (delete-region (point) - (progn - (gnus-article-forward-header) - (point))) + (goto-char date-position) + (delete-region + (or (and (bolp) date-position) + ;; There might be space(s) added for line unfolding. + (and (get-text-property date-position 'gnus-date-type) + (< (skip-chars-backward "\t ") 0) + (text-property-any (point) date-position + 'gnus-date-type nil)) + date-position) + (progn (gnus-article-forward-header) (point))) (article-transform-date date type bface eface)) (save-restriction (widen) @@ -3455,9 +3462,14 @@ possible values." (when (looking-at "[^:]+:[\t ]*") (setq bface (get-text-property (match-beginning 0) 'face) eface (get-text-property (match-end 0) 'face))) - (delete-region pos (or (text-property-any pos (point-max) - 'gnus-date-type nil) - (point-max)))) + ;; Note: a feature like `gnus-treat-unfold-headers' breaks + ;; the continuity of text props of a multi-line Date header, + ;; that a user-defined date format might create, by adding + ;; spaces. So, don't rely on gnus-date-type or original-date + ;; text prop in case of searching for the header boundary. + (delete-region pos (progn + (gnus-article-forward-header) + (point)))) (unless date ;; the 1st time (goto-char (point-min)) (while (re-search-forward "^Date:[\t ]*" nil t) @@ -3477,32 +3489,48 @@ possible values." (widen))))))) (defun article-transform-date (date type bface eface) - (dolist (this-type (cond - ((null type) - (list 'ut)) - ((atom type) - (list type)) - (t - type))) - (goto-char - (prog1 - (point) - (add-text-properties - (point) - (progn - (insert (article-make-date-line date (or this-type 'ut)) "\n") - (point)) - (list 'original-date date 'gnus-date-type this-type)))) - ;; Do highlighting. - (when (looking-at - "\\([^:]+:\\)[\t ]*\\(\\(?:[^\t\n ]+[\t ]+\\)*[^\t\n ]+\\)?") - (put-text-property (match-beginning 1) (match-end 1) 'face bface) + (let (begin date-line) + (dolist (this-type (cond ((null type) + (list 'ut)) + ((atom type) + (list type)) + (t + type))) + (setq begin (point) + date-line (article-make-date-line date (or this-type 'ut))) + (if (and (eq this-type 'user-defined) (bolp) + ;; Test if this is not a continuation. + (not (get-text-property + (prog2 (end-of-line 0) (point) (goto-char begin)) + 'gnus-date-type))) + (progn + (string-match "\\`\\([^\t\n :]+:\\)?[\t ]*" date-line) + (if (match-beginning 1) + (insert date-line "\n") + ;; This user-defined date seems to intend to be a continuation + ;; line of a multi-line Date header like this: + ;; Date: Thu, Jan 1 00:00:00 1970 +0000 + ;; (47 years, 5 months, 20 days ago) + (insert "Date: " (substring date-line (match-end 0)) "\n"))) + (insert date-line "\n")) + (add-text-properties begin (point) (list 'original-date date + 'gnus-date-type this-type)) + (goto-char begin) + ;; Do highlighting. + (beginning-of-line) + (looking-at + "\\([^\n:]+:\\)?[\t ]*\\(\\(?:[^\t\n ]+[\t ]+\\)*[^\t\n ]+\\)?") + (when (and bface (match-beginning 1)) + (put-text-property (match-beginning 1) (match-end 1) 'face bface)) (when (match-beginning 2) - (put-text-property (match-beginning 2) (match-end 2) 'face eface)) - (while (and (zerop (forward-line 1)) - (looking-at "[\t ]+\\(\\(?:[^\t\n ]+[\t ]+\\)*[^\t\n ]+\\)?")) - (when (match-beginning 1) - (put-text-property (match-beginning 1) (match-end 1) 'face eface)))))) + (when eface + (put-text-property (match-beginning 2) (match-end 2) 'face eface)) + (while (and (zerop (forward-line 1)) + (looking-at + "[\t ]+\\(\\(?:[^\t\n ]+[\t ]+\\)*[^\t\n ]+\\)?")) + (when (and eface (match-beginning 1)) + (put-text-property (match-beginning 1) (match-end 1) + 'face eface))))))) (defun article-make-date-line (date type) "Return a DATE line of TYPE." @@ -3600,8 +3628,7 @@ possible values." (defun article-lapsed-string (time &optional max-segments) ;; If the date is seriously mangled, the timezone functions are ;; liable to bug out, so we ignore all errors. - (let* ((now (current-time)) - (real-time (time-subtract now time)) + (let* ((real-time (time-subtract nil time)) (real-sec (and real-time (+ (* (float (car real-time)) 65536) (cadr real-time)))) @@ -3735,7 +3762,7 @@ is to run." "Convert the current article date to the user-defined format. This format is defined by the `gnus-article-time-format' variable." (interactive (list t)) - (article-date-ut 'user highlight)) + (article-date-ut 'user-defined highlight)) (defun article-date-iso8601 (&optional highlight) "Convert the current article date to ISO8601." @@ -4216,7 +4243,7 @@ If variable `gnus-use-long-file-name' is non-nil, it is (defun article-verify-x-pgp-sig () "Verify X-PGP-Sig." - ;; <ftp://ftp.isc.org/pub/pgpcontrol/FORMAT> + ;; <https://ftp.isc.org/pub/pgpcontrol/FORMAT> (interactive) (if (gnus-buffer-live-p gnus-original-article-buffer) (let ((sig (with-current-buffer gnus-original-article-buffer @@ -5030,11 +5057,14 @@ and `gnus-mime-delete-part', and not provided at run-time normally." (gnus-article-edit-done)) (gnus-configure-windows 'article) (sit-for 0) - (when (and current-id (integerp gnus-auto-select-part)) - (gnus-article-jump-to-part - (min (max (+ current-id gnus-auto-select-part) 1) - (with-current-buffer gnus-article-buffer - (length gnus-article-mime-handle-alist))))))) + (let ((handles (with-current-buffer gnus-article-buffer + gnus-article-mime-handle-alist))) + ;; `handles' will be nil if there is the only one part + ;; in the article and is deleted. + (when (and handles current-id (integerp gnus-auto-select-part)) + (gnus-article-jump-to-part + (min (max (+ current-id gnus-auto-select-part) 1) + (length handles))))))) (defun gnus-mime-replace-part (file) "Replace MIME part under point with an external body." @@ -6311,8 +6341,9 @@ Provided for backwards compatibility." ;; in each element are in the increasing order. (dolist (handle (reverse gnus-article-mime-handle-alist)) (if (stringp (cadr handle)) - (setq flat (nconc flat (gnus-article-mime-handles - (cddr handle) (list (car handle)) flat))) + (when (cddr handle) + (setq flat (nconc flat (gnus-article-mime-handles + (cddr handle) (list (car handle)) flat)))) (delq (rassq (cdr handle) flat) flat) (setq flat (nconc flat (list (cons (list (car handle)) (cdr handle))))))) @@ -6335,7 +6366,7 @@ buttons to be added to the header are only the ones that aren't inlined in the body. Use `gnus-header-face-alist' to highlight buttons." (interactive (list t)) (gnus-with-article-buffer - (let ((case-fold-search t) buttons handle type st) + (let ((case-fold-search t) buttons st) (save-excursion (save-restriction (widen) @@ -6356,22 +6387,7 @@ in the body. Use `gnus-header-face-alist' to highlight buttons." ;; Find buttons. (setq buttons nil) (dolist (button (gnus-article-mime-handles)) - (setq handle (cdr button) - type (mm-handle-media-type handle)) - (when (or (and (if (gnus-buffer-live-p gnus-summary-buffer) - (with-current-buffer gnus-summary-buffer - gnus-inhibit-images) - gnus-inhibit-images) - (string-match "\\`image/" type)) - (mm-inline-override-p handle) - (and (mm-handle-disposition handle) - (not (equal (car (mm-handle-disposition handle)) - "inline")) - (not (mm-attachment-override-p handle))) - (not (mm-automatic-display-p handle)) - (not (or (and (mm-inlinable-p handle) - (mm-inlined-p handle)) - (mm-automatic-external-display-p type)))) + (unless (mm-handle-undisplayer (cdr button)) (push button buttons))) (when buttons ;; Add header buttons. @@ -6382,8 +6398,7 @@ in the body. Use `gnus-header-face-alist' to highlight buttons." (dolist (button (nreverse buttons)) (setq st (point)) (insert " ") - (mm-handle-set-undisplayer (setq handle (cdr button)) nil) - (gnus-insert-mime-button handle (car button)) + (gnus-insert-mime-button (cdr button) (car button)) (skip-chars-backward "\t\n ") (delete-region (point) (point-max)) (when (> (current-column) (window-width)) @@ -6968,6 +6983,7 @@ If given a prefix, show the hidden text instead." (save-excursion (erase-buffer) (gnus-kill-all-overlays) + (setq bidi-paragraph-direction nil) (setq group (or group gnus-newsgroup-name)) ;; Using `gnus-request-article' directly will insert the article into @@ -7075,6 +7091,7 @@ If given a prefix, show the hidden text instead." (while (not result) (erase-buffer) (gnus-kill-all-overlays) + (setq bidi-paragraph-direction nil) (let ((gnus-newsgroup-name group)) (gnus-check-group-server)) (cond diff --git a/lisp/gnus/gnus-async.el b/lisp/gnus/gnus-async.el index 11e765d2d77..b9aa763bcd6 100644 --- a/lisp/gnus/gnus-async.el +++ b/lisp/gnus/gnus-async.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/gnus-bcklg.el b/lisp/gnus/gnus-bcklg.el index d85448e109f..30f377feea3 100644 --- a/lisp/gnus/gnus-bcklg.el +++ b/lisp/gnus/gnus-bcklg.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -144,8 +144,8 @@ (setq end (next-single-property-change (1+ beg) 'gnus-backlog (current-buffer) (point-max))))) - (with-current-buffer (or (current-buffer) buffer) - (let ((buffer-read-only nil)) + (with-current-buffer (or buffer (current-buffer)) + (let ((inhibit-read-only t)) (erase-buffer) (insert-buffer-substring gnus-backlog-buffer beg end))) t)))) diff --git a/lisp/gnus/gnus-bookmark.el b/lisp/gnus/gnus-bookmark.el index 655881396c0..cef7df5e91c 100644 --- a/lisp/gnus/gnus-bookmark.el +++ b/lisp/gnus/gnus-bookmark.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/gnus-cache.el b/lisp/gnus/gnus-cache.el index fa3df7b14aa..801728d2f26 100644 --- a/lisp/gnus/gnus-cache.el +++ b/lisp/gnus/gnus-cache.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -735,7 +735,7 @@ If LOW, update the lower bound instead." ;; `gnus-cache-unified-group-names' needless. (gnus-sethash (or (cdr (assoc group gnus-cache-unified-group-names)) group) - (cons (car nums) (gnus-last-element nums)) + (cons (car nums) (car (last nums))) gnus-cache-active-hashtb)) ;; Go through all the other files. (dolist (file alphs) diff --git a/lisp/gnus/gnus-cite.el b/lisp/gnus/gnus-cite.el index 3194e966f0f..3cd98ce680d 100644 --- a/lisp/gnus/gnus-cite.el +++ b/lisp/gnus/gnus-cite.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/gnus-cloud.el b/lisp/gnus/gnus-cloud.el index 605dda2509b..c57576cf3c7 100644 --- a/lisp/gnus/gnus-cloud.el +++ b/lisp/gnus/gnus-cloud.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -219,7 +219,7 @@ easy interactive way to set this from the Server buffer." Use old data if FORCE-OLDER is not nil." (let* ((contents (plist-get elem :contents)) (date (or (plist-get elem :timestamp) "0")) - (now (gnus-cloud-timestamp (current-time))) + (now (gnus-cloud-timestamp nil)) (newer (string-lessp date now)) (group-info (gnus-get-info group))) (if (and contents @@ -486,7 +486,7 @@ Otherwise, returns the Gnus Cloud data chunks." (gnus-method-to-server (gnus-find-method-for-group (gnus-info-group info)))) - (push `(:type :newsrc-data :name ,(gnus-info-group info) :contents ,info :timestamp ,(gnus-cloud-timestamp (current-time))) + (push `(:type :newsrc-data :name ,(gnus-info-group info) :contents ,info :timestamp ,(gnus-cloud-timestamp nil)) infos))) infos)) diff --git a/lisp/gnus/gnus-cus.el b/lisp/gnus/gnus-cus.el index e5787e86257..f698d806171 100644 --- a/lisp/gnus/gnus-cus.el +++ b/lisp/gnus/gnus-cus.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -406,7 +406,7 @@ category.")) ;; every duplicate ends up being displayed. So, rather than ;; display them, remove them from the list. - (let ((tmp (setq values (gnus-copy-sequence values))) + (let ((tmp (setq values (copy-tree values))) elem) (while (cdr tmp) (while (setq elem (assq (caar tmp) (cdr tmp))) @@ -454,7 +454,7 @@ Set variables local to the group you are entering. If you want to turn threading off in `news.answers', you could put `(gnus-show-threads nil)' in the group parameters of that group. `gnus-show-threads' will be made into a local variable in the summary -buffer you enter, and the form nil will be `eval'ed there. +buffer you enter, and the form nil will be `eval'uated there. This can also be used as a group-specific hook function, if you'd like. If you want to hear a beep when you enter a group, you could @@ -535,7 +535,7 @@ These files will not be loaded, even though they would normally be so, for some reason or other.") (eval (sexp :tag "Eval" :value nil) "\ -The value of this entry will be `eval'el. +The value of this entry will be `eval'uated. This element will be ignored when handling global score files.") (read-only (boolean :tag "Read-only" :value t) "\ diff --git a/lisp/gnus/gnus-delay.el b/lisp/gnus/gnus-delay.el index 7b599679125..0917b023af8 100644 --- a/lisp/gnus/gnus-delay.el +++ b/lisp/gnus/gnus-delay.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/gnus-demon.el b/lisp/gnus/gnus-demon.el index 81f9650ae3f..28e2699a6a4 100644 --- a/lisp/gnus/gnus-demon.el +++ b/lisp/gnus/gnus-demon.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/gnus-diary.el b/lisp/gnus/gnus-diary.el index b81c6d08f5e..5000486d19b 100644 --- a/lisp/gnus/gnus-diary.el +++ b/lisp/gnus/gnus-diary.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/gnus-dired.el b/lisp/gnus/gnus-dired.el index 10533cafd97..9394c3d7702 100644 --- a/lisp/gnus/gnus-dired.el +++ b/lisp/gnus/gnus-dired.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/gnus-draft.el b/lisp/gnus/gnus-draft.el index 6e7b307770c..77bf93af50c 100644 --- a/lisp/gnus/gnus-draft.el +++ b/lisp/gnus/gnus-draft.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/gnus-dup.el b/lisp/gnus/gnus-dup.el index f91ebbeff12..2f21efb6ee3 100644 --- a/lisp/gnus/gnus-dup.el +++ b/lisp/gnus/gnus-dup.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/gnus-eform.el b/lisp/gnus/gnus-eform.el index 93af05f4b3f..6f8722b0c71 100644 --- a/lisp/gnus/gnus-eform.el +++ b/lisp/gnus/gnus-eform.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/gnus-fun.el b/lisp/gnus/gnus-fun.el index 787c0e3a0f5..1b45847c0b3 100644 --- a/lisp/gnus/gnus-fun.el +++ b/lisp/gnus/gnus-fun.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/gnus-gravatar.el b/lisp/gnus/gnus-gravatar.el index b4763c76814..bcf09f434e9 100644 --- a/lisp/gnus/gnus-gravatar.el +++ b/lisp/gnus/gnus-gravatar.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/gnus-group.el b/lisp/gnus/gnus-group.el index 8a061b70bf6..63e59e94e2e 100644 --- a/lisp/gnus/gnus-group.el +++ b/lisp/gnus/gnus-group.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -1359,6 +1359,8 @@ if it is a string, only list groups matching REGEXP." (and gnus-permanently-visible-groups (string-match gnus-permanently-visible-groups group)) + ;; Marked groups are always visible. + (member group gnus-group-marked) (memq 'visible params) (cdr (assq 'visible params))))))) (gnus-group-insert-group-line @@ -2373,7 +2375,10 @@ specified by `gnus-gmane-group-download-format'." (with-temp-file tmpfile (url-insert-file-contents (format gnus-gmane-group-download-format - group start (+ start range))) + group start (+ start range)) + t) + ;; `url-insert-file-contents' sets this because of the 2nd arg. + (setq buffer-file-name nil) (write-region (point-min) (point-max) tmpfile) (gnus-group-read-ephemeral-group (format "nndoc+ephemeral:%s.start-%s.range-%s" group start range) @@ -2429,7 +2434,7 @@ Valid input formats include: (gnus-read-ephemeral-gmane-group group start range))) (defcustom gnus-bug-group-download-format-alist - '((emacs . "http://debbugs.gnu.org/cgi/bugreport.cgi?bug=%s;mboxmaint=yes;mboxstat=yes") + '((emacs . "https://debbugs.gnu.org/cgi/bugreport.cgi?bug=%s;mboxmaint=yes;mboxstat=yes") (debian . "http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=%s&mbox=yes;mboxmaint=yes")) "Alist of symbols for bug trackers and the corresponding URL format string. @@ -2463,13 +2468,11 @@ the bug number, and browsing the URL must return mbox output." (if (and (not gnus-plugged) (file-exists-p file)) (insert-file-contents file) - (url-insert-file-contents (format mbox-url id))))) + (url-insert-file-contents (format mbox-url id) t)))) ;; Add the debbugs address so that we can respond to reports easily. (let ((address (format "%s@%s" (car ids) - (replace-regexp-in-string - "/.*$" "" - (replace-regexp-in-string "^http://" "" mbox-url))))) + (url-host (url-generic-parse-url mbox-url))))) (goto-char (point-min)) (while (re-search-forward (concat "^" message-unix-mail-delimiter) nil t) @@ -2490,7 +2493,9 @@ the bug number, and browsing the URL must return mbox output." (insert ", " address)) (insert "To: " address "\n"))) (goto-char (point-max)) - (widen))))) + (widen))) + ;; `url-insert-file-contents' sets this because of the 2nd arg. + (setq buffer-file-name nil))) (gnus-group-read-ephemeral-group (format "nndoc+ephemeral:bug#%s" (mapconcat 'number-to-string ids ",")) @@ -2514,6 +2519,8 @@ the bug number, and browsing the URL must return mbox output." (interactive (list (string-to-number (read-string "Enter bug number: " (thing-at-point 'word) nil)))) + (when (stringp ids) + (setq ids (string-to-number ids))) (unless (listp ids) (setq ids (list ids))) (gnus-read-ephemeral-bug-group @@ -2993,7 +3000,7 @@ and NEW-NAME will be prompted for." ;; Set the info. (if (not (and info new-group)) (gnus-group-set-info form (or new-group group) part) - (setq info (gnus-copy-sequence info)) + (setq info (copy-tree info)) (setcar info new-group) (unless (gnus-server-equal method "native") (unless (nthcdr 3 info) @@ -3016,7 +3023,7 @@ and NEW-NAME will be prompted for." ;; Don't use `caddr' here since macros within the `interactive' ;; form won't be expanded. (car (cddr entry))))) - (setq method (gnus-copy-sequence method)) + (setq method (copy-tree method)) (let (entry) (while (setq entry (memq (assq 'eval method) method)) (setcar entry (eval (cadar entry))))) @@ -4560,7 +4567,7 @@ or `gnus-group-catchup-group-hook'." "Return the offset in seconds from the timestamp for GROUP to the current time, as a floating point number." (let* ((time (or (gnus-group-timestamp group) (list 0 0))) - (delta (time-subtract (current-time) time))) + (delta (time-subtract nil time))) (+ (* (nth 0 delta) 65536.0) (nth 1 delta)))) diff --git a/lisp/gnus/gnus-html.el b/lisp/gnus/gnus-html.el index d4dccfb7b1f..7fa36359f67 100644 --- a/lisp/gnus/gnus-html.el +++ b/lisp/gnus/gnus-html.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -99,11 +99,7 @@ fit these criteria." (not (file-exists-p (url-cache-create-filename url)))) (t (let ((cache-time (url-is-cached url))) (if cache-time - (time-less-p - (time-add - cache-time - ttl) - (current-time)) + (time-less-p (time-add cache-time ttl) nil) t))))) ;;;###autoload diff --git a/lisp/gnus/gnus-icalendar.el b/lisp/gnus/gnus-icalendar.el index 1f194f888d2..cca4a81d1c0 100644 --- a/lisp/gnus/gnus-icalendar.el +++ b/lisp/gnus/gnus-icalendar.el @@ -16,7 +16,7 @@ ;; 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 <http://www.gnu.org/licenses/>. +;; along with this program. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/gnus-int.el b/lisp/gnus/gnus-int.el index aaeba4a4331..0c7381286cd 100644 --- a/lisp/gnus/gnus-int.el +++ b/lisp/gnus/gnus-int.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/gnus-kill.el b/lisp/gnus/gnus-kill.el index c405c04e38e..4c15471b97a 100644 --- a/lisp/gnus/gnus-kill.el +++ b/lisp/gnus/gnus-kill.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/gnus-logic.el b/lisp/gnus/gnus-logic.el index b1499722f48..4762025bf75 100644 --- a/lisp/gnus/gnus-logic.el +++ b/lisp/gnus/gnus-logic.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/gnus-mh.el b/lisp/gnus/gnus-mh.el index 502b295cd60..90622926733 100644 --- a/lisp/gnus/gnus-mh.el +++ b/lisp/gnus/gnus-mh.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/gnus-ml.el b/lisp/gnus/gnus-ml.el index 32cf1713317..e3cdd9c3932 100644 --- a/lisp/gnus/gnus-ml.el +++ b/lisp/gnus/gnus-ml.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/gnus-mlspl.el b/lisp/gnus/gnus-mlspl.el index c42c34adceb..d0810ca8221 100644 --- a/lisp/gnus/gnus-mlspl.el +++ b/lisp/gnus/gnus-mlspl.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -72,8 +72,7 @@ match any of the group-specified splitting rules. See ;;;###autoload (defun gnus-group-split-update (&optional catch-all) "Computes nnmail-split-fancy from group params and CATCH-ALL. -It does this by calling by calling (gnus-group-split-fancy nil -nil CATCH-ALL). +It does this by calling (gnus-group-split-fancy nil nil CATCH-ALL). If CATCH-ALL is nil, `gnus-group-split-default-catch-all-group' is used instead. This variable is set by `gnus-group-split-setup'." diff --git a/lisp/gnus/gnus-msg.el b/lisp/gnus/gnus-msg.el index 85969edc81b..7a28be19d4a 100644 --- a/lisp/gnus/gnus-msg.el +++ b/lisp/gnus/gnus-msg.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/gnus-notifications.el b/lisp/gnus/gnus-notifications.el index 288dbe1b9f2..6e8dbb5c35e 100644 --- a/lisp/gnus/gnus-notifications.el +++ b/lisp/gnus/gnus-notifications.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -154,7 +154,7 @@ This is typically a function to add in (dolist (entry gnus-newsrc-alist) (let ((group (car entry))) ;; Check that the group level is less than - ;; `gnus-notifications-minimum-level' and the the group has unread + ;; `gnus-notifications-minimum-level' and the group has unread ;; messages. (when (and (<= (gnus-group-level group) gnus-notifications-minimum-level) (let ((unread (gnus-group-unread group))) diff --git a/lisp/gnus/gnus-picon.el b/lisp/gnus/gnus-picon.el index 41463e3f02f..da56b4eef05 100644 --- a/lisp/gnus/gnus-picon.el +++ b/lisp/gnus/gnus-picon.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/gnus-range.el b/lisp/gnus/gnus-range.el index 0680123e347..70548d02804 100644 --- a/lisp/gnus/gnus-range.el +++ b/lisp/gnus/gnus-range.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -38,17 +38,9 @@ If RANGE is a single range, return (RANGE). Otherwise, return RANGE." (while (cdr list) (setq list (cdr list))) (car list)) +(make-obsolete 'gnus-last-element "use `car' of `last' instead." "27.1") -(defun gnus-copy-sequence (list) - "Do a complete, total copy of a list." - (let (out) - (while (consp list) - (if (consp (car list)) - (push (gnus-copy-sequence (pop list)) out) - (push (pop list) out))) - (if list - (nconc (nreverse out) list) - (nreverse out)))) +(define-obsolete-function-alias 'gnus-copy-sequence 'copy-tree "27.1") (defun gnus-set-difference (list1 list2) "Return a list of elements of LIST1 that do not appear in LIST2." @@ -455,7 +447,7 @@ modified." (if (or (null range1) (null range2)) range1 (let (out r1 r2 r1_min r1_max r2_min r2_max - (range2 (gnus-copy-sequence range2))) + (range2 (copy-tree range2))) (setq range1 (if (listp (cdr range1)) range1 (list range1)) range2 (sort (if (listp (cdr range2)) range2 (list range2)) (lambda (e1 e2) diff --git a/lisp/gnus/gnus-registry.el b/lisp/gnus/gnus-registry.el index 51f6459d2f8..466238d2523 100644 --- a/lisp/gnus/gnus-registry.el +++ b/lisp/gnus/gnus-registry.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/gnus-rfc1843.el b/lisp/gnus/gnus-rfc1843.el index a47e19b8f0d..6477d0114a7 100644 --- a/lisp/gnus/gnus-rfc1843.el +++ b/lisp/gnus/gnus-rfc1843.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/gnus-salt.el b/lisp/gnus/gnus-salt.el index 7037328b7a4..ab2ffa9228e 100644 --- a/lisp/gnus/gnus-salt.el +++ b/lisp/gnus/gnus-salt.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/gnus-score.el b/lisp/gnus/gnus-score.el index 2defa76f50d..765dfab570a 100644 --- a/lisp/gnus/gnus-score.el +++ b/lisp/gnus/gnus-score.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -43,7 +43,7 @@ for each score file or each score file directory. Gnus will decide by itself what score files are applicable to which group. Say you want to use the single score file -\"/ftp.gnus.org@ftp:/pub/larsi/ding/score/soc.motss.SCORE\" and all +\"/ftp.gnus.org:/pub/larsi/ding/score/soc.motss.SCORE\" and all score files in the \"/ftp.some-where:/pub/score\" directory. (setq gnus-global-score-files @@ -1078,11 +1078,11 @@ EXTRA is the possible non-standard header." "Return the score of the current article. With prefix ARG, return the total score of the current (sub)thread." (interactive "P") - (gnus-message 1 "%s" (if arg - (gnus-thread-total-score - (gnus-id-to-thread - (mail-header-id (gnus-summary-article-header)))) - (gnus-summary-article-score)))) + (message "%s" (if arg + (gnus-thread-total-score + (gnus-id-to-thread + (mail-header-id (gnus-summary-article-header)))) + (gnus-summary-article-score)))) (defun gnus-score-change-score-file (file) "Change current score alist." @@ -1238,7 +1238,7 @@ If FORMAT, also format the current score file." (or (not decay) (gnus-decay-scores alist decay))) (gnus-score-set 'touched '(t) alist) - (gnus-score-set 'decay (list (time-to-days (current-time))) alist)) + (gnus-score-set 'decay (list (time-to-days nil)) alist)) ;; We do not respect eval and files atoms from global score ;; files. (when (and files (not global)) @@ -2318,7 +2318,7 @@ score in `gnus-newsgroup-scored' by SCORE." (when (or (not (listp gnus-newsgroup-adaptive)) (memq 'line gnus-newsgroup-adaptive)) (save-excursion - (let* ((malist (gnus-copy-sequence gnus-adaptive-score-alist)) + (let* ((malist (copy-tree gnus-adaptive-score-alist)) (alist malist) (date (current-time-string)) (data gnus-newsgroup-data) @@ -2731,8 +2731,10 @@ GROUP using BNews sys file syntax." (insert (car sfiles)) (goto-char (point-min)) ;; First remove the suffix itself. - (when (re-search-forward (concat "." score-regexp) nil t) - (replace-match "" t t) + (when (re-search-forward score-regexp nil t) + (unless (= (match-end 0) (match-beginning 0)) ; non-empty suffix + (replace-match "" t t) + (delete-char -1)) ; remove the "." before the suffix (goto-char (point-min)) (if (looking-at (regexp-quote kill-dir)) ;; If the file name was just "SCORE", `klen' is one character @@ -2961,8 +2963,8 @@ The list is determined from the variable `gnus-score-file-alist'." (expand-file-name suffix gnus-kill-files-directory)) ((gnus-use-long-file-name 'not-score) ;; Append ".SCORE" to newsgroup name. - (expand-file-name (concat (gnus-newsgroup-savable-name newsgroup) - "." suffix) + (expand-file-name (let ((name (gnus-newsgroup-savable-name newsgroup))) + (if (string= "" suffix) name (concat name "." suffix))) gnus-kill-files-directory)) (t ;; Place "SCORE" under the hierarchical directory. @@ -3060,7 +3062,7 @@ If ADAPT, return the home adaptive file instead." (defun gnus-decay-scores (alist day) "Decay non-permanent scores in ALIST." - (let ((times (- (time-to-days (current-time)) day)) + (let ((times (- (time-to-days nil) day)) kill entry updated score n) (unless (zerop times) ;Done decays today already? (while (setq entry (pop alist)) diff --git a/lisp/gnus/gnus-sieve.el b/lisp/gnus/gnus-sieve.el index 2c5fd34f8ca..00f0636cf77 100644 --- a/lisp/gnus/gnus-sieve.el +++ b/lisp/gnus/gnus-sieve.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/gnus-spec.el b/lisp/gnus/gnus-spec.el index 809371d6109..a6149062587 100644 --- a/lisp/gnus/gnus-spec.el +++ b/lisp/gnus/gnus-spec.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/gnus-srvr.el b/lisp/gnus/gnus-srvr.el index bed5993b9c1..a3341470fa2 100644 --- a/lisp/gnus/gnus-srvr.el +++ b/lisp/gnus/gnus-srvr.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -452,7 +452,8 @@ The following commands are available: (if server (error "No such server: %s" server) (error "No server on the current line"))) (unless (assoc server gnus-server-alist) - (error "Read-only server %s" server)) + (error "Server %s must be deleted from your configuration files" + server)) (gnus-dribble-touch) (let ((buffer-read-only nil)) (gnus-delete-line)) @@ -608,7 +609,7 @@ The following commands are available: (error "%s already exists" to)) (unless (gnus-server-to-method from) (error "%s: no such server" from)) - (let ((to-entry (cons from (gnus-copy-sequence + (let ((to-entry (cons from (copy-tree (gnus-server-to-method from))))) (setcar to-entry to) (setcar (nthcdr 2 to-entry) to) @@ -642,7 +643,8 @@ The following commands are available: (unless server (error "No server on current line")) (unless (assoc server gnus-server-alist) - (error "This server can't be edited")) + (error "Server %s must be edited in your configuration files" + server)) (let ((info (cdr (assoc server gnus-server-alist)))) (gnus-close-server info) (gnus-edit-form @@ -1157,7 +1159,7 @@ Requesting compaction of %s... (this may take a long time)" (error "The server under point can't host the Emacs Cloud")) (when (not (string-equal gnus-cloud-method server)) - (custom-set-variables '(gnus-cloud-method server)) + (customize-set-variable 'gnus-cloud-method server) ;; Note we can't use `Custom-save' here. (when (gnus-yes-or-no-p (format "The new cloud host server is %S now. Save it? " server)) diff --git a/lisp/gnus/gnus-start.el b/lisp/gnus/gnus-start.el index aaa8ab9a888..3c3c594fe7b 100644 --- a/lisp/gnus/gnus-start.el +++ b/lisp/gnus/gnus-start.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/gnus-sum.el b/lisp/gnus/gnus-sum.el index 9bdd0c66f56..e599a8460f3 100644 --- a/lisp/gnus/gnus-sum.el +++ b/lisp/gnus/gnus-sum.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -3992,7 +3992,7 @@ If SELECT-ARTICLES, only select those articles from GROUP." (spam-initialize)) ;; Save the active value in effect when the group was entered. (setq gnus-newsgroup-active - (gnus-copy-sequence + (copy-tree (gnus-active gnus-newsgroup-name))) (setq gnus-newsgroup-highest (cdr gnus-newsgroup-active)) ;; You can change the summary buffer in some way with this hook. @@ -5737,7 +5737,7 @@ If SELECT-ARTICLES, only select those articles from GROUP." (mail-header-number (car gnus-newsgroup-headers)) gnus-newsgroup-end (mail-header-number - (gnus-last-element gnus-newsgroup-headers)))) + (car (last gnus-newsgroup-headers))))) ;; GROUP is successfully selected. (or gnus-newsgroup-headers t))))) @@ -6076,12 +6076,12 @@ If SELECT-ARTICLES, only select those articles from GROUP." (del (gnus-list-range-intersection gnus-newsgroup-articles - (gnus-remove-from-range (gnus-copy-sequence old) list))) + (gnus-remove-from-range (copy-tree old) list))) (add (gnus-list-range-intersection gnus-newsgroup-articles (gnus-remove-from-range - (gnus-copy-sequence list) old)))) + (copy-tree list) old)))) (when add (push (list add 'add (list (cdr type))) delta-marks)) (when del @@ -6931,7 +6931,7 @@ displayed, no centering will be performed." (save-excursion ;; Take care of tree window mode. (if (get-buffer-window gnus-group-buffer 0) - (pop-to-buffer gnus-group-buffer) + (pop-to-buffer gnus-group-buffer t) (set-buffer gnus-group-buffer)) (gnus-group-jump-to-group newsgroup)))) @@ -9780,8 +9780,11 @@ If ARG is a negative number, hide the unwanted header lines." (inhibit-point-motion-hooks t) (hidden (if (numberp arg) (>= arg 0) - (or (not (looking-at "[^ \t\n]+:")) - (gnus-article-hidden-text-p 'headers)))) + (or + ;; The case where there's no visible header + ;; that matches `gnus-visible-headers'. + (looking-at "\n?\\'") + (gnus-article-hidden-text-p 'headers)))) s e) (delete-region (point-min) (point-max)) (with-current-buffer gnus-original-article-buffer @@ -9841,7 +9844,7 @@ IDNA encoded domain names looks like `xn--bar'. If a string remain unencoded after running this function, it is likely an invalid IDNA string (`xn--bar' is invalid). -You must have GNU Libidn (URL `http://www.gnu.org/software/libidn/') +You must have GNU Libidn (URL `https://www.gnu.org/software/libidn/') installed for this command to work." (interactive "P") (gnus-summary-select-article) @@ -10291,7 +10294,6 @@ latter case, they will be copied into the relevant groups." "Import an arbitrary file into a mail newsgroup." (interactive "fImport file: \nP") (let ((group gnus-newsgroup-name) - (now (current-time)) atts lines group-art) (unless (gnus-check-backend-function 'request-accept-article group) (error "%s does not support article importing" group)) @@ -10310,6 +10312,7 @@ latter case, they will be copied into the relevant groups." (goto-char (point-min)) (unless (re-search-forward "^date:" nil t) (goto-char (point-max)) + (setq atts (file-attributes file)) (insert "Date: " (message-make-date (nth 5 atts)) "\n"))) ;; This doesn't look like an article, so we fudge some headers. (setq atts (file-attributes file) @@ -11959,7 +11962,7 @@ Argument REVERSE means reverse order." (interactive "P") (gnus-summary-sort 'chars reverse)) -(defun gnus-summary-sort-by-mark (&optional reverse) +(defun gnus-summary-sort-by-marks (&optional reverse) "Sort the summary buffer by article marks. Argument REVERSE means reverse order." (interactive "P") @@ -12912,7 +12915,7 @@ returned." (mail-header-number (car gnus-newsgroup-headers)) gnus-newsgroup-end (mail-header-number - (gnus-last-element gnus-newsgroup-headers)))) + (car (last gnus-newsgroup-headers))))) (when gnus-use-scoring (gnus-possibly-score-headers)))) @@ -12999,7 +13002,7 @@ If ALL is a number, fetch this number of articles." i new) (unless new-active (error "Couldn't fetch new data")) - (setq gnus-newsgroup-active (gnus-copy-sequence new-active)) + (setq gnus-newsgroup-active (copy-tree new-active)) (setq i (cdr gnus-newsgroup-active) gnus-newsgroup-highest i) (while (> i old-high) diff --git a/lisp/gnus/gnus-topic.el b/lisp/gnus/gnus-topic.el index 6d6e20dc129..ba756e0314c 100644 --- a/lisp/gnus/gnus-topic.el +++ b/lisp/gnus/gnus-topic.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -220,6 +220,8 @@ If RECURSIVE is t, return groups in its subtopics too." ;; Check for permanent visibility. (and gnus-permanently-visible-groups (string-match gnus-permanently-visible-groups group)) + ;; Marked groups are always visible. + (member group gnus-group-marked) (memq 'visible params) (cdr (assq 'visible params))) ;; Add this group to the list of visible groups. diff --git a/lisp/gnus/gnus-undo.el b/lisp/gnus/gnus-undo.el index 74e0601c6e3..23cabadad6a 100644 --- a/lisp/gnus/gnus-undo.el +++ b/lisp/gnus/gnus-undo.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/gnus-util.el b/lisp/gnus/gnus-util.el index b509d8ad448..b7477a7fa80 100644 --- a/lisp/gnus/gnus-util.el +++ b/lisp/gnus/gnus-util.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -594,9 +594,6 @@ If N, return the Nth ancestor instead." (read-file-name "Copy file to: " default-directory))) (unless to (setq to (read-file-name "Copy file to: " default-directory))) - (when (file-directory-p to) - (setq to (concat (file-name-as-directory to) - (file-name-nondirectory file)))) (copy-file file to)) (defvar gnus-work-buffer " *gnus work*") diff --git a/lisp/gnus/gnus-uu.el b/lisp/gnus/gnus-uu.el index 62192173498..526d00754b7 100644 --- a/lisp/gnus/gnus-uu.el +++ b/lisp/gnus/gnus-uu.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/gnus-vm.el b/lisp/gnus/gnus-vm.el index 143500cc048..e05f849bb37 100644 --- a/lisp/gnus/gnus-vm.el +++ b/lisp/gnus/gnus-vm.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/gnus-win.el b/lisp/gnus/gnus-win.el index 255bb5f42eb..8e47ae3f984 100644 --- a/lisp/gnus/gnus-win.el +++ b/lisp/gnus/gnus-win.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/gnus.el b/lisp/gnus/gnus.el index d3edcd08513..3458fdea718 100644 --- a/lisp/gnus/gnus.el +++ b/lisp/gnus/gnus.el @@ -1,4 +1,4 @@ -;;; gnus.el --- a newsreader for GNU Emacs +;;; gnus.el --- a newsreader for GNU Emacs -*- lexical-binding:t -*- ;; Copyright (C) 1987-1990, 1993-1998, 2000-2017 Free Software ;; Foundation, Inc. @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -29,7 +29,7 @@ (run-hooks 'gnus-load-hook) -(eval-when-compile (require 'cl)) +(eval-when-compile (require 'cl-lib)) (require 'wid-edit) (require 'mm-util) (require 'nnheader) @@ -335,21 +335,6 @@ be set in `.emacs' instead." ;; We define these group faces here to avoid the display ;; update forced when creating new faces. -(defface gnus-group-news-1 - '((((class color) - (background dark)) - (:foreground "PaleTurquoise" :bold t)) - (((class color) - (background light)) - (:foreground "ForestGreen" :bold t)) - (t - ())) - "Level 1 newsgroup face." - :group 'gnus-group) -;; backward-compatibility alias -(put 'gnus-group-news-1-face 'face-alias 'gnus-group-news-1) -(put 'gnus-group-news-1-face 'obsolete-face "22.1") - (defface gnus-group-news-1-empty '((((class color) (background dark)) @@ -365,25 +350,18 @@ be set in `.emacs' instead." (put 'gnus-group-news-1-empty-face 'face-alias 'gnus-group-news-1-empty) (put 'gnus-group-news-1-empty-face 'obsolete-face "22.1") -(defface gnus-group-news-2 - '((((class color) - (background dark)) - (:foreground "turquoise" :bold t)) - (((class color) - (background light)) - (:foreground "CadetBlue4" :bold t)) - (t - ())) - "Level 2 newsgroup face." +(defface gnus-group-news-1 + '((t (:inherit gnus-group-news-1-empty :bold t))) + "Level 1 newsgroup face." :group 'gnus-group) ;; backward-compatibility alias -(put 'gnus-group-news-2-face 'face-alias 'gnus-group-news-2) -(put 'gnus-group-news-2-face 'obsolete-face "22.1") +(put 'gnus-group-news-1-face 'face-alias 'gnus-group-news-1) +(put 'gnus-group-news-1-face 'obsolete-face "22.1") (defface gnus-group-news-2-empty '((((class color) (background dark)) - (:foreground "turquoise")) + (:foreground "turquoise4")) (((class color) (background light)) (:foreground "CadetBlue4")) @@ -395,28 +373,21 @@ be set in `.emacs' instead." (put 'gnus-group-news-2-empty-face 'face-alias 'gnus-group-news-2-empty) (put 'gnus-group-news-2-empty-face 'obsolete-face "22.1") -(defface gnus-group-news-3 - '((((class color) - (background dark)) - (:bold t)) - (((class color) - (background light)) - (:bold t)) - (t - ())) - "Level 3 newsgroup face." +(defface gnus-group-news-2 + '((t (:inherit gnus-group-news-2-empty :bold t))) + "Level 2 newsgroup face." :group 'gnus-group) ;; backward-compatibility alias -(put 'gnus-group-news-3-face 'face-alias 'gnus-group-news-3) -(put 'gnus-group-news-3-face 'obsolete-face "22.1") +(put 'gnus-group-news-2-face 'face-alias 'gnus-group-news-2) +(put 'gnus-group-news-2-face 'obsolete-face "22.1") (defface gnus-group-news-3-empty '((((class color) (background dark)) - ()) + (:foreground "turquoise3")) (((class color) (background light)) - ()) + (:foreground "DeepSkyBlue4")) (t ())) "Level 3 empty newsgroup face." @@ -425,28 +396,21 @@ be set in `.emacs' instead." (put 'gnus-group-news-3-empty-face 'face-alias 'gnus-group-news-3-empty) (put 'gnus-group-news-3-empty-face 'obsolete-face "22.1") -(defface gnus-group-news-4 - '((((class color) - (background dark)) - (:bold t)) - (((class color) - (background light)) - (:bold t)) - (t - ())) - "Level 4 newsgroup face." +(defface gnus-group-news-3 + '((t (:inherit gnus-group-news-3-empty :bold t))) + "Level 3 newsgroup face." :group 'gnus-group) ;; backward-compatibility alias -(put 'gnus-group-news-4-face 'face-alias 'gnus-group-news-4) -(put 'gnus-group-news-4-face 'obsolete-face "22.1") +(put 'gnus-group-news-3-face 'face-alias 'gnus-group-news-3) +(put 'gnus-group-news-3-face 'obsolete-face "22.1") (defface gnus-group-news-4-empty '((((class color) (background dark)) - ()) + (:foreground "turquoise2")) (((class color) (background light)) - ()) + (:foreground "DeepSkyBlue3")) (t ())) "Level 4 empty newsgroup face." @@ -455,28 +419,21 @@ be set in `.emacs' instead." (put 'gnus-group-news-4-empty-face 'face-alias 'gnus-group-news-4-empty) (put 'gnus-group-news-4-empty-face 'obsolete-face "22.1") -(defface gnus-group-news-5 - '((((class color) - (background dark)) - (:bold t)) - (((class color) - (background light)) - (:bold t)) - (t - ())) - "Level 5 newsgroup face." +(defface gnus-group-news-4 + '((t (:inherit gnus-group-news-4-empty :bold t))) + "Level 4 newsgroup face." :group 'gnus-group) ;; backward-compatibility alias -(put 'gnus-group-news-5-face 'face-alias 'gnus-group-news-5) -(put 'gnus-group-news-5-face 'obsolete-face "22.1") +(put 'gnus-group-news-4-face 'face-alias 'gnus-group-news-4) +(put 'gnus-group-news-4-face 'obsolete-face "22.1") (defface gnus-group-news-5-empty '((((class color) (background dark)) - ()) + (:foreground "turquoise1")) (((class color) (background light)) - ()) + (:foreground "DeepSkyBlue2")) (t ())) "Level 5 empty newsgroup face." @@ -485,20 +442,13 @@ be set in `.emacs' instead." (put 'gnus-group-news-5-empty-face 'face-alias 'gnus-group-news-5-empty) (put 'gnus-group-news-5-empty-face 'obsolete-face "22.1") -(defface gnus-group-news-6 - '((((class color) - (background dark)) - (:bold t)) - (((class color) - (background light)) - (:bold t)) - (t - ())) - "Level 6 newsgroup face." +(defface gnus-group-news-5 + '((t (:inherit gnus-group-news-5-empty :bold t))) + "Level 5 newsgroup face." :group 'gnus-group) ;; backward-compatibility alias -(put 'gnus-group-news-6-face 'face-alias 'gnus-group-news-6) -(put 'gnus-group-news-6-face 'obsolete-face "22.1") +(put 'gnus-group-news-5-face 'face-alias 'gnus-group-news-5) +(put 'gnus-group-news-5-face 'obsolete-face "22.1") (defface gnus-group-news-6-empty '((((class color) @@ -515,20 +465,13 @@ be set in `.emacs' instead." (put 'gnus-group-news-6-empty-face 'face-alias 'gnus-group-news-6-empty) (put 'gnus-group-news-6-empty-face 'obsolete-face "22.1") -(defface gnus-group-news-low - '((((class color) - (background dark)) - (:foreground "DarkTurquoise" :bold t)) - (((class color) - (background light)) - (:foreground "DarkGreen" :bold t)) - (t - ())) - "Low level newsgroup face." +(defface gnus-group-news-6 + '((t (:inherit gnus-group-news-6-empty :bold t))) + "Level 6 newsgroup face." :group 'gnus-group) ;; backward-compatibility alias -(put 'gnus-group-news-low-face 'face-alias 'gnus-group-news-low) -(put 'gnus-group-news-low-face 'obsolete-face "22.1") +(put 'gnus-group-news-6-face 'face-alias 'gnus-group-news-6) +(put 'gnus-group-news-6-face 'obsolete-face "22.1") (defface gnus-group-news-low-empty '((((class color) @@ -545,20 +488,13 @@ be set in `.emacs' instead." (put 'gnus-group-news-low-empty-face 'face-alias 'gnus-group-news-low-empty) (put 'gnus-group-news-low-empty-face 'obsolete-face "22.1") -(defface gnus-group-mail-1 - '((((class color) - (background dark)) - (:foreground "#e1ffe1" :bold t)) - (((class color) - (background light)) - (:foreground "DeepPink3" :bold t)) - (t - (:bold t))) - "Level 1 mailgroup face." +(defface gnus-group-news-low + '((t (:inherit gnus-group-news-low-empty :bold t))) + "Low level newsgroup face." :group 'gnus-group) ;; backward-compatibility alias -(put 'gnus-group-mail-1-face 'face-alias 'gnus-group-mail-1) -(put 'gnus-group-mail-1-face 'obsolete-face "22.1") +(put 'gnus-group-news-low-face 'face-alias 'gnus-group-news-low) +(put 'gnus-group-news-low-face 'obsolete-face "22.1") (defface gnus-group-mail-1-empty '((((class color) @@ -568,27 +504,20 @@ be set in `.emacs' instead." (background light)) (:foreground "DeepPink3")) (t - (:italic t :bold t))) + (:italic t))) "Level 1 empty mailgroup face." :group 'gnus-group) ;; backward-compatibility alias (put 'gnus-group-mail-1-empty-face 'face-alias 'gnus-group-mail-1-empty) (put 'gnus-group-mail-1-empty-face 'obsolete-face "22.1") -(defface gnus-group-mail-2 - '((((class color) - (background dark)) - (:foreground "DarkSeaGreen1" :bold t)) - (((class color) - (background light)) - (:foreground "HotPink3" :bold t)) - (t - (:bold t))) - "Level 2 mailgroup face." +(defface gnus-group-mail-1 + '((t (:inherit gnus-group-mail-1-empty :bold t))) + "Level 1 mailgroup face." :group 'gnus-group) ;; backward-compatibility alias -(put 'gnus-group-mail-2-face 'face-alias 'gnus-group-mail-2) -(put 'gnus-group-mail-2-face 'obsolete-face "22.1") +(put 'gnus-group-mail-1-face 'face-alias 'gnus-group-mail-1) +(put 'gnus-group-mail-1-face 'obsolete-face "22.1") (defface gnus-group-mail-2-empty '((((class color) @@ -598,27 +527,20 @@ be set in `.emacs' instead." (background light)) (:foreground "HotPink3")) (t - (:bold t))) + (:italic t))) "Level 2 empty mailgroup face." :group 'gnus-group) ;; backward-compatibility alias (put 'gnus-group-mail-2-empty-face 'face-alias 'gnus-group-mail-2-empty) (put 'gnus-group-mail-2-empty-face 'obsolete-face "22.1") -(defface gnus-group-mail-3 - '((((class color) - (background dark)) - (:foreground "aquamarine1" :bold t)) - (((class color) - (background light)) - (:foreground "magenta4" :bold t)) - (t - (:bold t))) - "Level 3 mailgroup face." +(defface gnus-group-mail-2 + '((t (:inherit gnus-group-mail-2-empty :bold t))) + "Level 2 mailgroup face." :group 'gnus-group) ;; backward-compatibility alias -(put 'gnus-group-mail-3-face 'face-alias 'gnus-group-mail-3) -(put 'gnus-group-mail-3-face 'obsolete-face "22.1") +(put 'gnus-group-mail-2-face 'face-alias 'gnus-group-mail-2) +(put 'gnus-group-mail-2-face 'obsolete-face "22.1") (defface gnus-group-mail-3-empty '((((class color) @@ -635,20 +557,13 @@ be set in `.emacs' instead." (put 'gnus-group-mail-3-empty-face 'face-alias 'gnus-group-mail-3-empty) (put 'gnus-group-mail-3-empty-face 'obsolete-face "22.1") -(defface gnus-group-mail-low - '((((class color) - (background dark)) - (:foreground "aquamarine2" :bold t)) - (((class color) - (background light)) - (:foreground "DeepPink4" :bold t)) - (t - (:bold t))) - "Low level mailgroup face." +(defface gnus-group-mail-3 + '((t (:inherit gnus-group-mail-3-empty :bold t))) + "Level 3 mailgroup face." :group 'gnus-group) ;; backward-compatibility alias -(put 'gnus-group-mail-low-face 'face-alias 'gnus-group-mail-low) -(put 'gnus-group-mail-low-face 'obsolete-face "22.1") +(put 'gnus-group-mail-3-face 'face-alias 'gnus-group-mail-3) +(put 'gnus-group-mail-3-face 'obsolete-face "22.1") (defface gnus-group-mail-low-empty '((((class color) @@ -665,6 +580,14 @@ be set in `.emacs' instead." (put 'gnus-group-mail-low-empty-face 'face-alias 'gnus-group-mail-low-empty) (put 'gnus-group-mail-low-empty-face 'obsolete-face "22.1") +(defface gnus-group-mail-low + '((t (:inherit gnus-group-mail-low-empty :bold t))) + "Low level mailgroup face." + :group 'gnus-group) +;; backward-compatibility alias +(put 'gnus-group-mail-low-face 'face-alias 'gnus-group-mail-low) +(put 'gnus-group-mail-low-face 'obsolete-face "22.1") + ;; Summary mode faces. (defface gnus-summary-selected '((t (:underline t))) @@ -683,15 +606,23 @@ be set in `.emacs' instead." (put 'gnus-summary-cancelled-face 'face-alias 'gnus-summary-cancelled) (put 'gnus-summary-cancelled-face 'obsolete-face "22.1") -(defface gnus-summary-high-ticked +(defface gnus-summary-normal-ticked '((((class color) (background dark)) - (:foreground "pink" :bold t)) + (:foreground "pink")) (((class color) (background light)) - (:foreground "firebrick" :bold t)) + (:foreground "firebrick")) (t - (:bold t))) + ())) + "Face used for normal interest ticked articles." + :group 'gnus-summary) +;; backward-compatibility alias +(put 'gnus-summary-normal-ticked-face 'face-alias 'gnus-summary-normal-ticked) +(put 'gnus-summary-normal-ticked-face 'obsolete-face "22.1") + +(defface gnus-summary-high-ticked + '((t (:inherit gnus-summary-normal-ticked :bold t))) "Face used for high interest ticked articles." :group 'gnus-summary) ;; backward-compatibility alias @@ -699,44 +630,30 @@ be set in `.emacs' instead." (put 'gnus-summary-high-ticked-face 'obsolete-face "22.1") (defface gnus-summary-low-ticked - '((((class color) - (background dark)) - (:foreground "pink" :italic t)) - (((class color) - (background light)) - (:foreground "firebrick" :italic t)) - (t - (:italic t))) + '((t (:inherit gnus-summary-normal-ticked :italic t))) "Face used for low interest ticked articles." :group 'gnus-summary) ;; backward-compatibility alias (put 'gnus-summary-low-ticked-face 'face-alias 'gnus-summary-low-ticked) (put 'gnus-summary-low-ticked-face 'obsolete-face "22.1") -(defface gnus-summary-normal-ticked +(defface gnus-summary-normal-ancient '((((class color) (background dark)) - (:foreground "pink")) + (:foreground "SkyBlue")) (((class color) (background light)) - (:foreground "firebrick")) + (:foreground "RoyalBlue")) (t ())) - "Face used for normal interest ticked articles." + "Face used for normal interest ancient articles." :group 'gnus-summary) ;; backward-compatibility alias -(put 'gnus-summary-normal-ticked-face 'face-alias 'gnus-summary-normal-ticked) -(put 'gnus-summary-normal-ticked-face 'obsolete-face "22.1") +(put 'gnus-summary-normal-ancient-face 'face-alias 'gnus-summary-normal-ancient) +(put 'gnus-summary-normal-ancient-face 'obsolete-face "22.1") (defface gnus-summary-high-ancient - '((((class color) - (background dark)) - (:foreground "SkyBlue" :bold t)) - (((class color) - (background light)) - (:foreground "RoyalBlue" :bold t)) - (t - (:bold t))) + '((t (:inherit gnus-summary-normal-ancient :bold t))) "Face used for high interest ancient articles." :group 'gnus-summary) ;; backward-compatibility alias @@ -744,42 +661,28 @@ be set in `.emacs' instead." (put 'gnus-summary-high-ancient-face 'obsolete-face "22.1") (defface gnus-summary-low-ancient - '((((class color) - (background dark)) - (:foreground "SkyBlue" :italic t)) - (((class color) - (background light)) - (:foreground "RoyalBlue" :italic t)) - (t - (:italic t))) + '((t (:inherit gnus-summary-normal-ancient :italic t))) "Face used for low interest ancient articles." :group 'gnus-summary) ;; backward-compatibility alias (put 'gnus-summary-low-ancient-face 'face-alias 'gnus-summary-low-ancient) (put 'gnus-summary-low-ancient-face 'obsolete-face "22.1") -(defface gnus-summary-normal-ancient - '((((class color) - (background dark)) - (:foreground "SkyBlue")) - (((class color) - (background light)) - (:foreground "RoyalBlue")) - (t - ())) - "Face used for normal interest ancient articles." +(defface gnus-summary-normal-undownloaded + '((((class color) + (background light)) + (:foreground "cyan4" :bold nil)) + (((class color) (background dark)) + (:foreground "LightGray" :bold nil)) + (t (:inverse-video t))) + "Face used for normal interest uncached articles." :group 'gnus-summary) ;; backward-compatibility alias -(put 'gnus-summary-normal-ancient-face 'face-alias 'gnus-summary-normal-ancient) -(put 'gnus-summary-normal-ancient-face 'obsolete-face "22.1") +(put 'gnus-summary-normal-undownloaded-face 'face-alias 'gnus-summary-normal-undownloaded) +(put 'gnus-summary-normal-undownloaded-face 'obsolete-face "22.1") (defface gnus-summary-high-undownloaded - '((((class color) - (background light)) - (:bold t :foreground "cyan4")) - (((class color) (background dark)) - (:bold t :foreground "LightGray")) - (t (:inverse-video t :bold t))) + '((t (:inherit gnus-summary-normal-undownloaded :bold t))) "Face used for high interest uncached articles." :group 'gnus-summary) ;; backward-compatibility alias @@ -787,34 +690,24 @@ be set in `.emacs' instead." (put 'gnus-summary-high-undownloaded-face 'obsolete-face "22.1") (defface gnus-summary-low-undownloaded - '((((class color) - (background light)) - (:italic t :foreground "cyan4" :bold nil)) - (((class color) (background dark)) - (:italic t :foreground "LightGray" :bold nil)) - (t (:inverse-video t :italic t))) + '((t (:inherit gnus-summary-normal-undownloaded :italic t))) "Face used for low interest uncached articles." :group 'gnus-summary) ;; backward-compatibility alias (put 'gnus-summary-low-undownloaded-face 'face-alias 'gnus-summary-low-undownloaded) (put 'gnus-summary-low-undownloaded-face 'obsolete-face "22.1") -(defface gnus-summary-normal-undownloaded - '((((class color) - (background light)) - (:foreground "cyan4" :bold nil)) - (((class color) (background dark)) - (:foreground "LightGray" :bold nil)) - (t (:inverse-video t))) - "Face used for normal interest uncached articles." +(defface gnus-summary-normal-unread + '((t + ())) + "Face used for normal interest unread articles." :group 'gnus-summary) ;; backward-compatibility alias -(put 'gnus-summary-normal-undownloaded-face 'face-alias 'gnus-summary-normal-undownloaded) -(put 'gnus-summary-normal-undownloaded-face 'obsolete-face "22.1") +(put 'gnus-summary-normal-unread-face 'face-alias 'gnus-summary-normal-unread) +(put 'gnus-summary-normal-unread-face 'obsolete-face "22.1") (defface gnus-summary-high-unread - '((t - (:bold t))) + '((t (:inherit gnus-summary-normal-unread :bold t))) "Face used for high interest unread articles." :group 'gnus-summary) ;; backward-compatibility alias @@ -822,34 +715,30 @@ be set in `.emacs' instead." (put 'gnus-summary-high-unread-face 'obsolete-face "22.1") (defface gnus-summary-low-unread - '((t - (:italic t))) + '((t (:inherit gnus-summary-normal-unread :italic t))) "Face used for low interest unread articles." :group 'gnus-summary) ;; backward-compatibility alias (put 'gnus-summary-low-unread-face 'face-alias 'gnus-summary-low-unread) (put 'gnus-summary-low-unread-face 'obsolete-face "22.1") -(defface gnus-summary-normal-unread - '((t - ())) - "Face used for normal interest unread articles." - :group 'gnus-summary) -;; backward-compatibility alias -(put 'gnus-summary-normal-unread-face 'face-alias 'gnus-summary-normal-unread) -(put 'gnus-summary-normal-unread-face 'obsolete-face "22.1") - -(defface gnus-summary-high-read +(defface gnus-summary-normal-read '((((class color) (background dark)) - (:foreground "PaleGreen" - :bold t)) + (:foreground "PaleGreen")) (((class color) (background light)) - (:foreground "DarkGreen" - :bold t)) + (:foreground "DarkGreen")) (t - (:bold t))) + ())) + "Face used for normal interest read articles." + :group 'gnus-summary) +;; backward-compatibility alias +(put 'gnus-summary-normal-read-face 'face-alias 'gnus-summary-normal-read) +(put 'gnus-summary-normal-read-face 'obsolete-face "22.1") + +(defface gnus-summary-high-read + '((t (:inherit gnus-summary-normal-read :bold t))) "Face used for high interest read articles." :group 'gnus-summary) ;; backward-compatibility alias @@ -857,37 +746,13 @@ be set in `.emacs' instead." (put 'gnus-summary-high-read-face 'obsolete-face "22.1") (defface gnus-summary-low-read - '((((class color) - (background dark)) - (:foreground "PaleGreen" - :italic t)) - (((class color) - (background light)) - (:foreground "DarkGreen" - :italic t)) - (t - (:italic t))) + '((t (:inherit gnus-summary-normal-read :italic t))) "Face used for low interest read articles." :group 'gnus-summary) ;; backward-compatibility alias (put 'gnus-summary-low-read-face 'face-alias 'gnus-summary-low-read) (put 'gnus-summary-low-read-face 'obsolete-face "22.1") -(defface gnus-summary-normal-read - '((((class color) - (background dark)) - (:foreground "PaleGreen")) - (((class color) - (background light)) - (:foreground "DarkGreen")) - (t - ())) - "Face used for normal interest read articles." - :group 'gnus-summary) -;; backward-compatibility alias -(put 'gnus-summary-normal-read-face 'face-alias 'gnus-summary-normal-read) -(put 'gnus-summary-normal-read-face 'obsolete-face "22.1") - ;;; ;;; Gnus buffers @@ -1106,12 +971,11 @@ be set in `.emacs' instead." (cons (car list) (list :type type :data data))) list))) -(eval-when (load) - (let ((command (format "%s" this-command))) - (when (string-match "gnus" command) - (if (string-match "gnus-other-frame" command) - (gnus-get-buffer-create gnus-group-buffer) - (gnus-splash))))) +(let ((command (format "%s" this-command))) + (when (string-match "gnus" command) + (if (eq 'gnus-other-frame this-command) + (gnus-get-buffer-create gnus-group-buffer) + (gnus-splash)))) ;;; Do the rest. @@ -2479,7 +2343,7 @@ Disabling the agent may result in noticeable loss of performance." :group 'gnus-agent :type 'boolean) -(defcustom gnus-other-frame-function 'gnus +(defcustom gnus-other-frame-function #'gnus "Function called by the command `gnus-other-frame' when starting Gnus." :group 'gnus-start :type '(choice (function-item gnus) @@ -2487,7 +2351,9 @@ Disabling the agent may result in noticeable loss of performance." (function-item gnus-slave) (function-item gnus-slave-no-server))) -(defcustom gnus-other-frame-resume-function 'gnus-group-get-new-news +(declare-function gnus-group-get-new-news "gnus-group") + +(defcustom gnus-other-frame-resume-function #'gnus-group-get-new-news "Function called by the command `gnus-other-frame' when resuming Gnus." :version "24.4" :group 'gnus-start @@ -2555,7 +2421,7 @@ a string, be sure to use a valid format, see RFC 2616." ) (defvar gnus-agent-target-move-group-header "X-Gnus-Agent-Move-To") (defvar gnus-draft-meta-information-header "X-Draft-From") -(defvar gnus-group-get-parameter-function 'gnus-group-get-parameter) +(defvar gnus-group-get-parameter-function #'gnus-group-get-parameter) (defvar gnus-original-article-buffer " *Original Article*") (defvar gnus-newsgroup-name nil) (defvar gnus-ephemeral-servers nil) @@ -2592,7 +2458,9 @@ a string, be sure to use a valid format, see RFC 2616." (defvar gnus-group-history nil) (defvar gnus-server-alist nil - "List of available servers.") + "Servers created by Gnus, or via the server buffer. +Servers defined in the user's config files do not appear here. +This variable is persisted in the user's .newsrc.eld file.") (defcustom gnus-cache-directory (nnheader-concat gnus-directory "cache/") @@ -2755,7 +2623,6 @@ gnus-registry.el will populate this if it's loaded.") (nthcdr 3 package) (cdr package))))) '(("info" :interactive t Info-goto-node) - ("pp" pp-to-string) ("qp" quoted-printable-decode-region quoted-printable-decode-string) ("ps-print" ps-print-preprint) ("message" :interactive t @@ -2902,7 +2769,6 @@ gnus-registry.el will populate this if it's loaded.") gnus-check-reasonable-setup) ("gnus-dup" gnus-dup-suppress-articles gnus-dup-unsuppress-article gnus-dup-enter-articles) - ("gnus-range" gnus-copy-sequence) ("gnus-eform" gnus-edit-form) ("gnus-logic" gnus-score-advanced) ("gnus-undo" gnus-undo-mode gnus-undo-register) @@ -3179,9 +3045,9 @@ with a `subscribed' parameter." (or (gnus-group-fast-parameter group 'to-address) (gnus-group-fast-parameter group 'to-list)))) (when address - (add-to-list 'addresses address)))) + (cl-pushnew address addresses :test #'equal)))) (when addresses - (list (mapconcat 'regexp-quote addresses "\\|"))))) + (list (mapconcat #'regexp-quote addresses "\\|"))))) (defmacro gnus-string-or (&rest strings) "Return the first element of STRINGS that is a non-blank string. @@ -3234,6 +3100,8 @@ If ARG, insert string at point." minor least) (format "%d.%02d%02d" major minor least)))))) +(defvar gnus-info-buffer) + (defun gnus-info-find-node (&optional nodename) "Find Info documentation of Gnus." (interactive) @@ -3253,7 +3121,7 @@ If ARG, insert string at point." (defvar gnus-current-prefix-symbols nil "List of current prefix symbols.") -(defun gnus-interactive (string &optional params) +(defun gnus-interactive (string) "Return a list that can be fed to `interactive'. See `interactive' for full documentation. @@ -3345,9 +3213,9 @@ g -- Group name." (setq out (delq 'gnus-prefix-nil out)) (nreverse out))) -(defun gnus-symbolic-argument (&optional arg) +(defun gnus-symbolic-argument () "Read a symbolic argument and a command, and then execute command." - (interactive "P") + (interactive) (let* ((in-command (this-command-keys)) (command in-command) gnus-current-prefix-symbols @@ -3463,16 +3331,15 @@ that that variable is buffer-local to the summary buffers." (throw 'server-name (car name-method)))) gnus-server-method-cache)) - (mapc - (lambda (server-alist) - (mapc (lambda (name-method) - (when (gnus-methods-equal-p (cdr name-method) method) - (unless (member name-method gnus-server-method-cache) - (push name-method gnus-server-method-cache)) - (throw 'server-name (car name-method)))) - server-alist)) - (list gnus-server-alist - gnus-predefined-server-alist)) + (dolist (server-alist + (list gnus-server-alist + gnus-predefined-server-alist)) + (mapc (lambda (name-method) + (when (gnus-methods-equal-p (cdr name-method) method) + (unless (member name-method gnus-server-method-cache) + (push name-method gnus-server-method-cache)) + (throw 'server-name (car name-method)))) + server-alist)) (let* ((name (if (member (cadr method) '(nil "")) (format "%s" (car method)) @@ -3574,26 +3441,26 @@ that that variable is buffer-local to the summary buffers." (let ((p1 (copy-sequence (cddr m1))) (p2 (copy-sequence (cddr m2))) e1 e2) - (block nil + (cl-block nil (while (setq e1 (pop p1)) (unless (setq e2 (assq (car e1) p2)) ;; The parameter doesn't exist in p2. - (return nil)) + (cl-return nil)) (setq p2 (delq e2 p2)) (unless (equal e1 e2) (if (not (and (stringp (cadr e1)) (stringp (cadr e2)))) - (return nil) + (cl-return nil) ;; Special-case string parameter comparison so that we ;; can uniquify them. (let ((s1 (cadr e1)) (s2 (cadr e2))) - (when (string-match "/$" s1) + (when (string-match "/\\'" s1) (setq s1 (directory-file-name s1))) - (when (string-match "/$" s2) + (when (string-match "/\\'" s2) (setq s2 (directory-file-name s2))) (unless (equal s1 s2) - (return nil)))))) + (cl-return nil)))))) ;; If p2 now is empty, they were equal. (null p2)))) @@ -3981,8 +3848,7 @@ If SCORE is nil, add 1 to the score of GROUP." "Collapse GROUP name LEVELS. Select methods are stripped and any remote host name is stripped down to just the host name." - (let* ((name "") - (foreign "") + (let* ((foreign "") (depth 0) (skip 1) (levels (or levels @@ -4024,13 +3890,13 @@ just the host name." gsep ".")) (setq levels (- glen levels)) (dolist (g glist) - (push (if (>= (decf levels) 0) + (push (if (>= (cl-decf levels) 0) (if (zerop (length g)) "" (substring g 0 1)) g) res)) - (concat foreign (mapconcat 'identity (nreverse res) gsep)))))) + (concat foreign (mapconcat #'identity (nreverse res) gsep)))))) (defun gnus-narrow-to-body () "Narrow to the body of an article." @@ -4272,7 +4138,7 @@ Allow completion over sensible values." gnus-server-alist)) (method (gnus-completing-read - prompt (mapcar 'car servers) + prompt (mapcar #'car servers) t nil 'gnus-method-history))) (cond ((equal method "") @@ -4385,13 +4251,13 @@ current display is used." (progn (switch-to-buffer gnus-group-buffer) (funcall gnus-other-frame-resume-function arg)) (funcall gnus-other-frame-function arg) - (add-hook 'gnus-exit-gnus-hook 'gnus-delete-gnus-frame) + (add-hook 'gnus-exit-gnus-hook #'gnus-delete-gnus-frame) ;; One might argue that `gnus-delete-gnus-frame' should not be called ;; from `gnus-suspend-gnus-hook', but, on the other hand, one might ;; argue that it should. No matter what you think, for the sake of ;; those who want it to be called from it, please keep (defun ;; gnus-delete-gnus-frame) even if you remove the next `add-hook'. - (add-hook 'gnus-suspend-gnus-hook 'gnus-delete-gnus-frame))))) + (add-hook 'gnus-suspend-gnus-hook #'gnus-delete-gnus-frame))))) ;;;###autoload (defun gnus (&optional arg dont-connect slave) diff --git a/lisp/gnus/gssapi.el b/lisp/gnus/gssapi.el index b569c7f16c6..b6801f78852 100644 --- a/lisp/gnus/gssapi.el +++ b/lisp/gnus/gssapi.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/legacy-gnus-agent.el b/lisp/gnus/legacy-gnus-agent.el index e51181ef5f8..84db6c3528b 100644 --- a/lisp/gnus/legacy-gnus-agent.el +++ b/lisp/gnus/legacy-gnus-agent.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/mail-source.el b/lisp/gnus/mail-source.el index e15d820a274..93f03be72d0 100644 --- a/lisp/gnus/mail-source.el +++ b/lisp/gnus/mail-source.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -591,25 +591,21 @@ Return the number of files that were found." If CONFIRM is non-nil, ask for confirmation before removing a file." (interactive "P") (require 'gnus-util) - (let* ((high2days (/ 65536.0 60 60 24));; convert high bits to days - (low2days (/ 1.0 65536.0)) ;; convert low bits to days + (let* ((now (current-time)) (diff (if (natnump age) age 30));; fallback, if no valid AGE given - currday files) + files) (setq files (directory-files mail-source-directory t (concat "\\`" - (regexp-quote mail-source-incoming-file-prefix))) - currday (* (car (current-time)) high2days) - currday (+ currday (* low2days (nth 1 (current-time))))) + (regexp-quote mail-source-incoming-file-prefix)))) (while files (let* ((ffile (car files)) (bfile (replace-regexp-in-string "\\`.*/\\([^/]+\\)\\'" "\\1" ffile)) - (filetime (nth 5 (file-attributes ffile))) - (fileday (* (car filetime) high2days)) - (fileday (+ fileday (* low2days (nth 1 filetime))))) + (filetime (nth 5 (file-attributes ffile)))) (setq files (cdr files)) - (when (and (> (- currday fileday) diff) + (when (and (> (time-to-number-of-days (time-subtract now filetime)) + diff) (if confirm (y-or-n-p (format-message "\ diff --git a/lisp/gnus/message.el b/lisp/gnus/message.el index 0f8fdfc9c7f..0f99cb697dc 100644 --- a/lisp/gnus/message.el +++ b/lisp/gnus/message.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -28,8 +28,7 @@ ;;; Code: -(eval-when-compile - (require 'cl)) +(eval-when-compile (require 'cl-lib)) (require 'mailheader) (require 'gmm-utils) @@ -49,7 +48,8 @@ (require 'mm-util) (require 'rfc2047) (require 'puny) -(require 'subr-x) ; read-multiple-choice +(require 'rmc) ; read-multiple-choice +(eval-when-compile (require 'subr-x)) ; when-let* (autoload 'mailclient-send-it "mailclient") @@ -306,7 +306,7 @@ any confusion." (defcustom message-subject-trailing-was-query t "What to do with trailing \"(was: <old subject>)\" in subject lines. If nil, leave the subject unchanged. If it is the symbol `ask', query -the user what do do. In this case, the subject is matched against +the user what to do. In this case, the subject is matched against `message-subject-trailing-was-ask-regexp'. If `message-subject-trailing-was-query' is t, always strip the trailing old subject. In this case, `message-subject-trailing-was-regexp' is @@ -991,7 +991,6 @@ are replaced: %F The first name if present, e.g.: \"John\", else fall back to the mail address. %L The last name if present, e.g.: \"Doe\". - %Z, %z The time zone in the numeric form, e.g.:\"+0000\". All other format specifiers are passed to `format-time-string' which is called using the date from the article your replying to, but @@ -1434,7 +1433,7 @@ starting with `not' and followed by regexps." (:foreground "MidnightBlue" :bold t)) (t (:bold t :italic t))) - "Face used for displaying From headers." + "Face used for displaying To headers." :group 'message-faces) ;; backward-compatibility alias (put 'message-header-to-face 'face-alias 'message-header-to) @@ -1464,7 +1463,7 @@ starting with `not' and followed by regexps." (:foreground "navy blue" :bold t)) (t (:bold t))) - "Face used for displaying subject headers." + "Face used for displaying Subject headers." :group 'message-faces) ;; backward-compatibility alias (put 'message-header-subject-face 'face-alias 'message-header-subject) @@ -1479,7 +1478,7 @@ starting with `not' and followed by regexps." (:foreground "blue4" :bold t :italic t)) (t (:bold t :italic t))) - "Face used for displaying newsgroups headers." + "Face used for displaying Newsgroups headers." :group 'message-faces) ;; backward-compatibility alias (put 'message-header-newsgroups-face 'face-alias 'message-header-newsgroups) @@ -1494,7 +1493,7 @@ starting with `not' and followed by regexps." (:foreground "steel blue")) (t (:bold t :italic t))) - "Face used for displaying newsgroups headers." + "Face used for displaying other headers." :group 'message-faces) ;; backward-compatibility alias (put 'message-header-other-face 'face-alias 'message-header-other) @@ -2325,7 +2324,7 @@ With prefix-argument just set Follow-Up, don't cross-post." (setq message-cross-post-old-target target-group)) (defun message-cross-post-insert-note (target-group cross-post in-old - old-groups) + _old-groups) "Insert a in message body note about a set Followup or Crosspost. If there have been previous notes, delete them. TARGET-GROUP specifies the group to Followup-To. When CROSS-POST is t, insert note about @@ -2444,7 +2443,7 @@ Return the number of headers removed." (not (looking-at regexp)) (looking-at regexp)) (progn - (incf number) + (cl-incf number) (when first (setq last t)) (delete-region @@ -2469,10 +2468,10 @@ Return the number of headers removed." (save-excursion (goto-char (point-min)) (while (re-search-forward regexp nil t) - (incf count))) + (cl-incf count))) (while (> count 1) (message-remove-header header nil t) - (decf count)))) + (cl-decf count)))) (defun message-narrow-to-headers () "Narrow the buffer to the head of the message." @@ -2843,7 +2842,7 @@ These properties are essential to work, so we should never strip them." (eq message-mail-alias-type type) (memq type message-mail-alias-type))) -(defun message-strip-forbidden-properties (begin end &optional old-length) +(defun message-strip-forbidden-properties (begin end &optional _old-length) "Strip forbidden properties between BEGIN and END, ignoring the third arg. This function is intended to be called from `after-change-functions'. See also `message-forbidden-properties'." @@ -3227,13 +3226,13 @@ or in the synonym headers, defined by `message-header-synonyms'." (dolist (header headers) (let* ((header-name (symbol-name (car header))) (new-header (cdr header)) - (synonyms (loop for synonym in message-header-synonyms - when (memq (car header) synonym) return synonym)) + (synonyms (cl-loop for synonym in message-header-synonyms + when (memq (car header) synonym) return synonym)) (old-header - (loop for synonym in synonyms - for old-header = (mail-fetch-field (symbol-name synonym)) - when (and old-header (string-match new-header old-header)) - return synonym))) + (cl-loop for synonym in synonyms + for old-header = (mail-fetch-field (symbol-name synonym)) + when (and old-header (string-match new-header old-header)) + return synonym))) (if old-header (message "already have `%s' in `%s'" new-header old-header) (when (and (message-position-on-field header-name) @@ -3593,7 +3592,7 @@ text was killed." "Create a rot table with offset N." (let ((i -1) (table (make-string 256 0))) - (while (< (incf i) 256) + (while (< (cl-incf i) 256) (aset table i i)) (concat (substring table 0 ?A) @@ -3761,13 +3760,13 @@ To use this automatically, you may add this function to (goto-char (mark t)) (insert-before-markers ?\n) (goto-char pt)))) - (case message-cite-reply-position - (above + (pcase message-cite-reply-position + ('above (message-goto-body) (insert body-text) (insert (if (bolp) "\n" "\n\n")) (message-goto-body)) - (below + ('below (message-goto-signature))) ;; Add a `message-setup-very-last-hook' here? ;; Add `gnus-article-highlight-citation' here? @@ -4095,7 +4094,7 @@ Instead, just auto-save the buffer and then bury it." "Bury this mail BUFFER." ;; Note that this is not quite the same as (bury-buffer buffer), ;; since bury-buffer does extra stuff with a nil argument. - ;; Eg http://lists.gnu.org/archive/html/emacs-devel/2014-01/msg00539.html + ;; Eg https://lists.gnu.org/r/emacs-devel/2014-01/msg00539.html (with-current-buffer buffer (bury-buffer)) (if message-return-action (apply (car message-return-action) (cdr message-return-action)))) @@ -4346,7 +4345,7 @@ conformance." RECIPIENTS is a mail header. Return a list of potentially bogus addresses. If none is found, return nil. -An address might be bogus if if there's a matching entry in +An address might be bogus if there's a matching entry in `message-bogus-addresses'." ;; FIXME: How about "foo@subdomain", when the MTA adds ".domain.tld"? (let (found) @@ -4390,7 +4389,7 @@ This function could be useful in `message-setup-hook'." (if (string= encoded bog) "" (format " (%s)" encoded)))))) - (error "Bogus address")))))))) + (user-error "Bogus address")))))))) (custom-add-option 'message-setup-hook 'message-check-recipients) @@ -4612,9 +4611,9 @@ This function could be useful in `message-setup-hook'." (with-current-buffer mailbuf message-courtesy-message))) ;; Let's make sure we encoded all the body. - (assert (save-excursion - (goto-char (point-min)) - (not (re-search-forward "[^\000-\377]" nil t)))) + (cl-assert (save-excursion + (goto-char (point-min)) + (not (re-search-forward "[^\000-\377]" nil t)))) (mm-disable-multibyte) (if (or (not message-send-mail-partially-limit) (< (buffer-size) message-send-mail-partially-limit) @@ -4768,14 +4767,14 @@ to find out how to use this." (replace-match "\n") (run-hooks 'message-send-mail-hook) ;; send the message - (case + (pcase (let ((coding-system-for-write message-send-coding-system)) (apply 'call-process-region (point-min) (point-max) message-qmail-inject-program nil nil nil ;; qmail-inject's default behavior is to look for addresses on the ;; command line; if there're none, it scans the headers. - ;; yes, it does The Right Thing w.r.t. Resent-To and it's kin. + ;; yes, it does The Right Thing w.r.t. Resent-To and its kin. ;; ;; in general, ALL of qmail-inject's defaults are perfect for simply ;; reading a formatted (i. e., at least a To: or Resent-To header) @@ -4793,13 +4792,13 @@ to find out how to use this." (if (functionp message-qmail-inject-args) (funcall message-qmail-inject-args) message-qmail-inject-args))) - ;; qmail-inject doesn't say anything on it's stdout/stderr, + ;; qmail-inject doesn't say anything on its stdout/stderr, ;; we have to look at the retval instead (0 nil) (100 (error "qmail-inject reported permanent failure")) (111 (error "qmail-inject reported transient failure")) ;; should never happen - (t (error "qmail-inject reported unknown failure")))) + (_ (error "qmail-inject reported unknown failure")))) (defvar mh-previous-window-config) @@ -4842,17 +4841,13 @@ command evaluates `message-send-mail-hook' just before sending a message." (run-hooks 'message-send-mail-hook) (mailclient-send-it)) -(defvar sha1-maximum-internal-length) - (defun message-canlock-generate () "Return a string that is non-trivial to guess. Do not use this for anything important, it is cryptographically weak." - (require 'sha1) - (let (sha1-maximum-internal-length) - (sha1 (concat (message-unique-id) - (format "%x%x%x" (random) (random) (random)) - (prin1-to-string (recent-keys)) - (prin1-to-string (garbage-collect)))))) + (sha1 (concat (message-unique-id) + (format "%x%x%x" (random) (random) (random)) + (prin1-to-string (recent-keys)) + (prin1-to-string (garbage-collect))))) (defvar canlock-password) (defvar canlock-password-for-verify) @@ -5326,7 +5321,9 @@ Otherwise, generate and save a value for `canlock-password' first." ;; Check for control characters. (message-check 'control-chars (if (re-search-forward - (string-to-multibyte "[\000-\007\013\015-\032\034-\037\200-\237]") + (eval-when-compile + (decode-coding-string "[\000-\007\013\015-\032\034-\037\200-\237]" + 'binary)) nil t) (y-or-n-p "The article contains control characters. Really post? ") @@ -5782,7 +5779,10 @@ give as trustworthy answer as possible." (not (string-match message-bogus-system-names message-user-fqdn))) ;; `message-user-fqdn' seems to be valid message-user-fqdn) - ((not (string-match message-bogus-system-names sysname)) + ;; A system name without any dots is unlikely to be a good fully + ;; qualified domain name. + ((and (string-match "[.]" sysname) + (not (string-match message-bogus-system-names sysname))) ;; `system-name' returned the right result. sysname) ;; Try `mail-host-address'. @@ -5850,10 +5850,10 @@ subscribed address (and not the additional To and Cc header contents)." message-subscribed-address-functions)))) (save-match-data (let ((list - (loop for recipient in recipients - when (loop for regexp in mft-regexps - when (string-match regexp recipient) return t) - return recipient))) + (cl-loop for recipient in recipients + when (cl-loop for regexp in mft-regexps + thereis (string-match regexp recipient)) + return recipient))) (when list (if only-show-subscribed list @@ -6202,7 +6202,7 @@ they are." (when (> count maxcount) (let ((surplus (- count maxcount))) (message-shorten-1 refs cut surplus) - (decf count surplus))) + (cl-decf count surplus))) ;; When sending via news, make sure the total folded length will ;; be less than 998 characters. This is to cater to broken INN @@ -6680,7 +6680,7 @@ is a function used to switch to and display the mail buffer." ;; C-h f compose-mail says that headers should be specified as ;; (string . value); however all the rest of message expects ;; headers to be symbols, not strings (eg message-header-format-alist). - ;; http://lists.gnu.org/archive/html/emacs-devel/2011-01/msg00337.html + ;; https://lists.gnu.org/r/emacs-devel/2011-01/msg00337.html ;; We need to convert any string input, eg from rmail-start-mail. (dolist (h other-headers other-headers) (if (stringp (car h)) (setcar h (intern (capitalize (car h))))))) @@ -6727,9 +6727,9 @@ The function is called with one parameter, a cons cell ..." ;; Gmane renames "To". Look at "Original-To", too, if it is present in ;; message-header-synonyms. (setq to (or (message-fetch-field "to") - (and (loop for synonym in message-header-synonyms - when (memq 'Original-To synonym) - return t) + (and (cl-loop for synonym in message-header-synonyms + when (memq 'Original-To synonym) + return t) (message-fetch-field "original-to"))) cc (message-fetch-field "cc") extra (when message-extra-wide-headers @@ -8061,8 +8061,12 @@ regexp VARSTR." (or (null varstr) (string-match varstr (symbol-name (car local))))) (ignore-errors - (set (make-local-variable (car local)) - (cdr local))))) + ;; Cloning message-default-charset could cause an already + ;; encoded text to be encoded again, yielding raw bytes + ;; instead of characters in the message. + (unless (eq 'message-default-charset (car local)) + (set (make-local-variable (car local)) + (cdr local)))))) locals))) ;;; @@ -8130,11 +8134,12 @@ From headers in the original article." (message-tokenize-header (mail-strip-quoted-names (mapconcat 'message-fetch-reply-field fields ",")))) - (email (cond ((functionp message-alternative-emails) - (car (cl-remove-if-not message-alternative-emails emails))) - (t (loop for email in emails - if (string-match-p message-alternative-emails email) - return email))))) + (email + (cond ((functionp message-alternative-emails) + (car (cl-remove-if-not message-alternative-emails emails))) + (t (cl-loop for email in emails + if (string-match-p message-alternative-emails email) + return email))))) (unless (or (not email) (equal email user-mail-address)) (message-remove-header "From") (goto-char (point-max)) @@ -8414,7 +8419,7 @@ Used in `message-simplify-recipients'." (save-excursion (goto-char (point-min)) (while (not (eobp)) - (when-let ((props (get-text-property (point) 'display))) + (when-let* ((props (get-text-property (point) 'display))) (when (and (consp props) (eq (car props) 'image)) (put-text-property (point) (1+ (point)) 'display nil) diff --git a/lisp/gnus/mm-archive.el b/lisp/gnus/mm-archive.el index 103cc89c357..0451f217582 100644 --- a/lisp/gnus/mm-archive.el +++ b/lisp/gnus/mm-archive.el @@ -16,7 +16,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/mm-bodies.el b/lisp/gnus/mm-bodies.el index d773289722f..319d789c002 100644 --- a/lisp/gnus/mm-bodies.el +++ b/lisp/gnus/mm-bodies.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/mm-decode.el b/lisp/gnus/mm-decode.el index c6a0be36c40..82b378e6270 100644 --- a/lisp/gnus/mm-decode.el +++ b/lisp/gnus/mm-decode.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -676,7 +676,7 @@ MIME-Version header before proceeding." (mm-alist-to-plist (cdr ctl)) (car ctl)) ;; what really needs to be done here is a way to link a - ;; MIME handle back to it's parent MIME handle (in a multilevel + ;; MIME handle back to its parent MIME handle (in a multilevel ;; MIME article). That would probably require changing ;; the mm-handle API so we simply store the multipart buffer ;; name as a text property of the "multipart/whatever" string. @@ -1363,7 +1363,7 @@ PROMPT overrides the default one used to ask user for a file name." (mm-handle-disposition handle) 'filename) (mail-content-type-get (mm-handle-type handle) 'name))) - file) + file directory) (when filename (setq filename (gnus-map-function mm-file-name-rewrite-functions (file-name-nondirectory filename)))) @@ -1372,16 +1372,20 @@ PROMPT overrides the default one used to ask user for a file name." (setq file (read-file-name (or prompt - (format "Save MIME part to (default %s): " - (or filename ""))) - (or mm-default-directory default-directory) - (expand-file-name (or filename "") - (or mm-default-directory default-directory)))) + (format "Save MIME part to%s: " + (if filename + (format " (default %s)" filename) + ""))) + (or directory mm-default-directory default-directory) + (expand-file-name + (or filename "") + (or directory mm-default-directory default-directory)))) (cond ((or (not file) (equal file "")) (message "Please enter a file name") t) ((and (file-directory-p file) (not filename)) + (setq directory file) (message "Please enter a non-directory file name") t) (t nil))) diff --git a/lisp/gnus/mm-encode.el b/lisp/gnus/mm-encode.el index 1a9b5ab3de9..248992ea96d 100644 --- a/lisp/gnus/mm-encode.el +++ b/lisp/gnus/mm-encode.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/mm-extern.el b/lisp/gnus/mm-extern.el index a6e76ff7be3..79d9ae37411 100644 --- a/lisp/gnus/mm-extern.el +++ b/lisp/gnus/mm-extern.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/mm-partial.el b/lisp/gnus/mm-partial.el index e3e6f5d7805..68008ea0d27 100644 --- a/lisp/gnus/mm-partial.el +++ b/lisp/gnus/mm-partial.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/mm-url.el b/lisp/gnus/mm-url.el index 5c8f99b0483..39e1af94924 100644 --- a/lisp/gnus/mm-url.el +++ b/lisp/gnus/mm-url.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/mm-util.el b/lisp/gnus/mm-util.el index 89f397e3ed0..a7db3dadbc1 100644 --- a/lisp/gnus/mm-util.el +++ b/lisp/gnus/mm-util.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/mm-uu.el b/lisp/gnus/mm-uu.el index 10cdeed3fbb..436235c4631 100644 --- a/lisp/gnus/mm-uu.el +++ b/lisp/gnus/mm-uu.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -393,7 +393,7 @@ apply the face `mm-uu-extract'." (defun mm-uu-org-src-code-block-extract () (mm-make-handle (mm-uu-copy-to-buffer start-point end-point) - '("text/x-org"))) + '("text/x-org" (charset . gnus-decoded)))) (defvar gnus-newsgroup-name) diff --git a/lisp/gnus/mm-view.el b/lisp/gnus/mm-view.el index dd64bfed60a..d7a41b84930 100644 --- a/lisp/gnus/mm-view.el +++ b/lisp/gnus/mm-view.el @@ -16,7 +16,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -475,12 +475,12 @@ If MODE is not set, try to find mode automatically." (require 'font-lock) ;; I find font-lock a bit too verbose. (let ((font-lock-verbose nil) - (font-lock-support-mode nil)) + (font-lock-support-mode nil) + (enable-local-variables nil)) ;; Disable support modes, e.g., jit-lock, lazy-lock, etc. ;; Note: XEmacs people use `font-lock-mode-hook' to run those modes. (set (make-local-variable 'font-lock-mode-hook) nil) (setq buffer-file-name (mm-handle-filename handle)) - (set (make-local-variable 'enable-local-variables) nil) (with-demoted-errors (if mode (save-window-excursion diff --git a/lisp/gnus/mml-sec.el b/lisp/gnus/mml-sec.el index 57c371a65f4..80bd8d0e066 100644 --- a/lisp/gnus/mml-sec.el +++ b/lisp/gnus/mml-sec.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/mml-smime.el b/lisp/gnus/mml-smime.el index 1821d1a49fc..c6bc612a8f1 100644 --- a/lisp/gnus/mml-smime.el +++ b/lisp/gnus/mml-smime.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/mml.el b/lisp/gnus/mml.el index ce28607a04a..9ee2c95b7cb 100644 --- a/lisp/gnus/mml.el +++ b/lisp/gnus/mml.el @@ -16,7 +16,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/mml1991.el b/lisp/gnus/mml1991.el index 0df908f2a2e..86370729de1 100644 --- a/lisp/gnus/mml1991.el +++ b/lisp/gnus/mml1991.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/mml2015.el b/lisp/gnus/mml2015.el index f973670e8e9..11f3f750f3f 100644 --- a/lisp/gnus/mml2015.el +++ b/lisp/gnus/mml2015.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/nnagent.el b/lisp/gnus/nnagent.el index 2b4843488c4..025c3d3cad4 100644 --- a/lisp/gnus/nnagent.el +++ b/lisp/gnus/nnagent.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/nnbabyl.el b/lisp/gnus/nnbabyl.el index 5aa481e0673..c61cbc8d7c3 100644 --- a/lisp/gnus/nnbabyl.el +++ b/lisp/gnus/nnbabyl.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/nndiary.el b/lisp/gnus/nndiary.el index 0390b5b8d28..ca4dca4189d 100644 --- a/lisp/gnus/nndiary.el +++ b/lisp/gnus/nndiary.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -1532,7 +1532,7 @@ all. This may very well take some time.") ;; past. A permanent schedule never expires. (and sched (setq sched (nndiary-last-occurrence sched)) - (time-less-p sched (current-time)))) + (time-less-p sched nil))) ;; else (nnheader-report 'nndiary "Could not read file %s" file) nil) diff --git a/lisp/gnus/nndir.el b/lisp/gnus/nndir.el index 718306abce0..7eb3e824bca 100644 --- a/lisp/gnus/nndir.el +++ b/lisp/gnus/nndir.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/nndoc.el b/lisp/gnus/nndoc.el index 7f7db8721db..e9e769cac57 100644 --- a/lisp/gnus/nndoc.el +++ b/lisp/gnus/nndoc.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/nndraft.el b/lisp/gnus/nndraft.el index 12a1b2b284a..62a15752703 100644 --- a/lisp/gnus/nndraft.el +++ b/lisp/gnus/nndraft.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/nneething.el b/lisp/gnus/nneething.el index 6850cad2e60..f6bf5866970 100644 --- a/lisp/gnus/nneething.el +++ b/lisp/gnus/nneething.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/nnfolder.el b/lisp/gnus/nnfolder.el index 18c92f9f77b..9b1317347a7 100644 --- a/lisp/gnus/nnfolder.el +++ b/lisp/gnus/nnfolder.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/nngateway.el b/lisp/gnus/nngateway.el index 1e57f7c6f60..63bd063cbde 100644 --- a/lisp/gnus/nngateway.el +++ b/lisp/gnus/nngateway.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/nnheader.el b/lisp/gnus/nnheader.el index 4440f17c2bb..0ea99d53a4a 100644 --- a/lisp/gnus/nnheader.el +++ b/lisp/gnus/nnheader.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/nnimap.el b/lisp/gnus/nnimap.el index 2943c8dc7d2..297e2923ee6 100644 --- a/lisp/gnus/nnimap.el +++ b/lisp/gnus/nnimap.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -27,7 +27,8 @@ ;;; Code: (eval-when-compile - (require 'cl)) + (require 'cl) + (require 'subr-x)) (require 'nnheader) (require 'gnus-util) @@ -950,7 +951,7 @@ textual parts.") internal-move-group server message-id nnimap-request-articles-find-limit))))) ;; Move the article to a different method. - (when-let ((result (eval accept-form))) + (when-let* ((result (eval accept-form))) (nnimap-change-group group server) (nnimap-delete-article article) result)))))) diff --git a/lisp/gnus/nnir.el b/lisp/gnus/nnir.el index 9640f2c746f..be42ab74e4a 100644 --- a/lisp/gnus/nnir.el +++ b/lisp/gnus/nnir.el @@ -25,7 +25,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -64,7 +64,7 @@ ;; also be correct, see the documentation for `nnir-namazu-remove-prefix' ;; above. ;; -;; It is particularly important not to pass any any switches to namazu +;; It is particularly important not to pass any switches to namazu ;; that will change the output format. Good switches to use include ;; `--sort', `--ascending', `--early' and `--late'. Refer to the Namazu ;; documentation for further information on valid switches. diff --git a/lisp/gnus/nnmail.el b/lisp/gnus/nnmail.el index 71d9631776d..ad58d292082 100644 --- a/lisp/gnus/nnmail.el +++ b/lisp/gnus/nnmail.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -1883,7 +1883,7 @@ If TIME is nil, then return the cutoff time for oldness instead." (setq days (days-to-time days)) ;; Compare the time with the current time. (if (null time) - (time-subtract (current-time) days) + (time-subtract nil days) (ignore-errors (time-less-p days (time-since time))))))))) (declare-function gnus-group-mark-article-read "gnus-group" (group article)) diff --git a/lisp/gnus/nnmaildir.el b/lisp/gnus/nnmaildir.el index 03cb445675c..708a3426af1 100644 --- a/lisp/gnus/nnmaildir.el +++ b/lisp/gnus/nnmaildir.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -814,7 +814,7 @@ This variable is set by `nnmaildir-request-article'.") (when (or isnew nattr) (dolist (file (funcall ls ndir nil "\\`[^.]" 'nosort)) (setq x (concat ndir file)) - (and (time-less-p (nth 5 (file-attributes x)) (current-time)) + (and (time-less-p (nth 5 (file-attributes x)) nil) (rename-file x (concat cdir (nnmaildir--ensure-suffix file))))) (setf (nnmaildir--grp-new group) nattr)) (setq cattr (nth 5 (file-attributes cdir))) diff --git a/lisp/gnus/nnmairix.el b/lisp/gnus/nnmairix.el index a678a797439..7c96171623e 100644 --- a/lisp/gnus/nnmairix.el +++ b/lisp/gnus/nnmairix.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/nnmbox.el b/lisp/gnus/nnmbox.el index c854f19c7c2..3a33fb90751 100644 --- a/lisp/gnus/nnmbox.el +++ b/lisp/gnus/nnmbox.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/nnmh.el b/lisp/gnus/nnmh.el index bec174db86a..050f0cd2dde 100644 --- a/lisp/gnus/nnmh.el +++ b/lisp/gnus/nnmh.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/nnml.el b/lisp/gnus/nnml.el index b0c7bf41add..b7d1bc2237e 100644 --- a/lisp/gnus/nnml.el +++ b/lisp/gnus/nnml.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/nnnil.el b/lisp/gnus/nnnil.el index e40126d6e0d..7d400791fa2 100644 --- a/lisp/gnus/nnnil.el +++ b/lisp/gnus/nnnil.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/nnoo.el b/lisp/gnus/nnoo.el index 77e7f2a2d0e..be38f8d1d75 100644 --- a/lisp/gnus/nnoo.el +++ b/lisp/gnus/nnoo.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/nnregistry.el b/lisp/gnus/nnregistry.el index b1a2416e2fd..fac332af97a 100644 --- a/lisp/gnus/nnregistry.el +++ b/lisp/gnus/nnregistry.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/nnrss.el b/lisp/gnus/nnrss.el index 8115057723c..9a3a562a5dd 100644 --- a/lisp/gnus/nnrss.el +++ b/lisp/gnus/nnrss.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/nnspool.el b/lisp/gnus/nnspool.el index 1db0a4192a1..b14b5cde8d2 100644 --- a/lisp/gnus/nnspool.el +++ b/lisp/gnus/nnspool.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/nntp.el b/lisp/gnus/nntp.el index a71f4c7b5dd..ad93815b9c2 100644 --- a/lisp/gnus/nntp.el +++ b/lisp/gnus/nntp.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/nnvirtual.el b/lisp/gnus/nnvirtual.el index dcd610317ef..543f7b66c47 100644 --- a/lisp/gnus/nnvirtual.el +++ b/lisp/gnus/nnvirtual.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/nnweb.el b/lisp/gnus/nnweb.el index 8e5b20047f4..4327824c7e8 100644 --- a/lisp/gnus/nnweb.el +++ b/lisp/gnus/nnweb.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/score-mode.el b/lisp/gnus/score-mode.el index d106cf0c271..3e7428493e4 100644 --- a/lisp/gnus/score-mode.el +++ b/lisp/gnus/score-mode.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -85,7 +85,7 @@ This mode is an extended emacs-lisp mode. (defun gnus-score-edit-insert-date () "Insert date in numerical format." (interactive) - (princ (time-to-days (current-time)) (current-buffer))) + (princ (time-to-days nil) (current-buffer))) (defun gnus-score-pretty-print () "Format the current score file." diff --git a/lisp/gnus/smiley.el b/lisp/gnus/smiley.el index 763a1cd5be7..3a948636331 100644 --- a/lisp/gnus/smiley.el +++ b/lisp/gnus/smiley.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/smime.el b/lisp/gnus/smime.el index e3c284f033c..21f8c09e1cb 100644 --- a/lisp/gnus/smime.el +++ b/lisp/gnus/smime.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -67,7 +67,7 @@ ;; ;; To be able to verify messages you need to build up trust with ;; someone. Perhaps you trust the CA that issued your certificate, at -;; least I did, so I export it's certificates from my PKCS#12 +;; least I did, so I export its certificates from my PKCS#12 ;; certificate with: ;; ;; $ openssl pkcs12 -in mykey.p12 -cacerts -nodes > cacert.pem diff --git a/lisp/gnus/spam-report.el b/lisp/gnus/spam-report.el index 17a7f89ae91..b45b487d9e0 100644 --- a/lisp/gnus/spam-report.el +++ b/lisp/gnus/spam-report.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/spam-stat.el b/lisp/gnus/spam-stat.el index 5466cf9edd9..08d382bcbdc 100644 --- a/lisp/gnus/spam-stat.el +++ b/lisp/gnus/spam-stat.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/spam-wash.el b/lisp/gnus/spam-wash.el index bcdde736b38..04e62903d97 100644 --- a/lisp/gnus/spam-wash.el +++ b/lisp/gnus/spam-wash.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/gnus/spam.el b/lisp/gnus/spam.el index 5f0ea94b283..f14af741f75 100644 --- a/lisp/gnus/spam.el +++ b/lisp/gnus/spam.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -628,7 +628,7 @@ order for SpamAssassin to recognize the new registered spam." :group 'spam-spamassassin) (defcustom spam-sa-learn-unregister-switch "--forget" - "The switch that sa-learn uses to unregister messages messages." + "The switch that sa-learn uses to unregister messages." :type 'string :group 'spam-spamassassin) diff --git a/lisp/help-at-pt.el b/lisp/help-at-pt.el index 87b9e50c9d3..46229bcb91b 100644 --- a/lisp/help-at-pt.el +++ b/lisp/help-at-pt.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/help-fns.el b/lisp/help-fns.el index 2c635ffa500..643b0cbbc53 100644 --- a/lisp/help-fns.el +++ b/lisp/help-fns.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -560,10 +560,12 @@ FILE is the file where FUNCTION was probably defined." (setq short rel)))) short)) -;;;###autoload -(defun describe-function-1 (function) +(defun help-fns--analyze-function (function) + ;; FIXME: Document/explain the differences between FUNCTION, + ;; REAL-FUNCTION, DEF, and REAL-DEF. + "Return information about FUNCTION. +Returns a list of the form (REAL-FUNCTION DEF ALIASED REAL-DEF)." (let* ((advised (and (symbolp function) - (featurep 'nadvice) (advice--p (advice--symbol-function function)))) ;; If the function is advised, use the symbol that has the ;; real definition, if that symbol is already set up. @@ -594,22 +596,24 @@ FILE is the file where FUNCTION was probably defined." (setq f (symbol-function f))) f)) ((subrp def) (intern (subr-name def))) - (t def))) - (sig-key (if (subrp def) - (indirect-function real-def) - real-def)) - (file-name (find-lisp-object-file-name function (if aliased 'defun - def))) - (pt1 (with-current-buffer (help-buffer) (point))) - (beg (if (and (or (byte-code-function-p def) - (keymapp def) - (memq (car-safe def) '(macro lambda closure))) - (stringp file-name) - (help-fns--autoloaded-p function file-name)) - (if (commandp def) - "an interactive autoloaded " - "an autoloaded ") - (if (commandp def) "an interactive " "a ")))) + (t def)))) + (list real-function def aliased real-def))) + +(defun help-fns-function-description-header (function) + "Print a line describing FUNCTION to `standard-output'." + (pcase-let* ((`(,_real-function ,def ,aliased ,real-def) + (help-fns--analyze-function function)) + (file-name (find-lisp-object-file-name function (if aliased 'defun + def))) + (beg (if (and (or (byte-code-function-p def) + (keymapp def) + (memq (car-safe def) '(macro lambda closure))) + (stringp file-name) + (help-fns--autoloaded-p function file-name)) + (if (commandp def) + "an interactive autoloaded " + "an autoloaded ") + (if (commandp def) "an interactive " "a ")))) ;; Print what kind of function-like object FUNCTION is. (princ (cond ((or (stringp def) (vectorp def)) @@ -676,34 +680,56 @@ FILE is the file where FUNCTION was probably defined." (re-search-backward (substitute-command-keys "`\\([^`']+\\)'") nil t) (help-xref-button 1 'help-function-def function file-name)))) - (princ ".") - (with-current-buffer (help-buffer) - (fill-region-as-paragraph (save-excursion (goto-char pt1) (forward-line 0) (point)) - (point))) - (terpri)(terpri) - - (let ((doc-raw (documentation function t)) - (key-bindings-buffer (current-buffer))) - - ;; If the function is autoloaded, and its docstring has - ;; key substitution constructs, load the library. - (and (autoloadp real-def) doc-raw - help-enable-auto-load - (string-match "\\([^\\]=\\|[^=]\\|\\`\\)\\\\[[{<]" doc-raw) - (autoload-do-load real-def)) - - (help-fns--key-bindings function) - (with-current-buffer standard-output - (let ((doc (help-fns--signature function doc-raw sig-key - real-function key-bindings-buffer))) - (run-hook-with-args 'help-fns-describe-function-functions function) - (insert "\n" - (or doc "Not documented.")) - ;; Avoid asking the user annoying questions if she decides - ;; to save the help buffer, when her locale's codeset - ;; isn't UTF-8. - (unless (memq text-quoting-style '(straight grave)) - (set-buffer-file-coding-system 'utf-8)))))))) + (princ ".")))) + +;;;###autoload +(defun describe-function-1 (function) + (let ((pt1 (with-current-buffer (help-buffer) (point)))) + (help-fns-function-description-header function) + (with-current-buffer (help-buffer) + (fill-region-as-paragraph (save-excursion (goto-char pt1) (forward-line 0) (point)) + (point)))) + (terpri)(terpri) + + (pcase-let* ((`(,real-function ,def ,_aliased ,real-def) + (help-fns--analyze-function function)) + (doc-raw (condition-case nil + ;; FIXME: Maybe `documentation' should return nil + ;; for invalid functions i.s.o. signaling an error. + (documentation function t) + ;; E.g. an alias for a not yet defined function. + ((invalid-function void-function) nil))) + (key-bindings-buffer (current-buffer))) + + ;; If the function is autoloaded, and its docstring has + ;; key substitution constructs, load the library. + (and (autoloadp real-def) doc-raw + help-enable-auto-load + (string-match "\\([^\\]=\\|[^=]\\|\\`\\)\\\\[[{<]" doc-raw) + (autoload-do-load real-def)) + + (help-fns--key-bindings function) + (with-current-buffer standard-output + (let ((doc (condition-case nil + ;; FIXME: Maybe `help-fns--signature' should return `doc' + ;; for invalid functions i.s.o. signaling an error. + (help-fns--signature + function doc-raw + (if (subrp def) (indirect-function real-def) real-def) + real-function key-bindings-buffer) + ;; E.g. an alias for a not yet defined function. + ((invalid-function void-function) doc-raw)))) + (run-hook-with-args 'help-fns-describe-function-functions function) + (insert "\n" (or doc "Not documented."))) + (when (or (function-get function 'pure) + (function-get function 'side-effect-free)) + (insert "\nThis function does not change global state, " + "including the match data.")) + ;; Avoid asking the user annoying questions if she decides + ;; to save the help buffer, when her locale's codeset + ;; isn't UTF-8. + (unless (memq text-quoting-style '(straight grave)) + (set-buffer-file-coding-system 'utf-8))))) ;; Add defaults to `help-fns-describe-function-functions'. (add-hook 'help-fns-describe-function-functions #'help-fns--obsolete) @@ -873,7 +899,10 @@ it is displayed along with the global value." (not (equal origval :help-eval-error))) (princ "\nOriginal value was \n") (setq from (point)) - (pp origval) + (cl-prin1 origval) + (save-restriction + (narrow-to-region from (point)) + (save-excursion (pp-buffer))) (if (< (point) (+ from 20)) (delete-region (1- from) from))))))) (terpri) @@ -899,7 +928,10 @@ it is displayed along with the global value." ;; probably print it raw once and check it's a ;; sensible size before prettyprinting. -- fx (let ((from (point))) - (pp global-val) + (cl-prin1 global-val) + (save-restriction + (narrow-to-region from (point)) + (save-excursion (pp-buffer))) ;; See previous comment for this function. ;; (help-xref-on-pp from (point)) (if (< (point) (+ from 20)) @@ -1260,14 +1292,14 @@ BUFFER should be a buffer or a buffer name." (insert-file-contents file) (let (notfirst) (while (search-forward "" nil 'move) - (if (looking-at "S") + (if (= (following-char) ?S) (delete-region (1- (point)) (line-end-position)) (delete-char -1) (if notfirst (insert "\n.DE\n") (setq notfirst t)) (insert "\n.SH ") - (insert (if (looking-at "F") "Function " "Variable ")) + (insert (if (= (following-char) ?F) "Function " "Variable ")) (delete-char 1) (forward-line 1) (insert ".DS L\n")))) @@ -1293,7 +1325,7 @@ BUFFER should be a buffer or a buffer name." (forward-char 1)) (goto-char (point-min)) (while (search-forward "" nil t) - (unless (looking-at "S") + (when (/= (following-char) ?S) (setq type (char-after) name (buffer-substring (1+ (point)) (line-end-position)) doc (buffer-substring (line-beginning-position 2) diff --git a/lisp/help-macro.el b/lisp/help-macro.el index c8f93bc5e59..3181a492ff8 100644 --- a/lisp/help-macro.el +++ b/lisp/help-macro.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/help-mode.el b/lisp/help-mode.el index 3fb793e7aa5..a98bce0138b 100644 --- a/lisp/help-mode.el +++ b/lisp/help-mode.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -393,12 +393,12 @@ it does not already exist." (defvar describe-symbol-backends `((nil ,#'fboundp ,(lambda (s _b _f) (describe-function s))) - ("face" ,#'facep ,(lambda (s _b _f) (describe-face s))) (nil ,(lambda (symbol) (or (and (boundp symbol) (not (keywordp symbol))) (get symbol 'variable-documentation))) - ,#'describe-variable))) + ,#'describe-variable) + ("face" ,#'facep ,(lambda (s _b _f) (describe-face s))))) ;;;###autoload (defun help-make-xrefs (&optional buffer) diff --git a/lisp/help.el b/lisp/help.el index 361ab2a01ee..212e3679dad 100644 --- a/lisp/help.el +++ b/lisp/help.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -306,7 +306,7 @@ If that doesn't give a function, return nil." (defun describe-gnu-project () "Browse online information on the GNU project." (interactive) - (browse-url "http://www.gnu.org/gnu/thegnuproject.html")) + (browse-url "https://www.gnu.org/gnu/thegnuproject.html")) (define-obsolete-function-alias 'describe-project 'describe-gnu-project "22.2") @@ -593,6 +593,39 @@ If INSERT (the prefix arg) is non-nil, insert the message in the buffer." string (format "%s (translated from %s)" string otherstring)))))) +(defun help--analyze-key (key untranslated) + "Get information about KEY its corresponding UNTRANSLATED events. +Returns a list of the form (BRIEF-DESC DEFN EVENT MOUSE-MSG)." + (if (numberp untranslated) + (setq untranslated (this-single-command-raw-keys))) + (let* ((event (aref key (if (and (symbolp (aref key 0)) + (> (length key) 1) + (consp (aref key 1))) + 1 + 0))) + (modifiers (event-modifiers event)) + (mouse-msg (if (or (memq 'click modifiers) (memq 'down modifiers) + (memq 'drag modifiers)) " at that spot" "")) + (defn (key-binding key t))) + ;; Handle the case where we faked an entry in "Select and Paste" menu. + (when (and (eq defn nil) + (stringp (aref key (1- (length key)))) + (eq (key-binding (substring key 0 -1)) 'yank-menu)) + (setq defn 'menu-bar-select-yank)) + ;; Don't bother user with strings from (e.g.) the select-paste menu. + (when (stringp (aref key (1- (length key)))) + (aset key (1- (length key)) "(any string)")) + (when (and untranslated + (stringp (aref untranslated (1- (length untranslated))))) + (aset untranslated (1- (length untranslated)) "(any string)")) + (list + ;; Now describe the key, perhaps as changed. + (let ((key-desc (help-key-description key untranslated))) + (if (or (null defn) (integerp defn) (equal defn 'undefined)) + (format "%s%s is undefined" key-desc mouse-msg) + (format "%s%s runs the command %S" key-desc mouse-msg defn))) + defn event mouse-msg))) + (defun describe-key-briefly (&optional key insert untranslated) "Print the name of the function KEY invokes. KEY is a string. If INSERT (the prefix arg) is non-nil, insert the message in the buffer. @@ -603,73 +636,12 @@ the last key hit are used. If KEY is a menu item or a tool-bar button that is disabled, this command temporarily enables it to allow getting help on disabled items and buttons." (interactive - (let ((enable-disabled-menus-and-buttons t) - (cursor-in-echo-area t) - saved-yank-menu) - (unwind-protect - (let (key) - ;; If yank-menu is empty, populate it temporarily, so that - ;; "Select and Paste" menu can generate a complete event. - (when (null (cdr yank-menu)) - (setq saved-yank-menu (copy-sequence yank-menu)) - (menu-bar-update-yank-menu "(any string)" nil)) - (while - (progn - (setq key (read-key-sequence "Describe the following key, mouse click, or menu item: ")) - (and (vectorp key) - (consp (aref key 0)) - (symbolp (car (aref key 0))) - (string-match "\\(mouse\\|down\\|click\\|drag\\)" - (symbol-name (car (aref key 0)))) - (not (sit-for (/ double-click-time 1000.0) t))))) - ;; Clear the echo area message (Bug#7014). - (message nil) - ;; If KEY is a down-event, read and discard the - ;; corresponding up-event. Note that there are also - ;; down-events on scroll bars and mode lines: the actual - ;; event then is in the second element of the vector. - (and (vectorp key) - (let ((last-idx (1- (length key)))) - (and (eventp (aref key last-idx)) - (memq 'down (event-modifiers (aref key last-idx))))) - (read-event)) - (list - key - (if current-prefix-arg (prefix-numeric-value current-prefix-arg)) - 1)) - ;; Put yank-menu back as it was, if we changed it. - (when saved-yank-menu - (setq yank-menu (copy-sequence saved-yank-menu)) - (fset 'yank-menu (cons 'keymap yank-menu)))))) - (if (numberp untranslated) - (setq untranslated (this-single-command-raw-keys))) - (let* ((event (if (and (symbolp (aref key 0)) - (> (length key) 1) - (consp (aref key 1))) - (aref key 1) - (aref key 0))) - (modifiers (event-modifiers event)) - (standard-output (if insert (current-buffer) standard-output)) - (mouse-msg (if (or (memq 'click modifiers) (memq 'down modifiers) - (memq 'drag modifiers)) " at that spot" "")) - (defn (key-binding key t)) - key-desc) - ;; Handle the case where we faked an entry in "Select and Paste" menu. - (if (and (eq defn nil) - (stringp (aref key (1- (length key)))) - (eq (key-binding (substring key 0 -1)) 'yank-menu)) - (setq defn 'menu-bar-select-yank)) - ;; Don't bother user with strings from (e.g.) the select-paste menu. - (if (stringp (aref key (1- (length key)))) - (aset key (1- (length key)) "(any string)")) - (if (and (> (length untranslated) 0) - (stringp (aref untranslated (1- (length untranslated))))) - (aset untranslated (1- (length untranslated)) "(any string)")) - ;; Now describe the key, perhaps as changed. - (setq key-desc (help-key-description key untranslated)) - (if (or (null defn) (integerp defn) (equal defn 'undefined)) - (princ (format "%s%s is undefined" key-desc mouse-msg)) - (princ (format "%s%s runs the command %S" key-desc mouse-msg defn))))) + ;; Ignore mouse movement events because it's too easy to miss the + ;; message while moving the mouse. + (pcase-let ((`(,key ,_up-event) (help-read-key-sequence 'no-mouse-movement))) + `(,key ,current-prefix-arg 1))) + (princ (car (help--analyze-key key untranslated)) + (if insert (current-buffer) standard-output))) (defun help--key-binding-keymap (key &optional accept-default no-remap position) "Return a keymap holding a binding for KEY within current keymaps. @@ -734,6 +706,69 @@ function `key-binding'." (throw 'found x)))) nil))))) +(defun help-read-key-sequence (&optional no-mouse-movement) + "Reads a key sequence from the user. +Returns a list of the form (KEY UP-EVENT), where KEY is the key +sequence, and UP-EVENT is the up-event that was discarded by +reading KEY, or nil. +If NO-MOUSE-MOVEMENT is non-nil, ignore key sequences starting +with `mouse-movement' events." + (let ((enable-disabled-menus-and-buttons t) + (cursor-in-echo-area t) + saved-yank-menu) + (unwind-protect + (let (key down-ev) + ;; If yank-menu is empty, populate it temporarily, so that + ;; "Select and Paste" menu can generate a complete event. + (when (null (cdr yank-menu)) + (setq saved-yank-menu (copy-sequence yank-menu)) + (menu-bar-update-yank-menu "(any string)" nil)) + (while + (pcase (setq key (read-key-sequence "\ +Describe the following key, mouse click, or menu item: ")) + ((and (pred vectorp) (let `(,key0 . ,_) (aref key 0)) + (guard (symbolp key0)) (let keyname (symbol-name key0))) + (or + (and no-mouse-movement + (string-match "mouse-movement" keyname)) + (and (string-match "\\(mouse\\|down\\|click\\|drag\\)" + keyname) + (progn + ;; Discard events (e.g. <help-echo>) which might + ;; spuriously trigger the `sit-for'. + (sleep-for 0.01) + (while (read-event nil nil 0.01)) + (not (sit-for (/ double-click-time 1000.0) t)))))))) + (list + key + ;; If KEY is a down-event, read and include the + ;; corresponding up-event. Note that there are also + ;; down-events on scroll bars and mode lines: the actual + ;; event then is in the second element of the vector. + (and (vectorp key) + (let ((last-idx (1- (length key)))) + (and (eventp (aref key last-idx)) + (memq 'down (event-modifiers (aref key last-idx))))) + (or (and (eventp (setq down-ev (aref key 0))) + (memq 'down (event-modifiers down-ev)) + ;; However, for the C-down-mouse-2 popup + ;; menu, there is no subsequent up-event. In + ;; this case, the up-event is the next + ;; element in the supplied vector. + (= (length key) 1)) + (and (> (length key) 1) + (eventp (setq down-ev (aref key 1))) + (memq 'down (event-modifiers down-ev)))) + (if (and (terminal-parameter nil 'xterm-mouse-mode) + (equal (terminal-parameter nil 'xterm-mouse-last-down) + down-ev)) + (aref (read-key-sequence-vector nil) 0) + (read-event))))) + ;; Put yank-menu back as it was, if we changed it. + (when saved-yank-menu + (setq yank-menu (copy-sequence saved-yank-menu)) + (fset 'yank-menu (cons 'keymap yank-menu)))))) + (defun describe-key (&optional key untranslated up-event) "Display documentation of the function invoked by KEY. KEY can be any kind of a key sequence; it can include keyboard events, @@ -748,83 +783,20 @@ UP-EVENT is the up-event that was discarded by reading KEY, or nil. If KEY is a menu item or a tool-bar button that is disabled, this command temporarily enables it to allow getting help on disabled items and buttons." (interactive - (let ((enable-disabled-menus-and-buttons t) - (cursor-in-echo-area t) - saved-yank-menu) - (unwind-protect - (let (key) - ;; If yank-menu is empty, populate it temporarily, so that - ;; "Select and Paste" menu can generate a complete event. - (when (null (cdr yank-menu)) - (setq saved-yank-menu (copy-sequence yank-menu)) - (menu-bar-update-yank-menu "(any string)" nil)) - (while - (progn - (setq key (read-key-sequence "Describe the following key, mouse click, or menu item: ")) - (and (vectorp key) - (consp (aref key 0)) - (symbolp (car (aref key 0))) - (string-match "\\(mouse\\|down\\|click\\|drag\\)" - (symbol-name (car (aref key 0)))) - (not (sit-for (/ double-click-time 1000.0) t))))) - (list - key - (prefix-numeric-value current-prefix-arg) - ;; If KEY is a down-event, read and include the - ;; corresponding up-event. Note that there are also - ;; down-events on scroll bars and mode lines: the actual - ;; event then is in the second element of the vector. - (and (vectorp key) - (let ((last-idx (1- (length key)))) - (and (eventp (aref key last-idx)) - (memq 'down (event-modifiers (aref key last-idx))))) - (or (and (eventp (aref key 0)) - (memq 'down (event-modifiers (aref key 0))) - ;; However, for the C-down-mouse-2 popup - ;; menu, there is no subsequent up-event. In - ;; this case, the up-event is the next - ;; element in the supplied vector. - (= (length key) 1)) - (and (> (length key) 1) - (eventp (aref key 1)) - (memq 'down (event-modifiers (aref key 1))))) - (read-event)))) - ;; Put yank-menu back as it was, if we changed it. - (when saved-yank-menu - (setq yank-menu (copy-sequence saved-yank-menu)) - (fset 'yank-menu (cons 'keymap yank-menu)))))) - (if (numberp untranslated) - (setq untranslated (this-single-command-raw-keys))) - (let* ((event (aref key (if (and (symbolp (aref key 0)) - (> (length key) 1) - (consp (aref key 1))) - 1 - 0))) - (modifiers (event-modifiers event)) - (mouse-msg (if (or (memq 'click modifiers) (memq 'down modifiers) - (memq 'drag modifiers)) " at that spot" "")) - (defn (key-binding key t)) - key-locus key-locus-up key-locus-up-tricky - defn-up defn-up-tricky ev-type - mouse-1-remapped mouse-1-tricky) - - ;; Handle the case where we faked an entry in "Select and Paste" menu. - (when (and (eq defn nil) - (stringp (aref key (1- (length key)))) - (eq (key-binding (substring key 0 -1)) 'yank-menu)) - (setq defn 'menu-bar-select-yank)) - (if (or (null defn) (integerp defn) (equal defn 'undefined)) - (message "%s%s is undefined" - (help-key-description key untranslated) mouse-msg) + (pcase-let ((`(,key ,up-event) (help-read-key-sequence))) + `(,key ,(prefix-numeric-value current-prefix-arg) ,up-event))) + (pcase-let ((`(,brief-desc ,defn ,event ,mouse-msg) + (help--analyze-key key untranslated)) + (defn-up nil) (defn-up-tricky nil) + (key-locus-up nil) (key-locus-up-tricky nil) + (mouse-1-remapped nil) (mouse-1-tricky nil) + (ev-type nil)) + (if (or (null defn) + (integerp defn) + (equal defn 'undefined)) + (message "%s" brief-desc) (help-setup-xref (list #'describe-function defn) (called-interactively-p 'interactive)) - ;; Don't bother user with strings from (e.g.) the select-paste menu. - (when (stringp (aref key (1- (length key)))) - (aset key (1- (length key)) "(any string)")) - (when (and untranslated - (stringp (aref untranslated (1- (length untranslated))))) - (aset untranslated (1- (length untranslated)) - "(any string)")) ;; Need to do this before erasing *Help* buffer in case event ;; is a mouse click in an existing *Help* buffer. (when up-event @@ -849,13 +821,12 @@ temporarily enables it to allow getting help on disabled items and buttons." (aset sequence 0 'mouse-1) (setq defn-up-tricky (key-binding sequence nil nil (event-start up-event))) (setq key-locus-up-tricky (help--binding-locus sequence (event-start up-event)))))) - (setq key-locus (help--binding-locus key (event-start event))) (with-help-window (help-buffer) - (princ (help-key-description key untranslated)) - (princ (format "%s runs the command %S%s, which is " - mouse-msg defn (if key-locus - (format " (found in %s)" key-locus) - ""))) + (princ brief-desc) + (let ((key-locus (help--binding-locus key (event-start event)))) + (when key-locus + (princ (format " (found in %s)" key-locus)))) + (princ ", which is ") (describe-function-1 defn) (when up-event (unless (or (null defn-up) @@ -1374,7 +1345,7 @@ The result, when formatted by `substitute-command-keys', should equal STRING." ;; The following functions used to be in help-fns.el, which is not preloaded. ;; But for various reasons, they are more widely needed, so they were -;; moved to this file, which is preloaded. http://debbugs.gnu.org/17001 +;; moved to this file, which is preloaded. https://debbugs.gnu.org/17001 (defun help-split-fundoc (docstring def) "Split a function DOCSTRING into the actual doc and the usage info. @@ -1423,6 +1394,9 @@ If PRESERVE-NAMES is non-nil, return a formal arglist that uses the same names as used in the original source code, when possible." ;; Handle symbols aliased to other symbols. (if (and (symbolp def) (fboundp def)) (setq def (indirect-function def))) + ;; Advice wrappers have "catch all" args, so fetch the actual underlying + ;; function to find the real arguments. + (while (advice--p def) (setq def (advice--cdr def))) ;; If definition is a macro, find the function inside it. (if (eq (car-safe def) 'macro) (setq def (cdr def))) (cond diff --git a/lisp/hex-util.el b/lisp/hex-util.el index e2e3d7f07c0..5289f06f4ea 100644 --- a/lisp/hex-util.el +++ b/lisp/hex-util.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/hexl.el b/lisp/hexl.el index 0a598b22f66..f591439558a 100644 --- a/lisp/hexl.el +++ b/lisp/hexl.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/hfy-cmap.el b/lisp/hfy-cmap.el index 6fcaad085de..4dddc17b59c 100644 --- a/lisp/hfy-cmap.el +++ b/lisp/hfy-cmap.el @@ -26,7 +26,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/hi-lock.el b/lisp/hi-lock.el index 5139e01fa84..e3552fcac3f 100644 --- a/lisp/hi-lock.el +++ b/lisp/hi-lock.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -693,7 +693,8 @@ with completion and history." "Highlight REGEXP with face FACE." ;; Hashcons the regexp, so it can be passed to remove-overlays later. (setq regexp (hi-lock--hashcons regexp)) - (let ((pattern (list regexp (list 0 (list 'quote face) 'prepend)))) + (let ((pattern (list regexp (list 0 (list 'quote face) 'prepend))) + (no-matches t)) ;; Refuse to highlight a text that is already highlighted. (if (assoc regexp hi-lock-interactive-patterns) (add-to-list 'hi-lock--unused-faces (face-name face)) @@ -713,11 +714,16 @@ with completion and history." (save-excursion (goto-char search-start) (while (re-search-forward regexp search-end t) + (when no-matches (setq no-matches nil)) (let ((overlay (make-overlay (match-beginning 0) (match-end 0)))) (overlay-put overlay 'hi-lock-overlay t) (overlay-put overlay 'hi-lock-overlay-regexp regexp) (overlay-put overlay 'face face)) - (goto-char (match-end 0))))))))) + (goto-char (match-end 0))) + (when no-matches + (add-to-list 'hi-lock--unused-faces (face-name face)) + (setq hi-lock-interactive-patterns + (cdr hi-lock-interactive-patterns))))))))) (defun hi-lock-set-file-patterns (patterns) "Replace file patterns list with PATTERNS and refontify." diff --git a/lisp/hilit-chg.el b/lisp/hilit-chg.el index 1a410564814..4979ed84b6a 100644 --- a/lisp/hilit-chg.el +++ b/lisp/hilit-chg.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/hippie-exp.el b/lisp/hippie-exp.el index 8dc53bd8ec1..be3fedf0afd 100644 --- a/lisp/hippie-exp.el +++ b/lisp/hippie-exp.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/hl-line.el b/lisp/hl-line.el index 38fe683785a..9ccc354e845 100644 --- a/lisp/hl-line.el +++ b/lisp/hl-line.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/htmlfontify.el b/lisp/htmlfontify.el index 74393ffbaeb..cb4c83d33e0 100644 --- a/lisp/htmlfontify.el +++ b/lisp/htmlfontify.el @@ -29,7 +29,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -461,7 +461,7 @@ and so on." optimization - If on, preserve overlay highlighting (cf ediff or goo-font-lock) as well as basic faces.\n body-text-only : Emit only body-text. In concrete terms, - 1. Suppress calls to `hfy-page-header'and + 1. Suppress calls to `hfy-page-header' and `hfy-page-footer' 2. Pretend that `div-wrapper' option above is turned off @@ -650,7 +650,7 @@ STYLE is the inline CSS stylesheet (or tag referring to an external sheet)." var even = false; // if arguments are provided to specify the colors - // of the even & odd rows, then use the them; + // of the even & odd rows, then use them; // otherwise use the following defaults: var evenColor = arguments[1] ? arguments[1] : \"#fff\"; var oddColor = arguments[2] ? arguments[2] : \"#ddd\"; diff --git a/lisp/ibuf-ext.el b/lisp/ibuf-ext.el index 2a68f777d95..1ef7cb118cc 100644 --- a/lisp/ibuf-ext.el +++ b/lisp/ibuf-ext.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -375,7 +375,7 @@ format. See `ibuffer-update-saved-filters-format' and (let ((fixed (ibuffer-update-saved-filters-format ibuffer-saved-filters))) (prog1 (setq ibuffer-saved-filters (cdr fixed)) - (when-let (old-format-detected (car fixed)) + (when-let* ((old-format-detected (car fixed))) (let ((warning-series t) (updated-form (with-output-to-string @@ -1033,8 +1033,11 @@ group definitions by setting `ibuffer-filter-groups' to nil." (ibuffer-jump-to-buffer (buffer-name buf))))) (defun ibuffer-push-filter (filter-specification) - "Add FILTER-SPECIFICATION to `ibuffer-filtering-qualifiers'." - (push filter-specification ibuffer-filtering-qualifiers)) + "Add FILTER-SPECIFICATION to `ibuffer-filtering-qualifiers'. +If FILTER-SPECIFICATION is already in the list then return nil. Otherwise, +return the updated list." + (unless (member filter-specification ibuffer-filtering-qualifiers) + (push filter-specification ibuffer-filtering-qualifiers))) ;;;###autoload (defun ibuffer-decompose-filter () @@ -1283,6 +1286,12 @@ currently used by buffers." :reader (read-from-minibuffer "Filter by name (regexp): ")) (string-match qualifier (buffer-name buf))) +;;;###autoload (autoload 'ibuffer-filter-by-process "ibuf-ext") +(define-ibuffer-filter process + "Limit current view to buffers running a process." + (:description "process") + (get-buffer-process buf)) + ;;;###autoload (autoload 'ibuffer-filter-by-starred-name "ibuf-ext") (define-ibuffer-filter starred-name "Limit current view to buffers with name beginning and ending diff --git a/lisp/ibuf-macs.el b/lisp/ibuf-macs.el index 2e751cebd6e..c30067f2f58 100644 --- a/lisp/ibuf-macs.el +++ b/lisp/ibuf-macs.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -301,12 +301,16 @@ bound to the current value of the filter. (defun ,fn-name (qualifier) ,(or documentation "This filter is not documented.") (interactive (list ,reader)) - (ibuffer-push-filter (cons ',name qualifier)) - (message "%s" - (format ,(concat (format "Filter by %s added: " description) - " %s") - qualifier)) - (ibuffer-update nil t)) + (if (null (ibuffer-push-filter (cons ',name qualifier))) + (message "%s" + (format ,(concat (format "Filter by %s already applied: " description) + " %s") + qualifier)) + (message "%s" + (format ,(concat (format "Filter by %s added: " description) + " %s") + qualifier)) + (ibuffer-update nil t))) (push (list ',name ,description (lambda (buf qualifier) (condition-case nil diff --git a/lisp/ibuffer.el b/lisp/ibuffer.el index c83c21315a1..7ed77d29921 100644 --- a/lisp/ibuffer.el +++ b/lisp/ibuffer.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -522,6 +522,7 @@ directory, like `default-directory'." (define-key map (kbd "/ m") 'ibuffer-filter-by-used-mode) (define-key map (kbd "/ M") 'ibuffer-filter-by-derived-mode) (define-key map (kbd "/ n") 'ibuffer-filter-by-name) + (define-key map (kbd "/ E") 'ibuffer-filter-by-process) (define-key map (kbd "/ *") 'ibuffer-filter-by-starred-name) (define-key map (kbd "/ f") 'ibuffer-filter-by-filename) (define-key map (kbd "/ b") 'ibuffer-filter-by-basename) diff --git a/lisp/icomplete.el b/lisp/icomplete.el index a4153e806df..038f58f730d 100644 --- a/lisp/icomplete.el +++ b/lisp/icomplete.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/ido.el b/lisp/ido.el index 07a5bcf7229..96a362f7608 100644 --- a/lisp/ido.el +++ b/lisp/ido.el @@ -1,4 +1,4 @@ -;;; ido.el --- interactively do things with buffers and files +;;; ido.el --- interactively do things with buffers and files -*- lexical-binding: t -*- ;; Copyright (C) 1996-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -443,7 +443,7 @@ Possible values: `other-window' Show new file in another window (same frame) `display' Display file in another window without selecting to it `other-frame' Show new file in another frame -`maybe-frame' If a file is visible in another frame, prompt to ask if you +`maybe-frame' If a file is visible in another frame, prompt to ask if you want to see the file in the same window of the current frame or in the other frame `raise-frame' If a file is visible in another frame, raise that @@ -497,7 +497,7 @@ as first char even if `ido-enable-prefix' is nil." :type 'boolean :group 'ido) -;; See http://debbugs.gnu.org/2042 for more info. +;; See https://debbugs.gnu.org/2042 for more info. (defcustom ido-buffer-disable-smart-matches t "Non-nil means not to re-order matches for buffer switching. By default, Ido arranges matches in the following order: @@ -3678,7 +3678,7 @@ in this list." ido-temp-list))))) (ido-to-end ;; move . files to end (delq nil (mapcar - (lambda (x) (if (string-equal (substring x 0 1) ".") x)) + (lambda (x) (if (string-match "\\`\\." x) x)) ido-temp-list))) (if (and default (member default ido-temp-list)) (if (or ido-rotate-temp ido-rotate-file-list-default) @@ -4302,7 +4302,7 @@ For details of keybindings, see `ido-find-file'." ;;;###autoload (defun ido-find-alternate-file () - "Switch to another file and show it in another window. + "Find another file, select its buffer, kill previous buffer. The file name is selected interactively by typing a substring. For details of keybindings, see `ido-find-file'." (interactive) @@ -4701,7 +4701,7 @@ Modified from `icomplete-completions'." (if (and ido-use-faces comps) (let* ((fn (ido-name (car comps))) (ln (length fn))) - (setq first (format "%s" fn)) + (setq first (copy-sequence fn)) (put-text-property 0 ln 'face (if (= (length comps) 1) (if ido-incomplete-regexp @@ -4835,7 +4835,7 @@ Modified from `icomplete-completions'." (put 'dired 'ido 'dir) (put 'dired-other-window 'ido 'dir) (put 'dired-other-frame 'ido 'dir) -;; See http://debbugs.gnu.org/11954 for reasons. +;; See https://debbugs.gnu.org/11954 for reasons. (put 'dired-do-copy 'ido 'ignore) (put 'dired-do-rename 'ido 'ignore) diff --git a/lisp/ielm.el b/lisp/ielm.el index 42b065fe62d..4ec195528c7 100644 --- a/lisp/ielm.el +++ b/lisp/ielm.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/iimage.el b/lisp/iimage.el index abb88ec5029..7226476fac0 100644 --- a/lisp/iimage.el +++ b/lisp/iimage.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/image-dired.el b/lisp/image-dired.el index 49dba52c884..175d9df5e8c 100644 --- a/lisp/image-dired.el +++ b/lisp/image-dired.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -582,10 +582,11 @@ Create the thumbnails directory if it does not exist." "Return the image descriptor for a thumbnail of image file FILE." (unless (string-match (image-file-name-regexp) file) (error "%s is not a valid image file" file)) - (let ((thumb-file (image-dired-thumb-name file))) - (unless (and (file-exists-p thumb-file) - (<= (float-time (nth 5 (file-attributes file))) - (float-time (nth 5 (file-attributes thumb-file))))) + (let* ((thumb-file (image-dired-thumb-name file)) + (thumb-attr (file-attributes thumb-file))) + (when (or (not thumb-attr) + (time-less-p (nth 5 thumb-attr) + (nth 5 (file-attributes file)))) (image-dired-create-thumb file thumb-file)) (create-image thumb-file) ;; (list 'image :type 'jpeg @@ -748,7 +749,8 @@ Increase at own risk.") 'image-dired-cmd-create-thumbnail-program) (let* ((width (int-to-string (image-dired-thumb-size 'width))) (height (int-to-string (image-dired-thumb-size 'height))) - (modif-time (floor (float-time (nth 5 (file-attributes original-file))))) + (modif-time (format-time-string + "%s" (nth 5 (file-attributes original-file)))) (thumbnail-nq8-file (replace-regexp-in-string ".png\\'" "-nq8.png" thumbnail-file)) (spec diff --git a/lisp/image-file.el b/lisp/image-file.el index 33cea95d538..285151df90a 100644 --- a/lisp/image-file.el +++ b/lisp/image-file.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/image-mode.el b/lisp/image-mode.el index 4b92e8673a9..87d18fd3c47 100644 --- a/lisp/image-mode.el +++ b/lisp/image-mode.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/image.el b/lisp/image.el index 8cea7fb2c8b..ed32307ae24 100644 --- a/lisp/image.el +++ b/lisp/image.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -34,8 +34,8 @@ (defconst image-type-header-regexps `(("\\`/[\t\n\r ]*\\*.*XPM.\\*/" . xpm) ("\\`P[1-6]\\(?:\ -\\(?:\\(?:#[^\r\n]*[\r\n]\\)?[[:space:]]\\)+\ -\\(?:\\(?:#[^\r\n]*[\r\n]\\)?[0-9]\\)+\ +\\(?:\\(?:#[^\r\n]*[\r\n]\\)*[[:space:]]\\)+\ +\\(?:\\(?:#[^\r\n]*[\r\n]\\)*[0-9]\\)+\ \\)\\{2\\}" . pbm) ("\\`GIF8[79]a" . gif) ("\\`\x89PNG\r\n\x1a\n" . png) @@ -976,11 +976,12 @@ default is 20%." image)) (defun image--get-imagemagick-and-warn () - (unless (fboundp 'imagemagick-types) + (unless (or (fboundp 'imagemagick-types) (featurep 'ns)) (error "Can't rescale images without ImageMagick support")) (let ((image (image--get-image))) (image-flush image) - (plist-put (cdr image) :type 'imagemagick) + (when (fboundp 'imagemagick-types) + (plist-put (cdr image) :type 'imagemagick)) image)) (defun image--change-size (factor) diff --git a/lisp/image/compface.el b/lisp/image/compface.el index f4c3d5f4df0..ccbd0a3e3b5 100644 --- a/lisp/image/compface.el +++ b/lisp/image/compface.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/image/gravatar.el b/lisp/image/gravatar.el index 54ca3be96ae..6173c8527eb 100644 --- a/lisp/image/gravatar.el +++ b/lisp/image/gravatar.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -77,11 +77,7 @@ (not (file-exists-p (url-cache-create-filename url)))) (t (let ((cache-time (url-is-cached url))) (if cache-time - (time-less-p - (time-add - cache-time - gravatar-cache-ttl) - (current-time)) + (time-less-p (time-add cache-time gravatar-cache-ttl) nil) t))))) (defun gravatar-get-data () diff --git a/lisp/imenu.el b/lisp/imenu.el index c1fd4005ab6..e2c946c3a06 100644 --- a/lisp/imenu.el +++ b/lisp/imenu.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/indent.el b/lisp/indent.el index e7a30b885d7..d5ba0bd8491 100644 --- a/lisp/indent.el +++ b/lisp/indent.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/info-look.el b/lisp/info-look.el index 6963c782704..f52f48edec2 100644 --- a/lisp/info-look.el +++ b/lisp/info-look.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -31,9 +31,9 @@ ;; ;; Scheme: <URL:http://groups.csail.mit.edu/mac/ftpdir/scm/r5rs.info.tar.gz> ;; LaTeX: -;; <URL:ftp://ctan.tug.org/tex-archive/info/latex2e-help-texinfo/latex2e.texi> +;; <URL:http://ctan.tug.org/tex-archive/info/latex2e-help-texinfo/latex2e.texi> ;; (or CTAN mirrors) -;; Perl: <URL:ftp://ftp.cpan.org/pub/CPAN/doc/manual/texinfo/> (or CPAN mirrors) +;; Perl: <URL:http://ftp.cpan.org/pub/CPAN/doc/manual/texinfo/> (or CPAN mirrors) ;; Traditionally, makeinfo quoted `like this', but version 5 and later ;; quotes 'like this' or ‘like this’. Doc specs with patterns @@ -959,7 +959,7 @@ Return nil if there is nothing appropriate in the buffer near point." :mode 'scheme-mode :regexp "[^()`'‘’,\" \t\n]+" :ignore-case t - ;; Aubrey Jaffer's rendition from <URL:ftp://ftp-swiss.ai.mit.edu/pub/scm> + ;; Aubrey Jaffer's rendition from <https://people.csail.mit.edu/jaffer/SCM> :doc-spec '(("(r5rs)Index" nil "^[ \t]+-+ [^:]+:[ \t]*" "\\b"))) diff --git a/lisp/info-xref.el b/lisp/info-xref.el index 8c029d46b30..4fc7c4f699a 100644 --- a/lisp/info-xref.el +++ b/lisp/info-xref.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/info.el b/lisp/info.el index a2071533d8f..0a4f672b9f2 100644 --- a/lisp/info.el +++ b/lisp/info.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -171,7 +171,11 @@ A header-line does not scroll with the rest of the buffer." ;; defvar and explicitly give it a standard-value property, and ;; call custom-initialize-delay on it. ;; The progn forces the autoloader to include the whole thing, not -;; just an abbreviated version. +;; just an abbreviated version. The value is initialized at startup +;; time, when command-line calls custom-reevaluate-setting on all +;; the defcustoms in custom-delayed-init-variables. This is +;; somewhat sub-optimal, as ideally this should be done when Info +;; mode is first invoked. ;;;###autoload (progn (defcustom Info-default-directory-list @@ -436,22 +440,33 @@ Each element has the form (INFO-FILE INDEX-NODE-NAMES-LIST).") (defvar Info-virtual-files nil "List of definitions of virtual Info files. -Each element of the list has the format (FILENAME (OPERATION . HANDLER) ...) -where FILENAME is a regexp that matches a class of virtual Info file names. -It should be carefully chosen to not cause file name clashes with -existing file names. OPERATION is one of the following operation -symbols `find-file', `find-node', `toc-nodes' that define what HANDLER -function to call instead of calling the default corresponding function -to override it.") +Each element of the list has the form (FILENAME (OPERATION . HANDLER) EXTRA) +where FILENAME is a regexp that matches a class of virtual Info file names, +it should be carefully chosen to not cause file name clashes with +existing file names; +OPERATION is one of the symbols `find-file', `find-node', `toc-nodes'; +and HANDLER is a function to call when OPERATION is invoked on a +virtual Info file. +EXTRA, if present, is one or more cons cells specifying extra +attributes important to some applications which use this data. +For example, desktop saving and desktop restoring use the `slow' +attribute to avoid restoration of nodes that could be expensive +to compute.") (defvar Info-virtual-nodes nil "List of definitions of virtual Info nodes. -Each element of the list has the format (NODENAME (OPERATION . HANDLER) ...) -where NODENAME is a regexp that matches a class of virtual Info node names. -It should be carefully chosen to not cause node name clashes with -existing node names. OPERATION is one of the following operation -symbols `find-node' that define what HANDLER function to call instead -of calling the default corresponding function to override it.") +Each element of the list has the form (NODENAME (OPERATION . HANDLER) EXTRA) +where NODENAME is a regexp that matches a class of virtual Info node names, +it should be carefully chosen to not cause node name clashes with +existing node names; +OPERATION is the symbol `find-node'; +and HANDLER is a function to call when OPERATION is invoked on a +virtual Info node. +EXTRA, if present, is one or more cons cells specifying extra +attributes important to some applications which use this data. +For example, desktop saving and desktop restoring use the `slow' +attribute to avoid restoration of nodes that could be expensive +to compute.") (defvar-local Info-current-node-virtual nil "Non-nil if the current Info node is virtual.") @@ -634,7 +649,7 @@ Do the right thing if the file has been compressed or zipped." (attribs-new (and (stringp fullname) (file-attributes fullname))) (modtime-new (and attribs-new (nth 5 attribs-new)))) (when (and modtime-old modtime-new - (> (float-time modtime-new) (float-time modtime-old))) + (time-less-p modtime-old modtime-new)) (setq Info-index-nodes (remove (assoc (or Info-current-file filename) Info-index-nodes) Info-index-nodes)) @@ -1332,7 +1347,7 @@ is non-nil)." ;; Shouldn't really happen, but sometimes does, ;; eg on Debian systems with buggy packages; ;; so may as well try it. - ;; http://lists.gnu.org/archive/html/emacs-devel/2012-03/msg00005.html + ;; https://lists.gnu.org/r/emacs-devel/2012-03/msg00005.html (progn (setq file (expand-file-name "dir.gz" truename)) (file-attributes file))))) (setq dirs-done @@ -4008,7 +4023,7 @@ If FORK is non-nil, it is passed to `Info-goto-node'." (define-key map "h" 'Info-help) ;; This is for compatibility with standalone info (>~ version 5.2). ;; Though for some time, standalone info had H and h reversed. - ;; See <http://debbugs.gnu.org/16455>. + ;; See <https://debbugs.gnu.org/16455>. (define-key map "H" 'describe-mode) (define-key map "i" 'Info-index) (define-key map "I" 'Info-virtual-index) @@ -4650,7 +4665,7 @@ first line or header line, and for breadcrumb links.") (if (string-equal (downcase tag) "node") (put-text-property nbeg nend 'font-lock-face 'info-header-node) (put-text-property nbeg nend 'font-lock-face 'info-header-xref) - (put-text-property tbeg nend 'mouse-face 'highlight) + (put-text-property tbeg nend 'mouse-face 'header-line-highlight) (put-text-property tbeg nend 'help-echo (concat "mouse-2: Go to node " diff --git a/lisp/informat.el b/lisp/informat.el index b35e2ed379d..a1ed7a94843 100644 --- a/lisp/informat.el +++ b/lisp/informat.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/international/ccl.el b/lisp/international/ccl.el index aa9bd2d11c3..0ac79562e23 100644 --- a/lisp/international/ccl.el +++ b/lisp/international/ccl.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/international/characters.el b/lisp/international/characters.el index 33cb3d85223..51d8765f8b0 100644 --- a/lisp/international/characters.el +++ b/lisp/international/characters.el @@ -24,7 +24,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -148,6 +148,7 @@ with L, LRE, or LRO Unicode bidi character type.") (modify-category-entry '(#xF900 . #xFAFF) ?C) (modify-category-entry '(#xF900 . #xFAFF) ?c) (modify-category-entry '(#xF900 . #xFAFF) ?|) +(modify-category-entry '(#x1B170 . #x1B2FF) ?c) (modify-category-entry '(#x20000 . #x2FFFF) ?|) (modify-category-entry '(#x20000 . #x2FFFF) ?C) (modify-category-entry '(#x20000 . #x2FFFF) ?c) @@ -221,6 +222,8 @@ with L, LRE, or LRO Unicode bidi character type.") (modify-category-entry #x30A0 ?H) (modify-category-entry #x30FC ?H) +(modify-category-entry '(#x1B000 . #x1B1FF) ?j) + ;; JISX0208 (map-charset-chars #'modify-syntax-entry 'japanese-jisx0208 "_" #x2121 #x227E) @@ -1196,10 +1199,11 @@ with L, LRE, or LRO Unicode bidi character type.") (#xFE30 . #xFE6F) (#xFF01 . #xFF60) (#xFFE0 . #xFFE6) - (#x16FE0 . #x16FE0) + (#x16FE0 . #x16FE1) (#x17000 . #x187EC) (#x18800 . #x18AF2) - (#x1B000 . #x1B001) + (#x1B000 . #x1B11E) + (#x1B170 . #x1B2FB) (#x1F004 . #x1F004) (#x1F0CF . #x1F0CF) (#x1F18E . #x1F18E) @@ -1229,15 +1233,13 @@ with L, LRE, or LRO Unicode bidi character type.") (#x1F6CC . #x1F6CC) (#x1F6D0 . #x1F6D2) (#x1F6EB . #x1F6EC) - (#x1F6F4 . #x1F6F6) - (#x1F910 . #x1F91E) - (#x1F920 . #x1F927) - (#x1F930 . #x1F930) - (#x1F933 . #x1F93E) - (#x1F940 . #x1F94B) - (#x1F950 . #x1F95E) - (#x1F980 . #x1F991) + (#x1F6F4 . #x1F6F8) + (#x1F910 . #x1F93E) + (#x1F940 . #x1F94C) + (#x1F950 . #x1F96B) + (#x1F980 . #x1F997) (#x1F9C0 . #x1F9C0) + (#x1F9D0 . #x1F9E6) (#x20000 . #x2FFFF) (#x30000 . #x3FFFF)))) (dolist (elt l) diff --git a/lisp/international/fontset.el b/lisp/international/fontset.el index e023d253693..c6c62ef0a0c 100644 --- a/lisp/international/fontset.el +++ b/lisp/international/fontset.el @@ -24,7 +24,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -227,9 +227,12 @@ (modi #x11600) (takri #x11680) (warang-citi #x118A1) + (zanabazar-square #x11A00) + (soyombo #x11A50) (pau-cin-hau #x11AC0) (bhaiksuki #x11C00) (marchen #x11C72) + (masaram-gondi #x11D00) (cuneiform #x12000) (cuneiform-numbers-and-punctuation #x12400) (mro #x16A40) @@ -237,6 +240,7 @@ (pahawh-hmong #x16B11) (tangut #x17000) (tangut-components #x18800) + (nushu #x1B170) (duployan-shorthand #x1BC20) (byzantine-musical-symbol #x1D000) (musical-symbol #x1D100) diff --git a/lisp/international/isearch-x.el b/lisp/international/isearch-x.el index 40bdb38b223..dce323e4296 100644 --- a/lisp/international/isearch-x.el +++ b/lisp/international/isearch-x.el @@ -24,7 +24,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/international/iso-ascii.el b/lisp/international/iso-ascii.el index 39f1e9f46ba..327657512a4 100644 --- a/lisp/international/iso-ascii.el +++ b/lisp/international/iso-ascii.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/international/iso-cvt.el b/lisp/international/iso-cvt.el index ebf90a31224..69969d68576 100644 --- a/lisp/international/iso-cvt.el +++ b/lisp/international/iso-cvt.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; This lisp code is a general framework for translating various diff --git a/lisp/international/iso-transl.el b/lisp/international/iso-transl.el index a665a39b63c..86958474828 100644 --- a/lisp/international/iso-transl.el +++ b/lisp/international/iso-transl.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/international/ja-dic-cnv.el b/lisp/international/ja-dic-cnv.el index e80b1b28810..f5220b04cd4 100644 --- a/lisp/international/ja-dic-cnv.el +++ b/lisp/international/ja-dic-cnv.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -125,10 +125,10 @@ ;; Search postfix entries. (while (re-search-forward "^[#<>?]\\(\\(\\cH\\|$B!<(B\\)+\\) " nil t) - (let ((kana (match-string 1)) + (let ((kana (match-string-no-properties 1)) str candidates) (while (looking-at "/[#0-9 ]*\\([^/\n]*\\)/") - (setq str (match-string 1)) + (setq str (match-string-no-properties 1)) (if (not (member str candidates)) (setq candidates (cons str candidates))) (goto-char (match-end 1))) @@ -158,10 +158,10 @@ "(skkdic-set-prefix\n")) (save-excursion (while (re-search-forward "^\\(\\(\\cH\\|$B!<(B\\)+\\)[<>?] " nil t) - (let ((kana (match-string 1)) + (let ((kana (match-string-no-properties 1)) str candidates) (while (looking-at "/\\([^/\n]+\\)/") - (setq str (match-string 1)) + (setq str (match-string-no-properties 1)) (if (not (member str candidates)) (setq candidates (cons str candidates))) (goto-char (match-end 1))) @@ -180,8 +180,8 @@ (let (candidates) (goto-char from) (while (re-search-forward "/[^/ \n]+" to t) - (setq candidates (cons (buffer-substring (1+ (match-beginning 0)) - (match-end 0)) + (setq candidates (cons (buffer-substring-no-properties + (1+ (match-beginning 0)) (match-end 0)) candidates))) candidates)) @@ -251,12 +251,16 @@ ;; Return list of candidates which excludes some from CANDIDATES. ;; Excluded candidates can be derived from another entry. +(defconst skkdic--japanese-category-set (make-category-set "j")) + (defun skkdic-reduced-candidates (skkbuf kana candidates) (let (elt l) (while candidates (setq elt (car candidates)) (if (or (= (length elt) 1) - (and (string-match "^\\cj" elt) + (and (bool-vector-subsetp + skkdic--japanese-category-set + (char-category-set (aref elt 0))) (not (skkdic-breakup-string skkbuf kana elt 0 (length elt) 'first)))) (setq l (cons elt l))) @@ -267,24 +271,18 @@ (defvar skkdic-okuri-nasi-entries-count 0) (defun skkdic-collect-okuri-nasi () - (message "Collecting OKURI-NASI entries ...") (save-excursion - (let ((prev-ratio 0) - ratio) + (let ((progress (make-progress-reporter "Collecting OKURI-NASI entries" + (point) (point-max) + nil 10))) (while (re-search-forward "^\\(\\(\\cH\\|$B!<(B\\)+\\) \\(/\\cj.*\\)/$" nil t) - (let ((kana (match-string 1)) + (let ((kana (match-string-no-properties 1)) (candidates (skkdic-get-candidate-list (match-beginning 3) (match-end 3)))) (setq skkdic-okuri-nasi-entries - (cons (cons kana candidates) skkdic-okuri-nasi-entries) - skkdic-okuri-nasi-entries-count - (1+ skkdic-okuri-nasi-entries-count)) - (setq ratio (floor (* (point) 100.0) (point-max))) - (if (/= (/ prev-ratio 10) (/ ratio 10)) - (progn - (message "collected %2d%% ..." ratio) - (setq prev-ratio ratio))) + (cons (cons kana candidates) skkdic-okuri-nasi-entries)) + (progress-reporter-update progress (point)) (while candidates (let ((entry (lookup-nested-alist (car candidates) skkdic-word-list nil nil t))) @@ -292,26 +290,24 @@ (setcar entry (cons kana (car entry))) (set-nested-alist (car candidates) (list kana) skkdic-word-list))) - (setq candidates (cdr candidates)))))))) + (setq candidates (cdr candidates))))) + (setq skkdic-okuri-nasi-entries-count (length skkdic-okuri-nasi-entries)) + (progress-reporter-done progress)))) (defun skkdic-convert-okuri-nasi (skkbuf buf) - (message "Processing OKURI-NASI entries ...") (with-current-buffer buf (insert ";; Setting okuri-nasi entries.\n" "(skkdic-set-okuri-nasi\n") (let ((l (nreverse skkdic-okuri-nasi-entries)) - (count 0) - (prev-ratio 0) - ratio) + (progress (make-progress-reporter "Processing OKURI-NASI entries" + 0 skkdic-okuri-nasi-entries-count + nil 10)) + (count 0)) (while l (let ((kana (car (car l))) (candidates (cdr (car l)))) - (setq ratio (floor (* count 100.0) skkdic-okuri-nasi-entries-count) - count (1+ count)) - (if (/= (/ prev-ratio 10) (/ ratio 10)) - (progn - (message "processed %2d%% ..." ratio) - (setq prev-ratio ratio))) + (setq count (1+ count)) + (progress-reporter-update progress count) (if (setq candidates (skkdic-reduced-candidates skkbuf kana candidates)) (progn @@ -320,7 +316,8 @@ (insert " " (car candidates)) (setq candidates (cdr candidates))) (insert "\"\n")))) - (setq l (cdr l)))) + (setq l (cdr l))) + (progress-reporter-done progress)) (insert ")\n\n"))) (defun skkdic-convert (filename &optional dirname) @@ -467,7 +464,7 @@ To get complete usage, invoke: (i (match-end 0)) candidates) (while (string-match "[^ ]+" entry i) - (setq candidates (cons (match-string 0 entry) candidates)) + (setq candidates (cons (match-string-no-properties 0 entry) candidates)) (setq i (match-end 0))) (cons (skkdic-get-kana-compact-codes kana) candidates))) diff --git a/lisp/international/ja-dic-utl.el b/lisp/international/ja-dic-utl.el index 7005ba85726..86ba3749df8 100644 --- a/lisp/international/ja-dic-utl.el +++ b/lisp/international/ja-dic-utl.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/international/kinsoku.el b/lisp/international/kinsoku.el index f87d2e9ed16..9f20b3e978e 100644 --- a/lisp/international/kinsoku.el +++ b/lisp/international/kinsoku.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/international/kkc.el b/lisp/international/kkc.el index 17a3b6c2dbf..261c1c658c8 100644 --- a/lisp/international/kkc.el +++ b/lisp/international/kkc.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/international/latexenc.el b/lisp/international/latexenc.el index 4b09bfbd193..761b9643d96 100644 --- a/lisp/international/latexenc.el +++ b/lisp/international/latexenc.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/international/latin1-disp.el b/lisp/international/latin1-disp.el index d9b71c8f44b..79192486677 100644 --- a/lisp/international/latin1-disp.el +++ b/lisp/international/latin1-disp.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/international/mule-cmds.el b/lisp/international/mule-cmds.el index bdba8eeb112..9d22d6e8dd2 100644 --- a/lisp/international/mule-cmds.el +++ b/lisp/international/mule-cmds.el @@ -24,7 +24,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -354,11 +354,12 @@ This also sets the following values: (if (eq system-type 'darwin) ;; The file-name coding system on Darwin systems is always utf-8. - (setq default-file-name-coding-system 'utf-8) + (setq default-file-name-coding-system 'utf-8-unix) (if (and (default-value 'enable-multibyte-characters) (or (not coding-system) (coding-system-get coding-system 'ascii-compatible-p))) - (setq default-file-name-coding-system coding-system))) + (setq default-file-name-coding-system + (coding-system-change-eol-conversion coding-system 'unix)))) (setq default-terminal-coding-system coding-system) ;; Prevent default-terminal-coding-system from converting ^M to ^J. (setq default-keyboard-coding-system @@ -414,7 +415,7 @@ To prefer, for instance, utf-8, say the following: (coding-system-change-eol-conversion base eol-type))) (set-default-coding-systems base) (if (called-interactively-p 'interactive) - (or (eq base default-file-name-coding-system) + (or (eq base (coding-system-type default-file-name-coding-system)) (message "The default value of `file-name-coding-system' was not changed because the specified coding system is not suitable for file names."))))) (defvar sort-coding-systems-predicate nil @@ -1482,9 +1483,7 @@ If INPUT-METHOD is nil, deactivate any current input method." current-input-method-title nil) (funcall deactivate-current-input-method-function)) (unwind-protect - (run-hooks - 'input-method-inactivate-hook ; for backward compatibility - 'input-method-deactivate-hook) + (run-hooks 'input-method-deactivate-hook) (setq current-input-method nil) (force-mode-line-update))))) @@ -1799,9 +1798,9 @@ The default status is as follows: (set-default-coding-systems nil) (setq default-sendmail-coding-system 'iso-latin-1) - ;; On Darwin systems, this should be utf-8, but when this file is loaded - ;; utf-8 is not yet defined, so we set it in set-locale-environment instead. - (setq default-file-name-coding-system 'iso-latin-1) + ;; On Darwin systems, this should be utf-8-unix, but when this file is loaded + ;; that is not yet defined, so we set it in set-locale-environment instead. + (setq default-file-name-coding-system 'iso-latin-1-unix) ;; Preserve eol-type from existing default-process-coding-systems. ;; On non-unix-like systems in particular, these may have been set ;; carefully by the user, or by the startup code, to deal with the @@ -2724,7 +2723,7 @@ See also `locale-charset-language-names', `locale-language-names', (when (eq system-type 'darwin) ;; On Darwin, file names are always encoded in utf-8, no matter ;; the locale. - (setq default-file-name-coding-system 'utf-8) + (setq default-file-name-coding-system 'utf-8-unix) ;; macOS's Terminal.app by default uses utf-8 regardless of ;; the locale. (when (and (null window-system) @@ -2924,10 +2923,10 @@ on encoding." (make-obsolete-variable 'nonascii-translation-table "do not use it." "23.1") (defvar ucs-names nil - "Alist of cached (CHAR-NAME . CHAR-CODE) pairs.") + "Hash table of cached CHAR-NAME keys to CHAR-CODE values.") (defun ucs-names () - "Return alist of (CHAR-NAME . CHAR-CODE) pairs cached in `ucs-names'." + "Return table of CHAR-NAME keys and CHAR-CODE values cached in `ucs-names'." (or ucs-names (let ((ranges '((#x0000 . #x33FF) @@ -2945,46 +2944,49 @@ on encoding." ;; (#x17000 . #x187FF) Tangut Ideographs ;; (#x18800 . #x18AFF) Tangut Components ;; (#x18B00 . #x1AFFF) unused - (#x1B000 . #x1B0FF) - ;; (#x1B100 . #x1BBFF) unused + (#x1B000 . #x1B12F) + ;; (#x1B130 . #x1B16F) unused + (#x1B170 . #x1B2FF) + ;; (#x1B300 . #x1BBFF) unused (#x1BC00 . #x1BCAF) ;; (#x1BCB0 . #x1CFFF) unused (#x1D000 . #x1FFFF) ;; (#x20000 . #xDFFFF) CJK Ideograph Extension A, B, etc, unused (#xE0000 . #xE01FF))) (gc-cons-threshold 10000000) - names) - (dolist (range ranges) - (let ((c (car range)) - (end (cdr range))) - (while (<= c end) + (names (make-hash-table :size 42943 :test #'equal))) + (dolist (range ranges) + (let ((c (car range)) + (end (cdr range))) + (while (<= c end) (let ((new-name (get-char-code-property c 'name)) (old-name (get-char-code-property c 'old-name))) - ;; In theory this code could end up pushing an "old-name" that - ;; shadows a "new-name" but in practice every time an - ;; `old-name' conflicts with a `new-name', the newer one has a - ;; higher code, so it gets pushed later! - (if new-name (push (cons new-name c) names)) - (if old-name (push (cons old-name c) names)) - (setq c (1+ c)))))) - ;; Special case for "BELL" which is apparently the only char which - ;; doesn't have a new name and whose old-name is shadowed by a newer - ;; char with that name. - (setq ucs-names `(("BELL (BEL)" . 7) ,@names))))) + ;; In theory this code could end up pushing an "old-name" that + ;; shadows a "new-name" but in practice every time an + ;; `old-name' conflicts with a `new-name', the newer one has a + ;; higher code, so it gets pushed later! + (if new-name (puthash new-name c names)) + (if old-name (puthash old-name c names)) + (setq c (1+ c)))))) + ;; Special case for "BELL" which is apparently the only char which + ;; doesn't have a new name and whose old-name is shadowed by a newer + ;; char with that name. + (puthash "BELL (BEL)" ?\a names) + (setq ucs-names names)))) (defun mule--ucs-names-annotation (name) ;; FIXME: It would be much better to add this annotation before rather than ;; after the char name, so the annotations are aligned. ;; FIXME: The default behavior of displaying annotations in italics ;; doesn't work well here. - (let ((char (assoc name ucs-names))) - (when char (format " (%c)" (cdr char))))) + (let ((char (gethash name ucs-names))) + (when char (format " (%c)" char)))) (defun char-from-name (string &optional ignore-case) "Return a character as a number from its Unicode name STRING. If optional IGNORE-CASE is non-nil, ignore case in STRING. Return nil if STRING does not name a character." - (or (cdr (assoc-string string (ucs-names) ignore-case)) + (or (gethash (if ignore-case (upcase string) string) (ucs-names)) (let ((minus (string-match-p "-[0-9A-F]+\\'" string))) (when minus ;; Parse names like "VARIATION SELECTOR-17" and "CJK diff --git a/lisp/international/mule-conf.el b/lisp/international/mule-conf.el index 68a412f206e..d4bdfd49583 100644 --- a/lisp/international/mule-conf.el +++ b/lisp/international/mule-conf.el @@ -23,7 +23,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -1175,7 +1175,8 @@ :short-name "CNS11643-15" :long-name "CNS11643-15 (Chinese traditional)" :code-space [33 126 33 126] - :code-offset #x27A000) + :code-offset #x27A000 + :unify-map "CNS-F") (unify-charset 'chinese-gb2312) (unify-charset 'chinese-gbk) @@ -1186,6 +1187,7 @@ (unify-charset 'chinese-cns11643-5) (unify-charset 'chinese-cns11643-6) (unify-charset 'chinese-cns11643-7) +(unify-charset 'chinese-cns11643-15) (unify-charset 'big5) (unify-charset 'chinese-big5-1) (unify-charset 'chinese-big5-2) diff --git a/lisp/international/mule-diag.el b/lisp/international/mule-diag.el index c274621f772..e1e60d192ed 100644 --- a/lisp/international/mule-diag.el +++ b/lisp/international/mule-diag.el @@ -24,7 +24,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -838,7 +838,8 @@ The font must be already used by Emacs." (interactive "sFont name (default current choice for ASCII chars): ") (or (and window-system (fboundp 'fontset-list)) (error "No fonts being used")) - (let (font-info) + (let ((xref-item (list #'describe-font fontname)) + font-info) (if (or (not fontname) (= (length fontname) 0)) (setq fontname (face-attribute 'default :font))) (setq font-info (font-info fontname)) @@ -850,6 +851,7 @@ The font must be already used by Emacs." ;; this problem. (message "No information about \"%s\"" (font-xlfd-name fontname)) (message "No matching font found")) + (help-setup-xref xref-item (called-interactively-p 'interactive)) (with-output-to-temp-buffer "*Help*" (describe-font-internal font-info))))) diff --git a/lisp/international/mule-util.el b/lisp/international/mule-util.el index e34b01c3064..ca84a230779 100644 --- a/lisp/international/mule-util.el +++ b/lisp/international/mule-util.el @@ -24,7 +24,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -143,20 +143,43 @@ longer than KEYSEQ. See the documentation of `nested-alist-p' for more detail." (or (nested-alist-p alist) (error "Invalid argument %s" alist)) - (let ((islist (listp keyseq)) - (len (or len (length keyseq))) - (i 0) - key-elt slot) - (while (< i len) - (if (null (nested-alist-p alist)) - (error "Keyseq %s is too long for this nested alist" keyseq)) - (setq key-elt (if islist (nth i keyseq) (aref keyseq i))) - (setq slot (assoc key-elt (cdr alist))) - (unless slot - (setq slot (cons key-elt (list t))) - (setcdr alist (cons slot (cdr alist)))) - (setq alist (cdr slot)) - (setq i (1+ i))) + (let ((len (or len (length keyseq))) + (i 0)) + (cond + ((stringp keyseq) ; We can use `assq' for characters. + (while (< i len) + (if (null (nested-alist-p alist)) + (error "Keyseq %s is too long for this nested alist" keyseq)) + (let* ((key-elt (aref keyseq i)) + (slot (assq key-elt (cdr alist)))) + (unless slot + (setq slot (list key-elt t)) + (push slot (cdr alist))) + (setq alist (cdr slot))) + (setq i (1+ i)))) + ((arrayp keyseq) + (while (< i len) + (if (null (nested-alist-p alist)) + (error "Keyseq %s is too long for this nested alist" keyseq)) + (let* ((key-elt (aref keyseq i)) + (slot (assoc key-elt (cdr alist)))) + (unless slot + (setq slot (list key-elt t)) + (push slot (cdr alist))) + (setq alist (cdr slot))) + (setq i (1+ i)))) + ((listp keyseq) + (while (< i len) + (if (null (nested-alist-p alist)) + (error "Keyseq %s is too long for this nested alist" keyseq)) + (let* ((key-elt (pop keyseq)) + (slot (assoc key-elt (cdr alist)))) + (unless slot + (setq slot (list key-elt t)) + (push slot (cdr alist))) + (setq alist (cdr slot))) + (setq i (1+ i)))) + (t (signal 'wrong-type-argument (list keyseq)))) (setcar alist entry) (if branches (setcdr (last alist) branches)))) @@ -179,15 +202,23 @@ Optional 5th argument NIL-FOR-TOO-LONG non-nil means return nil (setq len (length keyseq))) (let ((i (or start 0))) (if (catch 'lookup-nested-alist-tag - (if (listp keyseq) - (while (< i len) - (if (setq alist (cdr (assoc (nth i keyseq) (cdr alist)))) - (setq i (1+ i)) - (throw 'lookup-nested-alist-tag t)))) - (while (< i len) - (if (setq alist (cdr (assoc (aref keyseq i) (cdr alist)))) - (setq i (1+ i)) - (throw 'lookup-nested-alist-tag t)))) + (cond ((stringp keyseq) ; We can use `assq' for characters. + (while (< i len) + (if (setq alist (cdr (assq (aref keyseq i) (cdr alist)))) + (setq i (1+ i)) + (throw 'lookup-nested-alist-tag t)))) + ((arrayp keyseq) + (while (< i len) + (if (setq alist (cdr (assoc (aref keyseq i) (cdr alist)))) + (setq i (1+ i)) + (throw 'lookup-nested-alist-tag t)))) + ((listp keyseq) + (setq keyseq (nthcdr i keyseq)) + (while (< i len) + (if (setq alist (cdr (assoc (pop keyseq) (cdr alist)))) + (setq i (1+ i)) + (throw 'lookup-nested-alist-tag t)))) + (t (signal 'wrong-type-argument (list keyseq))))) ;; KEYSEQ is too long. (if nil-for-too-long nil i) alist))) diff --git a/lisp/international/mule.el b/lisp/international/mule.el index 6cfb7e6d457..857fa800eb4 100644 --- a/lisp/international/mule.el +++ b/lisp/international/mule.el @@ -24,7 +24,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -773,7 +773,7 @@ never used by the other charsets. If it is a list, the elements must be charsets, nil, 94, or 96. GN can be used by all the listed charsets. If the list contains 94, any iso-2022 charset whose code-space ranges are 94 long can be designated -to GN. If the list contains 96, any charsets whose whose ranges are +to GN. If the list contains 96, any charsets whose ranges are 96 long can be designated to GN. If the first element is a charset, that charset is initially designated to GN. diff --git a/lisp/international/ogonek.el b/lisp/international/ogonek.el index ef3a980f19a..bdd621fe9a1 100644 --- a/lisp/international/ogonek.el +++ b/lisp/international/ogonek.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -301,13 +301,12 @@ Store the name in the parameter-variable DEFAULT-NAME-VAR. PROMPT is a string to be shown when the user is asked for a name." (let ((encoding (completing-read - (format "%s (default %s): " prompt (eval default-name-var)) + (format "%s (default %s): " prompt (symbol-value default-name-var)) ogonek-name-encoding-alist nil t))) - ;; change the default name to the one just read - (set default-name-var - (if (string= encoding "") (eval default-name-var) encoding)) + ;; change the default name to the one just read, and ;; return the new default as the name you read - (eval default-name-var))) + (set default-name-var + (if (string= encoding "") (symbol-value default-name-var) encoding)))) (defun ogonek-read-prefix (prompt default-prefix-var) "Read a prefix character for prefix notation. diff --git a/lisp/international/quail.el b/lisp/international/quail.el index 036b80eb028..1bbbb174d49 100644 --- a/lisp/international/quail.el +++ b/lisp/international/quail.el @@ -24,7 +24,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -569,9 +569,7 @@ While this input method is active, the variable (setq describe-current-input-method-function nil) (quail-hide-guidance) (remove-hook 'post-command-hook 'quail-show-guidance t) - (run-hooks - 'quail-inactivate-hook ; for backward compatibility - 'quail-deactivate-hook)) + (run-hooks 'quail-deactivate-hook)) (kill-local-variable 'input-method-function)) ;; Let's activate Quail input method. (if (null quail-current-package) @@ -2515,7 +2513,7 @@ package to describe." (setq buffer-read-only nil) ;; Without this, a keyboard layout with R2L characters might be ;; displayed reversed, right to left. See the thread starting at - ;; http://lists.gnu.org/archive/html/emacs-devel/2012-03/msg00062.html + ;; https://lists.gnu.org/r/emacs-devel/2012-03/msg00062.html ;; for a description of one such situation. (setq bidi-paragraph-direction 'left-to-right) (insert "Input method: " (quail-name) @@ -3049,7 +3047,7 @@ of each directory." (while quail-dirs (setq dirname (car quail-dirs)) (when dirname - (setq pkg-list (directory-files dirname 'full "\\.el$" 'nosort)) + (setq pkg-list (directory-files dirname 'full "\\.el$")) (while pkg-list (message "Checking %s ..." (car pkg-list)) (with-temp-buffer diff --git a/lisp/international/rfc1843.el b/lisp/international/rfc1843.el index 9b918547141..494de1d5a99 100644 --- a/lisp/international/rfc1843.el +++ b/lisp/international/rfc1843.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -54,9 +54,7 @@ HZ-encoded are decoded." "HZ+ decoding support if non-nil. HZ+ specification (also known as HZP) is to provide a standardized 7-bit representation of mixed Big5, GB, and ASCII text for convenient -e-mail transmission, news posting, etc. -The document of HZ+ 0.78 specification can be found at -ftp://ftp.math.psu.edu/pub/simpson/chinese/hzp/hzp.doc" +e-mail transmission, news posting, etc." :type 'boolean :group 'mime) diff --git a/lisp/international/robin.el b/lisp/international/robin.el index 0ef90b18932..94d2bf18088 100644 --- a/lisp/international/robin.el +++ b/lisp/international/robin.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -413,9 +413,7 @@ While this input method is active, the variable (progn (setq robin-mode nil) (setq describe-current-input-method-function nil) - (run-hooks - 'robin-inactivate-hook ; for backward compatibility - 'robin-deactivate-hook)) + (run-hooks 'robin-deactivate-hook)) (kill-local-variable 'input-method-function)) ;; activate robin input method. diff --git a/lisp/international/titdic-cnv.el b/lisp/international/titdic-cnv.el index 130bc742a51..5c6db19bb37 100644 --- a/lisp/international/titdic-cnv.el +++ b/lisp/international/titdic-cnv.el @@ -24,7 +24,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -647,7 +647,7 @@ To get complete usage, invoke \"emacs -batch -f batch-titdic-convert -h\"." ;; details. ;; ;; You should have received a copy of the GNU General Public License along with -;; CCE. If not, see <http://www.gnu.org/licenses/>.") +;; CCE. If not, see <https://www.gnu.org/licenses/>.") ("chinese-ziranma" "$AWTH;(B" "ziranma.cin" cn-gb-2312 "ZIRANMA.el" @@ -675,7 +675,7 @@ To get complete usage, invoke \"emacs -batch -f batch-titdic-convert -h\"." ;; details. ;; ;; You should have received a copy of the GNU General Public License along with -;; CCE. If not, see <http://www.gnu.org/licenses/>.") +;; CCE. If not, see <https://www.gnu.org/licenses/>.") ("chinese-ctlau" "$AAuTA(B" "CTLau.html" cn-gb-2312 "CTLau.el" @@ -700,7 +700,7 @@ To get complete usage, invoke \"emacs -batch -f batch-titdic-convert -h\"." ;; # 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 <http://www.gnu.org/licenses/>.") +;; # along with this program. If not, see <https://www.gnu.org/licenses/>.") ("chinese-ctlaub" "$(0N,Gn(B" "CTLau-b5.html" big5 "CTLau-b5.el" @@ -725,7 +725,7 @@ To get complete usage, invoke \"emacs -batch -f batch-titdic-convert -h\"." ;; # 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 <http://www.gnu.org/licenses/>.") +;; # along with this program. If not, see <https://www.gnu.org/licenses/>.") )) ;; Generate a code of a Quail package in the current buffer from Tsang diff --git a/lisp/international/ucs-normalize.el b/lisp/international/ucs-normalize.el index b510fe1aec1..08231080f86 100644 --- a/lisp/international/ucs-normalize.el +++ b/lisp/international/ucs-normalize.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/international/utf-7.el b/lisp/international/utf-7.el index 16942ceceea..82f725cccbf 100644 --- a/lisp/international/utf-7.el +++ b/lisp/international/utf-7.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -79,7 +79,7 @@ ESC and SKIP-CHARS are adjusted for the normal and IMAP versions." (esc (if imap ?& ?+)) ;; These are characters which can be encoded asis. (skip-chars (if imap - "\t\n\r\x20-\x25\x27-\x7e" ; rfc2060 + "\t\n\r\x20-\x25\x27-\x7e" ; rfc2060 ;; This includes the rfc2152 optional set. ;; Perhaps it shouldn't (like iconv). "\t\n\r -*,-[]-}")) diff --git a/lisp/international/utf7.el b/lisp/international/utf7.el index f245d7eb696..68081b23a83 100644 --- a/lisp/international/utf7.el +++ b/lisp/international/utf7.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/isearch.el b/lisp/isearch.el index 5f34dcadb5d..13fa97ea71f 100644 --- a/lisp/isearch.el +++ b/lisp/isearch.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -128,9 +128,10 @@ a tab, a carriage return (control-M), a newline, and `]+'." "If t incremental search/query-replace can match hidden text. A nil value means don't match invisible text. When the value is `open', if the text matched is made invisible by -an overlay having an `invisible' property and that overlay has a property -`isearch-open-invisible', then incremental search will show the contents. -\(This applies when using `outline.el' and `hideshow.el'.) +an overlay having a non-nil `invisible' property, and that overlay +has a non-nil property `isearch-open-invisible', then incremental +search will show the hidden text. (This applies when using `outline.el' +and `hideshow.el'.) To temporarily change the value for an active incremental search, use \\<isearch-mode-map>\\[isearch-toggle-invisible]. diff --git a/lisp/isearchb.el b/lisp/isearchb.el index 16a08dc9e45..86275f80f85 100644 --- a/lisp/isearchb.el +++ b/lisp/isearchb.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/jit-lock.el b/lisp/jit-lock.el index 8537dae7f8b..33a941676db 100644 --- a/lisp/jit-lock.el +++ b/lisp/jit-lock.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -602,7 +602,7 @@ non-nil in a repeated invocation of this function." (save-restriction ;; Don't be blindsided by narrowing that starts in the middle ;; of a jit-lock-defer-multiline. - (widen) + (widen) (when (and (>= jit-lock-context-unfontify-pos (point-min)) (< jit-lock-context-unfontify-pos (point-max))) ;; If we're in text that matches a complex multi-line diff --git a/lisp/jka-cmpr-hook.el b/lisp/jka-cmpr-hook.el index 0dedaa5ba0d..b1bdc278fe5 100644 --- a/lisp/jka-cmpr-hook.el +++ b/lisp/jka-cmpr-hook.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/jka-compr.el b/lisp/jka-compr.el index 26a7cf506fd..07b9033e24e 100644 --- a/lisp/jka-compr.el +++ b/lisp/jka-compr.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -252,7 +252,8 @@ There should be no more than seven characters after the final `/'." "This routine will return the name of a new file." (make-temp-file jka-compr-temp-name-template)) -(defun jka-compr-write-region (start end file &optional append visit) +(defun jka-compr-write-region (start end file &optional + append visit lockname mustbenew) (let* ((filename (expand-file-name file)) (visit-file (if (stringp visit) (expand-file-name visit) filename)) (info (jka-compr-get-compression-info visit-file)) @@ -334,7 +335,8 @@ There should be no more than seven characters after the final `/'." (jka-compr-run-real-handler 'write-region (list (point-min) (point-max) filename - (and append can-append) 'dont)) + (and append can-append) 'dont + lockname mustbenew)) (erase-buffer)) ) (delete-file temp-file) @@ -365,7 +367,8 @@ There should be no more than seven characters after the final `/'." nil) (jka-compr-run-real-handler 'write-region - (list start end filename append visit))))) + (list start end filename append visit + lockname mustbenew))))) (defun jka-compr-insert-file-contents (file &optional visit beg end replace) diff --git a/lisp/json.el b/lisp/json.el index 3def94ce042..d5f05fed95f 100644 --- a/lisp/json.el +++ b/lisp/json.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -187,30 +187,30 @@ Unlike `reverse', this keeps the property-value pairs intact." ;; Reader utilities -(defsubst json-advance (&optional n) +(define-inline json-advance (&optional n) "Advance N characters forward." - (forward-char n)) + (inline-quote (forward-char ,n))) -(defsubst json-peek () +(define-inline json-peek () "Return the character at point." - (let ((char (char-after (point)))) - (or char :json-eof))) + (inline-quote (following-char))) -(defsubst json-pop () +(define-inline json-pop () "Advance past the character at point, returning it." - (let ((char (json-peek))) - (if (eq char :json-eof) - (signal 'json-end-of-file nil) - (json-advance) - char))) - -(defun json-skip-whitespace () + (inline-quote + (let ((char (json-peek))) + (if (zerop char) + (signal 'json-end-of-file nil) + (json-advance) + char)))) + +(define-inline json-skip-whitespace () "Skip past the whitespace at point." ;; See ;; https://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf ;; or https://tools.ietf.org/html/rfc7159#section-2 for the ;; definition of whitespace in JSON. - (skip-chars-forward "\t\r\n ")) + (inline-quote (skip-chars-forward "\t\r\n "))) @@ -304,7 +304,8 @@ KEYWORD is the keyword expected." (thing-at-point 'word))))) (json-advance)) keyword) - (unless (looking-at "\\(\\s-\\|[],}]\\|$\\)") + (json-skip-whitespace) + (unless (looking-at "\\([],}]\\|$\\)") (signal 'json-unknown-keyword (list (save-excursion (backward-word-strictly 1) @@ -381,7 +382,7 @@ representation will be parsed correctly." (special (cdr special)) ((not (eq char ?u)) char) ;; Special-case UTF-16 surrogate pairs, - ;; cf. https://tools.ietf.org/html/rfc7159#section-7. Note that + ;; cf. <https://tools.ietf.org/html/rfc7159#section-7>. Note that ;; this clause overlaps with the next one and therefore has to ;; come first. ((looking-at @@ -407,6 +408,8 @@ representation will be parsed correctly." (let ((characters '()) (char (json-peek))) (while (not (= char ?\")) + (when (< char 32) + (signal 'json-string-format (list (prin1-char char)))) (push (if (= char ?\\) (json-read-escaped-char) (json-pop)) @@ -415,7 +418,7 @@ representation will be parsed correctly." ;; Skip over the '"' (json-advance) (if characters - (apply 'string (nreverse characters)) + (concat (nreverse characters)) ""))) ;; String encoding @@ -469,11 +472,10 @@ Returns the updated object, which you should save, e.g.: (setq obj (json-add-to-object obj \"foo\" \"bar\")) Please see the documentation of `json-object-type' and `json-key-type'." (let ((json-key-type - (if (eq json-key-type nil) + (or json-key-type (cdr (assq json-object-type '((hash-table . string) (alist . symbol) - (plist . keyword)))) - json-key-type))) + (plist . keyword))))))) (setq key (cond ((eq json-key-type 'string) key) @@ -639,7 +641,9 @@ become JSON objects." (signal 'json-error (list 'bleah))))) ;; Skip over the "]" (json-advance) - (apply json-array-type (nreverse elements)))) + (pcase json-array-type + (`vector (nreverse (vconcat elements))) + (`list (nreverse elements))))) ;; Array encoding @@ -666,31 +670,31 @@ become JSON objects." ;;; JSON reader. -(defvar json-readtable +(defmacro json-readtable-dispatch (char) + "Dispatch reader function for CHAR." + (declare (debug (symbolp))) (let ((table '((?t json-read-keyword "true") (?f json-read-keyword "false") (?n json-read-keyword "null") (?{ json-read-object) (?\[ json-read-array) - (?\" json-read-string)))) - (mapc (lambda (char) - (push (list char 'json-read-number) table)) - '(?- ?+ ?. ?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9)) - table) - "Readtable for JSON reader.") + (?\" json-read-string))) + res) + (dolist (c '(?- ?+ ?. ?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9)) + (push (list c 'json-read-number) table)) + (pcase-dolist (`(,c . ,rest) table) + (push `((eq ,char ,c) (,@rest)) res)) + `(cond ,@res (t (signal 'json-readtable-error ,char))))) (defun json-read () "Parse and return the JSON object following point. Advances point just past JSON object." (json-skip-whitespace) (let ((char (json-peek))) - (if (not (eq char :json-eof)) - (let ((record (cdr (assq char json-readtable)))) - (if (functionp (car record)) - (apply (car record) (cdr record)) - (signal 'json-readtable-error record))) - (signal 'json-end-of-file nil)))) + (if (zerop char) + (signal 'json-end-of-file nil) + (json-readtable-dispatch char)))) ;; Syntactic sugar for the reader diff --git a/lisp/kermit.el b/lisp/kermit.el index f1900b48531..8863f2ed1a9 100644 --- a/lisp/kermit.el +++ b/lisp/kermit.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/kmacro.el b/lisp/kmacro.el index 838a492b6cb..da02ab5aca4 100644 --- a/lisp/kmacro.el +++ b/lisp/kmacro.el @@ -1,4 +1,4 @@ -;;; kmacro.el --- enhanced keyboard macros +;;; kmacro.el --- enhanced keyboard macros -*- lexical-binding: t -*- ;; Copyright (C) 2002-2017 Free Software Foundation, Inc. @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -111,6 +111,7 @@ ;;; Code: ;; Customization: +(require 'replace) (defgroup kmacro nil "Simplified keyboard macro user interface." @@ -123,13 +124,11 @@ (defcustom kmacro-call-mouse-event 'S-mouse-3 "The mouse event used by kmacro to call a macro. Set to nil if no mouse binding is desired." - :type 'symbol - :group 'kmacro) + :type 'symbol) (defcustom kmacro-ring-max 8 "Maximum number of keyboard macros to save in macro ring." - :type 'integer - :group 'kmacro) + :type 'integer) (defcustom kmacro-execute-before-append t @@ -140,32 +139,27 @@ execute the macro. Otherwise, a single \\[universal-argument] prefix does not execute the macro, while more than one \\[universal-argument] prefix causes the macro to be executed before appending to it." - :type 'boolean - :group 'kmacro) + :type 'boolean) (defcustom kmacro-repeat-no-prefix t "Allow repeating certain macro commands without entering the C-x C-k prefix." - :type 'boolean - :group 'kmacro) + :type 'boolean) (defcustom kmacro-call-repeat-key t "Allow repeating macro call using last key or a specific key." :type '(choice (const :tag "Disabled" nil) (const :tag "Last key" t) (character :tag "Character" :value ?e) - (symbol :tag "Key symbol" :value RET)) - :group 'kmacro) + (symbol :tag "Key symbol" :value RET))) (defcustom kmacro-call-repeat-with-arg nil "Repeat macro call with original arg when non-nil; repeat once if nil." - :type 'boolean - :group 'kmacro) + :type 'boolean) (defcustom kmacro-step-edit-mini-window-height 0.75 "Override `max-mini-window-height' when step edit keyboard macro." - :type 'number - :group 'kmacro) + :type 'number) ;; Keymap @@ -260,7 +254,7 @@ previous `kmacro-counter', and do not modify counter." (if kmacro-initial-counter-value (setq kmacro-counter kmacro-initial-counter-value kmacro-initial-counter-value nil)) - (if (and arg (listp arg)) + (if (consp arg) (insert (format kmacro-counter-format kmacro-last-counter)) (insert (format kmacro-counter-format kmacro-counter)) (kmacro-add-counter (prefix-numeric-value arg)))) @@ -279,8 +273,8 @@ previous `kmacro-counter', and do not modify counter." (defun kmacro-display-counter (&optional value) "Display current counter value." (unless value (setq value kmacro-counter)) - (message "New macro counter value: %s (%d)" (format kmacro-counter-format value) value)) - + (message "New macro counter value: %s (%d)" + (format kmacro-counter-format value) value)) (defun kmacro-set-counter (arg) "Set `kmacro-counter' to ARG or prompt if missing. @@ -565,7 +559,8 @@ Use \\[kmacro-insert-counter] to insert (and increment) the macro counter. The counter value can be set or modified via \\[kmacro-set-counter] and \\[kmacro-add-counter]. The format of the counter can be modified via \\[kmacro-set-format]. -Use \\[kmacro-name-last-macro] to give it a permanent name. +Use \\[kmacro-name-last-macro] to give it a name that will remain valid even +after another macro is defined. Use \\[kmacro-bind-to-key] to bind it to a key sequence." (interactive "P") (if (or defining-kbd-macro executing-kbd-macro) @@ -628,8 +623,8 @@ just the last key in the key sequence that you used to call this command. See `kmacro-call-repeat-key' and `kmacro-call-repeat-with-arg' for details on how to adjust or disable this behavior. -To make a macro permanent so you can call it even after defining -others, use \\[kmacro-name-last-macro]." +To give a macro a name so you can call it even after defining others, +use \\[kmacro-name-last-macro]." (interactive "p") (let ((repeat-key (and (or (and (null no-repeat) (> (length (this-single-command-keys)) 1)) @@ -730,8 +725,8 @@ With \\[universal-argument], call second macro in macro ring." With numeric prefix ARG, repeat macro that many times. Zero argument means repeat until there is an error. -To give a macro a permanent name, so you can call it -even after defining other macros, use \\[kmacro-name-last-macro]." +To give a macro a name, so you can call it even after defining other +macros, use \\[kmacro-name-last-macro]." (interactive "P") (if defining-kbd-macro (kmacro-end-macro nil)) @@ -772,19 +767,18 @@ If kbd macro currently being defined end it before activating it." (defun kmacro-extract-lambda (mac) "Extract kmacro from a kmacro lambda form." - (and (consp mac) - (eq (car mac) 'lambda) + (and (eq (car-safe mac) 'lambda) (setq mac (assoc 'kmacro-exec-ring-item mac)) - (consp (cdr mac)) - (consp (car (cdr mac))) - (consp (cdr (car (cdr mac)))) - (setq mac (car (cdr (car (cdr mac))))) + (setq mac (car-safe (cdr-safe (car-safe (cdr-safe mac))))) (listp mac) (= (length mac) 3) (arrayp (car mac)) mac)) +(defalias 'kmacro-p #'kmacro-extract-lambda + "Return non-nil if MAC is a kmacro keyboard macro.") + (defun kmacro-bind-to-key (_arg) "When not defining or executing a macro, offer to bind last macro to a key. The key sequences [C-x C-k 0] through [C-x C-k 9] and [C-x C-k A] @@ -825,6 +819,13 @@ The ARG parameter is unused." (kmacro-lambda-form (kmacro-ring-head))) (message "Keyboard macro bound to %s" (format-kbd-macro key-seq)))))) +(defun kmacro-keyboard-macro-p (symbol) + "Return non-nil if SYMBOL is the name of some sort of keyboard macro." + (let ((f (symbol-function symbol))) + (when f + (or (stringp f) + (vectorp f) + (kmacro-p f))))) (defun kmacro-name-last-macro (symbol) "Assign a name to the last keyboard macro defined. @@ -835,14 +836,18 @@ Such a \"function\" cannot be called from Lisp, but it is a valid editor command (or last-kbd-macro (error "No keyboard macro defined")) (and (fboundp symbol) - (not (get symbol 'kmacro)) - (not (stringp (symbol-function symbol))) - (not (vectorp (symbol-function symbol))) + (not (kmacro-keyboard-macro-p symbol)) (error "Function %s is already defined and not a keyboard macro" symbol)) (if (string-equal symbol "") (error "No command name given")) + ;; FIXME: Use plain old `last-kbd-macro' for kmacros where it doesn't + ;; make a difference? (fset symbol (kmacro-lambda-form (kmacro-ring-head))) + ;; This used to be used to detect when a symbol corresponds to a kmacro. + ;; Nowadays it's unused because we used `kmacro-p' instead to see if the + ;; symbol's function definition matches that of a kmacro, which is more + ;; reliable. (put symbol 'kmacro t)) @@ -936,7 +941,7 @@ without repeating the prefix." ;;; Single-step editing of keyboard macros -(defvar kmacro-step-edit-active) ;; step-editing active +(defvar kmacro-step-edit-active nil) ;; step-editing active (defvar kmacro-step-edit-new-macro) ;; storage for new macro (defvar kmacro-step-edit-inserting) ;; inserting into macro (defvar kmacro-step-edit-appending) ;; append to end of macro @@ -1201,7 +1206,7 @@ following additional answers: `insert', `insert-1', `replace', `replace-1', (setq kmacro-step-edit-key-index next-index))) (defun kmacro-step-edit-pre-command () - (remove-hook 'post-command-hook 'kmacro-step-edit-post-command) + (remove-hook 'post-command-hook #'kmacro-step-edit-post-command) (when kmacro-step-edit-active (cond ((eq kmacro-step-edit-active 'ignore) @@ -1221,17 +1226,17 @@ following additional answers: `insert', `insert-1', `replace', `replace-1', (setq kmacro-step-edit-appending nil kmacro-step-edit-active 'ignore))))) (when (eq kmacro-step-edit-active t) - (add-hook 'post-command-hook 'kmacro-step-edit-post-command t))) + (add-hook 'post-command-hook #'kmacro-step-edit-post-command t))) (defun kmacro-step-edit-minibuf-setup () - (remove-hook 'pre-command-hook 'kmacro-step-edit-pre-command t) + (remove-hook 'pre-command-hook #'kmacro-step-edit-pre-command t) (when kmacro-step-edit-active - (add-hook 'pre-command-hook 'kmacro-step-edit-pre-command nil t))) + (add-hook 'pre-command-hook #'kmacro-step-edit-pre-command nil t))) (defun kmacro-step-edit-post-command () - (remove-hook 'pre-command-hook 'kmacro-step-edit-pre-command) + (remove-hook 'pre-command-hook #'kmacro-step-edit-pre-command) (when kmacro-step-edit-active - (add-hook 'pre-command-hook 'kmacro-step-edit-pre-command nil nil) + (add-hook 'pre-command-hook #'kmacro-step-edit-pre-command nil nil) (if kmacro-step-edit-key-index (setq executing-kbd-macro-index kmacro-step-edit-key-index) (setq kmacro-step-edit-key-index executing-kbd-macro-index)))) @@ -1254,9 +1259,9 @@ To customize possible responses, change the \"bindings\" in `kmacro-step-edit-ma (pre-command-hook pre-command-hook) (post-command-hook post-command-hook) (minibuffer-setup-hook minibuffer-setup-hook)) - (add-hook 'pre-command-hook 'kmacro-step-edit-pre-command nil) - (add-hook 'post-command-hook 'kmacro-step-edit-post-command t) - (add-hook 'minibuffer-setup-hook 'kmacro-step-edit-minibuf-setup t) + (add-hook 'pre-command-hook #'kmacro-step-edit-pre-command nil) + (add-hook 'post-command-hook #'kmacro-step-edit-post-command t) + (add-hook 'minibuffer-setup-hook #'kmacro-step-edit-minibuf-setup t) (call-last-kbd-macro nil nil) (when (and kmacro-step-edit-replace kmacro-step-edit-new-macro diff --git a/lisp/language/burmese.el b/lisp/language/burmese.el index 420e8d74919..25425ec4858 100644 --- a/lisp/language/burmese.el +++ b/lisp/language/burmese.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -56,4 +56,3 @@ (vector "." 0 'font-shape-gstring)))) (set-char-table-range composition-function-table '(#x1000 . #x107F) elt) (set-char-table-range composition-function-table '(#xAA60 . #xAA7B) elt)) - diff --git a/lisp/language/cham.el b/lisp/language/cham.el index a025ff0d209..4749f2e8db4 100644 --- a/lisp/language/cham.el +++ b/lisp/language/cham.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/language/china-util.el b/lisp/language/china-util.el index 955c2999b8c..f5174fb5e93 100644 --- a/lisp/language/china-util.el +++ b/lisp/language/china-util.el @@ -24,7 +24,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/language/chinese.el b/lisp/language/chinese.el index a412838af73..9ba178d7239 100644 --- a/lisp/language/chinese.el +++ b/lisp/language/chinese.el @@ -24,7 +24,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/language/cyril-util.el b/lisp/language/cyril-util.el index a96f2fb0475..7644064c5a0 100644 --- a/lisp/language/cyril-util.el +++ b/lisp/language/cyril-util.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/language/cyrillic.el b/lisp/language/cyrillic.el index 1e47057e9b5..ba985a4754f 100644 --- a/lisp/language/cyrillic.el +++ b/lisp/language/cyrillic.el @@ -25,7 +25,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/language/czech.el b/lisp/language/czech.el index 0ebf2cb7bde..21213c65fd5 100644 --- a/lisp/language/czech.el +++ b/lisp/language/czech.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/language/english.el b/lisp/language/english.el index fefb24171a0..3e8f3123c3f 100644 --- a/lisp/language/english.el +++ b/lisp/language/english.el @@ -24,7 +24,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/language/ethio-util.el b/lisp/language/ethio-util.el index 4d7ccd12692..cdf41ba909e 100644 --- a/lisp/language/ethio-util.el +++ b/lisp/language/ethio-util.el @@ -24,7 +24,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; Author: TAKAHASHI Naoto <ntakahas@m17n.org> diff --git a/lisp/language/ethiopic.el b/lisp/language/ethiopic.el index 3e71d437bdd..f0bb049fdbb 100644 --- a/lisp/language/ethiopic.el +++ b/lisp/language/ethiopic.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; Author: TAKAHASHI Naoto <ntakahas@m17n.org> diff --git a/lisp/language/european.el b/lisp/language/european.el index 6c0232efd3b..d9ce05c24ae 100644 --- a/lisp/language/european.el +++ b/lisp/language/european.el @@ -24,7 +24,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/language/georgian.el b/lisp/language/georgian.el index 14e35108445..43718092959 100644 --- a/lisp/language/georgian.el +++ b/lisp/language/georgian.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/language/greek.el b/lisp/language/greek.el index 357f0633a69..1a401480642 100644 --- a/lisp/language/greek.el +++ b/lisp/language/greek.el @@ -25,7 +25,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/language/hanja-util.el b/lisp/language/hanja-util.el index 200ae896b05..6af47982bae 100644 --- a/lisp/language/hanja-util.el +++ b/lisp/language/hanja-util.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/language/hebrew.el b/lisp/language/hebrew.el index 4e33fb63bca..9e049de8b5f 100644 --- a/lisp/language/hebrew.el +++ b/lisp/language/hebrew.el @@ -25,7 +25,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/language/ind-util.el b/lisp/language/ind-util.el index 930cba1bd91..fc8f4c9d983 100644 --- a/lisp/language/ind-util.el +++ b/lisp/language/ind-util.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/language/indian.el b/lisp/language/indian.el index c84c8fede6c..0bb123e1899 100644 --- a/lisp/language/indian.el +++ b/lisp/language/indian.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -116,7 +116,7 @@ South Indian Language Telugu is supported in this language environment.")) (sample-text . "Kannada (ಕನ್ನಡ) ನಮಸ್ಕಾರ") (documentation . "\ Kannada language and script is supported in this language -environment.")) +environment.")) '("Indian")) (set-language-info-alist @@ -140,7 +140,7 @@ South Indian language Malayalam is supported in this language environment.")) (defconst devanagari-composable-pattern (let ((table '(("a" . "[\u0900-\u0902]") ; vowel modifier (above) - ("A" . "\u0903") ; vowel modifier (post) + ("A" . "\u0903") ; vowel modifier (post) ("V" . "[\u0904-\u0914\u0960-\u0961\u0972]") ; independent vowel ("C" . "[\u0915-\u0939\u0958-\u095F\u0979-\u097F]") ; consonant ("R" . "\u0930") ; RA @@ -347,7 +347,7 @@ South Indian language Malayalam is supported in this language environment.")) (let ((table '(("A" . "[\u0D02-\u0D03]") ; SIGN ANUSVARA .. VISARGA ("V" . "[\u0D05-\u0D14\u0D60-\u0D61]") ; independent vowel - ("C" . "[\u0D15-\u0D39]") ; consonant + ("C" . "[\u0D15-\u0D39]") ; consonant ("Y" . "[\u0D2F-\u0D30\u0D32\u0D35]") ; YA, RA, LA, VA ("v" . "[\u0D3E-\u0D4C\u0D57\u0D62-\u0D63]") ; postbase matra ("H" . "\u0D4D") ; SIGN VIRAMA diff --git a/lisp/language/japan-util.el b/lisp/language/japan-util.el index 01cdd8bef9e..988b925409e 100644 --- a/lisp/language/japan-util.el +++ b/lisp/language/japan-util.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/language/japanese.el b/lisp/language/japanese.el index 4203c4cc940..57147f62e33 100644 --- a/lisp/language/japanese.el +++ b/lisp/language/japanese.el @@ -24,7 +24,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -185,7 +185,7 @@ eucJP-ms is defined in <http://www.opengroup.or.jp/jvc/cde/appendix.html>." "Shift_JIS 8-bit encoding for Japanese (MIME:SHIFT_JIS-2004)" :coding-type 'shift-jis :mnemonic ?S - :charset-list '(ascii katakana-jisx0201 + :charset-list '(ascii katakana-jisx0201 japanese-jisx0213.2004-1 japanese-jisx0213-2)) (define-coding-system-alias 'shift_jis-2004 'japanese-shift-jis-2004) @@ -197,15 +197,15 @@ eucJP-ms is defined in <http://www.opengroup.or.jp/jvc/cde/appendix.html>." (tutorial . "TUTORIAL.ja") (charset japanese-jisx0208 japanese-jisx0212 latin-jisx0201 katakana-jisx0201 - japanese-jisx0213.2004-1 japanese-jisx0213-1 + japanese-jisx0213.2004-1 japanese-jisx0213-1 japanese-jisx0213-2 japanese-jisx0208-1978) (coding-system iso-2022-jp japanese-iso-8bit japanese-shift-jis japanese-iso-7bit-1978-irv iso-2022-jp-2004 japanese-shift-jis-2004 euc-jis-2004) (coding-priority iso-2022-jp japanese-iso-8bit - japanese-shift-jis - iso-2022-jp-2004 euc-jis-2004 + japanese-shift-jis + iso-2022-jp-2004 euc-jis-2004 japanese-shift-jis-2004 iso-2022-jp-2) (input-method . "japanese") diff --git a/lisp/language/khmer.el b/lisp/language/khmer.el index 8663ff22ca0..4a070321961 100644 --- a/lisp/language/khmer.el +++ b/lisp/language/khmer.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: diff --git a/lisp/language/korea-util.el b/lisp/language/korea-util.el index 12bb1e10bd3..c49e627ea9b 100644 --- a/lisp/language/korea-util.el +++ b/lisp/language/korea-util.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/language/korean.el b/lisp/language/korean.el index a8a30110c79..52560d6fb4d 100644 --- a/lisp/language/korean.el +++ b/lisp/language/korean.el @@ -24,7 +24,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/language/lao-util.el b/lisp/language/lao-util.el index 28b2043ed51..94504ff9ba6 100644 --- a/lisp/language/lao-util.el +++ b/lisp/language/lao-util.el @@ -24,7 +24,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/language/lao.el b/lisp/language/lao.el index 03519c9beec..266c3c634f7 100644 --- a/lisp/language/lao.el +++ b/lisp/language/lao.el @@ -24,7 +24,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/language/misc-lang.el b/lisp/language/misc-lang.el index c03fd429fe9..c1aa79cae45 100644 --- a/lisp/language/misc-lang.el +++ b/lisp/language/misc-lang.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -75,12 +75,71 @@ and Italian."))) (sample-text . "Persian فارسی") (documentation . "Bidirectional editing is supported."))) +(defcustom arabic-shaper-ZWNJ-handling nil + "How to handle ZWMJ in Arabic text rendering. +This variable controls the way to handle a glyph for ZWNJ +returned by the underling shaping engine. + +The default value is nil, which means that the ZWNJ glyph is +displayed as is. + +If the value is `absorb', ZWNJ is absorbed into the previous +grapheme cluster, and not displayed. + +If the value is `as-space', the glyph is displayed by a +thin (i.e. 1-dot width) space." + :group 'mule + :version "26.1" + :type '(choice + (const :tag "default" nil) + (const :tag "as space" as-space) + (const :tag "absorb" absorb)) + :set (lambda (sym val) + (set-default sym val) + (clear-composition-cache))) + +;; Record error in arabic-change-gstring. +(defvar arabic-shape-log nil) + +(defun arabic-shape-gstring (gstring) + (setq gstring (font-shape-gstring gstring)) + (condition-case err + (when arabic-shaper-ZWNJ-handling + (let ((font (lgstring-font gstring)) + (i 1) + (len (lgstring-glyph-len gstring)) + (modified nil)) + (while (< i len) + (let ((glyph (lgstring-glyph gstring i))) + (when (eq (lglyph-char glyph) #x200c) + (cond + ((eq arabic-shaper-ZWNJ-handling 'as-space) + (if (> (- (lglyph-rbearing glyph) (lglyph-lbearing glyph)) 0) + (let ((space-glyph (aref (font-get-glyphs font 0 1 " ") 0))) + (when space-glyph + (lglyph-set-code glyph (aref space-glyph 3)) + (lglyph-set-width glyph (aref space-glyph 4))))) + (lglyph-set-adjustment glyph 0 0 1) + (setq modified t)) + ((eq arabic-shaper-ZWNJ-handling 'absorb) + (let ((prev (lgstring-glyph gstring (1- i)))) + (lglyph-set-from-to prev (lglyph-from prev) (lglyph-to glyph)) + (setq gstring (lgstring-remove-glyph gstring i)) + (setq len (1- len))) + (setq modified t))))) + (setq i (1+ i))) + (if modified + (lgstring-set-id gstring nil)))) + (error (push err arabic-shape-log))) + gstring) + (set-char-table-range composition-function-table '(#x600 . #x74F) - (list (vector "[\u0600-\u074F\u200C\u200D]+" 0 'font-shape-gstring) - (vector "[\u200C\u200D][\u0600-\u074F\u200C\u200D]+" - 1 'font-shape-gstring))) + (list (vector "[\u0600-\u074F\u200C\u200D]+" 0 + 'arabic-shape-gstring) + (vector "[\u200C\u200D][\u0600-\u074F\u200C\u200D]+" 1 + 'arabic-shape-gstring))) (provide 'misc-lang) diff --git a/lisp/language/romanian.el b/lisp/language/romanian.el index 421ddcdd1ac..00deb698848 100644 --- a/lisp/language/romanian.el +++ b/lisp/language/romanian.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/language/sinhala.el b/lisp/language/sinhala.el index 037d753f52a..efd8aacc5ac 100644 --- a/lisp/language/sinhala.el +++ b/lisp/language/sinhala.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: @@ -30,7 +30,7 @@ (sample-text . "Sinhala (සිංහල) ආයුබෝවන්") (documentation . t))) -(set-char-table-range +(set-char-table-range composition-function-table '(#xD80 . #xDFF) (list (vector diff --git a/lisp/language/slovak.el b/lisp/language/slovak.el index 6c729424d2f..9682722e6ee 100644 --- a/lisp/language/slovak.el +++ b/lisp/language/slovak.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/language/tai-viet.el b/lisp/language/tai-viet.el index f4074ae2714..3c589106254 100644 --- a/lisp/language/tai-viet.el +++ b/lisp/language/tai-viet.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/language/thai-util.el b/lisp/language/thai-util.el index db421ebd5a6..c8c844fbe25 100644 --- a/lisp/language/thai-util.el +++ b/lisp/language/thai-util.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/language/thai-word.el b/lisp/language/thai-word.el index d3c00f9ac36..e67dd093430 100644 --- a/lisp/language/thai-word.el +++ b/lisp/language/thai-word.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; The used Thai word list has been taken from IBM's ICU4J project ;; (file `thai6.ucs', version 1.4, converted to TIS encoding, with diff --git a/lisp/language/thai.el b/lisp/language/thai.el index 4d199842bcd..945ea31c8d7 100644 --- a/lisp/language/thai.el +++ b/lisp/language/thai.el @@ -24,7 +24,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/language/tibet-util.el b/lisp/language/tibet-util.el index 624da5c6d2c..f3648c9b204 100644 --- a/lisp/language/tibet-util.el +++ b/lisp/language/tibet-util.el @@ -23,7 +23,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; History: ;; 1997.03.13 Modification in treatment of text properties; diff --git a/lisp/language/tibetan.el b/lisp/language/tibetan.el index f24e3b373fc..962dd2bee5b 100644 --- a/lisp/language/tibetan.el +++ b/lisp/language/tibetan.el @@ -26,7 +26,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; History: diff --git a/lisp/language/tv-util.el b/lisp/language/tv-util.el index ba1ee668825..a667956a060 100644 --- a/lisp/language/tv-util.el +++ b/lisp/language/tv-util.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code @@ -72,7 +72,7 @@ (tone-rule '(tr . bl)) (prev-viet nil) ch info pos components overhang) - (while (< from to) + (while (< from to) (or ch (setq ch (char-after from) info (aref tai-viet-glyph-info ch))) @@ -138,4 +138,3 @@ ;; (provide 'tai-viet-util) - diff --git a/lisp/language/utf-8-lang.el b/lisp/language/utf-8-lang.el index 40aec43d7eb..4156bf5766b 100644 --- a/lisp/language/utf-8-lang.el +++ b/lisp/language/utf-8-lang.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/language/viet-util.el b/lisp/language/viet-util.el index ca670d80ff0..f1946f6b69f 100644 --- a/lisp/language/viet-util.el +++ b/lisp/language/viet-util.el @@ -24,7 +24,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/language/vietnamese.el b/lisp/language/vietnamese.el index cd36580d768..c170216062d 100644 --- a/lisp/language/vietnamese.el +++ b/lisp/language/vietnamese.el @@ -24,7 +24,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/ldefs-boot.el b/lisp/ldefs-boot.el index ae28ba93e61..b1f582c4044 100644 --- a/lisp/ldefs-boot.el +++ b/lisp/ldefs-boot.el @@ -1102,6 +1102,15 @@ options only, i.e. behave like `apropos-user-option'. \(fn PATTERN &optional DO-NOT-ALL)" t nil) +(autoload 'apropos-local-variable "apropos" "\ +Show buffer-local variables that match PATTERN. +Optional arg BUFFER (default: current buffer) is the buffer to check. + +The output includes variables that are not yet set in BUFFER, but that +will be buffer-local when set. + +\(fn PATTERN &optional BUFFER)" t nil) + (defalias 'command-apropos 'apropos-command) (autoload 'apropos-command "apropos" "\ @@ -1167,6 +1176,13 @@ Returns list of symbols and values found. \(fn PATTERN &optional DO-ALL)" t nil) +(autoload 'apropos-local-value "apropos" "\ +Show buffer-local variables whose values match PATTERN. +This is like `apropos-value', but only for buffer-local variables. +Optional arg BUFFER (default: current buffer) is the buffer to check. + +\(fn PATTERN &optional BUFFER)" t nil) + (autoload 'apropos-documentation "apropos" "\ Show symbols whose documentation contains matches for PATTERN. PATTERN can be a word, a list of words (separated by spaces), @@ -2878,6 +2894,8 @@ columns on its right towards the left. (put 'bug-reference-url-format 'safe-local-variable (lambda (s) (or (stringp s) (and (symbolp s) (get s 'bug-reference-url-format))))) +(put 'bug-reference-bug-regexp 'safe-local-variable 'stringp) + (autoload 'bug-reference-mode "bug-reference" "\ Toggle hyperlinking bug references in the buffer (Bug Reference mode). With a prefix argument ARG, enable Bug Reference mode if ARG is @@ -3840,7 +3858,7 @@ Key bindings: \(fn)" t nil) (autoload 'c-or-c++-mode "cc-mode" "\ -Analyse buffer and enable either C or C++ mode. +Analyze buffer and enable either C or C++ mode. Some people and projects use .h extension for C++ header files which is also the one used for C header files. This makes @@ -4943,16 +4961,20 @@ call other entry points instead, such as `cl-prin1'. \(fn OBJECT STREAM)" nil nil) (autoload 'cl-prin1 "cl-print" "\ - +Print OBJECT on STREAM according to its type. +Output is further controlled by the variables +`cl-print-readably', `cl-print-compiled', along with output +variables for the standard printing functions. See Info +node `(elisp)Output Variables'. \(fn OBJECT &optional STREAM)" nil nil) (autoload 'cl-prin1-to-string "cl-print" "\ - +Return a string containing the `cl-prin1'-printed representation of OBJECT. \(fn OBJECT)" nil nil) -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "cl-print" '("cl-print-"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "cl-print" '("cl-print-" "help-byte-code"))) ;;;*** @@ -5014,7 +5036,7 @@ is run). (autoload 'color-name-to-rgb "color" "\ Convert COLOR string to a list of normalized RGB components. COLOR should be a color name (e.g. \"white\") or an RGB triplet -string (e.g. \"#ff12ec\"). +string (e.g. \"#ffff1122eecc\"). Normally the return value is a list of three floating-point numbers, (RED GREEN BLUE), each between 0.0 and 1.0 inclusive. @@ -5426,16 +5448,7 @@ See also `conf-space-mode', `conf-colon-mode', `conf-javaprop-mode', (autoload 'conf-unix-mode "conf-mode" "\ Conf Mode starter for Unix style Conf files. -Comments start with `#'. -For details see `conf-mode'. Example: - -# Conf mode font-locks this right on Unix and with \\[conf-unix-mode] - -\[Desktop Entry] - Encoding=UTF-8 - Name=The GIMP - Name[ca]=El GIMP - Name[cs]=GIMP +Comments start with `#'. For details see `conf-mode'. \(fn)" t nil) @@ -5541,6 +5554,32 @@ For details see `conf-mode'. Example: \(fn)" t nil) +(autoload 'conf-toml-mode "conf-mode" "\ +Conf Mode starter for TOML files. +Comments start with `#' and \"assignments\" are with `='. +For details see `conf-mode'. Example: + +# Conf mode font-locks this right with \\[conf-toml-mode] + +\[entry] +value = \"some string\" + +\(fn)" t nil) + +(autoload 'conf-desktop-mode "conf-mode" "\ +Conf Mode started for freedesktop.org Desktop files. +Comments start with `#' and \"assignments\" are with `='. +For details see `conf-mode'. + +# Conf mode font-locks this correctly with \\[conf-desktop-mode] + [Desktop Entry] + Name=GNU Image Manipulation Program + Name[oc]=Editor d'imatge GIMP + Exec=gimp-2.8 %U + Terminal=false + +\(fn)" t nil) + (if (fboundp 'register-definition-prefixes) (register-definition-prefixes "conf-mode" '("conf-"))) ;;;*** @@ -6812,9 +6851,12 @@ or call the function `delete-selection-mode'.") (autoload 'delete-selection-mode "delsel" "\ Toggle Delete Selection mode. -With a prefix argument ARG, enable Delete Selection mode if ARG -is positive, and disable it otherwise. If called from Lisp, -enable the mode if ARG is omitted or nil. +Interactively, with a prefix argument, enable +Delete Selection mode if the prefix argument is positive, +and disable it otherwise. If called from Lisp, toggle +the mode if ARG is `toggle', disable the mode if ARG is +a non-positive integer, and enable the mode otherwise +\(including if ARG is omitted or nil or a positive integer). When Delete Selection mode is enabled, typed text replaces the selection if the selection is active. Otherwise, typed text is just inserted at @@ -7374,7 +7416,7 @@ May contain all other options that don't contradict `-l'; may contain even `F', `b', `i' and `s'. See also the variable `dired-ls-F-marks-symlinks' concerning the `F' switch. Options that include embedded whitespace must be quoted -like this: \\\"--option=value with spaces\\\"; you can use +like this: \"--option=value with spaces\"; you can use `combine-and-quote-strings' to produce the correct quoting of each option. On systems such as MS-DOS and MS-Windows, which use `ls' emulation in Lisp, @@ -7684,6 +7726,46 @@ in `.emacs'. ;;;*** +;;;### (autoloads nil "display-line-numbers" "display-line-numbers.el" +;;;;;; (0 0 0 0)) +;;; Generated autoloads from display-line-numbers.el + +(autoload 'display-line-numbers-mode "display-line-numbers" "\ +Toggle display of line numbers in the buffer. +This uses `display-line-numbers' internally. + +To change the type of line numbers displayed by default, +customize `display-line-numbers-type'. To change the type while +the mode is on, set `display-line-numbers' directly. + +\(fn &optional ARG)" t nil) + +(defvar global-display-line-numbers-mode nil "\ +Non-nil if Global Display-Line-Numbers mode is enabled. +See the `global-display-line-numbers-mode' command +for a description of this minor mode. +Setting this variable directly does not take effect; +either customize it (see the info node `Easy Customization') +or call the function `global-display-line-numbers-mode'.") + +(custom-autoload 'global-display-line-numbers-mode "display-line-numbers" nil) + +(autoload 'global-display-line-numbers-mode "display-line-numbers" "\ +Toggle Display-Line-Numbers mode in all buffers. +With prefix ARG, enable Global Display-Line-Numbers mode if ARG is positive; +otherwise, disable it. If called from Lisp, enable the mode if +ARG is omitted or nil. + +Display-Line-Numbers mode is enabled in all buffers where +`display-line-numbers--turn-on' would do it. +See `display-line-numbers-mode' for more information on Display-Line-Numbers mode. + +\(fn &optional ARG)" t nil) + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "display-line-numbers" '("display-line-numbers-"))) + +;;;*** + ;;;### (autoloads nil "dissociate" "play/dissociate.el" (0 0 0 0)) ;;; Generated autoloads from play/dissociate.el @@ -8017,12 +8099,16 @@ the constant's documentation. \(fn M BS DOC &rest ARGS)" nil t) +(function-put 'easy-mmode-defmap 'lisp-indent-function '1) + (autoload 'easy-mmode-defsyntax "easy-mmode" "\ Define variable ST as a syntax-table. CSS contains a list of syntax specifications of the form (CHAR . SYNTAX). \(fn ST CSS DOC &rest ARGS)" nil t) +(function-put 'easy-mmode-defsyntax 'lisp-indent-function '1) + (if (fboundp 'register-definition-prefixes) (register-definition-prefixes "easy-mmode" '("easy-mmode-"))) ;;;*** @@ -8251,7 +8337,7 @@ See also `ebnf-print-buffer'. (autoload 'ebnf-print-buffer "ebnf2ps" "\ Generate and print a PostScript syntactic chart image of the buffer. -When called with a numeric prefix argument (C-u), prompts the user for +When called with a numeric prefix argument (\\[universal-argument]), prompts the user for the name of a file to save the PostScript image in, instead of sending it to the printer. @@ -8373,7 +8459,7 @@ WARNING: This function does *NOT* ask any confirmation to override existing \(fn FROM TO)" t nil) -(defalias 'ebnf-despool 'ps-despool) +(defalias 'ebnf-despool #'ps-despool) (autoload 'ebnf-syntax-directory "ebnf2ps" "\ Do a syntactic analysis of the files in DIRECTORY. @@ -9068,11 +9154,15 @@ Toggle edebugging of all forms. (autoload 'ediff-files "ediff" "\ Run Ediff on a pair of files, FILE-A and FILE-B. +STARTUP-HOOKS is a list of functions that Emacs calls without +arguments after setting up the Ediff buffers. \(fn FILE-A FILE-B &optional STARTUP-HOOKS)" t nil) (autoload 'ediff-files3 "ediff" "\ Run Ediff on three files, FILE-A, FILE-B, and FILE-C. +STARTUP-HOOKS is a list of functions that Emacs calls without +arguments after setting up the Ediff buffers. \(fn FILE-A FILE-B FILE-C &optional STARTUP-HOOKS)" t nil) @@ -9096,6 +9186,13 @@ If this file is a backup, `ediff' it with its original. (autoload 'ediff-buffers "ediff" "\ Run Ediff on a pair of buffers, BUFFER-A and BUFFER-B. +STARTUP-HOOKS is a list of functions that Emacs calls without +arguments after setting up the Ediff buffers. JOB-NAME is a +symbol describing the Ediff job type; it defaults to +`ediff-buffers', but can also be one of +`ediff-merge-files-with-ancestor', `ediff-last-dir-ancestor', +`ediff-last-dir-C', `ediff-buffers3', `ediff-merge-buffers', or +`ediff-merge-buffers-with-ancestor'. \(fn BUFFER-A BUFFER-B &optional STARTUP-HOOKS JOB-NAME)" t nil) @@ -9103,6 +9200,13 @@ Run Ediff on a pair of buffers, BUFFER-A and BUFFER-B. (autoload 'ediff-buffers3 "ediff" "\ Run Ediff on three buffers, BUFFER-A, BUFFER-B, and BUFFER-C. +STARTUP-HOOKS is a list of functions that Emacs calls without +arguments after setting up the Ediff buffers. JOB-NAME is a +symbol describing the Ediff job type; it defaults to +`ediff-buffers3', but can also be one of +`ediff-merge-files-with-ancestor', `ediff-last-dir-ancestor', +`ediff-last-dir-C', `ediff-buffers', `ediff-merge-buffers', or +`ediff-merge-buffers-with-ancestor'. \(fn BUFFER-A BUFFER-B BUFFER-C &optional STARTUP-HOOKS JOB-NAME)" t nil) @@ -9139,6 +9243,7 @@ regular expression; only file names that match the regexp are considered. Run Ediff on a pair of directories, DIR1 and DIR2, merging files that have the same name in both. The third argument, REGEXP, is nil or a regular expression; only file names that match the regexp are considered. +MERGE-AUTOSTORE-DIR is the directory in which to store merged files. \(fn DIR1 DIR2 REGEXP &optional MERGE-AUTOSTORE-DIR)" t nil) @@ -9150,6 +9255,7 @@ Ediff merges files that have identical names in DIR1, DIR2. If a pair of files in DIR1 and DIR2 doesn't have an ancestor in ANCESTOR-DIR, Ediff will merge without ancestor. The fourth argument, REGEXP, is nil or a regular expression; only file names that match the regexp are considered. +MERGE-AUTOSTORE-DIR is the directory in which to store merged files. \(fn DIR1 DIR2 ANCESTOR-DIR REGEXP &optional MERGE-AUTOSTORE-DIR)" t nil) @@ -9157,6 +9263,7 @@ only file names that match the regexp are considered. Run Ediff on a directory, DIR1, merging its files with their revisions. The second argument, REGEXP, is a regular expression that filters the file names. Only the files that are under revision control are taken into account. +MERGE-AUTOSTORE-DIR is the directory in which to store merged files. \(fn DIR1 REGEXP &optional MERGE-AUTOSTORE-DIR)" t nil) @@ -9166,6 +9273,7 @@ names. Only the files that are under revision control are taken into account. Run Ediff on a directory, DIR1, merging its files with their revisions and ancestors. The second argument, REGEXP, is a regular expression that filters the file names. Only the files that are under revision control are taken into account. +MERGE-AUTOSTORE-DIR is the directory in which to store merged files. \(fn DIR1 REGEXP &optional MERGE-AUTOSTORE-DIR)" t nil) @@ -9179,6 +9287,8 @@ With prefix argument, DUMB-MODE, or on a non-windowing display, works as follows: If WIND-A is nil, use selected window. If WIND-B is nil, use window next to WIND-A. +STARTUP-HOOKS is a list of functions that Emacs calls without +arguments after setting up the Ediff buffers. \(fn DUMB-MODE &optional WIND-A WIND-B STARTUP-HOOKS)" t nil) @@ -9188,23 +9298,31 @@ With prefix argument, DUMB-MODE, or on a non-windowing display, works as follows: If WIND-A is nil, use selected window. If WIND-B is nil, use window next to WIND-A. +STARTUP-HOOKS is a list of functions that Emacs calls without +arguments after setting up the Ediff buffers. \(fn DUMB-MODE &optional WIND-A WIND-B STARTUP-HOOKS)" t nil) (autoload 'ediff-regions-wordwise "ediff" "\ Run Ediff on a pair of regions in specified buffers. +BUFFER-A and BUFFER-B are the buffers to be compared. Regions (i.e., point and mark) can be set in advance or marked interactively. This function is effective only for relatively small regions, up to 200 lines. For large regions, use `ediff-regions-linewise'. +STARTUP-HOOKS is a list of functions that Emacs calls without +arguments after setting up the Ediff buffers. \(fn BUFFER-A BUFFER-B &optional STARTUP-HOOKS)" t nil) (autoload 'ediff-regions-linewise "ediff" "\ Run Ediff on a pair of regions in specified buffers. +BUFFER-A and BUFFER-B are the buffers to be compared. Regions (i.e., point and mark) can be set in advance or marked interactively. Each region is enlarged to contain full lines. This function is effective for large regions, over 100-200 lines. For small regions, use `ediff-regions-wordwise'. +STARTUP-HOOKS is a list of functions that Emacs calls without +arguments after setting up the Ediff buffers. \(fn BUFFER-A BUFFER-B &optional STARTUP-HOOKS)" t nil) @@ -9212,11 +9330,20 @@ lines. For small regions, use `ediff-regions-wordwise'. (autoload 'ediff-merge-files "ediff" "\ Merge two files without ancestor. +FILE-A and FILE-B are the names of the files to be merged. +STARTUP-HOOKS is a list of functions that Emacs calls without +arguments after setting up the Ediff buffers. MERGE-BUFFER-FILE +is the name of the file to be associated with the merge buffer.. \(fn FILE-A FILE-B &optional STARTUP-HOOKS MERGE-BUFFER-FILE)" t nil) (autoload 'ediff-merge-files-with-ancestor "ediff" "\ Merge two files with ancestor. +FILE-A and FILE-B are the names of the files to be merged, and +FILE-ANCESTOR is the name of the ancestor file. STARTUP-HOOKS is +a list of functions that Emacs calls without arguments after +setting up the Ediff buffers. MERGE-BUFFER-FILE is the name of +the file to be associated with the merge buffer. \(fn FILE-A FILE-B FILE-ANCESTOR &optional STARTUP-HOOKS MERGE-BUFFER-FILE)" t nil) @@ -9224,25 +9351,49 @@ Merge two files with ancestor. (autoload 'ediff-merge-buffers "ediff" "\ Merge buffers without ancestor. +BUFFER-A and BUFFER-B are the buffers to be merged. +STARTUP-HOOKS is a list of functions that Emacs calls without +arguments after setting up the Ediff buffers. JOB-NAME is a +symbol describing the Ediff job type; it defaults to +`ediff-merge-buffers', but can also be one of +`ediff-merge-files-with-ancestor', `ediff-last-dir-ancestor', +`ediff-last-dir-C', `ediff-buffers', `ediff-buffers3', or +`ediff-merge-buffers-with-ancestor'. MERGE-BUFFER-FILE is the +name of the file to be associated with the merge buffer. \(fn BUFFER-A BUFFER-B &optional STARTUP-HOOKS JOB-NAME MERGE-BUFFER-FILE)" t nil) (autoload 'ediff-merge-buffers-with-ancestor "ediff" "\ Merge buffers with ancestor. +BUFFER-A and BUFFER-B are the buffers to be merged, and +BUFFER-ANCESTOR is their ancestor. STARTUP-HOOKS is a list of +functions that Emacs calls without arguments after setting up the +Ediff buffers. JOB-NAME is a symbol describing the Ediff job +type; it defaults to `ediff-merge-buffers-with-ancestor', but can +also be one of `ediff-merge-files-with-ancestor', +`ediff-last-dir-ancestor', `ediff-last-dir-C', `ediff-buffers', +`ediff-buffers3', or `ediff-merge-buffers'. MERGE-BUFFER-FILE is +the name of the file to be associated with the merge buffer. \(fn BUFFER-A BUFFER-B BUFFER-ANCESTOR &optional STARTUP-HOOKS JOB-NAME MERGE-BUFFER-FILE)" t nil) (autoload 'ediff-merge-revisions "ediff" "\ Run Ediff by merging two revisions of a file. -The file is the optional FILE argument or the file visited by the current -buffer. +The file is the optional FILE argument or the file visited by the +current buffer. STARTUP-HOOKS is a list of functions that Emacs +calls without arguments after setting up the Ediff buffers. +MERGE-BUFFER-FILE is the name of the file to be associated with +the merge buffer. \(fn &optional FILE STARTUP-HOOKS MERGE-BUFFER-FILE)" t nil) (autoload 'ediff-merge-revisions-with-ancestor "ediff" "\ Run Ediff by merging two revisions of a file with a common ancestor. -The file is the optional FILE argument or the file visited by the current -buffer. +The file is the optional FILE argument or the file visited by the +current buffer. STARTUP-HOOKS is a list of functions that Emacs +calls without arguments after setting up the Ediff buffers. +MERGE-BUFFER-FILE is the name of the file to be associated with +the merge buffer. \(fn &optional FILE STARTUP-HOOKS MERGE-BUFFER-FILE)" t nil) @@ -9250,8 +9401,8 @@ buffer. Query for a file name, and then run Ediff by patching that file. If optional PATCH-BUF is given, use the patch in that buffer and don't ask the user. -If prefix argument, then: if even argument, assume that the patch is in a -buffer. If odd -- assume it is in a file. +If prefix argument ARG, then: if even argument, assume that the +patch is in a buffer. If odd -- assume it is in a file. \(fn &optional ARG PATCH-BUF)" t nil) @@ -9262,7 +9413,7 @@ prompts for the buffer or a file, depending on the answer. With ARG=1, assumes the patch is in a file and prompts for the file. With ARG=2, assumes the patch is in a buffer and prompts for the buffer. PATCH-BUF is an optional argument, which specifies the buffer that contains the -patch. If not given, the user is prompted according to the prefix argument. +patch. If not given, the user is prompted according to the prefix argument. \(fn &optional ARG PATCH-BUF)" t nil) @@ -9275,6 +9426,8 @@ Run Ediff by comparing versions of a file. The file is an optional FILE argument or the file entered at the prompt. Default: the file visited by the current buffer. Uses `vc.el' or `rcs.el' depending on `ediff-version-control-package'. +STARTUP-HOOKS is a list of functions that Emacs calls without +arguments after setting up the Ediff buffers. \(fn &optional FILE STARTUP-HOOKS)" t nil) @@ -9293,42 +9446,42 @@ With optional NODE, goes to that node. \(fn &optional NODE)" t nil) (autoload 'ediff-files-command "ediff" "\ - +Call `ediff-files' with the next two command line arguments. \(fn)" nil nil) (autoload 'ediff3-files-command "ediff" "\ - +Call `ediff3-files' with the next three command line arguments. \(fn)" nil nil) (autoload 'ediff-merge-command "ediff" "\ - +Call `ediff-merge-files' with the next two command line arguments. \(fn)" nil nil) (autoload 'ediff-merge-with-ancestor-command "ediff" "\ - +Call `ediff-merge-files-with-ancestor' with the next three command line arguments. \(fn)" nil nil) (autoload 'ediff-directories-command "ediff" "\ - +Call `ediff-directories' with the next three command line arguments. \(fn)" nil nil) (autoload 'ediff-directories3-command "ediff" "\ - +Call `ediff-directories3' with the next four command line arguments. \(fn)" nil nil) (autoload 'ediff-merge-directories-command "ediff" "\ - +Call `ediff-merge-directories' with the next three command line arguments. \(fn)" nil nil) (autoload 'ediff-merge-directories-with-ancestor-command "ediff" "\ - +Call `ediff-merge-directories-with-ancestor' with the next four command line arguments. \(fn)" nil nil) @@ -9642,15 +9795,6 @@ It creates an autoload function for CNAME's constructor. ;;;### (autoloads nil "elec-pair" "elec-pair.el" (0 0 0 0)) ;;; Generated autoloads from elec-pair.el -(defvar electric-pair-text-pairs '((34 . 34) ((nth 0 electric-quote-chars) nth 1 electric-quote-chars) ((nth 2 electric-quote-chars) nth 3 electric-quote-chars)) "\ -Alist of pairs that should always be used in comments and strings. - -Pairs of delimiters in this list are a fallback in case they have -no syntax relevant to `electric-pair-mode' in the syntax table -defined in `electric-pair-text-syntax-table'") - -(custom-autoload 'electric-pair-text-pairs "elec-pair" t) - (defvar electric-pair-mode nil "\ Non-nil if Electric-Pair mode is enabled. See the `electric-pair-mode' command @@ -10470,12 +10614,11 @@ Otherwise, connect to HOST:PORT as USER and /join CHANNEL. ;;;*** -;;;### (autoloads nil "erc-autoaway" "erc/erc-autoaway.el" (0 0 0 -;;;;;; 0)) +;;;### (autoloads "actual autoloads are elsewhere" "erc-autoaway" +;;;;;; "erc/erc-autoaway.el" (0 0 0 0)) ;;; Generated autoloads from erc/erc-autoaway.el - (autoload 'erc-autoaway-mode "erc-autoaway") -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-autoaway" '("erc-auto" "autoaway"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-autoaway" '("erc-auto"))) ;;;*** @@ -10486,144 +10629,57 @@ Otherwise, connect to HOST:PORT as USER and /join CHANNEL. ;;;*** -;;;### (autoloads nil "erc-button" "erc/erc-button.el" (0 0 0 0)) +;;;### (autoloads "actual autoloads are elsewhere" "erc-button" "erc/erc-button.el" +;;;;;; (0 0 0 0)) ;;; Generated autoloads from erc/erc-button.el - (autoload 'erc-button-mode "erc-button" nil t) -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-button" '("erc-" "button"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-button" '("erc-"))) ;;;*** -;;;### (autoloads nil "erc-capab" "erc/erc-capab.el" (0 0 0 0)) +;;;### (autoloads "actual autoloads are elsewhere" "erc-capab" "erc/erc-capab.el" +;;;;;; (0 0 0 0)) ;;; Generated autoloads from erc/erc-capab.el - (autoload 'erc-capab-identify-mode "erc-capab" nil t) -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-capab" '("erc-capab-identify-" "capab-identify"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-capab" '("erc-capab-identify-"))) ;;;*** -;;;### (autoloads nil "erc-compat" "erc/erc-compat.el" (0 0 0 0)) +;;;### (autoloads "actual autoloads are elsewhere" "erc-compat" "erc/erc-compat.el" +;;;;;; (0 0 0 0)) ;;; Generated autoloads from erc/erc-compat.el - (autoload 'erc-define-minor-mode "erc-compat") (if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-compat" '("erc-"))) ;;;*** -;;;### (autoloads nil "erc-dcc" "erc/erc-dcc.el" (0 0 0 0)) +;;;### (autoloads "actual autoloads are elsewhere" "erc-dcc" "erc/erc-dcc.el" +;;;;;; (0 0 0 0)) ;;; Generated autoloads from erc/erc-dcc.el - (autoload 'erc-dcc-mode "erc-dcc") - -(autoload 'erc-cmd-DCC "erc-dcc" "\ -Parser for /dcc command. -This figures out the dcc subcommand and calls the appropriate routine to -handle it. The function dispatched should be named \"erc-dcc-do-FOO-command\", -where FOO is one of CLOSE, GET, SEND, LIST, CHAT, etc. - -\(fn CMD &rest ARGS)" nil nil) -(autoload 'pcomplete/erc-mode/DCC "erc-dcc" "\ -Provides completion for the /DCC command. - -\(fn)" nil nil) - -(defvar erc-ctcp-query-DCC-hook '(erc-ctcp-query-DCC) "\ -Hook variable for CTCP DCC queries.") - -(autoload 'erc-ctcp-query-DCC "erc-dcc" "\ -The function called when a CTCP DCC request is detected by the client. -It examines the DCC subcommand, and calls the appropriate routine for -that subcommand. - -\(fn PROC NICK LOGIN HOST TO QUERY)" nil nil) - -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-dcc" '("erc-" "pcomplete/erc-mode/" "dcc"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-dcc" '("erc-" "pcomplete/erc-mode/"))) ;;;*** -;;;### (autoloads nil "erc-desktop-notifications" "erc/erc-desktop-notifications.el" -;;;;;; (0 0 0 0)) +;;;### (autoloads "actual autoloads are elsewhere" "erc-desktop-notifications" +;;;;;; "erc/erc-desktop-notifications.el" (0 0 0 0)) ;;; Generated autoloads from erc/erc-desktop-notifications.el -(autoload 'erc-notifications-mode "erc-desktop-notifications" "" t) -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-desktop-notifications" '("notifications" "erc-notifications-"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-desktop-notifications" '("erc-notifications-"))) ;;;*** -;;;### (autoloads nil "erc-ezbounce" "erc/erc-ezbounce.el" (0 0 0 -;;;;;; 0)) +;;;### (autoloads "actual autoloads are elsewhere" "erc-ezbounce" +;;;;;; "erc/erc-ezbounce.el" (0 0 0 0)) ;;; Generated autoloads from erc/erc-ezbounce.el -(autoload 'erc-cmd-ezb "erc-ezbounce" "\ -Send EZB commands to the EZBouncer verbatim. - -\(fn LINE &optional FORCE)" nil nil) - -(autoload 'erc-ezb-get-login "erc-ezbounce" "\ -Return an appropriate EZBounce login for SERVER and PORT. -Look up entries in `erc-ezb-login-alist'. If the username or password -in the alist is nil, prompt for the appropriate values. - -\(fn SERVER PORT)" nil nil) - -(autoload 'erc-ezb-lookup-action "erc-ezbounce" "\ - - -\(fn MESSAGE)" nil nil) - -(autoload 'erc-ezb-notice-autodetect "erc-ezbounce" "\ -React on an EZBounce NOTICE request. - -\(fn PROC PARSED)" nil nil) - -(autoload 'erc-ezb-identify "erc-ezbounce" "\ -Identify to the EZBouncer server. - -\(fn MESSAGE)" nil nil) - -(autoload 'erc-ezb-init-session-list "erc-ezbounce" "\ -Reset the EZBounce session list to nil. - -\(fn MESSAGE)" nil nil) - -(autoload 'erc-ezb-end-of-session-list "erc-ezbounce" "\ -Indicate the end of the EZBounce session listing. - -\(fn MESSAGE)" nil nil) - -(autoload 'erc-ezb-add-session "erc-ezbounce" "\ -Add an EZBounce session to the session list. - -\(fn MESSAGE)" nil nil) - -(autoload 'erc-ezb-select "erc-ezbounce" "\ -Select an IRC server to use by EZBounce, in ERC style. - -\(fn MESSAGE)" nil nil) - -(autoload 'erc-ezb-select-session "erc-ezbounce" "\ -Select a detached EZBounce session. - -\(fn)" nil nil) - -(autoload 'erc-ezb-initialize "erc-ezbounce" "\ -Add EZBouncer convenience functions to ERC. - -\(fn)" nil nil) - (if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-ezbounce" '("erc-ezb-"))) ;;;*** -;;;### (autoloads nil "erc-fill" "erc/erc-fill.el" (0 0 0 0)) +;;;### (autoloads "actual autoloads are elsewhere" "erc-fill" "erc/erc-fill.el" +;;;;;; (0 0 0 0)) ;;; Generated autoloads from erc/erc-fill.el - (autoload 'erc-fill-mode "erc-fill" nil t) - -(autoload 'erc-fill "erc-fill" "\ -Fill a region using the function referenced in `erc-fill-function'. -You can put this on `erc-insert-modify-hook' and/or `erc-send-modify-hook'. - -\(fn)" nil nil) (if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-fill" '("erc-"))) @@ -10632,7 +10688,7 @@ You can put this on `erc-insert-modify-hook' and/or `erc-send-modify-hook'. ;;;### (autoloads nil "erc-goodies" "erc/erc-goodies.el" (0 0 0 0)) ;;; Generated autoloads from erc/erc-goodies.el -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-goodies" '("erc-" "unmorse" "scrolltobottom" "smiley" "irccontrols" "noncommands" "keep-place" "move-to-prompt" "readonly"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-goodies" '("erc-"))) ;;;*** @@ -10643,46 +10699,27 @@ You can put this on `erc-insert-modify-hook' and/or `erc-send-modify-hook'. ;;;*** -;;;### (autoloads nil "erc-identd" "erc/erc-identd.el" (0 0 0 0)) +;;;### (autoloads "actual autoloads are elsewhere" "erc-identd" "erc/erc-identd.el" +;;;;;; (0 0 0 0)) ;;; Generated autoloads from erc/erc-identd.el - (autoload 'erc-identd-mode "erc-identd") - -(autoload 'erc-identd-start "erc-identd" "\ -Start an identd server listening to port 8113. -Port 113 (auth) will need to be redirected to port 8113 on your -machine -- using iptables, or a program like redir which can be -run from inetd. The idea is to provide a simple identd server -when you need one, without having to install one globally on your -system. -\(fn &optional PORT)" t nil) - -(autoload 'erc-identd-stop "erc-identd" "\ - - -\(fn &rest IGNORE)" t nil) - -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-identd" '("erc-identd-" "identd"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-identd" '("erc-identd-"))) ;;;*** -;;;### (autoloads nil "erc-imenu" "erc/erc-imenu.el" (0 0 0 0)) +;;;### (autoloads "actual autoloads are elsewhere" "erc-imenu" "erc/erc-imenu.el" +;;;;;; (0 0 0 0)) ;;; Generated autoloads from erc/erc-imenu.el -(autoload 'erc-create-imenu-index "erc-imenu" "\ - - -\(fn)" nil nil) - (if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-imenu" '("erc-unfill-notice"))) ;;;*** -;;;### (autoloads nil "erc-join" "erc/erc-join.el" (0 0 0 0)) +;;;### (autoloads "actual autoloads are elsewhere" "erc-join" "erc/erc-join.el" +;;;;;; (0 0 0 0)) ;;; Generated autoloads from erc/erc-join.el - (autoload 'erc-autojoin-mode "erc-join" nil t) -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-join" '("erc-" "autojoin"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-join" '("erc-"))) ;;;*** @@ -10693,112 +10730,43 @@ system. ;;;*** -;;;### (autoloads nil "erc-list" "erc/erc-list.el" (0 0 0 0)) +;;;### (autoloads "actual autoloads are elsewhere" "erc-list" "erc/erc-list.el" +;;;;;; (0 0 0 0)) ;;; Generated autoloads from erc/erc-list.el - (autoload 'erc-list-mode "erc-list") -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-list" '("erc-" "list"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-list" '("erc-"))) ;;;*** -;;;### (autoloads nil "erc-log" "erc/erc-log.el" (0 0 0 0)) +;;;### (autoloads "actual autoloads are elsewhere" "erc-log" "erc/erc-log.el" +;;;;;; (0 0 0 0)) ;;; Generated autoloads from erc/erc-log.el - (autoload 'erc-log-mode "erc-log" nil t) -(autoload 'erc-logging-enabled "erc-log" "\ -Return non-nil if logging is enabled for BUFFER. -If BUFFER is nil, the value of `current-buffer' is used. -Logging is enabled if `erc-log-channels-directory' is non-nil, the directory -is writable (it will be created as necessary) and -`erc-enable-logging' returns a non-nil value. - -\(fn &optional BUFFER)" nil nil) - -(autoload 'erc-save-buffer-in-logs "erc-log" "\ -Append BUFFER contents to the log file, if logging is enabled. -If BUFFER is not provided, current buffer is used. -Logging is enabled if `erc-logging-enabled' returns non-nil. - -This is normally done on exit, to save the unsaved portion of the -buffer, since only the text that runs off the buffer limit is logged -automatically. - -You can save every individual message by putting this function on -`erc-insert-post-hook'. - -\(fn &optional BUFFER)" t nil) - -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-log" '("erc-" "log"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-log" '("erc-"))) ;;;*** -;;;### (autoloads nil "erc-match" "erc/erc-match.el" (0 0 0 0)) +;;;### (autoloads "actual autoloads are elsewhere" "erc-match" "erc/erc-match.el" +;;;;;; (0 0 0 0)) ;;; Generated autoloads from erc/erc-match.el - (autoload 'erc-match-mode "erc-match") - -(autoload 'erc-add-pal "erc-match" "\ -Add pal interactively to `erc-pals'. - -\(fn)" t nil) - -(autoload 'erc-delete-pal "erc-match" "\ -Delete pal interactively to `erc-pals'. -\(fn)" t nil) - -(autoload 'erc-add-fool "erc-match" "\ -Add fool interactively to `erc-fools'. - -\(fn)" t nil) - -(autoload 'erc-delete-fool "erc-match" "\ -Delete fool interactively to `erc-fools'. - -\(fn)" t nil) - -(autoload 'erc-add-keyword "erc-match" "\ -Add keyword interactively to `erc-keywords'. - -\(fn)" t nil) - -(autoload 'erc-delete-keyword "erc-match" "\ -Delete keyword interactively to `erc-keywords'. - -\(fn)" t nil) - -(autoload 'erc-add-dangerous-host "erc-match" "\ -Add dangerous-host interactively to `erc-dangerous-hosts'. - -\(fn)" t nil) - -(autoload 'erc-delete-dangerous-host "erc-match" "\ -Delete dangerous-host interactively to `erc-dangerous-hosts'. - -\(fn)" t nil) - -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-match" '("erc-" "match"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-match" '("erc-"))) ;;;*** -;;;### (autoloads nil "erc-menu" "erc/erc-menu.el" (0 0 0 0)) +;;;### (autoloads "actual autoloads are elsewhere" "erc-menu" "erc/erc-menu.el" +;;;;;; (0 0 0 0)) ;;; Generated autoloads from erc/erc-menu.el - (autoload 'erc-menu-mode "erc-menu" nil t) -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-menu" '("erc-menu-" "menu"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-menu" '("erc-menu-"))) ;;;*** -;;;### (autoloads nil "erc-netsplit" "erc/erc-netsplit.el" (0 0 0 -;;;;;; 0)) +;;;### (autoloads "actual autoloads are elsewhere" "erc-netsplit" +;;;;;; "erc/erc-netsplit.el" (0 0 0 0)) ;;; Generated autoloads from erc/erc-netsplit.el - (autoload 'erc-netsplit-mode "erc-netsplit") - -(autoload 'erc-cmd-WHOLEFT "erc-netsplit" "\ -Show who's gone. -\(fn)" nil nil) - -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-netsplit" '("erc-" "netsplit"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-netsplit" '("erc-"))) ;;;*** @@ -10818,182 +10786,111 @@ Interactively select a server to connect to using `erc-server-alist'. \(fn)" t nil) -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-networks" '("erc-" "networks"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-networks" '("erc-"))) ;;;*** -;;;### (autoloads nil "erc-notify" "erc/erc-notify.el" (0 0 0 0)) +;;;### (autoloads "actual autoloads are elsewhere" "erc-notify" "erc/erc-notify.el" +;;;;;; (0 0 0 0)) ;;; Generated autoloads from erc/erc-notify.el - (autoload 'erc-notify-mode "erc-notify" nil t) -(autoload 'erc-cmd-NOTIFY "erc-notify" "\ -Change `erc-notify-list' or list current notify-list members online. -Without args, list the current list of notified people online, -with args, toggle notify status of people. - -\(fn &rest ARGS)" nil nil) - -(autoload 'pcomplete/erc-mode/NOTIFY "erc-notify" "\ - - -\(fn)" nil nil) - -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-notify" '("erc-" "notify"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-notify" '("erc-"))) ;;;*** -;;;### (autoloads nil "erc-page" "erc/erc-page.el" (0 0 0 0)) +;;;### (autoloads "actual autoloads are elsewhere" "erc-page" "erc/erc-page.el" +;;;;;; (0 0 0 0)) ;;; Generated autoloads from erc/erc-page.el - (autoload 'erc-page-mode "erc-page") -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-page" '("erc-" "page"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-page" '("erc-"))) ;;;*** -;;;### (autoloads nil "erc-pcomplete" "erc/erc-pcomplete.el" (0 0 -;;;;;; 0 0)) +;;;### (autoloads "actual autoloads are elsewhere" "erc-pcomplete" +;;;;;; "erc/erc-pcomplete.el" (0 0 0 0)) ;;; Generated autoloads from erc/erc-pcomplete.el - (autoload 'erc-completion-mode "erc-pcomplete" nil t) (if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-pcomplete" '("pcomplete" "erc-pcomplet"))) ;;;*** -;;;### (autoloads nil "erc-replace" "erc/erc-replace.el" (0 0 0 0)) +;;;### (autoloads "actual autoloads are elsewhere" "erc-replace" +;;;;;; "erc/erc-replace.el" (0 0 0 0)) ;;; Generated autoloads from erc/erc-replace.el - (autoload 'erc-replace-mode "erc-replace") -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-replace" '("replace" "erc-replace-"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-replace" '("erc-replace-"))) ;;;*** -;;;### (autoloads nil "erc-ring" "erc/erc-ring.el" (0 0 0 0)) +;;;### (autoloads "actual autoloads are elsewhere" "erc-ring" "erc/erc-ring.el" +;;;;;; (0 0 0 0)) ;;; Generated autoloads from erc/erc-ring.el - (autoload 'erc-ring-mode "erc-ring" nil t) -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-ring" '("erc-" "ring"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-ring" '("erc-"))) ;;;*** -;;;### (autoloads nil "erc-services" "erc/erc-services.el" (0 0 0 -;;;;;; 0)) +;;;### (autoloads "actual autoloads are elsewhere" "erc-services" +;;;;;; "erc/erc-services.el" (0 0 0 0)) ;;; Generated autoloads from erc/erc-services.el - (autoload 'erc-services-mode "erc-services" nil t) - -(autoload 'erc-nickserv-identify-mode "erc-services" "\ -Set up hooks according to which MODE the user has chosen. - -\(fn MODE)" t nil) - -(autoload 'erc-nickserv-identify "erc-services" "\ -Send an \"identify <PASSWORD>\" message to NickServ. -When called interactively, read the password using `read-passwd'. - -\(fn PASSWORD)" t nil) -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-services" '("erc-" "services"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-services" '("erc-"))) ;;;*** -;;;### (autoloads nil "erc-sound" "erc/erc-sound.el" (0 0 0 0)) +;;;### (autoloads "actual autoloads are elsewhere" "erc-sound" "erc/erc-sound.el" +;;;;;; (0 0 0 0)) ;;; Generated autoloads from erc/erc-sound.el - (autoload 'erc-sound-mode "erc-sound") -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-sound" '("erc-" "sound"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-sound" '("erc-"))) ;;;*** -;;;### (autoloads nil "erc-speedbar" "erc/erc-speedbar.el" (0 0 0 -;;;;;; 0)) +;;;### (autoloads "actual autoloads are elsewhere" "erc-speedbar" +;;;;;; "erc/erc-speedbar.el" (0 0 0 0)) ;;; Generated autoloads from erc/erc-speedbar.el -(autoload 'erc-speedbar-browser "erc-speedbar" "\ -Initialize speedbar to display an ERC browser. -This will add a speedbar major display mode. - -\(fn)" t nil) - (if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-speedbar" '("erc-"))) ;;;*** -;;;### (autoloads nil "erc-spelling" "erc/erc-spelling.el" (0 0 0 -;;;;;; 0)) +;;;### (autoloads "actual autoloads are elsewhere" "erc-spelling" +;;;;;; "erc/erc-spelling.el" (0 0 0 0)) ;;; Generated autoloads from erc/erc-spelling.el - (autoload 'erc-spelling-mode "erc-spelling" nil t) -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-spelling" '("erc-spelling-" "spelling"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-spelling" '("erc-spelling-"))) ;;;*** -;;;### (autoloads nil "erc-stamp" "erc/erc-stamp.el" (0 0 0 0)) +;;;### (autoloads "actual autoloads are elsewhere" "erc-stamp" "erc/erc-stamp.el" +;;;;;; (0 0 0 0)) ;;; Generated autoloads from erc/erc-stamp.el - (autoload 'erc-timestamp-mode "erc-stamp" nil t) -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-stamp" '("erc-" "stamp"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-stamp" '("erc-"))) ;;;*** -;;;### (autoloads nil "erc-track" "erc/erc-track.el" (0 0 0 0)) +;;;### (autoloads "actual autoloads are elsewhere" "erc-track" "erc/erc-track.el" +;;;;;; (0 0 0 0)) ;;; Generated autoloads from erc/erc-track.el -(defvar erc-track-minor-mode nil "\ -Non-nil if Erc-Track minor mode is enabled. -See the `erc-track-minor-mode' command -for a description of this minor mode.") - -(custom-autoload 'erc-track-minor-mode "erc-track" nil) - -(autoload 'erc-track-minor-mode "erc-track" "\ -Toggle mode line display of ERC activity (ERC Track minor mode). -With a prefix argument ARG, enable ERC Track minor mode if ARG is -positive, and disable it otherwise. If called from Lisp, enable -the mode if ARG is omitted or nil. - -ERC Track minor mode is a global minor mode. It exists for the -sole purpose of providing the C-c C-SPC and C-c C-@ keybindings. -Make sure that you have enabled the track module, otherwise the -keybindings will not do anything useful. - -\(fn &optional ARG)" t nil) - (autoload 'erc-track-mode "erc-track" nil t) - -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-track" '("erc-" "track"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-track" '("erc-"))) ;;;*** -;;;### (autoloads nil "erc-truncate" "erc/erc-truncate.el" (0 0 0 -;;;;;; 0)) +;;;### (autoloads "actual autoloads are elsewhere" "erc-truncate" +;;;;;; "erc/erc-truncate.el" (0 0 0 0)) ;;; Generated autoloads from erc/erc-truncate.el - (autoload 'erc-truncate-mode "erc-truncate" nil t) -(autoload 'erc-truncate-buffer-to-size "erc-truncate" "\ -Truncates the buffer to the size SIZE. -If BUFFER is not provided, the current buffer is assumed. The deleted -region is logged if `erc-logging-enabled' returns non-nil. - -\(fn SIZE &optional BUFFER)" nil nil) - -(autoload 'erc-truncate-buffer "erc-truncate" "\ -Truncates the current buffer to `erc-max-buffer-size'. -Meant to be used in hooks, like `erc-insert-post-hook'. - -\(fn)" t nil) - -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-truncate" '("truncate" "erc-max-buffer-size"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-truncate" '("erc-max-buffer-size"))) ;;;*** -;;;### (autoloads nil "erc-xdcc" "erc/erc-xdcc.el" (0 0 0 0)) +;;;### (autoloads "actual autoloads are elsewhere" "erc-xdcc" "erc/erc-xdcc.el" +;;;;;; (0 0 0 0)) ;;; Generated autoloads from erc/erc-xdcc.el - (autoload 'erc-xdcc-mode "erc-xdcc") - -(autoload 'erc-xdcc-add-file "erc-xdcc" "\ -Add a file to `erc-xdcc-files'. - -\(fn FILE)" t nil) -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-xdcc" '("erc-" "xdcc"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "erc-xdcc" '("erc-"))) ;;;*** @@ -11892,9 +11789,12 @@ Render FILE using EWW. (autoload 'eww-search-words "eww" "\ Search the web for the text between BEG and END. -See the `eww-search-prefix' variable for the search engine used. +If region is active (and not whitespace), search the web for +the text between BEG and END. Else, prompt the user for a search +string. See the `eww-search-prefix' variable for the search +engine used. -\(fn &optional BEG END)" t nil) +\(fn)" t nil) (autoload 'eww-mode "eww" "\ Mode for browsing the web. @@ -11935,7 +11835,7 @@ command to find the next error. The buffer is also in `comint-mode' and (autoload 'executable-set-magic "executable" "\ Set this buffer's interpreter to INTERPRETER with optional ARGUMENT. -The variables `executable-magicless-file-regexp', `executable-prefix', +The variables `executable-magicless-file-regexp', `executable-prefix-env', `executable-insert', `executable-query' and `executable-chmod' control when and how magic numbers are inserted or replaced and scripts made executable. @@ -12241,6 +12141,49 @@ Besides the choice of face, it is the same as `buffer-face-mode'. ;;;*** +;;;### (autoloads nil "faceup" "emacs-lisp/faceup.el" (0 0 0 0)) +;;; Generated autoloads from emacs-lisp/faceup.el +(push (purecopy '(faceup 0 0 6)) package--builtin-versions) + +(autoload 'faceup-view-buffer "faceup" "\ +Display the faceup representation of the current buffer. + +\(fn)" t nil) + +(autoload 'faceup-write-file "faceup" "\ +Save the faceup representation of the current buffer to the file FILE-NAME. + +Unless a name is given, the file will be named xxx.faceup, where +xxx is the file name associated with the buffer. + +If optional second arg CONFIRM is non-nil, this function +asks for confirmation before overwriting an existing file. +Interactively, confirmation is required unless you supply a prefix argument. + +\(fn &optional FILE-NAME CONFIRM)" t nil) + +(autoload 'faceup-render-view-buffer "faceup" "\ +Convert BUFFER containing Faceup markup to a new buffer and display it. + +\(fn &optional BUFFER)" t nil) + +(autoload 'faceup-clean-buffer "faceup" "\ +Remove faceup markup from buffer. + +\(fn)" t nil) + +(autoload 'faceup-defexplainer "faceup" "\ +Defines an Ert explainer function for FUNCTION. + +FUNCTION must return an explanation when the test fails and +`faceup-test-explain' is set. + +\(fn FUNCTION)" nil t) + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "faceup" '("faceup-"))) + +;;;*** + ;;;### (autoloads nil "feedmail" "mail/feedmail.el" (0 0 0 0)) ;;; Generated autoloads from mail/feedmail.el (push (purecopy '(feedmail 11)) package--builtin-versions) @@ -12316,7 +12259,8 @@ If `ffap-url-regexp' is not nil, the FILENAME may also be an URL. With a prefix, this command behaves exactly like `ffap-file-finder'. If `ffap-require-prefix' is set, the prefix meaning is reversed. See also the variables `ffap-dired-wildcards', `ffap-newfile-prompt', -and the functions `ffap-file-at-point' and `ffap-url-at-point'. +`ffap-url-unwrap-local', `ffap-url-unwrap-remote', and the functions +`ffap-file-at-point' and `ffap-url-at-point'. \(fn &optional FILENAME)" t nil) @@ -12399,7 +12343,7 @@ STRING is passed as an argument to the locate command. \(fn STRING)" t nil) (autoload 'file-cache-add-directory-recursively "filecache" "\ -Adds DIR and any subdirectories to the file-cache. +Add DIR and any subdirectories to the file-cache. This function does not use any external programs. If the optional REGEXP argument is non-nil, only files which match it will be added to the cache. Note that the REGEXP is applied to the @@ -12614,14 +12558,14 @@ See `find-name-arg' to customize the arguments. \(fn DIR PATTERN)" t nil) (autoload 'find-grep-dired "find-dired" "\ -Find files in DIR matching a regexp REGEXP and start Dired on output. +Find files in DIR that contain matches for REGEXP and start Dired on output. The command run (after changing into DIR) is find . \\( -type f -exec `grep-program' `find-grep-options' \\ -e REGEXP {} \\; \\) -ls -where the car of the variable `find-ls-option' specifies what to -use in place of \"-ls\" as the final argument. +where the first string in the value of the variable `find-ls-option' +specifies what to use in place of \"-ls\" as the final argument. \(fn DIR REGEXP)" t nil) @@ -12999,31 +12943,96 @@ to get the effect of a C-q. ;;; Generated autoloads from progmodes/flymake.el (push (purecopy '(flymake 0 3)) package--builtin-versions) +(autoload 'flymake-log "flymake" "\ +Log, at level LEVEL, the message MSG formatted with ARGS. +LEVEL is passed to `display-warning', which is used to display +the warning. If this form is included in a byte-compiled file, +the generated warning contains an indication of the file that +generated it. + +\(fn LEVEL MSG &rest ARGS)" nil t) + +(autoload 'flymake-make-diagnostic "flymake" "\ +Make a Flymake diagnostic for BUFFER's region from BEG to END. +TYPE is a key to `flymake-diagnostic-types-alist' and TEXT is a +description of the problem detected in this region. + +\(fn BUFFER BEG END TYPE TEXT)" nil nil) + +(autoload 'flymake-diagnostics "flymake" "\ +Get Flymake diagnostics in region determined by BEG and END. + +If neither BEG or END is supplied, use the whole buffer, +otherwise if BEG is non-nil and END is nil, consider only +diagnostics at BEG. + +\(fn &optional BEG END)" nil nil) + +(autoload 'flymake-diag-region "flymake" "\ +Compute BUFFER's region (BEG . END) corresponding to LINE and COL. +If COL is nil, return a region just for LINE. Return nil if the +region is invalid. + +\(fn BUFFER LINE &optional COL)" nil nil) + (autoload 'flymake-mode "flymake" "\ Toggle Flymake mode on or off. With a prefix argument ARG, enable Flymake mode if ARG is positive, and disable it otherwise. If called from Lisp, enable the mode if ARG is omitted or nil, and toggle it if ARG is `toggle'. -\\{flymake-mode-map} + +Flymake is an Emacs minor mode for on-the-fly syntax checking. +Flymake collects diagnostic information from multiple sources, +called backends, and visually annotates the buffer with the +results. + +Flymake performs these checks while the user is editing. The +customization variables `flymake-start-on-flymake-mode', +`flymake-no-changes-timeout' and +`flymake-start-syntax-check-on-newline' determine the exact +circumstances whereupon Flymake decides to initiate a check of +the buffer. + +The commands `flymake-goto-next-error' and +`flymake-goto-prev-error' can be used to navigate among Flymake +diagnostics annotated in the buffer. + +The visual appearance of each type of diagnostic can be changed +in the variable `flymake-diagnostic-types-alist'. + +Activation or deactivation of backends used by Flymake in each +buffer happens via the special hook +`flymake-diagnostic-functions'. + +Some backends may take longer than others to respond or complete, +and some may decide to disable themselves if they are not +suitable for the current buffer. The commands +`flymake-running-backends', `flymake-disabled-backends' and +`flymake-reporting-backends' summarize the situation, as does the +special *Flymake log* buffer. \(fn &optional ARG)" t nil) (autoload 'flymake-mode-on "flymake" "\ -Turn flymake mode on. +Turn Flymake mode on. \(fn)" nil nil) (autoload 'flymake-mode-off "flymake" "\ -Turn flymake mode off. +Turn Flymake mode off. \(fn)" nil nil) -(autoload 'flymake-find-file-hook "flymake" "\ - +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "flymake" '("flymake-"))) -\(fn)" nil nil) +;;;*** + +;;;### (autoloads nil "flymake-proc" "progmodes/flymake-proc.el" +;;;;;; (0 0 0 0)) +;;; Generated autoloads from progmodes/flymake-proc.el +(push (purecopy '(flymake-proc 0 3)) package--builtin-versions) -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "flymake" '("flymake-"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "flymake-proc" '("flymake-proc-"))) ;;;*** @@ -13448,7 +13457,7 @@ and choose the directory as the fortune-file. Minimum set of parameters to filter for live (on-session) framesets. DO NOT MODIFY. See `frameset-filter-alist' for a full description.") -(defvar frameset-persistent-filter-alist (nconc '((background-color . frameset-filter-sanitize-color) (buffer-list . :never) (buffer-predicate . :never) (buried-buffer-list . :never) (delete-before . :never) (font . frameset-filter-shelve-param) (foreground-color . frameset-filter-sanitize-color) (fullscreen . frameset-filter-shelve-param) (GUI:font . frameset-filter-unshelve-param) (GUI:fullscreen . frameset-filter-unshelve-param) (GUI:height . frameset-filter-unshelve-param) (GUI:width . frameset-filter-unshelve-param) (height . frameset-filter-shelve-param) (outer-window-id . :never) (parent-frame . :never) (parent-id . :never) (mouse-wheel-frame . :never) (tty . frameset-filter-tty-to-GUI) (tty-type . frameset-filter-tty-to-GUI) (width . frameset-filter-shelve-param) (window-id . :never) (window-system . :never)) frameset-session-filter-alist) "\ +(defvar frameset-persistent-filter-alist (nconc '((background-color . frameset-filter-sanitize-color) (buffer-list . :never) (buffer-predicate . :never) (buried-buffer-list . :never) (client . :never) (delete-before . :never) (font . frameset-filter-font-param) (foreground-color . frameset-filter-sanitize-color) (fullscreen . frameset-filter-shelve-param) (GUI:font . frameset-filter-unshelve-param) (GUI:fullscreen . frameset-filter-unshelve-param) (GUI:height . frameset-filter-unshelve-param) (GUI:width . frameset-filter-unshelve-param) (height . frameset-filter-shelve-param) (outer-window-id . :never) (parent-frame . :never) (parent-id . :never) (mouse-wheel-frame . :never) (tty . frameset-filter-tty-to-GUI) (tty-type . frameset-filter-tty-to-GUI) (width . frameset-filter-shelve-param) (window-id . :never) (window-system . :never)) frameset-session-filter-alist) "\ Parameters to filter for persistent framesets. DO NOT MODIFY. See `frameset-filter-alist' for a full description.") @@ -14484,8 +14493,7 @@ match any of the group-specified splitting rules. See (autoload 'gnus-group-split-update "gnus-mlspl" "\ Computes nnmail-split-fancy from group params and CATCH-ALL. -It does this by calling by calling (gnus-group-split-fancy nil -nil CATCH-ALL). +It does this by calling (gnus-group-split-fancy nil nil CATCH-ALL). If CATCH-ALL is nil, `gnus-group-split-default-catch-all-group' is used instead. This variable is set by `gnus-group-split-setup'. @@ -14998,8 +15006,9 @@ List of hook functions run by `grep-process-setup' (see `run-hooks').") (custom-autoload 'grep-setup-hook "grep" t) -(defconst grep-regexp-alist '(("^\\(.*?[^/\n]\\):[ ]*\\([1-9][0-9]*\\)[ ]*:" 1 2 ((lambda nil (when grep-highlight-matches (let* ((beg (match-end 0)) (end (save-excursion (goto-char beg) (line-end-position))) (mbeg (text-property-any beg end 'font-lock-face grep-match-face))) (when mbeg (- mbeg beg))))) lambda nil (when grep-highlight-matches (let* ((beg (match-end 0)) (end (save-excursion (goto-char beg) (line-end-position))) (mbeg (text-property-any beg end 'font-lock-face grep-match-face)) (mend (and mbeg (next-single-property-change mbeg 'font-lock-face nil end)))) (when mend (- mend beg)))))) ("^Binary file \\(.+\\) matches$" 1 nil nil 0 1)) "\ -Regexp used to match grep hits. See `compilation-error-regexp-alist'.") +(defconst grep-regexp-alist `((,(concat "^\\(?:" "\\(?1:[^ +Regexp used to match grep hits. +See `compilation-error-regexp-alist' for format details.") (defvar grep-program (purecopy "grep") "\ The default grep program for `grep-command' and `grep-find-command'. @@ -15085,7 +15094,9 @@ easily repeat a find command. Run grep, searching for REGEXP in FILES in directory DIR. The search is limited to file names matching shell pattern FILES. FILES may use abbreviations defined in `grep-files-aliases', e.g. -entering `ch' is equivalent to `*.[ch]'. +entering `ch' is equivalent to `*.[ch]'. As whitespace triggers +completion when entering a pattern, including it requires +quoting, e.g. `\\[quoted-insert]<space>'. With \\[universal-argument] prefix, you can edit the constructed shell command line before it is executed. @@ -15103,7 +15114,9 @@ This command shares argument histories with \\[rgrep] and \\[grep]. Recursively grep for REGEXP in FILES in directory tree rooted at DIR. The search is limited to file names matching shell pattern FILES. FILES may use abbreviations defined in `grep-files-aliases', e.g. -entering `ch' is equivalent to `*.[ch]'. +entering `ch' is equivalent to `*.[ch]'. As whitespace triggers +completion when entering a pattern, including it requires +quoting, e.g. `\\[quoted-insert]<space>'. With \\[universal-argument] prefix, you can edit the constructed shell command line before it is executed. @@ -16544,18 +16557,6 @@ The optional LABEL is used to label the buffer created. ;;;*** -;;;### (autoloads nil "html2text" "net/html2text.el" (0 0 0 0)) -;;; Generated autoloads from net/html2text.el - -(autoload 'html2text "html2text" "\ -Convert HTML to plain text in the current buffer. - -\(fn)" t nil) - -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "html2text" '("html2text-"))) - -;;;*** - ;;;### (autoloads nil "htmlfontify" "htmlfontify.el" (0 0 0 0)) ;;; Generated autoloads from htmlfontify.el (push (purecopy '(htmlfontify 0 21)) package--builtin-versions) @@ -16596,7 +16597,7 @@ You may also want to set `hfy-page-header' and `hfy-page-footer'. ;;;;;; (0 0 0 0)) ;;; Generated autoloads from ibuf-ext.el -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ibuf-ext" '("ibuffer-" "file" "shell-command-" "starred-name" "size" "alphabetic" "major-mode" "mod" "print" "predicate" "content" "view-and-eval" "visiting-file" "derived-mode" "directory" "basename" "name" "used-mode" "query-replace" "rename-uniquely" "revert" "replace-regexp" "eval"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ibuf-ext" '("ibuffer-" "file" "shell-command-" "starred-name" "size" "alphabetic" "major-mode" "mod" "print" "process" "predicate" "content" "view-and-eval" "visiting-file" "derived-mode" "directory" "basename" "name" "used-mode" "query-replace" "rename-uniquely" "revert" "replace-regexp" "eval"))) ;;;*** @@ -17245,7 +17246,7 @@ For details of keybindings, see `ido-find-file'. \(fn)" t nil) (autoload 'ido-find-alternate-file "ido" "\ -Switch to another file and show it in another window. +Find another file, select its buffer, kill previous buffer. The file name is selected interactively by typing a substring. For details of keybindings, see `ido-find-file'. @@ -19159,7 +19160,8 @@ Use \\[kmacro-insert-counter] to insert (and increment) the macro counter. The counter value can be set or modified via \\[kmacro-set-counter] and \\[kmacro-add-counter]. The format of the counter can be modified via \\[kmacro-set-format]. -Use \\[kmacro-name-last-macro] to give it a permanent name. +Use \\[kmacro-name-last-macro] to give it a name that will remain valid even +after another macro is defined. Use \\[kmacro-bind-to-key] to bind it to a key sequence. \(fn ARG)" t nil) @@ -19187,8 +19189,8 @@ just the last key in the key sequence that you used to call this command. See `kmacro-call-repeat-key' and `kmacro-call-repeat-with-arg' for details on how to adjust or disable this behavior. -To make a macro permanent so you can call it even after defining -others, use \\[kmacro-name-last-macro]. +To give a macro a name so you can call it even after defining others, +use \\[kmacro-name-last-macro]. \(fn ARG &optional NO-REPEAT END-MACRO MACRO)" t nil) @@ -19223,8 +19225,8 @@ Call last keyboard macro, ending it first if currently being defined. With numeric prefix ARG, repeat macro that many times. Zero argument means repeat until there is an error. -To give a macro a permanent name, so you can call it -even after defining other macros, use \\[kmacro-name-last-macro]. +To give a macro a name, so you can call it even after defining other +macros, use \\[kmacro-name-last-macro]. \(fn ARG &optional NO-REPEAT)" t nil) @@ -19400,6 +19402,30 @@ A major mode to edit GNU ld script files ;;;*** +;;;### (autoloads nil "less-css-mode" "textmodes/less-css-mode.el" +;;;;;; (0 0 0 0)) +;;; Generated autoloads from textmodes/less-css-mode.el + +(put 'less-css-compile-at-save 'safe-local-variable 'booleanp) + +(put 'less-css-lessc-options 'safe-local-variable t) + +(put 'less-css-output-directory 'safe-local-variable 'stringp) + +(put 'less-css-input-file-name 'safe-local-variable 'stringp) + (add-to-list 'auto-mode-alist '("\\.less\\'" . less-css-mode)) + +(autoload 'less-css-mode "less-css-mode" "\ +Major mode for editing Less files (http://lesscss.org/). +Special commands: +\\{less-css-mode-map} + +\(fn)" t nil) + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "less-css-mode" '("less-css-"))) + +;;;*** + ;;;### (autoloads nil "let-alist" "emacs-lisp/let-alist.el" (0 0 ;;;;;; 0 0)) ;;; Generated autoloads from emacs-lisp/let-alist.el @@ -19534,7 +19560,7 @@ something strange, such as redefining an Emacs function. \(fn FEATURE &optional FORCE)" t nil) -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "loadhist" '("unload-" "loadhist-hook-functions" "read-feature" "feature-" "file-"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "loadhist" '("loadhist-" "unload-" "read-feature" "feature-" "file-"))) ;;;*** @@ -19776,13 +19802,7 @@ A major mode to edit m4 macro files. ;;;### (autoloads nil "macros" "macros.el" (0 0 0 0)) ;;; Generated autoloads from macros.el -(autoload 'name-last-kbd-macro "macros" "\ -Assign a name to the last keyboard macro defined. -Argument SYMBOL is the name to define. -The symbol's function definition becomes the keyboard macro string. -Such a \"function\" cannot be called from Lisp, but it is a valid editor command. - -\(fn SYMBOL)" t nil) +(defalias 'name-last-kbd-macro #'kmacro-name-last-macro) (autoload 'insert-kbd-macro "macros" "\ Insert in buffer the definition of kbd macro MACRONAME, as Lisp code. @@ -20372,7 +20392,7 @@ Default bookmark handler for Man buffers. ;;;### (autoloads nil "map" "emacs-lisp/map.el" (0 0 0 0)) ;;; Generated autoloads from emacs-lisp/map.el -(push (purecopy '(map 1 1)) package--builtin-versions) +(push (purecopy '(map 1 2)) package--builtin-versions) (if (fboundp 'register-definition-prefixes) (register-definition-prefixes "map" '("map"))) @@ -22722,10 +22742,25 @@ Many aspects this mode can be customized using ;;;*** +;;;### (autoloads nil "ob-J" "org/ob-J.el" (0 0 0 0)) +;;; Generated autoloads from org/ob-J.el + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ob-J" '("obj-" "org-babel-"))) + +;;;*** + ;;;### (autoloads nil "ob-R" "org/ob-R.el" (0 0 0 0)) ;;; Generated autoloads from org/ob-R.el -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ob-R" '("org-babel-"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ob-R" '("ob-R-" "org-babel-"))) + +;;;*** + +;;;### (autoloads nil "ob-abc" "org/ob-abc.el" (0 0 0 0)) +;;; Generated autoloads from org/ob-abc.el +(push (purecopy '(ob-abc 0 1)) package--builtin-versions) + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ob-abc" '("org-babel-"))) ;;;*** @@ -22765,6 +22800,13 @@ Many aspects this mode can be customized using ;;;*** +;;;### (autoloads nil "ob-coq" "org/ob-coq.el" (0 0 0 0)) +;;; Generated autoloads from org/ob-coq.el + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ob-coq" '("org-babel-" "coq-program-name"))) + +;;;*** + ;;;### (autoloads "actual autoloads are elsewhere" "ob-core" "org/ob-core.el" ;;;;;; (0 0 0 0)) ;;; Generated autoloads from org/ob-core.el @@ -22794,6 +22836,14 @@ Many aspects this mode can be customized using ;;;*** +;;;### (autoloads nil "ob-ebnf" "org/ob-ebnf.el" (0 0 0 0)) +;;; Generated autoloads from org/ob-ebnf.el +(push (purecopy '(ob-ebnf 1 0)) package--builtin-versions) + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ob-ebnf" '("org-babel-"))) + +;;;*** + ;;;### (autoloads nil "ob-emacs-lisp" "org/ob-emacs-lisp.el" (0 0 ;;;;;; 0 0)) ;;; Generated autoloads from org/ob-emacs-lisp.el @@ -22816,6 +22866,13 @@ Many aspects this mode can be customized using ;;;*** +;;;### (autoloads nil "ob-forth" "org/ob-forth.el" (0 0 0 0)) +;;; Generated autoloads from org/ob-forth.el + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ob-forth" '("org-babel-"))) + +;;;*** + ;;;### (autoloads nil "ob-fortran" "org/ob-fortran.el" (0 0 0 0)) ;;; Generated autoloads from org/ob-fortran.el @@ -22830,6 +22887,13 @@ Many aspects this mode can be customized using ;;;*** +;;;### (autoloads nil "ob-groovy" "org/ob-groovy.el" (0 0 0 0)) +;;; Generated autoloads from org/ob-groovy.el + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ob-groovy" '("org-babel-"))) + +;;;*** + ;;;### (autoloads nil "ob-haskell" "org/ob-haskell.el" (0 0 0 0)) ;;; Generated autoloads from org/ob-haskell.el @@ -22837,6 +22901,13 @@ Many aspects this mode can be customized using ;;;*** +;;;### (autoloads nil "ob-hledger" "org/ob-hledger.el" (0 0 0 0)) +;;; Generated autoloads from org/ob-hledger.el + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ob-hledger" '("org-babel-"))) + +;;;*** + ;;;### (autoloads nil "ob-io" "org/ob-io.el" (0 0 0 0)) ;;; Generated autoloads from org/ob-io.el @@ -22869,7 +22940,7 @@ Many aspects this mode can be customized using ;;;### (autoloads nil "ob-latex" "org/ob-latex.el" (0 0 0 0)) ;;; Generated autoloads from org/ob-latex.el -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ob-latex" '("org-babel-" "convert-pdf"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ob-latex" '("org-babel-"))) ;;;*** @@ -22902,6 +22973,13 @@ Many aspects this mode can be customized using ;;;*** +;;;### (autoloads nil "ob-lua" "org/ob-lua.el" (0 0 0 0)) +;;; Generated autoloads from org/ob-lua.el + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ob-lua" '("org-babel-"))) + +;;;*** + ;;;### (autoloads nil "ob-makefile" "org/ob-makefile.el" (0 0 0 0)) ;;; Generated autoloads from org/ob-makefile.el @@ -22965,6 +23043,14 @@ Many aspects this mode can be customized using ;;;*** +;;;### (autoloads nil "ob-processing" "org/ob-processing.el" (0 0 +;;;;;; 0 0)) +;;; Generated autoloads from org/ob-processing.el + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ob-processing" '("org-babel-"))) + +;;;*** + ;;;### (autoloads nil "ob-python" "org/ob-python.el" (0 0 0 0)) ;;; Generated autoloads from org/ob-python.el @@ -22993,13 +23079,6 @@ Many aspects this mode can be customized using ;;;*** -;;;### (autoloads nil "ob-scala" "org/ob-scala.el" (0 0 0 0)) -;;; Generated autoloads from org/ob-scala.el - -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ob-scala" '("org-babel-"))) - -;;;*** - ;;;### (autoloads nil "ob-scheme" "org/ob-scheme.el" (0 0 0 0)) ;;; Generated autoloads from org/ob-scheme.el @@ -23014,10 +23093,18 @@ Many aspects this mode can be customized using ;;;*** -;;;### (autoloads nil "ob-sh" "org/ob-sh.el" (0 0 0 0)) -;;; Generated autoloads from org/ob-sh.el +;;;### (autoloads nil "ob-sed" "org/ob-sed.el" (0 0 0 0)) +;;; Generated autoloads from org/ob-sed.el +(push (purecopy '(ob-sed 0 1 0)) package--builtin-versions) -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ob-sh" '("org-babel-"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ob-sed" '("org-babel-"))) + +;;;*** + +;;;### (autoloads nil "ob-shell" "org/ob-shell.el" (0 0 0 0)) +;;; Generated autoloads from org/ob-shell.el + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ob-shell" '("org-babel-"))) ;;;*** @@ -23031,7 +23118,7 @@ Many aspects this mode can be customized using ;;;### (autoloads nil "ob-sql" "org/ob-sql.el" (0 0 0 0)) ;;; Generated autoloads from org/ob-sql.el -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ob-sql" '("org-babel-" "dbstring-mysql"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ob-sql" '("org-babel-"))) ;;;*** @@ -23042,6 +23129,13 @@ Many aspects this mode can be customized using ;;;*** +;;;### (autoloads nil "ob-stan" "org/ob-stan.el" (0 0 0 0)) +;;; Generated autoloads from org/ob-stan.el + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ob-stan" '("org-babel-"))) + +;;;*** + ;;;### (autoloads nil "ob-table" "org/ob-table.el" (0 0 0 0)) ;;; Generated autoloads from org/ob-table.el @@ -23057,6 +23151,13 @@ Many aspects this mode can be customized using ;;;*** +;;;### (autoloads nil "ob-vala" "org/ob-vala.el" (0 0 0 0)) +;;; Generated autoloads from org/ob-vala.el + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ob-vala" '("org-babel-"))) + +;;;*** + ;;;### (autoloads nil "octave" "progmodes/octave.el" (0 0 0 0)) ;;; Generated autoloads from progmodes/octave.el @@ -23149,7 +23250,7 @@ Load the languages defined in `org-babel-load-languages'. \(fn SYM VALUE)" nil nil) (autoload 'org-babel-load-file "org" "\ -Load Emacs Lisp source code blocks in the Org-mode FILE. +Load Emacs Lisp source code blocks in the Org FILE. This function exports the source code using `org-babel-tangle' and then loads the resulting file using `load-file'. With prefix arg (noninteractively: 2nd arg) COMPILE the tangled Emacs Lisp @@ -23158,10 +23259,11 @@ file to byte-code before it is loaded. \(fn FILE &optional COMPILE)" t nil) (autoload 'org-version "org" "\ -Show the org-mode version in the echo area. -With prefix argument HERE, insert it at point. -When FULL is non-nil, use a verbose version string. -When MESSAGE is non-nil, display a message with the version. +Show the Org version. +Interactively, or when MESSAGE is non-nil, show it in echo area. +With prefix argument, or when HERE is non-nil, insert it at point. +In non-interactive uses, a reduced version string is output unless +FULL is given. \(fn &optional HERE FULL MESSAGE)" t nil) @@ -23179,15 +23281,15 @@ Set up hooks for clock persistence. Outline-based notes management and organizer, alias \"Carsten's outline-mode for keeping track of everything.\" -Org-mode develops organizational tasks around a NOTES file which -contains information about projects as plain text. Org-mode is -implemented on top of outline-mode, which is ideal to keep the content +Org mode develops organizational tasks around a NOTES file which +contains information about projects as plain text. Org mode is +implemented on top of Outline mode, which is ideal to keep the content of large files well structured. It supports ToDo items, deadlines and time stamps, which magically appear in the diary listing of the Emacs calendar. Tables are easily created with a built-in table editor. Plain text URL-like links connect to websites, emails (VM), Usenet messages (Gnus), BBDB entries, and any files related to the project. -For printing and sharing of notes, an Org-mode file (or a part of it) +For printing and sharing of notes, an Org file (or a part of it) can be exported as a structured ASCII or HTML file. The following commands are available: @@ -23197,58 +23299,60 @@ The following commands are available: \(fn)" t nil) (autoload 'org-cycle "org" "\ -TAB-action and visibility cycling for Org-mode. +TAB-action and visibility cycling for Org mode. -This is the command invoked in Org-mode by the TAB key. Its main purpose -is outline visibility cycling, but it also invokes other actions +This is the command invoked in Org mode by the `TAB' key. Its main +purpose is outline visibility cycling, but it also invokes other actions in special contexts. -- When this function is called with a prefix argument, rotate the entire - buffer through 3 states (global cycling) +When this function is called with a `\\[universal-argument]' prefix, rotate the entire +buffer through 3 states (global cycling) 1. OVERVIEW: Show only top-level headlines. 2. CONTENTS: Show all headlines of all levels, but no body text. 3. SHOW ALL: Show everything. - When called with two `C-u C-u' prefixes, switch to the startup visibility, - determined by the variable `org-startup-folded', and by any VISIBILITY - properties in the buffer. - When called with three `C-u C-u C-u' prefixed, show the entire buffer, - including any drawers. -- When inside a table, re-align the table and move to the next field. +With a `\\[universal-argument] \\[universal-argument]' prefix argument, switch to the startup visibility, +determined by the variable `org-startup-folded', and by any VISIBILITY +properties in the buffer. + +With a `\\[universal-argument] \\[universal-argument] \\[universal-argument]' prefix argument, show the entire buffer, including +any drawers. -- When point is at the beginning of a headline, rotate the subtree started - by this line through 3 different states (local cycling) +When inside a table, re-align the table and move to the next field. + +When point is at the beginning of a headline, rotate the subtree started +by this line through 3 different states (local cycling) 1. FOLDED: Only the main headline is shown. 2. CHILDREN: The main headline and the direct children are shown. From this state, you can move to one of the children and zoom in further. 3. SUBTREE: Show the entire subtree, including body text. - If there is no subtree, switch directly from CHILDREN to FOLDED. +If there is no subtree, switch directly from CHILDREN to FOLDED. -- When point is at the beginning of an empty headline and the variable - `org-cycle-level-after-item/entry-creation' is set, cycle the level - of the headline by demoting and promoting it to likely levels. This - speeds up creation document structure by pressing TAB once or several - times right after creating a new headline. +When point is at the beginning of an empty headline and the variable +`org-cycle-level-after-item/entry-creation' is set, cycle the level +of the headline by demoting and promoting it to likely levels. This +speeds up creation document structure by pressing `TAB' once or several +times right after creating a new headline. -- When there is a numeric prefix, go up to a heading with level ARG, do - a `show-subtree' and return to the previous cursor position. If ARG - is negative, go up that many levels. +When there is a numeric prefix, go up to a heading with level ARG, do +a `show-subtree' and return to the previous cursor position. If ARG +is negative, go up that many levels. -- When point is not at the beginning of a headline, execute the global - binding for TAB, which is re-indenting the line. See the option - `org-cycle-emulate-tab' for details. +When point is not at the beginning of a headline, execute the global +binding for `TAB', which is re-indenting the line. See the option +`org-cycle-emulate-tab' for details. -- Special case: if point is at the beginning of the buffer and there is - no headline in line 1, this function will act as if called with prefix arg - (C-u TAB, same as S-TAB) also when called without prefix arg. - But only if also the variable `org-cycle-global-at-bob' is t. +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. \(fn &optional ARG)" t nil) (autoload 'org-global-cycle "org" "\ Cycle the global visibility. For details see `org-cycle'. -With \\[universal-argument] prefix arg, switch to startup visibility. +With `\\[universal-argument]' prefix ARG, switch to startup visibility. With a numeric prefix, show all headlines up to that level. \(fn &optional ARG)" t nil) @@ -23256,10 +23360,10 @@ With a numeric prefix, show all headlines up to that level. (autoload 'orgstruct-mode "org" "\ Toggle the minor mode `orgstruct-mode'. -This mode is for using Org-mode structure commands in other -modes. The following keys behave as if Org-mode were active, if +This mode is for using Org mode structure commands in other +modes. The following keys behave as if Org mode were active, if the cursor is on a headline, or on a plain list item (both as -defined by Org-mode). +defined by Org mode). \(fn &optional ARG)" t nil) @@ -23274,61 +23378,59 @@ Unconditionally turn on `orgstruct++-mode'. \(fn)" nil nil) (autoload 'org-run-like-in-org-mode "org" "\ -Run a command, pretending that the current buffer is in Org-mode. +Run a command, pretending that the current buffer is in Org mode. This will temporarily bind local variables that are typically bound in -Org-mode to the values they have in Org-mode, and then interactively +Org mode to the values they have in Org mode, and then interactively call CMD. \(fn CMD)" nil nil) (autoload 'org-store-link "org" "\ -\\<org-mode-map>Store an org-link to the current location. +Store an org-link to the current location. +\\<org-mode-map> This link is added to `org-stored-links' and can later be inserted -into an org-buffer with \\[org-insert-link]. +into an Org buffer with `org-insert-link' (`\\[org-insert-link]'). -For some link types, a prefix arg is interpreted. -For links to Usenet articles, arg negates `org-gnus-prefer-web-links'. -For file links, arg negates `org-context-in-file-links'. +For some link types, a `\\[universal-argument]' prefix ARG is interpreted. A single +`\\[universal-argument]' negates `org-context-in-file-links' for file links or +`org-gnus-prefer-web-links' for links to Usenet articles. -A double prefix arg force skipping storing functions that are not -part of Org's core. +A `\\[universal-argument] \\[universal-argument]' prefix ARG forces skipping storing functions that are not +part of Org core. -A triple prefix arg force storing a link for each line in the +A `\\[universal-argument] \\[universal-argument] \\[universal-argument]' prefix ARG forces storing a link for each line in the active region. \(fn ARG)" t nil) (autoload 'org-insert-link-global "org" "\ -Insert a link like Org-mode does. -This command can be called in any mode to insert a link in Org-mode syntax. +Insert a link like Org mode does. +This command can be called in any mode to insert a link in Org syntax. \(fn)" t nil) (autoload 'org-open-at-point-global "org" "\ -Follow a link like Org-mode does. -This command can be called in any mode to follow a link that has -Org-mode syntax. +Follow a link or time-stamp like Org mode does. +This command can be called in any mode to follow an external link +or a time-stamp that has Org mode syntax. Its behavior is +undefined when called on internal links (e.g., fuzzy links). +Raise an error when there is nothing to follow. \(fn)" t nil) (autoload 'org-open-link-from-string "org" "\ -Open a link in the string S, as if it was in Org-mode. +Open a link in the string S, as if it was in Org mode. \(fn S &optional ARG REFERENCE-BUFFER)" t nil) (autoload 'org-switchb "org" "\ Switch between Org buffers. -With one prefix argument, restrict available buffers to files. -With two prefix arguments, restrict available buffers to agenda files. - -Defaults to `iswitchb' for buffer name completion. -Set `org-completion-use-ido' to make it use ido instead. -\(fn &optional ARG)" t nil) +With `\\[universal-argument]' prefix, restrict available buffers to files. -(defalias 'org-ido-switchb 'org-switchb) +With `\\[universal-argument] \\[universal-argument]' prefix, restrict available buffers to agenda files. -(defalias 'org-iswitchb 'org-switchb) +\(fn &optional ARG)" t nil) (autoload 'org-cycle-agenda-files "org" "\ Cycle through the files in `org-agenda-files'. @@ -23338,13 +23440,13 @@ If the current buffer does not, find the first agenda file. \(fn)" t nil) (autoload 'org-submit-bug-report "org" "\ -Submit a bug report on Org-mode via mail. +Submit a bug report on Org via mail. Don't hesitate to report any problems or inaccurate documentation. If you don't have setup sending mail from (X)Emacs, please copy the output buffer into your mail program, as it gives us important -information about your Org-mode version and configuration. +information about your Org version and configuration. \(fn)" t nil) @@ -23383,7 +23485,6 @@ T Call `org-todo-list' to display the global todo list, select only m Call `org-tags-view' to display headlines with tags matching a condition (the user is prompted for the condition). M Like `m', but select only TODO entries, no ordinary headlines. -L Create a timeline for the current buffer. e Export views to associated files. s Search entries for keywords. S Search entries for keywords, only with TODO keywords. @@ -23400,9 +23501,9 @@ More commands can be added by configuring the variable `org-agenda-custom-commands'. In particular, specific tags and TODO keyword searches can be pre-defined in this way. -If the current buffer is in Org-mode and visiting a file, you can also +If the current buffer is in Org mode and visiting a file, you can also first press `<' once to indicate that the agenda should be temporarily -\(until the next use of \\[org-agenda]) restricted to the current file. +\(until the next use of `\\[org-agenda]') restricted to the current file. Pressing `<' twice means to restrict to the current subtree or region \(if active). @@ -23523,15 +23624,16 @@ as a whole, to include whitespace. with a colon, this will mean that the (non-regexp) snippets of the Boolean search must match as full words. -This command searches the agenda files, and in addition the files listed -in `org-agenda-text-search-extra-files'. +This command searches the agenda files, and in addition the files +listed in `org-agenda-text-search-extra-files' unless a restriction lock +is active. \(fn &optional TODO-ONLY STRING EDIT-AT)" t nil) (autoload 'org-todo-list "org-agenda" "\ Show all (not done) TODO entries from all agenda file in a single list. The prefix arg can be used to select a specific TODO keyword and limit -the list to these. When using \\[universal-argument], you will be prompted +the list to these. When using `\\[universal-argument]', you will be prompted for a keyword. A numeric prefix directly selects the Nth keyword in `org-todo-keywords-1'. @@ -23587,22 +23689,22 @@ Do we have a reason to ignore this TODO entry because it has a time stamp? (autoload 'org-agenda-set-restriction-lock "org-agenda" "\ Set restriction lock for agenda, to current subtree or file. -Restriction will be the file if TYPE is `file', or if TYPE is the -universal prefix `(4)', or if the cursor is before the first headline +Restriction will be the file if TYPE is `file', or if type is the +universal prefix \\='(4), or if the cursor is before the first headline in the file. Otherwise, restriction will be to the current subtree. \(fn &optional TYPE)" t nil) (autoload 'org-calendar-goto-agenda "org-agenda" "\ -Compute the Org-mode agenda for the calendar date displayed at the cursor. +Compute the Org agenda for the calendar date displayed at the cursor. This is a command that has to be installed in `calendar-mode-map'. \(fn)" t nil) (autoload 'org-agenda-to-appt "org-agenda" "\ Activate appointments found in `org-agenda-files'. -With a \\[universal-argument] prefix, refresh the list of -appointments. + +With a `\\[universal-argument]' prefix, refresh the list of appointments. If FILTER is t, interactively prompt the user for a regular expression, and filter out entries that don't match it. @@ -23617,8 +23719,8 @@ argument: an entry from `org-agenda-get-day-entries'. FILTER can also be an alist with the car of each cell being either `headline' or `category'. For example: - ((headline \"IMPORTANT\") - (category \"Work\")) + \\='((headline \"IMPORTANT\") + (category \"Work\")) will only add headlines containing IMPORTANT or headlines belonging to the \"Work\" category. @@ -23680,19 +23782,23 @@ Capture STRING with the template selected by KEYS. (autoload 'org-capture "org-capture" "\ Capture something. \\<org-capture-mode-map> -This will let you select a template from `org-capture-templates', and then -file the newly captured information. The text is immediately inserted -at the target location, and an indirect buffer is shown where you can -edit it. Pressing \\[org-capture-finalize] brings you back to the previous state -of Emacs, so that you can continue your work. +This will let you select a template from `org-capture-templates', and +then file the newly captured information. The text is immediately +inserted at the target location, and an indirect buffer is shown where +you can edit it. Pressing `\\[org-capture-finalize]' brings you back to the previous +state of Emacs, so that you can continue your work. + +When called interactively with a `\\[universal-argument]' prefix argument GOTO, don't +capture anything, just go to the file/headline where the selected +template stores its notes. -When called interactively with a \\[universal-argument] prefix argument GOTO, don't capture -anything, just go to the file/headline where the selected template -stores its notes. With a double prefix argument \\[universal-argument] \\[universal-argument], go to the last note -stored. +With a `\\[universal-argument] \\[universal-argument]' prefix argument, go to the last note stored. When called with a `C-0' (zero) prefix, insert a template at point. +When called with a `C-1' (one) prefix, force prompting for a date when +a datetree entry is made. + ELisp programs can set KEYS to a string associated with a template in `org-capture-templates'. In this case, interactive selection will be bypassed. @@ -23735,26 +23841,29 @@ Remove all currently active column overlays. \(fn)" nil nil) (autoload 'org-columns "org-colview" "\ -Turn on column view on an org-mode file. +Turn on column view on an Org mode file. + +Column view applies to the whole buffer if point is before the +first headline. Otherwise, it applies to the first ancestor +setting \"COLUMNS\" property. If there is none, it defaults to +the current headline. With a `\\[universal-argument]' prefix argument, turn on column +view for the whole buffer unconditionally. + When COLUMNS-FMT-STRING is non-nil, use it as the column format. -\(fn &optional COLUMNS-FMT-STRING)" t nil) +\(fn &optional GLOBAL COLUMNS-FMT-STRING)" t nil) (autoload 'org-columns-compute "org-colview" "\ -Sum the values of property PROPERTY hierarchically, for the entire buffer. +Summarize the values of PROPERTY hierarchically. +Also update existing values for PROPERTY according to the first +column specification. \(fn PROPERTY)" t nil) -(autoload 'org-columns-number-to-string "org-colview" "\ -Convert a computed column number to a string value, according to FMT. - -\(fn N FMT &optional PRINTF)" nil nil) - (autoload 'org-dblock-write:columnview "org-colview" "\ Write the column view table. PARAMS is a property list of parameters: -:width enforce same column widths with <N> specifiers. :id the :ID: property of the entry where the columns view should be built. When the symbol `local', call locally. When `global' call column view with the cursor at the beginning @@ -23764,15 +23873,17 @@ PARAMS is a property list of parameters: using `org-id-find'. :hlines When t, insert a hline before each item. When a number, insert a hline before each level <= that number. +:indent When non-nil, indent each ITEM field according to its level. :vlines When t, make each column a colgroup to enforce vertical lines. :maxlevel When set to a number, don't capture headlines below this level. :skip-empty-rows When t, skip rows where all specifiers other than ITEM are empty. +:width apply widths specified in columns format using <N> specifiers. :format When non-nil, specify the column view format to use. \(fn PARAMS)" nil nil) -(autoload 'org-insert-columns-dblock "org-colview" "\ +(autoload 'org-columns-insert-dblock "org-colview" "\ Create a dynamic block capturing a column view table. \(fn)" t nil) @@ -23808,7 +23919,7 @@ Try very hard to provide sensible version strings. ;;;### (autoloads nil "org-ctags" "org/org-ctags.el" (0 0 0 0)) ;;; Generated autoloads from org/org-ctags.el -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "org-ctags" '("org-ctags-" "y-or-n-minibuffer"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "org-ctags" '("org-ctags-"))) ;;;*** @@ -23827,6 +23938,63 @@ Try very hard to provide sensible version strings. ;;;*** +;;;### (autoloads nil "org-duration" "org/org-duration.el" (0 0 0 +;;;;;; 0)) +;;; Generated autoloads from org/org-duration.el + +(autoload 'org-duration-set-regexps "org-duration" "\ +Set duration related regexps. + +\(fn)" t nil) + +(autoload 'org-duration-p "org-duration" "\ +Non-nil when string S is a time duration. + +\(fn S)" nil nil) + +(autoload 'org-duration-to-minutes "org-duration" "\ +Return number of minutes of DURATION string. + +When optional argument CANONICAL is non-nil, ignore +`org-duration-units' and use standard time units value. + +A bare number is translated into minutes. The empty string is +translated into 0.0. + +Return value as a float. Raise an error if duration format is +not recognized. + +\(fn DURATION &optional CANONICAL)" nil nil) + +(autoload 'org-duration-from-minutes "org-duration" "\ +Return duration string for a given number of MINUTES. + +Format duration according to `org-duration-format' or FMT, when +non-nil. + +When optional argument CANONICAL is non-nil, ignore +`org-duration-units' and use standard time units value. + +Raise an error if expected format is unknown. + +\(fn MINUTES &optional FMT CANONICAL)" nil nil) + +(autoload 'org-duration-h:mm-only-p "org-duration" "\ +Non-nil when every duration in TIMES has \"H:MM\" or \"H:MM:SS\" format. + +TIMES is a list of duration strings. + +Return nil if any duration is expressed with units, as defined in +`org-duration-units'. Otherwise, if any duration is expressed +with \"H:MM:SS\" format, return `h:mm:ss'. Otherwise, return +`h:mm'. + +\(fn TIMES)" nil nil) + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "org-duration" '("org-duration-"))) + +;;;*** + ;;;### (autoloads "actual autoloads are elsewhere" "org-element" ;;;;;; "org/org-element.el" (0 0 0 0)) ;;; Generated autoloads from org/org-element.el @@ -23839,7 +24007,7 @@ Try very hard to provide sensible version strings. ;;;;;; 0)) ;;; Generated autoloads from org/org-entities.el -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "org-entities" '("replace-amp" "org-entit"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "org-entities" '("org-entit"))) ;;;*** @@ -23850,6 +24018,13 @@ Try very hard to provide sensible version strings. ;;;*** +;;;### (autoloads nil "org-eww" "org/org-eww.el" (0 0 0 0)) +;;; Generated autoloads from org/org-eww.el + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "org-eww" '("org-eww-"))) + +;;;*** + ;;;### (autoloads nil "org-faces" "org/org-faces.el" (0 0 0 0)) ;;; Generated autoloads from org/org-faces.el @@ -23926,6 +24101,24 @@ Try very hard to provide sensible version strings. ;;;*** +;;;### (autoloads nil "org-lint" "org/org-lint.el" (0 0 0 0)) +;;; Generated autoloads from org/org-lint.el + +(autoload 'org-lint "org-lint" "\ +Check current Org buffer for syntax mistakes. + +By default, run all checkers. With a `\\[universal-argument]' prefix ARG, select one +category of checkers only. With a `\\[universal-argument] \\[universal-argument]' prefix, run one precise +checker by its name. + +ARG can also be a list of checker names, as symbols, to run. + +\(fn &optional ARG)" t nil) + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "org-lint" '("org-lint-"))) + +;;;*** + ;;;### (autoloads nil "org-list" "org/org-list.el" (0 0 0 0)) ;;; Generated autoloads from org/org-list.el @@ -23944,7 +24137,7 @@ Try very hard to provide sensible version strings. ;;; Generated autoloads from org/org-macs.el (autoload 'org-load-noerror-mustsuffix "org-macs" "\ -Load FILE with optional arguments NOERROR and MUSTSUFFIX. Drop the MUSTSUFFIX argument for XEmacs, which doesn't recognize it. +Load FILE with optional arguments NOERROR and MUSTSUFFIX. \(fn FILE)" nil t) @@ -24016,7 +24209,7 @@ Load FILE with optional arguments NOERROR and MUSTSUFFIX. Drop the MUSTSUFFIX a ;;;;;; (0 0 0 0)) ;;; Generated autoloads from org/org-table.el -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "org-table" '("org" "*orgtbl-"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "org-table" '("org"))) ;;;*** @@ -24032,14 +24225,14 @@ Load FILE with optional arguments NOERROR and MUSTSUFFIX. Drop the MUSTSUFFIX a ;;; Generated autoloads from org/org-version.el (autoload 'org-release "org-version" "\ -The release version of org-mode. - Inserted by installing org-mode or when a release is made. +The release version of Org. +Inserted by installing Org mode or when a release is made. \(fn)" nil nil) (autoload 'org-git-version "org-version" "\ The Git version of org-mode. - Inserted by installing org-mode or when a release is made. +Inserted by installing Org or when a release is made. \(fn)" nil nil) @@ -24303,7 +24496,9 @@ Display the full documentation of PACKAGE (a symbol). Display a list of packages. This first fetches the updated list of packages before displaying, unless a prefix argument NO-FETCH is specified. -The list is displayed in a buffer named `*Packages*'. +The list is displayed in a buffer named `*Packages*', and +includes the package's version, availability status, and a +short description. \(fn &optional NO-FETCH)" t nil) @@ -24914,6 +25109,14 @@ Global menu used by PCL-CVS.") (put 'perl-brace-imaginary-offset 'safe-local-variable 'integerp) (put 'perl-label-offset 'safe-local-variable 'integerp) +(autoload 'perl-flymake "perl-mode" "\ +Perl backend for Flymake. Launches +`perl-flymake-command' (which see) and passes to its standard +input the contents of the current buffer. The output of this +command is analyzed for error and warning messages. + +\(fn REPORT-FN &rest ARGS)" nil nil) + (autoload 'perl-mode "perl-mode" "\ Major mode for editing Perl code. Expression and list commands understand all Perl brackets. @@ -25050,25 +25253,6 @@ they are not by default assigned to keys. ;;;*** -;;;### (autoloads nil "pinentry" "net/pinentry.el" (0 0 0 0)) -;;; Generated autoloads from net/pinentry.el -(push (purecopy '(pinentry 0 1)) package--builtin-versions) - -(autoload 'pinentry-start "pinentry" "\ -Start a Pinentry service. - -Once the environment is properly set, subsequent invocations of -the gpg command will interact with Emacs for passphrase input. - -If the optional QUIET argument is non-nil, messages at startup -will not be shown. - -\(fn &optional QUIET)" t nil) - -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "pinentry" '("pinentry-"))) - -;;;*** - ;;;### (autoloads nil "pixel-scroll" "pixel-scroll.el" (0 0 0 0)) ;;; Generated autoloads from pixel-scroll.el @@ -25862,7 +26046,11 @@ is not a part of a detectable project either, return a (autoload 'project-find-regexp "project" "\ Find all matches for REGEXP in the current project's roots. With \\[universal-argument] prefix, you can specify the directory -to search in, and the file name pattern to search for. +to search in, and the file name pattern to search for. The +pattern may use abbreviations defined in `grep-files-aliases', +e.g. entering `ch' is equivalent to `*.[ch]'. As whitespace +triggers completion when entering a pattern, including it +requires quoting, e.g. `\\[quoted-insert]<space>'. \(fn REGEXP)" t nil) @@ -26232,7 +26420,7 @@ Optional argument FACE specifies the face to do the highlighting. ;;; Generated autoloads from progmodes/python.el (push (purecopy '(python 0 25 2)) package--builtin-versions) -(add-to-list 'auto-mode-alist (cons (purecopy "\\.pyw?\\'") 'python-mode)) +(add-to-list 'auto-mode-alist (cons (purecopy "\\.py[iw]?\\'") 'python-mode)) (add-to-list 'interpreter-mode-alist (cons (purecopy "python[0-9.]*") 'python-mode)) @@ -26896,6 +27084,10 @@ With a prefix (or a FILL) argument, also fill too short lines. Replace rectangle contents with STRING on each line. The length of STRING need not be the same as the rectangle width. +When called interactively and option `rectangle-preview' is +non-nil, display the result as the user enters the string into +the minibuffer. + Called from a program, takes three args; START, END and STRING. \(fn START END STRING)" t nil) @@ -27795,6 +27987,46 @@ than appending to it. Deletes the message after writing if ;;;*** +;;;### (autoloads nil "rmc" "emacs-lisp/rmc.el" (0 0 0 0)) +;;; Generated autoloads from emacs-lisp/rmc.el + +(autoload 'read-multiple-choice "rmc" "\ +Ask user a multiple choice question. +PROMPT should be a string that will be displayed as the prompt. + +CHOICES is an alist where the first element in each entry is a +character to be entered, the second element is a short name for +the entry to be displayed while prompting (if there's room, it +might be shortened), and the third, optional entry is a longer +explanation that will be displayed in a help buffer if the user +requests more help. + +This function translates user input into responses by consulting +the bindings in `query-replace-map'; see the documentation of +that variable for more information. In this case, the useful +bindings are `recenter', `scroll-up', and `scroll-down'. If the +user enters `recenter', `scroll-up', or `scroll-down' responses, +perform the requested window recentering or scrolling and ask +again. + +When `use-dialog-box' is t (the default), this function can pop +up a dialog window to collect the user input. That functionality +requires `display-popup-menus-p' to return t. Otherwise, a text +dialog will be used. + +The return value is the matching entry from the CHOICES list. + +Usage example: + +\(read-multiple-choice \"Continue connecting?\" + \\='((?a \"always\") + (?s \"session only\") + (?n \"no\"))) + +\(fn PROMPT CHOICES)" nil nil) + +;;;*** + ;;;### (autoloads nil "rng-cmpct" "nxml/rng-cmpct.el" (0 0 0 0)) ;;; Generated autoloads from nxml/rng-cmpct.el @@ -30399,7 +30631,7 @@ then `snmpv2-mode-hook'. ;;;### (autoloads nil "soap-client" "net/soap-client.el" (0 0 0 0)) ;;; Generated autoloads from net/soap-client.el -(push (purecopy '(soap-client 3 1 2)) package--builtin-versions) +(push (purecopy '(soap-client 3 1 3)) package--builtin-versions) (if (fboundp 'register-definition-prefixes) (register-definition-prefixes "soap-client" '("soap-"))) @@ -30616,7 +30848,7 @@ the sort order. \(fn FIELD BEG END)" t nil) (autoload 'sort-regexp-fields "sort" "\ -Sort the text in the region region lexicographically. +Sort the text in the region lexicographically. If called interactively, prompt for two regular expressions, RECORD-REGEXP and KEY-REGEXP. @@ -30818,7 +31050,7 @@ Return a vector containing the lines from `spook-phrases-file'. ;;;### (autoloads nil "sql" "progmodes/sql.el" (0 0 0 0)) ;;; Generated autoloads from progmodes/sql.el -(push (purecopy '(sql 3 5)) package--builtin-versions) +(push (purecopy '(sql 3 6)) package--builtin-versions) (autoload 'sql-add-product-keywords "sql" "\ Add highlighting KEYWORDS for SQL PRODUCT. @@ -30880,7 +31112,7 @@ their settings. The user will not be prompted for any login parameters if a value is specified in the connection settings. -\(fn CONNECTION &optional NEW-NAME)" t nil) +\(fn CONNECTION &optional BUF-NAME)" t nil) (autoload 'sql-product-interactive "sql" "\ Run PRODUCT interpreter as an inferior process. @@ -31639,7 +31871,7 @@ Studlify-case the current buffer. ;;;### (autoloads nil "subr-x" "emacs-lisp/subr-x.el" (0 0 0 0)) ;;; Generated autoloads from emacs-lisp/subr-x.el -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "subr-x" '("read-multiple-choice" "string-" "hash-table-" "and-let*" "when-let" "internal--" "if-let" "thread-"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "subr-x" '("string-" "hash-table-" "when-let" "internal--" "if-let" "and-let*" "thread-"))) ;;;*** @@ -32617,10 +32849,8 @@ use in that buffer. ;;; Generated autoloads from emacs-lisp/testcover.el (autoload 'testcover-start "testcover" "\ -Uses edebug to instrument all macros and functions in FILENAME, then -changes the instrumentation from edebug to testcover--much faster, no -problems with type-ahead or post-command-hook, etc. If BYTE-COMPILE is -non-nil, byte-compiles each function after instrumenting. +Use Edebug to instrument for coverage all macros and functions in FILENAME. +If BYTE-COMPILE is non-nil, byte compile each function after instrumenting. \(fn FILENAME &optional BYTE-COMPILE)" t nil) @@ -33198,7 +33428,7 @@ Return the Lisp list at point, or nil if none is found. \(fn)" nil nil) -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "thingatpt" '("form-at-point" "thing-at-point-" "sentence-at-point" "word-at-point" "in-string-p" "end-of-thing" "beginning-of-thing"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "thingatpt" '("filename" "form-at-point" "thing-at-point-" "sentence-at-point" "word-at-point" "define-thing-chars" "in-string-p" "end-of-thing" "beginning-of-thing"))) ;;;*** @@ -33451,7 +33681,7 @@ Return a string giving the duration of the Emacs initialization. \(fn)" t nil) -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "time" '("display-time-" "legacy-style-world-list" "zoneinfo-style-world-list"))) +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "time" '("display-time-" "time--display-world-list" "legacy-style-world-list" "zoneinfo-style-world-list"))) ;;;*** @@ -33714,11 +33944,11 @@ relative only to the time worked today, and not to past time. ;;;;;; 0 0 0)) ;;; Generated autoloads from emacs-lisp/timer-list.el -(autoload 'timer-list "timer-list" "\ +(autoload 'list-timers "timer-list" "\ List all timers in a buffer. \(fn &optional IGNORE-AUTO NONCONFIRM)" t nil) - (put 'timer-list 'disabled "Beware: manually canceling timers can ruin your Emacs session.") + (put 'list-timers 'disabled "Beware: manually canceling timers can ruin your Emacs session.") (if (fboundp 'register-definition-prefixes) (register-definition-prefixes "timer-list" '("timer-list-"))) @@ -34025,62 +34255,31 @@ If it is set to nil, all remote file names are used literally.") (custom-autoload 'tramp-mode "tramp" t) -(defvar tramp-syntax 'default "\ -Tramp filename syntax to be used. - -It can have the following values: - - `default' -- Default syntax - `simplified' -- Ange-FTP like syntax - `separate' -- Syntax as defined for XEmacs originally - -Do not change the value by `setq', it must be changed only by -`custom-set-variables'. See also `tramp-change-syntax'.") - -(custom-autoload 'tramp-syntax "tramp" nil) - (defconst tramp-initial-file-name-regexp "\\`/.+:.*:" "\ Value for `tramp-file-name-regexp' for autoload. It must match the initial `tramp-syntax' settings.") (defvar tramp-file-name-regexp tramp-initial-file-name-regexp "\ -Value for `tramp-file-name-regexp' for autoload. -It must match the initial `tramp-syntax' settings.") - -(defconst tramp-completion-file-name-regexp-default (concat "\\`/\\(" "\\([^/|:]+:[^/|:]*|\\)*" (if (memq system-type '(cygwin windows-nt)) "\\(-\\|[^/|:]\\{2,\\}\\)" "[^/|:]+") "\\(:[^/|:]*\\)?" "\\)?\\'") "\ -Value for `tramp-completion-file-name-regexp' for default remoting. -See `tramp-file-name-structure' for more explanations. - -On W32 systems, the volume letter must be ignored.") - -(defconst tramp-initial-completion-file-name-regexp tramp-completion-file-name-regexp-default "\ -Value for `tramp-completion-file-name-regexp' for autoload. -It must match the initial `tramp-syntax' settings.") - -(defconst tramp-completion-file-name-handler-alist '((file-name-all-completions . tramp-completion-handle-file-name-all-completions) (file-name-completion . tramp-completion-handle-file-name-completion)) "\ -Alist of completion handler functions. -Used for file names matching `tramp-completion-file-name-regexp'. -Operations not mentioned here will be handled by Tramp's file -name handler functions, or the normal Emacs functions.") +Regular expression matching file names handled by Tramp. +This regexp should match Tramp file names but no other file +names. When calling `tramp-register-file-name-handlers', the +initial value is overwritten by the car of `tramp-file-name-structure'.") -(autoload 'tramp-completion-file-name-handler "tramp" "\ -Invoke Tramp file name completion handler. -Falls back to normal file name handler if no Tramp file name handler exists. - -\(fn OPERATION &rest ARGS)" nil nil) +(defconst tramp-autoload-file-name-regexp (concat "\\`/" (if (memq system-type '(cygwin windows-nt)) "\\(-\\|[^/|:]\\{2,\\}\\)" "[^/|:]+") ":") "\ +Regular expression matching file names handled by Tramp autoload. +It must match the initial `tramp-syntax' settings. It should not +match file names at root of the underlying local file system, +like \"/sys\" or \"/C:\".") (defun tramp-autoload-file-name-handler (operation &rest args) "\ -Load Tramp file name handler, and perform OPERATION." (let ((default-directory temporary-file-directory)) (load "tramp" (quote noerror) (quote nomessage))) (apply operation args)) +Load Tramp file name handler, and perform OPERATION." (if tramp-mode (let ((default-directory temporary-file-directory)) (load "tramp" (quote noerror) (quote nomessage))) (tramp-unload-file-name-handlers)) (apply operation args)) (defun tramp-register-autoload-file-name-handlers nil "\ -Add Tramp file name handlers to `file-name-handler-alist' during autoload." (add-to-list (quote file-name-handler-alist) (cons tramp-initial-file-name-regexp (quote tramp-autoload-file-name-handler))) (put (quote tramp-autoload-file-name-handler) (quote safe-magic) t) (add-to-list (quote file-name-handler-alist) (cons tramp-initial-completion-file-name-regexp (quote tramp-completion-file-name-handler))) (put (quote tramp-completion-file-name-handler) (quote safe-magic) t) (put (quote tramp-completion-file-name-handler) (quote operations) (mapcar (quote car) tramp-completion-file-name-handler-alist))) - -(tramp-register-autoload-file-name-handlers) +Add Tramp file name handlers to `file-name-handler-alist' during autoload." (add-to-list (quote file-name-handler-alist) (cons tramp-autoload-file-name-regexp (quote tramp-autoload-file-name-handler))) (put (quote tramp-autoload-file-name-handler) (quote safe-magic) t)) + (tramp-register-autoload-file-name-handlers) -(autoload 'tramp-unload-file-name-handlers "tramp" "\ -Unload Tramp file name handlers from `file-name-handler-alist'. - -\(fn)" nil nil) +(defun tramp-unload-file-name-handlers nil "\ +Unload Tramp file name handlers from `file-name-handler-alist'." (dolist (fnh (quote (tramp-file-name-handler tramp-completion-file-name-handler tramp-autoload-file-name-handler))) (let ((a1 (rassq fnh file-name-handler-alist))) (setq file-name-handler-alist (delq a1 file-name-handler-alist))))) (defvar tramp-completion-mode nil "\ If non-nil, external packages signal that they are in file name completion.") @@ -34165,7 +34364,7 @@ Reenable Ange-FTP, when Tramp is unloaded. ;;;### (autoloads nil "trampver" "net/trampver.el" (0 0 0 0)) ;;; Generated autoloads from net/trampver.el -(push (purecopy '(tramp 2 3 2 -1)) package--builtin-versions) +(push (purecopy '(tramp 2 3 3 -1)) package--builtin-versions) (if (fboundp 'register-definition-prefixes) (register-definition-prefixes "trampver" '("tramp-"))) @@ -35634,7 +35833,7 @@ When called interactively with a prefix argument, prompt for LIMIT. \(fn &optional LIMIT)" t nil) (autoload 'vc-print-branch-log "vc" "\ - +Show the change log for BRANCH in a window. \(fn BRANCH)" t nil) @@ -38230,17 +38429,26 @@ Zone out, completely. ;;;;;; "emacs-lisp/eieio-opt.el" "emacs-lisp/eldoc.el" "emacs-lisp/float-sup.el" ;;;;;; "emacs-lisp/lisp-mode.el" "emacs-lisp/lisp.el" "emacs-lisp/macroexp.el" ;;;;;; "emacs-lisp/map-ynp.el" "emacs-lisp/nadvice.el" "emacs-lisp/syntax.el" -;;;;;; "emacs-lisp/timer.el" "env.el" "epa-hook.el" "eshell/em-alias.el" -;;;;;; "eshell/em-banner.el" "eshell/em-basic.el" "eshell/em-cmpl.el" -;;;;;; "eshell/em-dirs.el" "eshell/em-glob.el" "eshell/em-hist.el" -;;;;;; "eshell/em-ls.el" "eshell/em-pred.el" "eshell/em-prompt.el" -;;;;;; "eshell/em-rebind.el" "eshell/em-script.el" "eshell/em-smart.el" -;;;;;; "eshell/em-term.el" "eshell/em-tramp.el" "eshell/em-unix.el" -;;;;;; "eshell/em-xtra.el" "facemenu.el" "faces.el" "files.el" "font-core.el" -;;;;;; "font-lock.el" "format.el" "frame.el" "help.el" "hfy-cmap.el" -;;;;;; "ibuf-ext.el" "indent.el" "international/characters.el" "international/charprop.el" -;;;;;; "international/charscript.el" "international/cp51932.el" -;;;;;; "international/eucjp-ms.el" "international/mule-cmds.el" +;;;;;; "emacs-lisp/timer.el" "env.el" "epa-hook.el" "erc/erc-autoaway.el" +;;;;;; "erc/erc-button.el" "erc/erc-capab.el" "erc/erc-compat.el" +;;;;;; "erc/erc-dcc.el" "erc/erc-desktop-notifications.el" "erc/erc-ezbounce.el" +;;;;;; "erc/erc-fill.el" "erc/erc-identd.el" "erc/erc-imenu.el" +;;;;;; "erc/erc-join.el" "erc/erc-list.el" "erc/erc-log.el" "erc/erc-match.el" +;;;;;; "erc/erc-menu.el" "erc/erc-netsplit.el" "erc/erc-notify.el" +;;;;;; "erc/erc-page.el" "erc/erc-pcomplete.el" "erc/erc-replace.el" +;;;;;; "erc/erc-ring.el" "erc/erc-services.el" "erc/erc-sound.el" +;;;;;; "erc/erc-speedbar.el" "erc/erc-spelling.el" "erc/erc-stamp.el" +;;;;;; "erc/erc-track.el" "erc/erc-truncate.el" "erc/erc-xdcc.el" +;;;;;; "eshell/em-alias.el" "eshell/em-banner.el" "eshell/em-basic.el" +;;;;;; "eshell/em-cmpl.el" "eshell/em-dirs.el" "eshell/em-glob.el" +;;;;;; "eshell/em-hist.el" "eshell/em-ls.el" "eshell/em-pred.el" +;;;;;; "eshell/em-prompt.el" "eshell/em-rebind.el" "eshell/em-script.el" +;;;;;; "eshell/em-smart.el" "eshell/em-term.el" "eshell/em-tramp.el" +;;;;;; "eshell/em-unix.el" "eshell/em-xtra.el" "facemenu.el" "faces.el" +;;;;;; "files.el" "font-core.el" "font-lock.el" "format.el" "frame.el" +;;;;;; "help.el" "hfy-cmap.el" "ibuf-ext.el" "indent.el" "international/characters.el" +;;;;;; "international/charprop.el" "international/charscript.el" +;;;;;; "international/cp51932.el" "international/eucjp-ms.el" "international/mule-cmds.el" ;;;;;; "international/mule-conf.el" "international/mule.el" "international/uni-bidi.el" ;;;;;; "international/uni-brackets.el" "international/uni-category.el" ;;;;;; "international/uni-combining.el" "international/uni-comment.el" diff --git a/lisp/leim/quail/arabic.el b/lisp/leim/quail/arabic.el index 22d61172177..b027d4019fe 100644 --- a/lisp/leim/quail/arabic.el +++ b/lisp/leim/quail/arabic.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/leim/quail/croatian.el b/lisp/leim/quail/croatian.el index 7ebf8758aa6..a9b1ca85165 100644 --- a/lisp/leim/quail/croatian.el +++ b/lisp/leim/quail/croatian.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/leim/quail/cyril-jis.el b/lisp/leim/quail/cyril-jis.el index 609b6015686..210fe97f5a3 100644 --- a/lisp/leim/quail/cyril-jis.el +++ b/lisp/leim/quail/cyril-jis.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/leim/quail/cyrillic.el b/lisp/leim/quail/cyrillic.el index 600193ddc18..2218095f880 100644 --- a/lisp/leim/quail/cyrillic.el +++ b/lisp/leim/quail/cyrillic.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -831,6 +831,120 @@ Sorry, but `ghe with upturn' is not included in ISO 8859-5." ("|" ?Ґ)) ;; +(quail-define-package + "uzbek-cyrillic" "Ўзбекча" "Ўзб" nil + "ЙЦУКЕН Uzbek computer layout" + nil t t t t nil nil nil nil nil t) + +;; Ё 1! 2" 3№ 4; 5% 6: 7? 8* 9( 0) Ғ Ҳ +;; Й Ц У К Е Н Г Ш Ў З Х Ъ \| +;; Ф Қ В А П Р О Л Д Ж Э +;; Я Ч С М И Т Ь Б Ю ., + +(quail-define-rules + ("`" ?ё) + ("1" ?1) + ("2" ?2) + ("3" ?3) + ("4" ?4) + ("5" ?5) + ("6" ?6) + ("7" ?7) + ("8" ?8) + ("9" ?9) + ("0" ?0) + ("-" ?ғ) + ("=" ?ҳ) + + ("q" ?й) + ("w" ?ц) + ("e" ?у) + ("r" ?к) + ("t" ?е) + ("y" ?н) + ("u" ?г) + ("i" ?ш) + ("o" ?ў) + ("p" ?з) + ("[" ?х) + ("]" ?ъ) + ("\\" ?\\) + + ("a" ?ф) + ("s" ?қ) + ("d" ?в) + ("f" ?а) + ("g" ?п) + ("h" ?р) + ("j" ?о) + ("k" ?л) + ("l" ?д) + (";" ?ж) + ("'" ?э) + + ("z" ?я) + ("x" ?ч) + ("c" ?с) + ("v" ?м) + ("b" ?и) + ("n" ?т) + ("m" ?ь) + ("," ?б) + ("." ?ю) + ("/" ?.) + + ("~" ?Ё) + ("!" ?!) + ("@" ?\") + ("#" ?№) + ("$" ?\;) + ("%" ?%) + ("^" ?:) + ("&" ??) + ("*" ?*) + ("(" ?\() + (")" ?\)) + ("_" ?Ғ) + ("+" ?Ҳ) + + ("Q" ?Й) + ("W" ?Ц) + ("E" ?У) + ("R" ?К) + ("T" ?Е) + ("Y" ?Н) + ("U" ?Г) + ("I" ?Ш) + ("O" ?Ў) + ("P" ?З) + ("{" ?Х) + ("}" ?Ъ) + ("|" ?|) + + ("A" ?Ф) + ("S" ?Қ) + ("D" ?В) + ("F" ?А) + ("G" ?П) + ("H" ?Р) + ("J" ?О) + ("K" ?Л) + ("L" ?Д) + (":" ?Ж) + ("\"" ?Э) + + ("Z" ?Я) + ("X" ?Ч) + ("C" ?С) + ("V" ?М) + ("B" ?И) + ("N" ?Т) + ("M" ?Ь) + ("<" ?Б) + (">" ?Ю) + ("?" ?,)) + + ;; Alexander Mikhailian says this is of limited use. It has been ;; popular among emigrants or foreigners who have to type in Cyrillic ;; (mostly Russian) from time to time. diff --git a/lisp/leim/quail/czech.el b/lisp/leim/quail/czech.el index 762d702f435..26f30f36ec6 100644 --- a/lisp/leim/quail/czech.el +++ b/lisp/leim/quail/czech.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/leim/quail/ethiopic.el b/lisp/leim/quail/ethiopic.el index eaf3a03bafa..8d19a233709 100644 --- a/lisp/leim/quail/ethiopic.el +++ b/lisp/leim/quail/ethiopic.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; Author: TAKAHASHI Naoto <ntakahas@etl.go.jp> diff --git a/lisp/leim/quail/georgian.el b/lisp/leim/quail/georgian.el index df297156592..bc3b5d2f6d1 100644 --- a/lisp/leim/quail/georgian.el +++ b/lisp/leim/quail/georgian.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/leim/quail/greek.el b/lisp/leim/quail/greek.el index 05351e0e556..d1414abddcd 100644 --- a/lisp/leim/quail/greek.el +++ b/lisp/leim/quail/greek.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/leim/quail/hangul.el b/lisp/leim/quail/hangul.el index 782d8d50a72..5d509c96e8a 100644 --- a/lisp/leim/quail/hangul.el +++ b/lisp/leim/quail/hangul.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/leim/quail/hanja-jis.el b/lisp/leim/quail/hanja-jis.el index 2c7eebb8345..79730b816ef 100644 --- a/lisp/leim/quail/hanja-jis.el +++ b/lisp/leim/quail/hanja-jis.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/leim/quail/hanja.el b/lisp/leim/quail/hanja.el index 8c00ad1bbf7..9c659e224ea 100644 --- a/lisp/leim/quail/hanja.el +++ b/lisp/leim/quail/hanja.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/leim/quail/hanja3.el b/lisp/leim/quail/hanja3.el index c140f902235..0b58f6762df 100644 --- a/lisp/leim/quail/hanja3.el +++ b/lisp/leim/quail/hanja3.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/leim/quail/hebrew.el b/lisp/leim/quail/hebrew.el index d90b362407b..772da70b5ce 100644 --- a/lisp/leim/quail/hebrew.el +++ b/lisp/leim/quail/hebrew.el @@ -23,7 +23,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/leim/quail/indian.el b/lisp/leim/quail/indian.el index 8f549ae226b..c1a9b2e4f84 100644 --- a/lisp/leim/quail/indian.el +++ b/lisp/leim/quail/indian.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/leim/quail/ipa-praat.el b/lisp/leim/quail/ipa-praat.el index 42bc2010cb9..2c1c8df5f0d 100644 --- a/lisp/leim/quail/ipa-praat.el +++ b/lisp/leim/quail/ipa-praat.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/leim/quail/ipa.el b/lisp/leim/quail/ipa.el index 6f0368c9811..e513c5f0552 100644 --- a/lisp/leim/quail/ipa.el +++ b/lisp/leim/quail/ipa.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/leim/quail/japanese.el b/lisp/leim/quail/japanese.el index 98865aceb74..2d39d5e2fd9 100644 --- a/lisp/leim/quail/japanese.el +++ b/lisp/leim/quail/japanese.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/leim/quail/lao.el b/lisp/leim/quail/lao.el index 14cf9268287..af3b5892629 100644 --- a/lisp/leim/quail/lao.el +++ b/lisp/leim/quail/lao.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/leim/quail/latin-alt.el b/lisp/leim/quail/latin-alt.el index 6c0dab28b41..a6a5ac84592 100644 --- a/lisp/leim/quail/latin-alt.el +++ b/lisp/leim/quail/latin-alt.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; Author (of latin-post.el): TAKAHASHI Naoto <ntakahas@etl.go.jp> @@ -1152,7 +1152,7 @@ Doubling the postfix separates the letter and postfix: e.g. a^^ -> a^ (quail-define-package "dutch" "Dutch" "NL" t "Dutch character mixfix input method. -Caters for French and Turkish as well as Dutch. +Caters for French and Dutch. | | examples ------------+---------+---------- @@ -1163,8 +1163,6 @@ Caters for French and Turkish as well as Dutch. acute | \\=' | a\\=' -> á grave | \\=` | a\\=` -> à circumflex | ^ | a^ -> â - Turkish | various | i/ -> ı s, -> ş g^ -> ğ I/ -> İ - | | S, -> Ş G^ -> Ğ ------------+---------+---------- | prefix | ------------+---------+---------- @@ -1176,9 +1174,6 @@ Doubling the postfix separates the letter and postfix: e.g. a\\='\\=' -> a\\=' (quail-define-rules ("fl." ?ƒ) ;; LATIN SMALL LETTER F WITH HOOK (florin currency symbol) ("eur." ?€) ;; EURO SIGN - ;; “The 25th letter of the Dutch alphabet.” - ("ij" ?ij) ;; LATIN SMALL LIGATURE IJ - ("IJ" ?IJ) ;; LATIN CAPITAL LIGATURE IJ ;; “Trema on the second letter of vowel pair.” Yudit uses `:', not `"'. ("\"a" ?ä) ;; LATIN SMALL LETTER A WITH DIAERESIS ("\"e" ?ë) ;; LATIN SMALL LETTER E WITH DIAERESIS @@ -1226,15 +1221,6 @@ Doubling the postfix separates the letter and postfix: e.g. a\\='\\=' -> a\\=' ("I^" ?Î) ;; LATIN CAPITAL LETTER I WITH CIRCUMFLEX ("O^" ?Ô) ;; LATIN CAPITAL LETTER O WITH CIRCUMFLEX ("U^" ?Û) ;; LATIN CAPITAL LETTER U WITH CIRCUMFLEX - ;; “Follow the example of the Dutch POSIX locale, using ISO-8859-9 to - ;; cater to the many Turks in Dutch society.” Perhaps German methods - ;; should do so too. Follow turkish-alt-postfix here. - ("i/" ?ı) ;; LATIN SMALL LETTER I WITH NO DOT - ("s," ?ş) ;; LATIN SMALL LETTER S WITH CEDILLA - ("g^" ?ğ) ;; LATIN SMALL LETTER G WITH BREVE - ("I/" ?İ) ;; LATIN CAPITAL LETTER I WITH DOT ABOVE - ("S," ?Ş) ;; LATIN CAPITAL LETTER S WITH CEDILLA - ("G^" ?Ğ) ;; LATIN CAPITAL LETTER G WITH BREVE ) ;; Originally from Yudit, discussed with Albertas Agejevas diff --git a/lisp/leim/quail/latin-ltx.el b/lisp/leim/quail/latin-ltx.el index 6c5afcd4f93..313de991d89 100644 --- a/lisp/leim/quail/latin-ltx.el +++ b/lisp/leim/quail/latin-ltx.el @@ -23,7 +23,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -75,20 +75,20 @@ system, including many technical ones. Examples: (`(,seq ,re) (let ((count 0) (re (eval re t))) - (dolist (pair (ucs-names)) - (let ((name (car pair)) - (char (cdr pair))) - (when (and (characterp char) ;; Ignore char-ranges. - (string-match re name)) - (let ((keys (if (stringp seq) - (replace-match seq nil nil name) - (funcall seq name char)))) - (if (listp keys) - (dolist (x keys) - (setq count (1+ count)) - (push (list x char) newrules)) - (setq count (1+ count)) - (push (list keys char) newrules)))))) + (maphash + (lambda (name char) + (when (and (characterp char) ;; Ignore char-ranges. + (string-match re name)) + (let ((keys (if (stringp seq) + (replace-match seq nil nil name) + (funcall seq name char)))) + (if (listp keys) + (dolist (x keys) + (setq count (1+ count)) + (push (list x char) newrules)) + (setq count (1+ count)) + (push (list keys char) newrules))))) + (ucs-names)) ;; (message "latin-ltx: %d mappings for %S" count re) )))) (setq newrules (delete-dups newrules)) @@ -206,7 +206,7 @@ system, including many technical ones. Examples: ((lambda (name char) (let* ((base (concat (match-string 1 name) (match-string 3 name))) - (basechar (cdr (assoc base (ucs-names))))) + (basechar (gethash base (ucs-names)))) (when (latin-ltx--ascii-p basechar) (string (if (match-end 2) ?^ ?_) basechar)))) "\\(.*\\)SU\\(?:B\\|\\(PER\\)\\)SCRIPT \\(.*\\)") @@ -547,7 +547,7 @@ system, including many technical ones. Examples: ("\\propto" ?∝) ("\\qed" ?∎) ("\\quad" ? ) - ("\\rangle" ?⟩) ;; Was ?〉, see bug#12948. + ("\\rangle" ?\⟩) ;; Was ?〉, see bug#12948. ("\\rbrace" ?}) ("\\rbrack" ?\]) ("\\rceil" ?⌉) @@ -739,8 +739,8 @@ system, including many technical ones. Examples: ("\\textdiscount" ?⁒) ("\\textestimated" ?℮) ("\\textopenbullet" ?◦) - ("\\textlquill" ?⁅) - ("\\textrquill" ?⁆) + ("\\textlquill" ?\⁅) + ("\\textrquill" ?\⁆) ("\\textcircledP" ?℗) ("\\textreferencemark" ?※) ) diff --git a/lisp/leim/quail/latin-post.el b/lisp/leim/quail/latin-post.el index a5564483ee2..238b0efc093 100644 --- a/lisp/leim/quail/latin-post.el +++ b/lisp/leim/quail/latin-post.el @@ -24,7 +24,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; Author: TAKAHASHI Naoto <ntakahas@etl.go.jp> diff --git a/lisp/leim/quail/latin-pre.el b/lisp/leim/quail/latin-pre.el index 9e4726abffb..ca9c5f6e467 100644 --- a/lisp/leim/quail/latin-pre.el +++ b/lisp/leim/quail/latin-pre.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/leim/quail/lrt.el b/lisp/leim/quail/lrt.el index d3cfce68634..bad41559528 100644 --- a/lisp/leim/quail/lrt.el +++ b/lisp/leim/quail/lrt.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/leim/quail/persian.el b/lisp/leim/quail/persian.el index 56ba145daf6..093d30665da 100644 --- a/lisp/leim/quail/persian.el +++ b/lisp/leim/quail/persian.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -215,7 +215,7 @@ (">" ?<) ("?" ?؟) - ;; Level 3 Entered with \ + ;; Level 3 Entered with \ ;; ("\\" ?\\) ;; خط اريب وارو ("\\\\" ?\\) diff --git a/lisp/leim/quail/programmer-dvorak.el b/lisp/leim/quail/programmer-dvorak.el index 1dc8edc1efb..00d9a3c594d 100644 --- a/lisp/leim/quail/programmer-dvorak.el +++ b/lisp/leim/quail/programmer-dvorak.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/leim/quail/py-punct.el b/lisp/leim/quail/py-punct.el index 9fe06c07c01..39809af14b3 100644 --- a/lisp/leim/quail/py-punct.el +++ b/lisp/leim/quail/py-punct.el @@ -23,7 +23,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/leim/quail/pypunct-b5.el b/lisp/leim/quail/pypunct-b5.el index 37c12659cc3..ef5863101d2 100644 --- a/lisp/leim/quail/pypunct-b5.el +++ b/lisp/leim/quail/pypunct-b5.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/leim/quail/rfc1345.el b/lisp/leim/quail/rfc1345.el index 74f7d095653..5b66d91b5a6 100644 --- a/lisp/leim/quail/rfc1345.el +++ b/lisp/leim/quail/rfc1345.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/leim/quail/sgml-input.el b/lisp/leim/quail/sgml-input.el index c334b51cb4d..7383683120c 100644 --- a/lisp/leim/quail/sgml-input.el +++ b/lisp/leim/quail/sgml-input.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/leim/quail/sisheng.el b/lisp/leim/quail/sisheng.el index 8a1ddcff1e0..bbc251ab553 100644 --- a/lisp/leim/quail/sisheng.el +++ b/lisp/leim/quail/sisheng.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/leim/quail/slovak.el b/lisp/leim/quail/slovak.el index 817dcd08c4b..779f9b0c282 100644 --- a/lisp/leim/quail/slovak.el +++ b/lisp/leim/quail/slovak.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/leim/quail/symbol-ksc.el b/lisp/leim/quail/symbol-ksc.el index 31e839bfe82..70a54c7be63 100644 --- a/lisp/leim/quail/symbol-ksc.el +++ b/lisp/leim/quail/symbol-ksc.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; 94.10.24 Written for Mule Ver.2.0 (koaunghi.un@zdv.uni-tuebingen.de) ;;; 94.11.04 Updated for Mule Ver.2.1 (koaunghi.un@zdv.uni-tuebingen.de) diff --git a/lisp/leim/quail/tamil-dvorak.el b/lisp/leim/quail/tamil-dvorak.el index a625d900015..d080f7e5968 100644 --- a/lisp/leim/quail/tamil-dvorak.el +++ b/lisp/leim/quail/tamil-dvorak.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/leim/quail/thai.el b/lisp/leim/quail/thai.el index 02f8b78d76b..7cf11daf9d0 100644 --- a/lisp/leim/quail/thai.el +++ b/lisp/leim/quail/thai.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/leim/quail/tibetan.el b/lisp/leim/quail/tibetan.el index 4e1c5b51c52..8971b1ddf79 100644 --- a/lisp/leim/quail/tibetan.el +++ b/lisp/leim/quail/tibetan.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; Author: Toru TOMABECHI <Toru.Tomabechi@orient.unil.ch> diff --git a/lisp/leim/quail/uni-input.el b/lisp/leim/quail/uni-input.el index 595155e026b..744edc61470 100644 --- a/lisp/leim/quail/uni-input.el +++ b/lisp/leim/quail/uni-input.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/leim/quail/viqr.el b/lisp/leim/quail/viqr.el index 879fba4da2c..b7591b15e05 100644 --- a/lisp/leim/quail/viqr.el +++ b/lisp/leim/quail/viqr.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/leim/quail/vntelex.el b/lisp/leim/quail/vntelex.el index 074b806bd41..210e26ad18d 100644 --- a/lisp/leim/quail/vntelex.el +++ b/lisp/leim/quail/vntelex.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/leim/quail/vnvni.el b/lisp/leim/quail/vnvni.el index 5d21030a524..327ebb847b5 100644 --- a/lisp/leim/quail/vnvni.el +++ b/lisp/leim/quail/vnvni.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/leim/quail/welsh.el b/lisp/leim/quail/welsh.el index 7b0ca2c2dfe..c524139d2e1 100644 --- a/lisp/leim/quail/welsh.el +++ b/lisp/leim/quail/welsh.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/linum.el b/lisp/linum.el index 9cfb94dab68..3bee384708f 100644 --- a/lisp/linum.el +++ b/lisp/linum.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/loadhist.el b/lisp/loadhist.el index 28d0b18c812..5d42ed958e5 100644 --- a/lisp/loadhist.el +++ b/lisp/loadhist.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -162,6 +162,70 @@ documentation of `unload-feature' for details.") ;; mode, or proposed is not nil and not major-mode, and so we use it. (funcall (or proposed 'fundamental-mode))))))) +(cl-defgeneric loadhist-unload-element (x) + "Unload an element from the `load-history'." + (message "Unexpected element %S in load-history" x)) + +;; In `load-history', the definition of a previously autoloaded +;; function is represented by 2 entries: (t . SYMBOL) comes before +;; (defun . SYMBOL) and says we should restore SYMBOL's autoload when +;; we undefine it. +;; So we use this auxiliary variable to keep track of the last (t . SYMBOL) +;; that occurred. +(defvar loadhist--restore-autoload + "If non-nil, this is a symbol for which we should +restore a previous autoload if possible.") + +(cl-defmethod loadhist-unload-element ((x (head t))) + (setq loadhist--restore-autoload (cdr x))) + +(defun loadhist--unload-function (x) + (let ((fun (cdr x))) + (when (fboundp fun) + (when (fboundp 'ad-unadvise) + (ad-unadvise fun)) + (let ((aload (get fun 'autoload))) + (defalias fun + (if (and aload (eq fun loadhist--restore-autoload)) + (cons 'autoload aload) + nil))))) + (setq loadhist--restore-autoload nil)) + +(cl-defmethod loadhist-unload-element ((x (head defun))) + (loadhist--unload-function x)) +(cl-defmethod loadhist-unload-element ((x (head autoload))) + (loadhist--unload-function x)) + +(cl-defmethod loadhist-unload-element ((_ (head require))) nil) +(cl-defmethod loadhist-unload-element ((_ (head defface))) nil) + +(cl-defmethod loadhist-unload-element ((x (head provide))) + ;; Remove any feature names that this file provided. + (setq features (delq (cdr x) features))) + +(cl-defmethod loadhist-unload-element ((x symbol)) + ;; Kill local values as much as possible. + (dolist (buf (buffer-list)) + (with-current-buffer buf + (if (and (boundp x) (timerp (symbol-value x))) + (cancel-timer (symbol-value x))) + (kill-local-variable x))) + (if (and (boundp x) (timerp (symbol-value x))) + (cancel-timer (symbol-value x))) + ;; Get rid of the default binding if we can. + (unless (local-variable-if-set-p x) + (makunbound x))) + +(cl-defmethod loadhist-unload-element ((x (head define-type))) + (let* ((name (cdr x))) + ;; Remove the struct. + (setf (cl--find-class name) nil))) + +(cl-defmethod loadhist-unload-element ((x (head define-symbol-props))) + (pcase-dolist (`(,symbol . ,props) (cdr x)) + (dolist (prop props) + (put symbol prop nil)))) + ;;;###autoload (defun unload-feature (feature &optional force) "Unload the library that provided FEATURE. @@ -200,9 +264,6 @@ something strange, such as redefining an Emacs function." (prin1-to-string dependents) file)))) (let* ((unload-function-defs-list (feature-symbols feature)) (file (pop unload-function-defs-list)) - ;; If non-nil, this is a symbol for which we should - ;; restore a previous autoload if possible. - restore-autoload (name (symbol-name feature)) (unload-hook (intern-soft (concat name "-unload-hook"))) (unload-func (intern-soft (concat name "-unload-function")))) @@ -245,43 +306,7 @@ something strange, such as redefining an Emacs function." ;; Change major mode in all buffers using one defined in the feature being unloaded. (unload--set-major-mode) - (when (fboundp 'elp-restore-function) ; remove ELP stuff first - (dolist (elt unload-function-defs-list) - (when (symbolp elt) - (elp-restore-function elt)))) - - (dolist (x unload-function-defs-list) - (if (consp x) - (pcase (car x) - ;; Remove any feature names that this file provided. - (`provide - (setq features (delq (cdr x) features))) - ((or `defun `autoload) - (let ((fun (cdr x))) - (when (fboundp fun) - (when (fboundp 'ad-unadvise) - (ad-unadvise fun)) - (let ((aload (get fun 'autoload))) - (if (and aload (eq fun restore-autoload)) - (fset fun (cons 'autoload aload)) - (fmakunbound fun)))))) - ;; (t . SYMBOL) comes before (defun . SYMBOL) - ;; and says we should restore SYMBOL's autoload - ;; when we undefine it. - (`t (setq restore-autoload (cdr x))) - ((or `require `defface) nil) - (_ (message "Unexpected element %s in load-history" x))) - ;; Kill local values as much as possible. - (dolist (buf (buffer-list)) - (with-current-buffer buf - (if (and (boundp x) (timerp (symbol-value x))) - (cancel-timer (symbol-value x))) - (kill-local-variable x))) - (if (and (boundp x) (timerp (symbol-value x))) - (cancel-timer (symbol-value x))) - ;; Get rid of the default binding if we can. - (unless (local-variable-if-set-p x) - (makunbound x)))) + (mapc #'loadhist-unload-element unload-function-defs-list) ;; Delete the load-history element for this file. (setq load-history (delq (assoc file load-history) load-history)))) ;; Don't return load-history, it is not useful. diff --git a/lisp/loadup.el b/lisp/loadup.el index af42cd97111..40e5651aa1d 100644 --- a/lisp/loadup.el +++ b/lisp/loadup.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -76,6 +76,7 @@ (setq max-lisp-eval-depth 2200) (setq load-path (list (expand-file-name "." dir) (expand-file-name "emacs-lisp" dir) + (expand-file-name "progmodes" dir) (expand-file-name "language" dir) (expand-file-name "international" dir) (expand-file-name "textmodes" dir) @@ -337,7 +338,7 @@ ;; We reset load-path after dumping. ;; For a permanent change in load-path, use configure's ;; --enable-locallisppath option. - ;; See http://debbugs.gnu.org/16107 for more details. + ;; See https://debbugs.gnu.org/16107 for more details. (or (equal lp load-path) (message "Warning: Change in load-path due to site-load will be \ lost after dumping"))) diff --git a/lisp/locate.el b/lisp/locate.el index 738c333ac2d..20b05c234f6 100644 --- a/lisp/locate.el +++ b/lisp/locate.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/lpr.el b/lisp/lpr.el index 4c8dc2c3e75..b0a6e94975f 100644 --- a/lisp/lpr.el +++ b/lisp/lpr.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/ls-lisp.el b/lisp/ls-lisp.el index 7ae23434415..ad2a770430f 100644 --- a/lisp/ls-lisp.el +++ b/lisp/ls-lisp.el @@ -1,4 +1,4 @@ -;;; ls-lisp.el --- emulate insert-directory completely in Emacs Lisp +;;; ls-lisp.el --- emulate insert-directory completely in Emacs Lisp -*- lexical-binding: t -*- ;; Copyright (C) 1992, 1994, 2000-2017 Free Software Foundation, Inc. @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -60,6 +60,8 @@ ;;; Code: + + (defgroup ls-lisp nil "Emulate the ls program completely in Emacs Lisp." :version "21.1" @@ -245,11 +247,11 @@ to fail to line up, e.g. if month names are not all of the same length." "Format to display integer GIDs.") (defvar ls-lisp-gid-s-fmt " %s" "Format to display user group names.") -(defvar ls-lisp-filesize-d-fmt "%d" +(defvar ls-lisp-filesize-d-fmt " %d" "Format to display integer file sizes.") -(defvar ls-lisp-filesize-f-fmt "%.0f" +(defvar ls-lisp-filesize-f-fmt " %.0f" "Format to display float file sizes.") -(defvar ls-lisp-filesize-b-fmt "%.0f" +(defvar ls-lisp-filesize-b-fmt " %.0f" "Format to display file sizes in blocks (for the -s switch).") ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -477,6 +479,34 @@ not contain `d', so that a full listing is expected." (message "%s: doesn't exist or is inaccessible" file) (ding) (sit-for 2))))) ; to show user the message! +(declare-function dired-read-dir-and-switches "dired" (str)) +(declare-function dired-goto-next-file "dired" ()) + +(defun ls-lisp--dired (orig-fun dir-or-list &optional switches) + (interactive (dired-read-dir-and-switches "")) + (if (consp dir-or-list) + (funcall orig-fun dir-or-list switches) + (let ((dir-wildcard (insert-directory-wildcard-in-dir-p + (expand-file-name dir-or-list)))) + (if (not dir-wildcard) + (funcall orig-fun dir-or-list switches) + (let* ((default-directory (car dir-wildcard)) + (files (file-expand-wildcards (cdr dir-wildcard))) + (dir (car dir-wildcard))) + (if files + (let ((inhibit-read-only t) + (buf + (apply orig-fun (nconc (list dir) files) (and switches (list switches))))) + (with-current-buffer buf + (save-excursion + (goto-char (point-min)) + (dired-goto-next-file) + (forward-line 0) + (insert " wildcard " (cdr dir-wildcard) "\n")))) + (user-error "No files matching regexp"))))))) + +(advice-add 'dired :around #'ls-lisp--dired) + (defun ls-lisp-sanitize (file-alist) "Sanitize the elements in FILE-ALIST. Fixes any elements in the alist for directory entries whose file @@ -537,6 +567,8 @@ Responds to the window width as ls should but may not!" (setq list (cdr list))) result)) +(defvar w32-collate-ignore-punctuation) ; Declare for non-w32 builds. + (defsubst ls-lisp-string-lessp (s1 s2) "Return t if string S1 should sort before string S2. Case is significant if `ls-lisp-ignore-case' is nil. @@ -681,23 +713,26 @@ SWITCHES is a list of characters. Default sorting is alphabetic." (defun ls-lisp-classify-file (filename fattr) "Append a character to FILENAME indicating the file type. +This function puts the `dired-filename' property on FILENAME, but +not on the character indicator it appends. FATTR is the file attributes returned by `file-attributes' for the file. The file type indicators are `/' for directories, `@' for symbolic links, `|' for FIFOs, `=' for sockets, `*' for regular files that are executable, and nothing for other types of files." (let* ((type (car fattr)) (modestr (nth 8 fattr)) - (typestr (substring modestr 0 1))) + (typestr (substring modestr 0 1)) + (file-name (propertize filename 'dired-filename t))) (cond (type - (concat filename (if (eq type t) "/" "@"))) + (concat file-name (if (eq type t) "/" "@"))) ((string-match "x" modestr) - (concat filename "*")) + (concat file-name "*")) ((string= "p" typestr) - (concat filename "|")) + (concat file-name "|")) ((string= "s" typestr) - (concat filename "=")) - (t filename)))) + (concat file-name "=")) + (t file-name)))) (defun ls-lisp-classify (filedata) "Append a character to file name in FILEDATA indicating the file type. @@ -710,7 +745,6 @@ links, `|' for FIFOs, `=' for sockets, `*' for regular files that are executable, and nothing for other types of files." (let ((file-name (car filedata)) (fattr (cdr filedata))) - (setq file-name (propertize file-name 'dired-filename t)) (cons (ls-lisp-classify-file file-name fattr) fattr))) (defun ls-lisp-extension (filename) @@ -809,7 +843,7 @@ SWITCHES and TIME-INDEX give the full switch list and time data." " " (ls-lisp-format-time file-attr time-index) " " - (if (not (memq ?F switches)) ; ls-lisp-classify already did that + (if (not (memq ?F switches)) ; ls-lisp-classify-file already did that (propertize file-name 'dired-filename t) file-name) (if (stringp file-type) ; is a symbolic link @@ -831,7 +865,7 @@ Use the same method as ls to decide whether to show time-of-day or year, depending on distance between file date and the current time. All ls time options, namely c, t and u, are handled." (let* ((time (nth (or time-index 5) file-attr)) ; default is last modtime - (diff (- (float-time time) (float-time))) + (diff (time-subtract time nil)) ;; Consider a time to be recent if it is within the past six ;; months. A Gregorian year has 365.2425 * 24 * 60 * 60 == ;; 31556952 seconds on the average, and half of that is 15778476. @@ -848,7 +882,8 @@ All ls time options, namely c, t and u, are handled." (if (member locale '("C" "POSIX")) (setq locale nil)) (format-time-string - (if (and (<= past-cutoff diff) (<= diff 0)) + (if (and (not (time-less-p diff past-cutoff)) + (not (time-less-p 0 diff))) (if (and locale (not ls-lisp-use-localized-time-format)) "%m-%d %H:%M" (nth 0 ls-lisp-format-time-list)) @@ -866,6 +901,13 @@ All ls time options, namely c, t and u, are handled." file-size) (format " %6s" (file-size-human-readable file-size)))) +(defun ls-lisp-unload-function () + "Unload ls-lisp library." + (advice-remove 'insert-directory #'ls-lisp--insert-directory) + (advice-remove 'dired #'ls-lisp--dired) + ;; Continue standard unloading. + nil) + (provide 'ls-lisp) ;;; ls-lisp.el ends here diff --git a/lisp/macros.el b/lisp/macros.el index fc65489fe65..5583c02f68b 100644 --- a/lisp/macros.el +++ b/lisp/macros.el @@ -1,4 +1,4 @@ -;;; macros.el --- non-primitive commands for keyboard macros +;;; macros.el --- non-primitive commands for keyboard macros -*- lexical-binding:t -*- ;; Copyright (C) 1985-1987, 1992, 1994-1995, 2001-2017 Free Software ;; Foundation, Inc. @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -31,23 +31,10 @@ ;;; Code: +(require 'kmacro) + ;;;###autoload -(defun name-last-kbd-macro (symbol) - "Assign a name to the last keyboard macro defined. -Argument SYMBOL is the name to define. -The symbol's function definition becomes the keyboard macro string. -Such a \"function\" cannot be called from Lisp, but it is a valid editor command." - (interactive "SName for last kbd macro: ") - (or last-kbd-macro - (user-error "No keyboard macro defined")) - (and (fboundp symbol) - (not (stringp (symbol-function symbol))) - (not (vectorp (symbol-function symbol))) - (user-error "Function %s is already defined and not a keyboard macro" - symbol)) - (if (string-equal symbol "") - (user-error "No command name given")) - (fset symbol last-kbd-macro)) +(defalias 'name-last-kbd-macro #'kmacro-name-last-macro) ;;;###autoload (defun insert-kbd-macro (macroname &optional keys) @@ -66,11 +53,7 @@ To save a kbd macro, visit a file of Lisp code such as your `~/.emacs', use this command, and then save the file." (interactive (list (intern (completing-read "Insert kbd macro (name): " obarray - (lambda (elt) - (and (fboundp elt) - (or (stringp (symbol-function elt)) - (vectorp (symbol-function elt)) - (get elt 'kmacro)))) + #'kmacro-keyboard-macro-p t)) current-prefix-arg)) (let (definition) @@ -137,6 +120,9 @@ use this command, and then save the file." (prin1 char (current-buffer)) (princ (prin1-char char) (current-buffer)))) (insert ?\])) + ;; FIXME: For kmacros, we shouldn't write the (lambda ...) + ;; gunk but instead we should write something more abstract like + ;; (kmacro-create [<keys>] 0 "%d"). (prin1 definition (current-buffer)))) (insert ")\n") (if keys diff --git a/lisp/mail/binhex.el b/lisp/mail/binhex.el index 1d6828b44bb..f055215a8c6 100644 --- a/lisp/mail/binhex.el +++ b/lisp/mail/binhex.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mail/blessmail.el b/lisp/mail/blessmail.el index fc3b9618d68..c5e634607a3 100644 --- a/lisp/mail/blessmail.el +++ b/lisp/mail/blessmail.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mail/emacsbug.el b/lisp/mail/emacsbug.el index c1aec6923fb..92f39659360 100644 --- a/lisp/mail/emacsbug.el +++ b/lisp/mail/emacsbug.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -179,7 +179,7 @@ Prompts for bug subject. Leaves you in a mail buffer." 'face 'link 'help-echo (concat "mouse-2, RET: Follow this link") 'action (lambda (button) - (browse-url "http://lists.gnu.org/archive/html/bug-gnu-emacs/")) + (browse-url "https://lists.gnu.org/r/bug-gnu-emacs/")) 'follow-link t) (insert " mailing list\nand the GNU bug tracker at ") (insert-text-button @@ -187,7 +187,7 @@ Prompts for bug subject. Leaves you in a mail buffer." 'face 'link 'help-echo (concat "mouse-2, RET: Follow this link") 'action (lambda (button) - (browse-url "http://debbugs.gnu.org/")) + (browse-url "https://debbugs.gnu.org/")) 'follow-link t) (insert ". Please check that diff --git a/lisp/mail/flow-fill.el b/lisp/mail/flow-fill.el index 860d353002c..130e1640572 100644 --- a/lisp/mail/flow-fill.el +++ b/lisp/mail/flow-fill.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mail/footnote.el b/lisp/mail/footnote.el index 07e24bd78be..5e18d892d4a 100644 --- a/lisp/mail/footnote.el +++ b/lisp/mail/footnote.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mail/hashcash.el b/lisp/mail/hashcash.el index 71567b4c0fd..ff00ce4069e 100644 --- a/lisp/mail/hashcash.el +++ b/lisp/mail/hashcash.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mail/ietf-drums.el b/lisp/mail/ietf-drums.el index fd793a28309..86496beb0fd 100644 --- a/lisp/mail/ietf-drums.el +++ b/lisp/mail/ietf-drums.el @@ -16,7 +16,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mail/mail-extr.el b/lisp/mail/mail-extr.el index 180d195d553..81af0d541cf 100644 --- a/lisp/mail/mail-extr.el +++ b/lisp/mail/mail-extr.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mail/mail-hist.el b/lisp/mail/mail-hist.el index b056739c655..b525d8972c3 100644 --- a/lisp/mail/mail-hist.el +++ b/lisp/mail/mail-hist.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mail/mail-parse.el b/lisp/mail/mail-parse.el index 0578b98c933..49df82c38b0 100644 --- a/lisp/mail/mail-parse.el +++ b/lisp/mail/mail-parse.el @@ -16,7 +16,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mail/mail-prsvr.el b/lisp/mail/mail-prsvr.el index 07f650942c0..9703e47fc30 100644 --- a/lisp/mail/mail-prsvr.el +++ b/lisp/mail/mail-prsvr.el @@ -16,7 +16,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mail/mail-utils.el b/lisp/mail/mail-utils.el index c23af873650..0164ffdc46f 100644 --- a/lisp/mail/mail-utils.el +++ b/lisp/mail/mail-utils.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mail/mailabbrev.el b/lisp/mail/mailabbrev.el index 4e3a3f9d118..ef0e40f0201 100644 --- a/lisp/mail/mailabbrev.el +++ b/lisp/mail/mailabbrev.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mail/mailalias.el b/lisp/mail/mailalias.el index 8f3f901c22a..56fdd26b383 100644 --- a/lisp/mail/mailalias.el +++ b/lisp/mail/mailalias.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mail/mailclient.el b/lisp/mail/mailclient.el index 88624199df7..102730f476a 100644 --- a/lisp/mail/mailclient.el +++ b/lisp/mail/mailclient.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mail/mailheader.el b/lisp/mail/mailheader.el index bceba77c46d..b388c32c73b 100644 --- a/lisp/mail/mailheader.el +++ b/lisp/mail/mailheader.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mail/metamail.el b/lisp/mail/metamail.el index 4e0802804f4..e4886eabe61 100644 --- a/lisp/mail/metamail.el +++ b/lisp/mail/metamail.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mail/mspools.el b/lisp/mail/mspools.el index 21856c325c7..13a39e52119 100644 --- a/lisp/mail/mspools.el +++ b/lisp/mail/mspools.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mail/qp.el b/lisp/mail/qp.el index 262191db4ac..9533697c778 100644 --- a/lisp/mail/qp.el +++ b/lisp/mail/qp.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mail/reporter.el b/lisp/mail/reporter.el index b13da94c407..6cb5e4a8873 100644 --- a/lisp/mail/reporter.el +++ b/lisp/mail/reporter.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mail/rfc2045.el b/lisp/mail/rfc2045.el index 11a6151887a..f5185d22f74 100644 --- a/lisp/mail/rfc2045.el +++ b/lisp/mail/rfc2045.el @@ -16,7 +16,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; RFC 2045 is: "Multipurpose Internet Mail Extensions (MIME) Part ;; One: Format of Internet Message Bodies". diff --git a/lisp/mail/rfc2047.el b/lisp/mail/rfc2047.el index e2af86b3246..0c93331de8b 100644 --- a/lisp/mail/rfc2047.el +++ b/lisp/mail/rfc2047.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mail/rfc2231.el b/lisp/mail/rfc2231.el index 66f539f6986..e27113a9e39 100644 --- a/lisp/mail/rfc2231.el +++ b/lisp/mail/rfc2231.el @@ -16,7 +16,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mail/rfc2368.el b/lisp/mail/rfc2368.el index 6cb243ce5c1..3f09f87f1b3 100644 --- a/lisp/mail/rfc2368.el +++ b/lisp/mail/rfc2368.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/mail/rfc822.el b/lisp/mail/rfc822.el index 5edcef54284..e8bbea32573 100644 --- a/lisp/mail/rfc822.el +++ b/lisp/mail/rfc822.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mail/rmail-spam-filter.el b/lisp/mail/rmail-spam-filter.el index 648aa22aaa5..dee2d1c5133 100644 --- a/lisp/mail/rmail-spam-filter.el +++ b/lisp/mail/rmail-spam-filter.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;;; ----------- diff --git a/lisp/mail/rmail.el b/lisp/mail/rmail.el index df07140d87b..994570edcb2 100644 --- a/lisp/mail/rmail.el +++ b/lisp/mail/rmail.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -278,7 +278,7 @@ Otherwise, look for `movemail' in the directories in ;; rmail-insert-inbox-text before r1.439 fell back to using ;; (expand-file-name "movemail" exec-directory) and just ;; assuming it would work. - ;; http://lists.gnu.org/archive/html/bug-gnu-emacs/2008-02/msg00087.html + ;; https://lists.gnu.org/r/bug-gnu-emacs/2008-02/msg00087.html (let ((progname (expand-file-name (concat "movemail" (if (memq system-type '(ms-dos windows-nt)) @@ -534,7 +534,7 @@ still the current message in the Rmail buffer.") ;; It's not clear what it should do now, since there is nothing that ;; records when a message is shown for the first time (unseen is not ;; necessarily the same thing). -;; See http://lists.gnu.org/archive/html/emacs-devel/2009-03/msg00013.html +;; See https://lists.gnu.org/r/emacs-devel/2009-03/msg00013.html (defcustom rmail-message-filter nil "If non-nil, a filter function for new messages in RMAIL. Called with region narrowed to the message, including headers, @@ -2828,8 +2828,6 @@ The current mail message becomes the message displayed." (re-search-forward "mime-version: 1.0" nil t)) (let ((rmail-buffer mbox-buf) (rmail-view-buffer view-buf)) - (setq showing-message t) - (message "Showing message %d..." msg) (set (make-local-variable 'rmail-mime-decoded) t) (funcall rmail-show-mime-function)) (setq body-start (search-forward "\n\n" nil t)) diff --git a/lisp/mail/rmailedit.el b/lisp/mail/rmailedit.el index df1577fa915..640febd0473 100644 --- a/lisp/mail/rmailedit.el +++ b/lisp/mail/rmailedit.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -188,10 +188,6 @@ This function runs the hooks `text-mode-hook' and `rmail-edit-mode-hook'. (beginning-of-line) (insert ">") (forward-line))) - ;; Make sure buffer ends with a blank line so as not to run this - ;; message together with the following one. - (goto-char (point-max)) - (rmail-ensure-blank-line) (let ((old rmail-old-text) (pruned rmail-old-pruned) (mime-state rmail-old-mime-state) @@ -224,10 +220,9 @@ This function runs the hooks `text-mode-hook' and `rmail-edit-mode-hook'. (setq old nil) (goto-char (point-min)) (search-forward "\n\n") - (setq headers-end (point-marker)) - (goto-char (point-min)) + (setq headers-end (point-marker)) ; first character of body (save-restriction - (narrow-to-region (point) headers-end) + (narrow-to-region (point-min) headers-end) ;; If they changed the message's encoding, rewrite the charset= ;; header for them, so that subsequent rmail-show-message ;; decodes it correctly. @@ -240,6 +235,38 @@ This function runs the hooks `text-mode-hook' and `rmail-edit-mode-hook'. 'us-ascii new-coding)))) old-coding mime-beg mime-end content-type) + ;; If there's no content-type in the edited headers, look for one + ;; in the original headers and add it to the edited headers + ;; (Bug #26918) + (unless (mail-fetch-field "Content-Type") + (let (old-content-type + (msgbeg (rmail-msgbeg rmail-current-message)) + (msgend (rmail-msgend rmail-current-message))) + (with-current-buffer rmail-view-buffer ; really the mbox buffer + (save-restriction + (narrow-to-region msgbeg msgend) + (goto-char (point-min)) + (setq limit (search-forward "\n\n")) + (narrow-to-region (point-min) limit) + (goto-char (point-min)) + (when (re-search-forward "^content-type:" limit t) + (forward-line) + (setq old-content-type (buffer-substring + (match-beginning 0) (point)))))) + (when old-content-type + (save-excursion + (goto-char headers-end) ; first char of body + (backward-char) ; add header before second newline + (insert old-content-type) + ;;Add it to rmail-old-headers as though it had been + ;;there originally, to avoid rmail-edit-update-headers + ;;an extra copy + (let ((header (substring old-content-type 0 + (length "content-type")))) + (unless (assoc header rmail-old-headers) + (push (cons header old-content-type) rmail-old-headers))) + )))) + (goto-char (point-min)) (if (re-search-forward rmail-mime-charset-pattern nil 'move) (setq mime-beg (match-beginning 1) mime-end (match-end 1) @@ -281,29 +308,40 @@ This function runs the hooks `text-mode-hook' and `rmail-edit-mode-hook'. (setq character-coding (downcase character-coding))) (goto-char limit) - (let ((inhibit-read-only t)) - (let ((data-buffer (current-buffer)) - (end (copy-marker (point) t))) - (with-current-buffer rmail-view-buffer - (encode-coding-region headers-end (point-max) coding-system - data-buffer)) - (delete-region end (point-max))) - + (let ((inhibit-read-only t) + (data-buffer (current-buffer)) + (start (copy-marker (point) nil)) ; new body will be between + (end (copy-marker (point) t))) ; these two markers + (if mime-state + ;; Message is already in encoded state + (insert-buffer-substring rmail-view-buffer headers-end + (with-current-buffer rmail-view-buffer + (point-max))) + (with-current-buffer rmail-view-buffer + (encode-coding-region headers-end (point-max) coding-system + data-buffer))) ;; Apply to the mbox buffer any changes in header fields ;; that the user made while editing in the view buffer. - (rmail-edit-update-headers (rmail-edit-diff-headers + (rmail-edit-update-headers (rmail-edit-diff-headers rmail-old-headers new-headers)) - ;; Re-apply content-transfer-encoding, if any, on the message body. (cond + (mime-state) ; if set, already transfer-encoded ((string= character-coding "quoted-printable") - (mail-quote-printable-region (point) (point-max))) + (mail-quote-printable-region start end)) ((and (string= character-coding "base64") is-text-message) - (base64-encode-region (point) (point-max))) + (base64-encode-region start end)) ((and (eq character-coding 'uuencode) is-text-message) - (error "uuencoded messages are not supported")))) + (error "uuencoded messages are not supported"))) + ;; After encoding, make sure buffer ends with a blank line so as not to + ;; run this message together with the following one. + (goto-char end) + (rmail-ensure-blank-line) + ;; Delete previous body. This must be after all insertions at the end, + ;; so the marker for the beginning of the next message isn't messed up. + (delete-region end (point-max))) (rmail-set-attribute rmail-edited-attr-index t)) - ;;??? BROKEN perhaps. +;;;??? BROKEN perhaps. ;;; (if (boundp 'rmail-summary-vector) ;;; (aset rmail-summary-vector (1- rmail-current-message) nil)) (rmail-show-message) diff --git a/lisp/mail/rmailkwd.el b/lisp/mail/rmailkwd.el index 761a58f9311..b366e5c71bc 100644 --- a/lisp/mail/rmailkwd.el +++ b/lisp/mail/rmailkwd.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mail/rmailmm.el b/lisp/mail/rmailmm.el index 1ffd4668ac8..60b2066b2c2 100644 --- a/lisp/mail/rmailmm.el +++ b/lisp/mail/rmailmm.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mail/rmailmsc.el b/lisp/mail/rmailmsc.el index ac151f97fa6..b53b95ea52c 100644 --- a/lisp/mail/rmailmsc.el +++ b/lisp/mail/rmailmsc.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mail/rmailout.el b/lisp/mail/rmailout.el index a2f9320446e..8b918ec6e6c 100644 --- a/lisp/mail/rmailout.el +++ b/lisp/mail/rmailout.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mail/rmailsort.el b/lisp/mail/rmailsort.el index 681a9c4340c..a668d2e0bd8 100644 --- a/lisp/mail/rmailsort.el +++ b/lisp/mail/rmailsort.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mail/rmailsum.el b/lisp/mail/rmailsum.el index 37ac46c6af6..95d9b63f14f 100644 --- a/lisp/mail/rmailsum.el +++ b/lisp/mail/rmailsum.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mail/sendmail.el b/lisp/mail/sendmail.el index 12d69aa23c3..cd802115276 100644 --- a/lisp/mail/sendmail.el +++ b/lisp/mail/sendmail.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -243,15 +243,6 @@ Used by `mail-yank-original' via `mail-indent-citation'." :type 'integer :group 'sendmail) -(defvar mail-yank-hooks nil - "Obsolete hook for modifying a citation just inserted in the mail buffer. -Each hook function can find the citation between (point) and (mark t). -And each hook function should leave point and mark around the citation -text as modified. -This is a normal hook, misnamed for historical reasons. -It is obsolete and mail agents should no longer use it.") -(make-obsolete-variable 'mail-yank-hooks 'mail-citation-hook "19.34") - ;;;###autoload (defcustom mail-citation-hook nil "Hook for modifying a citation just inserted in the mail buffer. @@ -1718,8 +1709,6 @@ and don't delete any header fields." (rfc822-goto-eoh) (point)))))) (run-hooks 'mail-citation-hook))) - (mail-yank-hooks - (run-hooks 'mail-yank-hooks)) (t (mail-indent-citation))))) ;; This is like exchange-point-and-mark, but doesn't activate the mark. @@ -1788,9 +1777,7 @@ and don't delete any header fields." (rfc822-goto-eoh) (point)))))) (run-hooks 'mail-citation-hook)) - (if mail-yank-hooks - (run-hooks 'mail-yank-hooks) - (mail-indent-citation)))))))) + (mail-indent-citation))))))) (defun mail-split-line () "Split current line, moving portion beyond point vertically down. diff --git a/lisp/mail/smtpmail.el b/lisp/mail/smtpmail.el index 93bfe0e39d8..aff90d33ed3 100644 --- a/lisp/mail/smtpmail.el +++ b/lisp/mail/smtpmail.el @@ -24,7 +24,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mail/supercite.el b/lisp/mail/supercite.el index f3a6e3115bd..4a424ece0b1 100644 --- a/lisp/mail/supercite.el +++ b/lisp/mail/supercite.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; LCD Archive Entry ;; supercite|Barry A. Warsaw|supercite-help@python.org @@ -713,7 +713,7 @@ the list should be unique." ;; regi functions -;; http://lists.gnu.org/archive/html/emacs-devel/2009-02/msg00691.html +;; https://lists.gnu.org/r/emacs-devel/2009-02/msg00691.html ;; When rmail replies to a message with full headers visible, the "From " ;; line can be included. (defun sc-mail-check-from () diff --git a/lisp/mail/uce.el b/lisp/mail/uce.el index 3dce1c69023..db50c4e6bf6 100644 --- a/lisp/mail/uce.el +++ b/lisp/mail/uce.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mail/undigest.el b/lisp/mail/undigest.el index 73d7464bc13..77e97c7be91 100644 --- a/lisp/mail/undigest.el +++ b/lisp/mail/undigest.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mail/unrmail.el b/lisp/mail/unrmail.el index 2ff66467478..16e1ba3995f 100644 --- a/lisp/mail/unrmail.el +++ b/lisp/mail/unrmail.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mail/uudecode.el b/lisp/mail/uudecode.el index e68acbd2b8f..2811b0bf44a 100644 --- a/lisp/mail/uudecode.el +++ b/lisp/mail/uudecode.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mail/yenc.el b/lisp/mail/yenc.el index c8e2d2c7bcd..b84b16144d3 100644 --- a/lisp/mail/yenc.el +++ b/lisp/mail/yenc.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/makesum.el b/lisp/makesum.el index 48f51dee4c9..ffebf15db91 100644 --- a/lisp/makesum.el +++ b/lisp/makesum.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/man.el b/lisp/man.el index 0e1c92956b3..798e78bbe76 100644 --- a/lisp/man.el +++ b/lisp/man.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -278,7 +278,7 @@ Used in `bookmark-set' to get the default bookmark name." :type 'hook :group 'man) -(defvar Man-name-regexp "[-a-zA-Z0-9_+][-a-zA-Z0-9_.:+]*" +(defvar Man-name-regexp "[-[:alnum:]_+][-[:alnum:]_.:+]*" "Regular expression describing the name of a manpage (without section).") (defvar Man-section-regexp "[0-9][a-zA-Z0-9+]*\\|[LNln]" @@ -286,16 +286,16 @@ Used in `bookmark-set' to get the default bookmark name." (defvar Man-page-header-regexp (if (string-match "-solaris2\\." system-configuration) - (concat "^[-A-Za-z0-9_].*[ \t]\\(" Man-name-regexp + (concat "^[-[:alnum:]_].*[ \t]\\(" Man-name-regexp "(\\(" Man-section-regexp "\\))\\)$") (concat "^[ \t]*\\(" Man-name-regexp "(\\(" Man-section-regexp "\\))\\).*\\1")) "Regular expression describing the heading of a page.") -(defvar Man-heading-regexp "^\\([A-Z][A-Z0-9 /-]+\\)$" +(defvar Man-heading-regexp "^\\([[:upper:]][[:upper:]0-9 /-]+\\)$" "Regular expression describing a manpage heading entry.") -(defvar Man-see-also-regexp "SEE ALSO" +(defvar Man-see-also-regexp "\\(SEE ALSO\\|VOIR AUSSI\\|SIEHE AUCH\\|VÉASE TAMBIÉN\\|VEJA TAMBÉM\\|VEDERE ANCHE\\|ZOBACZ TAKŻE\\|İLGİLİ BELGELER\\|参照\\|参见 SEE ALSO\\|參見 SEE ALSO\\)" "Regular expression for SEE ALSO heading (or your equivalent). This regexp should not start with a `^' character.") @@ -1174,10 +1174,7 @@ See the variable `Man-notify-method' for the different notification behaviors." (unless (eq t (compare-strings "latin-" 0 nil current-language-environment 0 6 t)) (goto-char (point-min)) - (let ((str "\255")) - (if enable-multibyte-characters - (setq str (string-as-multibyte str))) - (while (search-forward str nil t) (replace-match "-"))))) + (while (search-forward "" nil t) (replace-match "-")))) (defun Man-fontify-manpage () "Convert overstriking and underlining to the correct fonts. @@ -1516,16 +1513,17 @@ The following key bindings are currently in effect in the buffer: (set (make-local-variable 'bookmark-make-record-function) 'Man-bookmark-make-record)) -(defsubst Man-build-section-alist () +(defun Man-build-section-list () "Build the list of manpage sections." - (setq Man--sections nil) + (setq Man--sections ()) (goto-char (point-min)) (let ((case-fold-search nil)) - (while (re-search-forward Man-heading-regexp (point-max) t) + (while (re-search-forward Man-heading-regexp nil t) (let ((section (match-string 1))) (unless (member section Man--sections) (push section Man--sections))) - (forward-line 1)))) + (forward-line))) + (setq Man--sections (nreverse Man--sections))) (defsubst Man-build-references-alist () "Build the list of references (in the SEE ALSO section)." @@ -1805,7 +1803,7 @@ Specify which REFERENCE to use; default is based on word at point." (widen) (goto-char page-start) (narrow-to-region page-start page-end) - (Man-build-section-alist) + (Man-build-section-list) (Man-build-references-alist) (goto-char (point-min))))) diff --git a/lisp/master.el b/lisp/master.el index 07e9ee5abc0..3745e216c4f 100644 --- a/lisp/master.el +++ b/lisp/master.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mb-depth.el b/lisp/mb-depth.el index 57fe7abde51..5bdf8b9dda3 100644 --- a/lisp/mb-depth.el +++ b/lisp/mb-depth.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/md4.el b/lisp/md4.el index 23d00ab0609..10f3d188830 100644 --- a/lisp/md4.el +++ b/lisp/md4.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: diff --git a/lisp/menu-bar.el b/lisp/menu-bar.el index 9c7bcffbaab..2b38cb5f2b0 100644 --- a/lisp/menu-bar.el +++ b/lisp/menu-bar.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; Avishai Yacobi suggested some menu rearrangements. @@ -669,7 +669,8 @@ The selected font will be the default on both the existing and future frames." (let ((need-save nil)) ;; These are set with menu-bar-make-mm-toggle, which does not ;; put on a customized-value property. - (dolist (elt '(line-number-mode column-number-mode size-indication-mode + (dolist (elt '(global-display-line-numbers-mode display-line-numbers-type + line-number-mode column-number-mode size-indication-mode cua-mode show-paren-mode transient-mark-mode blink-cursor-mode display-time-mode display-battery-mode ;; These are set by other functions that don't set @@ -1101,17 +1102,78 @@ The selected font will be the default on both the existing and future frames." :button (:radio . (eq tool-bar-mode nil)))) menu))) +(defvar display-line-numbers-type) +(defun menu-bar-display-line-numbers-mode (type) + (setq display-line-numbers-type type) + (if global-display-line-numbers-mode + (global-display-line-numbers-mode) + (display-line-numbers-mode))) + +(defvar menu-bar-showhide-line-numbers-menu + (let ((menu (make-sparse-keymap "Line Numbers"))) + + (bindings--define-key menu [visual] + `(menu-item "Visual Line Numbers" + ,(lambda () + (interactive) + (menu-bar-display-line-numbers-mode 'visual) + (message "Visual line numbers enabled")) + :help "Enable visual line numbers" + :button (:radio . (eq display-line-numbers 'visual)) + :visible (menu-bar-menu-frame-live-and-visible-p))) + + (bindings--define-key menu [relative] + `(menu-item "Relative Line Numbers" + ,(lambda () + (interactive) + (menu-bar-display-line-numbers-mode 'relative) + (message "Relative line numbers enabled")) + :help "Enable relative line numbers" + :button (:radio . (eq display-line-numbers 'relative)) + :visible (menu-bar-menu-frame-live-and-visible-p))) + + (bindings--define-key menu [absolute] + `(menu-item "Absolute Line Numbers" + ,(lambda () + (interactive) + (menu-bar-display-line-numbers-mode t) + (setq display-line-numbers t) + (message "Absolute line numbers enabled")) + :help "Enable absolute line numbers" + :button (:radio . (eq display-line-numbers t)) + :visible (menu-bar-menu-frame-live-and-visible-p))) + + (bindings--define-key menu [none] + `(menu-item "No Line Numbers" + ,(lambda () + (interactive) + (menu-bar-display-line-numbers-mode nil) + (message "Line numbers disabled")) + :help "Disable line numbers" + :button (:radio . (null display-line-numbers)) + :visible (menu-bar-menu-frame-live-and-visible-p))) + + (bindings--define-key menu [global] + (menu-bar-make-mm-toggle global-display-line-numbers-mode + "Global Line Numbers Mode" + "Set line numbers globally")) + menu)) + (defvar menu-bar-showhide-menu (let ((menu (make-sparse-keymap "Show/Hide"))) + (bindings--define-key menu [display-line-numbers] + `(menu-item "Line Numbers for All Lines" + ,menu-bar-showhide-line-numbers-menu)) + (bindings--define-key menu [column-number-mode] (menu-bar-make-mm-toggle column-number-mode - "Column Numbers" + "Column Numbers in Mode Line" "Show the current column number in the mode line")) (bindings--define-key menu [line-number-mode] (menu-bar-make-mm-toggle line-number-mode - "Line Numbers" + "Line Numbers in Mode Line" "Show the current line number in the mode line")) (bindings--define-key menu [size-indication-mode] diff --git a/lisp/mh-e/ChangeLog.1 b/lisp/mh-e/ChangeLog.1 index 31a9ea7651b..9bf28b0f132 100644 --- a/lisp/mh-e/ChangeLog.1 +++ b/lisp/mh-e/ChangeLog.1 @@ -11434,7 +11434,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; Local Variables: ;; coding: utf-8 diff --git a/lisp/mh-e/ChangeLog.2 b/lisp/mh-e/ChangeLog.2 index 487198663e3..c3f28ae8164 100644 --- a/lisp/mh-e/ChangeLog.2 +++ b/lisp/mh-e/ChangeLog.2 @@ -3688,7 +3688,7 @@ See ChangeLog.1 for earlier changes. GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; Local Variables: ;; coding: utf-8 diff --git a/lisp/mh-e/mh-acros.el b/lisp/mh-e/mh-acros.el index d424247a4fc..86248feff6d 100644 --- a/lisp/mh-e/mh-acros.el +++ b/lisp/mh-e/mh-acros.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mh-e/mh-alias.el b/lisp/mh-e/mh-alias.el index d62ac671ea1..7e69e7556cd 100644 --- a/lisp/mh-e/mh-alias.el +++ b/lisp/mh-e/mh-alias.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mh-e/mh-buffers.el b/lisp/mh-e/mh-buffers.el index 233f8988f07..e088bca48b2 100644 --- a/lisp/mh-e/mh-buffers.el +++ b/lisp/mh-e/mh-buffers.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mh-e/mh-comp.el b/lisp/mh-e/mh-comp.el index c0523989230..98067ce1293 100644 --- a/lisp/mh-e/mh-comp.el +++ b/lisp/mh-e/mh-comp.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mh-e/mh-compat.el b/lisp/mh-e/mh-compat.el index dbdadb10bf6..3dc7a62f3c9 100644 --- a/lisp/mh-e/mh-compat.el +++ b/lisp/mh-e/mh-compat.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mh-e/mh-e.el b/lisp/mh-e/mh-e.el index 38558f2dc09..f511bf7dc40 100644 --- a/lisp/mh-e/mh-e.el +++ b/lisp/mh-e/mh-e.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -3035,12 +3035,12 @@ XEmacs. For more information, see URL `ftp://ftp.cs.indiana.edu/pub/faces/compface/compface.tar.z'). Recent versions of XEmacs have internal support for \"X-Face:\" images. If your version of XEmacs does not, then you'll need both \"uncompface\" -and the x-face package (see URL `ftp://ftp.jpl.org/pub/elisp/'). +and the x-face package (see URL `http://www.jpl.org/ftp/pub/elisp/'). Finally, MH-E will display images referenced by the \"X-Image-URL:\" header field if neither the \"Face:\" nor the \"X-Face:\" fields are present. The display of the images requires \"wget\" (see URL -`http://www.gnu.org/software/wget/wget.html'), \"fetch\", or \"curl\" +`https://www.gnu.org/software/wget/wget.html'), \"fetch\", or \"curl\" to fetch the image and the \"convert\" program from the ImageMagick suite (see URL `http://www.imagemagick.org/'). Of the three header fields this is the most efficient in terms of network usage since the diff --git a/lisp/mh-e/mh-folder.el b/lisp/mh-e/mh-folder.el index f846f179433..49cf3d3dff0 100644 --- a/lisp/mh-e/mh-folder.el +++ b/lisp/mh-e/mh-folder.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mh-e/mh-funcs.el b/lisp/mh-e/mh-funcs.el index 5252f92966f..cfff8cb6629 100644 --- a/lisp/mh-e/mh-funcs.el +++ b/lisp/mh-e/mh-funcs.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mh-e/mh-gnus.el b/lisp/mh-e/mh-gnus.el index 318759ddc1b..33673251c95 100644 --- a/lisp/mh-e/mh-gnus.el +++ b/lisp/mh-e/mh-gnus.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mh-e/mh-identity.el b/lisp/mh-e/mh-identity.el index ecc7f7e5430..9518e967993 100644 --- a/lisp/mh-e/mh-identity.el +++ b/lisp/mh-e/mh-identity.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mh-e/mh-inc.el b/lisp/mh-e/mh-inc.el index a7ff8f31467..fcdb3f02274 100644 --- a/lisp/mh-e/mh-inc.el +++ b/lisp/mh-e/mh-inc.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mh-e/mh-junk.el b/lisp/mh-e/mh-junk.el index 25e116cb28e..9057af43d66 100644 --- a/lisp/mh-e/mh-junk.el +++ b/lisp/mh-e/mh-junk.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mh-e/mh-letter.el b/lisp/mh-e/mh-letter.el index ca4ec39733e..871ba49522a 100644 --- a/lisp/mh-e/mh-letter.el +++ b/lisp/mh-e/mh-letter.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -60,17 +60,6 @@ (to . mh-alias-letter-expand-alias)) "Alist of header fields and completion functions to use.") -(defvar mh-yank-hooks nil - "Obsolete hook for modifying a citation just inserted in the mail buffer. - -Each hook function can find the citation between point and mark. -And each hook function should leave point and mark around the -citation text as modified. - -This is a normal hook, misnamed for historical reasons. -It is obsolete and is only used if `mail-citation-hook' is nil.") -(mh-make-obsolete-variable 'mh-yank-hooks 'mail-citation-hook "19.34") - ;;; Letter Menu @@ -972,8 +961,6 @@ Otherwise, simply insert MH-INS-STRING before each line." (sc-cite-original)) (mail-citation-hook (run-hooks 'mail-citation-hook)) - (mh-yank-hooks ;old hook name - (run-hooks 'mh-yank-hooks)) (t (or (bolp) (forward-line 1)) (while (< (point) (point-max)) diff --git a/lisp/mh-e/mh-limit.el b/lisp/mh-e/mh-limit.el index bc4a0066420..280bcc683f5 100644 --- a/lisp/mh-e/mh-limit.el +++ b/lisp/mh-e/mh-limit.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mh-e/mh-mime.el b/lisp/mh-e/mh-mime.el index 7238de08b9b..69c57e0afdc 100644 --- a/lisp/mh-e/mh-mime.el +++ b/lisp/mh-e/mh-mime.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -659,6 +659,7 @@ buttons for alternative parts that are usually suppressed." (attachmentp (equal (car (mm-handle-disposition handle)) "attachment")) (inlinep (and (equal (car (mm-handle-disposition handle)) "inline") + (mm-automatic-display-p handle) (mm-inlinable-p handle) (mm-inlined-p handle))) (displayp (or inlinep ; show if inline OR @@ -669,6 +670,7 @@ buttons for alternative parts that are usually suppressed." (and (not (equal (mm-handle-media-supertype handle) "image")) + (mm-automatic-display-p handle) (mm-inlinable-p handle) (mm-inlined-p handle))))))) (save-restriction diff --git a/lisp/mh-e/mh-print.el b/lisp/mh-e/mh-print.el index 906899d3b6d..d7b686cfec4 100644 --- a/lisp/mh-e/mh-print.el +++ b/lisp/mh-e/mh-print.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mh-e/mh-scan.el b/lisp/mh-e/mh-scan.el index 1e708e529cf..936d451e2d2 100644 --- a/lisp/mh-e/mh-scan.el +++ b/lisp/mh-e/mh-scan.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mh-e/mh-search.el b/lisp/mh-e/mh-search.el index b0fdfce8e87..95a5a08b1af 100644 --- a/lisp/mh-e/mh-search.el +++ b/lisp/mh-e/mh-search.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mh-e/mh-seq.el b/lisp/mh-e/mh-seq.el index 6fc518b57c4..9d3bd2dcd2d 100644 --- a/lisp/mh-e/mh-seq.el +++ b/lisp/mh-e/mh-seq.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mh-e/mh-show.el b/lisp/mh-e/mh-show.el index ab320caf604..ce843a6a7cf 100644 --- a/lisp/mh-e/mh-show.el +++ b/lisp/mh-e/mh-show.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mh-e/mh-speed.el b/lisp/mh-e/mh-speed.el index 8d14d852397..4438bf2c8e1 100644 --- a/lisp/mh-e/mh-speed.el +++ b/lisp/mh-e/mh-speed.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mh-e/mh-thread.el b/lisp/mh-e/mh-thread.el index b1b1512614a..9b9ef341507 100644 --- a/lisp/mh-e/mh-thread.el +++ b/lisp/mh-e/mh-thread.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mh-e/mh-tool-bar.el b/lisp/mh-e/mh-tool-bar.el index 07dd29b4be3..3add54f03e2 100644 --- a/lisp/mh-e/mh-tool-bar.el +++ b/lisp/mh-e/mh-tool-bar.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mh-e/mh-utils.el b/lisp/mh-e/mh-utils.el index 1ed2e0f8713..7cb52ffa9ef 100644 --- a/lisp/mh-e/mh-utils.el +++ b/lisp/mh-e/mh-utils.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mh-e/mh-xface.el b/lisp/mh-e/mh-xface.el index dbfaa35c738..92afd63262b 100644 --- a/lisp/mh-e/mh-xface.el +++ b/lisp/mh-e/mh-xface.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/midnight.el b/lisp/midnight.el index b9893fbfced..dfe0df33397 100644 --- a/lisp/midnight.el +++ b/lisp/midnight.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/minibuf-eldef.el b/lisp/minibuf-eldef.el index 096800155bb..1d223e6fd0f 100644 --- a/lisp/minibuf-eldef.el +++ b/lisp/minibuf-eldef.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el index 0377cd549a2..3b1d6f447a5 100644 --- a/lisp/minibuffer.el +++ b/lisp/minibuffer.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -392,7 +392,7 @@ obeys predicates." (and (funcall pred1 x) (funcall pred2 x))))) ;; If completion failed and we're not applying pred1 strictly, try ;; again without pred1. - (and (not strict) pred1 pred2 + (and (not strict) pred1 (complete-with-action action table string pred2)))))) (defun completion-table-in-turn (&rest tables) @@ -746,7 +746,7 @@ If the current buffer is not a minibuffer, erase its entire contents." (defcustom completion-auto-help t "Non-nil means automatically provide help for invalid completion input. -If the value is t the *Completion* buffer is displayed whenever completion +If the value is t the *Completions* buffer is displayed whenever completion is requested but cannot be done. If the value is `lazy', the *Completions* buffer is only displayed after the second failed attempt to complete." @@ -896,8 +896,15 @@ This overrides the defaults specified in `completion-category-defaults'." ;; than from completion-extra-properties) because it may apply only to some ;; part of the string (e.g. substitute-in-file-name). (let ((requote - (when (completion-metadata-get metadata 'completion--unquote-requote) - (cl-assert (functionp table)) + (when (and + (completion-metadata-get metadata 'completion--unquote-requote) + ;; Sometimes a table's metadata is used on another + ;; table (typically that other table is just a list taken + ;; from the output of `all-completions' or something equivalent, + ;; for progressive refinement). See bug#28898 and bug#16274. + ;; FIXME: Rather than do nothing, we should somehow call + ;; the original table, in that case! + (functionp table)) (let ((new (funcall table string point 'completion--unquote))) (setq string (pop new)) (setq table (pop new)) @@ -1312,7 +1319,7 @@ Repeated uses step through the possible completions." (defvar minibuffer-confirm-exit-commands '(completion-at-point minibuffer-complete minibuffer-complete-word PC-complete PC-complete-word) - "A list of commands which cause an immediately following + "List of commands which cause an immediately following `minibuffer-complete-and-exit' to ask for extra confirmation.") (defun minibuffer-complete-and-exit () @@ -2979,6 +2986,17 @@ or a symbol, see `completion-pcm--merge-completions'." (setq re (replace-match "" t t re 1))) re)) +(defun completion-pcm--pattern-point-idx (pattern) + "Return index of subgroup corresponding to `point' element of PATTERN. +Return nil if there's no such element." + (let ((idx nil) + (i 0)) + (dolist (x pattern) + (unless (stringp x) + (cl-incf i) + (if (eq x 'point) (setq idx i)))) + idx)) + (defun completion-pcm--all-completions (prefix pattern table pred) "Find all completions for PATTERN in TABLE obeying PRED. PATTERN is as returned by `completion-pcm--string->pattern'." @@ -3006,11 +3024,12 @@ PATTERN is as returned by `completion-pcm--string->pattern'." (let ((poss ())) (dolist (c compl) (when (string-match-p regex c) (push c poss))) - poss))))) + (nreverse poss)))))) (defun completion-pcm--hilit-commonality (pattern completions) (when completions - (let* ((re (completion-pcm--pattern->regex pattern '(point))) + (let* ((re (completion-pcm--pattern->regex pattern 'group)) + (point-idx (completion-pcm--pattern-point-idx pattern)) (case-fold-search completion-ignore-case)) (mapcar (lambda (str) @@ -3018,8 +3037,16 @@ PATTERN is as returned by `completion-pcm--string->pattern'." (setq str (copy-sequence str)) (unless (string-match re str) (error "Internal error: %s does not match %s" re str)) - (let ((pos (or (match-beginning 1) (match-end 0)))) - (put-text-property 0 pos + (let* ((pos (if point-idx (match-beginning point-idx) (match-end 0))) + (md (match-data)) + (start (pop md)) + (end (pop md))) + (while md + (put-text-property start (pop md) + 'font-lock-face 'completions-common-part + str) + (setq start (pop md))) + (put-text-property start end 'font-lock-face 'completions-common-part str) (if (> (length str) pos) @@ -3258,7 +3285,7 @@ the same set of elements." "\\)\\'"))) (dolist (f all) (unless (string-match-p re f) (push f try))) - (or try all)))) + (or (nreverse try) all)))) (defun completion-pcm--merge-try (pattern all prefix suffix) diff --git a/lisp/misc.el b/lisp/misc.el index dc47c37dbc0..8806ac83837 100644 --- a/lisp/misc.el +++ b/lisp/misc.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/misearch.el b/lisp/misearch.el index 884b33020a8..89b437f1f16 100644 --- a/lisp/misearch.el +++ b/lisp/misearch.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mouse-copy.el b/lisp/mouse-copy.el index 44d9973e630..4da25dee9c1 100644 --- a/lisp/mouse-copy.el +++ b/lisp/mouse-copy.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mouse-drag.el b/lisp/mouse-drag.el index 5a83e57347b..775a464b236 100644 --- a/lisp/mouse-drag.el +++ b/lisp/mouse-drag.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mouse.el b/lisp/mouse.el index 9b6b169e568..17d1732e501 100644 --- a/lisp/mouse.el +++ b/lisp/mouse.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -380,7 +380,7 @@ This command must be bound to a mouse click." (defun mouse-drag-line (start-event line) "Drag a mode line, header line, or vertical line with the mouse. -START-EVENT is the starting mouse-event of the drag action. LINE +START-EVENT is the starting mouse event of the drag action. LINE must be one of the symbols `header', `mode', or `vertical'." ;; Give temporary modes such as isearch a chance to turn off. (run-hooks 'mouse-leave-buffer-hook) @@ -405,29 +405,15 @@ must be one of the symbols `header', `mode', or `vertical'." ;; window's edge we drag. (cond ((eq line 'header) - (if (window-at-side-p window 'top) - ;; We can't drag the header line of a topmost window. - (setq draggable nil) - ;; Drag bottom edge of window above the header line. - (setq window (window-in-direction 'above window t)))) - ((eq line 'mode) - (if (and (window-at-side-p window 'bottom) - ;; Allow resizing the minibuffer window if it's on the - ;; same frame as and immediately below `window', and it's - ;; either active or `resize-mini-windows' is nil. - (let ((minibuffer-window (minibuffer-window frame))) - (not (and (eq (window-frame minibuffer-window) frame) - (or (not resize-mini-windows) - (eq minibuffer-window - (active-minibuffer-window))))))) - (setq draggable nil))) + ;; Drag bottom edge of window above the header line. + (setq window (window-in-direction 'above window t))) + ((eq line 'mode)) ((eq line 'vertical) (let ((divider-width (frame-right-divider-width frame))) (when (and (or (not (numberp divider-width)) (zerop divider-width)) (eq (frame-parameter frame 'vertical-scroll-bars) 'left)) (setq window (window-in-direction 'left window t)))))) - (let* ((exitfun nil) (move (lambda (event) (interactive "e") @@ -530,20 +516,405 @@ must be one of the symbols `header', `mode', or `vertical'." t (lambda () (setq track-mouse old-track-mouse))))))) (defun mouse-drag-mode-line (start-event) - "Change the height of a window by dragging on the mode line." + "Change the height of a window by dragging on its mode line. +START-EVENT is the starting mouse event of the drag action. + +If the drag happens in a mode line on the bottom of a frame and +that frame's `drag-with-mode-line' parameter is non-nil, drag the +frame instead." (interactive "e") - (mouse-drag-line start-event 'mode)) + (let* ((start (event-start start-event)) + (window (posn-window start)) + (frame (window-frame window))) + (cond + ((not (window-live-p window))) + ((or (not (window-at-side-p window 'bottom)) + ;; Allow resizing the minibuffer window if it's on the + ;; same frame as and immediately below `window', and it's + ;; either active or `resize-mini-windows' is nil. + (let ((minibuffer-window (minibuffer-window frame))) + (and (eq (window-frame minibuffer-window) frame) + (or (not resize-mini-windows) + (eq minibuffer-window + (active-minibuffer-window)))))) + (mouse-drag-line start-event 'mode)) + ((and (frame-parameter frame 'drag-with-mode-line) + (window-at-side-p window 'bottom) + (let ((minibuffer-window (minibuffer-window frame))) + (not (eq (window-frame minibuffer-window) frame)))) + ;; Drag frame when the window is on the bottom of its frame and + ;; there is no minibuffer window below. + (mouse-drag-frame start-event 'move))))) (defun mouse-drag-header-line (start-event) - "Change the height of a window by dragging on the header line." + "Change the height of a window by dragging on its header line. +START-EVENT is the starting mouse event of the drag action. + +If the drag happens in a header line on the top of a frame and +that frame's `drag-with-header-line' parameter is non-nil, drag +the frame instead." (interactive "e") - (mouse-drag-line start-event 'header)) + (let* ((start (event-start start-event)) + (window (posn-window start))) + (if (and (window-live-p window) + (not (window-at-side-p window 'top))) + (mouse-drag-line start-event 'header) + (let ((frame (window-frame window))) + (when (frame-parameter frame 'drag-with-header-line) + (mouse-drag-frame start-event 'move)))))) (defun mouse-drag-vertical-line (start-event) - "Change the width of a window by dragging on the vertical line." + "Change the width of a window by dragging on a vertical line. +START-EVENT is the starting mouse event of the drag action." (interactive "e") (mouse-drag-line start-event 'vertical)) +(defun mouse-resize-frame (frame x-diff y-diff &optional x-move y-move) + "Helper function for `mouse-drag-frame'." + (let* ((frame-x-y (frame-position frame)) + (frame-x (car frame-x-y)) + (frame-y (cdr frame-x-y)) + alist) + (if (> x-diff 0) + (when x-move + (setq x-diff (min x-diff frame-x)) + (setq x-move (- frame-x x-diff))) + (let* ((min-width (frame-windows-min-size frame t nil t)) + (min-diff (max 0 (- (frame-inner-width frame) min-width)))) + (setq x-diff (max x-diff (- min-diff))) + (when x-move + (setq x-move (+ frame-x (- x-diff)))))) + + (if (> y-diff 0) + (when y-move + (setq y-diff (min y-diff frame-y)) + (setq y-move (- frame-y y-diff))) + (let* ((min-height (frame-windows-min-size frame nil nil t)) + (min-diff (max 0 (- (frame-inner-height frame) min-height)))) + (setq y-diff (max y-diff (- min-diff))) + (when y-move + (setq y-move (+ frame-y (- y-diff)))))) + + (unless (zerop x-diff) + (when x-move + (push `(left . ,x-move) alist)) + (push `(width . (text-pixels . ,(+ (frame-text-width frame) x-diff))) + alist)) + (unless (zerop y-diff) + (when y-move + (push `(top . ,y-move) alist)) + (push `(height . (text-pixels . ,(+ (frame-text-height frame) y-diff))) + alist)) + (when alist + (modify-frame-parameters frame alist)))) + +(defun mouse-drag-frame (start-event part) + "Drag a frame or one of its edges with the mouse. +START-EVENT is the starting mouse event of the drag action. Its +position window denotes the frame that will be dragged. + +PART specifies the part that has been dragged and must be one of +the symbols 'left', 'top', 'right', 'bottom', 'top-left', +'top-right', 'bottom-left', 'bottom-right' to drag an internal +border or edge. If PART equals 'move', this means to move the +frame with the mouse." + ;; Give temporary modes such as isearch a chance to turn off. + (run-hooks 'mouse-leave-buffer-hook) + (let* ((echo-keystrokes 0) + (start (event-start start-event)) + (window (posn-window start)) + ;; FRAME is the frame to drag. + (frame (if (window-live-p window) + (window-frame window) + window)) + (width (frame-native-width frame)) + (height (frame-native-height frame)) + ;; PARENT is the parent frame of FRAME or, if FRAME is a + ;; top-level frame, FRAME's workarea. + (parent (frame-parent frame)) + (parent-edges + (if parent + `(0 0 ,(frame-native-width parent) ,(frame-native-height parent)) + (let* ((attributes + (car (display-monitor-attributes-list))) + (workarea (assq 'workarea attributes))) + (and workarea + `(,(nth 1 workarea) ,(nth 2 workarea) + ,(+ (nth 1 workarea) (nth 3 workarea)) + ,(+ (nth 2 workarea) (nth 4 workarea))))))) + (parent-left (and parent-edges (nth 0 parent-edges))) + (parent-top (and parent-edges (nth 1 parent-edges))) + (parent-right (and parent-edges (nth 2 parent-edges))) + (parent-bottom (and parent-edges (nth 3 parent-edges))) + ;; `pos-x' and `pos-y' record the x- and y-coordinates of the + ;; last sampled mouse position. Note that we sample absolute + ;; mouse positions to avoid that moving the mouse from one + ;; frame into another gets into our way. `last-x' and `last-y' + ;; records the x- and y-coordinates of the previously sampled + ;; position. The differences between `last-x' and `pos-x' as + ;; well as `last-y' and `pos-y' determine the amount the mouse + ;; has been dragged between the last two samples. + pos-x-y pos-x pos-y + (last-x-y (mouse-absolute-pixel-position)) + (last-x (car last-x-y)) + (last-y (cdr last-x-y)) + ;; `snap-x' and `snap-y' record the x- and y-coordinates of the + ;; mouse position when FRAME snapped. As soon as the + ;; difference between `pos-x' and `snap-x' (or `pos-y' and + ;; `snap-y') exceeds the value of FRAME's `snap-width' + ;; parameter, unsnap FRAME (at the respective side). `snap-x' + ;; and `snap-y' nil mean FRAME is currently not snapped. + snap-x snap-y + (exitfun nil) + (move + (lambda (event) + (interactive "e") + (when (consp event) + (setq pos-x-y (mouse-absolute-pixel-position)) + (setq pos-x (car pos-x-y)) + (setq pos-y (cdr pos-x-y)) + (cond + ((eq part 'left) + (mouse-resize-frame frame (- last-x pos-x) 0 t)) + ((eq part 'top) + (mouse-resize-frame frame 0 (- last-y pos-y) nil t)) + ((eq part 'right) + (mouse-resize-frame frame (- pos-x last-x) 0)) + ((eq part 'bottom) + (mouse-resize-frame frame 0 (- pos-y last-y))) + ((eq part 'top-left) + (mouse-resize-frame + frame (- last-x pos-x) (- last-y pos-y) t t)) + ((eq part 'top-right) + (mouse-resize-frame + frame (- pos-x last-x) (- last-y pos-y) nil t)) + ((eq part 'bottom-left) + (mouse-resize-frame + frame (- last-x pos-x) (- pos-y last-y) t)) + ((eq part 'bottom-right) + (mouse-resize-frame + frame (- pos-x last-x) (- pos-y last-y))) + ((eq part 'move) + (let* ((old-position (frame-position frame)) + (old-left (car old-position)) + (old-top (cdr old-position)) + (left (+ old-left (- pos-x last-x))) + (top (+ old-top (- pos-y last-y))) + right bottom + ;; `snap-width' (maybe also a yet to be provided + ;; `snap-height') could become floats to handle + ;; proportionality wrt PARENT. We don't do any + ;; checks on this parameter so far. + (snap-width (frame-parameter frame 'snap-width))) + ;; Docking and constraining. + (when (and (numberp snap-width) parent-edges) + (cond + ;; Docking at the left parent edge. + ((< pos-x last-x) + (cond + ((and (> left parent-left) + (<= (- left parent-left) snap-width)) + ;; Snap when the mouse moved leftward and + ;; FRAME's left edge would end up within + ;; `snap-width' pixels from PARENT's left edge. + (setq snap-x pos-x) + (setq left parent-left)) + ((and (<= left parent-left) + (<= (- parent-left left) snap-width) + snap-x (<= (- snap-x pos-x) snap-width)) + ;; Stay snapped when the mouse moved leftward + ;; but not more than `snap-width' pixels from + ;; the time FRAME snapped. + (setq left parent-left)) + (t + ;; Unsnap when the mouse moved more than + ;; `snap-width' pixels leftward from the time + ;; FRAME snapped. + (setq snap-x nil)))) + ((> pos-x last-x) + (setq right (+ left width)) + (cond + ((and (< right parent-right) + (<= (- parent-right right) snap-width)) + ;; Snap when the mouse moved rightward and + ;; FRAME's right edge would end up within + ;; `snap-width' pixels from PARENT's right edge. + (setq snap-x pos-x) + (setq left (- parent-right width))) + ((and (>= right parent-right) + (<= (- right parent-right) snap-width) + snap-x (<= (- pos-x snap-x) snap-width)) + ;; Stay snapped when the mouse moved rightward + ;; but not more more than `snap-width' pixels + ;; from the time FRAME snapped. + (setq left (- parent-right width))) + (t + ;; Unsnap when the mouse moved rightward more + ;; than `snap-width' pixels from the time FRAME + ;; snapped. + (setq snap-x nil))))) + + (cond + ((< pos-y last-y) + (cond + ((and (> top parent-top) + (<= (- top parent-top) snap-width)) + ;; Snap when the mouse moved upward and FRAME's + ;; top edge would end up within `snap-width' + ;; pixels from PARENT's top edge. + (setq snap-y pos-y) + (setq top parent-top)) + ((and (<= top parent-top) + (<= (- parent-top top) snap-width) + snap-y (<= (- snap-y pos-y) snap-width)) + ;; Stay snapped when the mouse moved upward but + ;; not more more than `snap-width' pixels from + ;; the time FRAME snapped. + (setq top parent-top)) + (t + ;; Unsnap when the mouse moved upward more than + ;; `snap-width' pixels from the time FRAME + ;; snapped. + (setq snap-y nil)))) + ((> pos-y last-y) + (setq bottom (+ top height)) + (cond + ((and (< bottom parent-bottom) + (<= (- parent-bottom bottom) snap-width)) + ;; Snap when the mouse moved downward and + ;; FRAME's bottom edge would end up within + ;; `snap-width' pixels from PARENT's bottom + ;; edge. + (setq snap-y pos-y) + (setq top (- parent-bottom height))) + ((and (>= bottom parent-bottom) + (<= (- bottom parent-bottom) snap-width) + snap-y (<= (- pos-y snap-y) snap-width)) + ;; Stay snapped when the mouse moved downward + ;; but not more more than `snap-width' pixels + ;; from the time FRAME snapped. + (setq top (- parent-bottom height))) + (t + ;; Unsnap when the mouse moved downward more + ;; than `snap-width' pixels from the time FRAME + ;; snapped. + (setq snap-y nil)))))) + + ;; If requested, constrain FRAME's draggable areas to + ;; PARENT's edges. The `top-visible' parameter should + ;; be set when FRAME has a draggable header-line. If + ;; set to a number, it ascertains that the top of + ;; FRAME is always constrained to the top of PARENT + ;; and that at least as many pixels of FRAME as + ;; specified by that number are visible on each of the + ;; three remaining sides of PARENT. + ;; + ;; The `bottom-visible' parameter should be set when + ;; FRAME has a draggable mode-line. If set to a + ;; number, it ascertains that the bottom of FRAME is + ;; always constrained to the bottom of PARENT and that + ;; at least as many pixels of FRAME as specified by + ;; that number are visible on each of the three + ;; remaining sides of PARENT. + (let ((par (frame-parameter frame 'top-visible)) + bottom-visible) + (unless par + (setq par (frame-parameter frame 'bottom-visible)) + (setq bottom-visible t)) + (when (and (numberp par) parent-edges) + (setq left + (max (min (- parent-right par) left) + (+ (- parent-left width) par))) + (setq top + (if bottom-visible + (min (max top (- parent-top (- height par))) + (- parent-bottom height)) + (min (max top parent-top) + (- parent-bottom par)))))) + + ;; Use `modify-frame-parameters' since `left' and + ;; `top' may want to move FRAME out of its PARENT. + (modify-frame-parameters + frame + `((left . (+ ,left)) (top . (+ ,top))))))) + (setq last-x pos-x) + (setq last-y pos-y)))) + (old-track-mouse track-mouse)) + ;; Start tracking. The special value 'dragging' signals the + ;; display engine to freeze the mouse pointer shape for as long + ;; as we drag. + (setq track-mouse 'dragging) + ;; Loop reading events and sampling the position of the mouse. + (setq exitfun + (set-transient-map + (let ((map (make-sparse-keymap))) + (define-key map [switch-frame] #'ignore) + (define-key map [select-window] #'ignore) + (define-key map [scroll-bar-movement] #'ignore) + (define-key map [mouse-movement] move) + ;; Swallow drag-mouse-1 events to avoid selecting some other window. + (define-key map [drag-mouse-1] + (lambda () (interactive) (funcall exitfun))) + ;; Some of the events will of course end up looked up + ;; with a mode-line, header-line or vertical-line prefix ... + (define-key map [mode-line] map) + (define-key map [header-line] map) + (define-key map [vertical-line] map) + ;; ... and some maybe even with a right- or bottom-divider + ;; prefix. + (define-key map [right-divider] map) + (define-key map [bottom-divider] map) + map) + t (lambda () (setq track-mouse old-track-mouse)))))) + +(defun mouse-drag-left-edge (start-event) + "Drag left edge of a frame with the mouse. +START-EVENT is the starting mouse event of the drag action." + (interactive "e") + (mouse-drag-frame start-event 'left)) + +(defun mouse-drag-top-left-corner (start-event) + "Drag top left corner of a frame with the mouse. +START-EVENT is the starting mouse event of the drag action." + (interactive "e") + (mouse-drag-frame start-event 'top-left)) + +(defun mouse-drag-top-edge (start-event) + "Drag top edge of a frame with the mouse. +START-EVENT is the starting mouse event of the drag action." + (interactive "e") + (mouse-drag-frame start-event 'top)) + +(defun mouse-drag-top-right-corner (start-event) + "Drag top right corner of a frame with the mouse. +START-EVENT is the starting mouse event of the drag action." + (interactive "e") + (mouse-drag-frame start-event 'top-right)) + +(defun mouse-drag-right-edge (start-event) + "Drag right edge of a frame with the mouse. +START-EVENT is the starting mouse event of the drag action." + (interactive "e") + (mouse-drag-frame start-event 'right)) + +(defun mouse-drag-bottom-right-corner (start-event) + "Drag bottom right corner of a frame with the mouse. +START-EVENT is the starting mouse event of the drag action." + (interactive "e") + (mouse-drag-frame start-event 'bottom-right)) + +(defun mouse-drag-bottom-edge (start-event) + "Drag bottom edge of a frame with the mouse. +START-EVENT is the starting mouse event of the drag action." + (interactive "e") + (mouse-drag-frame start-event 'bottom)) + +(defun mouse-drag-bottom-left-corner (start-event) + "Drag bottom left corner of a frame with the mouse. +START-EVENT is the starting mouse event of the drag action." + (interactive "e") + (mouse-drag-frame start-event 'bottom-left)) + (defcustom mouse-select-region-move-to-beginning nil "Effect of selecting a region extending backward from double click. Nil means keep point at the position clicked (region end); @@ -1545,6 +1916,34 @@ CLICK position, kill the secondary selection." (> (length str) 0) (gui-set-selection 'SECONDARY str)))) +(defun secondary-selection-exist-p () + "Return non-nil if the secondary selection exists in the current buffer." + (memq mouse-secondary-overlay (overlays-in (point-min) (point-max)))) + +(defun secondary-selection-to-region () + "Set beginning and end of the region to those of the secondary selection. +This puts mark and point at the beginning and the end of the +secondary selection, respectively. This works when the secondary +selection exists and the region does not exist in current buffer; +the secondary selection will be deleted afterward. +If the region is active, or the secondary selection doesn't exist, +this function does nothing." + (when (and (not (region-active-p)) + (secondary-selection-exist-p)) + (let ((beg (overlay-start mouse-secondary-overlay)) + (end (overlay-end mouse-secondary-overlay))) + (push-mark beg t t) + (goto-char end)) + ;; Delete the secondary selection on current buffer. + (delete-overlay mouse-secondary-overlay))) + +(defun secondary-selection-from-region () + "Set beginning and end of the secondary selection to those of the region. +When there is no region, this function does nothing." + (when (region-active-p) ; Create the secondary selection from the region. + (delete-overlay mouse-secondary-overlay) ; Delete the secondary selection even on a different buffer. + (move-overlay mouse-secondary-overlay (region-beginning) (region-end)))) + (defcustom mouse-buffer-menu-maxlen 20 "Number of buffers in one pane (submenu) of the buffer menu. @@ -1950,7 +2349,15 @@ choose a font." If the value is a modifier, such as `control' or `shift' or `meta', then if that modifier key is pressed when dropping the region, region text is copied instead of being cut." - :type 'symbol + :type `(choice + (const :tag "Disable dragging the region" nil) + ,@(mapcar + (lambda (modifier) + `(const :tag ,(format "Enable, but copy with the %s modifier" + modifier) + modifier)) + '(alt super hyper shift control meta)) + (other :tag "Enable dragging the region" t)) :version "26.1" :group 'mouse) @@ -1973,7 +2380,9 @@ is copied instead of being cut." ;; When event was click instead of drag, skip loop (while (progn (setq event (read-event)) - (mouse-movement-p event)) + (or (mouse-movement-p event) + ;; Handle `mouse-autoselect-window'. + (eq (car-safe event) 'select-window))) (unless value-selection ; initialization (delete-overlay mouse-secondary-overlay) (setq value-selection (buffer-substring start end)) @@ -2078,6 +2487,22 @@ is copied instead of being cut." (global-set-key [bottom-divider down-mouse-1] 'mouse-drag-mode-line) (global-set-key [bottom-divider mouse-1] 'ignore) (global-set-key [bottom-divider C-mouse-2] 'mouse-split-window-horizontally) +(global-set-key [left-edge down-mouse-1] 'mouse-drag-left-edge) +(global-set-key [left-edge mouse-1] 'ignore) +(global-set-key [top-left-corner down-mouse-1] 'mouse-drag-top-left-corner) +(global-set-key [top-left-corner mouse-1] 'ignore) +(global-set-key [top-edge down-mouse-1] 'mouse-drag-top-edge) +(global-set-key [top-edge mouse-1] 'ignore) +(global-set-key [top-right-corner down-mouse-1] 'mouse-drag-top-right-corner) +(global-set-key [top-right-corner mouse-1] 'ignore) +(global-set-key [right-edge down-mouse-1] 'mouse-drag-right-edge) +(global-set-key [right-edge mouse-1] 'ignore) +(global-set-key [bottom-right-corner down-mouse-1] 'mouse-drag-bottom-right-corner) +(global-set-key [bottom-right-corner mouse-1] 'ignore) +(global-set-key [bottom-edge down-mouse-1] 'mouse-drag-bottom-edge) +(global-set-key [bottom-edge mouse-1] 'ignore) +(global-set-key [bottom-left-corner down-mouse-1] 'mouse-drag-bottom-left-corner) +(global-set-key [bottom-left-corner mouse-1] 'ignore) (provide 'mouse) diff --git a/lisp/mpc.el b/lisp/mpc.el index cce752739be..98f4a031834 100644 --- a/lisp/mpc.el +++ b/lisp/mpc.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -1029,12 +1029,12 @@ If PLAYLIST is t or nil or missing, use the main playlist." (let ((dir (file-name-directory (cdr (assq 'file info))))) ;; (debug) (push `(equal ',dir (file-name-directory (cdr (assq 'file info)))) pred) - (if-let ((covers '(".folder.png" "cover.jpg" "folder.jpg")) - (cover (cl-loop for file in (directory-files (mpc-file-local-copy dir)) - if (member (downcase file) covers) - return (concat dir file))) - (file (with-demoted-errors "MPC: %s" - (mpc-file-local-copy cover)))) + (if-let* ((covers '(".folder.png" "cover.jpg" "folder.jpg")) + (cover (cl-loop for file in (directory-files (mpc-file-local-copy dir)) + if (member (downcase file) covers) + return (concat dir file))) + (file (with-demoted-errors "MPC: %s" + (mpc-file-local-copy cover)))) (let (image) (if (null size) (setq image (create-image file)) (let ((tempfile (make-temp-file "mpc" nil ".jpg"))) @@ -2403,10 +2403,38 @@ This is used so that they can be compared with `eq', which is needed for (interactive) (mpc-cmd-pause "0")) +(defun mpc-read-seek (prompt) + "Read a seek time. +Returns a string suitable for MPD \"seekcur\" protocol command." + (let* ((str (read-from-minibuffer prompt nil nil nil nil nil t)) + (seconds "\\(?1:[[:digit:]]+\\(?:\\.[[:digit:]]*\\)?\\)") + (minsec (concat "\\(?2:[[:digit:]]+\\):" seconds "?")) + (hrminsec (concat "\\(?3:[[:digit:]]+\\):\\(?:" minsec "?\\|:\\)")) + time sign) + (setq str (string-trim str)) + (when (memq (string-to-char str) '(?+ ?-)) + (setq sign (string (string-to-char str))) + (setq str (substring str 1))) + (setq time + ;; `string-to-number' returns 0 on failure + (cond + ((string-match (concat "^" hrminsec "$") str) + (+ (* 3600 (string-to-number (match-string 3 str))) + (* 60 (string-to-number (or (match-string 2 str) ""))) + (string-to-number (or (match-string 1 str) "")))) + ((string-match (concat "^" minsec "$") str) + (+ (* 60 (string-to-number (match-string 2 str))) + (string-to-number (match-string 1 str)))) + ((string-match (concat "^" seconds "$") str) + (string-to-number (match-string 1 str))) + (t (user-error "Invalid time")))) + (setq time (number-to-string time)) + (if (null sign) time (concat sign time)))) + (defun mpc-seek-current (pos) "Seek within current track." (interactive - (list (read-string "Position to go ([+-]seconds): "))) + (list (mpc-read-seek "Position to go ([+-][[H:]M:]seconds): "))) (mpc-cmd-seekcur pos)) (defun mpc-toggle-play () diff --git a/lisp/msb.el b/lisp/msb.el index 7b48af729e1..c2ab2f5e9b8 100644 --- a/lisp/msb.el +++ b/lisp/msb.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/mwheel.el b/lisp/mwheel.el index 1428e5f4d01..0c0dcb3beb1 100644 --- a/lisp/mwheel.el +++ b/lisp/mwheel.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -232,6 +232,7 @@ non-Windows systems." ;; When the double-mouse-N comes in, a mouse-N has been executed already, ;; So by adding things up we get a squaring up (1, 3, 6, 10, 15, ...). (setq amt (* amt (event-click-count event)))) + (when (numberp amt) (setq amt (* amt (event-line-count event)))) (unwind-protect (let ((button (mwheel-event-button event))) (cond ((eq button mouse-wheel-down-event) diff --git a/lisp/net/ange-ftp.el b/lisp/net/ange-ftp.el index ecb60e5a4f4..cf65e10e510 100644 --- a/lisp/net/ange-ftp.el +++ b/lisp/net/ange-ftp.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -530,33 +530,8 @@ ;; to fix its files hashtable. A cookie to anyone who can think of a ;; fast, sure-fire way to recognize ULTRIX over ftp. -;; If you find any bugs or problems with this package, PLEASE either e-mail -;; the above author, or send a message to the ange-ftp-lovers mailing list -;; below. Ideas and constructive comments are especially welcome. - -;; ange-ftp-lovers: -;; -;; ange-ftp has its own mailing list modestly called ange-ftp-lovers. All -;; users of ange-ftp are welcome to subscribe (see below) and to discuss -;; aspects of ange-ftp. New versions of ange-ftp are posted periodically to -;; the mailing list. - -;; To [un]subscribe to ange-ftp-lovers, or to report mailer problems with the -;; list, please mail one of the following addresses: -;; -;; ange-ftp-lovers-request@hplb.hpl.hp.com -;; -;; Please don't forget the -request part. -;; -;; For mail to be posted directly to ange-ftp-lovers, send to one of the -;; following addresses: -;; -;; ange-ftp-lovers@hplb.hpl.hp.com -;; -;; Alternatively, there is a mailing list that only gets announcements of new -;; ange-ftp releases. This is called ange-ftp-lovers-announce, and can be -;; subscribed to by e-mailing to the -request address as above. Please make -;; it clear in the request which mailing list you wish to join. +;; If you find any bugs or problems with this package, PLEASE report a +;; bug to the Emacs maintainers via M-x report-emacs-bug. ;; ----------------------------------------------------------- ;; Technical information on this package: @@ -714,10 +689,17 @@ parenthesized expressions in REGEXP for the components (in that order)." ;; authentication methods (typically) at connection establishment. Non ;; security-aware FTP servers should respond to this with a 500 code, ;; which we ignore. + +;; Further messages are needed to support ftp-ssl. (defcustom ange-ftp-skip-msgs (concat "^200 \\(PORT\\|Port\\) \\|^331 \\|^150 \\|^350 \\|^[0-9]+ bytes \\|" "^Connected \\|^$\\|^Remote system\\|^Using\\|^ \\|Password:\\|" "^Data connection \\|" + "^200 PBSZ\\|" "^200 Protection set to Private\\|" + "^234 AUTH TLS successful\\|" + "^SSL not available\\|" + "^\\[SSL Cipher .+\\]\\|" + "^\\[Encrypted data transfer\\.\\]\\|" "^local:\\|^Trying\\|^125 \\|^550-\\|^221 .*oodbye\\|" "^500 .*AUTH\\|^KERBEROS\\|" "^500 This security scheme is not implemented\\|" @@ -727,7 +709,7 @@ parenthesized expressions in REGEXP for the components (in that order)." "^22[789] .*[Pp]assive\\|^200 EPRT\\|^500 .*EPRT\\|^500 .*EPSV") "Regular expression matching FTP messages that can be ignored." :group 'ange-ftp - :version "24.4" ; add EPSV + :version "26.1" :type 'regexp) (defcustom ange-ftp-fatal-msgs @@ -3223,8 +3205,12 @@ system TYPE.") (defun ange-ftp-binary-file (file) (string-match-p ange-ftp-binary-file-name-regexp file)) -(defun ange-ftp-write-region (start end filename &optional append visit) +(defun ange-ftp-write-region + (start end filename &optional append visit _lockname mustbenew) (setq filename (expand-file-name filename)) + (when mustbenew + (ange-ftp-barf-or-query-if-file-exists + filename "overwrite" (not (eq mustbenew 'excl)))) (let ((parsed (ange-ftp-ftp-name filename))) (if parsed (let* ((host (nth 0 parsed)) @@ -3493,7 +3479,7 @@ system TYPE.") (f2-mt (nth 5 (file-attributes f2)))) (cond ((null f1-mt) nil) ((null f2-mt) t) - (t (> (float-time f1-mt) (float-time f2-mt))))) + (t (time-less-p f2-mt f1-mt)))) (ange-ftp-real-file-newer-than-file-p f1 f2)))) (defun ange-ftp-file-writable-p (file) @@ -3575,7 +3561,7 @@ Value is (0 0) if the modification time cannot be determined." (let ((file-mdtm (ange-ftp-file-modtime name)) (buf-mdtm (with-current-buffer buf (visited-file-modtime)))) (or (zerop (car file-mdtm)) - (<= (float-time file-mdtm) (float-time buf-mdtm)))) + (not (time-less-p buf-mdtm file-mdtm)))) (ange-ftp-real-verify-visited-file-modtime buf)))) (defun ange-ftp-file-size (file &optional ascii-mode) @@ -3867,12 +3853,12 @@ E.g., (unless okay-p (error "%s: %s" 'ange-ftp-copy-files-async line)) (if files (let* ((ff (car files)) - (from-file (nth 0 ff)) - (to-file (nth 1 ff)) - (ok-if-exists (nth 2 ff)) - (keep-date (nth 3 ff))) + (from-file (nth 0 ff)) + (to-file (nth 1 ff)) + (ok-if-already-exists (nth 2 ff)) + (keep-date (nth 3 ff))) (ange-ftp-copy-file-internal - from-file to-file ok-if-exists keep-date + from-file to-file ok-if-already-exists keep-date (and verbose-p (format "%s --> %s" from-file to-file)) (list 'ange-ftp-copy-files-async verbose-p (cdr files)) t)) diff --git a/lisp/net/browse-url.el b/lisp/net/browse-url.el index 20ae072f652..b104148d548 100644 --- a/lisp/net/browse-url.el +++ b/lisp/net/browse-url.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -1317,7 +1317,7 @@ used instead of `browse-url-new-window-flag'." (if (file-exists-p (setq pidfile (format "/tmp/Mosaic.%d" pid))) (delete-file pidfile)) - ;; http://debbugs.gnu.org/17428. Use O_EXCL. + ;; https://debbugs.gnu.org/17428. Use O_EXCL. (write-region nil nil pidfile nil 'silent nil 'excl))) ;; Send signal SIGUSR to Mosaic (message "Signaling Mosaic...") diff --git a/lisp/net/dbus.el b/lisp/net/dbus.el index d740829f99c..e79e326dbe2 100644 --- a/lisp/net/dbus.el +++ b/lisp/net/dbus.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/dig.el b/lisp/net/dig.el index 7e733675b63..ee98e5c444d 100644 --- a/lisp/net/dig.el +++ b/lisp/net/dig.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/dns.el b/lisp/net/dns.el index 8615813e074..b4500bd4323 100644 --- a/lisp/net/dns.el +++ b/lisp/net/dns.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/eudc-bob.el b/lisp/net/eudc-bob.el index 661ef51e60e..899cdb00a49 100644 --- a/lisp/net/eudc-bob.el +++ b/lisp/net/eudc-bob.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/eudc-export.el b/lisp/net/eudc-export.el index 222673247b0..1077cc4e8bf 100644 --- a/lisp/net/eudc-export.el +++ b/lisp/net/eudc-export.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/eudc-hotlist.el b/lisp/net/eudc-hotlist.el index 083fd7fe7e4..05f682d2675 100644 --- a/lisp/net/eudc-hotlist.el +++ b/lisp/net/eudc-hotlist.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/eudc-vars.el b/lisp/net/eudc-vars.el index 79d6f2ebc69..6a831b1265e 100644 --- a/lisp/net/eudc-vars.el +++ b/lisp/net/eudc-vars.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/eudc.el b/lisp/net/eudc.el index 644df7ab786..b19a838e640 100644 --- a/lisp/net/eudc.el +++ b/lisp/net/eudc.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; This package provides a common interface to query directory servers using diff --git a/lisp/net/eudcb-bbdb.el b/lisp/net/eudcb-bbdb.el index bfca103bdb0..8dff028b9f1 100644 --- a/lisp/net/eudcb-bbdb.el +++ b/lisp/net/eudcb-bbdb.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; This library provides an interface to use BBDB as a backend of diff --git a/lisp/net/eudcb-ldap.el b/lisp/net/eudcb-ldap.el index e1900e71ff2..bdc72ef6216 100644 --- a/lisp/net/eudcb-ldap.el +++ b/lisp/net/eudcb-ldap.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; This library provides specific LDAP protocol support for the diff --git a/lisp/net/eudcb-mab.el b/lisp/net/eudcb-mab.el index 43384e2d6fd..2653cfab697 100644 --- a/lisp/net/eudcb-mab.el +++ b/lisp/net/eudcb-mab.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; This library provides an interface to use the Mac's AddressBook, diff --git a/lisp/net/eww.el b/lisp/net/eww.el index fe316579142..bff592c3fe2 100644 --- a/lisp/net/eww.el +++ b/lisp/net/eww.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -297,7 +297,8 @@ word(s) will be searched for via `eww-search-prefix'." (when (string= (url-filename (url-generic-parse-url url)) "") (setq url (concat url "/")))) (setq url (concat eww-search-prefix - (replace-regexp-in-string " " "+" url)))))) + (mapconcat + #'url-hexify-string (split-string url) "+")))))) url) ;;;###autoload (defalias 'browse-web 'eww) @@ -312,11 +313,19 @@ word(s) will be searched for via `eww-search-prefix'." (expand-file-name file)))) ;;;###autoload -(defun eww-search-words (&optional beg end) +(defun eww-search-words () "Search the web for the text between BEG and END. -See the `eww-search-prefix' variable for the search engine used." - (interactive "r") - (eww (buffer-substring beg end))) +If region is active (and not whitespace), search the web for +the text between BEG and END. Else, prompt the user for a search +string. See the `eww-search-prefix' variable for the search +engine used." + (interactive) + (if (use-region-p) + (let ((region-string (buffer-substring (region-beginning) (region-end)))) + (if (not (string-match-p "\\`[ \n\t\r\v\f]*\\'" region-string)) + (eww region-string) + (call-interactively 'eww))) + (call-interactively 'eww))) (defun eww-open-in-new-buffer () "Fetch link at point in a new EWW buffer." @@ -512,7 +521,7 @@ Currently this means either text/html or application/xhtml+xml." (defun eww-tag-meta (dom) (when (and (cl-equalp (dom-attr dom 'http-equiv) "refresh") (< eww-redirect-level 5)) - (when-let (refresh (dom-attr dom 'content)) + (when-let* ((refresh (dom-attr dom 'content))) (when (or (string-match "^\\([0-9]+\\) *;.*url=\"\\([^\"]+\\)\"" refresh) (string-match "^\\([0-9]+\\) *;.*url='\\([^']+\\)'" refresh) (string-match "^\\([0-9]+\\) *;.*url=\\([^ ]+\\)" refresh)) @@ -1101,13 +1110,13 @@ just re-display the HTML already fetched." See URL `https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Input'.") (defun eww-process-text-input (beg end replace-length) - (when-let (pos (and (< (1+ end) (point-max)) - (> (1- end) (point-min)) - (cond - ((get-text-property (1+ end) 'eww-form) - (1+ end)) - ((get-text-property (1- end) 'eww-form) - (1- end))))) + (when-let* ((pos (and (< (1+ end) (point-max)) + (> (1- end) (point-min)) + (cond + ((get-text-property (1+ end) 'eww-form) + (1+ end)) + ((get-text-property (1- end) 'eww-form) + (1- end)))))) (let* ((form (get-text-property pos 'eww-form)) (properties (text-properties-at pos)) (buffer-undo-list t) @@ -1790,8 +1799,8 @@ If CHARSET is nil then use UTF-8." (setq eww-data (list :title "")) ;; Don't let the history grow infinitely. We store quite a lot of ;; data per page. - (when-let (tail (and eww-history-limit - (nthcdr eww-history-limit eww-history))) + (when-let* ((tail (and eww-history-limit + (nthcdr eww-history-limit eww-history)))) (setcdr tail nil))) (defvar eww-current-buffer) diff --git a/lisp/net/gnutls.el b/lisp/net/gnutls.el index 5db87329c36..d4943a33031 100644 --- a/lisp/net/gnutls.el +++ b/lisp/net/gnutls.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/goto-addr.el b/lisp/net/goto-addr.el index 2c2274d41ba..6356b9047fb 100644 --- a/lisp/net/goto-addr.el +++ b/lisp/net/goto-addr.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/hmac-def.el b/lisp/net/hmac-def.el index 246683444f4..24246af02e7 100644 --- a/lisp/net/hmac-def.el +++ b/lisp/net/hmac-def.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/hmac-md5.el b/lisp/net/hmac-md5.el index 53fa153a1eb..b4ef54038ee 100644 --- a/lisp/net/hmac-md5.el +++ b/lisp/net/hmac-md5.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/imap.el b/lisp/net/imap.el index 7b293921a43..34206ef84cc 100644 --- a/lisp/net/imap.el +++ b/lisp/net/imap.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/ldap.el b/lisp/net/ldap.el index d5303387663..22873ba2334 100644 --- a/lisp/net/ldap.el +++ b/lisp/net/ldap.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/mailcap.el b/lisp/net/mailcap.el index 89f6c91156b..b4b38707c89 100644 --- a/lisp/net/mailcap.el +++ b/lisp/net/mailcap.el @@ -1,4 +1,4 @@ -;;; mailcap.el --- MIME media types configuration +;;; mailcap.el --- MIME media types configuration -*- lexical-binding: t -*- ;; Copyright (C) 1998-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -29,7 +29,6 @@ ;;; Code: -(eval-when-compile (require 'cl-lib)) (autoload 'mail-header-parse-content-type "mail-parse") (defgroup mailcap nil @@ -70,11 +69,10 @@ (defun mailcap--set-user-mime-data (sym val) (let (res) - (dolist (entry val) - (push `((viewer . ,(car entry)) - (type . ,(cadr entry)) - ,@(when (cl-caddr entry) - `((test . ,(cl-caddr entry))))) + (pcase-dolist (`(,viewer ,type ,test) val) + (push `((viewer . ,viewer) + (type . ,type) + ,@(when test `((test . ,test)))) res)) (set-default sym (nreverse res)))) @@ -121,12 +119,6 @@ is consulted." (viewer . "gnumeric %s") (test . (getenv "DISPLAY")) (type . "application/vnd.ms-excel")) - ("x-x509-ca-cert" - (viewer . ssl-view-site-cert) - (type . "application/x-x509-ca-cert")) - ("x-x509-user-cert" - (viewer . ssl-view-user-cert) - (type . "application/x-x509-user-cert")) ("octet-stream" (viewer . mailcap-save-binary-file) (non-viewer . t) @@ -175,11 +167,11 @@ is consulted." ("pdf" (viewer . pdf-view-mode) (type . "application/pdf") - (test . (eq window-system 'x))) + (test . window-system)) ("pdf" (viewer . doc-view-mode) (type . "application/pdf") - (test . (eq window-system 'x))) + (test . window-system)) ("pdf" (viewer . "gv -safer %s") (type . "application/pdf") @@ -331,7 +323,7 @@ means the viewer is always valid. If it is a Lisp function, it is called with a list of items from any extra fields from the Content-Type header as argument to return a boolean value for the validity. Otherwise, if it is a non-function Lisp symbol or list -whose car is a symbol, it is `eval'led to yield the validity. If it +whose car is a symbol, it is `eval'uated to yield the validity. If it is a string or list of strings, it represents a shell command to run to return a true or false shell value for the validity.") (put 'mailcap-mime-data 'risky-local-variable t) @@ -434,9 +426,8 @@ MAILCAPS if set; otherwise (on Unix) use the path from RFC 1524, plus (if (stringp path) (split-string path path-separator t) path))) - (if (and (file-readable-p fname) - (file-regular-p fname)) - (mailcap-parse-mailcap fname))) + (when (and (file-readable-p fname) (file-regular-p fname)) + (mailcap-parse-mailcap fname))) (setq mailcap-parsed-p t))) (defun mailcap-parse-mailcap (fname) @@ -597,13 +588,12 @@ the test clause will be unchanged." "Return a list of possible viewers from MAJOR for minor type MINOR." (let ((exact '()) (wildcard '())) - (while major + (pcase-dolist (`(,type . ,attrs) major) (cond - ((equal (car (car major)) minor) - (push (cdr (car major)) exact)) - ((and minor (string-match (concat "^" (car (car major)) "$") minor)) - (push (cdr (car major)) wildcard))) - (setq major (cdr major))) + ((equal type minor) + (push attrs exact)) + ((and minor (string-match (concat "^" type "$") minor)) + (push attrs wildcard)))) (nconc exact wildcard))) (defun mailcap-unescape-mime-test (test type-info) @@ -801,10 +791,9 @@ If NO-DECODE is non-nil, don't decode STRING." (setq info (mapcar (lambda (a) (cons (symbol-name (car a)) (cdr a))) (cdr ctl))) - (while viewers - (if (mailcap-viewer-passes-test (car viewers) info) - (push (car viewers) passed)) - (setq viewers (cdr viewers))) + (dolist (entry viewers) + (when (mailcap-viewer-passes-test entry info) + (push entry passed))) (setq passed (sort passed 'mailcap-viewer-lessp)) (setq viewer (car passed)))) (when (and (stringp (cdr (assq 'viewer viewer))) @@ -971,8 +960,8 @@ If FORCE, re-parse even if already parsed." (dolist (fname (reverse (if (stringp path) (split-string path path-separator t) path))) - (if (and (file-readable-p fname)) - (mailcap-parse-mimetype-file fname))) + (when (file-readable-p fname) + (mailcap-parse-mimetype-file fname))) (setq mailcap-mimetypes-parsed-p t))) (defun mailcap-parse-mimetype-file (fname) @@ -980,7 +969,7 @@ If FORCE, re-parse even if already parsed." (let (type ; The MIME type for this line extns ; The extensions for this line save-pos ; Misc. saved buffer positions - ) + save-extn) (with-temp-buffer (insert-file-contents fname) (mailcap-replace-regexp "#.*" "") @@ -1000,15 +989,13 @@ If FORCE, re-parse even if already parsed." (skip-chars-forward " \t") (setq save-pos (point)) (skip-chars-forward "^ \t\n") - (setq extns (cons (buffer-substring save-pos (point)) extns))) - (while extns - (setq mailcap-mime-extensions - (cons - (cons (if (= (string-to-char (car extns)) ?.) - (car extns) - (concat "." (car extns))) type) - mailcap-mime-extensions) - extns (cdr extns))))))) + (setq save-extn (buffer-substring save-pos (point))) + (push (cons (if (= (string-to-char save-extn) ?.) + save-extn (concat "." save-extn)) + type) + extns)) + (setq mailcap-mime-extensions (append extns mailcap-mime-extensions) + extns nil))))) (defun mailcap-extension-to-mime (extn) "Return the MIME content type of the file extensions EXTN." @@ -1018,29 +1005,19 @@ If FORCE, re-parse even if already parsed." (setq extn (concat "." extn))) (cdr (assoc (downcase extn) mailcap-mime-extensions))) -;; Unused? -(defalias 'mailcap-command-p 'executable-find) - (defun mailcap-mime-types () "Return a list of MIME media types." (mailcap-parse-mimetypes) (delete-dups (nconc (mapcar 'cdr mailcap-mime-extensions) - (apply - 'nconc - (mapcar - (lambda (l) - (delq nil - (mapcar - (lambda (m) - (let ((type (cdr (assq 'type (cdr m))))) - (if (equal (cadr (split-string type "/")) - "*") - nil - type))) - (cdr l)))) - mailcap-mime-data))))) + (let (res type) + (dolist (data mailcap-mime-data) + (dolist (info (cdr data)) + (setq type (cdr (assq 'type (cdr info)))) + (unless (string-match-p "\\*" type) + (push type res)))) + (nreverse res))))) ;;; ;;; Useful supplementary functions @@ -1067,32 +1044,31 @@ If FORCE, re-parse even if already parsed." ;; Intersection of mime-infos from different mime-types; ;; or just the first MIME info for a single MIME type (if (cdr all-mime-info) - (delq nil (mapcar (lambda (mi1) - (unless (memq nil (mapcar - (lambda (mi2) - (member mi1 mi2)) - (cdr all-mime-info))) - mi1)) - (car all-mime-info))) - (car all-mime-info))) - (commands - ;; Command strings from `viewer' field of the MIME info - (delete-dups - (delq nil (mapcar - (lambda (mime-info) - (let ((command (cdr (assoc 'viewer mime-info)))) - (if (stringp command) - (replace-regexp-in-string - ;; Replace mailcap's `%s' placeholder - ;; with dired's `?' placeholder - "%s" "?" - (replace-regexp-in-string - ;; Remove the final filename placeholder - "[ \t\n]*\\('\\)?%s\\1?[ \t\n]*\\'" "" - command nil t) - nil t)))) - common-mime-info))))) - commands)) + (let (res) + (dolist (mi1 (car all-mime-info)) + (dolist (mi2 (cdr all-mime-info)) + (when (member mi1 mi2) + (push mi1 res)))) + (nreverse res)) + (car all-mime-info)))) + ;; Command strings from `viewer' field of the MIME info + (delete-dups + (let (res command) + (dolist (mime-info common-mime-info) + (setq command (cdr (assq 'viewer mime-info))) + (when (stringp command) + (push + (replace-regexp-in-string + ;; Replace mailcap's `%s' placeholder + ;; with dired's `?' placeholder + "%s" "?" + (replace-regexp-in-string + ;; Remove the final filename placeholder + "[ \t\n]*\\('\\)?%s\\1?[ \t\n]*\\'" "" + command nil t) + nil t) + res))) + (nreverse res))))) (defun mailcap-view-mime (type) "View the data in the current buffer that has MIME type TYPE. diff --git a/lisp/net/mairix.el b/lisp/net/mairix.el index 00806a178b3..5dd190c101a 100644 --- a/lisp/net/mairix.el +++ b/lisp/net/mairix.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/net-utils.el b/lisp/net/net-utils.el index 280c6674707..d15df6974b2 100644 --- a/lisp/net/net-utils.el +++ b/lisp/net/net-utils.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/netrc.el b/lisp/net/netrc.el index a30d9f6aad8..46a93ee76b3 100644 --- a/lisp/net/netrc.el +++ b/lisp/net/netrc.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/network-stream.el b/lisp/net/network-stream.el index bf60eee673c..7d8f996fd2d 100644 --- a/lisp/net/network-stream.el +++ b/lisp/net/network-stream.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/newst-backend.el b/lisp/net/newst-backend.el index f38c72a26b0..0b3881428e2 100644 --- a/lisp/net/newst-backend.el +++ b/lisp/net/newst-backend.el @@ -1,4 +1,4 @@ -;;; newst-backend.el --- Retrieval backend for newsticker. +;;; newst-backend.el --- Retrieval backend for newsticker -*- lexical-binding:t -*- ;; Copyright (C) 2003-2017 Free Software Foundation, Inc. @@ -23,7 +23,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; ====================================================================== @@ -72,13 +72,9 @@ considered to be running if the newsticker timer list is not empty." ("Debian Security Advisories - Long format" "http://www.debian.org/security/dsa-long.en.rdf") ("Emacs Wiki" - "http://www.emacswiki.org/cgi-bin/wiki.pl?action=rss" + "https://www.emacswiki.org/emacs?action=rss" nil 3600) - ("Freshmeat.net" - "http://freshmeat.net/index.atom") - ("Kuro5hin.org" - "http://www.kuro5hin.org/backend.rdf") ("LWN (Linux Weekly News)" "http://lwn.net/headlines/rss") ("NY Times: Technology" @@ -102,9 +98,7 @@ considered to be running if the newsticker timer list is not empty." ("Tagesschau (german)" "http://www.tagesschau.de/newsticker.rdf" nil - 1800) - ("Telepolis (german)" - "http://www.heise.de/tp/news.rdf")) + 1800)) "Default URL list in raw form. This list is fed into defcustom via `newsticker--splicer'.") @@ -392,12 +386,12 @@ This hook is run at the very end of `newsticker-stop'." (defcustom newsticker-new-item-functions nil "List of functions run after a new headline has been retrieved. -Each function is called with the following three arguments: -FEED the name of the corresponding news feed, -TITLE the title of the headline, -DESC the decoded description of the headline. +Each function is called with the following two arguments: +FEEDNAME the name of the corresponding news feed, +ITEM the decoded headline. -See `newsticker-download-images', and +See `newsticker-new-item-functions-sample', +`newsticker-download-images', and `newsticker-download-enclosures' for sample functions. Please note that these functions are called only once for a @@ -605,7 +599,7 @@ name/timer pair to `newsticker--retrieval-timer-list'." (cons feed-name timer)))))) ;;;###autoload -(defun newsticker-start (&optional do-not-complain-if-running) +(defun newsticker-start (&optional _do-not-complain-if-running) "Start the newsticker. Start the timers for display and retrieval. If the newsticker, i.e. the timers, are running already a warning message is printed unless @@ -641,9 +635,8 @@ if newsticker has been running." (when (fboundp 'newsticker-stop-ticker) ; silence compiler warnings (newsticker-stop-ticker)) (when (newsticker-running-p) - (mapc (lambda (name-and-timer) - (newsticker--stop-feed (car name-and-timer))) - newsticker--retrieval-timer-list) + (dolist (name-and-timer newsticker--retrieval-timer-list) + (newsticker--stop-feed (car name-and-timer))) (setq newsticker--retrieval-timer-list nil) (run-hooks 'newsticker-stop-hook) (message "Newsticker stopped!"))) @@ -653,9 +646,8 @@ if newsticker has been running." This does NOT start the retrieval timers." (interactive) ;; launch retrieval of news - (mapc (lambda (item) - (newsticker-get-news (car item))) - (append newsticker-url-list-defaults newsticker-url-list))) + (dolist (item (append newsticker-url-list-defaults newsticker-url-list)) + (newsticker-get-news (car item)))) (defun newsticker-save-item (feed item) "Save FEED ITEM." @@ -711,7 +703,7 @@ See `newsticker-get-news'." (let ((buffername (concat " *newsticker-funcall-" feed-name "*"))) (with-current-buffer (get-buffer-create buffername) (erase-buffer) - (insert (string-to-multibyte (funcall function feed-name))) + (newsticker--insert-bytes (funcall function feed-name)) (newsticker--sentinel-work nil t feed-name function (current-buffer))))) @@ -732,10 +724,10 @@ STATUS is the return status as delivered by `url-retrieve', and FEED-NAME is the name of the feed that the news were retrieved from." (let ((buf (get-buffer-create (concat " *newsticker-url-" feed-name "*"))) - (result (string-to-multibyte (buffer-string)))) + (result (buffer-string))) (set-buffer buf) (erase-buffer) - (insert result) + (newsticker--insert-bytes result) ;; remove MIME header (goto-char (point-min)) (search-forward "\n\n" nil t) @@ -1257,9 +1249,6 @@ For the RSS 0.91 specification see URL `http://backend.userland.com/rss091' or URL `http://my.netscape.com/publish/formats/rss-spec-0.91.html'." (newsticker--debug-msg "Parsing RSS 0.91 feed %s" name) (let* ((channelnode (car (xml-get-children topnode 'channel))) - (pub-date (newsticker--decode-rfc822-date - (car (xml-node-children - (car (xml-get-children channelnode 'pubDate)))))) is-new-feed has-new-items) (setq is-new-feed (newsticker--parse-generic-feed name time @@ -1295,7 +1284,7 @@ or URL `http://my.netscape.com/publish/formats/rss-spec-0.91.html'." (car (xml-node-children (car (xml-get-children node 'pubDate)))))) ;; guid-fn - (lambda (node) + (lambda (_node) nil) ;; extra-fn (lambda (node) @@ -1310,9 +1299,6 @@ same as in `newsticker--parse-atom-1.0'. For the RSS 0.92 specification see URL `http://backend.userland.com/rss092'." (newsticker--debug-msg "Parsing RSS 0.92 feed %s" name) (let* ((channelnode (car (xml-get-children topnode 'channel))) - (pub-date (newsticker--decode-rfc822-date - (car (xml-node-children - (car (xml-get-children channelnode 'pubDate)))))) is-new-feed has-new-items) (setq is-new-feed (newsticker--parse-generic-feed name time @@ -1348,7 +1334,7 @@ For the RSS 0.92 specification see URL `http://backend.userland.com/rss092'." (car (xml-node-children (car (xml-get-children node 'pubDate)))))) ;; guid-fn - (lambda (node) + (lambda (_node) nil) ;; extra-fn (lambda (node) @@ -1407,7 +1393,7 @@ For the RSS 1.0 specification see URL `http://web.resource.org/rss/1.0/spec'." (car (xml-node-children (car (xml-get-children node 'date))))))) ;; guid-fn - (lambda (node) + (lambda (_node) nil) ;; extra-fn (lambda (node) @@ -1488,7 +1474,6 @@ The arguments TITLE, DESC, LINK, and EXTRA-ELEMENTS give the feed's title, description, link, and extra elements resp." (let ((title (or title "[untitled]")) (link (or link "")) - (old-item nil) (position 0) (something-was-added nil)) ;; decode numeric entities @@ -1524,89 +1509,89 @@ The arguments TITLE-FN, DESC-FN, LINK-FN, TIME-FN, GUID-FN, and EXTRA-FN give functions for extracting title, description, link, time, guid, and extra-elements resp. They are called with one argument, which is one of the items in ITEMLIST." - (let (title desc link - (old-item nil) - (position 0) + (let ((position 0) (something-was-added nil)) ;; gather all items for this feed - (mapc (lambda (node) - (setq position (1+ position)) - (setq title (or (funcall title-fn node) "[untitled]")) - (setq desc (funcall desc-fn node)) - (setq link (or (funcall link-fn node) "")) - (setq time (or (funcall time-fn node) time)) - ;; It happened that the title or description - ;; contained evil HTML code that confused the - ;; xml parser. Therefore: - (unless (stringp title) - (setq title (prin1-to-string title))) - (unless (or (stringp desc) (not desc)) - (setq desc (prin1-to-string desc))) - ;; ignore items with empty title AND empty desc - (when (or (> (length title) 0) - (> (length desc) 0)) - ;; decode numeric entities - (setq title (xml-substitute-numeric-entities title)) - (when desc - (setq desc (xml-substitute-numeric-entities desc))) - (setq link (xml-substitute-numeric-entities link)) - ;; remove whitespace from title, desc, and link - (setq title (newsticker--remove-whitespace title)) - (setq desc (newsticker--remove-whitespace desc)) - (setq link (newsticker--remove-whitespace link)) - ;; add data to cache - ;; do we have this item already? - (let* ((guid (funcall guid-fn node))) - ;;(message "guid=%s" guid) - (setq old-item - (newsticker--cache-contains newsticker--cache - (intern name) title - desc link nil guid))) - ;; add this item, or mark it as old, or do nothing - (let ((age1 'new) - (age2 'old) - (item-new-p nil)) - (if old-item - (let ((prev-age (newsticker--age old-item))) - (unless newsticker-automatically-mark-items-as-old - ;; Some feeds deliver items multiply, the - ;; first time we find an 'obsolete-old one in - ;; the cache, the following times we find an - ;; 'old one - (if (memq prev-age '(obsolete-old old)) - (setq age2 'old) - (setq age2 'new))) - (if (eq prev-age 'immortal) - (setq age2 'immortal)) - (setq time (newsticker--time old-item))) - ;; item was not there - (setq item-new-p t) - (setq something-was-added t)) - (let ((extra-elements-with-guid (funcall extra-fn node))) - (unless (assoc 'guid extra-elements-with-guid) - (setq extra-elements-with-guid - (cons `(guid nil ,(funcall guid-fn node)) - extra-elements-with-guid))) - (setq newsticker--cache - (newsticker--cache-add - newsticker--cache (intern name) title desc link - time age1 position extra-elements-with-guid - time age2))) - (when item-new-p - (let ((item (newsticker--cache-contains - newsticker--cache (intern name) title - desc link nil))) - (if newsticker-auto-mark-filter-list - (newsticker--run-auto-mark-filter name item)) - (run-hook-with-args - 'newsticker-new-item-functions name item)))))) - itemlist) + (dolist (node itemlist) + (setq position (1+ position)) + (let ((title (or (funcall title-fn node) "[untitled]")) + (desc (funcall desc-fn node)) + (link (or (funcall link-fn node) ""))) + (setq time (or (funcall time-fn node) time)) + ;; It happened that the title or description + ;; contained evil HTML code that confused the + ;; xml parser. Therefore: + (unless (stringp title) + (setq title (prin1-to-string title))) + (unless (or (stringp desc) (not desc)) + (setq desc (prin1-to-string desc))) + ;; ignore items with empty title AND empty desc + (when (or (> (length title) 0) + (> (length desc) 0)) + ;; decode numeric entities + (setq title (xml-substitute-numeric-entities title)) + (when desc + (setq desc (xml-substitute-numeric-entities desc))) + (setq link (xml-substitute-numeric-entities link)) + ;; remove whitespace from title, desc, and link + (setq title (newsticker--remove-whitespace title)) + (setq desc (newsticker--remove-whitespace desc)) + (setq link (newsticker--remove-whitespace link)) + ;; add data to cache + ;; do we have this item already? + (let ((old-item + (let* ((guid (funcall guid-fn node))) + ;;(message "guid=%s" guid) + (newsticker--cache-contains newsticker--cache + (intern name) title + desc link nil guid))) + (age1 'new) + (age2 'old) + (item-new-p nil)) + ;; Add this item, or mark it as old, or do nothing + (if old-item + (let ((prev-age (newsticker--age old-item))) + (unless newsticker-automatically-mark-items-as-old + ;; Some feeds deliver items multiply, the + ;; first time we find an 'obsolete-old one in + ;; the cache, the following times we find an + ;; 'old one + (if (memq prev-age '(obsolete-old old)) + (setq age2 'old) + (setq age2 'new))) + (if (eq prev-age 'immortal) + (setq age2 'immortal)) + (setq time (newsticker--time old-item))) + ;; item was not there + (setq item-new-p t) + (setq something-was-added t)) + (let ((extra-elements-with-guid (funcall extra-fn node))) + (unless (assoc 'guid extra-elements-with-guid) + (setq extra-elements-with-guid + (cons `(guid nil ,(funcall guid-fn node)) + extra-elements-with-guid))) + (setq newsticker--cache + (newsticker--cache-add + newsticker--cache (intern name) title desc link + time age1 position extra-elements-with-guid + time age2))) + (when item-new-p + (let ((item (newsticker--cache-contains + newsticker--cache (intern name) title + desc link nil))) + (if newsticker-auto-mark-filter-list + (newsticker--run-auto-mark-filter name item)) + (run-hook-with-args + 'newsticker-new-item-functions name item))))))) something-was-added)) ;; ====================================================================== ;;; Misc ;; ====================================================================== +(defun newsticker--insert-bytes (bytes) + (insert (decode-coding-string bytes 'binary))) + (defun newsticker--remove-whitespace (string) "Remove leading and trailing whitespace from STRING." ;; we must have ...+ but not ...* in the regexps otherwise xemacs loops @@ -1761,12 +1746,11 @@ Sat, 07 Sep 2002 00:00:01 GMT (setq minute (+ minute offset-minute))))) (condition-case error-data (let ((i 1)) - (mapc (lambda (m) - (if (string= month-name m) - (setq month i)) - (setq i (1+ i))) - '("Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" - "Sep" "Oct" "Nov" "Dec")) + (dolist (m '("Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" + "Sep" "Oct" "Nov" "Dec")) + (if (string= month-name m) + (setq month i)) + (setq i (1+ i))) (encode-time second minute hour day month year t)) (error (message "Cannot decode \"%s\": %s %s" rfc822-string @@ -1777,22 +1761,19 @@ Sat, 07 Sep 2002 00:00:01 GMT (defun newsticker--lists-intersect-p (list1 list2) "Return t if LIST1 and LIST2 share elements." (let ((result nil)) - (mapc (lambda (elt) - (if (memq elt list2) - (setq result t))) - list1) + (dolist (elt list1) + (if (memq elt list2) + (setq result t))) result)) (defun newsticker--update-process-ids () "Update list of ids of active newsticker processes. Checks list of active processes against list of newsticker processes." - (let ((active-procs (process-list)) - (new-list nil)) - (mapc (lambda (proc) - (let ((id (process-id proc))) - (if (memq id newsticker--process-ids) - (setq new-list (cons id new-list))))) - active-procs) + (let ((new-list nil)) + (dolist (proc (process-list)) + (let ((id (process-id proc))) + (if (memq id newsticker--process-ids) + (setq new-list (cons id new-list))))) (setq newsticker--process-ids new-list)) (force-mode-line-update)) @@ -1813,7 +1794,7 @@ If the file does no exist or if it is older than 24 hours download it from URL first." (let ((image-name (concat directory feed-name))) (if (and (file-exists-p image-name) - (time-less-p (current-time) + (time-less-p nil (time-add (nth 5 (file-attributes image-name)) (seconds-to-time 86400)))) (newsticker--debug-msg "%s: Getting image for %s skipped" @@ -1855,7 +1836,7 @@ Save image as FILENAME in DIRECTORY, download it from URL." (process-put proc 'nt-feed-name feed-name) (process-put proc 'nt-filename filename))))) -(defun newsticker--image-sentinel (process event) +(defun newsticker--image-sentinel (process _event) "Sentinel for image-retrieving PROCESS caused by EVENT." (let* ((p-status (process-status process)) (exit-status (process-exit-status process)) @@ -1916,21 +1897,21 @@ from. The image is saved in DIRECTORY as FILENAME." (let ((do-save (or (not status) - (let ((status-type (car status)) - (status-details (cdr status))) - (cond ((eq status-type :redirect) - ;; don't care about redirects - t) - ((eq status-type :error) - ;; silently ignore errors - nil)))))) + ;; (let ((status-type (car status))) + ;; (cond ((eq status-type :redirect) + ;; ;; don't care about redirects + ;; t) + ;; ((eq status-type :error) + ;; ;; silently ignore errors + ;; nil))) + (eq (car status) :redirect)))) (when do-save (let ((buf (get-buffer-create (concat " *newsticker-url-image-" feed-name "-" directory "*"))) - (result (string-to-multibyte (buffer-string)))) + (result (buffer-string))) (set-buffer buf) (erase-buffer) - (insert result) + (newsticker--insert-bytes result) ;; remove MIME header (goto-char (point-min)) (search-forward "\n\n") @@ -2010,7 +1991,7 @@ older than TIME." (when (eq (newsticker--age item) old-age) (let ((exp-time (time-add (newsticker--time item) (seconds-to-time time)))) - (when (time-less-p exp-time (current-time)) + (when (time-less-p exp-time nil) (newsticker--debug-msg "Item `%s' from %s has expired on %s" (newsticker--title item) @@ -2022,7 +2003,7 @@ older than TIME." data) data) -(defun newsticker--cache-contains (data feed title desc link age +(defun newsticker--cache-contains (data feed title desc link _age &optional guid) "Check DATA whether FEED contains an item with the given properties. This function returns the contained item or nil if it is not @@ -2295,9 +2276,8 @@ FEED is a symbol!" (newsticker--cache-read-version1)) (when (y-or-n-p (format "Delete old newsticker cache file? ")) (delete-file newsticker-cache-filename))) - (mapc (lambda (f) - (newsticker--cache-read-feed (car f))) - (append newsticker-url-list-defaults newsticker-url-list)))) + (dolist (f (append newsticker-url-list-defaults newsticker-url-list)) + (newsticker--cache-read-feed (car f))))) (defun newsticker--cache-read-feed (feed-name) "Read cache data for feed named FEED-NAME." @@ -2364,14 +2344,13 @@ Export subscriptions to a buffer in OPML Format." " <ownerName>" (user-full-name) "</ownerName>\n" " </head>\n" " <body>\n")) - (mapc (lambda (sub) - (insert " <outline text=\"") - (insert (newsticker--title sub)) - (insert "\" xmlUrl=\"") - (insert (xml-escape-string (let ((url (cadr sub))) - (if (stringp url) url (prin1-to-string url))))) - (insert "\"/>\n")) - (append newsticker-url-list newsticker-url-list-defaults)) + (dolist (sub (append newsticker-url-list newsticker-url-list-defaults)) + (insert " <outline text=\"") + (insert (newsticker--title sub)) + (insert "\" xmlUrl=\"") + (insert (xml-escape-string (let ((url (cadr sub))) + (if (stringp url) url (prin1-to-string url))))) + (insert "\"/>\n")) (insert " </body>\n</opml>\n")) (pop-to-buffer "*OPML Export*") (when (fboundp 'sgml-mode) @@ -2411,28 +2390,26 @@ removed." This function checks the variable `newsticker-auto-mark-filter-list' for an entry that matches FEED and ITEM." (let ((case-fold-search t)) - (mapc (lambda (filter) - (let ((filter-feed (car filter)) - (pattern-list (cadr filter))) - (when (string-match filter-feed feed) - (newsticker--do-run-auto-mark-filter item pattern-list)))) - newsticker-auto-mark-filter-list))) + (dolist (filter newsticker-auto-mark-filter-list) + (let ((filter-feed (car filter)) + (pattern-list (cadr filter))) + (when (string-match filter-feed feed) + (newsticker--do-run-auto-mark-filter item pattern-list)))))) (defun newsticker--do-run-auto-mark-filter (item list) "Actually compare ITEM against the pattern-LIST. LIST must be an element of `newsticker-auto-mark-filter-list'." - (mapc (lambda (pattern) - (let ((place (nth 1 pattern)) - (regexp (nth 2 pattern)) - (title (newsticker--title item)) - (desc (newsticker--desc item))) - (when (or (eq place 'title) (eq place 'all)) - (when (and title (string-match regexp title)) - (newsticker--process-auto-mark-filter-match item pattern))) - (when (or (eq place 'description) (eq place 'all)) - (when (and desc (string-match regexp desc)) - (newsticker--process-auto-mark-filter-match item pattern))))) - list)) + (dolist (pattern list) + (let ((place (nth 1 pattern)) + (regexp (nth 2 pattern)) + (title (newsticker--title item)) + (desc (newsticker--desc item))) + (when (or (eq place 'title) (eq place 'all)) + (when (and title (string-match regexp title)) + (newsticker--process-auto-mark-filter-match item pattern))) + (when (or (eq place 'description) (eq place 'all)) + (when (and desc (string-match regexp desc)) + (newsticker--process-auto-mark-filter-match item pattern)))))) (defun newsticker--process-auto-mark-filter-match (item pattern) "Process ITEM that matches an auto-mark-filter PATTERN." @@ -2450,24 +2427,25 @@ LIST must be an element of `newsticker-auto-mark-filter-list'." ;; ====================================================================== ;;; Hook samples ;; ====================================================================== -(defun newsticker-new-item-functions-sample (feed item) +(defun newsticker-new-item-functions-sample (feedname item) "Demonstrate the use of the `newsticker-new-item-functions' hook. -This function just prints out the values of the FEED and title of the ITEM." +This function just prints out the values of the FEEDNAME and title of the ITEM." (message (concat "newsticker-new-item-functions-sample: feed=`%s', " "title=`%s'") - feed (newsticker--title item))) + feedname (newsticker--title item))) -(defun newsticker-download-images (feed item) +(defun newsticker-download-images (feedname item) "Download the first image. -If FEED equals \"imagefeed\" download the first image URL found -in the description=contents of ITEM to the directory -\"~/tmp/newsticker/FEED/TITLE\" where TITLE is the title of the item." - (when (string= feed "imagefeed") +If FEEDNAME equals \"imagefeed\" download the first image URL +found in the description=contents of ITEM to the directory +\"~/tmp/newsticker/FEEDNAME/TITLE\" where TITLE is the title of +the item." + (when (string= feedname "imagefeed") (let ((title (newsticker--title item)) (desc (newsticker--desc item))) (when (string-match "<img src=\"\\(http://[^ \"]+\\)\"" desc) (let ((url (substring desc (match-beginning 1) (match-end 1))) - (temp-dir (concat "~/tmp/newsticker/" feed "/" title)) + (temp-dir (concat "~/tmp/newsticker/" feedname "/" title)) (org-dir default-directory)) (unless (file-directory-p temp-dir) (make-directory temp-dir t)) @@ -2479,17 +2457,17 @@ in the description=contents of ITEM to the directory (list url)) (cd org-dir)))))) -(defun newsticker-download-enclosures (feed item) - "In all FEEDs download the enclosed object of the news ITEM. -The object is saved to the directory \"~/tmp/newsticker/FEED/TITLE\", which +(defun newsticker-download-enclosures (feedname item) + "In all feeds download the enclosed object of the news ITEM. +The object is saved to the directory \"~/tmp/newsticker/FEEDNAME/TITLE\", which is created if it does not exist. TITLE is the title of the news -item. Argument FEED is ignored. +item. Argument FEEDNAME is ignored. This function is suited for adding it to `newsticker-new-item-functions'." (let ((title (newsticker--title item)) (enclosure (newsticker--enclosure item))) (when enclosure (let ((url (cdr (assoc 'url enclosure))) - (temp-dir (concat "~/tmp/newsticker/" feed "/" title)) + (temp-dir (concat "~/tmp/newsticker/" feedname "/" title)) (org-dir default-directory)) (unless (file-directory-p temp-dir) (make-directory temp-dir t)) @@ -2504,7 +2482,7 @@ This function is suited for adding it to `newsticker-new-item-functions'." ;; ====================================================================== ;;; Retrieve samples ;; ====================================================================== -(defun newsticker-retrieve-random-message (feed-name) +(defun newsticker-retrieve-random-message (_feed-name) "Return an artificial RSS string under the name FEED-NAME." (concat "<?xml version=\"1.0\" encoding=\"iso-8859-1\" ?><rss version=\"0.91\">" "<channel>" diff --git a/lisp/net/newst-plainview.el b/lisp/net/newst-plainview.el index eab3e244411..d5c9d32a07d 100644 --- a/lisp/net/newst-plainview.el +++ b/lisp/net/newst-plainview.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; ====================================================================== ;;; Commentary: diff --git a/lisp/net/newst-reader.el b/lisp/net/newst-reader.el index c781f0dfec1..97bb21ee649 100644 --- a/lisp/net/newst-reader.el +++ b/lisp/net/newst-reader.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; ====================================================================== ;;; Commentary: diff --git a/lisp/net/newst-ticker.el b/lisp/net/newst-ticker.el index 93198e3dbad..eb6ff19d5c2 100644 --- a/lisp/net/newst-ticker.el +++ b/lisp/net/newst-ticker.el @@ -23,7 +23,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; ====================================================================== @@ -118,7 +118,7 @@ been added between the last two retrievals." (defcustom newsticker-hide-obsolete-items-in-echo-area t - "Decides whether to show obsolete items items in the ticker. + "Decides whether to show obsolete items in the ticker. If t the echo area will not show obsolete items. See also `newsticker-hide-old-items-in-echo-area'." :type 'boolean diff --git a/lisp/net/newst-treeview.el b/lisp/net/newst-treeview.el index e93da3e1c47..61b98165d1b 100644 --- a/lisp/net/newst-treeview.el +++ b/lisp/net/newst-treeview.el @@ -23,7 +23,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; ====================================================================== ;;; Commentary: diff --git a/lisp/net/newsticker.el b/lisp/net/newsticker.el index 971bdf64f41..075671e0fb9 100644 --- a/lisp/net/newsticker.el +++ b/lisp/net/newsticker.el @@ -23,7 +23,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. (defconst newsticker-version "1.99" "Version number of newsticker.el.") (make-obsolete-variable 'newsticker-version 'emacs-version "25.1") diff --git a/lisp/net/nsm.el b/lisp/net/nsm.el index 8d3463ef0a5..87fa9778b6d 100644 --- a/lisp/net/nsm.el +++ b/lisp/net/nsm.el @@ -18,14 +18,14 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;;; Code: (require 'cl-lib) -(require 'subr-x) ; read-multiple-choice +(require 'rmc) ; read-multiple-choice (defvar nsm-permanent-host-settings nil) (defvar nsm-temporary-host-settings nil) diff --git a/lisp/net/ntlm.el b/lisp/net/ntlm.el index 4baa8f2081a..137231c9af7 100644 --- a/lisp/net/ntlm.el +++ b/lisp/net/ntlm.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/pinentry.el b/lisp/net/pinentry.el deleted file mode 100644 index 3e43b7d9dea..00000000000 --- a/lisp/net/pinentry.el +++ /dev/null @@ -1,460 +0,0 @@ -;;; pinentry.el --- GnuPG Pinentry server implementation -*- lexical-binding: t -*- - -;; Copyright (C) 2015-2017 Free Software Foundation, Inc. - -;; Author: Daiki Ueno <ueno@gnu.org> -;; Version: 0.1 -;; Keywords: GnuPG - -;; This file is part of GNU Emacs. - -;; GNU Emacs is free software: you can redistribute it and/or modify -;; it under the terms of the GNU General Public License as published by -;; the Free Software Foundation, either version 3 of the License, or -;; (at your option) any later version. - -;; GNU Emacs is distributed in the hope that it will be useful, -;; but WITHOUT ANY WARRANTY; without even the implied warranty of -;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -;; GNU General Public License for more details. - -;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. - -;;; Commentary: - -;; This package allows GnuPG passphrase to be prompted through the -;; minibuffer instead of graphical dialog. -;; -;; To use, add "allow-emacs-pinentry" to "~/.gnupg/gpg-agent.conf", -;; reload the configuration with "gpgconf --reload gpg-agent", and -;; start the server with M-x pinentry-start. -;; -;; The actual communication path between the relevant components is -;; as follows: -;; -;; gpg --> gpg-agent --> pinentry --> Emacs -;; -;; where pinentry and Emacs communicate through a Unix domain socket -;; created at: -;; -;; ${TMPDIR-/tmp}/emacs$(id -u)/pinentry -;; -;; under the same directory which server.el uses. The protocol is a -;; subset of the Pinentry Assuan protocol described in (info -;; "(pinentry) Protocol"). -;; -;; NOTE: As of August 2015, this feature requires newer versions of -;; GnuPG (2.1.5+) and Pinentry (0.9.5+). - -;;; Code: - -(eval-when-compile (require 'cl-lib)) - -(defgroup pinentry nil - "The Pinentry server" - :version "25.1" - :group 'external) - -(defcustom pinentry-popup-prompt-window t - "If non-nil, display multiline prompt in another window." - :type 'boolean - :group 'pinentry) - -(defcustom pinentry-prompt-window-height 5 - "Number of lines used to display multiline prompt." - :type 'integer - :group 'pinentry) - -(defvar pinentry-debug nil) -(defvar pinentry-debug-buffer nil) -(defvar pinentry--server-process nil) -(defvar pinentry--connection-process-list nil) - -(defvar pinentry--labels nil) -(put 'pinentry-read-point 'permanent-local t) -(defvar pinentry--read-point nil) -(put 'pinentry--read-point 'permanent-local t) - -(defvar pinentry--prompt-buffer nil) - -;; We use the same location as `server-socket-dir', when local sockets -;; are supported. -(defvar pinentry--socket-dir - (format "%s/emacs%d" (or (getenv "TMPDIR") "/tmp") (user-uid)) - "The directory in which to place the server socket. -If local sockets are not supported, this is nil.") - -(defconst pinentry--set-label-commands - '("SETPROMPT" "SETTITLE" "SETDESC" - "SETREPEAT" "SETREPEATERROR" - "SETOK" "SETCANCEL" "SETNOTOK")) - -;; These error codes are defined in libgpg-error/src/err-codes.h.in. -(defmacro pinentry--error-code (code) - (logior (lsh 5 24) code)) -(defconst pinentry--error-not-implemented - (cons (pinentry--error-code 69) "not implemented")) -(defconst pinentry--error-cancelled - (cons (pinentry--error-code 99) "cancelled")) -(defconst pinentry--error-not-confirmed - (cons (pinentry--error-code 114) "not confirmed")) - -(autoload 'server-ensure-safe-dir "server") - -(defvar pinentry-prompt-mode-map - (let ((keymap (make-sparse-keymap))) - (define-key keymap "q" 'quit-window) - keymap)) - -(define-derived-mode pinentry-prompt-mode special-mode "Pinentry" - "Major mode for `pinentry--prompt-buffer'." - (buffer-disable-undo) - (setq truncate-lines t - buffer-read-only t)) - -(defun pinentry--prompt (labels query-function &rest query-args) - (let ((desc (cdr (assq 'desc labels))) - (error (cdr (assq 'error labels))) - (prompt (cdr (assq 'prompt labels)))) - (when (string-match "[ \n]*\\'" prompt) - (setq prompt (concat - (substring - prompt 0 (match-beginning 0)) " "))) - (when error - (setq desc (concat "Error: " (propertize error 'face 'error) - "\n" desc))) - (if (and desc pinentry-popup-prompt-window) - (save-window-excursion - (delete-other-windows) - (unless (and pinentry--prompt-buffer - (buffer-live-p pinentry--prompt-buffer)) - (setq pinentry--prompt-buffer (generate-new-buffer "*Pinentry*"))) - (if (get-buffer-window pinentry--prompt-buffer) - (delete-window (get-buffer-window pinentry--prompt-buffer))) - (with-current-buffer pinentry--prompt-buffer - (let ((inhibit-read-only t) - buffer-read-only) - (erase-buffer) - (insert desc)) - (pinentry-prompt-mode) - (goto-char (point-min))) - (if (> (window-height) - pinentry-prompt-window-height) - (set-window-buffer (split-window nil - (- (window-height) - pinentry-prompt-window-height)) - pinentry--prompt-buffer) - (pop-to-buffer pinentry--prompt-buffer) - (if (> (window-height) pinentry-prompt-window-height) - (shrink-window (- (window-height) - pinentry-prompt-window-height)))) - (prog1 (apply query-function prompt query-args) - (quit-window))) - (apply query-function (concat desc "\n" prompt) query-args)))) - -;;;###autoload -(defun pinentry-start (&optional quiet) - "Start a Pinentry service. - -Once the environment is properly set, subsequent invocations of -the gpg command will interact with Emacs for passphrase input. - -If the optional QUIET argument is non-nil, messages at startup -will not be shown." - (interactive) - (unless (featurep 'make-network-process '(:family local)) - (error "local sockets are not supported")) - (if (process-live-p pinentry--server-process) - (unless quiet - (message "Pinentry service is already running")) - (let* ((server-file (expand-file-name "pinentry" pinentry--socket-dir))) - (server-ensure-safe-dir pinentry--socket-dir) - ;; Delete the socket files made by previous server invocations. - (ignore-errors - (let (delete-by-moving-to-trash) - (delete-file server-file))) - (cl-letf (((default-file-modes) ?\700)) - (setq pinentry--server-process - (make-network-process - :name "pinentry" - :server t - :noquery t - :sentinel #'pinentry--process-sentinel - :filter #'pinentry--process-filter - :coding 'no-conversion - :family 'local - :service server-file)) - (process-put pinentry--server-process :server-file server-file))))) - -(defun pinentry-stop () - "Stop a Pinentry service." - (interactive) - (when (process-live-p pinentry--server-process) - (delete-process pinentry--server-process)) - (setq pinentry--server-process nil) - (dolist (process pinentry--connection-process-list) - (when (buffer-live-p (process-buffer process)) - (kill-buffer (process-buffer process)))) - (setq pinentry--connection-process-list nil)) - -(defun pinentry--labels-to-shortcuts (labels) - "Convert strings in LABEL by stripping mnemonics." - (mapcar (lambda (label) - (when label - (let (c) - (if (string-match "\\(?:\\`\\|[^_]\\)_\\([[:alnum:]]\\)" label) - (let ((key (match-string 1 label))) - (setq c (downcase (aref key 0))) - (setq label (replace-match - (propertize key 'face 'underline) - t t label))) - (setq c (if (= (length label) 0) - ?? - (downcase (aref label 0))))) - ;; Double underscores mean a single underscore. - (when (string-match "__" label) - (setq label (replace-match "_" t t label))) - (cons c label)))) - labels)) - -(defun pinentry--escape-string (string) - "Escape STRING in the Assuan percent escape." - (let ((length (length string)) - (index 0) - (count 0)) - (while (< index length) - (if (memq (aref string index) '(?\n ?\r ?%)) - (setq count (1+ count))) - (setq index (1+ index))) - (setq index 0) - (let ((result (make-string (+ length (* count 2)) ?\0)) - (result-index 0) - c) - (while (< index length) - (setq c (aref string index)) - (if (memq c '(?\n ?\r ?%)) - (let ((hex (format "%02X" c))) - (aset result result-index ?%) - (setq result-index (1+ result-index)) - (aset result result-index (aref hex 0)) - (setq result-index (1+ result-index)) - (aset result result-index (aref hex 1)) - (setq result-index (1+ result-index))) - (aset result result-index c) - (setq result-index (1+ result-index))) - (setq index (1+ index))) - result))) - -(defun pinentry--unescape-string (string) - "Unescape STRING in the Assuan percent escape." - (let ((length (length string)) - (index 0)) - (let ((result (make-string length ?\0)) - (result-index 0) - c) - (while (< index length) - (setq c (aref string index)) - (if (and (eq c '?%) (< (+ index 2) length)) - (progn - (aset result result-index - (string-to-number (substring string - (1+ index) - (+ index 3)) - 16)) - (setq result-index (1+ result-index)) - (setq index (+ index 2))) - (aset result result-index c) - (setq result-index (1+ result-index))) - (setq index (1+ index))) - (substring result 0 result-index)))) - -(defun pinentry--send-data (process escaped) - "Send a string ESCAPED to a process PROCESS. -ESCAPED will be split if it exceeds the line length limit of the -Assuan protocol." - (let ((length (length escaped)) - (index 0)) - (if (= length 0) - (process-send-string process "D \n") - (while (< index length) - ;; 997 = ASSUAN_LINELENGTH (= 1000) - strlen ("D \n") - (let* ((sub-length (min (- length index) 997)) - (sub (substring escaped index (+ index sub-length)))) - (unwind-protect - (progn - (process-send-string process "D ") - (process-send-string process sub) - (process-send-string process "\n")) - (clear-string sub)) - (setq index (+ index sub-length))))))) - -(defun pinentry--send-error (process error) - (process-send-string process (format "ERR %d %s\n" (car error) (cdr error)))) - -(defun pinentry--process-filter (process input) - (unless (buffer-live-p (process-buffer process)) - (let ((buffer (generate-new-buffer " *pinentry*"))) - (set-process-buffer process buffer) - (with-current-buffer buffer - (if (fboundp 'set-buffer-multibyte) - (set-buffer-multibyte nil)) - (make-local-variable 'pinentry--read-point) - (setq pinentry--read-point (point-min)) - (make-local-variable 'pinentry--labels)))) - (with-current-buffer (process-buffer process) - (when pinentry-debug - (with-current-buffer - (or pinentry-debug-buffer - (setq pinentry-debug-buffer (generate-new-buffer - " *pinentry-debug*"))) - (goto-char (point-max)) - (insert input))) - (save-excursion - (goto-char (point-max)) - (insert input) - (goto-char pinentry--read-point) - (beginning-of-line) - (while (looking-at ".*\n") ;the input line finished - (if (looking-at "\\([A-Z_]+\\) ?\\(.*\\)") - (let ((command (match-string 1)) - (string (pinentry--unescape-string (match-string 2)))) - (pcase command - ((and set (guard (member set pinentry--set-label-commands))) - (when (> (length string) 0) - (let* ((symbol (intern (downcase (substring set 3)))) - (entry (assq symbol pinentry--labels)) - (label (decode-coding-string string 'utf-8))) - (if entry - (setcdr entry label) - (push (cons symbol label) pinentry--labels)))) - (ignore-errors - (process-send-string process "OK\n"))) - ("NOP" - (ignore-errors - (process-send-string process "OK\n"))) - ("GETPIN" - (let ((confirm (not (null (assq 'repeat pinentry--labels)))) - passphrase escaped-passphrase encoded-passphrase) - (unwind-protect - (condition-case err - (progn - (setq passphrase - (pinentry--prompt - pinentry--labels - #'read-passwd confirm)) - (setq escaped-passphrase - (pinentry--escape-string - passphrase)) - (setq encoded-passphrase (encode-coding-string - escaped-passphrase - 'utf-8)) - (ignore-errors - (pinentry--send-data - process encoded-passphrase) - (process-send-string process "OK\n"))) - (error - (message "GETPIN error %S" err) - (ignore-errors - (pinentry--send-error - process - pinentry--error-cancelled)))) - (if passphrase - (clear-string passphrase)) - (if escaped-passphrase - (clear-string escaped-passphrase)) - (if encoded-passphrase - (clear-string encoded-passphrase)))) - (setq pinentry--labels nil)) - ("CONFIRM" - (let ((prompt - (or (cdr (assq 'prompt pinentry--labels)) - "Confirm? ")) - (buttons - (delq nil - (pinentry--labels-to-shortcuts - (list (cdr (assq 'ok pinentry--labels)) - (cdr (assq 'notok pinentry--labels)) - (cdr (assq 'cancel pinentry--labels)))))) - entry) - (if buttons - (progn - (setq prompt - (concat prompt " (" - (mapconcat #'cdr buttons - ", ") - ") ")) - (if (setq entry (assq 'prompt pinentry--labels)) - (setcdr entry prompt) - (setq pinentry--labels (cons (cons 'prompt prompt) - pinentry--labels))) - (condition-case nil - (let ((result (pinentry--prompt pinentry--labels - #'read-char))) - (if (eq result (caar buttons)) - (ignore-errors - (process-send-string process "OK\n")) - (if (eq result (car (nth 1 buttons))) - (ignore-errors - (pinentry--send-error - process - pinentry--error-not-confirmed)) - (ignore-errors - (pinentry--send-error - process - pinentry--error-cancelled))))) - (error - (ignore-errors - (pinentry--send-error - process - pinentry--error-cancelled))))) - (if (setq entry (assq 'prompt pinentry--labels)) - (setcdr entry prompt) - (setq pinentry--labels (cons (cons 'prompt prompt) - pinentry--labels))) - (if (condition-case nil - (pinentry--prompt pinentry--labels #'y-or-n-p) - (quit)) - (ignore-errors - (process-send-string process "OK\n")) - (ignore-errors - (pinentry--send-error - process - pinentry--error-not-confirmed)))) - (setq pinentry--labels nil))) - (_ (ignore-errors - (pinentry--send-error - process - pinentry--error-not-implemented)))) - (forward-line) - (setq pinentry--read-point (point)))))))) - -(defun pinentry--process-sentinel (process _status) - "The process sentinel for Emacs server connections." - ;; If this is a new client process, set the query-on-exit flag to nil - ;; for this process (it isn't inherited from the server process). - (when (and (eq (process-status process) 'open) - (process-query-on-exit-flag process)) - (push process pinentry--connection-process-list) - (set-process-query-on-exit-flag process nil) - (ignore-errors - (process-send-string process "OK Your orders please\n"))) - ;; Kill the process buffer of the connection process. - (when (and (not (process-contact process :server)) - (eq (process-status process) 'closed)) - (when (buffer-live-p (process-buffer process)) - (kill-buffer (process-buffer process))) - (setq pinentry--connection-process-list - (delq process pinentry--connection-process-list))) - ;; Delete the associated connection file, if applicable. - ;; Although there's no 100% guarantee that the file is owned by the - ;; running Emacs instance, server-start uses server-running-p to check - ;; for possible servers before doing anything, so it *should* be ours. - (and (process-contact process :server) - (eq (process-status process) 'closed) - (ignore-errors - (delete-file (process-get process :server-file))))) - -(provide 'pinentry) - -;;; pinentry.el ends here diff --git a/lisp/net/pop3.el b/lisp/net/pop3.el index 2ef63217256..91408b8278a 100644 --- a/lisp/net/pop3.el +++ b/lisp/net/pop3.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/puny.el b/lisp/net/puny.el index bdd59be070a..af9b031bf21 100644 --- a/lisp/net/puny.el +++ b/lisp/net/puny.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/quickurl.el b/lisp/net/quickurl.el index 652eb2ffe82..c9b17937df1 100644 --- a/lisp/net/quickurl.el +++ b/lisp/net/quickurl.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -39,7 +39,7 @@ ;; where <Lookup> is a string that acts as the keyword lookup and <URL> is ;; the URL associated with it. An example might be: ;; -;; ("GNU" . "http://www.gnu.org/") +;; ("GNU" . "https://www.gnu.org/") ;; ;; A list entry looks like: ;; @@ -50,12 +50,12 @@ ;; used when presenting a list of URLS using `quickurl-list'. An example ;; might be: ;; -;; ("FSF" "http://www.fsf.org/" "The Free Software Foundation") +;; ("FSF" "https://www.fsf.org/" "The Free Software Foundation") ;; ;; Given the above, your quickurl file might look like: ;; -;; (("GNU" . "http://www.gnu.org/") -;; ("FSF" "http://www.fsf.org/" "The Free Software Foundation") +;; (("GNU" . "https://www.gnu.org/") +;; ("FSF" "https://www.fsf.org/" "The Free Software Foundation") ;; ("emacs" . "http://www.emacs.org/") ;; ("davep" "http://www.davep.org/" "Dave's homepage")) ;; diff --git a/lisp/net/rcirc.el b/lisp/net/rcirc.el index ddff25c1e92..3b6b6c8c807 100644 --- a/lisp/net/rcirc.el +++ b/lisp/net/rcirc.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -176,10 +176,30 @@ underneath each nick." "If non-nil, activity in this buffer is considered low priority.") (make-variable-buffer-local 'rcirc-low-priority-flag) -(defvar rcirc-omit-mode nil - "Non-nil if Rcirc-Omit mode is enabled. -Use the command `rcirc-omit-mode' to change this variable.") -(make-variable-buffer-local 'rcirc-omit-mode) +(defcustom rcirc-omit-responses + '("JOIN" "PART" "QUIT" "NICK") + "Responses which will be hidden when `rcirc-omit-mode' is enabled." + :type '(repeat string) + :group 'rcirc) + +(define-minor-mode rcirc-omit-mode + "Toggle the hiding of \"uninteresting\" lines. +With a prefix argument ARG, enable Rcirc-Omit mode if ARG is +positive, and disable it otherwise. If called from Lisp, enable +the mode if ARG is omitted or nil. + +Uninteresting lines are those whose responses are listed in +`rcirc-omit-responses'." + nil " Omit" nil + (if rcirc-omit-mode + (progn + (add-to-invisibility-spec '(rcirc-omit . nil)) + (message "Rcirc-Omit mode enabled")) + (remove-from-invisibility-spec '(rcirc-omit . nil)) + (message "Rcirc-Omit mode disabled")) + (dolist (window (get-buffer-window-list (current-buffer))) + (with-selected-window window + (recenter (when (> (point) rcirc-prompt-start-marker) -1))))) (defcustom rcirc-time-format "%H:%M " "Describes how timestamps are printed. @@ -1405,12 +1425,6 @@ the of the following escape sequences replaced by the described values: :value-type string) :group 'rcirc) -(defcustom rcirc-omit-responses - '("JOIN" "PART" "QUIT" "NICK") - "Responses which will be hidden when `rcirc-omit-mode' is enabled." - :type '(repeat string) - :group 'rcirc) - (defun rcirc-format-response-string (process sender response target text) "Return a nicely-formatted response string, incorporating TEXT \(and perhaps other arguments). The specific formatting used @@ -1881,9 +1895,6 @@ if ARG is omitted or nil." (or (assq 'rcirc-low-priority-flag minor-mode-alist) (setq minor-mode-alist (cons '(rcirc-low-priority-flag " LowPri") minor-mode-alist))) -(or (assq 'rcirc-omit-mode minor-mode-alist) - (setq minor-mode-alist - (cons '(rcirc-omit-mode " Omit") minor-mode-alist))) (defun rcirc-toggle-ignore-buffer-activity () "Toggle the value of `rcirc-ignore-buffer-activity-flag'." @@ -1905,23 +1916,6 @@ if ARG is omitted or nil." "Activity in this buffer is normal priority")) (force-mode-line-update)) -(defun rcirc-omit-mode () - "Toggle the Rcirc-Omit mode. -If enabled, \"uninteresting\" lines are not shown. -Uninteresting lines are those whose responses are listed in -`rcirc-omit-responses'." - (interactive) - (setq rcirc-omit-mode (not rcirc-omit-mode)) - (if rcirc-omit-mode - (progn - (add-to-invisibility-spec '(rcirc-omit . nil)) - (message "Rcirc-Omit mode enabled")) - (remove-from-invisibility-spec '(rcirc-omit . nil)) - (message "Rcirc-Omit mode disabled")) - (dolist (window (get-buffer-window-list (current-buffer))) - (with-selected-window window - (recenter (when (> (point) rcirc-prompt-start-marker) -1))))) - (defun rcirc-switch-to-server-buffer () "Switch to the server buffer associated with current channel buffer." (interactive) @@ -2339,7 +2333,7 @@ With a prefix arg, prompt for new topic." (defun rcirc-ctcp-sender-PING (process target _request) "Send a CTCP PING message to TARGET." - (let ((timestamp (format "%.0f" (float-time)))) + (let ((timestamp (format-time-string "%s"))) (rcirc-send-ctcp process target "PING" timestamp))) (defun rcirc-cmd-me (args &optional process target) @@ -2505,12 +2499,15 @@ If ARG is given, opens the URL in a new browser window." (end (match-end 0)) (url (match-string-no-properties 0)) (link-text (buffer-substring-no-properties start end))) - (make-button start end - 'face 'rcirc-url - 'follow-link t - 'rcirc-url url - 'action (lambda (button) - (browse-url (button-get button 'rcirc-url)))) + ;; Add a button for the URL. Note that we use `make-text-button', + ;; rather than `make-button', as text-buttons are much faster in + ;; large buffers. + (make-text-button start end + 'face 'rcirc-url + 'follow-link t + 'rcirc-url url + 'action (lambda (button) + (browse-url (button-get button 'rcirc-url)))) ;; record the url if it is not already the latest stored url (when (not (string= link-text (caar rcirc-urls))) (push (cons link-text start) rcirc-urls))))) diff --git a/lisp/net/rfc2104.el b/lisp/net/rfc2104.el index 71cf5bd8283..7d85c34ff61 100644 --- a/lisp/net/rfc2104.el +++ b/lisp/net/rfc2104.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/rlogin.el b/lisp/net/rlogin.el index a07c4901545..2843833a27a 100644 --- a/lisp/net/rlogin.el +++ b/lisp/net/rlogin.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -38,7 +38,7 @@ ;; FIXME? ;; Maybe this file should be obsolete. -;; http://lists.gnu.org/archive/html/emacs-devel/2013-02/msg00517.html +;; https://lists.gnu.org/r/emacs-devel/2013-02/msg00517.html ;; It only adds rlogin-directory-tracking-mode. Is that useful? (require 'comint) diff --git a/lisp/net/sasl-cram.el b/lisp/net/sasl-cram.el index cd6c7e1a583..269e9a5462c 100644 --- a/lisp/net/sasl-cram.el +++ b/lisp/net/sasl-cram.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/sasl-digest.el b/lisp/net/sasl-digest.el index 445d4bf37b3..e74b90dabcd 100644 --- a/lisp/net/sasl-digest.el +++ b/lisp/net/sasl-digest.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/sasl-ntlm.el b/lisp/net/sasl-ntlm.el index cb6961b14b5..606aa036078 100644 --- a/lisp/net/sasl-ntlm.el +++ b/lisp/net/sasl-ntlm.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/sasl-scram-rfc.el b/lisp/net/sasl-scram-rfc.el index 1dc4803c828..18415359b86 100644 --- a/lisp/net/sasl-scram-rfc.el +++ b/lisp/net/sasl-scram-rfc.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/sasl.el b/lisp/net/sasl.el index 6128b91b1db..2a166db7cee 100644 --- a/lisp/net/sasl.el +++ b/lisp/net/sasl.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/secrets.el b/lisp/net/secrets.el index 9bcfc378f42..fa49b646b04 100644 --- a/lisp/net/secrets.el +++ b/lisp/net/secrets.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/shr-color.el b/lisp/net/shr-color.el index b0c706eb5da..65ab544bb50 100644 --- a/lisp/net/shr-color.el +++ b/lisp/net/shr-color.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/shr.el b/lisp/net/shr.el index 2a6b3960c46..ad5d869531c 100644 --- a/lisp/net/shr.el +++ b/lisp/net/shr.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -185,8 +185,8 @@ and other things: (define-key map [follow-link] 'mouse-face) (define-key map [mouse-2] 'shr-browse-url) (define-key map "I" 'shr-insert-image) - (define-key map "w" 'shr-copy-url) - (define-key map "u" 'shr-copy-url) + (define-key map "w" 'shr-maybe-probe-and-copy-url) + (define-key map "u" 'shr-maybe-probe-and-copy-url) (define-key map "v" 'shr-browse-url) (define-key map "O" 'shr-save-contents) (define-key map "\r" 'shr-browse-url) @@ -290,43 +290,59 @@ DOM should be a parse tree as generated by (forward-line 1) (delete-region (point) (point-max)))))) -(defun shr-copy-url (&optional image-url) +(defun shr-url-at-point (image-url) + "Return the URL under point as a string. +If IMAGE-URL is non-nil, or there is no link under point, but +there is an image under point then copy the URL of the image +under point instead." + (if image-url + (get-text-property (point) 'image-url) + (or (get-text-property (point) 'shr-url) + (get-text-property (point) 'image-url)))) + +(defun shr-copy-url (url) "Copy the URL under point to the kill ring. If IMAGE-URL (the prefix) is non-nil, or there is no link under point, but there is an image under point then copy the URL of the -image under point instead. -If called twice, then try to fetch the URL and see whether it -redirects somewhere else." +image under point instead." + (interactive (list (shr-url-at-point current-prefix-arg))) + (if (not url) + (message "No URL under point") + (setq url (url-encode-url url)) + (kill-new url) + (message "Copied %s" url))) + +(defun shr-probe-url (url cont) + "Pass URL's redirect destination to CONT, if it has one. +CONT should be a function of one argument, the redirect +destination URL. If URL is not redirected, then CONT is never +called." (interactive "P") - (let ((url (if image-url - (get-text-property (point) 'image-url) - (or (get-text-property (point) 'shr-url) - (get-text-property (point) 'image-url))))) - (cond - ((not url) - (message "No URL under point")) - ;; Resolve redirected URLs. - ((equal url (car kill-ring)) - (url-retrieve - url - (lambda (a) - (when (and (consp a) - (eq (car a) :redirect)) - (with-temp-buffer - (insert (cadr a)) - (goto-char (point-min)) - ;; Remove common tracking junk from the URL. - (when (re-search-forward ".utm_.*" nil t) - (replace-match "" t t)) - (message "Copied %s" (buffer-string)) - (copy-region-as-kill (point-min) (point-max))))) - nil t)) - ;; Copy the URL to the kill ring. - (t - (with-temp-buffer - (insert (url-encode-url url)) - (copy-region-as-kill (point-min) (point-max)) - (message "Copied %s" (buffer-string))))))) + (url-retrieve + url (lambda (a) + (pcase a + (`(:redirect ,destination . ,_) + ;; Remove common tracking junk from the URL. + (funcall cont (replace-regexp-in-string + ".utm_.*" "" destination))))) + nil t)) + +(defun shr-probe-and-copy-url (url) + "Copy the URL under point to the kill ring. +Like `shr-copy-url', but additionally fetch URL and use its +redirection destination if it has one." + (interactive (list (shr-url-at-point current-prefix-arg))) + (if url (shr-probe-url url #'shr-copy-url) + (shr-copy-url url))) + +(defun shr-maybe-probe-and-copy-url (url) + "Copy the URL under point to the kill ring. +If the URL is already at the front of the kill ring act like +`shr-probe-and-copy-url', otherwise like `shr-copy-url'." + (interactive (list (shr-url-at-point current-prefix-arg))) + (if (equal url (car kill-ring)) + (shr-probe-and-copy-url url) + (shr-copy-url url))) (defun shr-next-link () "Skip to the next link." @@ -454,6 +470,18 @@ size, and full-buffer size." (shr-insert sub) (shr-descend sub)))) +(defun shr-indirect-call (tag-name dom &rest args) + (let ((function (intern (concat "shr-tag-" (symbol-name tag-name)) obarray)) + ;; Allow other packages to override (or provide) rendering + ;; of elements. + (external (cdr (assq tag-name shr-external-rendering-functions)))) + (cond (external + (apply external dom args)) + ((fboundp function) + (apply function dom args)) + (t + (apply 'shr-generic dom args))))) + (defun shr-descend (dom) (let ((function (intern (concat "shr-tag-" (symbol-name (dom-tag dom))) obarray)) @@ -474,6 +502,11 @@ size, and full-buffer size." (setq style nil))) ;; If we have a display:none, then just ignore this part of the DOM. (unless (equal (cdr (assq 'display shr-stylesheet)) "none") + ;; We don't use shr-indirect-call here, since shr-descend is + ;; the central bit of shr.el, and should be as fast as + ;; possible. Having one more level of indirection with its + ;; negative effect on performance is deemed unjustified in + ;; this case. (cond (external (funcall external dom)) ((fboundp function) @@ -512,6 +545,7 @@ size, and full-buffer size." (* (frame-char-width) 2) 0)))) (shr-insert text) + (shr-fill-lines (point-min) (point-max)) (buffer-string))))) (define-inline shr-char-breakable-p (char) @@ -601,7 +635,7 @@ size, and full-buffer size." (replace-match " " t t)) (shr--translate-insertion-chars) (goto-char (point-max))) - ;; We may have removed everything we inserted if if was just + ;; We may have removed everything we inserted if it was just ;; spaces. (unless (= font-start (point)) ;; Mark all lines that should possibly be folded afterwards. @@ -666,12 +700,16 @@ size, and full-buffer size." ;; Success; continue. (when (= (preceding-char) ?\s) (delete-char -1)) - (let ((props (text-properties-at (point))) + (let ((props `(face ,(get-text-property (point) 'face) + ;; Don't break the image-displayer property + ;; as it will cause `gnus-article-show-images' + ;; to show the two or more same images. + image-displayer + ,(get-text-property (point) 'image-displayer))) (gap-start (point))) (insert "\n") (shr-indent) - (when props - (add-text-properties gap-start (point) props))) + (add-text-properties gap-start (point) props)) (setq start (point)) (shr-vertical-motion shr-internal-width) (when (looking-at " $") @@ -928,6 +966,7 @@ If EXTERNAL, browse the URL using `shr-external-browser'." (when (and (buffer-name buffer) (not (plist-get status :error))) (url-store-in-cache image-buffer) + (goto-char (point-min)) (when (or (search-forward "\n\n" nil t) (search-forward "\r\n\r\n" nil t)) (let ((data (shr-parse-image-data))) @@ -955,7 +994,8 @@ If EXTERNAL, browse the URL using `shr-external-browser'." data) (let ((param (match-string 4 data)) (payload (url-unhex-string (match-string 5 data)))) - (when (string-match "^.*\\(;[ \t]*base64\\)$" param) + (when (and param + (string-match "^.*\\(;[ \t]*base64\\)$" param)) (setq payload (ignore-errors (base64-decode-string payload)))) payload))) @@ -981,7 +1021,7 @@ element is the data blob and the second element is the content-type." (create-image data nil t :ascent 100 :format content-type)) ((eq content-type 'image/svg+xml) - (create-image data 'imagemagick t :ascent 100)) + (create-image data 'svg t :ascent 100)) ((eq size 'full) (ignore-errors (shr-rescale-image data content-type @@ -1345,7 +1385,7 @@ ones, in case fg and bg are nil." plist))) (defun shr-tag-base (dom) - (when-let (base (dom-attr dom 'href)) + (when-let* ((base (dom-attr dom 'href))) (setq shr-base (shr-parse-base base))) (shr-generic dom)) @@ -1370,7 +1410,7 @@ ones, in case fg and bg are nil." (unless shr-inhibit-images (let ((start (point)) url multimedia image) - (when-let (type (dom-attr dom 'type)) + (when-let* ((type (dom-attr dom 'type))) (when (string-match "\\`image/svg" type) (setq url (dom-attr dom 'data) image t))) @@ -1386,7 +1426,7 @@ ones, in case fg and bg are nil." (when url (cond (image - (shr-tag-img dom url) + (shr-indirect-call 'img dom url) (setq dom nil)) (multimedia (shr-insert " [multimedia] ") @@ -1451,7 +1491,7 @@ The preference is a float determined from `shr-prefer-media-type'." (unless url (setq url (car (shr--extract-best-source dom)))) (if (> (length image) 0) - (shr-tag-img nil image) + (shr-indirect-call 'img nil image) (shr-insert " [video] ")) (shr-urlify start (shr-expand-url url)))) @@ -1946,9 +1986,9 @@ flags that control whether to collect or render objects." do (setq tag (dom-tag child)) and unless (memq tag '(comment style)) if (eq tag 'img) - do (shr-tag-img child) + do (shr-indirect-call 'img child) else if (eq tag 'object) - do (shr-tag-object child) + do (shr-indirect-call 'object child) else do (setq recurse t) and if (eq tag 'tr) @@ -1962,7 +2002,7 @@ flags that control whether to collect or render objects." do (setq flags nil) else if (car flags) do (setq recurse nil) - (shr-tag-table child) + (shr-indirect-call 'table child) end end end end end end end end end end when recurse append (shr-collect-extra-strings-in-table child flags))) @@ -2160,7 +2200,7 @@ flags that control whether to collect or render objects." (when (and (not (stringp column)) (or (memq (dom-tag column) '(td th)) (not column))) - (when-let (span (dom-attr column 'rowspan)) + (when-let* ((span (dom-attr column 'rowspan))) (aset rowspans i (+ (aref rowspans i) (1- (string-to-number span))))) ;; Sanity check for invalid column-spans. @@ -2250,8 +2290,10 @@ flags that control whether to collect or render objects." (<= (car (cdr attr)) width)) (setq result (cdr attr))))))) result)) - (let ((result (shr-render-td-1 dom width fill))) + (let* ((pt (point)) + (result (shr-render-td-1 dom width fill))) (dom-set-attribute dom cache result) + (goto-char pt) result)))) (defun shr-render-td-1 (dom width fill) diff --git a/lisp/net/sieve-manage.el b/lisp/net/sieve-manage.el index 1a54e1aa738..832b443b12f 100644 --- a/lisp/net/sieve-manage.el +++ b/lisp/net/sieve-manage.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/sieve-mode.el b/lisp/net/sieve-mode.el index 87bb3a245b8..165bbbd8d40 100644 --- a/lisp/net/sieve-mode.el +++ b/lisp/net/sieve-mode.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/sieve.el b/lisp/net/sieve.el index 665a0a8e15d..c3acd36fa45 100644 --- a/lisp/net/sieve.el +++ b/lisp/net/sieve.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/snmp-mode.el b/lisp/net/snmp-mode.el index e6a27f43a08..413882ae861 100644 --- a/lisp/net/snmp-mode.el +++ b/lisp/net/snmp-mode.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/soap-client.el b/lisp/net/soap-client.el index c0b71cdf170..4fdd0382444 100644 --- a/lisp/net/soap-client.el +++ b/lisp/net/soap-client.el @@ -24,7 +24,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/net/soap-inspect.el b/lisp/net/soap-inspect.el index 2516bc99248..722d4d62882 100644 --- a/lisp/net/soap-inspect.el +++ b/lisp/net/soap-inspect.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/net/socks.el b/lisp/net/socks.el index f18e69514bb..63a65069c55 100644 --- a/lisp/net/socks.el +++ b/lisp/net/socks.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/starttls.el b/lisp/net/starttls.el index 4de3d69e4f8..276807a374b 100644 --- a/lisp/net/starttls.el +++ b/lisp/net/starttls.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -42,7 +42,7 @@ ;; it performs more verification of the certificates. ;; The GnuTLS support requires GnuTLS 0.9.90 (released 2003-10-08) or -;; later, from <http://www.gnu.org/software/gnutls/>, or "starttls" +;; later, from <https://www.gnu.org/software/gnutls/>, or "starttls" ;; from <ftp://ftp.opaopa.org/pub/elisp/>. ;; Usage is similar to `open-network-stream'. For example: diff --git a/lisp/net/telnet.el b/lisp/net/telnet.el index b38ef6c654a..03569415edb 100644 --- a/lisp/net/telnet.el +++ b/lisp/net/telnet.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/tls.el b/lisp/net/tls.el index 11aae635aae..76c39b0bece 100644 --- a/lisp/net/tls.el +++ b/lisp/net/tls.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -106,7 +106,7 @@ successful negotiation." (repeat :inline t :tag "Other" (string))) (list :tag "List of commands" (repeat :tag "Command" (string)))) - :version "25.3" ; remove s_client + :version "26.1" ; remove s_client :group 'tls) (defcustom tls-process-connection-type nil diff --git a/lisp/net/tramp-adb.el b/lisp/net/tramp-adb.el index 23aa90186a6..8399c02923d 100644 --- a/lisp/net/tramp-adb.el +++ b/lisp/net/tramp-adb.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -72,7 +72,7 @@ It is used for TCP/IP devices." (defconst tramp-adb-ls-toolbox-regexp (concat "^[[:space:]]*\\([-[:alpha:]]+\\)" ; \1 permissions - "\\(?:[[:space:]][[:digit:]]+\\)?" ; links (Android 7/ToolBox) + "\\(?:[[:space:]]+[[:digit:]]+\\)?" ; links (Android 7/toybox) "[[:space:]]*\\([^[:space:]]+\\)" ; \2 username "[[:space:]]+\\([^[:space:]]+\\)" ; \3 group "[[:space:]]+\\([[:digit:]]+\\)" ; \4 size @@ -97,7 +97,7 @@ It is used for TCP/IP devices." ;;;###tramp-autoload (defconst tramp-adb-file-name-handler-alist '((access-file . ignore) - (add-name-to-file . tramp-adb-handle-copy-file) + (add-name-to-file . tramp-handle-add-name-to-file) ;; `byte-compiler-base-file-name' performed by default handler. ;; `copy-directory' performed by default handler. (copy-file . tramp-adb-handle-copy-file) @@ -137,8 +137,9 @@ It is used for TCP/IP devices." (file-readable-p . tramp-handle-file-exists-p) (file-regular-p . tramp-handle-file-regular-p) (file-remote-p . tramp-handle-file-remote-p) - (file-selinux-context . ignore) + (file-selinux-context . tramp-handle-file-selinux-context) (file-symlink-p . tramp-handle-file-symlink-p) + (file-system-info . tramp-adb-handle-file-system-info) (file-truename . tramp-adb-handle-file-truename) (file-writable-p . tramp-adb-handle-file-writable-p) (find-backup-file-name . tramp-handle-find-backup-file-name) @@ -255,6 +256,30 @@ pass to the OPERATION." (file-attributes (file-truename filename))) t)) +(defun tramp-adb-handle-file-system-info (filename) + "Like `file-system-info' for Tramp files." + (ignore-errors + (with-parsed-tramp-file-name (expand-file-name filename) nil + (tramp-message v 5 "file system info: %s" localname) + (tramp-adb-send-command + v (format "df -k %s" (tramp-shell-quote-argument localname))) + (with-current-buffer (tramp-get-connection-buffer v) + (goto-char (point-min)) + (forward-line) + (when (looking-at + (concat "[[:space:]]*[^[:space:]]+" + "[[:space:]]+\\([[:digit:]]+\\)" + "[[:space:]]+\\([[:digit:]]+\\)" + "[[:space:]]+\\([[:digit:]]+\\)")) + ;; The values are given as 1k numbers, so we must change + ;; them to number of bytes. + (list (* 1024 (string-to-number (concat (match-string 1) "e0"))) + ;; The second value is the used size. We need the + ;; free size. + (* 1024 (- (string-to-number (concat (match-string 1) "e0")) + (string-to-number (concat (match-string 2) "e0")))) + (* 1024 (string-to-number (concat (match-string 3) "e0"))))))))) + ;; This is derived from `tramp-sh-handle-file-truename'. Maybe the ;; code could be shared? (defun tramp-adb-handle-file-truename (filename) @@ -411,15 +436,17 @@ pass to the OPERATION." (tramp-adb-get-ls-command v) (tramp-shell-quote-argument localname))) ;; We insert also filename/. and filename/.., because "ls" doesn't. - (narrow-to-region (point) (point)) - (tramp-adb-send-command - v (format "%s -d -a -l %s %s" - (tramp-adb-get-ls-command v) - (tramp-shell-quote-argument - (concat (file-name-as-directory localname) ".")) - (tramp-shell-quote-argument - (concat (file-name-as-directory localname) "..")))) - (widen)) + ;; Looks like it does include them in toybox, since Android 6. + (unless (re-search-backward "\\.$" nil t) + (narrow-to-region (point-max) (point-max)) + (tramp-adb-send-command + v (format "%s -d -a -l %s %s" + (tramp-adb-get-ls-command v) + (tramp-shell-quote-argument + (concat (file-name-as-directory localname) ".")) + (tramp-shell-quote-argument + (concat (file-name-as-directory localname) "..")))) + (widen))) (tramp-adb-sh-fix-ls-output) (let ((result (tramp-do-parse-file-attributes-with-ls v (or id-format 'integer)))) @@ -443,11 +470,12 @@ pass to the OPERATION." (with-tramp-connection-property vec "ls" (tramp-message vec 5 "Finding a suitable `ls' command") (cond - ;; Can't disable coloring explicitly for toybox ls command - ((tramp-adb-send-command-and-check vec "toybox") "ls") + ;; Can't disable coloring explicitly for toybox ls command. We + ;; must force "ls" to print just one column. + ((tramp-adb-send-command-and-check vec "toybox") "env COLUMNS=1 ls") ;; On CyanogenMod based system BusyBox is used and "ls" output - ;; coloring is enabled by default. So we try to disable it - ;; when possible. + ;; coloring is enabled by default. So we try to disable it when + ;; possible. ((tramp-adb-send-command-and-check vec "ls --color=never -al /dev/null") "ls --color=never") (t "ls")))) @@ -521,11 +549,12 @@ Emacs dired can't find files." (let ((par (expand-file-name ".." dir))) (unless (file-directory-p par) (make-directory par parents)))) - (tramp-adb-barf-unless-okay - v (format "mkdir %s" (tramp-shell-quote-argument localname)) - "Couldn't make directory %s" dir) (tramp-flush-file-property v (file-name-directory localname)) - (tramp-flush-directory-property v localname))) + (tramp-flush-directory-property v localname) + (unless (or (tramp-adb-send-command-and-check + v (format "mkdir %s" (tramp-shell-quote-argument localname))) + (and parents (file-directory-p dir))) + (tramp-error v 'file-error "Couldn't make directory %s" dir)))) (defun tramp-adb-handle-delete-directory (directory &optional recursive _trash) "Like `delete-directory' for Tramp files." @@ -569,13 +598,17 @@ Emacs dired can't find files." (file-name-as-directory f) f)) (with-current-buffer (tramp-get-buffer v) - (append - '("." "..") - (delq - nil - (mapcar - (lambda (l) (and (not (string-match "^[[:space:]]*$" l)) l)) - (split-string (buffer-string) "\n"))))))))))) + (delete-dups + (append + ;; In older Android versions, "." and ".." are not + ;; included. In newer versions (toybox, since Android + ;; 6) they are. We fix this by `delete-dups'. + '("." "..") + (delq + nil + (mapcar + (lambda (l) (and (not (string-match "^[[:space:]]*$" l)) l)) + (split-string (buffer-string) "\n")))))))))))) (defun tramp-adb-handle-file-local-copy (filename) "Like `file-local-copy' for Tramp files." @@ -623,14 +656,17 @@ But handle the case, if the \"test\" command is not available." rw-path))))))) (defun tramp-adb-handle-write-region - (start end filename &optional append visit lockname confirm) + (start end filename &optional append visit lockname mustbenew) "Like `write-region' for Tramp files." (setq filename (expand-file-name filename)) (with-parsed-tramp-file-name filename nil - (when (and confirm (file-exists-p filename)) - (unless (y-or-n-p (format "File %s exists; overwrite anyway? " - filename)) - (tramp-error v 'file-error "File not overwritten"))) + (when (and mustbenew (file-exists-p filename) + (or (eq mustbenew 'excl) + (not + (y-or-n-p + (format "File %s exists; overwrite anyway? " filename))))) + (tramp-error v 'file-already-exists filename)) + ;; We must also flush the cache of the directory, because ;; `file-attributes' reads the values from there. (tramp-flush-file-property v (file-name-directory localname)) @@ -643,8 +679,7 @@ But handle the case, if the \"test\" command is not available." tmpfile (logior (or (file-modes tmpfile) 0) (string-to-number "0600" 8)))) (tramp-run-real-handler - 'write-region - (list start end tmpfile append 'no-message lockname confirm)) + 'write-region (list start end tmpfile append 'no-message lockname)) (with-tramp-progress-reporter v 3 (format-message "Moving tmp file `%s' to `%s'" tmpfile filename) @@ -730,7 +765,8 @@ PRESERVE-UID-GID and PRESERVE-EXTENDED-ATTRIBUTES are completely ignored." (signal (car err) (cdr err)))) ;; Remote newname. - (when (file-directory-p newname) + (when (and (file-directory-p newname) + (tramp-compat-directory-name-p newname)) (setq newname (expand-file-name (file-name-nondirectory filename) newname))) @@ -766,38 +802,43 @@ PRESERVE-UID-GID and PRESERVE-EXTENDED-ATTRIBUTES are completely ignored." (setq filename (expand-file-name filename) newname (expand-file-name newname)) - (let ((t1 (tramp-tramp-file-p filename)) - (t2 (tramp-tramp-file-p newname))) - (with-parsed-tramp-file-name (if t1 filename newname) nil - (with-tramp-progress-reporter - v 0 (format "Renaming %s to %s" filename newname) - - (if (and t1 t2 - (tramp-equal-remote filename newname) - (not (file-directory-p filename))) - (let ((l1 (file-remote-p filename 'localname)) - (l2 (file-remote-p newname 'localname))) - (when (and (not ok-if-already-exists) - (file-exists-p newname)) - (tramp-error v 'file-already-exists newname)) - ;; We must also flush the cache of the directory, because - ;; `file-attributes' reads the values from there. - (tramp-flush-file-property v (file-name-directory l1)) - (tramp-flush-file-property v l1) - (tramp-flush-file-property v (file-name-directory l2)) - (tramp-flush-file-property v l2) - ;; Short track. - (tramp-adb-barf-unless-okay - v (format - "mv -f %s %s" - (tramp-shell-quote-argument l1) - (tramp-shell-quote-argument l2)) - "Error renaming %s to %s" filename newname)) - - ;; Rename by copy. - (copy-file - filename newname ok-if-already-exists 'keep-time 'preserve-uid-gid) - (delete-file filename)))))) + (if (file-directory-p filename) + (progn + (copy-directory filename newname t t) + (delete-directory filename 'recursive)) + + (let ((t1 (tramp-tramp-file-p filename)) + (t2 (tramp-tramp-file-p newname))) + (with-parsed-tramp-file-name (if t1 filename newname) nil + (with-tramp-progress-reporter + v 0 (format "Renaming %s to %s" filename newname) + + (if (and t1 t2 + (tramp-equal-remote filename newname) + (not (file-directory-p filename))) + (let ((l1 (file-remote-p filename 'localname)) + (l2 (file-remote-p newname 'localname))) + (when (and (not ok-if-already-exists) + (file-exists-p newname)) + (tramp-error v 'file-already-exists newname)) + ;; We must also flush the cache of the directory, because + ;; `file-attributes' reads the values from there. + (tramp-flush-file-property v (file-name-directory l1)) + (tramp-flush-file-property v l1) + (tramp-flush-file-property v (file-name-directory l2)) + (tramp-flush-file-property v l2) + ;; Short track. + (tramp-adb-barf-unless-okay + v (format + "mv -f %s %s" + (tramp-shell-quote-argument l1) + (tramp-shell-quote-argument l2)) + "Error renaming %s to %s" filename newname)) + + ;; Rename by copy. + (copy-file + filename newname ok-if-already-exists 'keep-time 'preserve-uid-gid) + (delete-file filename))))))) (defun tramp-adb-handle-process-file (program &optional infile destination display &rest args) diff --git a/lisp/net/tramp-cache.el b/lisp/net/tramp-cache.el index ac5a9c45bbd..dc97501be3d 100644 --- a/lisp/net/tramp-cache.el +++ b/lisp/net/tramp-cache.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -136,7 +136,11 @@ Returns DEFAULT if not set." (tramp-message key 8 "%s %s %s" file property value) (when (>= tramp-verbose 10) (let* ((var (intern (concat "tramp-cache-get-count-" property))) - (val (or (and (boundp var) (symbol-value var)) 0))) + (val (or (bound-and-true-p var) + (progn + (add-hook 'tramp-cache-unload-hook + (lambda () (makunbound var))) + 0)))) (set var (1+ val)))) value)) @@ -156,7 +160,11 @@ Returns VALUE." (tramp-message key 8 "%s %s %s" file property value) (when (>= tramp-verbose 10) (let* ((var (intern (concat "tramp-cache-set-count-" property))) - (val (or (and (boundp var) (symbol-value var)) 0))) + (val (or (bound-and-true-p var) + (progn + (add-hook 'tramp-cache-unload-hook + (lambda () (makunbound var))) + 0)))) (set var (1+ val)))) value)) diff --git a/lisp/net/tramp-cmds.el b/lisp/net/tramp-cmds.el index 4c5a12d33ba..37a6521680b 100644 --- a/lisp/net/tramp-cmds.el +++ b/lisp/net/tramp-cmds.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -49,7 +49,7 @@ SYNTAX can be one of the symbols `default' (default), (unless (string-equal input "") (list (intern input))))) (when syntax - (custom-set-variables `(tramp-syntax ',syntax)))) + (customize-set-variable 'tramp-syntax syntax))) (defun tramp-list-tramp-buffers () "Return a list of all Tramp connection buffers." diff --git a/lisp/net/tramp-compat.el b/lisp/net/tramp-compat.el index c998df814c1..9326f7b1864 100644 --- a/lisp/net/tramp-compat.el +++ b/lisp/net/tramp-compat.el @@ -19,12 +19,13 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: -;; Tramp's main Emacs version for development is Emacs 26. This -;; package provides compatibility functions for Emacs 24 and Emacs 25. +;; Tramp's main Emacs version for development is Emacs 27. This +;; package provides compatibility functions for Emacs 24, Emacs 25 and +;; Emacs 26. ;;; Code: @@ -50,33 +51,6 @@ `(when (functionp ,function) (with-no-warnings (funcall ,function ,@arguments)))) -;; We currently use "[" and "]" in the filename format for IPv6 hosts -;; of GNU Emacs. This means that Emacs wants to expand wildcards if -;; `find-file-wildcards' is non-nil, and then barfs because no -;; expansion could be found. We detect this situation and do -;; something really awful: we have `file-expand-wildcards' return the -;; original filename if it can't expand anything. Let's just hope -;; that this doesn't break anything else. It is not needed anymore -;; since GNU Emacs 23.2. -(unless (featurep 'files 'remote-wildcards) - (defadvice file-expand-wildcards - (around tramp-advice-file-expand-wildcards activate) - (let ((name (ad-get-arg 0))) - ;; If it's a Tramp file, look if wildcards need to be expanded - ;; at all. - (if (and - (tramp-tramp-file-p name) - (not (string-match "[[*?]" (file-remote-p name 'localname)))) - (setq ad-return-value (list name)) - ;; Otherwise, just run the original function. - ad-do-it))) - (add-hook - 'tramp-unload-hook - (lambda () - (ad-remove-advice - 'file-expand-wildcards 'around 'tramp-advice-file-expand-wildcards) - (ad-activate 'file-expand-wildcards)))) - (defsubst tramp-compat-temporary-file-directory () "Return name of directory for temporary files. It is the default value of `temporary-file-directory'." @@ -131,6 +105,10 @@ Add the extension of F, if existing." 'tramp-error vec-or-proc (if (fboundp 'user-error) 'user-error 'error) format args)) +;; `default-toplevel-value' has been declared in Emacs 24.4. +(unless (fboundp 'default-toplevel-value) + (defalias 'default-toplevel-value 'symbol-value)) + ;; `file-attribute-*' are introduced in Emacs 25.1. (if (fboundp 'file-attribute-type) @@ -190,14 +168,23 @@ This is a floating point number if the size is too large for an integer." This is a string of ten letters or dashes as in ls -l." (nth 8 attributes))) -;; `default-toplevel-value' has been declared in Emacs 24.4. -(unless (fboundp 'default-toplevel-value) - (defalias 'default-toplevel-value 'symbol-value)) - ;; `format-message' is new in Emacs 25.1. (unless (fboundp 'format-message) (defalias 'format-message 'format)) +;; `directory-name-p' is new in Emacs 25.1. +(if (fboundp 'directory-name-p) + (defalias 'tramp-compat-directory-name-p 'directory-name-p) + (defsubst tramp-compat-directory-name-p (name) + "Return non-nil if NAME ends with a directory separator character." + (let ((len (length name)) + (lastc ?.)) + (if (> len 0) + (setq lastc (aref name (1- len)))) + (or (= lastc ?/) + (and (memq system-type '(windows-nt ms-dos)) + (= lastc ?\\)))))) + ;; `file-missing' is introduced in Emacs 26.1. (defconst tramp-file-missing (if (get 'file-missing 'error-conditions) 'file-missing 'file-error) @@ -248,11 +235,11 @@ If NAME is a remote file name, the local part of NAME is unquoted." ((eq tramp-syntax 'sep) 'separate) (t tramp-syntax))) -;; Older Emacsen keep incompatible autoloaded values of `tramp-syntax'. -(eval-after-load 'tramp - '(unless - (memq tramp-syntax (tramp-compat-funcall (quote tramp-syntax-values))) - (tramp-change-syntax (tramp-compat-tramp-syntax)))) +;; `cl-struct-slot-info' has been introduced with Emacs 25. +(defmacro tramp-compat-tramp-file-name-slots () + (if (fboundp 'cl-struct-slot-info) + `(cdr (mapcar 'car (cl-struct-slot-info 'tramp-file-name))) + `(cdr (mapcar 'car (get 'tramp-file-name 'cl-struct-slots))))) (provide 'tramp-compat) diff --git a/lisp/net/tramp-ftp.el b/lisp/net/tramp-ftp.el index 8e489eee801..9fd2e6d9dec 100644 --- a/lisp/net/tramp-ftp.el +++ b/lisp/net/tramp-ftp.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -121,10 +121,10 @@ pass to the OPERATION." (or (boundp 'ange-ftp-name-format) (let (file-name-handler-alist) (require 'ange-ftp))) (let ((ange-ftp-name-format - (list (nth 0 (tramp-file-name-structure)) - (nth 3 (tramp-file-name-structure)) - (nth 2 (tramp-file-name-structure)) - (nth 4 (tramp-file-name-structure)))) + (list (nth 0 tramp-file-name-structure) + (nth 3 tramp-file-name-structure) + (nth 2 tramp-file-name-structure) + (nth 4 tramp-file-name-structure))) ;; ange-ftp uses `ange-ftp-ftp-name-arg' and `ange-ftp-ftp-name-res' ;; for optimization in `ange-ftp-ftp-name'. If Tramp wasn't active, ;; there could be incorrect values from previous calls in case the diff --git a/lisp/net/tramp-gvfs.el b/lisp/net/tramp-gvfs.el index 4c750df3c40..fe5a98909e0 100644 --- a/lisp/net/tramp-gvfs.el +++ b/lisp/net/tramp-gvfs.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -416,6 +416,19 @@ Every entry is a list (NAME ADDRESS).") (defconst tramp-hal-interface-device "org.freedesktop.Hal.Device" "The device interface of the HAL daemon.") +;; "gvfs-<command>" utilities have been deprecated in GVFS 1.31.1. We +;; must use "gio <command>" tool instead. +(defconst tramp-gvfs-gio-mapping + '(("gvfs-copy" . "copy") + ("gvfs-info" . "info") + ("gvfs-ls" . "list") + ("gvfs-mkdir" . "mkdir") + ("gvfs-monitor-file" . "monitor") + ("gvfs-move" . "move") + ("gvfs-rm" . "remove") + ("gvfs-trash" . "trash")) + "List of cons cells, mapping \"gvfs-<command>\" to \"gio <command>\".") + (defconst tramp-gvfs-file-attributes '("name" "type" @@ -448,12 +461,24 @@ Every entry is a list (NAME ADDRESS).") ":[[:blank:]]+\\(.*\\)$") "Regexp to parse GVFS file attributes with `gvfs-info'.") +(defconst tramp-gvfs-file-system-attributes + '("filesystem::free" + "filesystem::size" + "filesystem::used") + "GVFS file system attributes.") + +(defconst tramp-gvfs-file-system-attributes-regexp + (concat "^[[:blank:]]*" + (regexp-opt tramp-gvfs-file-system-attributes t) + ":[[:blank:]]+\\(.*\\)$") + "Regexp to parse GVFS file system attributes with `gvfs-info'.") + ;; New handlers should be added here. ;;;###tramp-autoload (defconst tramp-gvfs-file-name-handler-alist '((access-file . ignore) - (add-name-to-file . tramp-gvfs-handle-copy-file) + (add-name-to-file . tramp-handle-add-name-to-file) ;; `byte-compiler-base-file-name' performed by default handler. ;; `copy-directory' performed by default handler. (copy-file . tramp-gvfs-handle-copy-file) @@ -492,9 +517,10 @@ Every entry is a list (NAME ADDRESS).") (file-readable-p . tramp-gvfs-handle-file-readable-p) (file-regular-p . tramp-handle-file-regular-p) (file-remote-p . tramp-handle-file-remote-p) - (file-selinux-context . ignore) + (file-selinux-context . tramp-handle-file-selinux-context) (file-symlink-p . tramp-handle-file-symlink-p) - ;; `file-truename' performed by default handler. + (file-system-info . tramp-gvfs-handle-file-system-info) + (file-truename . tramp-handle-file-truename) (file-writable-p . tramp-gvfs-handle-file-writable-p) (find-backup-file-name . tramp-handle-find-backup-file-name) ;; `find-file-noselect' performed by default handler. @@ -649,6 +675,11 @@ file names." (unless (memq op '(copy rename)) (error "Unknown operation `%s', must be `copy' or `rename'" op)) + (if (file-directory-p filename) + (progn + (copy-directory filename newname keep-date t) + (when (eq op 'rename) (delete-directory filename 'recursive))) + (let ((t1 (tramp-tramp-file-p filename)) (t2 (tramp-tramp-file-p newname)) (equal-remote (tramp-equal-remote filename newname)) @@ -658,8 +689,7 @@ file names." (with-parsed-tramp-file-name (if t1 filename newname) nil (when (and (not ok-if-already-exists) (file-exists-p newname)) - (tramp-error - v 'file-already-exists "File %s already exists" newname)) + (tramp-error v 'file-already-exists newname)) (if (or (and equal-remote (tramp-get-connection-property v "direct-copy-failed" nil)) @@ -713,7 +743,7 @@ file names." (when t2 (with-parsed-tramp-file-name newname nil (tramp-flush-file-property v (file-name-directory localname)) - (tramp-flush-file-property v localname))))))) + (tramp-flush-file-property v localname)))))))) (defun tramp-gvfs-handle-copy-file (filename newname &optional ok-if-already-exists keep-date @@ -826,7 +856,7 @@ file names." (let ((last-coding-system-used last-coding-system-used) result) (with-parsed-tramp-file-name directory nil - (with-tramp-file-property v localname "directory-gvfs-attributes" + (with-tramp-file-property v localname "directory-attributes" (tramp-message v 5 "directory gvfs attributes: %s" localname) ;; Send command. (tramp-gvfs-send-command @@ -861,23 +891,34 @@ file names." (forward-line))) result))))) -(defun tramp-gvfs-get-root-attributes (filename) - "Return GVFS attributes association list of FILENAME." +(defun tramp-gvfs-get-root-attributes (filename &optional file-system) + "Return GVFS attributes association list of FILENAME. +If FILE-SYSTEM is non-nil, return file system attributes." (ignore-errors ;; Don't modify `last-coding-system-used' by accident. (let ((last-coding-system-used last-coding-system-used) result) (with-parsed-tramp-file-name filename nil - (with-tramp-file-property v localname "file-gvfs-attributes" - (tramp-message v 5 "file gvfs attributes: %s" localname) + (with-tramp-file-property + v localname + (if file-system "file-system-attributes" "file-attributes") + (tramp-message + v 5 "file%s gvfs attributes: %s" + (if file-system " system" "") localname) ;; Send command. - (tramp-gvfs-send-command - v "gvfs-info" (tramp-gvfs-url-file-name filename)) + (if file-system + (tramp-gvfs-send-command + v "gvfs-info" "--filesystem" (tramp-gvfs-url-file-name filename)) + (tramp-gvfs-send-command + v "gvfs-info" (tramp-gvfs-url-file-name filename))) ;; Parse output. (with-current-buffer (tramp-get-connection-buffer v) (goto-char (point-min)) (while (re-search-forward - tramp-gvfs-file-attributes-with-gvfs-info-regexp nil t) + (if file-system + tramp-gvfs-file-system-attributes-regexp + tramp-gvfs-file-attributes-with-gvfs-info-regexp) + nil t) (push (cons (match-string 1) (match-string 2)) result)) result)))))) @@ -951,7 +992,7 @@ file names." (tramp-file-mode-from-int (string-to-number n)) (format "%s%s%s%s------" - (if dirp "d" "-") + (if dirp "d" (if res-symlink-target "l" "-")) (if (equal (cdr (assoc "access::can-read" attributes)) "FALSE") "-" "r") @@ -1015,11 +1056,11 @@ file names." (defun tramp-gvfs-handle-file-local-copy (filename) "Like `file-local-copy' for Tramp files." (with-parsed-tramp-file-name filename nil + (unless (file-exists-p filename) + (tramp-error + v tramp-file-missing + "Cannot make local copy of non-existing file `%s'" filename)) (let ((tmpfile (tramp-compat-make-temp-file filename))) - (unless (file-exists-p filename) - (tramp-error - v tramp-file-missing - "Cannot make local copy of non-existing file `%s'" filename)) (copy-file filename tmpfile 'ok-if-already-exists 'keep-time) tmpfile))) @@ -1055,9 +1096,12 @@ file names." ((memq 'change flags) '(created changed changes-done-hint moved deleted)) ((memq 'attribute-change flags) '(attribute-changed)))) - (p (start-process - "gvfs-monitor-file" (generate-new-buffer " *gvfs-monitor-file*") - "gvfs-monitor-file" (tramp-gvfs-url-file-name file-name)))) + (p (apply + 'start-process + "gvfs-monitor" (generate-new-buffer " *gvfs-monitor*") + (if (tramp-gvfs-gio-tool-p v) + `("gio" "monitor" ,(tramp-gvfs-url-file-name file-name))) + `("gvfs-monitor-file" (tramp-gvfs-url-file-name file-name))))) (if (not (processp p)) (tramp-error v 'file-notify-error "Monitoring not supported for `%s'" file-name) @@ -1128,6 +1172,22 @@ file-notify events." (with-tramp-file-property v localname "file-readable-p" (tramp-check-cached-permissions v ?r)))) +(defun tramp-gvfs-handle-file-system-info (filename) + "Like `file-system-info' for Tramp files." + (setq filename (directory-file-name (expand-file-name filename))) + (with-parsed-tramp-file-name filename nil + ;; We don't use cached values. + (tramp-set-file-property v localname "file-system-attributes" 'undef) + (let* ((attr (tramp-gvfs-get-root-attributes filename 'file-system)) + (size (cdr (assoc "filesystem::size" attr))) + (used (cdr (assoc "filesystem::used" attr))) + (free (cdr (assoc "filesystem::free" attr)))) + (when (and (stringp size) (stringp used) (stringp free)) + (list (string-to-number (concat size "e0")) + (- (string-to-number (concat size "e0")) + (string-to-number (concat used "e0"))) + (string-to-number (concat free "e0"))))))) + (defun tramp-gvfs-handle-file-writable-p (filename) "Like `file-writable-p' for Tramp files." (with-parsed-tramp-file-name filename nil @@ -1151,8 +1211,9 @@ file-notify events." (when (and parents (not (file-directory-p ldir))) (make-directory ldir parents)) ;; Just do it. - (unless (tramp-gvfs-send-command - v "gvfs-mkdir" (tramp-gvfs-url-file-name dir)) + (unless (or (tramp-gvfs-send-command + v "gvfs-mkdir" (tramp-gvfs-url-file-name dir)) + (and parents (file-directory-p dir))) (tramp-error v 'file-error "Couldn't make directory %s" dir)))))) (defun tramp-gvfs-handle-rename-file @@ -1172,12 +1233,16 @@ file-notify events." 'rename-file (list filename newname ok-if-already-exists)))) (defun tramp-gvfs-handle-write-region - (start end filename &optional append visit lockname confirm) + (start end filename &optional append visit lockname mustbenew) "Like `write-region' for Tramp files." + (setq filename (expand-file-name filename)) (with-parsed-tramp-file-name filename nil - (when (and confirm (file-exists-p filename)) - (unless (y-or-n-p (format "File %s exists; overwrite anyway? " filename)) - (tramp-error v 'file-error "File not overwritten"))) + (when (and mustbenew (file-exists-p filename) + (or (eq mustbenew 'excl) + (not + (y-or-n-p + (format "File %s exists; overwrite anyway? " filename))))) + (tramp-error v 'file-already-exists filename)) (let ((tmpfile (tramp-compat-make-temp-file filename))) (when (and append (file-exists-p filename)) @@ -1186,10 +1251,7 @@ file-notify events." ;; modtime data to be clobbered from the temp file. We call ;; `set-visited-file-modtime' ourselves later on. (tramp-run-real-handler - 'write-region - (if confirm ; don't pass this arg unless defined for backward compat. - (list start end tmpfile append 'no-message lockname confirm) - (list start end tmpfile append 'no-message lockname))) + 'write-region (list start end tmpfile append 'no-message lockname)) (condition-case nil (rename-file tmpfile filename 'ok-if-already-exists) (error @@ -1230,7 +1292,8 @@ file-notify events." (when (and user domain) (setq user (concat domain ";" user))) (url-parse-make-urlobj - method (and user (url-hexify-string user)) nil host + method (and user (url-hexify-string user)) + nil (and host (url-hexify-string host)) (if (stringp port) (string-to-number port) port) (and localname (url-hexify-string localname)) nil nil t)) (url-parse-make-urlobj @@ -1745,10 +1808,16 @@ connection if a previous connection has died for some reason." (tramp-gvfs-get-remote-uid vec 'string) (tramp-gvfs-get-remote-gid vec 'string)))) +(defun tramp-gvfs-gio-tool-p (vec) + "Check, whether the gio tool is available." + (with-tramp-connection-property vec "gio-tool" + (zerop (tramp-call-process vec "gio" nil nil nil "version")))) + (defun tramp-gvfs-send-command (vec command &rest args) "Send the COMMAND with its ARGS to connection VEC. -COMMAND is usually a command from the gvfs-* utilities. -`call-process' is applied, and it returns t if the return code is zero." +COMMAND is a command from the gvfs-* utilities. It is replaced +by the corresponding gio tool call if available. `call-process' +is applied, and it returns t if the return code is zero." (let* ((locale (tramp-get-local-locale vec)) (process-environment (append @@ -1756,6 +1825,11 @@ COMMAND is usually a command from the gvfs-* utilities. ,(format "LANGUAGE=%s" locale) ,(format "LC_ALL=%s" locale)) process-environment))) + (when (tramp-gvfs-gio-tool-p vec) + ;; Use gio tool. + (setq args (cons (cdr (assoc command tramp-gvfs-gio-mapping)) args) + command "gio")) + (with-current-buffer (tramp-get-connection-buffer vec) (tramp-gvfs-maybe-open-connection vec) (erase-buffer) diff --git a/lisp/net/tramp-sh.el b/lisp/net/tramp-sh.el index f7b457ebf04..acb5a12ba2a 100644 --- a/lisp/net/tramp-sh.el +++ b/lisp/net/tramp-sh.el @@ -23,7 +23,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: @@ -533,9 +533,7 @@ the list by the special value `tramp-own-remote-path'." ;;;###tramp-autoload (defcustom tramp-remote-process-environment - `("ENV=''" "TMOUT=0" "LC_CTYPE=''" - ,(format "TERM=%s" tramp-terminal-type) - ,(format "INSIDE_EMACS='%s,tramp:%s'" emacs-version tramp-version) + '("ENV=''" "TMOUT=0" "LC_CTYPE=''" "CDPATH=" "HISTORY=" "MAIL=" "MAILCHECK=" "MAILPATH=" "PAGER=cat" "autocorrect=" "correct=") "List of environment variables to be set on the remote host. @@ -544,8 +542,15 @@ Each element should be a string of the form ENVVARNAME=VALUE. An entry ENVVARNAME= disables the corresponding environment variable, which might have been set in the init files like ~/.profile. -Special handling is applied to the PATH environment, which should -not be set here. Instead, it should be set via `tramp-remote-path'." +Special handling is applied to some environment variables, +which should not be set here: + +The PATH environment variable should be set via `tramp-remote-path'. + +The TERM environment variable should be set via `tramp-terminal-type'. + +The INSIDE_EMACS environment variable will automatically be set +based on the TRAMP and Emacs versions, and should not be set here." :group 'tramp :version "26.1" :type '(repeat string) @@ -562,11 +567,7 @@ This variable is only used when Tramp needs to start up another shell for tilde expansion. The extra arguments should typically prevent the shell from reading its init file." :group 'tramp - ;; This might be the wrong way to test whether the widget type - ;; `alist' is available. Who knows the right way to test it? - :type (if (get 'alist 'widget-type) - '(alist :key-type string :value-type string) - '(repeat (cons string string))) + :type '(alist :key-type regexp :value-type string) :require 'tramp) (defconst tramp-actions-before-shell @@ -617,7 +618,7 @@ use Cwd \"realpath\"; sub myrealpath { my ($file) = @_; - return realpath($file) if -e $file; + return realpath($file) if (-e $file || -l $file); } sub recursive { @@ -1024,6 +1025,7 @@ of command line.") (file-remote-p . tramp-handle-file-remote-p) (file-selinux-context . tramp-sh-handle-file-selinux-context) (file-symlink-p . tramp-handle-file-symlink-p) + (file-system-info . tramp-sh-handle-file-system-info) (file-truename . tramp-sh-handle-file-truename) (file-writable-p . tramp-sh-handle-file-writable-p) (find-backup-file-name . tramp-handle-find-backup-file-name) @@ -1057,63 +1059,69 @@ Operations not mentioned here will be handled by the normal Emacs functions.") ;;; File Name Handler Functions: (defun tramp-sh-handle-make-symbolic-link - (filename linkname &optional ok-if-already-exists) + (target linkname &optional ok-if-already-exists) "Like `make-symbolic-link' for Tramp files. -If LINKNAME is a non-Tramp file, it is used verbatim as the target of -the symlink. If LINKNAME is a Tramp file, only the localname component is -used as the target of the symlink. - -If LINKNAME is a Tramp file and the localname component is relative, then -it is expanded first, before the localname component is taken. Note that -this can give surprising results if the user/host for the source and -target of the symlink differ." - (with-parsed-tramp-file-name linkname l - (let ((ln (tramp-get-remote-ln l)) - (cwd (tramp-run-real-handler - 'file-name-directory (list l-localname)))) - (unless ln - (tramp-error - l 'file-error - "Making a symbolic link. ln(1) does not exist on the remote host.")) - - ;; Do the 'confirm if exists' thing. - (when (file-exists-p linkname) - ;; What to do? - (if (or (null ok-if-already-exists) ; not allowed to exist - (and (numberp ok-if-already-exists) - (not (yes-or-no-p +If TARGET is a non-Tramp file, it is used verbatim as the target +of the symlink. If TARGET is a Tramp file, only the localname +component is used as the target of the symlink." + (if (not (tramp-tramp-file-p (expand-file-name linkname))) + (tramp-run-real-handler + 'make-symbolic-link (list target linkname ok-if-already-exists)) + + (with-parsed-tramp-file-name linkname nil + ;; If TARGET is a Tramp name, use just the localname component. + (when (and (tramp-tramp-file-p target) + (tramp-file-name-equal-p v (tramp-dissect-file-name target))) + (setq target + (tramp-file-name-localname + (tramp-dissect-file-name (expand-file-name target))))) + + ;; If TARGET is still remote, quote it. + (if (tramp-tramp-file-p target) + (make-symbolic-link + (let (file-name-handler-alist) (tramp-compat-file-name-quote target)) + linkname ok-if-already-exists) + + (let ((ln (tramp-get-remote-ln v)) + (cwd (tramp-run-real-handler + 'file-name-directory (list localname)))) + (unless ln + (tramp-error + v 'file-error + "Making a symbolic link. ln(1) does not exist on the remote host.")) + + ;; Do the 'confirm if exists' thing. + (when (file-exists-p linkname) + ;; What to do? + (if (or (null ok-if-already-exists) ; not allowed to exist + (and (numberp ok-if-already-exists) + (not + (yes-or-no-p (format "File %s already exists; make it a link anyway? " - l-localname))))) - (tramp-error - l 'file-already-exists "File %s already exists" l-localname) - (delete-file linkname))) + localname))))) + (tramp-error v 'file-already-exists localname) + (delete-file linkname))) - ;; If FILENAME is a Tramp name, use just the localname component. - (when (tramp-tramp-file-p filename) - (setq filename - (tramp-file-name-localname - (tramp-dissect-file-name (expand-file-name filename))))) - - (tramp-flush-file-property l (file-name-directory l-localname)) - (tramp-flush-file-property l l-localname) - - ;; Right, they are on the same host, regardless of user, method, - ;; etc. We now make the link on the remote machine. This will - ;; occur as the user that FILENAME belongs to. - (and (tramp-send-command-and-check - l (format "cd %s" (tramp-shell-quote-argument cwd))) - (tramp-send-command-and-check - l (format - "%s -sf %s %s" - ln - (tramp-shell-quote-argument filename) - ;; The command could exceed PATH_MAX, so we use - ;; relative file names. However, relative file names - ;; could start with "-". `tramp-shell-quote-argument' - ;; does not handle this, we must do it ourselves. - (tramp-shell-quote-argument - (concat "./" (file-name-nondirectory l-localname))))))))) + (tramp-flush-file-property v (file-name-directory localname)) + (tramp-flush-file-property v localname) + + ;; Right, they are on the same host, regardless of user, + ;; method, etc. We now make the link on the remote + ;; machine. This will occur as the user that TARGET belongs to. + (and (tramp-send-command-and-check + v (format "cd %s" (tramp-shell-quote-argument cwd))) + (tramp-send-command-and-check + v (format + "%s -sf %s %s" ln + (tramp-shell-quote-argument target) + ;; The command could exceed PATH_MAX, so we use + ;; relative file names. However, relative file + ;; names could start with "-". + ;; `tramp-shell-quote-argument' does not handle + ;; this, we must do it ourselves. + (tramp-shell-quote-argument + (concat "./" (file-name-nondirectory localname))))))))))) (defun tramp-sh-handle-file-truename (filename) "Like `file-truename' for Tramp files." @@ -1191,16 +1199,6 @@ target of the symlink differ." (setq numchase (1+ numchase)) (when (file-name-absolute-p symlink-target) (setq result nil)) - ;; If the symlink was absolute, we'll get a - ;; string like "/user@host:/some/target"; - ;; extract the "/some/target" part from it. - (when (tramp-tramp-file-p symlink-target) - (unless (tramp-equal-remote filename symlink-target) - (tramp-error - v 'file-error - "Symlink target `%s' on wrong host" - symlink-target)) - (setq symlink-target localname)) (setq steps (append (split-string symlink-target "/" 'omit) steps))) @@ -1220,7 +1218,17 @@ target of the symlink differ." (when (string= "" result) (setq result "/"))))) - (when quoted (setq result (tramp-compat-file-name-quote result))) + ;; Detect cycle. + (when (and (file-symlink-p filename) + (string-equal result localname)) + (tramp-error + v 'file-error + "Apparent cycle of symbolic links for %s" filename)) + ;; If the resulting localname looks remote, we must quote it + ;; for security reasons. + (when (or quoted (file-remote-p result)) + (let (file-name-handler-alist) + (setq result (tramp-compat-file-name-quote result)))) (tramp-message v 4 "True name of `%s' is `%s'" localname result) result)))) @@ -1919,16 +1927,18 @@ tramp-sh-handle-file-name-all-completions: internal error accessing `%s': `%s'" (with-parsed-tramp-file-name filename v1 (with-parsed-tramp-file-name newname v2 (let ((ln (when v1 (tramp-get-remote-ln v1)))) - (when (and (numberp ok-if-already-exists) - (file-exists-p newname) - (yes-or-no-p - (format - "File %s already exists; make it a new name anyway? " - newname))) - (tramp-error - v2 'file-already-exists - "add-name-to-file: file %s already exists" newname)) - (when ok-if-already-exists (setq ln (concat ln " -f"))) + + ;; Do the 'confirm if exists' thing. + (when (file-exists-p newname) + ;; What to do? + (if (or (null ok-if-already-exists) ; not allowed to exist + (and (numberp ok-if-already-exists) + (not (yes-or-no-p + (format + "File %s already exists; make it a link anyway? " + v2-localname))))) + (tramp-error v2 'file-already-exists newname) + (delete-file newname))) (tramp-flush-file-property v2 (file-name-directory v2-localname)) (tramp-flush-file-property v2 v2-localname) (tramp-barf-unless-okay @@ -1972,24 +1982,26 @@ tramp-sh-handle-file-name-all-completions: internal error accessing `%s': `%s'" (tramp-dissect-file-name newname))))) ;; scp or rsync DTRT. (progn + (when (and (file-directory-p newname) + (not (tramp-compat-directory-name-p newname))) + (tramp-error v 'file-already-exists newname)) (setq dirname (directory-file-name (expand-file-name dirname)) newname (directory-file-name (expand-file-name newname))) - (if (and (file-directory-p newname) - (not (string-equal (file-name-nondirectory dirname) - (file-name-nondirectory newname)))) - (setq newname - (expand-file-name - (file-name-nondirectory dirname) newname))) - (if (not (file-directory-p (file-name-directory newname))) + (when (and (file-directory-p newname) + (not (string-equal (file-name-nondirectory dirname) + (file-name-nondirectory newname)))) + (setq newname + (expand-file-name + (file-name-nondirectory dirname) newname))) + (when (not (file-directory-p (file-name-directory newname))) (make-directory (file-name-directory newname) parents)) (tramp-do-copy-or-rename-file-out-of-band 'copy dirname newname keep-date)) + ;; We must do it file-wise. (tramp-run-real-handler 'copy-directory - (if copy-contents - (list dirname newname keep-date parents copy-contents) - (list dirname newname keep-date parents)))) + (list dirname newname keep-date parents copy-contents))) ;; When newname did exist, we have wrong cached values. (when t2 @@ -2032,97 +2044,102 @@ of `copy' and `rename'. FILENAME and NEWNAME must be absolute file names." (unless (memq op '(copy rename)) (error "Unknown operation `%s', must be `copy' or `rename'" op)) - (let ((t1 (tramp-tramp-file-p filename)) - (t2 (tramp-tramp-file-p newname)) - (length (tramp-compat-file-attribute-size - (file-attributes (file-truename filename)))) - (attributes (and preserve-extended-attributes - (apply 'file-extended-attributes (list filename))))) - - (with-parsed-tramp-file-name (if t1 filename newname) nil - (when (and (not ok-if-already-exists) (file-exists-p newname)) - (tramp-error - v 'file-already-exists "File %s already exists" newname)) - - (with-tramp-progress-reporter - v 0 (format "%s %s to %s" - (if (eq op 'copy) "Copying" "Renaming") - filename newname) - (cond - ;; Both are Tramp files. - ((and t1 t2) - (with-parsed-tramp-file-name filename v1 - (with-parsed-tramp-file-name newname v2 - (cond - ;; Shortcut: if method, host, user are the same for - ;; both files, we invoke `cp' or `mv' on the remote - ;; host directly. - ((tramp-equal-remote filename newname) - (tramp-do-copy-or-rename-file-directly - op filename newname - ok-if-already-exists keep-date preserve-uid-gid)) - - ;; Try out-of-band operation. - ((and - (tramp-method-out-of-band-p v1 length) - (tramp-method-out-of-band-p v2 length)) - (tramp-do-copy-or-rename-file-out-of-band - op filename newname keep-date)) - - ;; No shortcut was possible. So we copy the file - ;; first. If the operation was `rename', we go back - ;; and delete the original file (if the copy was - ;; successful). The approach is simple-minded: we - ;; create a new buffer, insert the contents of the - ;; source file into it, then write out the buffer to - ;; the target file. The advantage is that it doesn't - ;; matter which file name handlers are used for the - ;; source and target file. - (t - (tramp-do-copy-or-rename-file-via-buffer - op filename newname keep-date)))))) - - ;; One file is a Tramp file, the other one is local. - ((or t1 t2) - (cond - ;; Fast track on local machine. - ((tramp-local-host-p v) - (tramp-do-copy-or-rename-file-directly - op filename newname - ok-if-already-exists keep-date preserve-uid-gid)) + (if (file-directory-p filename) + (progn + (copy-directory filename newname keep-date t) + (when (eq op 'rename) (delete-directory filename 'recursive))) - ;; If the Tramp file has an out-of-band method, the - ;; corresponding copy-program can be invoked. - ((tramp-method-out-of-band-p v length) - (tramp-do-copy-or-rename-file-out-of-band - op filename newname keep-date)) - - ;; Use the inline method via a Tramp buffer. - (t (tramp-do-copy-or-rename-file-via-buffer - op filename newname keep-date)))) + (let ((t1 (tramp-tramp-file-p filename)) + (t2 (tramp-tramp-file-p newname)) + (length (tramp-compat-file-attribute-size + (file-attributes (file-truename filename)))) + (attributes (and preserve-extended-attributes + (apply 'file-extended-attributes (list filename))))) - (t - ;; One of them must be a Tramp file. - (error "Tramp implementation says this cannot happen"))) + (with-parsed-tramp-file-name (if t1 filename newname) nil + (when (and (not ok-if-already-exists) (file-exists-p newname)) + (tramp-error v 'file-already-exists newname)) - ;; Handle `preserve-extended-attributes'. We ignore possible - ;; errors, because ACL strings could be incompatible. - (when attributes - (ignore-errors - (apply 'set-file-extended-attributes (list newname attributes)))) + (with-tramp-progress-reporter + v 0 (format "%s %s to %s" + (if (eq op 'copy) "Copying" "Renaming") + filename newname) - ;; In case of `rename', we must flush the cache of the source file. - (when (and t1 (eq op 'rename)) - (with-parsed-tramp-file-name filename v1 - (tramp-flush-file-property v1 (file-name-directory v1-localname)) - (tramp-flush-file-property v1 v1-localname))) + (cond + ;; Both are Tramp files. + ((and t1 t2) + (with-parsed-tramp-file-name filename v1 + (with-parsed-tramp-file-name newname v2 + (cond + ;; Shortcut: if method, host, user are the same for + ;; both files, we invoke `cp' or `mv' on the remote + ;; host directly. + ((tramp-equal-remote filename newname) + (tramp-do-copy-or-rename-file-directly + op filename newname + ok-if-already-exists keep-date preserve-uid-gid)) + + ;; Try out-of-band operation. + ((and + (tramp-method-out-of-band-p v1 length) + (tramp-method-out-of-band-p v2 length)) + (tramp-do-copy-or-rename-file-out-of-band + op filename newname keep-date)) + + ;; No shortcut was possible. So we copy the file + ;; first. If the operation was `rename', we go back + ;; and delete the original file (if the copy was + ;; successful). The approach is simple-minded: we + ;; create a new buffer, insert the contents of the + ;; source file into it, then write out the buffer to + ;; the target file. The advantage is that it doesn't + ;; matter which file name handlers are used for the + ;; source and target file. + (t + (tramp-do-copy-or-rename-file-via-buffer + op filename newname keep-date)))))) + + ;; One file is a Tramp file, the other one is local. + ((or t1 t2) + (cond + ;; Fast track on local machine. + ((tramp-local-host-p v) + (tramp-do-copy-or-rename-file-directly + op filename newname + ok-if-already-exists keep-date preserve-uid-gid)) + + ;; If the Tramp file has an out-of-band method, the + ;; corresponding copy-program can be invoked. + ((tramp-method-out-of-band-p v length) + (tramp-do-copy-or-rename-file-out-of-band + op filename newname keep-date)) + + ;; Use the inline method via a Tramp buffer. + (t (tramp-do-copy-or-rename-file-via-buffer + op filename newname keep-date)))) - ;; When newname did exist, we have wrong cached values. - (when t2 - (with-parsed-tramp-file-name newname v2 - (tramp-flush-file-property v2 (file-name-directory v2-localname)) - (tramp-flush-file-property v2 v2-localname))))))) + (t + ;; One of them must be a Tramp file. + (error "Tramp implementation says this cannot happen"))) + + ;; Handle `preserve-extended-attributes'. We ignore possible + ;; errors, because ACL strings could be incompatible. + (when attributes + (ignore-errors + (apply 'set-file-extended-attributes (list newname attributes)))) + + ;; In case of `rename', we must flush the cache of the source file. + (when (and t1 (eq op 'rename)) + (with-parsed-tramp-file-name filename v1 + (tramp-flush-file-property v1 (file-name-directory v1-localname)) + (tramp-flush-file-property v1 v1-localname))) + + ;; When newname did exist, we have wrong cached values. + (when t2 + (with-parsed-tramp-file-name newname v2 + (tramp-flush-file-property v2 (file-name-directory v2-localname)) + (tramp-flush-file-property v2 v2-localname)))))))) (defun tramp-do-copy-or-rename-file-via-buffer (op filename newname keep-date) "Use an Emacs buffer to copy or rename a file. @@ -2734,6 +2751,17 @@ The method used must be an out-of-band method." beg 'noerror) (replace-match (file-relative-name filename) t)) + ;; Try to insert the amount of free space. + (goto-char (point-min)) + ;; First find the line to put it on. + (when (re-search-forward "^\\([[:space:]]*total\\)" nil t) + (let ((available (get-free-disk-space "."))) + (when available + ;; Replace "total" with "total used", to avoid confusion. + (replace-match "\\1 used in directory") + (end-of-line) + (insert " available " available)))) + (goto-char (point-max))))))) ;; Canonicalization of file names. @@ -2879,7 +2907,8 @@ the result will be a local, non-Tramp, file name." ;; We do not want to raise an error when ;; `start-file-process' has been started several times in ;; `eshell' and friends. - (tramp-current-connection nil)) + (tramp-current-connection nil) + p) (while (get-process name1) ;; NAME must be unique as process name. @@ -2909,33 +2938,37 @@ the result will be a local, non-Tramp, file name." ;; to cleanup the prompt afterwards. (catch 'suppress (tramp-maybe-open-connection v) + (setq p (tramp-get-connection-process v)) + ;; Set the pid of the remote shell. This is + ;; needed when sending signals remotely. + (let ((pid (tramp-send-command-and-read v "echo $$"))) + (process-put p 'remote-pid pid) + (tramp-set-connection-property p "remote-pid" pid)) (widen) - (delete-region mark (point)) + (delete-region mark (point-max)) (narrow-to-region (point-max) (point-max)) ;; Now do it. (if command ;; Send the command. (tramp-send-command v command nil t) ; nooutput ;; Check, whether a pty is associated. - (unless (process-get - (tramp-get-connection-process v) 'remote-tty) + (unless (process-get p 'remote-tty) (tramp-error v 'file-error "pty association is not supported for `%s'" name)))) - (let ((p (tramp-get-connection-process v))) - ;; Set query flag and process marker for this - ;; process. We ignore errors, because the process - ;; could have finished already. - (ignore-errors - (set-process-query-on-exit-flag p t) - (set-marker (process-mark p) (point))) - ;; Return process. - p)))) + ;; Set query flag and process marker for this + ;; process. We ignore errors, because the process + ;; could have finished already. + (ignore-errors + (set-process-query-on-exit-flag p t) + (set-marker (process-mark p) (point))) + ;; Return process. + p))) ;; Save exit. (if (string-match tramp-temp-buffer-name (buffer-name)) (ignore-errors - (set-process-buffer (tramp-get-connection-process v) nil) + (set-process-buffer p nil) (kill-buffer (current-buffer))) (set-buffer-modified-p bmp)) (tramp-set-connection-property v "process-name" nil) @@ -3071,7 +3104,7 @@ the result will be a local, non-Tramp, file name." (defun tramp-sh-handle-file-local-copy (filename) "Like `file-local-copy' for Tramp files." (with-parsed-tramp-file-name filename nil - (unless (file-exists-p filename) + (unless (file-exists-p (file-truename filename)) (tramp-error v tramp-file-missing "Cannot make local copy of non-existing file `%s'" filename)) @@ -3150,23 +3183,16 @@ the result will be a local, non-Tramp, file name." ;; CCC grok LOCKNAME (defun tramp-sh-handle-write-region - (start end filename &optional append visit lockname confirm) + (start end filename &optional append visit lockname mustbenew) "Like `write-region' for Tramp files." (setq filename (expand-file-name filename)) (with-parsed-tramp-file-name filename nil - ;; Following part commented out because we don't know what to do about - ;; file locking, and it does not appear to be a problem to ignore it. - ;; Ange-ftp ignores it, too. - ;; (when (and lockname (stringp lockname)) - ;; (setq lockname (expand-file-name lockname))) - ;; (unless (or (eq lockname nil) - ;; (string= lockname filename)) - ;; (error - ;; "tramp-sh-handle-write-region: LOCKNAME must be nil or equal FILENAME")) - - (when (and confirm (file-exists-p filename)) - (unless (y-or-n-p (format "File %s exists; overwrite anyway? " filename)) - (tramp-error v 'file-error "File not overwritten"))) + (when (and mustbenew (file-exists-p filename) + (or (eq mustbenew 'excl) + (not + (y-or-n-p + (format "File %s exists; overwrite anyway? " filename))))) + (tramp-error v 'file-already-exists filename)) (let ((uid (or (tramp-compat-file-attribute-user-id (file-attributes filename 'integer)) @@ -3185,8 +3211,7 @@ the result will be a local, non-Tramp, file name." (file-writable-p localname))))) ;; Short track: if we are on the local host, we can run directly. (tramp-run-real-handler - 'write-region - (list start end localname append 'no-message lockname confirm)) + 'write-region (list start end localname append 'no-message lockname)) (let* ((modes (save-excursion (tramp-default-file-modes filename))) ;; We use this to save the value of @@ -3223,7 +3248,7 @@ the result will be a local, non-Tramp, file name." (condition-case err (tramp-run-real-handler 'write-region - (list start end tmpfile append 'no-message lockname confirm)) + (list start end tmpfile append 'no-message lockname)) ((error quit) (setq tramp-temp-buffer-file-name nil) (delete-file tmpfile) @@ -3429,10 +3454,12 @@ the result will be a local, non-Tramp, file name." (let (tramp-vc-registered-file-names (remote-file-name-inhibit-cache (current-time)) (file-name-handler-alist - `((,(tramp-file-name-regexp) . tramp-vc-file-name-handler)))) + `((,tramp-file-name-regexp . tramp-vc-file-name-handler)))) ;; Here we collect only file names, which need an operation. - (ignore-errors (tramp-run-real-handler 'vc-registered (list file))) + (tramp-with-demoted-errors + v "Error in 1st pass of `vc-registered': %s" + (tramp-run-real-handler 'vc-registered (list file))) (tramp-message v 10 "\n%s" tramp-vc-registered-file-names) ;; Send just one command, in order to fill the cache. @@ -3493,28 +3520,18 @@ the result will be a local, non-Tramp, file name." v vc-hg-program (tramp-get-remote-path v))))) (setq vc-handled-backends (remq 'Hg vc-handled-backends))) ;; Run. - (ignore-errors + (tramp-with-demoted-errors + v "Error in 2nd pass of `vc-registered': %s" (tramp-run-real-handler 'vc-registered (list file)))))))) ;;;###tramp-autoload (defun tramp-sh-file-name-handler (operation &rest args) "Invoke remote-shell Tramp file name handler. Fall back to normal file name handler if no Tramp handler exists." - (when (and tramp-locked (not tramp-locker)) - (setq tramp-locked nil) - (tramp-error - (car-safe tramp-current-connection) 'file-error - "Forbidden reentrant call of Tramp")) - (let ((tl tramp-locked)) - (setq tramp-locked t) - (unwind-protect - (let ((tramp-locker t)) - (save-match-data - (let ((fn (assoc operation tramp-sh-file-name-handler-alist))) - (if fn - (apply (cdr fn) args) - (tramp-run-real-handler operation args))))) - (setq tramp-locked tl)))) + (let ((fn (assoc operation tramp-sh-file-name-handler-alist))) + (if fn + (save-match-data (apply (cdr fn) args)) + (tramp-run-real-handler operation args)))) ;; This must be the last entry, because `identity' always matches. ;;;###tramp-autoload @@ -3707,6 +3724,30 @@ file-notify events." 'file-notify-handle-event `(file-notify ,object file-notify-callback))))))) +(defun tramp-sh-handle-file-system-info (filename) + "Like `file-system-info' for Tramp files." + (ignore-errors + (with-parsed-tramp-file-name (expand-file-name filename) nil + (when (tramp-get-remote-df v) + (tramp-message v 5 "file system info: %s" localname) + (tramp-send-command + v (format + "%s --block-size=1 --output=size,used,avail %s" + (tramp-get-remote-df v) (tramp-shell-quote-argument localname))) + (with-current-buffer (tramp-get-connection-buffer v) + (goto-char (point-min)) + (forward-line) + (when (looking-at + (concat "[[:space:]]*\\([[:digit:]]+\\)" + "[[:space:]]+\\([[:digit:]]+\\)" + "[[:space:]]+\\([[:digit:]]+\\)")) + (list (string-to-number (concat (match-string 1) "e0")) + ;; The second value is the used size. We need the + ;; free size. + (- (string-to-number (concat (match-string 1) "e0")) + (string-to-number (concat (match-string 2) "e0"))) + (string-to-number (concat (match-string 3) "e0"))))))))) + ;;; Internal Functions: (defun tramp-maybe-send-script (vec script name) @@ -3912,9 +3953,17 @@ file exists and nonzero exit status otherwise." ;; file clobbering $PS1. $PROMPT_COMMAND is another way to set ;; the prompt in /bin/bash, it must be discarded as well. ;; $HISTFILE is set according to `tramp-histfile-override'. + ;; $TERM and $INSIDE_EMACS set here to ensure they have the + ;; correct values when the shell starts, not just processes + ;; run within the shell. (Which processes include our + ;; initial probes to ensure the remote shell is usable.) (tramp-send-command vec (format - "exec env ENV=%s %s PROMPT_COMMAND='' PS1=%s PS2='' PS3='' %s %s" + (concat + "exec env TERM='%s' INSIDE_EMACS='%s,tramp:%s' " + "ENV=%s %s PROMPT_COMMAND='' PS1=%s PS2='' PS3='' %s %s") + tramp-terminal-type + emacs-version tramp-version ; INSIDE_EMACS (or (getenv-internal "ENV" tramp-remote-process-environment) "") (if (stringp tramp-histfile-override) (format "HISTFILE=%s" @@ -4131,7 +4180,8 @@ process to set up. VEC specifies the connection." ;; Set `remote-tty' process property. (let ((tty (tramp-send-command-and-read vec "echo \\\"`tty`\\\"" 'noerror))) (unless (zerop (length tty)) - (process-put proc 'remote-tty tty))) + (process-put proc 'remote-tty tty) + (tramp-set-connection-property proc "remote-tty" tty))) ;; Dump stty settings in the traces. (when (>= tramp-verbose 9) @@ -4467,7 +4517,7 @@ Goes through the list `tramp-inline-compress-commands'." (let ((user (tramp-file-name-user item)) (host (tramp-file-name-host item)) (proxy (concat - (tramp-prefix-format) proxy (tramp-postfix-host-format)))) + tramp-prefix-format proxy tramp-postfix-host-format))) (tramp-message vec 5 "Add proxy (\"%s\" \"%s\" \"%s\")" (and (stringp host) (regexp-quote host)) @@ -5409,6 +5459,17 @@ This command is returned only if `delete-by-moving-to-trash' is non-nil." (delete-file tmpfile)) result))) +(defun tramp-get-remote-df (vec) + "Determine remote `df' command." + (with-tramp-connection-property vec "df" + (tramp-message vec 5 "Finding a suitable `df' command") + (let ((result (tramp-find-executable vec "df" (tramp-get-remote-path vec)))) + (and + result + (tramp-send-command-and-check + vec (format "%s --block-size=1 --output=size,used,avail /" result)) + result)))) + (defun tramp-get-remote-gvfs-monitor-dir (vec) "Determine remote `gvfs-monitor-dir' command." (with-tramp-connection-property vec "gvfs-monitor-dir" @@ -5707,9 +5768,6 @@ function cell is returned to be applied on a buffer." ;; * Reconnect directly to a compliant shell without first going ;; through the user's default shell. (Pete Forman) ;; -;; * How can I interrupt the remote process with a signal -;; (interrupt-process seems not to work)? (Markus Triska) -;; ;; * Avoid the local shell entirely for starting remote processes. If ;; so, I think even a signal, when delivered directly to the local ;; SSH instance, would correctly be propagated to the remote process diff --git a/lisp/net/tramp-smb.el b/lisp/net/tramp-smb.el index 1aadd14fb41..eb0d6b50731 100644 --- a/lisp/net/tramp-smb.el +++ b/lisp/net/tramp-smb.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -130,6 +130,7 @@ call, letting the SMB client use the default one." "NT_STATUS_ACCOUNT_LOCKED_OUT" "NT_STATUS_BAD_NETWORK_NAME" "NT_STATUS_CANNOT_DELETE" + "NT_STATUS_CONNECTION_DISCONNECTED" "NT_STATUS_CONNECTION_REFUSED" "NT_STATUS_DIRECTORY_NOT_EMPTY" "NT_STATUS_DUPLICATE_NAME" @@ -137,6 +138,7 @@ call, letting the SMB client use the default one." "NT_STATUS_HOST_UNREACHABLE" "NT_STATUS_IMAGE_ALREADY_LOADED" "NT_STATUS_INVALID_LEVEL" + "NT_STATUS_INVALID_PARAMETER_MIX" "NT_STATUS_IO_TIMEOUT" "NT_STATUS_LOGON_FAILURE" "NT_STATUS_NETWORK_ACCESS_DENIED" @@ -147,6 +149,7 @@ call, letting the SMB client use the default one." "NT_STATUS_OBJECT_NAME_COLLISION" "NT_STATUS_OBJECT_NAME_INVALID" "NT_STATUS_OBJECT_NAME_NOT_FOUND" + "NT_STATUS_OBJECT_PATH_SYNTAX_BAD" "NT_STATUS_PASSWORD_MUST_CHANGE" "NT_STATUS_SHARING_VIOLATION" "NT_STATUS_TRUSTED_RELATIONSHIP_FAILURE" @@ -250,9 +253,10 @@ See `tramp-actions-before-shell' for more info.") (file-readable-p . tramp-handle-file-exists-p) (file-regular-p . tramp-handle-file-regular-p) (file-remote-p . tramp-handle-file-remote-p) - ;; `file-selinux-context' performed by default handler. + (file-selinux-context . tramp-handle-file-selinux-context) (file-symlink-p . tramp-handle-file-symlink-p) - ;; `file-truename' performed by default handler. + (file-system-info . tramp-smb-handle-file-system-info) + (file-truename . tramp-handle-file-truename) (file-writable-p . tramp-smb-handle-file-writable-p) (find-backup-file-name . tramp-handle-find-backup-file-name) ;; `find-file-noselect' performed by default handler. @@ -353,16 +357,17 @@ pass to the OPERATION." (tramp-error v2 'file-error "add-name-to-file: %s must not be a directory" filename)) - (when (and (not ok-if-already-exists) - (file-exists-p newname) - (not (numberp ok-if-already-exists)) - (y-or-n-p - (format - "File %s already exists; make it a new name anyway? " - newname))) - (tramp-error - v2 'file-error - "add-name-to-file: file %s already exists" newname)) + ;; Do the 'confirm if exists' thing. + (when (file-exists-p newname) + ;; What to do? + (if (or (null ok-if-already-exists) ; not allowed to exist + (and (numberp ok-if-already-exists) + (not (yes-or-no-p + (format + "File %s already exists; make it a link anyway? " + v2-localname))))) + (tramp-error v2 'file-already-exists newname) + (delete-file newname))) ;; We must also flush the cache of the directory, because ;; `file-attributes' reads the values from there. (tramp-flush-file-property v2 (file-name-directory v2-localname)) @@ -410,6 +415,9 @@ pass to the OPERATION." (with-parsed-tramp-file-name (if t1 dirname newname) nil (with-tramp-progress-reporter v 0 (format "Copying %s to %s" dirname newname) + (when (and (file-directory-p newname) + (not (tramp-compat-directory-name-p newname))) + (tramp-error v 'file-already-exists newname)) (cond ;; We must use a local temporary directory. ((and t1 t2) @@ -421,14 +429,16 @@ pass to the OPERATION." (unwind-protect (progn (make-directory tmpdir) - (copy-directory dirname tmpdir keep-date 'parents) + (copy-directory + dirname (file-name-as-directory tmpdir) keep-date 'parents) (copy-directory (expand-file-name (file-name-nondirectory dirname) tmpdir) newname keep-date parents)) (delete-directory tmpdir 'recursive)))) ;; We can copy recursively. - ((and (or t1 t2) (tramp-smb-get-cifs-capabilities v)) + ;; Does not work reliably. + (nil ;(and (or t1 t2) (tramp-smb-get-cifs-capabilities v)) (when (and (file-directory-p newname) (not (string-equal (file-name-nondirectory dirname) (file-name-nondirectory newname)))) @@ -526,7 +536,7 @@ pass to the OPERATION." ;; Reset the transfer process properties. (tramp-set-connection-property v "process-name" nil) (tramp-set-connection-property v "process-buffer" nil) - (when t1 (delete-directory tmpdir 'recurse)))) + (when t1 (delete-directory tmpdir 'recursive)))) ;; Handle KEEP-DATE argument. (when keep-date @@ -564,8 +574,7 @@ PRESERVE-UID-GID and PRESERVE-EXTENDED-ATTRIBUTES are completely ignored." 0 (format "Copying %s to %s" filename newname) (if (file-directory-p filename) - (copy-directory - filename newname keep-date 'parents 'copy-contents) + (copy-directory filename newname keep-date 'parents 'copy-contents) (let ((tmpfile (file-local-copy filename))) (if tmpfile @@ -577,7 +586,8 @@ PRESERVE-UID-GID and PRESERVE-EXTENDED-ATTRIBUTES are completely ignored." (signal (car err) (cdr err)))) ;; Remote newname. - (when (file-directory-p newname) + (when (and (file-directory-p newname) + (tramp-compat-directory-name-p newname)) (setq newname (expand-file-name (file-name-nondirectory filename) newname))) @@ -886,9 +896,20 @@ PRESERVE-UID-GID and PRESERVE-EXTENDED-ATTRIBUTES are completely ignored." (string-to-number (match-string 2)) ;; month (string-to-number (match-string 1)))))) ;; year (forward-line)) + + ;; Resolve symlink. + (when (and (stringp id) + (tramp-smb-send-command + vec + (format "readlink \"%s\"" (tramp-smb-get-localname vec)))) + (goto-char (point-min)) + (and (looking-at ".+ -> \\(.+\\)") + (setq id (match-string 1)))) + ;; Return the result. - (list id link uid gid atime mtime ctime size mode nil inode - (tramp-get-device vec))))))) + (when (or id link uid gid atime mtime ctime size mode inode) + (list id link uid gid atime mtime ctime size mode nil inode + (tramp-get-device vec)))))))) (defun tramp-smb-handle-file-directory-p (filename) "Like `file-directory-p' for Tramp files." @@ -899,8 +920,8 @@ PRESERVE-UID-GID and PRESERVE-EXTENDED-ATTRIBUTES are completely ignored." (defun tramp-smb-handle-file-local-copy (filename) "Like `file-local-copy' for Tramp files." - (with-parsed-tramp-file-name filename nil - (unless (file-exists-p filename) + (with-parsed-tramp-file-name (file-truename filename) nil + (unless (file-exists-p (file-truename filename)) (tramp-error v tramp-file-missing "Cannot make local copy of non-existing file `%s'" filename)) @@ -934,6 +955,38 @@ PRESERVE-UID-GID and PRESERVE-EXTENDED-ATTRIBUTES are completely ignored." (nth 0 x)))) (tramp-smb-get-file-entries directory)))))))) +(defun tramp-smb-handle-file-system-info (filename) + "Like `file-system-info' for Tramp files." + (ignore-errors + (unless (file-directory-p filename) + (setq filename (file-name-directory filename))) + (with-parsed-tramp-file-name (expand-file-name filename) nil + (tramp-message v 5 "file system info: %s" localname) + (tramp-smb-send-command v (format "du %s/*" (tramp-smb-get-localname v))) + (with-current-buffer (tramp-get-connection-buffer v) + (let (total avail blocksize) + (goto-char (point-min)) + (forward-line) + (when (looking-at + (concat "[[:space:]]*\\([[:digit:]]+\\)" + " blocks of size \\([[:digit:]]+\\)" + "\\. \\([[:digit:]]+\\) blocks available")) + (setq blocksize (string-to-number (concat (match-string 2) "e0")) + total (* blocksize + (string-to-number (concat (match-string 1) "e0"))) + avail (* blocksize + (string-to-number (concat (match-string 3) "e0"))))) + (forward-line) + (when (looking-at "Total number of bytes: \\([[:digit:]]+\\)") + ;; The used number of bytes is not part of the result. As + ;; side effect, we store it as file property. + (tramp-set-file-property + v localname "used-bytes" + (string-to-number (concat (match-string 1) "e0")))) + ;; Result. + (when (and total avail) + (list total (- total avail) avail))))))) + (defun tramp-smb-handle-file-writable-p (filename) "Like `file-writable-p' for Tramp files." (if (file-exists-p filename) @@ -964,7 +1017,14 @@ PRESERVE-UID-GID and PRESERVE-EXTENDED-ATTRIBUTES are completely ignored." ;; We should not destroy the cache entry. (entries (copy-sequence (tramp-smb-get-file-entries - (file-name-directory filename))))) + (file-name-directory filename)))) + (avail (get-free-disk-space filename)) + ;; `get-free-disk-space' calls `file-system-info', which + ;; sets file property "used-bytes" as side effect. + (used + (format + "%.0f" + (/ (tramp-get-file-property v localname "used-bytes" 0) 1024)))) (when wildcard (string-match "\\." base) @@ -1012,15 +1072,25 @@ PRESERVE-UID-GID and PRESERVE-EXTENDED-ATTRIBUTES are completely ignored." (setcar x (concat (car x) "*")))))) entries)) + ;; Insert size information. + (when full-directory-p + (insert + (if avail + (format "total used in directory %s available %s\n" used avail) + (format "total %s\n" used)))) + ;; Print entries. (mapc (lambda (x) (when (not (zerop (length (nth 0 x)))) - (when (string-match "l" switches) - (let ((attr - (when (tramp-smb-get-stat-capability v) - (ignore-errors - (file-attributes filename 'string))))) + (let ((attr + (when (tramp-smb-get-stat-capability v) + (ignore-errors + (file-attributes + (expand-file-name + (nth 0 x) (file-name-directory filename)) + 'string))))) + (when (string-match "l" switches) (insert (format "%10s %3d %-8s %-8s %8s %s " @@ -1034,20 +1104,27 @@ PRESERVE-UID-GID and PRESERVE-EXTENDED-ATTRIBUTES are completely ignored." tramp-half-a-year) "%b %e %R" "%b %e %Y") - (nth 3 x)))))) ; date - - ;; We mark the file name. The inserted name could be - ;; from somewhere else, so we use the relative file name - ;; of `default-directory'. - (let ((start (point))) - (insert - (format - "%s\n" - (file-relative-name - (expand-file-name - (nth 0 x) (file-name-directory filename)) - (when full-directory-p (file-name-directory filename))))) - (put-text-property start (1- (point)) 'dired-filename t)) + (nth 3 x))))) ; date + + ;; We mark the file name. The inserted name could be + ;; from somewhere else, so we use the relative file name + ;; of `default-directory'. + (let ((start (point))) + (insert + (format + "%s" + (file-relative-name + (expand-file-name + (nth 0 x) (file-name-directory filename)) + (when full-directory-p (file-name-directory filename))))) + (put-text-property start (point) 'dired-filename t)) + + ;; Insert symlink. + (when (and (string-match "l" switches) + (stringp (tramp-compat-file-attribute-type attr))) + (insert " -> " (tramp-compat-file-attribute-type attr)))) + + (insert "\n") (forward-line) (beginning-of-line))) entries)))))) @@ -1094,56 +1171,58 @@ PRESERVE-UID-GID and PRESERVE-EXTENDED-ATTRIBUTES are completely ignored." v 'file-error "Couldn't make directory %s" directory)))))) (defun tramp-smb-handle-make-symbolic-link - (filename linkname &optional ok-if-already-exists) + (target linkname &optional ok-if-already-exists) "Like `make-symbolic-link' for Tramp files. -If LINKNAME is a non-Tramp file, it is used verbatim as the target of -the symlink. If LINKNAME is a Tramp file, only the localname component is -used as the target of the symlink. - -If LINKNAME is a Tramp file and the localname component is relative, then -it is expanded first, before the localname component is taken. Note that -this can give surprising results if the user/host for the source and -target of the symlink differ." - (unless (tramp-equal-remote filename linkname) - (with-parsed-tramp-file-name - (if (tramp-tramp-file-p filename) filename linkname) nil - (tramp-error - v 'file-error - "make-symbolic-link: %s" - "only implemented for same method, same user, same host"))) - (with-parsed-tramp-file-name filename v1 - (with-parsed-tramp-file-name linkname v2 - (when (file-directory-p filename) - (tramp-error - v2 'file-error - "make-symbolic-link: %s must not be a directory" filename)) - (when (and (not ok-if-already-exists) - (file-exists-p linkname) - (not (numberp ok-if-already-exists)) - (y-or-n-p - (format - "File %s already exists; make it a new name anyway? " - linkname))) - (tramp-error - v2 'file-already-exists - "make-symbolic-link: file %s already exists" linkname)) - (unless (tramp-smb-get-cifs-capabilities v1) - (tramp-error v2 'file-error "make-symbolic-link not supported")) - ;; We must also flush the cache of the directory, because - ;; `file-attributes' reads the values from there. - (tramp-flush-file-property v2 (file-name-directory v2-localname)) - (tramp-flush-file-property v2 v2-localname) - (unless - (tramp-smb-send-command - v1 - (format - "symlink \"%s\" \"%s\"" - (tramp-smb-get-localname v1) - (tramp-smb-get-localname v2))) - (tramp-error - v2 'file-error - "error with make-symbolic-link, see buffer `%s' for details" - (buffer-name)))))) +If TARGET is a non-Tramp file, it is used verbatim as the target +of the symlink. If TARGET is a Tramp file, only the localname +component is used as the target of the symlink." + (if (not (tramp-tramp-file-p (expand-file-name linkname))) + (tramp-run-real-handler + 'make-symbolic-link (list target linkname ok-if-already-exists)) + + (with-parsed-tramp-file-name linkname nil + ;; If TARGET is a Tramp name, use just the localname component. + (when (and (tramp-tramp-file-p target) + (tramp-file-name-equal-p v (tramp-dissect-file-name target))) + (setq target + (tramp-file-name-localname + (tramp-dissect-file-name (expand-file-name target))))) + + ;; If TARGET is still remote, quote it. + (if (tramp-tramp-file-p target) + (make-symbolic-link + (let (file-name-handler-alist) (tramp-compat-file-name-quote target)) + linkname ok-if-already-exists) + + ;; Do the 'confirm if exists' thing. + (when (file-exists-p linkname) + ;; What to do? + (if (or (null ok-if-already-exists) ; not allowed to exist + (and (numberp ok-if-already-exists) + (not (yes-or-no-p + (format + "File %s already exists; make it a link anyway? " + localname))))) + (tramp-error v 'file-already-exists localname) + (delete-file linkname))) + + (unless (tramp-smb-get-cifs-capabilities v) + (tramp-error v 'file-error "make-symbolic-link not supported")) + + ;; We must also flush the cache of the directory, because + ;; `file-attributes' reads the values from there. + (tramp-flush-file-property v (file-name-directory localname)) + (tramp-flush-file-property v localname) + + (unless + (tramp-smb-send-command + v (format "symlink \"%s\" \"%s\"" + (tramp-compat-file-name-unquote target) + (tramp-smb-get-localname v))) + (tramp-error + v 'file-error + "error with make-symbolic-link, see buffer `%s' for details" + (buffer-name))))))) (defun tramp-smb-handle-process-file (program &optional infile destination display &rest args) @@ -1469,14 +1548,17 @@ errors for shares like \"C$/\", which are common in Microsoft Windows." (error filename)))) (defun tramp-smb-handle-write-region - (start end filename &optional append visit lockname confirm) + (start end filename &optional append visit lockname mustbenew) "Like `write-region' for Tramp files." (setq filename (expand-file-name filename)) (with-parsed-tramp-file-name filename nil - (when (and confirm (file-exists-p filename)) - (unless (y-or-n-p (format "File %s exists; overwrite anyway? " - filename)) - (tramp-error v 'file-error "File not overwritten"))) + (when (and mustbenew (file-exists-p filename) + (or (eq mustbenew 'excl) + (not + (y-or-n-p + (format "File %s exists; overwrite anyway? " filename))))) + (tramp-error v 'file-already-exists filename)) + ;; We must also flush the cache of the directory, because ;; `file-attributes' reads the values from there. (tramp-flush-file-property v (file-name-directory localname)) @@ -1489,10 +1571,7 @@ errors for shares like \"C$/\", which are common in Microsoft Windows." ;; modtime data to be clobbered from the temp file. We call ;; `set-visited-file-modtime' ourselves later on. (tramp-run-real-handler - 'write-region - (if confirm ; don't pass this arg unless defined for backward compat. - (list start end tmpfile append 'no-message lockname confirm) - (list start end tmpfile append 'no-message lockname))) + 'write-region (list start end tmpfile append 'no-message lockname)) (with-tramp-progress-reporter v 3 (format "Moving tmp file %s to %s" tmpfile filename) @@ -1551,6 +1630,10 @@ If VEC has no cifs capabilities, exchange \"/\" by \"\\\\\"." "Read entries which match DIRECTORY. Either the shares are listed, or the `dir' command is executed. Result is a list of (LOCALNAME MODE SIZE MONTH DAY TIME YEAR)." + ;; If CIFS capabilities are enabled, symlinks are not listed + ;; by `dir'. This is a consequence of + ;; <https://www.samba.org/samba/news/symlink_attack.html>. See also + ;; <https://bugzilla.samba.org/show_bug.cgi?id=5116>. (with-parsed-tramp-file-name (file-name-as-directory directory) nil (setq localname (or localname "/")) (with-tramp-file-property v localname "file-entries" @@ -1696,13 +1779,17 @@ Result is the list (LOCALNAME MODE SIZE MTIME)." (if (string-match "\\([0-9]+\\)$" line) (let ((length (- (max 10 (1+ (length (match-string 1 line))))))) (setq size (string-to-number (match-string 1 line))) - (when (string-match "\\([ADHRSV]+\\)" (substring line length)) + (when (string-match + "\\([ACDEHNORrsSTV]+\\)" (substring line length)) (setq length (+ length (match-end 0)))) (setq line (substring line 0 length))) (cl-return)) - ;; mode: ARCH, DIR, HIDDEN, RONLY, SYSTEM, VOLID. - (if (string-match "\\([ADHRSV]+\\)?$" line) + ;; mode: ARCHIVE, COMPRESSED, DIRECTORY, ENCRYPTED, HIDDEN, + ;; NONINDEXED, NORMAL, OFFLINE, READONLY, + ;; REPARSE_POINT, SPARSE, SYSTEM, TEMPORARY, VOLID. + + (if (string-match "\\([ACDEHNORrsSTV]+\\)?$" line) (setq mode (or (match-string 1 line) "") mode (save-match-data (format diff --git a/lisp/net/tramp-uu.el b/lisp/net/tramp-uu.el index 5e5f05da4a8..12d4cd4d9d5 100644 --- a/lisp/net/tramp-uu.el +++ b/lisp/net/tramp-uu.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/tramp.el b/lisp/net/tramp.el index 8d81ac64aa2..433baed6ed6 100644 --- a/lisp/net/tramp.el +++ b/lisp/net/tramp.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -40,16 +40,16 @@ ;; Also see the todo list at the bottom of this file. ;; ;; The current version of Tramp can be retrieved from the following URL: -;; http://ftp.gnu.org/gnu/tramp/ +;; https://ftp.gnu.org/gnu/tramp/ ;; ;; There's a mailing list for this, as well. Its name is: ;; tramp-devel@gnu.org ;; You can use the Web to subscribe, under the following URL: -;; http://lists.gnu.org/mailman/listinfo/tramp-devel +;; https://lists.gnu.org/mailman/listinfo/tramp-devel ;; ;; For the adventurous, the current development sources are available ;; via Git. You can find instructions about this at the following URL: -;; http://savannah.gnu.org/projects/tramp/ +;; https://savannah.gnu.org/projects/tramp/ ;; ;; Don't forget to put on your asbestos longjohns, first! @@ -660,7 +660,7 @@ Useful for \"rsync\" like methods.") (make-variable-buffer-local 'tramp-temp-buffer-file-name) (put 'tramp-temp-buffer-file-name 'permanent-local t) -;;;###autoload +;;;###tramp-autoload (defcustom tramp-syntax 'default "Tramp filename syntax to be used. @@ -670,29 +670,58 @@ It can have the following values: `simplified' -- Ange-FTP like syntax `separate' -- Syntax as defined for XEmacs originally -Do not change the value by `setq', it must be changed only by -`custom-set-variables'. See also `tramp-change-syntax'." +Do not change the value by `setq', it must be changed only via +Customize. See also `tramp-change-syntax'." :group 'tramp :version "26.1" - :package-version '(Tramp . "2.3.2") + :package-version '(Tramp . "2.3.3") :type '(choice (const :tag "Default" default) (const :tag "Ange-FTP" simplified) (const :tag "XEmacs" separate)) :require 'tramp :initialize 'custom-initialize-set - :set (lambda (symbol value) - ;; Check allowed values. - (unless (memq value (tramp-syntax-values)) - (tramp-compat-user-error "Wrong `tramp-syntax' %s" tramp-syntax)) - ;; Cleanup existing buffers. - (unless (eq (symbol-value symbol) value) - (tramp-cleanup-all-buffers)) - ;; Set the value: - (set-default symbol value) - ;; Reset `tramp-file-name-regexp'. - (setq tramp-file-name-regexp (tramp-file-name-regexp)) - ;; Rearrange file name handlers. - (tramp-register-file-name-handlers))) + :set 'tramp-set-syntax) + +(defun tramp-set-syntax (symbol value) + "Set SYMBOL to value VALUE. +Used in user option `tramp-syntax'. There are further variables +to be set, depending on VALUE." + ;; Check allowed values. + (unless (memq value (tramp-syntax-values)) + (tramp-compat-user-error "Wrong `tramp-syntax' %s" tramp-syntax)) + ;; Cleanup existing buffers. + (unless (eq (symbol-value symbol) value) + (tramp-cleanup-all-buffers)) + ;; Set the value: + (set-default symbol value) + ;; Reset the depending variables. + (with-no-warnings + (setq tramp-prefix-format (tramp-build-prefix-format) + tramp-prefix-regexp (tramp-build-prefix-regexp) + tramp-method-regexp (tramp-build-method-regexp) + tramp-postfix-method-format (tramp-build-postfix-method-format) + tramp-postfix-method-regexp (tramp-build-postfix-method-regexp) + tramp-prefix-ipv6-format (tramp-build-prefix-ipv6-format) + tramp-prefix-ipv6-regexp (tramp-build-prefix-ipv6-regexp) + tramp-postfix-ipv6-format (tramp-build-postfix-ipv6-format) + tramp-postfix-ipv6-regexp (tramp-build-postfix-ipv6-regexp) + tramp-postfix-host-format (tramp-build-postfix-host-format) + tramp-postfix-host-regexp (tramp-build-postfix-host-regexp) + tramp-remote-file-name-spec-regexp + (tramp-build-remote-file-name-spec-regexp) + tramp-file-name-structure (tramp-build-file-name-structure) + tramp-file-name-regexp (tramp-build-file-name-regexp) + tramp-completion-file-name-regexp + (tramp-build-completion-file-name-regexp))) + ;; Rearrange file name handlers. + (tramp-register-file-name-handlers)) + +;; Initialize the Tramp syntax variables. We want to override initial +;; value of `tramp-file-name-regexp'. Other Tramp syntax variables +;; must be initialized as well to proper values. We do not call +;; `custom-set-variable', this would load Tramp via custom.el. +(eval-after-load 'tramp + '(tramp-set-syntax 'tramp-syntax (tramp-compat-tramp-syntax))) (defun tramp-syntax-values () "Return possible values of `tramp-syntax', a list" @@ -700,40 +729,65 @@ Do not change the value by `setq', it must be changed only by (setq values (mapcar 'last values) values (mapcar 'car values)))) -(defun tramp-prefix-format () +(defun tramp-lookup-syntax (alist) + "Look up a syntax string in ALIST according to `tramp-compat-tramp-syntax.' +Raise an error if `tramp-syntax' is invalid." + (or (cdr (assq (tramp-compat-tramp-syntax) alist)) + (error "Wrong `tramp-syntax' %s" tramp-syntax))) + +(defconst tramp-prefix-format-alist + '((default . "/") + (simplified . "/") + (separate . "/[")) + "Alist mapping Tramp syntax to strings beginning Tramp file names.") + +(defun tramp-build-prefix-format () + (tramp-lookup-syntax tramp-prefix-format-alist)) + +(defvar tramp-prefix-format (tramp-build-prefix-format) "String matching the very beginning of Tramp file names. -Used in `tramp-make-tramp-file-name'." - (cond ((eq (tramp-compat-tramp-syntax) 'default) "/") - ((eq (tramp-compat-tramp-syntax) 'simplified) "/") - ((eq (tramp-compat-tramp-syntax) 'separate) "/[") - (t (error "Wrong `tramp-syntax' %s" tramp-syntax)))) +Used in `tramp-make-tramp-file-name'.") -(defun tramp-prefix-regexp () +(defun tramp-build-prefix-regexp () + (concat "^" (regexp-quote tramp-prefix-format))) + +(defvar tramp-prefix-regexp (tramp-build-prefix-regexp) "Regexp matching the very beginning of Tramp file names. -Should always start with \"^\". Derived from `tramp-prefix-format'." - (concat "^" (regexp-quote (tramp-prefix-format)))) +Should always start with \"^\". Derived from `tramp-prefix-format'.") + +(defconst tramp-method-regexp-alist + '((default . "[a-zA-Z0-9-]+") + (simplified . "") + (separate . "[a-zA-Z0-9-]*")) + "Alist mapping Tramp syntax to regexps matching methods identifiers.") -(defun tramp-method-regexp () +(defun tramp-build-method-regexp () + (tramp-lookup-syntax tramp-method-regexp-alist)) + +(defvar tramp-method-regexp (tramp-build-method-regexp) "Regexp matching methods identifiers. -The `ftp' syntax does not support methods." - (cond ((eq (tramp-compat-tramp-syntax) 'default) "[a-zA-Z0-9-]+") - ((eq (tramp-compat-tramp-syntax) 'simplified) "") - ((eq (tramp-compat-tramp-syntax) 'separate) "[a-zA-Z0-9-]*") - (t (error "Wrong `tramp-syntax' %s" tramp-syntax)))) +The `ftp' syntax does not support methods.") + +(defconst tramp-postfix-method-format-alist + '((default . ":") + (simplified . "") + (separate . "/")) + "Alist mapping Tramp syntax to the delimiter after the method.") -(defun tramp-postfix-method-format () +(defun tramp-build-postfix-method-format () + (tramp-lookup-syntax tramp-postfix-method-format-alist)) + +(defvar tramp-postfix-method-format (tramp-build-postfix-method-format) "String matching delimiter between method and user or host names. The `ftp' syntax does not support methods. -Used in `tramp-make-tramp-file-name'." - (cond ((eq (tramp-compat-tramp-syntax) 'default) ":") - ((eq (tramp-compat-tramp-syntax) 'simplified) "") - ((eq (tramp-compat-tramp-syntax) 'separate) "/") - (t (error "Wrong `tramp-syntax' %s" tramp-syntax)))) +Used in `tramp-make-tramp-file-name'.") -(defun tramp-postfix-method-regexp () +(defun tramp-build-postfix-method-regexp () + (regexp-quote tramp-postfix-method-format)) + +(defvar tramp-postfix-method-regexp (tramp-build-postfix-method-regexp) "Regexp matching delimiter between method and user or host names. -Derived from `tramp-postfix-method-format'." - (regexp-quote (tramp-postfix-method-format))) +Derived from `tramp-postfix-method-format'.") (defconst tramp-user-regexp "[^/|: \t]+" "Regexp matching user names.") @@ -743,8 +797,7 @@ Derived from `tramp-postfix-method-format'." "String matching delimiter between user and domain names.") ;;;###tramp-autoload -(defconst tramp-prefix-domain-regexp - (regexp-quote tramp-prefix-domain-format) +(defconst tramp-prefix-domain-regexp (regexp-quote tramp-prefix-domain-format) "Regexp matching delimiter between user and domain names. Derived from `tramp-prefix-domain-format'.") @@ -761,52 +814,63 @@ Derived from `tramp-prefix-domain-format'.") "String matching delimiter between user and host names. Used in `tramp-make-tramp-file-name'.") -(defconst tramp-postfix-user-regexp - (regexp-quote tramp-postfix-user-format) +(defconst tramp-postfix-user-regexp (regexp-quote tramp-postfix-user-format) "Regexp matching delimiter between user and host names. Derived from `tramp-postfix-user-format'.") -(defconst tramp-host-regexp "[a-zA-Z0-9_.-]+" +(defconst tramp-host-regexp "[a-zA-Z0-9_.%-]+" "Regexp matching host names.") -(defun tramp-prefix-ipv6-format () +(defconst tramp-prefix-ipv6-format-alist + '((default . "[") + (simplified . "[") + (separate . "")) + "Alist mapping Tramp syntax to strings prefixing IPv6 addresses.") + +(defun tramp-build-prefix-ipv6-format () + (tramp-lookup-syntax tramp-prefix-ipv6-format-alist)) + +(defvar tramp-prefix-ipv6-format (tramp-build-prefix-ipv6-format) "String matching left hand side of IPv6 addresses. -Used in `tramp-make-tramp-file-name'." - (cond ((eq (tramp-compat-tramp-syntax) 'default) "[") - ((eq (tramp-compat-tramp-syntax) 'simplified) "[") - ((eq (tramp-compat-tramp-syntax) 'separate) "") - (t (error "Wrong `tramp-syntax' %s" tramp-syntax)))) +Used in `tramp-make-tramp-file-name'.") -(defun tramp-prefix-ipv6-regexp () +(defun tramp-build-prefix-ipv6-regexp () + (regexp-quote tramp-prefix-ipv6-format)) + +(defvar tramp-prefix-ipv6-regexp (tramp-build-prefix-ipv6-regexp) "Regexp matching left hand side of IPv6 addresses. -Derived from `tramp-prefix-ipv6-format'." - (regexp-quote (tramp-prefix-ipv6-format))) +Derived from `tramp-prefix-ipv6-format'.") ;; The following regexp is a bit sloppy. But it shall serve our ;; purposes. It covers also IPv4 mapped IPv6 addresses, like in ;; "::ffff:192.168.0.1". -(defconst tramp-ipv6-regexp - "\\(?:\\(?:[a-zA-Z0-9]+\\)?:\\)+[a-zA-Z0-9.]+" +(defconst tramp-ipv6-regexp "\\(?:\\(?:[a-zA-Z0-9]+\\)?:\\)+[a-zA-Z0-9.]+" "Regexp matching IPv6 addresses.") -(defun tramp-postfix-ipv6-format () +(defconst tramp-postfix-ipv6-format-alist + '((default . "]") + (simplified . "]") + (separate . "")) + "Alist mapping Tramp syntax to suffix for IPv6 addresses.") + +(defun tramp-build-postfix-ipv6-format () + (tramp-lookup-syntax tramp-postfix-ipv6-format-alist)) + +(defvar tramp-postfix-ipv6-format (tramp-build-postfix-ipv6-format) "String matching right hand side of IPv6 addresses. -Used in `tramp-make-tramp-file-name'." - (cond ((eq (tramp-compat-tramp-syntax) 'default) "]") - ((eq (tramp-compat-tramp-syntax) 'simplified) "]") - ((eq (tramp-compat-tramp-syntax) 'separate) "") - (t (error "Wrong `tramp-syntax' %s" tramp-syntax)))) +Used in `tramp-make-tramp-file-name'.") -(defun tramp-postfix-ipv6-regexp () +(defun tramp-build-postfix-ipv6-regexp () + (regexp-quote tramp-postfix-ipv6-format)) + +(defvar tramp-postfix-ipv6-regexp (tramp-build-postfix-ipv6-regexp) "Regexp matching right hand side of IPv6 addresses. -Derived from `tramp-postfix-ipv6-format'." - (regexp-quote (tramp-postfix-ipv6-format))) +Derived from `tramp-postfix-ipv6-format'.") (defconst tramp-prefix-port-format "#" "String matching delimiter between host names and port numbers.") -(defconst tramp-prefix-port-regexp - (regexp-quote tramp-prefix-port-format) +(defconst tramp-prefix-port-regexp (regexp-quote tramp-prefix-port-format) "Regexp matching delimiter between host names and port numbers. Derived from `tramp-prefix-port-format'.") @@ -822,23 +886,29 @@ Derived from `tramp-prefix-port-format'.") (defconst tramp-postfix-hop-format "|" "String matching delimiter after ad-hoc hop definitions.") -(defconst tramp-postfix-hop-regexp - (regexp-quote tramp-postfix-hop-format) +(defconst tramp-postfix-hop-regexp (regexp-quote tramp-postfix-hop-format) "Regexp matching delimiter after ad-hoc hop definitions. Derived from `tramp-postfix-hop-format'.") -(defun tramp-postfix-host-format () +(defconst tramp-postfix-host-format-alist + '((default . ":") + (simplified . ":") + (separate . "]")) + "Alist mapping Tramp syntax to strings between host and local names.") + +(defun tramp-build-postfix-host-format () + (tramp-lookup-syntax tramp-postfix-host-format-alist)) + +(defvar tramp-postfix-host-format (tramp-build-postfix-host-format) "String matching delimiter between host names and localnames. -Used in `tramp-make-tramp-file-name'." - (cond ((eq (tramp-compat-tramp-syntax) 'default) ":") - ((eq (tramp-compat-tramp-syntax) 'simplified) ":") - ((eq (tramp-compat-tramp-syntax) 'separate) "]") - (t (error "Wrong `tramp-syntax' %s" tramp-syntax)))) +Used in `tramp-make-tramp-file-name'.") -(defun tramp-postfix-host-regexp () +(defun tramp-build-postfix-host-regexp () + (regexp-quote tramp-postfix-host-format)) + +(defvar tramp-postfix-host-regexp (tramp-build-postfix-host-regexp) "Regexp matching delimiter between host names and localnames. -Derived from `tramp-postfix-host-format'." - (regexp-quote (tramp-postfix-host-format))) +Derived from `tramp-postfix-host-format'.") (defconst tramp-localname-regexp ".*$" "Regexp matching localnames.") @@ -851,18 +921,35 @@ Derived from `tramp-postfix-host-format'." ;;; File name format: -(defun tramp-remote-file-name-spec-regexp () - "Regular expression matching a Tramp file name between prefix and postfix." +(defun tramp-build-remote-file-name-spec-regexp () + "Construct a regexp matching a Tramp file name for a Tramp syntax. +It is expected, that `tramp-syntax' has the proper value." (concat - "\\(" (tramp-method-regexp) "\\)" (tramp-postfix-method-regexp) - "\\(?:" "\\(" tramp-user-regexp "\\)" tramp-postfix-user-regexp "\\)?" - "\\(" "\\(?:" tramp-host-regexp "\\|" - (tramp-prefix-ipv6-regexp) - "\\(?:" tramp-ipv6-regexp "\\)?" - (tramp-postfix-ipv6-regexp) "\\)?" - "\\(?:" tramp-prefix-port-regexp tramp-port-regexp "\\)?" "\\)?")) - -(defun tramp-file-name-structure () + "\\(" tramp-method-regexp "\\)" tramp-postfix-method-regexp + "\\(?:" "\\(" tramp-user-regexp "\\)" tramp-postfix-user-regexp "\\)?" + "\\(" "\\(?:" tramp-host-regexp "\\|" + tramp-prefix-ipv6-regexp "\\(?:" tramp-ipv6-regexp "\\)?" + tramp-postfix-ipv6-regexp "\\)" + "\\(?:" tramp-prefix-port-regexp tramp-port-regexp "\\)?" "\\)?")) + +(defvar tramp-remote-file-name-spec-regexp + (tramp-build-remote-file-name-spec-regexp) + "Regular expression matching a Tramp file name between prefix and postfix.") + +(defun tramp-build-file-name-structure () + "Construct the Tramp file name structure for a Tramp syntax. +It is expected, that `tramp-syntax' has the proper value. +See `tramp-file-name-structure'." + (list + (concat + tramp-prefix-regexp + "\\(" "\\(?:" tramp-remote-file-name-spec-regexp + tramp-postfix-hop-regexp "\\)+" "\\)?" + tramp-remote-file-name-spec-regexp tramp-postfix-host-regexp + "\\(" tramp-localname-regexp "\\)") + 5 6 7 8 1)) + +(defvar tramp-file-name-structure (tramp-build-file-name-structure) "List of six elements (REGEXP METHOD USER HOST FILE HOP), detailing \ the Tramp file name structure. @@ -880,34 +967,23 @@ cascade of several hops. These numbers are passed directly to `match-string', which see. That means the opening parentheses are counted to identify the pair. -See also `tramp-file-name-regexp'." - (list - (concat - (tramp-prefix-regexp) - "\\(" "\\(?:" (tramp-remote-file-name-spec-regexp) - tramp-postfix-hop-regexp "\\)+" "\\)?" - (tramp-remote-file-name-spec-regexp) (tramp-postfix-host-regexp) - "\\(" tramp-localname-regexp "\\)") - 5 6 7 8 1)) +See also `tramp-file-name-regexp'.") -(defun tramp-file-name-regexp () - "Regular expression matching file names handled by Tramp. -This regexp should match Tramp file names but no other file names." - (car (tramp-file-name-structure))) +(defun tramp-build-file-name-regexp () + (car tramp-file-name-structure)) ;;;###autoload (defconst tramp-initial-file-name-regexp "\\`/.+:.*:" "Value for `tramp-file-name-regexp' for autoload. It must match the initial `tramp-syntax' settings.") -;; External packages use constant `tramp-file-name-regexp'. In order -;; not to break them, we still provide it. It is a variable now. ;;;###autoload (defvar tramp-file-name-regexp tramp-initial-file-name-regexp - "Value for `tramp-file-name-regexp' for autoload. -It must match the initial `tramp-syntax' settings.") + "Regular expression matching file names handled by Tramp. +This regexp should match Tramp file names but no other file +names. When calling `tramp-register-file-name-handlers', the +initial value is overwritten by the car of `tramp-file-name-structure'.") -;;;###autoload (defconst tramp-completion-file-name-regexp-default (concat "\\`/\\(" @@ -949,7 +1025,17 @@ On W32 systems, the volume letter must be ignored.") "Value for `tramp-completion-file-name-regexp' for separate remoting. See `tramp-file-name-structure' for more explanations.") -(defun tramp-completion-file-name-regexp () +(defconst tramp-completion-file-name-regexp-alist + `((default . ,tramp-completion-file-name-regexp-default) + (simplified . ,tramp-completion-file-name-regexp-simplified) + (separate . ,tramp-completion-file-name-regexp-separate)) + "Alist mapping incomplete Tramp file names.") + +(defun tramp-build-completion-file-name-regexp () + (tramp-lookup-syntax tramp-completion-file-name-regexp-alist)) + +(defvar tramp-completion-file-name-regexp + (tramp-build-completion-file-name-regexp) "Regular expression matching file names handled by Tramp completion. This regexp should match partial Tramp file names only. @@ -958,20 +1044,22 @@ this file \(tramp.el) is loaded. This means that this variable must be set before loading tramp.el. Alternatively, `file-name-handler-alist' can be updated after changing this variable. -Also see `tramp-file-name-structure'." - (cond ((eq (tramp-compat-tramp-syntax) 'default) - tramp-completion-file-name-regexp-default) - ((eq (tramp-compat-tramp-syntax) 'simplified) - tramp-completion-file-name-regexp-simplified) - ((eq (tramp-compat-tramp-syntax) 'separate) - tramp-completion-file-name-regexp-separate) - (t (error "Wrong `tramp-syntax' %s" tramp-syntax)))) +Also see `tramp-file-name-structure'.") ;;;###autoload -(defconst tramp-initial-completion-file-name-regexp - tramp-completion-file-name-regexp-default - "Value for `tramp-completion-file-name-regexp' for autoload. -It must match the initial `tramp-syntax' settings.") +(defconst tramp-autoload-file-name-regexp + (concat + "\\`/" + (if (memq system-type '(cygwin windows-nt)) + ;; The method is either "-", or at least two characters. + "\\(-\\|[^/|:]\\{2,\\}\\)" + ;; At least one character for method. + "[^/|:]+") + ":") + "Regular expression matching file names handled by Tramp autoload. +It must match the initial `tramp-syntax' settings. It should not +match file names at root of the underlying local file system, +like \"/sys\" or \"/C:\".") ;; Chunked sending kludge. We set this to 500 for black-listed constellations ;; known to have a bug in `process-send-string'; some ssh connections appear @@ -1112,7 +1200,6 @@ means to use always cached values for the directory contents." (defvar tramp-current-connection nil "Last connection timestamp.") -;;;###autoload (defconst tramp-completion-file-name-handler-alist '((file-name-all-completions . tramp-completion-handle-file-name-all-completions) @@ -1195,14 +1282,14 @@ entry does not exist, return nil." ;;;###tramp-autoload (defun tramp-tramp-file-p (name) "Return t if NAME is a string with Tramp file name syntax." - (save-match-data - (and (stringp name) - ;; No "/:" and "/c:". This is not covered by `tramp-file-name-regexp'. - (not (string-match - (if (memq system-type '(cygwin windows-nt)) - "^/[[:alpha:]]?:" "^/:") - name)) - (string-match (tramp-file-name-regexp) name)))) + (and (stringp name) + ;; No "/:" and "/c:". This is not covered by `tramp-file-name-regexp'. + (not (string-match-p + (if (memq system-type '(cygwin windows-nt)) + "^/[[:alpha:]]?:" "^/:") + name)) + (string-match-p tramp-file-name-regexp name) + t)) (defun tramp-find-method (method user host) "Return the right method string to use. @@ -1274,13 +1361,13 @@ values." (save-match-data (unless (tramp-tramp-file-p name) (tramp-compat-user-error nil "Not a Tramp file name: \"%s\"" name)) - (if (not (string-match (nth 0 (tramp-file-name-structure)) name)) + (if (not (string-match (nth 0 tramp-file-name-structure) name)) (error "`tramp-file-name-structure' didn't match!") - (let ((method (match-string (nth 1 (tramp-file-name-structure)) name)) - (user (match-string (nth 2 (tramp-file-name-structure)) name)) - (host (match-string (nth 3 (tramp-file-name-structure)) name)) - (localname (match-string (nth 4 (tramp-file-name-structure)) name)) - (hop (match-string (nth 5 (tramp-file-name-structure)) name)) + (let ((method (match-string (nth 1 tramp-file-name-structure) name)) + (user (match-string (nth 2 tramp-file-name-structure) name)) + (host (match-string (nth 3 tramp-file-name-structure) name)) + (localname (match-string (nth 4 tramp-file-name-structure) name)) + (hop (match-string (nth 5 tramp-file-name-structure) name)) domain port) (when user (when (string-match tramp-user-with-domain-regexp user) @@ -1291,9 +1378,9 @@ values." (when (string-match tramp-host-with-port-regexp host) (setq port (match-string 2 host) host (match-string 1 host))) - (when (string-match (tramp-prefix-ipv6-regexp) host) + (when (string-match tramp-prefix-ipv6-regexp host) (setq host (replace-match "" nil t host))) - (when (string-match (tramp-postfix-ipv6-regexp) host) + (when (string-match tramp-postfix-ipv6-regexp host) (setq host (replace-match "" nil t host)))) (unless nodefault @@ -1318,42 +1405,41 @@ values." (method user domain host port localname &optional hop) "Constructs a Tramp file name from METHOD, USER, HOST and LOCALNAME. When not nil, optional DOMAIN, PORT and HOP are used." - (concat (tramp-prefix-format) hop + (concat tramp-prefix-format hop (unless (or (zerop (length method)) - (zerop (length (tramp-postfix-method-format)))) - (concat method (tramp-postfix-method-format))) + (zerop (length tramp-postfix-method-format))) + (concat method tramp-postfix-method-format)) user (unless (zerop (length domain)) (concat tramp-prefix-domain-format domain)) (unless (zerop (length user)) - tramp-postfix-user-format) + tramp-postfix-user-format) (when host (if (string-match tramp-ipv6-regexp host) - (concat - (tramp-prefix-ipv6-format) host (tramp-postfix-ipv6-format)) + (concat tramp-prefix-ipv6-format host tramp-postfix-ipv6-format) host)) (unless (zerop (length port)) (concat tramp-prefix-port-format port)) - (tramp-postfix-host-format) + tramp-postfix-host-format (when localname localname))) (defun tramp-completion-make-tramp-file-name (method user host localname) "Constructs a Tramp file name from METHOD, USER, HOST and LOCALNAME. It must not be a complete Tramp file name, but as long as there are necessary only. This function will be used in file name completion." - (concat (tramp-prefix-format) + (concat tramp-prefix-format (unless (or (zerop (length method)) - (zerop (length (tramp-postfix-method-format)))) - (concat method (tramp-postfix-method-format))) + (zerop (length tramp-postfix-method-format))) + (concat method tramp-postfix-method-format)) (unless (zerop (length user)) (concat user tramp-postfix-user-format)) (unless (zerop (length host)) (concat (if (string-match tramp-ipv6-regexp host) (concat - (tramp-prefix-ipv6-format) host (tramp-postfix-ipv6-format)) + tramp-prefix-ipv6-format host tramp-postfix-ipv6-format) host) - (tramp-postfix-host-format))) + tramp-postfix-host-format)) (when localname localname))) (defun tramp-get-buffer (vec) @@ -1597,6 +1683,12 @@ signal identifier to be raised, remaining arguments passed to `tramp-message'. Finally, signal SIGNAL is raised." (let (tramp-message-show-message) (tramp-backtrace vec-or-proc) + (unless arguments + ;; FMT-STRING could be just a file name, as in + ;; `file-already-exists' errors. It could contain the ?\% + ;; character, as in smb domain spec. + (setq arguments (list fmt-string) + fmt-string "%s")) (when vec-or-proc (tramp-message vec-or-proc 1 "%s" @@ -1641,6 +1733,18 @@ an input event arrives. The other arguments are passed to `tramp-error'." (when (tramp-file-name-equal-p vec (car tramp-current-connection)) (setcdr tramp-current-connection (current-time))))))) +(defmacro tramp-with-demoted-errors (vec-or-proc format &rest body) + "Execute BODY while redirecting the error message to `tramp-message'. +BODY is executed like wrapped by `with-demoted-errors'. FORMAT +is a format-string containing a %-sequence meaning to substitute +the resulting error message." + (declare (debug (symbolp body)) + (indent 2)) + (let ((err (make-symbol "err"))) + `(condition-case-unless-debug ,err + (progn ,@body) + (error (tramp-message ,vec-or-proc 3 ,format ,err) nil)))) + (defmacro with-parsed-tramp-file-name (filename var &rest body) "Parse a Tramp filename and make components available in the body. @@ -1649,20 +1753,20 @@ Second arg VAR is a symbol. It is used as a variable name to hold the filename structure. It is also used as a prefix for the variables holding the components. For example, if VAR is the symbol `foo', then `foo' will be bound to the whole structure, `foo-method' will be bound to -the method component, and so on for `foo-user', `foo-host', `foo-localname', -`foo-hop'. +the method component, and so on for `foo-user', `foo-domain', `foo-host', +`foo-port', `foo-localname', `foo-hop'. Remaining args are Lisp expressions to be evaluated (inside an implicit `progn'). If VAR is nil, then we bind `v' to the structure and `method', `user', -`host', `localname', `hop' to the components." +`domain', `host', `port', `localname', `hop' to the components." (let ((bindings (mapcar (lambda (elem) `(,(if var (intern (format "%s-%s" var elem)) elem) (,(intern (format "tramp-file-name-%s" elem)) ,(or var 'v)))) - '(method user domain host port localname hop)))) + `,(tramp-compat-tramp-file-name-slots)))) `(let* ((,(or var 'v) (tramp-dissect-file-name ,filename)) ,@bindings) ;; We don't know which of those vars will be used, so we bind them all, @@ -1847,7 +1951,7 @@ special handling of `substitute-in-file-name'." 'tramp-rfn-eshadow-setup-minibuffer))) (defun tramp-rfn-eshadow-update-overlay-regexp () - (format "[^%s/~]*\\(/\\|~\\)" (tramp-postfix-host-format))) + (format "[^%s/~]*\\(/\\|~\\)" tramp-postfix-host-format)) (defun tramp-rfn-eshadow-update-overlay () "Update `rfn-eshadow-overlay' to cover shadowed part of minibuffer input. @@ -1988,7 +2092,9 @@ ARGS are the arguments OPERATION has been called with." substitute-in-file-name unhandled-file-name-directory vc-registered ;; Emacs 26+ only. - file-name-case-insensitive-p)) + file-name-case-insensitive-p + ;; Emacs 27+ only. + file-system-info)) (if (file-name-absolute-p (nth 0 args)) (nth 0 args) default-directory)) @@ -1997,6 +2103,11 @@ ARGS are the arguments OPERATION has been called with." '(add-name-to-file copy-directory copy-file expand-file-name file-equal-p file-in-directory-p file-name-all-completions file-name-completion + ;; Starting with Emacs 26.1, just the 2nd argument of + ;; `make-symbolic-link' matters. For backward + ;; compatibility, we still accept the first argument as + ;; file name to be checked. Handled properly in + ;; `tramp-handle-*-make-symbolic-link'. file-newer-than-file-p make-symbolic-link rename-file)) (save-match-data (cond @@ -2053,6 +2164,33 @@ ARGS are the arguments OPERATION has been called with." `(let ((debug-on-error tramp-debug-on-error)) (condition-case-unless-debug ,var ,bodyform ,@handlers))) +;; In Emacs, there is some concurrency due to timers. If a timer +;; interrupts Tramp and wishes to use the same connection buffer as +;; the "main" Emacs, then garbage might occur in the connection +;; buffer. Therefore, we need to make sure that a timer does not use +;; the same connection buffer as the "main" Emacs. We implement a +;; cheap global lock, instead of locking each connection buffer +;; separately. The global lock is based on two variables, +;; `tramp-locked' and `tramp-locker'. `tramp-locked' is set to true +;; (with setq) to indicate a lock. But Tramp also calls itself during +;; processing of a single file operation, so we need to allow +;; recursive calls. That's where the `tramp-locker' variable comes in +;; -- it is let-bound to t during the execution of the current +;; handler. So if `tramp-locked' is t and `tramp-locker' is also t, +;; then we should just proceed because we have been called +;; recursively. But if `tramp-locker' is nil, then we are a timer +;; interrupting the "main" Emacs, and then we signal an error. + +(defvar tramp-locked nil + "If non-nil, then Tramp is currently busy. +Together with `tramp-locker', this implements a locking mechanism +preventing reentrant calls of Tramp.") + +(defvar tramp-locker nil + "If non-nil, then a caller has locked Tramp. +Together with `tramp-locked', this implements a locking mechanism +preventing reentrant calls of Tramp.") + ;; Main function. (defun tramp-file-name-handler (operation &rest args) "Invoke Tramp file name handler. @@ -2090,7 +2228,18 @@ Falls back to normal file name handler if no Tramp file name handler exists." (setq result (catch 'non-essential (catch 'suppress - (apply foreign operation args)))) + (when (and tramp-locked (not tramp-locker)) + (setq tramp-locked nil) + (tramp-error + (car-safe tramp-current-connection) + 'file-error + "Forbidden reentrant call of Tramp")) + (let ((tl tramp-locked)) + (setq tramp-locked t) + (unwind-protect + (let ((tramp-locker t)) + (apply foreign operation args)) + (setq tramp-locked tl)))))) (cond ((eq result 'non-essential) (tramp-message @@ -2145,34 +2294,6 @@ Falls back to normal file name handler if no Tramp file name handler exists." ;; we don't do anything. (tramp-run-real-handler operation args)))) -;; In Emacs, there is some concurrency due to timers. If a timer -;; interrupts Tramp and wishes to use the same connection buffer as -;; the "main" Emacs, then garbage might occur in the connection -;; buffer. Therefore, we need to make sure that a timer does not use -;; the same connection buffer as the "main" Emacs. We implement a -;; cheap global lock, instead of locking each connection buffer -;; separately. The global lock is based on two variables, -;; `tramp-locked' and `tramp-locker'. `tramp-locked' is set to true -;; (with setq) to indicate a lock. But Tramp also calls itself during -;; processing of a single file operation, so we need to allow -;; recursive calls. That's where the `tramp-locker' variable comes in -;; -- it is let-bound to t during the execution of the current -;; handler. So if `tramp-locked' is t and `tramp-locker' is also t, -;; then we should just proceed because we have been called -;; recursively. But if `tramp-locker' is nil, then we are a timer -;; interrupting the "main" Emacs, and then we signal an error. - -(defvar tramp-locked nil - "If non-nil, then Tramp is currently busy. -Together with `tramp-locker', this implements a locking mechanism -preventing reentrant calls of Tramp.") - -(defvar tramp-locker nil - "If non-nil, then a caller has locked Tramp. -Together with `tramp-locked', this implements a locking mechanism -preventing reentrant calls of Tramp.") - -;;;###autoload (defun tramp-completion-file-name-handler (operation &rest args) "Invoke Tramp file name completion handler. Falls back to normal file name handler if no Tramp file name handler exists." @@ -2184,8 +2305,10 @@ Falls back to normal file name handler if no Tramp file name handler exists." ;;;###autoload (progn (defun tramp-autoload-file-name-handler (operation &rest args) "Load Tramp file name handler, and perform OPERATION." - (let ((default-directory temporary-file-directory)) - (load "tramp" 'noerror 'nomessage)) + (if tramp-mode + (let ((default-directory temporary-file-directory)) + (load "tramp" 'noerror 'nomessage)) + (tramp-unload-file-name-handlers)) (apply operation args))) ;; `tramp-autoload-file-name-handler' must be registered before @@ -2195,20 +2318,11 @@ Falls back to normal file name handler if no Tramp file name handler exists." (progn (defun tramp-register-autoload-file-name-handlers () "Add Tramp file name handlers to `file-name-handler-alist' during autoload." (add-to-list 'file-name-handler-alist - (cons tramp-initial-file-name-regexp + (cons tramp-autoload-file-name-regexp 'tramp-autoload-file-name-handler)) - (put 'tramp-autoload-file-name-handler 'safe-magic t) + (put 'tramp-autoload-file-name-handler 'safe-magic t))) - (add-to-list 'file-name-handler-alist - (cons tramp-initial-completion-file-name-regexp - 'tramp-completion-file-name-handler)) - (put 'tramp-completion-file-name-handler 'safe-magic t) - ;; Mark `operations' the handler is responsible for. - (put 'tramp-completion-file-name-handler 'operations - (mapcar 'car tramp-completion-file-name-handler-alist)))) - -;;;###autoload -(tramp-register-autoload-file-name-handlers) +;;;###autoload (tramp-register-autoload-file-name-handlers) (defun tramp-use-absolute-autoload-file-names () "Change Tramp autoload objects to use absolute file names. @@ -2249,11 +2363,11 @@ remote file names." ;; property of `tramp-file-name-handler', this shall be done by the ;; respective foreign handlers. (add-to-list 'file-name-handler-alist - (cons (tramp-file-name-regexp) 'tramp-file-name-handler)) + (cons tramp-file-name-regexp 'tramp-file-name-handler)) (put 'tramp-file-name-handler 'safe-magic t) (add-to-list 'file-name-handler-alist - (cons (tramp-completion-file-name-regexp) + (cons tramp-completion-file-name-regexp 'tramp-completion-file-name-handler)) (put 'tramp-completion-file-name-handler 'safe-magic t) ;; Mark `operations' the handler is responsible for. @@ -2309,12 +2423,13 @@ Add operations defined in `HANDLER-alist' to `tramp-file-name-handler'." (equal (apply operation args) operation)))) ;;;###autoload -(defun tramp-unload-file-name-handlers () +(progn (defun tramp-unload-file-name-handlers () "Unload Tramp file name handlers from `file-name-handler-alist'." (dolist (fnh '(tramp-file-name-handler - tramp-completion-file-name-handler)) + tramp-completion-file-name-handler + tramp-autoload-file-name-handler)) (let ((a1 (rassq fnh file-name-handler-alist))) - (setq file-name-handler-alist (delq a1 file-name-handler-alist))))) + (setq file-name-handler-alist (delq a1 file-name-handler-alist)))))) (add-hook 'tramp-unload-hook 'tramp-unload-file-name-handlers) @@ -2346,7 +2461,8 @@ not in completion mode." ;; Method, host name and user name completion. ;; `tramp-completion-dissect-file-name' returns a list of -;; tramp-file-name structures. For all of them we return possible completions. +;; `tramp-file-name' structures. For all of them we return possible +;; completions. (defun tramp-completion-handle-file-name-all-completions (filename directory) "Like `file-name-all-completions' for partial Tramp files." @@ -2357,8 +2473,8 @@ not in completion mode." ;; Suppress hop from completion. (when (string-match (concat - (tramp-prefix-regexp) - "\\(" "\\(" (tramp-remote-file-name-spec-regexp) + tramp-prefix-regexp + "\\(" "\\(" tramp-remote-file-name-spec-regexp tramp-postfix-hop-regexp "\\)+" "\\)") fullname) @@ -2403,9 +2519,8 @@ not in completion mode." ;; Unify list, add hop, remove nil elements. (dolist (elt result) (when elt - (string-match (tramp-prefix-regexp) elt) - (setq elt - (replace-match (concat (tramp-prefix-format) hop) nil nil elt)) + (string-match tramp-prefix-regexp elt) + (setq elt (replace-match (concat tramp-prefix-format hop) nil nil elt)) (push (substring elt (length (tramp-drop-volume-letter directory))) result1))) @@ -2428,9 +2543,9 @@ not in completion mode." (tramp-connectable-p (expand-file-name filename directory))) (lambda (x) (funcall predicate (expand-file-name (car x) directory)))))) -;; I misuse a little bit the tramp-file-name structure in order to +;; I misuse a little bit the `tramp-file-name' structure in order to ;; handle completion possibilities for partial methods / user names / -;; host names. Return value is a list of tramp-file-name structures +;; host names. Return value is a list of `tramp-file-name' structures ;; according to possible completions. If "localname" is non-nil it ;; means there shouldn't be a completion anymore. @@ -2453,58 +2568,58 @@ They are collected by `tramp-completion-dissect-file-name1'." (tramp-completion-ipv6-regexp (format "[^%s]*" - (if (zerop (length (tramp-postfix-ipv6-format))) - (tramp-postfix-host-format) - (tramp-postfix-ipv6-format)))) + (if (zerop (length tramp-postfix-ipv6-format)) + tramp-postfix-host-format + tramp-postfix-ipv6-format))) ;; "/method" "/[method" (tramp-completion-file-name-structure1 (list (concat - (tramp-prefix-regexp) - "\\(" (tramp-method-regexp) x-nil "\\)$") + tramp-prefix-regexp + "\\(" tramp-method-regexp x-nil "\\)$") 1 nil nil nil)) ;; "/method:user" "/[method/user" (tramp-completion-file-name-structure2 (list (concat - (tramp-prefix-regexp) - "\\(" (tramp-method-regexp) "\\)" (tramp-postfix-method-regexp) - "\\(" tramp-user-regexp x-nil "\\)$") + tramp-prefix-regexp + "\\(" tramp-method-regexp "\\)" tramp-postfix-method-regexp + "\\(" tramp-user-regexp x-nil "\\)$") 1 2 nil nil)) ;; "/method:host" "/[method/host" (tramp-completion-file-name-structure3 (list (concat - (tramp-prefix-regexp) - "\\(" (tramp-method-regexp) "\\)" (tramp-postfix-method-regexp) - "\\(" tramp-host-regexp x-nil "\\)$") + tramp-prefix-regexp + "\\(" tramp-method-regexp "\\)" tramp-postfix-method-regexp + "\\(" tramp-host-regexp x-nil "\\)$") 1 nil 2 nil)) ;; "/method:[ipv6" "/[method/ipv6" (tramp-completion-file-name-structure4 (list (concat - (tramp-prefix-regexp) - "\\(" (tramp-method-regexp) "\\)" (tramp-postfix-method-regexp) - (tramp-prefix-ipv6-regexp) + tramp-prefix-regexp + "\\(" tramp-method-regexp "\\)" tramp-postfix-method-regexp + tramp-prefix-ipv6-regexp "\\(" tramp-completion-ipv6-regexp x-nil "\\)$") 1 nil 2 nil)) ;; "/method:user@host" "/[method/user@host" (tramp-completion-file-name-structure5 (list (concat - (tramp-prefix-regexp) - "\\(" (tramp-method-regexp) "\\)" (tramp-postfix-method-regexp) - "\\(" tramp-user-regexp "\\)" tramp-postfix-user-regexp - "\\(" tramp-host-regexp x-nil "\\)$") + tramp-prefix-regexp + "\\(" tramp-method-regexp "\\)" tramp-postfix-method-regexp + "\\(" tramp-user-regexp "\\)" tramp-postfix-user-regexp + "\\(" tramp-host-regexp x-nil "\\)$") 1 2 3 nil)) ;; "/method:user@[ipv6" "/[method/user@ipv6" (tramp-completion-file-name-structure6 (list (concat - (tramp-prefix-regexp) - "\\(" (tramp-method-regexp) "\\)" (tramp-postfix-method-regexp) - "\\(" tramp-user-regexp "\\)" tramp-postfix-user-regexp - (tramp-prefix-ipv6-regexp) + tramp-prefix-regexp + "\\(" tramp-method-regexp "\\)" tramp-postfix-method-regexp + "\\(" tramp-user-regexp "\\)" tramp-postfix-user-regexp + tramp-prefix-ipv6-regexp "\\(" tramp-completion-ipv6-regexp x-nil "\\)$") 1 2 3 nil))) (delq @@ -2790,18 +2905,44 @@ User is always nil." (defvar tramp-handle-write-region-hook nil "Normal hook to be run at the end of `tramp-*-handle-write-region'.") +(defun tramp-handle-add-name-to-file + (filename newname &optional ok-if-already-exists) + "Like `add-name-to-file' for Tramp files." + (with-parsed-tramp-file-name + (if (tramp-tramp-file-p newname) newname filename) nil + (unless (tramp-equal-remote filename newname) + (tramp-error + v 'file-error + "add-name-to-file: %s" + "only implemented for same method, same user, same host")) + ;; Do the 'confirm if exists' thing. + (when (file-exists-p newname) + ;; What to do? + (if (or (null ok-if-already-exists) ; not allowed to exist + (and (numberp ok-if-already-exists) + (not (yes-or-no-p + (format + "File %s already exists; make it a link anyway? " + localname))))) + (tramp-error v 'file-already-exists newname) + (delete-file newname))) + (tramp-flush-file-property v (file-name-directory localname)) + (tramp-flush-file-property v localname) + (copy-file + filename newname 'ok-if-already-exists 'keep-time + 'preserve-uid-gid 'preserve-permissions))) + (defun tramp-handle-directory-file-name (directory) "Like `directory-file-name' for Tramp files." ;; If localname component of filename is "/", leave it unchanged. ;; Otherwise, remove any trailing slash from localname component. - ;; Method, host, etc, are unchanged. Does it make sense to try - ;; to avoid parsing the filename? - (with-parsed-tramp-file-name directory nil - (if (and (not (zerop (length localname))) - (eq (aref localname (1- (length localname))) ?/) - (not (string= localname "/"))) - (substring directory 0 -1) - directory))) + ;; Method, host, etc, are unchanged. + (while (with-parsed-tramp-file-name directory nil + (and (not (zerop (length localname))) + (eq (aref localname (1- (length localname))) ?/) + (not (string= localname "/")))) + (setq directory (substring directory 0 -1))) + directory) (defun tramp-handle-directory-files (directory &optional full match nosort) "Like `directory-files' for Tramp files." @@ -3029,14 +3170,57 @@ User is always nil." (t (tramp-make-tramp-file-name method user domain host port "" hop))))))))) +(defun tramp-handle-file-selinux-context (_filename) + "Like `file-selinux-context' for Tramp files." + ;; Return nil context. + '(nil nil nil nil)) + (defun tramp-handle-file-symlink-p (filename) "Like `file-symlink-p' for Tramp files." - (with-parsed-tramp-file-name filename nil - (let ((x (tramp-compat-file-attribute-type (file-attributes filename)))) - (when (stringp x) - (if (file-name-absolute-p x) - (tramp-make-tramp-file-name method user domain host port x) - x))))) + (let ((x (tramp-compat-file-attribute-type (file-attributes filename)))) + (and (stringp x) x))) + +(defun tramp-handle-file-truename (filename) + "Like `file-truename' for Tramp files." + (let ((result (expand-file-name filename)) + (numchase 0) + ;; Don't make the following value larger than + ;; necessary. People expect an error message in a + ;; timely fashion when something is wrong; + ;; otherwise they might think that Emacs is hung. + ;; Of course, correctness has to come first. + (numchase-limit 20) + symlink-target) + (format + "%s%s" + (with-parsed-tramp-file-name result v1 + (with-tramp-file-property v1 v1-localname "file-truename" + (while (and (setq symlink-target (file-symlink-p result)) + (< numchase numchase-limit)) + (setq numchase (1+ numchase) + result + (with-parsed-tramp-file-name (expand-file-name result) v2 + (tramp-make-tramp-file-name + v2-method v2-user v2-domain v2-host v2-port + (funcall + (if (tramp-compat-file-name-quoted-p v2-localname) + 'tramp-compat-file-name-quote 'identity) + + (if (stringp symlink-target) + (if (file-remote-p symlink-target) + (let (file-name-handler-alist) + (tramp-compat-file-name-quote symlink-target)) + (expand-file-name + symlink-target (file-name-directory v2-localname))) + v2-localname))))) + (when (>= numchase numchase-limit) + (tramp-error + v1 'file-error + "Maximum number (%d) of symlinks exceeded" numchase-limit))) + result)) + + ;; Preserve trailing "/". + (if (string-equal (file-name-nondirectory filename) "") "/" "")))) (defun tramp-handle-find-backup-file-name (filename) "Like `find-backup-file-name' for Tramp files." @@ -3049,9 +3233,9 @@ User is always nil." (car x) (if (and (stringp (cdr x)) (file-name-absolute-p (cdr x)) - (not (tramp-file-name-p (cdr x)))) + (not (tramp-tramp-file-p (cdr x)))) (tramp-make-tramp-file-name - method user domain host port (cdr x)) + method user domain host port (cdr x) hop) (cdr x)))) tramp-backup-directory-alist) backup-directory-alist))) @@ -3239,11 +3423,18 @@ User is always nil." t))) (defun tramp-handle-make-symbolic-link - (filename linkname &optional _ok-if-already-exists) - "Like `make-symbolic-link' for Tramp files." - (with-parsed-tramp-file-name - (if (tramp-tramp-file-p filename) filename linkname) nil - (tramp-error v 'file-error "make-symbolic-link not supported"))) + (target linkname &optional ok-if-already-exists) + "Like `make-symbolic-link' for Tramp files. +This is the fallback implementation for backends which do not +support symbolic links." + (if (tramp-tramp-file-p (expand-file-name linkname)) + (tramp-error + (tramp-dissect-file-name (expand-file-name linkname)) 'file-error + "make-symbolic-link not supported") + ;; This is needed prior Emacs 26.1, where TARGET has also be + ;; checked for a file name handler. + (tramp-run-real-handler + 'make-symbolic-link (list target linkname ok-if-already-exists)))) (defun tramp-handle-shell-command (command &optional output-buffer error-buffer) @@ -3631,31 +3822,17 @@ connection buffer." "Like `accept-process-output' for Tramp processes. This is needed in order to hide `last-coding-system-used', which is set for process communication also." - ;; FIXME: There are problems, when an asynchronous process runs in - ;; parallel, and also timers are active. See - ;; <http://lists.gnu.org/archive/html/tramp-devel/2017-01/msg00010.html>. - (when (and timer-event-last - (string-prefix-p "*tramp/" (process-name proc)) - (let (result) - (maphash - (lambda (key _value) - (and (processp key) - (not (string-prefix-p "*tramp/" (process-name key))) - (process-live-p key) - (setq result t))) - tramp-cache-data) - result)) - (sit-for 0.01 'nodisp)) (with-current-buffer (process-buffer proc) (let (buffer-read-only last-coding-system-used) - ;; Under Windows XP, accept-process-output doesn't return + ;; Under Windows XP, `accept-process-output' doesn't return ;; sometimes. So we add an additional timeout. JUST-THIS-ONE - ;; is set due to Bug#12145. + ;; is set due to Bug#12145. It is an integer, in order to avoid + ;; running timers as well. (tramp-message proc 10 "%s %s %s\n%s" proc (process-status proc) (with-timeout (timeout) - (accept-process-output proc timeout nil t)) + (accept-process-output proc timeout nil 0)) (buffer-string))))) (defun tramp-check-for-regexp (proc regexp) @@ -3687,7 +3864,7 @@ Erase echoed commands if exists." (min (+ (point-min) tramp-echo-mark-marker-length) (point-max)))))) ;; No echo to be handled, now we can look for the regexp. - ;; Sometimes, lines are much to long, and we run into a "Stack + ;; Sometimes, lines are much too long, and we run into a "Stack ;; overflow in regexp matcher". For example, //DIRED// lines of ;; directory listings with some thousand files. Therefore, we ;; look from the end. @@ -4306,10 +4483,10 @@ Invokes `password-read' if available, `read-passwd' else." (tramp-clear-passwd (tramp-dissect-file-name (concat - (tramp-prefix-format) + tramp-prefix-format (replace-regexp-in-string (concat tramp-postfix-hop-regexp "$") - (tramp-postfix-host-format) hop))))) + tramp-postfix-host-format hop))))) (auth-source-forget `(:max 1 ,(and user-domain :user) ,user-domain :host ,host-port :port ,method)) @@ -4369,6 +4546,47 @@ Only works for Bourne-like shells." t t result))) result)))) +;;; Signal handling. This works for remote processes, which have set +;;; the process property `remote-pid'. + +(defun tramp-interrupt-process (&optional process _current-group) + "Interrupt remote process PROC." + ;; CURRENT-GROUP is not implemented yet. + (let ((proc (cond + ((processp process) process) + ((bufferp process) (get-buffer-process process)) + ((stringp process) (or (get-process process) + (get-buffer-process process))) + ((null process) (get-buffer-process (current-buffer))) + (t process))) + pid) + ;; If it's a Tramp process, send the INT signal remotely. + (when (and (processp proc) (setq pid (process-get proc 'remote-pid))) + (if (not (process-live-p proc)) + (tramp-error proc 'error "Process %s is not active" proc) + (tramp-message proc 5 "Interrupt process %s with pid %s" proc pid) + ;; This is for tramp-sh.el. Other backends do not support this (yet). + (tramp-compat-funcall + 'tramp-send-command + (tramp-get-connection-property proc "vector" nil) + (format "kill -2 %d" pid)) + ;; Wait, until the process has disappeared. + (with-timeout + (1 (tramp-error proc 'error "Process %s did not interrupt" proc)) + (while (process-live-p proc) + ;; We cannot run `tramp-accept-process-output', it blocks timers. + (accept-process-output proc 0.1))) + ;; Report success. + proc)))) + +;; `interrupt-process-functions' exists since Emacs 26.1. +(when (boundp 'interrupt-process-functions) + (add-hook 'interrupt-process-functions 'tramp-interrupt-process) + (add-hook + 'tramp-unload-hook + (lambda () + (remove-hook 'interrupt-process-functions 'tramp-interrupt-process)))) + ;;; Integration of eshell.el: ;; eshell.el keeps the path in `eshell-path-env'. We must change it @@ -4420,9 +4638,6 @@ Only works for Bourne-like shells." (provide 'tramp) ;;; TODO: - -;; * In Emacs 21, `insert-directory' shows total number of bytes used -;; by the files in that directory. Add this here. ;; ;; * Avoid screen blanking when hitting `g' in dired. (Eli Tziperman) ;; @@ -4438,7 +4653,7 @@ Only works for Bourne-like shells." ;; are. (Andrea Crotti) ;; ;; * Run emerge on two remote files. Bug is described here: -;; <http://www.mail-archive.com/tramp-devel@nongnu.org/msg01041.html>. +;; <https://www.mail-archive.com/tramp-devel@nongnu.org/msg01041.html>. ;; (Bug#6850) ;; ;; * Refactor code from different handlers. Start with diff --git a/lisp/net/trampver.el b/lisp/net/trampver.el index 387a3c8bb36..51af455e635 100644 --- a/lisp/net/trampver.el +++ b/lisp/net/trampver.el @@ -7,7 +7,7 @@ ;; Maintainer: Michael Albinus <michael.albinus@gmx.de> ;; Keywords: comm, processes ;; Package: tramp -;; Version: 2.3.2-pre +;; Version: 2.3.3-pre ;; This file is part of GNU Emacs. @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: @@ -33,7 +33,7 @@ ;; should be changed only there. ;;;###tramp-autoload -(defconst tramp-version "2.3.2-pre" +(defconst tramp-version "2.3.3-pre" "This version of Tramp.") ;;;###tramp-autoload @@ -55,7 +55,7 @@ ;; Check for Emacs version. (let ((x (if (>= emacs-major-version 24) "ok" - (format "Tramp 2.3.2-pre is not fit for %s" + (format "Tramp 2.3.3-pre is not fit for %s" (when (string-match "^.*$" (emacs-version)) (match-string 0 (emacs-version))))))) (unless (string-match "\\`ok\\'" x) (error "%s" x))) @@ -68,7 +68,9 @@ ("2.1.20" . "23.3") ("2.1.21-pre" . "23.4") ("2.2.3-24.1" . "24.1") ("2.2.3-24.1" . "24.2") ("2.2.6-24.3" . "24.3") ("2.2.9-24.4" . "24.4") ("2.2.11-24.5" . "24.5") - ("2.2.13.25.1" . "25.1") ("2.2.13.25.2" . "25.2"))) + ("2.2.13.25.1" . "25.1") ("2.2.13.25.2" . "25.2") + ("2.2.13.25.2" . "25.3") + ("2.3.3.26.1" . "26.1"))) (add-hook 'tramp-unload-hook (lambda () diff --git a/lisp/net/webjump.el b/lisp/net/webjump.el index f6e0cf87b9c..79a06021e1e 100644 --- a/lisp/net/webjump.el +++ b/lisp/net/webjump.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -76,9 +76,9 @@ '( ;; FSF, not including Emacs-specific. ("GNU Project FTP Archive" . - ;; GNU FTP Mirror List from http://www.gnu.org/order/ftp.html - [mirrors "ftp://ftp.gnu.org/pub/gnu/" - "http://ftpmirror.gnu.org"]) + ;; GNU FTP Mirror List from https://www.gnu.org/order/ftp.html + [mirrors "https://ftp.gnu.org/pub/gnu/" + "https://ftpmirror.gnu.org"]) ("GNU Project Home Page" . "www.gnu.org") ;; Emacs. diff --git a/lisp/net/zeroconf.el b/lisp/net/zeroconf.el index 393f3a549f9..7ad9c9f5c9b 100644 --- a/lisp/net/zeroconf.el +++ b/lisp/net/zeroconf.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/newcomment.el b/lisp/newcomment.el index 4b261c34c65..56ae14dee41 100644 --- a/lisp/newcomment.el +++ b/lisp/newcomment.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -69,6 +69,9 @@ ;;; Code: +(eval-when-compile + (require 'subr-x)) + ;;;###autoload (defalias 'indent-for-comment 'comment-indent) ;;;###autoload @@ -142,9 +145,10 @@ Should be an empty string if comments are terminated by end-of-line.") ;;;###autoload (defvar comment-indent-function 'comment-indent-default "Function to compute desired indentation for a comment. -This function is called with no args with point at the beginning of -the comment's starting delimiter and should return either the desired -column indentation or nil. +This function is called with no args with point at the beginning +of the comment's starting delimiter and should return either the +desired column indentation, a range of acceptable +indentation (MIN . MAX), or nil. If nil is returned, indentation is delegated to `indent-according-to-mode'.") ;;;###autoload @@ -523,7 +527,7 @@ Ensure that `comment-normalize-vars' has been called before you use this." ;; comment-search-backward is only used to find the comment-column (in ;; comment-set-column) and to find the comment-start string (via ;; comment-beginning) in indent-new-comment-line, it should be harmless. - (if (not (re-search-backward comment-start-skip limit t)) + (if (not (re-search-backward comment-start-skip limit 'move)) (unless noerror (error "No comment")) (beginning-of-line) (let* ((end (match-end 0)) @@ -649,13 +653,20 @@ The criteria are (in this order): - prefer INDENT (or `comment-column' if nil). Point is expected to be at the start of the comment." (unless indent (setq indent comment-column)) - ;; Avoid moving comments past the fill-column. - (let ((max (+ (current-column) - (- (or comment-fill-column fill-column) - (save-excursion (end-of-line) (current-column))))) - (other nil) - (min (save-excursion (skip-chars-backward " \t") - (if (bolp) 0 (+ comment-inline-offset (current-column)))))) + (let ((other nil) + min max) + (pcase indent + (`(,lo . ,hi) (setq min lo) (setq max hi) + (setq indent comment-column)) + (_ ;; Avoid moving comments past the fill-column. + (setq max (+ (current-column) + (- (or comment-fill-column fill-column) + (save-excursion (end-of-line) (current-column))))) + (setq min (save-excursion + (skip-chars-backward " \t") + ;; Leave at least `comment-inline-offset' space after + ;; other nonwhite text on the line. + (if (bolp) 0 (+ comment-inline-offset (current-column))))))) ;; Fix up the range. (if (< max min) (setq max min)) ;; Don't move past the fill column. @@ -750,13 +761,6 @@ If CONTINUE is non-nil, use the `comment-continue' markers if any." ;; If the comment is at the right of code, adjust the indentation. (unless (save-excursion (skip-chars-backward " \t") (bolp)) (setq indent (comment-choose-indent indent))) - ;; Update INDENT to leave at least one space - ;; after other nonwhite text on the line. - (save-excursion - (skip-chars-backward " \t") - (unless (bolp) - (setq indent (max indent - (+ (current-column) comment-inline-offset))))) ;; If that's different from comment's current position, change it. (unless (= (current-column) indent) (delete-region (point) (progn (skip-chars-backward " \t") (point))) @@ -815,7 +819,7 @@ N defaults to 0. If N is `re', a regexp is returned instead, that would match the string for any N." (setq n (or n 0)) - (when (and (stringp str) (not (string= "" str))) + (when (and (stringp str) (string-match "\\S-" str)) ;; Separate the actual string from any leading/trailing padding (string-match "\\`\\s-*\\(.*?\\)\\s-*\\'" str) (let ((s (match-string 1 str)) ;actual string @@ -1140,6 +1144,9 @@ the region rather than at left margin." ;; make the leading and trailing lines if requested (when lines + ;; Trim trailing whitespace from cs if there's some. + (setq cs (string-trim-right cs)) + (let ((csce (comment-make-extra-lines cs ce ccs cce min-indent max-indent block))) @@ -1210,7 +1217,7 @@ changed with `comment-style'." (progn (goto-char end) (end-of-line) (skip-syntax-backward " ") (<= (point) end)) (or block (not (string= "" comment-end))) - (or block (progn (goto-char beg) (search-forward "\n" end t))))) + (or block (progn (goto-char beg) (re-search-forward "$" end t))))) ;; don't add end-markers just because the user asked for `block' (unless (or lines (string= "" comment-end)) (setq block nil)) diff --git a/lisp/notifications.el b/lisp/notifications.el index 194b0894a9b..9290f71d4ee 100644 --- a/lisp/notifications.el +++ b/lisp/notifications.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/novice.el b/lisp/novice.el index a5ad2a0c565..72c16af5feb 100644 --- a/lisp/novice.el +++ b/lisp/novice.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/nxml/nxml-enc.el b/lisp/nxml/nxml-enc.el index b359076ef4d..1a82b917754 100644 --- a/lisp/nxml/nxml-enc.el +++ b/lisp/nxml/nxml-enc.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -91,7 +91,7 @@ ;; no-conversion gives the user a chance to fix it. 'no-conversion) ;; There are other things we might try here in the future - ;; eg UTF-8 BOM, UTF-16 with no BOM + ;; eg UTF-8 BOM, UTF-16 with no BOM ;; translate to EBCDIC (t (let ((enc-pos (xmltok-get-declared-encoding-position limit))) diff --git a/lisp/nxml/nxml-maint.el b/lisp/nxml/nxml-maint.el index 55abca18e05..9ba2b3287df 100644 --- a/lisp/nxml/nxml-maint.el +++ b/lisp/nxml/nxml-maint.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/nxml/nxml-mode.el b/lisp/nxml/nxml-mode.el index 7e33e743de0..3f4dce261d9 100644 --- a/lisp/nxml/nxml-mode.el +++ b/lisp/nxml/nxml-mode.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/nxml/nxml-ns.el b/lisp/nxml/nxml-ns.el index a9388d98824..c4845a67f81 100644 --- a/lisp/nxml/nxml-ns.el +++ b/lisp/nxml/nxml-ns.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/nxml/nxml-outln.el b/lisp/nxml/nxml-outln.el index 2c414e489da..5a2ecae220e 100644 --- a/lisp/nxml/nxml-outln.el +++ b/lisp/nxml/nxml-outln.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/nxml/nxml-parse.el b/lisp/nxml/nxml-parse.el index bce8cc9ee0b..6c00dc7375e 100644 --- a/lisp/nxml/nxml-parse.el +++ b/lisp/nxml/nxml-parse.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/nxml/nxml-rap.el b/lisp/nxml/nxml-rap.el index 0132a2b9234..daec948f1c8 100644 --- a/lisp/nxml/nxml-rap.el +++ b/lisp/nxml/nxml-rap.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/nxml/nxml-util.el b/lisp/nxml/nxml-util.el index 9f085458d88..dcb3ef4bf60 100644 --- a/lisp/nxml/nxml-util.el +++ b/lisp/nxml/nxml-util.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/nxml/rng-cmpct.el b/lisp/nxml/rng-cmpct.el index a09c77c51ae..b35774f4710 100644 --- a/lisp/nxml/rng-cmpct.el +++ b/lisp/nxml/rng-cmpct.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/nxml/rng-dt.el b/lisp/nxml/rng-dt.el index 6e60609445e..b62ba57dc27 100644 --- a/lisp/nxml/rng-dt.el +++ b/lisp/nxml/rng-dt.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/nxml/rng-loc.el b/lisp/nxml/rng-loc.el index 359a7178684..891f1019089 100644 --- a/lisp/nxml/rng-loc.el +++ b/lisp/nxml/rng-loc.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/nxml/rng-maint.el b/lisp/nxml/rng-maint.el index 8378b1d6491..8d85f2ea06b 100644 --- a/lisp/nxml/rng-maint.el +++ b/lisp/nxml/rng-maint.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -226,11 +226,10 @@ (defun rng-time-function (function &rest args) (let* ((start (current-time)) - (val (apply function args)) - (end (current-time))) + (val (apply function args))) (message "%s ran in %g seconds" function - (float-time (time-subtract end start))) + (float-time (time-subtract nil start))) val)) (defun rng-time-tokenize-buffer () diff --git a/lisp/nxml/rng-match.el b/lisp/nxml/rng-match.el index e3401741fbf..075695bd5cb 100644 --- a/lisp/nxml/rng-match.el +++ b/lisp/nxml/rng-match.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/nxml/rng-nxml.el b/lisp/nxml/rng-nxml.el index caa3d63e390..e878cfefaa0 100644 --- a/lisp/nxml/rng-nxml.el +++ b/lisp/nxml/rng-nxml.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/nxml/rng-parse.el b/lisp/nxml/rng-parse.el index f3afbdd07de..9796c8a70c8 100644 --- a/lisp/nxml/rng-parse.el +++ b/lisp/nxml/rng-parse.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/nxml/rng-pttrn.el b/lisp/nxml/rng-pttrn.el index 29b55816a79..6975f3c1b78 100644 --- a/lisp/nxml/rng-pttrn.el +++ b/lisp/nxml/rng-pttrn.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/nxml/rng-uri.el b/lisp/nxml/rng-uri.el index 6b3190a1b09..4bd619eb6d4 100644 --- a/lisp/nxml/rng-uri.el +++ b/lisp/nxml/rng-uri.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/nxml/rng-util.el b/lisp/nxml/rng-util.el index a804771e33a..f49a6814cd3 100644 --- a/lisp/nxml/rng-util.el +++ b/lisp/nxml/rng-util.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/nxml/rng-valid.el b/lisp/nxml/rng-valid.el index 6837424857c..a96aedfdc4c 100644 --- a/lisp/nxml/rng-valid.el +++ b/lisp/nxml/rng-valid.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/nxml/rng-xsd.el b/lisp/nxml/rng-xsd.el index 51a05f8cad5..79039abf183 100644 --- a/lisp/nxml/rng-xsd.el +++ b/lisp/nxml/rng-xsd.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/nxml/xmltok.el b/lisp/nxml/xmltok.el index 69dc541bc51..5d31392aa99 100644 --- a/lisp/nxml/xmltok.el +++ b/lisp/nxml/xmltok.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/nxml/xsd-regexp.el b/lisp/nxml/xsd-regexp.el index d56960c9fa9..e22d6f75421 100644 --- a/lisp/nxml/xsd-regexp.el +++ b/lisp/nxml/xsd-regexp.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obarray.el b/lisp/obarray.el index b1160ebea43..0915e22a72c 100644 --- a/lisp/obarray.el +++ b/lisp/obarray.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obsolete/abbrevlist.el b/lisp/obsolete/abbrevlist.el index ebef215fcc0..34393b3d797 100644 --- a/lisp/obsolete/abbrevlist.el +++ b/lisp/obsolete/abbrevlist.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obsolete/assoc.el b/lisp/obsolete/assoc.el index eab8d13a81e..6313006f7d1 100644 --- a/lisp/obsolete/assoc.el +++ b/lisp/obsolete/assoc.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obsolete/bruce.el b/lisp/obsolete/bruce.el index 6af597d9fe5..99f33b0d126 100644 --- a/lisp/obsolete/bruce.el +++ b/lisp/obsolete/bruce.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -39,7 +39,7 @@ ;; reading your meeting schedule notices or other email boring to everyone ;; but you and (you hope) the recipient. See below (I left in the original ;; writeup when I made this conversion), or the emacs documentation at -;; ftp://prep.ai.mit.edu/pub/gnu/emacs-manual*. +;; https://www.gnu.org/software/emacs/manual/. ;; Bruce is a direct copy of spook, with the word "spook" replaced with ;; the word "bruce". Thanks to "esr", whoever he, she or it may be, this diff --git a/lisp/obsolete/cc-compat.el b/lisp/obsolete/cc-compat.el index c9fdf739f1d..6d05eec8e4e 100644 --- a/lisp/obsolete/cc-compat.el +++ b/lisp/obsolete/cc-compat.el @@ -23,7 +23,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -106,7 +106,7 @@ This is in addition to c-continued-statement-offset.") (if (eq (char-before) ?{) (forward-char -1) (goto-char (cdr langelem))) - (let* ((curcol (save-excursion + (let* ((curcol (save-excursion (goto-char (cdr langelem)) (current-column))) (bocm-lossage @@ -138,7 +138,7 @@ This is in addition to c-continued-statement-offset.") (defun cc-block-close-offset (langelem) (save-excursion (let* ((here (point)) - bracep + bracep (curcol (progn (goto-char (cdr langelem)) (current-column))) @@ -154,7 +154,7 @@ This is in addition to c-continued-statement-offset.") (current-column)))) (- bocm-lossage curcol (if bracep 0 c-indent-level))))) - + (defun cc-substatement-open-offset (langelem) (+ c-continued-statement-offset c-continued-brace-offset)) diff --git a/lisp/obsolete/cl-compat.el b/lisp/obsolete/cl-compat.el index 930b59e89d3..d021c68571e 100644 --- a/lisp/obsolete/cl-compat.el +++ b/lisp/obsolete/cl-compat.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obsolete/complete.el b/lisp/obsolete/complete.el index 6a7fdc59c22..1f154a4d2e9 100644 --- a/lisp/obsolete/complete.el +++ b/lisp/obsolete/complete.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -924,7 +924,7 @@ or properties are considered." (or (boundp sym) (fboundp sym) (symbol-plist sym)))))) (PC-not-minibuffer t)) - ;; http://lists.gnu.org/archive/html/emacs-devel/2007-03/msg01211.html + ;; https://lists.gnu.org/r/emacs-devel/2007-03/msg01211.html ;; ;; This deals with cases like running PC-l-c-s on "M-: (n-f". ;; The first call to PC-l-c-s expands this to "(ne-f", and moves diff --git a/lisp/obsolete/crisp.el b/lisp/obsolete/crisp.el index aa13be1bc6d..85fd4dcdaf3 100644 --- a/lisp/obsolete/crisp.el +++ b/lisp/obsolete/crisp.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obsolete/cust-print.el b/lisp/obsolete/cust-print.el index b9aef43e0ba..8c12306112e 100644 --- a/lisp/obsolete/cust-print.el +++ b/lisp/obsolete/cust-print.el @@ -24,7 +24,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obsolete/erc-hecomplete.el b/lisp/obsolete/erc-hecomplete.el index 4b0b8efa6a3..b1201eb9a9a 100644 --- a/lisp/obsolete/erc-hecomplete.el +++ b/lisp/obsolete/erc-hecomplete.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -219,4 +219,3 @@ Window configurations are stored in ;; indent-tabs-mode: t ;; tab-width: 8 ;; End: - diff --git a/lisp/obsolete/eudcb-ph.el b/lisp/obsolete/eudcb-ph.el index 06d6f52f5b4..28b9be0ffac 100644 --- a/lisp/obsolete/eudcb-ph.el +++ b/lisp/obsolete/eudcb-ph.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obsolete/fast-lock.el b/lisp/obsolete/fast-lock.el index d1e2c24febc..ebcdd235cf4 100644 --- a/lisp/obsolete/fast-lock.el +++ b/lisp/obsolete/fast-lock.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obsolete/gs.el b/lisp/obsolete/gs.el index 5bc77d8c349..c821ebf79f8 100644 --- a/lisp/obsolete/gs.el +++ b/lisp/obsolete/gs.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obsolete/gulp.el b/lisp/obsolete/gulp.el index 11a7e02ab96..5aa4fb4e1da 100644 --- a/lisp/obsolete/gulp.el +++ b/lisp/obsolete/gulp.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/html2text.el b/lisp/obsolete/html2text.el index 87c71dc504a..d1dc876f289 100644 --- a/lisp/net/html2text.el +++ b/lisp/obsolete/html2text.el @@ -3,6 +3,7 @@ ;; Copyright (C) 2002-2017 Free Software Foundation, Inc. ;; Author: Joakim Hove <hove@phys.ntnu.no> +;; Obsolete-since: 26.1 ;; This file is part of GNU Emacs. @@ -17,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -29,14 +30,14 @@ ;; ;; The main function is `html2text'. +;; This package was obsoleted by shr.el. + ;;; Code: ;; ;; <Global variables> ;; -(eval-when-compile - (require 'cl)) (defvar html2text-format-single-element-list '(("hr" . html2text-clean-hr))) @@ -297,7 +298,7 @@ formatting, and then moved afterward.") (defun html2text-clean-blockquote (p1 p2 p3 p4) (html2text-delete-tags p1 p2 p3 p4)) -(defun html2text-clean-anchor (p1 p2 p3 p4) +(defun html2text-clean-anchor (p1 p2 _p3 p4) ;; If someone can explain how to make the URL clickable I will surely ;; improve upon this. ;; Maybe `goto-addr.el' can be used here. diff --git a/lisp/obsolete/iswitchb.el b/lisp/obsolete/iswitchb.el index 71cc917938d..59c2ee7eb00 100644 --- a/lisp/obsolete/iswitchb.el +++ b/lisp/obsolete/iswitchb.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obsolete/landmark.el b/lisp/obsolete/landmark.el index b6bbca44801..7795279bf42 100644 --- a/lisp/obsolete/landmark.el +++ b/lisp/obsolete/landmark.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -260,7 +260,7 @@ is non-nil. One interesting value is `turn-on-font-lock'." "Vector recording the actual score of the free squares.") -;; The key point point about the algorithm is that, rather than considering +;; The key point about the algorithm is that, rather than considering ;; the board as just a set of squares, we prefer to see it as a "space" of ;; internested 5-tuples of contiguous squares (called qtuples). ;; diff --git a/lisp/obsolete/lazy-lock.el b/lisp/obsolete/lazy-lock.el index 5fa8fa48fa8..64304391bb8 100644 --- a/lisp/obsolete/lazy-lock.el +++ b/lisp/obsolete/lazy-lock.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obsolete/ledit.el b/lisp/obsolete/ledit.el index 3dde96c3bb7..9cf6f7629f4 100644 --- a/lisp/obsolete/ledit.el +++ b/lisp/obsolete/ledit.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obsolete/levents.el b/lisp/obsolete/levents.el index e445b1ac553..b9b153553d7 100644 --- a/lisp/obsolete/levents.el +++ b/lisp/obsolete/levents.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -113,7 +113,7 @@ In actual Lucid Emacs, you MUST NOT use this event object after calling this function with it. You will lose. It is not necessary to call this function, as event objects are garbage- collected like all other objects; however, it may be more efficient to explicitly -deallocate events when you are sure that that is safe. +deallocate events when you are sure that this is safe. This emulation does not actually deallocate or reuse events except via garbage collection and `cons'." diff --git a/lisp/obsolete/lmenu.el b/lisp/obsolete/lmenu.el index a790d211485..44ef617031b 100644 --- a/lisp/obsolete/lmenu.el +++ b/lisp/obsolete/lmenu.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obsolete/longlines.el b/lisp/obsolete/longlines.el index a6c6a0c9fcf..b45b4a4af9a 100644 --- a/lisp/obsolete/longlines.el +++ b/lisp/obsolete/longlines.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obsolete/lucid.el b/lisp/obsolete/lucid.el index bf8347bf9e6..562c60aee2d 100644 --- a/lisp/obsolete/lucid.el +++ b/lisp/obsolete/lucid.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obsolete/messcompat.el b/lisp/obsolete/messcompat.el index 46adf836005..6dc4df0cc88 100644 --- a/lisp/obsolete/messcompat.el +++ b/lisp/obsolete/messcompat.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obsolete/mouse-sel.el b/lisp/obsolete/mouse-sel.el index 3e673725aea..aee1ef8e82b 100644 --- a/lisp/obsolete/mouse-sel.el +++ b/lisp/obsolete/mouse-sel.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obsolete/old-emacs-lock.el b/lisp/obsolete/old-emacs-lock.el index 233c105dc0d..61986fe1fce 100644 --- a/lisp/obsolete/old-emacs-lock.el +++ b/lisp/obsolete/old-emacs-lock.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obsolete/old-whitespace.el b/lisp/obsolete/old-whitespace.el index defd18b35aa..0b96c52a741 100644 --- a/lisp/obsolete/old-whitespace.el +++ b/lisp/obsolete/old-whitespace.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obsolete/options.el b/lisp/obsolete/options.el index 2a61dc01ca3..ae1ad3b9ab6 100644 --- a/lisp/obsolete/options.el +++ b/lisp/obsolete/options.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obsolete/otodo-mode.el b/lisp/obsolete/otodo-mode.el index 5784601674c..dd25e336f0a 100644 --- a/lisp/obsolete/otodo-mode.el +++ b/lisp/obsolete/otodo-mode.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; --------------------------------------------------------------------------- @@ -164,7 +164,7 @@ ;; might be nicer and to that effect a function has been declared ;; further down in the code. You may wish to auto-load this. ;; -;; Carsten also writes that that *changing* the prefix after the +;; Carsten also writes that *changing* the prefix after the ;; todo list is already established is not as simple as changing ;; the variable - the todo files have to be changed by hand. ;; diff --git a/lisp/obsolete/pc-mode.el b/lisp/obsolete/pc-mode.el index c1b7ff92c70..fe282ffee56 100644 --- a/lisp/obsolete/pc-mode.el +++ b/lisp/obsolete/pc-mode.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obsolete/pc-select.el b/lisp/obsolete/pc-select.el index 59da29391d7..5353859a627 100644 --- a/lisp/obsolete/pc-select.el +++ b/lisp/obsolete/pc-select.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obsolete/pgg-def.el b/lisp/obsolete/pgg-def.el index 8d59c688b9d..25827269b28 100644 --- a/lisp/obsolete/pgg-def.el +++ b/lisp/obsolete/pgg-def.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: diff --git a/lisp/obsolete/pgg-gpg.el b/lisp/obsolete/pgg-gpg.el index 189b119bfae..1c08755bff6 100644 --- a/lisp/obsolete/pgg-gpg.el +++ b/lisp/obsolete/pgg-gpg.el @@ -23,7 +23,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: diff --git a/lisp/obsolete/pgg-parse.el b/lisp/obsolete/pgg-parse.el index b44117773d9..019d53d660d 100644 --- a/lisp/obsolete/pgg-parse.el +++ b/lisp/obsolete/pgg-parse.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obsolete/pgg-pgp.el b/lisp/obsolete/pgg-pgp.el index 507fbbb9136..cac5240a1bb 100644 --- a/lisp/obsolete/pgg-pgp.el +++ b/lisp/obsolete/pgg-pgp.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: diff --git a/lisp/obsolete/pgg-pgp5.el b/lisp/obsolete/pgg-pgp5.el index 8fd976fc23f..1504283b692 100644 --- a/lisp/obsolete/pgg-pgp5.el +++ b/lisp/obsolete/pgg-pgp5.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: diff --git a/lisp/obsolete/pgg.el b/lisp/obsolete/pgg.el index f99d759ec45..d84dc92e53b 100644 --- a/lisp/obsolete/pgg.el +++ b/lisp/obsolete/pgg.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: diff --git a/lisp/obsolete/rcompile.el b/lisp/obsolete/rcompile.el index dd2506841fd..1ad4f5a07f3 100644 --- a/lisp/obsolete/rcompile.el +++ b/lisp/obsolete/rcompile.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obsolete/s-region.el b/lisp/obsolete/s-region.el index 8a85f3c7961..9898f5f47a8 100644 --- a/lisp/obsolete/s-region.el +++ b/lisp/obsolete/s-region.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obsolete/sregex.el b/lisp/obsolete/sregex.el index f57befa5043..9790e7ffbcc 100644 --- a/lisp/obsolete/sregex.el +++ b/lisp/obsolete/sregex.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obsolete/sup-mouse.el b/lisp/obsolete/sup-mouse.el index 4aabe41951d..28822e1fbcd 100644 --- a/lisp/obsolete/sup-mouse.el +++ b/lisp/obsolete/sup-mouse.el @@ -23,7 +23,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obsolete/terminal.el b/lisp/obsolete/terminal.el index e5d85e69a3b..4e5f3694031 100644 --- a/lisp/obsolete/terminal.el +++ b/lisp/obsolete/terminal.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obsolete/tpu-edt.el b/lisp/obsolete/tpu-edt.el index ee1c2771640..cebb426a2db 100644 --- a/lisp/obsolete/tpu-edt.el +++ b/lisp/obsolete/tpu-edt.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; TPU-edt is based on tpu.el by Jeff Kowalski and Bob Covey. diff --git a/lisp/obsolete/tpu-extras.el b/lisp/obsolete/tpu-extras.el index 06291ce5734..56ccbf09a8a 100644 --- a/lisp/obsolete/tpu-extras.el +++ b/lisp/obsolete/tpu-extras.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obsolete/tpu-mapper.el b/lisp/obsolete/tpu-mapper.el index bb7e28b03c4..c44eba213d1 100644 --- a/lisp/obsolete/tpu-mapper.el +++ b/lisp/obsolete/tpu-mapper.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obsolete/vc-arch.el b/lisp/obsolete/vc-arch.el index 92eaa62be85..d153f9add12 100644 --- a/lisp/obsolete/vc-arch.el +++ b/lisp/obsolete/vc-arch.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obsolete/vip.el b/lisp/obsolete/vip.el index 4d70d6a5dfc..c6a5d236b04 100644 --- a/lisp/obsolete/vip.el +++ b/lisp/obsolete/vip.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obsolete/ws-mode.el b/lisp/obsolete/ws-mode.el index 62cccf725af..c276cfcc4a7 100644 --- a/lisp/obsolete/ws-mode.el +++ b/lisp/obsolete/ws-mode.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obsolete/xesam.el b/lisp/obsolete/xesam.el index c553d0023b5..62844b94cbe 100644 --- a/lisp/obsolete/xesam.el +++ b/lisp/obsolete/xesam.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/obsolete/yow.el b/lisp/obsolete/yow.el index 55f19a80e5f..df8302e19ff 100644 --- a/lisp/obsolete/yow.el +++ b/lisp/obsolete/yow.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/org/ChangeLog.1 b/lisp/org/ChangeLog.1 index 366a3ee9fcd..b7cfd1e4aa1 100644 --- a/lisp/org/ChangeLog.1 +++ b/lisp/org/ChangeLog.1 @@ -5015,10 +5015,10 @@ * ox-latex.el (org-latex-listings): Update docstring. * org-pcomplete.el (pcomplete/org-mode/file-option/options): - Apply changes to export back-end definiton. + Apply changes to export back-end definition. * org.el (org-get-export-keywords): Apply changes to export - back-end definiton. + back-end definition. * ox-html.el (org-html--format-toc-headline): Make use of anonymous back-ends. @@ -11560,7 +11560,7 @@ break after the last footnote definition. This is an an implicit assumption made by the org-lparse.el library. With this change, footnote definitions can reliably be exported with ODT backend. - See http://lists.gnu.org/archive/html/emacs-orgmode/2012-02/msg01013.html. + See https://lists.gnu.org/r/emacs-orgmode/2012-02/msg01013.html. 2012-04-01 Eric Schulte <eric.schulte@gmx.com> @@ -13952,7 +13952,7 @@ * org.el (org-mode): Force left-to-right paragraphs in Org buffers. For a related discussions, see - https://lists.gnu.org/archive/html/emacs-devel/2011-09/msg00349.html. + https://lists.gnu.org/r/emacs-devel/2011-09/msg00349.html. 2012-01-03 Nicolas Goaziou <n.goaziou@gmail.com> @@ -14933,7 +14933,7 @@ * org.el (org-mode): Force left-to-right paragraphs in Org buffers. For a related discussions, see - https://lists.gnu.org/archive/html/emacs-devel/2011-09/msg00349.html. + https://lists.gnu.org/r/emacs-devel/2011-09/msg00349.html. 2011-09-17 Juanma Barranquero <lekktu@gmail.com> @@ -32848,4 +32848,4 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. diff --git a/lisp/org/ob-C.el b/lisp/org/ob-C.el index 77cfd537857..78528a882bc 100644 --- a/lisp/org/ob-C.el +++ b/lisp/org/ob-C.el @@ -1,8 +1,9 @@ -;;; ob-C.el --- org-babel functions for C and similar languages +;;; ob-C.el --- Babel Functions for C and Similar Languages -*- lexical-binding: t; -*- ;; Copyright (C) 2010-2017 Free Software Foundation, Inc. ;; Author: Eric Schulte +;; Thierry Banel ;; Keywords: literate programming, reproducible research ;; Homepage: http://orgmode.org @@ -19,41 +20,74 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: -;; Org-Babel support for evaluating C code. +;; Org-Babel support for evaluating C, C++, D code. ;; ;; very limited implementation: ;; - currently only support :results output ;; - not much in the way of error feedback ;;; Code: -(eval-when-compile - (require 'cl)) -(require 'ob) + (require 'cc-mode) +(require 'ob) -(declare-function org-entry-get "org" - (pom property &optional inherit literal-nil)) +(declare-function org-entry-get "org" (pom property &optional inherit literal-nil)) +(declare-function org-remove-indentation "org" (code &optional n)) +(declare-function org-trim "org" (s &optional keep-lead)) (defvar org-babel-tangle-lang-exts) (add-to-list 'org-babel-tangle-lang-exts '("C++" . "cpp")) +(add-to-list 'org-babel-tangle-lang-exts '("D" . "d")) (defvar org-babel-default-header-args:C '()) -(defvar org-babel-C-compiler "gcc" - "Command used to compile a C source code file into an -executable.") - -(defvar org-babel-C++-compiler "g++" - "Command used to compile a C++ source code file into an -executable.") +(defconst org-babel-header-args:C '((includes . :any) + (defines . :any) + (main . :any) + (flags . :any) + (cmdline . :any) + (libs . :any)) + "C/C++-specific header arguments.") + +(defconst org-babel-header-args:C++ + (append '((namespaces . :any)) + org-babel-header-args:C) + "C++-specific header arguments.") + +(defcustom org-babel-C-compiler "gcc" + "Command used to compile a C source code file into an executable. +May be either a command in the path, like gcc +or an absolute path name, like /usr/local/bin/gcc +parameter may be used, like gcc -v" + :group 'org-babel + :version "24.3" + :type 'string) + +(defcustom org-babel-C++-compiler "g++" + "Command used to compile a C++ source code file into an executable. +May be either a command in the path, like g++ +or an absolute path name, like /usr/local/bin/g++ +parameter may be used, like g++ -v" + :group 'org-babel + :version "24.3" + :type 'string) + +(defcustom org-babel-D-compiler "rdmd" + "Command used to compile and execute a D source code file. +May be either a command in the path, like rdmd +or an absolute path name, like /usr/local/bin/rdmd +parameter may be used, like rdmd --chatty" + :group 'org-babel + :version "24.3" + :type 'string) (defvar org-babel-c-variant nil - "Internal variable used to hold which type of C (e.g. C or C++) + "Internal variable used to hold which type of C (e.g. C or C++ or D) is currently being evaluated.") (defun org-babel-execute:cpp (body params) @@ -61,88 +95,197 @@ is currently being evaluated.") This function calls `org-babel-execute:C++'." (org-babel-execute:C++ body params)) +(defun org-babel-expand-body:cpp (body params) + "Expand a block of C++ code with org-babel according to its +header arguments." + (org-babel-expand-body:C++ body params)) + (defun org-babel-execute:C++ (body params) "Execute a block of C++ code with org-babel. This function is called by `org-babel-execute-src-block'." (let ((org-babel-c-variant 'cpp)) (org-babel-C-execute body params))) (defun org-babel-expand-body:C++ (body params) - "Expand a block of C++ code with org-babel according to it's -header arguments (calls `org-babel-C-expand')." - (let ((org-babel-c-variant 'cpp)) (org-babel-C-expand body params))) + "Expand a block of C++ code with org-babel according to its +header arguments." + (let ((org-babel-c-variant 'cpp)) (org-babel-C-expand-C++ body params))) + +(defun org-babel-execute:D (body params) + "Execute a block of D code with org-babel. +This function is called by `org-babel-execute-src-block'." + (let ((org-babel-c-variant 'd)) (org-babel-C-execute body params))) + +(defun org-babel-expand-body:D (body params) + "Expand a block of D code with org-babel according to its +header arguments." + (let ((org-babel-c-variant 'd)) (org-babel-C-expand-D body params))) (defun org-babel-execute:C (body params) "Execute a block of C code with org-babel. This function is called by `org-babel-execute-src-block'." (let ((org-babel-c-variant 'c)) (org-babel-C-execute body params))) -(defun org-babel-expand-body:c (body params) - "Expand a block of C code with org-babel according to it's -header arguments (calls `org-babel-C-expand')." - (let ((org-babel-c-variant 'c)) (org-babel-C-expand body params))) +(defun org-babel-expand-body:C (body params) + "Expand a block of C code with org-babel according to its +header arguments." + (let ((org-babel-c-variant 'c)) (org-babel-C-expand-C body params))) (defun org-babel-C-execute (body params) "This function should only be called by `org-babel-execute:C' -or `org-babel-execute:C++'." +or `org-babel-execute:C++' or `org-babel-execute:D'." (let* ((tmp-src-file (org-babel-temp-file "C-src-" - (cond - ((equal org-babel-c-variant 'c) ".c") - ((equal org-babel-c-variant 'cpp) ".cpp")))) - (tmp-bin-file (org-babel-temp-file "C-bin-" org-babel-exeext)) - (cmdline (cdr (assoc :cmdline params))) - (flags (cdr (assoc :flags params))) - (full-body (org-babel-C-expand body params)) - (compile - (progn - (with-temp-file tmp-src-file (insert full-body)) - (org-babel-eval - (format "%s -o %s %s %s" - (cond - ((equal org-babel-c-variant 'c) org-babel-C-compiler) - ((equal org-babel-c-variant 'cpp) org-babel-C++-compiler)) - (org-babel-process-file-name tmp-bin-file) - (mapconcat 'identity - (if (listp flags) flags (list flags)) " ") - (org-babel-process-file-name tmp-src-file)) "")))) + (pcase org-babel-c-variant + (`c ".c") (`cpp ".cpp") (`d ".d")))) + (tmp-bin-file ;not used for D + (org-babel-process-file-name + (org-babel-temp-file "C-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)) + " ")) + (full-body + (pcase org-babel-c-variant + (`c (org-babel-C-expand-C body params)) + (`cpp (org-babel-C-expand-C++ body params)) + (`d (org-babel-C-expand-D body params))))) + (with-temp-file tmp-src-file (insert full-body)) + (pcase org-babel-c-variant + ((or `c `cpp) + (org-babel-eval + (format "%s -o %s %s %s %s" + (pcase org-babel-c-variant + (`c org-babel-C-compiler) + (`cpp org-babel-C++-compiler)) + tmp-bin-file + flags + (org-babel-process-file-name tmp-src-file) + libs) + "")) + (`d nil)) ;; no separate compilation for D (let ((results - (org-babel-trim - (org-babel-eval - (concat tmp-bin-file (if cmdline (concat " " cmdline) "")) "")))) - (org-babel-reassemble-table - (org-babel-result-cond (cdr (assoc :result-params params)) - (org-babel-read results) - (let ((tmp-file (org-babel-temp-file "c-"))) - (with-temp-file tmp-file (insert results)) - (org-babel-import-elisp-from-file tmp-file))) - (org-babel-pick-name - (cdr (assoc :colname-names params)) (cdr (assoc :colnames params))) - (org-babel-pick-name - (cdr (assoc :rowname-names params)) (cdr (assoc :rownames params))))) - )) - -(defun org-babel-C-expand (body params) + (org-babel-eval + (pcase org-babel-c-variant + ((or `c `cpp) + (concat tmp-bin-file cmdline)) + (`d + (format "%s %s %s %s" + org-babel-D-compiler + flags + (org-babel-process-file-name tmp-src-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 "c-"))) + (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-C-expand-C++ (body params) "Expand a block of C or C++ code with org-babel according to -it's header arguments." - (let ((vars (mapcar #'cdr (org-babel-get-header params :var))) - (main-p (not (string= (cdr (assoc :main params)) "no"))) - (includes (or (cdr (assoc :includes params)) - (org-babel-read (org-entry-get nil "includes" t)))) - (defines (org-babel-read - (or (cdr (assoc :defines params)) - (org-babel-read (org-entry-get nil "defines" t)))))) +its header arguments." + (org-babel-C-expand-C body params)) + +(defun org-babel-C-expand-C (body params) + "Expand a block of C or C++ code with org-babel according to +its header arguments." + (let ((vars (org-babel--get-vars params)) + (colnames (cdr (assq :colname-names params))) + (main-p (not (string= (cdr (assq :main params)) "no"))) + (includes (org-babel-read + (cdr (assq :includes params)) + nil)) + (defines (org-babel-read + (cdr (assq :defines params)) + nil)) + (namespaces (org-babel-read + (cdr (assq :namespaces params)) + nil))) + (when (stringp includes) + (setq includes (split-string includes))) + (when (stringp namespaces) + (setq namespaces (split-string namespaces))) + (when (stringp defines) + (let ((y nil) + (result (list t))) + (dolist (x (split-string defines)) + (if (null y) + (setq y x) + (nconc result (list (concat y " " x))) + (setq y nil))) + (setq defines (cdr result)))) (mapconcat 'identity (list ;; includes (mapconcat (lambda (inc) (format "#include %s" inc)) - (if (listp includes) includes (list includes)) "\n") + includes "\n") ;; defines (mapconcat (lambda (inc) (format "#define %s" inc)) (if (listp defines) defines (list defines)) "\n") + ;; namespaces + (mapconcat + (lambda (inc) (format "using namespace %s;" inc)) + namespaces + "\n") ;; variables (mapconcat 'org-babel-C-var-to-C vars "\n") + ;; table sizes + (mapconcat 'org-babel-C-table-sizes-to-C vars "\n") + ;; tables headers utility + (when colnames + (org-babel-C-utility-header-to-C)) + ;; tables headers + (mapconcat 'org-babel-C-header-to-C colnames "\n") + ;; body + (if main-p + (org-babel-C-ensure-main-wrap body) + body) "\n") "\n"))) + +(defun org-babel-C-expand-D (body params) + "Expand a block of D code with org-babel according to +its header arguments." + (let ((vars (org-babel--get-vars params)) + (colnames (cdr (assq :colname-names params))) + (main-p (not (string= (cdr (assq :main params)) "no"))) + (imports (or (cdr (assq :imports params)) + (org-babel-read (org-entry-get nil "imports" t))))) + (when (stringp imports) + (setq imports (split-string imports))) + (setq imports (append imports '("std.stdio" "std.conv"))) + (mapconcat 'identity + (list + "module mmm;" + ;; imports + (mapconcat + (lambda (inc) (format "import %s;" inc)) + imports "\n") + ;; variables + (mapconcat 'org-babel-C-var-to-C vars "\n") + ;; table sizes + (mapconcat 'org-babel-C-table-sizes-to-C vars "\n") + ;; tables headers utility + (when colnames + (org-babel-C-utility-header-to-C)) + ;; tables headers + (mapconcat 'org-babel-C-header-to-C colnames "\n") ;; body (if main-p (org-babel-C-ensure-main-wrap body) @@ -154,12 +297,12 @@ it's header arguments." body (format "int main() {\n%s\nreturn 0;\n}\n" body))) -(defun org-babel-prep-session:C (session params) +(defun org-babel-prep-session:C (_session _params) "This function does nothing as C is a compiled language with no support for sessions" (error "C is a compiled language -- no support for sessions")) -(defun org-babel-load-session:C (session body params) +(defun org-babel-load-session:C (_session _body _params) "This function does nothing as C is a compiled language with no support for sessions" (error "C is a compiled language -- no support for sessions")) @@ -177,58 +320,79 @@ support for sessions" "Determine the type of VAL. Return a list (TYPE-NAME FORMAT). TYPE-NAME should be the name of the type. FORMAT can be either a format string or a function which is called with VAL." + (let* ((basetype (org-babel-C-val-to-base-type val)) + (type + (pcase basetype + (`integerp '("int" "%d")) + (`floatp '("double" "%f")) + (`stringp + (list + (if (eq org-babel-c-variant 'd) "string" "const char*") + "\"%s\"")) + (_ (error "unknown type %S" basetype))))) + (cond + ((integerp val) type) ;; an integer declared in the #+begin_src line + ((floatp val) type) ;; a numeric declared in the #+begin_src line + ((and (listp val) (listp (car val))) ;; a table + `(,(car type) + (lambda (val) + (cons + (format "[%d][%d]" (length val) (length (car val))) + (concat + (if (eq org-babel-c-variant 'd) "[\n" "{\n") + (mapconcat + (lambda (v) + (concat + (if (eq org-babel-c-variant 'd) " [" " {") + (mapconcat (lambda (w) (format ,(cadr type) w)) v ",") + (if (eq org-babel-c-variant 'd) "]" "}"))) + val + ",\n") + (if (eq org-babel-c-variant 'd) "\n]" "\n}")))))) + ((or (listp val) (vectorp val)) ;; a list declared in the #+begin_src line + `(,(car type) + (lambda (val) + (cons + (format "[%d]" (length val)) + (concat + (if (eq org-babel-c-variant 'd) "[" "{") + (mapconcat (lambda (v) (format ,(cadr type) v)) val ",") + (if (eq org-babel-c-variant 'd) "]" "}")))))) + (t ;; treat unknown types as string + type)))) + +(defun org-babel-C-val-to-base-type (val) + "Determine the base type of VAL which may be +`integerp' if all base values are integers +`floatp' if all base values are either floating points or integers +`stringp' otherwise." (cond - ((integerp val) '("int" "%d")) - ((floatp val) '("double" "%f")) + ((integerp val) 'integerp) + ((floatp val) 'floatp) ((or (listp val) (vectorp val)) - (lexical-let ((type (org-babel-C-val-to-C-list-type val))) - (list (car type) - (lambda (val) - (cons - (format "[%d]%s" - (length val) - (car (org-babel-C-format-val type (elt val 0)))) - (concat "{ " - (mapconcat (lambda (v) - (cdr (org-babel-C-format-val type v))) - val - ", ") - " }")))))) - (t ;; treat unknown types as string - '("char" (lambda (val) - (let ((s (format "%s" val))) ;; convert to string for unknown types - (cons (format "[%d]" (1+ (length s))) - (concat "\"" s "\"")))))))) - -(defun org-babel-C-val-to-C-list-type (val) - "Determine the C array type of a VAL." - (let (type) - (mapc - #'(lambda (i) - (let* ((tmp-type (org-babel-C-val-to-C-type i)) - (type-name (car type)) - (tmp-type-name (car tmp-type))) - (when (and type (not (string= type-name tmp-type-name))) - (if (and (member type-name '("int" "double" "int32_t")) - (member tmp-type-name '("int" "double" "int32_t"))) - (setq tmp-type '("double" "" "%f")) - (error "Only homogeneous lists are supported by C. You can not mix %s and %s" - type-name - tmp-type-name))) - (setq type tmp-type))) - val) - type)) + (let ((type nil)) + (mapc (lambda (v) + (pcase (org-babel-C-val-to-base-type v) + (`stringp (setq type 'stringp)) + (`floatp + (if (or (not type) (eq type 'integerp)) + (setq type 'floatp))) + (`integerp + (unless type (setq type 'integerp))))) + val) + type)) + (t 'stringp))) (defun org-babel-C-var-to-C (pair) "Convert an elisp val into a string of C code specifying a var of the same value." ;; TODO list support (let ((var (car pair)) - (val (cdr pair))) + (val (cdr pair))) (when (symbolp val) (setq val (symbol-name val)) (when (= (length val) 1) - (setq val (string-to-char val)))) + (setq val (string-to-char val)))) (let* ((type-data (org-babel-C-val-to-C-type val)) (type (car type-data)) (formated (org-babel-C-format-val type-data val)) @@ -240,6 +404,66 @@ of the same value." suffix data)))) +(defun org-babel-C-table-sizes-to-C (pair) + "Create constants of table dimensions, if PAIR is a table." + (when (listp (cdr pair)) + (cond + ((listp (cadr pair)) ;; a table + (concat + (format "const int %s_rows = %d;" (car pair) (length (cdr pair))) + "\n" + (format "const int %s_cols = %d;" (car pair) (length (cadr pair))))) + (t ;; a list declared in the #+begin_src line + (format "const int %s_cols = %d;" (car pair) (length (cdr pair))))))) + +(defun org-babel-C-utility-header-to-C () + "Generate a utility function to convert a column name +into a column number." + (pcase org-babel-c-variant + ((or `c `cpp) + "int get_column_num (int nbcols, const char** header, const char* column) +{ + int c; + for (c=0; c<nbcols; c++) + if (strcmp(header[c],column)==0) + return c; + return -1; +} +") + (`d + "int get_column_num (string[] header, string column) +{ + foreach (c, h; header) + if (h==column) + return to!int(c); + return -1; +} +"))) + +(defun org-babel-C-header-to-C (head) + "Convert an elisp list of header table into a C or D vector +specifying a variable with the name of the table." + (let ((table (car head)) + (headers (cdr head))) + (concat + (format + (pcase org-babel-c-variant + ((or `c `cpp) "const char* %s_header[%d] = {%s};") + (`d "string %s_header[%d] = [%s];")) + table + (length headers) + (mapconcat (lambda (h) (format "%S" h)) headers ",")) + "\n" + (pcase org-babel-c-variant + ((or `c `cpp) + (format + "const char* %s_h (int row, const char* col) { return %s[row][get_column_num(%d,%s_header,col)]; }" + table table (length headers) table)) + (`d + (format + "string %s_h (size_t row, string col) { return %s[row][get_column_num(%s_header,col)]; }" + table table table)))))) + (provide 'ob-C) ;;; ob-C.el ends here diff --git a/lisp/org/ob-J.el b/lisp/org/ob-J.el new file mode 100644 index 00000000000..eaccac81212 --- /dev/null +++ b/lisp/org/ob-J.el @@ -0,0 +1,186 @@ +;;; ob-J.el --- Babel Functions for J -*- lexical-binding: t; -*- + +;; Copyright (C) 2011-2017 Free Software Foundation, Inc. + +;; Author: Oleh Krehel +;; Keywords: literate programming, reproducible research +;; Homepage: http://orgmode.org + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Org-Babel support for evaluating J code. +;; +;; Session interaction depends on `j-console' from package `j-mode' +;; (available in MELPA). + +;;; Code: + +(require 'ob) + +(declare-function org-trim "org" (s &optional keep-lead)) +(declare-function j-console-ensure-session "ext:j-console" ()) + +(defcustom org-babel-J-command "jconsole" + "Command to call J." + :group 'org-babel + :version "26.1" + :package-version '(Org . "9.0") + :type 'string) + +(defun org-babel-expand-body:J (body _params &optional _processed-params) + "Expand BODY according to PARAMS, return the expanded body. +PROCESSED-PARAMS isn't used yet." + (org-babel-J-interleave-echos-except-functions body)) + +(defun org-babel-J-interleave-echos (body) + "Interleave echo',' between each source line of BODY." + (mapconcat #'identity (split-string body "\n") "\necho','\n")) + +(defun org-babel-J-interleave-echos-except-functions (body) + "Interleave echo',' between source lines of BODY that aren't functions." + (if (obj-string-match-m "\\(?:^\\|\n\\)[^\n]*\\(?:0\\|1\\|2\\|3\\|4\\|dyad\\) : 0\n.*\n)\\(?:\n\\|$\\)" body) + (let ((s1 (substring body 0 (match-beginning 0))) + (s2 (match-string 0 body)) + (s3 (substring body (match-end 0)))) + (concat + (if (string= s1 "") + "" + (concat (org-babel-J-interleave-echos s1) + "\necho','\n")) + s2 + "\necho','\n" + (org-babel-J-interleave-echos-except-functions s3))) + (org-babel-J-interleave-echos body))) + +(defalias 'org-babel-execute:j 'org-babel-execute:J) + +(defun org-babel-execute:J (body params) + "Execute a block of J code BODY. +PARAMS are given by org-babel. +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))) + (full-body (org-babel-expand-body:J + body params processed-params)) + (tmp-script-file (org-babel-temp-file "J-src"))) + (org-babel-j-initiate-session sessionp) + (org-babel-J-strip-whitespace + (if (string= sessionp "none") + (progn + (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))))) + +(defun org-babel-J-eval-string (str) + "Sends STR to the `j-console-cmd' session and executes it." + (let ((session (j-console-ensure-session))) + (with-current-buffer (process-buffer session) + (goto-char (point-max)) + (insert (format "\n%s\n" str)) + (let ((beg (point))) + (comint-send-input) + (sit-for .1) + (buffer-substring-no-properties + beg (point-max)))))) + +(defun org-babel-J-strip-whitespace (str) + "Remove whitespace from jconsole output STR." + (mapconcat + #'identity + (delete "" (mapcar + #'org-babel-J-print-block + (split-string str "^ *,\n" t))) + "\n\n")) + +(defun obj-get-string-alignment (str) + "Return a number to describe STR alignment. +STR represents a table. +Positive/negative/zero result means right/left/undetermined. +Don't trust first line." + (let* ((str (org-trim str)) + (lines (split-string str "\n" t)) + n1 n2) + (cond ((<= (length lines) 1) + 0) + ((= (length lines) 2) + ;; numbers are right-aligned + (if (and + (numberp (read (car lines))) + (numberp (read (cadr lines))) + (setq n1 (obj-match-second-space-right (nth 0 lines))) + (setq n2 (obj-match-second-space-right (nth 1 lines)))) + n2 + 0)) + ((not (obj-match-second-space-left (nth 0 lines))) + 0) + ((and + (setq n1 (obj-match-second-space-left (nth 1 lines))) + (setq n2 (obj-match-second-space-left (nth 2 lines))) + (= n1 n2)) + n1) + ((and + (setq n1 (obj-match-second-space-right (nth 1 lines))) + (setq n2 (obj-match-second-space-right (nth 2 lines))) + (= n1 n2)) + (- n1)) + (t 0)))) + +(defun org-babel-J-print-block (x) + "Prettify jconsole output X." + (let* ((x (org-trim x)) + (a (obj-get-string-alignment x)) + (lines (split-string x "\n" t)) + b) + (cond ((< a 0) + (setq b (obj-match-second-space-right (nth 0 lines))) + (concat (make-string (+ a b) ? ) x)) + ((> a 0) + (setq b (obj-match-second-space-left (nth 0 lines))) + (concat (make-string (- a b) ? ) x)) + (t x)))) + +(defun obj-match-second-space-left (s) + "Return position of leftmost space in second space block of S or nil." + (and (string-match "^ *[^ ]+\\( \\)" s) + (match-beginning 1))) + +(defun obj-match-second-space-right (s) + "Return position of rightmost space in second space block of S or nil." + (and (string-match "^ *[^ ]+ *\\( \\)[^ ]" s) + (match-beginning 1))) + +(defun obj-string-match-m (regexp string &optional start) + "Call (string-match REGEXP STRING START). +REGEXP is modified so that .* matches newlines as well." + (string-match + (replace-regexp-in-string "\\.\\*" "[\0-\377[:nonascii:]]*" regexp) + string + start)) + +(defun org-babel-j-initiate-session (&optional session) + "Initiate a J session. +SESSION is a parameter given by org-babel." + (unless (string= session "none") + (require 'j-console) + (j-console-ensure-session))) + +(provide 'ob-J) + +;;; ob-J.el ends here diff --git a/lisp/org/ob-R.el b/lisp/org/ob-R.el index 51d342702ce..6781fb30a3b 100644 --- a/lisp/org/ob-R.el +++ b/lisp/org/ob-R.el @@ -1,4 +1,4 @@ -;;; ob-R.el --- org-babel functions for R code evaluation +;;; ob-R.el --- Babel Functions for R -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. @@ -20,23 +20,24 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; Org-Babel support for evaluating R code ;;; Code: + +(require 'cl-lib) (require 'ob) -(eval-when-compile (require 'cl)) (declare-function orgtbl-to-tsv "org-table" (table params)) (declare-function R "ext:essd-r" (&optional start-args)) (declare-function inferior-ess-send-input "ext:ess-inf" ()) (declare-function ess-make-buffer-current "ext:ess-inf" ()) (declare-function ess-eval-buffer "ext:ess-inf" (vis)) -(declare-function org-number-sequence "org-compat" (from &optional to inc)) -(declare-function org-remove-if-not "org" (predicate seq)) +(declare-function ess-wait-for-process "ext:ess-inf" + (&optional proc sec-prompt wait force-redisplay)) (defconst org-babel-header-args:R '((width . :any) @@ -60,12 +61,25 @@ (useDingbats . :any) (horizontal . :any) (results . ((file list vector table scalar verbatim) - (raw org html latex code pp wrap) - (replace silent append prepend) + (raw html latex org code pp drawer) + (replace silent none append prepend) (output value graphics)))) "R-specific header arguments.") +(defconst ob-R-safe-header-args + (append org-babel-safe-header-args + '(:width :height :bg :units :pointsize :antialias :quality + :compression :res :type :family :title :fonts + :version :paper :encoding :pagecentre :colormodel + :useDingbats :horizontal)) + "Header args which are safe for R babel blocks. + +See `org-babel-safe-header-args' for documentation of the format of +this variable.") + (defvar org-babel-default-header-args:R '()) +(put 'org-babel-default-header-args:R 'safe-local-variable + (org-babel-header-args-safe-fn ob-R-safe-header-args)) (defcustom org-babel-R-command "R --slave --no-save" "Name of command to use for executing R code." @@ -73,56 +87,103 @@ :version "24.1" :type 'string) -(defvar ess-local-process-name) ; dynamically scoped +(defvar ess-current-process-name) ; dynamically scoped +(defvar ess-local-process-name) ; dynamically scoped (defun org-babel-edit-prep:R (info) - (let ((session (cdr (assoc :session (nth 2 info))))) - (when (and session (string-match "^\\*\\(.+?\\)\\*$" session)) - (save-match-data (org-babel-R-initiate-session session nil))))) - -(defun org-babel-expand-body:R (body params &optional graphics-file) + (let ((session (cdr (assq :session (nth 2 info))))) + (when (and session + (string-prefix-p "*" session) + (string-suffix-p "*" session)) + (org-babel-R-initiate-session session nil)))) + +;; The usage of utils::read.table() ensures that the command +;; read.table() can be found even in circumstances when the utils +;; package is not in the search path from R. +(defconst ob-R-transfer-variable-table-with-header + "%s <- local({ + con <- textConnection( + %S + ) + res <- utils::read.table( + con, + header = %s, + row.names = %s, + sep = \"\\t\", + as.is = TRUE + ) + close(con) + res + })" + "R code used to transfer a table defined as a variable from org to R. + +This function is used when the table contains a header.") + +(defconst ob-R-transfer-variable-table-without-header + "%s <- local({ + con <- textConnection( + %S + ) + res <- utils::read.table( + con, + header = %s, + row.names = %s, + sep = \"\\t\", + as.is = TRUE, + fill = TRUE, + col.names = paste(\"V\", seq_len(%d), sep =\"\") + ) + close(con) + res + })" + "R code used to transfer a table defined as a variable from org to R. + +This function is used when the table does not contain a header.") + +(defun org-babel-expand-body:R (body params &optional _graphics-file) "Expand BODY according to PARAMS, return the expanded body." - (let ((graphics-file - (or graphics-file (org-babel-R-graphical-output-file params)))) - (mapconcat - #'identity - (let ((inside - (append - (when (cdr (assoc :prologue params)) - (list (cdr (assoc :prologue params)))) - (org-babel-variable-assignments:R params) - (list body) - (when (cdr (assoc :epilogue params)) - (list (cdr (assoc :epilogue params))))))) - (if graphics-file - (append - (list (org-babel-R-construct-graphics-device-call - graphics-file params)) - inside - (list "dev.off()")) - inside)) - "\n"))) + (mapconcat 'identity + (append + (when (cdr (assq :prologue params)) + (list (cdr (assq :prologue params)))) + (org-babel-variable-assignments:R params) + (list body) + (when (cdr (assq :epilogue params)) + (list (cdr (assq :epilogue params))))) + "\n")) (defun org-babel-execute:R (body params) "Execute a block of R code. This function is called by `org-babel-execute-src-block'." (save-excursion - (let* ((result-params (cdr (assoc :result-params params))) - (result-type (cdr (assoc :result-type params))) + (let* ((result-params (cdr (assq :result-params params))) + (result-type (cdr (assq :result-type params))) (session (org-babel-R-initiate-session - (cdr (assoc :session params)) params)) - (colnames-p (cdr (assoc :colnames params))) - (rownames-p (cdr (assoc :rownames params))) - (graphics-file (org-babel-R-graphical-output-file params)) - (full-body (org-babel-expand-body:R body params graphics-file)) + (cdr (assq :session params)) params)) + (graphics-file (and (member "graphics" (assq :result-params params)) + (org-babel-graphical-output-file params))) + (colnames-p (unless graphics-file (cdr (assq :colnames params)))) + (rownames-p (unless graphics-file (cdr (assq :rownames params)))) + (full-body + (let ((inside + (list (org-babel-expand-body:R body params graphics-file)))) + (mapconcat 'identity + (if graphics-file + (append + (list (org-babel-R-construct-graphics-device-call + graphics-file params)) + inside + (list "},error=function(e){plot(x=-1:1, y=-1:1, type='n', xlab='', ylab='', axes=FALSE); text(x=0, y=0, labels=e$message, col='red'); paste('ERROR', e$message, sep=' : ')}); dev.off()")) + inside) + "\n"))) (result (org-babel-R-evaluate session full-body result-type result-params (or (equal "yes" colnames-p) (org-babel-pick-name - (cdr (assoc :colname-names params)) colnames-p)) + (cdr (assq :colname-names params)) colnames-p)) (or (equal "yes" rownames-p) (org-babel-pick-name - (cdr (assoc :rowname-names params)) rownames-p))))) + (cdr (assq :rowname-names params)) rownames-p))))) (if graphics-file nil result)))) (defun org-babel-prep-session:R (session params) @@ -148,21 +209,21 @@ This function is called by `org-babel-execute-src-block'." (defun org-babel-variable-assignments:R (params) "Return list of R statements assigning the block's variables." - (let ((vars (mapcar #'cdr (org-babel-get-header params :var)))) + (let ((vars (org-babel--get-vars params))) (mapcar (lambda (pair) (org-babel-R-assign-elisp (car pair) (cdr pair) - (equal "yes" (cdr (assoc :colnames params))) - (equal "yes" (cdr (assoc :rownames params))))) + (equal "yes" (cdr (assq :colnames params))) + (equal "yes" (cdr (assq :rownames params))))) (mapcar (lambda (i) (cons (car (nth i vars)) (org-babel-reassemble-table (cdr (nth i vars)) - (cdr (nth i (cdr (assoc :colname-names params)))) - (cdr (nth i (cdr (assoc :rowname-names params))))))) - (org-number-sequence 0 (1- (length vars))))))) + (cdr (nth i (cdr (assq :colname-names params)))) + (cdr (nth i (cdr (assq :rowname-names params))))))) + (number-sequence 0 (1- (length vars))))))) (defun org-babel-R-quote-tsv-field (s) "Quote field S for export to R." @@ -173,35 +234,25 @@ This function is called by `org-babel-execute-src-block'." (defun org-babel-R-assign-elisp (name value colnames-p rownames-p) "Construct R code assigning the elisp VALUE to a variable named NAME." (if (listp value) - (let* ((lengths (mapcar 'length (org-remove-if-not 'sequencep value))) + (let* ((lengths (mapcar 'length (cl-remove-if-not 'sequencep value))) (max (if lengths (apply 'max lengths) 0)) - (min (if lengths (apply 'min lengths) 0)) - (transition-file (org-babel-temp-file "R-import-"))) + (min (if lengths (apply 'min lengths) 0))) ;; Ensure VALUE has an orgtbl structure (depth of at least 2). (unless (listp (car value)) (setq value (list value))) - (with-temp-file transition-file - (insert - (orgtbl-to-tsv value '(:fmt org-babel-R-quote-tsv-field)) - "\n")) - (let ((file (org-babel-process-file-name transition-file 'noquote)) + (let ((file (orgtbl-to-tsv value '(:fmt org-babel-R-quote-tsv-field))) (header (if (or (eq (nth 1 value) 'hline) colnames-p) "TRUE" "FALSE")) (row-names (if rownames-p "1" "NULL"))) (if (= max min) - (format "%s <- read.table(\"%s\", - header=%s, - row.names=%s, - sep=\"\\t\", - as.is=TRUE)" name file header row-names) - (format "%s <- read.table(\"%s\", - header=%s, - row.names=%s, - sep=\"\\t\", - as.is=TRUE, - fill=TRUE, - col.names = paste(\"V\", seq_len(%d), sep =\"\"))" + (format ob-R-transfer-variable-table-with-header + name file header row-names) + (format ob-R-transfer-variable-table-without-header name file header row-names max)))) - (format "%s <- %s" name (org-babel-R-quote-tsv-field value)))) + (cond ((integerp value) (format "%s <- %s" name (concat (number-to-string value) "L"))) + ((floatp value) (format "%s <- %s" name value)) + ((stringp value) (format "%s <- %S" name (org-no-properties value))) + (t (format "%s <- %S" name (prin1-to-string value)))))) + (defvar ess-ask-for-ess-directory) ; dynamically scoped (defun org-babel-R-initiate-session (session params) @@ -209,8 +260,9 @@ This function is called by `org-babel-execute-src-block'." (unless (string= session "none") (let ((session (or session "*R*")) (ess-ask-for-ess-directory - (and (and (boundp 'ess-ask-for-ess-directory) ess-ask-for-ess-directory) - (not (cdr (assoc :dir params)))))) + (and (boundp 'ess-ask-for-ess-directory) + ess-ask-for-ess-directory + (not (cdr (assq :dir params)))))) (if (org-babel-comint-buffer-livep session) session (save-window-excursion @@ -218,6 +270,10 @@ This function is called by `org-babel-execute-src-block'." ;; Session buffer exists, but with dead process (set-buffer session)) (require 'ess) (R) + (let ((R-proc (get-process (or ess-local-process-name + ess-current-process-name)))) + (while (process-get R-proc 'callbacks) + (ess-wait-for-process R-proc))) (rename-buffer (if (bufferp session) (buffer-name session) @@ -234,11 +290,6 @@ current code buffer." (process-name (get-buffer-process session))) (ess-make-buffer-current)) -(defun org-babel-R-graphical-output-file (params) - "Name of file to which R should send graphical output." - (and (member "graphics" (cdr (assq :result-params params))) - (cdr (assq :file params)))) - (defvar org-babel-R-graphics-devices '((:bmp "bmp" "filename") (:jpg "jpeg" "filename") @@ -265,8 +316,7 @@ Each member of this list is a list with three members: :type :family :title :fonts :version :paper :encoding :pagecentre :colormodel :useDingbats :horizontal)) - (device (and (string-match ".+\\.\\([^.]+\\)" out-file) - (match-string 1 out-file))) + (device (file-name-extension out-file)) (device-info (or (assq (intern (concat ":" device)) org-babel-R-graphics-devices) (assq :png org-babel-R-graphics-devices))) @@ -280,14 +330,43 @@ Each member of this list is a list with three members: (substring (symbol-name (car pair)) 1) (cdr pair)) "")) params "")) - (format "%s(%s=\"%s\"%s%s%s)" + (format "%s(%s=\"%s\"%s%s%s); tryCatch({" device filearg out-file args (if extra-args "," "") (or extra-args "")))) -(defvar org-babel-R-eoe-indicator "'org_babel_R_eoe'") -(defvar org-babel-R-eoe-output "[1] \"org_babel_R_eoe\"") - -(defvar org-babel-R-write-object-command "{function(object,transfer.file){object;invisible(if(inherits(try({tfile<-tempfile();write.table(object,file=tfile,sep=\"\\t\",na=\"nil\",row.names=%s,col.names=%s,quote=FALSE);file.rename(tfile,transfer.file)},silent=TRUE),\"try-error\")){if(!file.exists(transfer.file))file.create(transfer.file)})}}(object=%s,transfer.file=\"%s\")") +(defconst org-babel-R-eoe-indicator "'org_babel_R_eoe'") +(defconst org-babel-R-eoe-output "[1] \"org_babel_R_eoe\"") + +(defconst org-babel-R-write-object-command "{ + function(object,transfer.file) { + object + invisible( + if ( + inherits( + try( + { + tfile<-tempfile() + write.table(object, file=tfile, sep=\"\\t\", + na=\"nil\",row.names=%s,col.names=%s, + quote=FALSE) + file.rename(tfile,transfer.file) + }, + silent=TRUE), + \"try-error\")) + { + if(!file.exists(transfer.file)) + file.create(transfer.file) + } + ) + } +}(object=%s,transfer.file=\"%s\")" + "A template for an R command to evaluate a block of code and write the result to a file. + +Has four %s escapes to be filled in: +1. Row names, \"TRUE\" or \"FALSE\" +2. Column names, \"TRUE\" or \"FALSE\" +3. The code to be run (must be an expression, not a statement) +4. The name of the file to write to") (defun org-babel-R-evaluate (session body result-type result-params column-names-p row-names-p) @@ -299,12 +378,12 @@ Each member of this list is a list with three members: body result-type result-params column-names-p row-names-p))) (defun org-babel-R-evaluate-external-process - (body result-type result-params column-names-p row-names-p) + (body result-type result-params column-names-p row-names-p) "Evaluate BODY in external R process. -If RESULT-TYPE equals 'output then return standard output as a -string. If RESULT-TYPE equals 'value then return the value of the +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." - (case result-type + (cl-case result-type (value (let ((tmp-file (org-babel-temp-file "R-"))) (org-babel-eval org-babel-R-command @@ -319,7 +398,7 @@ last statement in BODY, as elisp." (org-babel-result-cond result-params (with-temp-buffer (insert-file-contents tmp-file) - (buffer-string)) + (org-babel-chomp (buffer-string) "\n")) (org-babel-import-elisp-from-file tmp-file '(16))) column-names-p))) (output (org-babel-eval org-babel-R-command body)))) @@ -327,12 +406,12 @@ last statement in BODY, as elisp." (defvar ess-eval-visibly-p) (defun org-babel-R-evaluate-session - (session body result-type result-params column-names-p row-names-p) + (session body result-type result-params column-names-p row-names-p) "Evaluate BODY 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 +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." - (case result-type + (cl-case result-type (value (with-temp-buffer (insert (org-babel-chomp body)) @@ -353,12 +432,12 @@ last statement in BODY, as elisp." (org-babel-result-cond result-params (with-temp-buffer (insert-file-contents tmp-file) - (buffer-string)) + (org-babel-chomp (buffer-string) "\n")) (org-babel-import-elisp-from-file tmp-file '(16))) column-names-p))) (output (mapconcat - #'org-babel-chomp + 'org-babel-chomp (butlast (delq nil (mapcar @@ -366,11 +445,12 @@ last statement in BODY, as elisp." (mapcar (lambda (line) ;; cleanup extra prompts left in output (if (string-match - "^\\([ ]*[>+\\.][ ]?\\)+\\([[0-9]+\\|[ ]\\)" line) + "^\\([>+.]\\([ ][>.+]\\)*[ ]\\)" + (car (split-string line "\n"))) (substring line (match-end 1)) line)) (org-babel-comint-with-output (session org-babel-R-eoe-output) - (insert (mapconcat #'org-babel-chomp + (insert (mapconcat 'org-babel-chomp (list body org-babel-R-eoe-indicator) "\n")) (inferior-ess-send-input)))))) "\n")))) diff --git a/lisp/org/ob-abc.el b/lisp/org/ob-abc.el new file mode 100644 index 00000000000..693c5d8f60f --- /dev/null +++ b/lisp/org/ob-abc.el @@ -0,0 +1,90 @@ +;;; ob-abc.el --- Org Babel Functions for ABC -*- lexical-binding: t; -*- + +;; Copyright (C) 2013-2017 Free Software Foundation, Inc. + +;; Author: William Waites +;; Keywords: literate programming, music +;; Homepage: http://www.tardis.ed.ac.uk/wwaites +;; Version: 0.01 + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;;; This file adds support to Org Babel for music in ABC notation. +;;; It requires that the abcm2ps program is installed. +;;; See http://moinejf.free.fr/ + +(require 'ob) + +;; optionally define a file extension for this language +(add-to-list 'org-babel-tangle-lang-exts '("abc" . "abc")) + +;; optionally declare default header arguments for this language +(defvar org-babel-default-header-args:abc + '((:results . "file") (:exports . "results")) + "Default arguments to use when evaluating an ABC source block.") + +(defun org-babel-expand-body:abc (body params) + "Expand BODY according to PARAMS, return the expanded body." + (let ((vars (org-babel--get-vars params))) + (mapc + (lambda (pair) + (let ((name (symbol-name (car pair))) + (value (cdr pair))) + (setq body + (replace-regexp-in-string + (concat "\$" (regexp-quote name)) + (if (stringp value) value (format "%S" value)) + body)))) + vars) + body)) + +(defun org-babel-execute:abc (body params) + "Execute a block of ABC code with org-babel. This function is + called by `org-babel-execute-src-block'" + (message "executing Abc source code block") + (let* ((cmdline (cdr (assq :cmdline params))) + (out-file (let ((file (cdr (assq :file params)))) + (if file (replace-regexp-in-string "\.pdf$" ".ps" file) + (error "abc code block requires :file header argument")))) + (in-file (org-babel-temp-file "abc-")) + (render (concat "abcm2ps" " " cmdline + " -O " (org-babel-process-file-name out-file) + " " (org-babel-process-file-name in-file)))) + (with-temp-file in-file (insert (org-babel-expand-body:abc body params))) + (org-babel-eval render "") + ;;; handle where abcm2ps changes the file name (to support multiple files + (when (or (string= (file-name-extension out-file) "eps") + (string= (file-name-extension out-file) "svg")) + (rename-file (concat + (file-name-sans-extension out-file) "001." + (file-name-extension out-file)) + out-file t)) + ;;; if we were asked for a pdf... + (when (string= (file-name-extension (cdr (assq :file params))) "pdf") + (org-babel-eval (concat "ps2pdf" " " out-file " " (cdr (assq :file params))) "")) + ;;; indicate that the file has been written + nil)) + +;; This function should be used to assign any variables in params in +;; the context of the session environment. +(defun org-babel-prep-session:abc (_session _params) + "Return an error because abc does not support sessions." + (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 e3b73c19ac9..819273aecef 100644 --- a/lisp/org/ob-asymptote.el +++ b/lisp/org/ob-asymptote.el @@ -1,4 +1,4 @@ -;;; ob-asymptote.el --- org-babel functions for asymptote evaluation +;;; ob-asymptote.el --- Babel Functions for Asymptote -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -43,11 +43,6 @@ ;;; Code: (require 'ob) -(eval-when-compile (require 'cl)) - -(declare-function orgtbl-to-generic "org-table" - (table params &optional backend)) -(declare-function org-combine-plists "org" (&rest plists)) (defvar org-babel-tangle-lang-exts) (add-to-list 'org-babel-tangle-lang-exts '("asymptote" . "asy")) @@ -59,13 +54,10 @@ (defun org-babel-execute:asymptote (body params) "Execute a block of Asymptote code. This function is called by `org-babel-execute-src-block'." - (let* ((result-params (split-string (or (cdr (assoc :results params)) ""))) - (out-file (cdr (assoc :file params))) - (format (or (and out-file - (string-match ".+\\.\\(.+\\)" out-file) - (match-string 1 out-file)) + (let* ((out-file (cdr (assq :file params))) + (format (or (file-name-extension out-file) "pdf")) - (cmdline (cdr (assoc :cmdline params))) + (cmdline (cdr (assq :cmdline params))) (in-file (org-babel-temp-file "asymptote-")) (cmd (concat "asy " @@ -83,7 +75,7 @@ This function is called by `org-babel-execute-src-block'." (message cmd) (shell-command cmd) nil)) ;; signal that output has already been written to file -(defun org-babel-prep-session:asymptote (session params) +(defun org-babel-prep-session:asymptote (_session _params) "Return an error if the :session header argument is set. Asymptote does not support sessions" (error "Asymptote does not support sessions")) @@ -91,7 +83,7 @@ Asymptote does not support sessions" (defun org-babel-variable-assignments:asymptote (params) "Return list of asymptote statements assigning the block's variables." (mapcar #'org-babel-asymptote-var-to-asymptote - (mapcar #'cdr (org-babel-get-header params :var)))) + (org-babel--get-vars params))) (defun org-babel-asymptote-var-to-asymptote (pair) "Convert an elisp value into an Asymptote variable. @@ -128,21 +120,17 @@ a variable of the same value." DATA is a list. Return type as a symbol. -The type is `string' if any element in DATA is -a string. Otherwise, it is either `real', if some elements are -floats, or `int'." - (let* ((type 'int) - find-type ; for byte-compiler - (find-type - (function - (lambda (row) - (catch 'exit - (mapc (lambda (el) - (cond ((listp el) (funcall find-type el)) - ((stringp el) (throw 'exit (setq type 'string))) - ((floatp el) (setq type 'real)))) - row)))))) - (funcall find-type data) type)) +The type is `string' if any element in DATA is a string. +Otherwise, it is either `real', if some elements are floats, or +`int'." + (letrec ((type 'int) + (find-type + (lambda (row) + (dolist (e row type) + (cond ((listp e) (setq type (funcall find-type e))) + ((stringp e) (throw 'exit 'string)) + ((floatp e) (setq type 'real))))))) + (catch 'exit (funcall find-type data)) type)) (provide 'ob-asymptote) diff --git a/lisp/org/ob-awk.el b/lisp/org/ob-awk.el index c2ac5cac3bf..e2eec9bf7f0 100644 --- a/lisp/org/ob-awk.el +++ b/lisp/org/ob-awk.el @@ -1,4 +1,4 @@ -;;; ob-awk.el --- org-babel functions for awk evaluation +;;; ob-awk.el --- Babel Functions for Awk -*- lexical-binding: t; -*- ;; Copyright (C) 2011-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -27,17 +27,15 @@ ;; ;; - :in-file takes a path to a file of data to be processed by awk ;; -;; - :stdin takes an Org-mode data or code block reference, the value -;; of which will be passed to the awk process through STDIN +;; - :stdin takes an Org data or code block reference, the value of +;; which will be passed to the awk process through STDIN ;;; Code: (require 'ob) (require 'org-compat) -(eval-when-compile (require 'cl)) (declare-function org-babel-ref-resolve "ob-ref" (ref)) -(declare-function orgtbl-to-generic "org-table" - (table params &optional backend)) +(declare-function orgtbl-to-generic "org-table" (table params)) (defvar org-babel-tangle-lang-exts) (add-to-list 'org-babel-tangle-lang-exts '("awk" . "awk")) @@ -45,34 +43,38 @@ (defvar org-babel-awk-command "awk" "Name of the awk executable command.") -(defun org-babel-expand-body:awk (body params) +(defun org-babel-expand-body:awk (body _params) "Expand BODY according to PARAMS, return the expanded body." - (dolist (pair (mapcar #'cdr (org-babel-get-header params :var))) - (setf body (replace-regexp-in-string - (regexp-quote (format "$%s" (car pair))) (cdr pair) body))) body) (defun org-babel-execute:awk (body params) "Execute a block of Awk code with org-babel. This function is called by `org-babel-execute-src-block'" (message "executing Awk source code block") - (let* ((result-params (cdr (assoc :result-params params))) - (cmd-line (cdr (assoc :cmd-line params))) - (in-file (cdr (assoc :in-file params))) + (let* ((result-params (cdr (assq :result-params params))) + (cmd-line (cdr (assq :cmd-line params))) + (in-file (cdr (assq :in-file params))) (full-body (org-babel-expand-body:awk body params)) (code-file (let ((file (org-babel-temp-file "awk-"))) (with-temp-file file (insert full-body)) file)) - (stdin (let ((stdin (cdr (assoc :stdin params)))) + (stdin (let ((stdin (cdr (assq :stdin params)))) (when stdin (let ((tmp (org-babel-temp-file "awk-stdin-")) (res (org-babel-ref-resolve stdin))) (with-temp-file tmp (insert (org-babel-awk-var-to-awk res))) tmp)))) - (cmd (mapconcat #'identity (remove nil (list org-babel-awk-command - "-f" code-file - cmd-line - in-file)) + (cmd (mapconcat #'identity + (append + (list org-babel-awk-command + "-f" code-file cmd-line) + (mapcar (lambda (pair) + (format "-v %s='%s'" + (car pair) + (org-babel-awk-var-to-awk + (cdr pair)))) + (org-babel--get-vars params)) + (list in-file)) " "))) (org-babel-reassemble-table (let ((results @@ -88,9 +90,9 @@ called by `org-babel-execute-src-block'" (with-temp-file tmp (insert results)) (org-babel-import-elisp-from-file tmp))))) (org-babel-pick-name - (cdr (assoc :colname-names params)) (cdr (assoc :colnames params))) + (cdr (assq :colname-names params)) (cdr (assq :colnames params))) (org-babel-pick-name - (cdr (assoc :rowname-names params)) (cdr (assoc :rownames params)))))) + (cdr (assq :rowname-names params)) (cdr (assq :rownames params)))))) (defun org-babel-awk-var-to-awk (var &optional sep) "Return a printed value of VAR suitable for parsing with awk." @@ -102,11 +104,6 @@ called by `org-babel-execute-src-block'" (mapconcat echo-var var "\n")) (t (funcall echo-var var))))) -(defun org-babel-awk-table-or-string (results) - "If the results look like a table, then convert them into an -Emacs-lisp table, otherwise return the results as a string." - (org-babel-script-escape results)) - (provide 'ob-awk) diff --git a/lisp/org/ob-calc.el b/lisp/org/ob-calc.el index 6298bba522a..76d36cf7801 100644 --- a/lisp/org/ob-calc.el +++ b/lisp/org/ob-calc.el @@ -1,4 +1,4 @@ -;;; ob-calc.el --- org-babel functions for calc code evaluation +;;; ob-calc.el --- Babel Functions for Calc -*- lexical-binding: t; -*- ;; Copyright (C) 2010-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -28,18 +28,18 @@ ;;; Code: (require 'ob) (require 'calc) -(unless (featurep 'xemacs) - (require 'calc-trail) - (require 'calc-store)) +(require 'calc-trail) +(require 'calc-store) (declare-function calc-store-into "calc-store" (&optional var)) (declare-function calc-recall "calc-store" (&optional var)) (declare-function math-evaluate-expr "calc-ext" (x)) +(declare-function org-trim "org" (s &optional keep-lead)) (defvar org-babel-default-header-args:calc nil "Default arguments for evaluating an calc source block.") -(defun org-babel-expand-body:calc (body params) +(defun org-babel-expand-body:calc (body _params) "Expand BODY according to PARAMS, return the expanded body." body) (defvar org--var-syms) ; Dynamically scoped from org-babel-execute:calc @@ -48,7 +48,7 @@ "Execute a block of calc code with Babel." (unless (get-buffer "*Calculator*") (save-window-excursion (calc) (calc-quit))) - (let* ((vars (mapcar #'cdr (org-babel-get-header params :var))) + (let* ((vars (org-babel--get-vars params)) (org--var-syms (mapcar #'car vars)) (var-names (mapcar #'symbol-name org--var-syms))) (mapc @@ -85,15 +85,17 @@ ;; parse line into calc objects (car (math-read-exprs line))))))))) )))))) - (mapcar #'org-babel-trim + (mapcar #'org-trim (split-string (org-babel-expand-body:calc body params) "[\n\r]")))) (save-excursion (with-current-buffer (get-buffer "*Calculator*") - (calc-eval (calc-top 1))))) + (prog1 + (calc-eval (calc-top 1)) + (calc-pop 1))))) (defun org-babel-calc-maybe-resolve-var (el) (if (consp el) - (if (and (equal 'var (car el)) (member (cadr el) org--var-syms)) + (if (and (eq 'var (car el)) (member (cadr el) org--var-syms)) (progn (calc-recall (cadr el)) (prog1 (calc-top 1) diff --git a/lisp/org/ob-clojure.el b/lisp/org/ob-clojure.el index b9af45adfeb..b49bfe58898 100644 --- a/lisp/org/ob-clojure.el +++ b/lisp/org/ob-clojure.el @@ -1,9 +1,9 @@ -;;; ob-clojure.el --- org-babel functions for clojure evaluation +;;; ob-clojure.el --- Babel Functions for Clojure -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. -;; Author: Joel Boehland -;; Eric Schulte +;; Author: Joel Boehland, Eric Schulte, Oleh Krehel, Frederick Giasson +;; ;; Keywords: literate programming, reproducible research ;; Homepage: http://orgmode.org @@ -20,76 +20,179 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: -;; Support for evaluating clojure code, relies on slime for all eval. +;; Support for evaluating clojure code -;;; Requirements: +;; Requirements: ;; - clojure (at least 1.2.0) ;; - clojure-mode -;; - slime +;; - either cider or SLIME -;; By far, the best way to install these components is by following +;; For Cider, 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) +(declare-function cider-current-connection "ext:cider-client" (&optional type)) +(declare-function cider-current-ns "ext:cider-client" ()) +(declare-function nrepl--merge "ext:nrepl-client" (dict1 dict2)) +(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 session ns line column additional-params)) +(declare-function nrepl-sync-request:eval "ext:nrepl-client" + (input connection session &optional ns)) +(declare-function org-trim "org" (s &optional keep-lead)) (declare-function slime-eval "ext:slime" (sexp &optional package)) +(defvar nrepl-sync-request-timeout) + (defvar org-babel-tangle-lang-exts) (add-to-list 'org-babel-tangle-lang-exts '("clojure" . "clj")) (defvar org-babel-default-header-args:clojure '()) (defvar org-babel-header-args:clojure '((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)) + "Backend used to evaluate Clojure code blocks." + :group 'org-babel + :type '(choice + (const :tag "cider" cider) + (const :tag "SLIME" slime))) + (defun org-babel-expand-body:clojure (body params) "Expand BODY according to PARAMS, return the expanded body." - (let* ((vars (mapcar #'cdr (org-babel-get-header params :var))) - (result-params (cdr (assoc :result-params params))) + (let* ((vars (org-babel--get-vars params)) + (result-params (cdr (assq :result-params params))) (print-level nil) (print-length nil) - (body (org-babel-trim - (if (> (length vars) 0) - (concat "(let [" - (mapconcat - (lambda (var) - (format "%S (quote %S)" (car var) (cdr var))) - vars "\n ") - "]\n" body ")") - body)))) - (cond ((or (member "code" result-params) (member "pp" result-params)) - (format (concat "(let [org-mode-print-catcher (java.io.StringWriter.)] " - "(clojure.pprint/with-pprint-dispatch clojure.pprint/%s-dispatch " - "(clojure.pprint/pprint (do %s) org-mode-print-catcher) " - "(str org-mode-print-catcher)))") - (if (member "code" result-params) "code" "simple") body)) - ;; if (:results output), collect printed output - ((member "output" result-params) - (format "(clojure.core/with-out-str %s)" body)) - (t body)))) + (body (org-trim + (if (null vars) (org-trim body) + (concat "(let [" + (mapconcat + (lambda (var) + (format "%S (quote %S)" (car var) (cdr var))) + vars "\n ") + "]\n" body ")"))))) + (if (or (member "code" result-params) + (member "pp" result-params)) + (format "(clojure.pprint/pprint (do %s))" body) + body))) (defun org-babel-execute:clojure (body params) - "Execute a block of Clojure code with Babel." - (require 'slime) - (with-temp-buffer - (insert (org-babel-expand-body:clojure body params)) - (let ((result - (slime-eval - `(swank:eval-and-grab-output - ,(buffer-substring-no-properties (point-min) (point-max))) - (cdr (assoc :package params))))) - (let ((result-params (cdr (assoc :result-params params)))) - (org-babel-result-cond result-params - result - (condition-case nil (org-babel-script-escape result) - (error result))))))) + "Execute a block of Clojure code with Babel. +The underlying process performed by the code block can be output +using the :show-process parameter." + (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) + (cider-current-ns)))) + (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) + (cider-current-ns)) + + ;; 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 + (condition-case nil (org-babel-script-escape result) + (error result))))) (provide 'ob-clojure) - - ;;; ob-clojure.el ends here diff --git a/lisp/org/ob-comint.el b/lisp/org/ob-comint.el index 78c5021b1b2..2a1d274365c 100644 --- a/lisp/org/ob-comint.el +++ b/lisp/org/ob-comint.el @@ -1,4 +1,4 @@ -;;; ob-comint.el --- org-babel functions for interaction with comint buffers +;;; ob-comint.el --- Babel Functions for Interaction with Comint Buffers -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -33,10 +33,6 @@ (require 'ob-core) (require 'org-compat) (require 'comint) -(eval-when-compile (require 'cl)) -(declare-function with-parsed-tramp-file-name "tramp" - (filename var &rest body) t) -(declare-function tramp-flush-directory-property "tramp-cache" (key directory)) (defun org-babel-comint-buffer-livep (buffer) "Check if BUFFER is a comint buffer with a live process." @@ -49,12 +45,14 @@ BUFFER is checked with `org-babel-comint-buffer-livep'. BODY is executed inside the protection of `save-excursion' and `save-match-data'." (declare (indent 1)) - `(save-excursion + `(progn + (unless (org-babel-comint-buffer-livep ,buffer) + (error "Buffer %s does not exist or has no process" ,buffer)) (save-match-data - (unless (org-babel-comint-buffer-livep ,buffer) - (error "Buffer %s does not exist or has no process" ,buffer)) - (set-buffer ,buffer) - ,@body))) + (with-current-buffer ,buffer + (save-excursion + (let ((comint-input-filter (lambda (_input) nil))) + ,@body)))))) (def-edebug-spec org-babel-comint-in-buffer (form body)) (defmacro org-babel-comint-with-output (meta &rest body) @@ -70,53 +68,49 @@ elements are optional. This macro ensures that the filter is removed in case of an error or user `keyboard-quit' during execution of body." (declare (indent 1)) - (let ((buffer (car meta)) - (eoe-indicator (cadr meta)) - (remove-echo (cadr (cdr meta))) - (full-body (cadr (cdr (cdr meta))))) + (let ((buffer (nth 0 meta)) + (eoe-indicator (nth 1 meta)) + (remove-echo (nth 2 meta)) + (full-body (nth 3 meta))) `(org-babel-comint-in-buffer ,buffer - (let ((string-buffer "") dangling-text raw) - ;; setup filter - (setq comint-output-filter-functions + (let* ((string-buffer "") + (comint-output-filter-functions (cons (lambda (text) (setq string-buffer (concat string-buffer text))) comint-output-filter-functions)) - (unwind-protect - (progn - ;; got located, and save dangling text - (goto-char (process-mark (get-buffer-process (current-buffer)))) - (let ((start (point)) - (end (point-max))) - (setq dangling-text (buffer-substring start end)) - (delete-region start end)) - ;; pass FULL-BODY to process - ,@body - ;; wait for end-of-evaluation indicator - (while (progn - (goto-char comint-last-input-end) - (not (save-excursion - (and (re-search-forward - (regexp-quote ,eoe-indicator) nil t) - (re-search-forward - comint-prompt-regexp nil t))))) - (accept-process-output (get-buffer-process (current-buffer))) - ;; thought the following this would allow async - ;; background running, but I was wrong... - ;; (run-with-timer .5 .5 'accept-process-output - ;; (get-buffer-process (current-buffer))) - ) - ;; replace cut dangling text - (goto-char (process-mark (get-buffer-process (current-buffer)))) - (insert dangling-text)) - ;; remove filter - (setq comint-output-filter-functions - (cdr comint-output-filter-functions))) + dangling-text) + ;; got located, and save dangling text + (goto-char (process-mark (get-buffer-process (current-buffer)))) + (let ((start (point)) + (end (point-max))) + (setq dangling-text (buffer-substring start end)) + (delete-region start end)) + ;; pass FULL-BODY to process + ,@body + ;; wait for end-of-evaluation indicator + (while (progn + (goto-char comint-last-input-end) + (not (save-excursion + (and (re-search-forward + (regexp-quote ,eoe-indicator) nil t) + (re-search-forward + comint-prompt-regexp nil t))))) + (accept-process-output (get-buffer-process (current-buffer))) + ;; thought the following this would allow async + ;; background running, but I was wrong... + ;; (run-with-timer .5 .5 'accept-process-output + ;; (get-buffer-process (current-buffer))) + ) + ;; replace cut dangling text + (goto-char (process-mark (get-buffer-process (current-buffer)))) + (insert dangling-text) + ;; remove echo'd FULL-BODY from input - (if (and ,remove-echo ,full-body - (string-match - (replace-regexp-in-string - "\n" "[\r\n]+" (regexp-quote (or ,full-body ""))) - string-buffer)) - (setq raw (substring string-buffer (match-end 0)))) + (when (and ,remove-echo ,full-body + (string-match + (replace-regexp-in-string + "\n" "[\r\n]+" (regexp-quote (or ,full-body ""))) + string-buffer)) + (setq string-buffer (substring string-buffer (match-end 0)))) (split-string string-buffer comint-prompt-regexp))))) (def-edebug-spec org-babel-comint-with-output (sexp body)) @@ -149,15 +143,10 @@ Don't return until FILE exists. Code in STRING must ensure that FILE exists at end of evaluation." (unless (org-babel-comint-buffer-livep buffer) (error "Buffer %s does not exist or has no process" buffer)) - (if (file-exists-p file) (delete-file file)) + (when (file-exists-p file) (delete-file file)) (process-send-string (get-buffer-process buffer) - (if (string-match "\n$" string) string (concat string "\n"))) - ;; From Tramp 2.1.19 the following cache flush is not necessary - (if (file-remote-p default-directory) - (let (v) - (with-parsed-tramp-file-name default-directory nil - (tramp-flush-directory-property v "")))) + (if (= (aref string (1- (length string))) ?\n) string (concat string "\n"))) (while (not (file-exists-p file)) (sit-for (or period 0.25)))) (provide 'ob-comint) diff --git a/lisp/org/ob-coq.el b/lisp/org/ob-coq.el new file mode 100644 index 00000000000..76bfc5add90 --- /dev/null +++ b/lisp/org/ob-coq.el @@ -0,0 +1,78 @@ +;;; ob-coq.el --- Babel Functions for Coq -*- lexical-binding: t; -*- + +;; Copyright (C) 2010-2017 Free Software Foundation, Inc. + +;; Author: Eric Schulte +;; Keywords: literate programming, reproducible research +;; Homepage: http://orgmode.org + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Rudimentary support for evaluating Coq code blocks. Currently only +;; session evaluation is supported. Requires both coq.el and +;; coq-inferior.el, both of which are distributed with Coq. +;; +;; http://coq.inria.fr/ + +;;; Code: +(require 'ob) + +(declare-function run-coq "ext:coq-inferior.el" (cmd)) +(declare-function coq-proc "ext:coq-inferior.el" ()) + +(defvar coq-program-name "coqtop" + "Name of the coq toplevel to run.") + +(defvar org-babel-coq-buffer "*coq*" + "Buffer in which to evaluate coq code blocks.") + +(defun org-babel-coq-clean-prompt (string) + (if (string-match "^[^[:space:]]+ < " string) + (substring string 0 (match-beginning 0)) + string)) + +(defun org-babel-execute:coq (body params) + (let ((full-body (org-babel-expand-body:generic body params)) + (session (org-babel-coq-initiate-session)) + (pt (lambda () + (marker-position + (process-mark (get-buffer-process (current-buffer))))))) + (org-babel-coq-clean-prompt + (org-babel-comint-in-buffer session + (let ((start (funcall pt))) + (with-temp-buffer + (insert full-body) + (comint-send-region (coq-proc) (point-min) (point-max)) + (comint-send-string (coq-proc) + (if (string= (buffer-substring (- (point-max) 1) (point-max)) ".") + "\n" + ".\n"))) + (while (equal start (funcall pt)) (sleep-for 0.1)) + (buffer-substring start (funcall pt))))))) + +(defun org-babel-coq-initiate-session () + "Initiate a coq session. +If there is not a current inferior-process-buffer in SESSION then +create one. Return the initialized session." + (unless (fboundp 'run-coq) + (error "`run-coq' not defined, load coq-inferior.el")) + (save-window-excursion (run-coq coq-program-name)) + (sit-for 0.1) + (get-buffer org-babel-coq-buffer)) + +(provide 'ob-coq) diff --git a/lisp/org/ob-core.el b/lisp/org/ob-core.el index cfbcbe6eced..17aae68434a 100644 --- a/lisp/org/ob-core.el +++ b/lisp/org/ob-core.el @@ -1,4 +1,4 @@ -;;; ob-core.el --- working with code blocks in org-mode +;;; ob-core.el --- Working with Code Blocks -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. @@ -20,11 +20,10 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: -(eval-when-compile - (require 'cl)) +(require 'cl-lib) (require 'ob-eval) (require 'org-macs) (require 'org-compat) @@ -33,66 +32,69 @@ (if (memq system-type '(windows-nt cygwin)) ".exe" nil)) -;; dynamically scoped for tramp -(defvar org-babel-call-process-region-original nil) -(defvar org-src-lang-modes) + (defvar org-babel-library-of-babel) -(declare-function outline-show-all "outline" ()) -(declare-function org-every "org" (pred seq)) -(declare-function org-reduce "org" (CL-FUNC CL-SEQ &rest CL-KEYS)) +(defvar org-edit-src-content-indentation) +(defvar org-src-lang-modes) +(defvar org-src-preserve-indentation) + +(declare-function org-at-item-p "org-list" ()) +(declare-function org-at-table-p "org" (&optional table-type)) +(declare-function org-babel-lob-execute-maybe "ob-lob" ()) +(declare-function org-babel-ref-goto-headline-id "ob-ref" (id)) +(declare-function org-babel-ref-headline-body "ob-ref" ()) +(declare-function org-babel-ref-parse "ob-ref" (assignment)) +(declare-function org-babel-ref-resolve "ob-ref" (ref)) +(declare-function org-babel-ref-split-args "ob-ref" (arg-string)) +(declare-function org-babel-tangle-comment-links "ob-tangle" (&optional info)) +(declare-function org-completing-read "org" (&rest args)) +(declare-function org-current-level "org" ()) +(declare-function org-cycle "org" (&optional arg)) +(declare-function org-do-remove-indentation "org" (&optional n)) +(declare-function org-edit-src-code "org-src" (&optional code edit-buffer-name)) +(declare-function org-edit-src-exit "org-src" ()) +(declare-function org-element-at-point "org-element" ()) +(declare-function org-element-context "org-element" (&optional element)) +(declare-function org-element-normalize-string "org-element" (s)) +(declare-function org-element-property "org-element" (property element)) +(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-get-indentation "org" (&optional line)) +(declare-function org-get-indentation "org" (&optional line)) +(declare-function org-in-regexp "org" (regexp &optional nlines visually)) +(declare-function org-indent-line "org" ()) +(declare-function org-list-get-list-end "org-list" (item struct prevs)) +(declare-function org-list-prevs-alist "org-list" (struct)) +(declare-function org-list-struct "org-list" ()) +(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 tramp-compat-make-temp-file "tramp-compat" - (filename &optional dir-flag)) -(declare-function org-icompleting-read "org" (&rest args)) -(declare-function org-edit-src-code "org-src" - (&optional context code edit-buffer-name)) -(declare-function org-edit-src-exit "org-src" (&optional context)) -(declare-function org-open-at-point "org" (&optional in-emacs reference-buffer)) -(declare-function org-outline-overlay-data "org" (&optional use-markers)) -(declare-function org-set-outline-overlay-data "org" (data)) (declare-function org-narrow-to-subtree "org" ()) -(declare-function org-split-string "org" (string &optional separators)) -(declare-function org-entry-get "org" - (pom property &optional inherit literal-nil)) -(declare-function org-make-options-regexp "org" (kwds &optional extra)) -(declare-function org-do-remove-indentation "org" (&optional n)) (declare-function org-next-block "org" (arg &optional backward block-regexp)) +(declare-function org-number-sequence "org-compat" (from &optional to inc)) +(declare-function org-open-at-point "org" (&optional in-emacs reference-buffer)) +(declare-function org-outline-overlay-data "org" (&optional use-markers)) (declare-function org-previous-block "org" (arg &optional block-regexp)) +(declare-function org-remove-indentation "org" (code &optional n)) +(declare-function org-reverse-string "org" (string)) +(declare-function org-set-outline-overlay-data "org" (data)) (declare-function org-show-context "org" (&optional key)) -(declare-function org-at-table-p "org" (&optional table-type)) -(declare-function org-cycle "org" (&optional arg)) -(declare-function org-uniquify "org" (list)) -(declare-function org-current-level "org" ()) -(declare-function org-table-import "org-table" (file arg)) -(declare-function org-add-hook "org-compat" - (hook function &optional append local)) +(declare-function org-src-coderef-format "org-src" (element)) +(declare-function org-src-coderef-regexp "org-src" (fmt &optional label)) (declare-function org-table-align "org-table" ()) (declare-function org-table-end "org-table" (&optional table-type)) -(declare-function orgtbl-to-generic "org-table" - (table params &optional backend)) -(declare-function orgtbl-to-orgtbl "org-table" (table params)) -(declare-function org-babel-tangle-comment-links "ob-tangle" (&optional info)) -(declare-function org-babel-lob-get-info "ob-lob" nil) -(declare-function org-babel-ref-split-args "ob-ref" (arg-string)) -(declare-function org-babel-ref-parse "ob-ref" (assignment)) -(declare-function org-babel-ref-resolve "ob-ref" (ref)) -(declare-function org-babel-ref-goto-headline-id "ob-ref" (id)) -(declare-function org-babel-ref-headline-body "ob-ref" ()) -(declare-function org-babel-lob-execute-maybe "ob-lob" ()) -(declare-function org-number-sequence "org-compat" (from &optional to inc)) -(declare-function org-at-item-p "org-list" ()) -(declare-function org-list-parse-list "org-list" (&optional delete)) -(declare-function org-list-to-generic "org-list" (LIST PARAMS)) -(declare-function org-list-struct "org-list" ()) -(declare-function org-list-prevs-alist "org-list" (struct)) -(declare-function org-list-get-list-end "org-list" (item struct prevs)) -(declare-function org-remove-if "org" (predicate seq)) -(declare-function org-completing-read "org" (&rest args)) -(declare-function org-escape-code-in-region "org-src" (beg end)) -(declare-function org-unescape-code-in-string "org-src" (s)) +(declare-function org-table-import "org-table" (file arg)) (declare-function org-table-to-lisp "org-table" (&optional txt)) -(declare-function org-reverse-string "org" (string)) -(declare-function org-element-context "org-element" (&optional ELEMENT)) +(declare-function org-trim "org" (s &optional keep-lead)) +(declare-function org-unescape-code-in-string "org-src" (s)) +(declare-function org-uniquify "org" (list)) +(declare-function orgtbl-to-generic "org-table" (table params)) +(declare-function orgtbl-to-orgtbl "org-table" (table params)) +(declare-function outline-show-all "outline" ()) +(declare-function tramp-compat-make-temp-file "tramp-compat" (filename &optional dir-flag)) (defgroup org-babel nil "Code block evaluation and management in `org-mode' documents." @@ -101,11 +103,12 @@ (defcustom org-confirm-babel-evaluate t "Confirm before evaluation. +\\<org-mode-map>\ Require confirmation before interactively evaluating code -blocks in Org-mode buffers. The default value of this variable -is t, meaning confirmation is required for any code block -evaluation. This variable can be set to nil to inhibit any -future confirmation requests. This variable can also be set to a +blocks in Org buffers. The default value of this variable is t, +meaning confirmation is required for any code block evaluation. +This variable can be set to nil to inhibit any future +confirmation requests. This variable can also be set to a function which takes two arguments the language of the code block and the body of the code block. Such a function should then return a non-nil value if the user should be prompted for @@ -113,10 +116,11 @@ execution or nil if no prompt is required. Warning: Disabling confirmation may result in accidental evaluation of potentially harmful code. It may be advisable -remove code block execution from C-c C-c as further protection +remove code block execution from `\\[org-ctrl-c-ctrl-c]' \ +as further protection against accidental code block evaluation. The `org-babel-no-eval-on-ctrl-c-ctrl-c' variable can be used to -remove code block execution from the C-c C-c keybinding." +remove code block execution from the `\\[org-ctrl-c-ctrl-c]' keybinding." :group 'org-babel :version "24.1" :type '(choice boolean function)) @@ -124,19 +128,24 @@ remove code block execution from the C-c C-c keybinding." (put 'org-confirm-babel-evaluate 'safe-local-variable (lambda (x) (eq x t))) (defcustom org-babel-no-eval-on-ctrl-c-ctrl-c nil - "Remove code block evaluation from the C-c C-c key binding." + "\\<org-mode-map>\ +Remove code block evaluation from the `\\[org-ctrl-c-ctrl-c]' key binding." :group 'org-babel :version "24.1" :type 'boolean) (defcustom org-babel-results-keyword "RESULTS" "Keyword used to name results generated by code blocks. -Should be either RESULTS or NAME however any capitalization may -be used." +It should be \"RESULTS\". However any capitalization may be +used." :group 'org-babel :version "24.4" :package-version '(Org . "8.0") - :type 'string) + :type 'string + :safe (lambda (v) + (and (stringp v) + (eq (compare-strings "RESULTS" nil nil v nil nil t) + t)))) (defcustom org-babel-noweb-wrap-start "<<" "String used to begin a noweb reference in a code block. @@ -155,6 +164,27 @@ See also `org-babel-noweb-wrap-start'." This string must include a \"%s\" which will be replaced by the results." :group 'org-babel :type 'string) +(put 'org-babel-inline-result-wrap + 'safe-local-variable + (lambda (value) + (and (stringp value) + (string-match-p "%s" value)))) + +(defcustom org-babel-hash-show-time nil + "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) + +(defcustom org-babel-uppercase-example-markers nil + "When non-nil, begin/end example markers will be inserted in upper case." + :group 'org-babel + :type 'boolean + :version "26.1" + :package-version '(Org . "9.1") + :safe #'booleanp) (defun org-babel-noweb-wrap (&optional regexp) (concat org-babel-noweb-wrap-start @@ -169,14 +199,6 @@ This string must include a \"%s\" which will be replaced by the results." "^[ \t]*#\\+headers?:[ \t]*\\([^\n]*\\)$" "Regular expression used to match multi-line header arguments.") -(defvar org-babel-src-name-w-name-regexp - (concat org-babel-src-name-regexp - "\\(" - org-babel-multi-line-header-regexp - "\\)*" - "\\([^ ()\f\t\n\r\v]+\\)") - "Regular expression matching source name lines with a name.") - (defvar org-babel-src-block-regexp (concat ;; (1) indentation (2) lang @@ -189,168 +211,98 @@ This string must include a \"%s\" which will be replaced by the results." "\\([^\000]*?\n\\)??[ \t]*#\\+end_src") "Regexp used to identify code blocks.") -(defvar org-babel-inline-src-block-regexp - (concat - ;; (1) replacement target (2) lang - "\\(?:^\\|[^-[:alnum:]]\\)\\(src_\\([^ \f\t\n\r\v]+\\)" - ;; (3,4) (unused, headers) - "\\(\\|\\[\\(.*?\\)\\]\\)" - ;; (5) body - "{\\([^\f\n\r\v]+?\\)}\\)") - "Regexp used to identify inline src-blocks.") - -(defun org-babel-get-header (params key &optional others) - "Select only header argument of type KEY from a list. -Optional argument OTHERS indicates that only the header that do -not match KEY should be returned." - (delq nil - (mapcar - (lambda (p) (when (funcall (if others #'not #'identity) (eq (car p) key)) p)) - params))) - -(defun org-babel-get-inline-src-block-matches() - "Set match data if within body of an inline source block. -Returns non-nil if match-data set" - (let ((src-at-0-p (save-excursion - (beginning-of-line 1) - (string= "src" (thing-at-point 'word)))) - (first-line-p (= (line-beginning-position) (point-min))) - (orig (point))) - (let ((search-for (cond ((and src-at-0-p first-line-p "src_")) - (first-line-p "[[:punct:] \t]src_") - (t "[[:punct:] \f\t\n\r\v]src_"))) - (lower-limit (if first-line-p - nil - (- (point-at-bol) 1)))) - (save-excursion - (when (or (and src-at-0-p (bobp)) - (and (re-search-forward "}" (point-at-eol) t) - (re-search-backward search-for lower-limit t) - (> orig (point)))) - (when (looking-at org-babel-inline-src-block-regexp) - t )))))) - -(defvar org-babel-inline-lob-one-liner-regexp) -(defun org-babel-get-lob-one-liner-matches() - "Set match data if on line of an lob one liner. -Returns non-nil if match-data set" - (save-excursion - (unless (= (point) (point-at-bol)) ;; move before inline block - (re-search-backward "[ \f\t\n\r\v]" nil t)) - (if (looking-at org-babel-inline-lob-one-liner-regexp) - t - nil))) - -(defun org-babel-get-src-block-info (&optional light) - "Get information on the current source block. - -Optional argument LIGHT does not resolve remote variable -references; a process which could likely result in the execution -of other code blocks. +(defun org-babel--get-vars (params) + "Return the babel variable assignments in PARAMS. + +PARAMS is a quasi-alist of header args, which may contain +multiple entries for the key `:var'. This function returns a +list of the cdr of all the `:var' entries." + (mapcar #'cdr + (cl-remove-if-not (lambda (x) (eq (car x) :var)) params))) + +(defvar org-babel-exp-reference-buffer nil + "Buffer containing original contents of the exported buffer. +This is used by Babel to resolve references in source blocks. +Its value is dynamically bound during export.") + +(defun org-babel-check-confirm-evaluate (info) + "Check whether INFO allows code block evaluation. + +Returns nil if evaluation is disallowed, t if it is +unconditionally allowed, and the symbol `query' if the user +should be asked whether to allow evaluation." + (let* ((headers (nth 2 info)) + (eval (or (cdr (assq :eval headers)) + (when (assq :noeval headers) "no"))) + (eval-no (member eval '("no" "never"))) + (export org-babel-exp-reference-buffer) + (eval-no-export (and export (member eval '("no-export" "never-export")))) + (noeval (or eval-no eval-no-export)) + (query (or (equal eval "query") + (and export (equal eval "query-export")) + (if (functionp org-confirm-babel-evaluate) + (funcall org-confirm-babel-evaluate + ;; Language, code block body. + (nth 0 info) (nth 1 info)) + org-confirm-babel-evaluate)))) + (cond + (noeval nil) + (query 'query) + (t t)))) -Returns a list - (language body header-arguments-alist switches name indent block-head)." - (let ((case-fold-search t) head info name indent) - ;; full code block - (if (setq head (org-babel-where-is-src-block-head)) - (save-excursion - (goto-char head) - (setq info (org-babel-parse-src-block-match)) - (setq indent (car (last info))) - (setq info (butlast info)) - (while (and (forward-line -1) - (looking-at org-babel-multi-line-header-regexp)) - (setf (nth 2 info) - (org-babel-merge-params - (nth 2 info) - (org-babel-parse-header-arguments (match-string 1))))) - (when (looking-at org-babel-src-name-w-name-regexp) - (setq name (org-no-properties (match-string 3))))) - ;; inline source block - (when (org-babel-get-inline-src-block-matches) - (setq info (org-babel-parse-inline-src-block-match)))) - ;; resolve variable references and add summary parameters - (when (and info (not light)) - (setf (nth 2 info) (org-babel-process-params (nth 2 info)))) - (when info (append info (list name indent head))))) - -(defvar org-current-export-file) ; dynamically bound -(defmacro org-babel-check-confirm-evaluate (info &rest body) - "Evaluate BODY with special execution confirmation variables set. - -Specifically; NOEVAL will indicate if evaluation is allowed, -QUERY will indicate if a user query is required, CODE-BLOCK will -hold the language of the code block, and BLOCK-NAME will hold the -name of the code block." - (declare (indent defun)) - (org-with-gensyms - (lang block-body headers name eval eval-no export eval-no-export) - `(let* ((,lang (nth 0 ,info)) - (,block-body (nth 1 ,info)) - (,headers (nth 2 ,info)) - (,name (nth 4 ,info)) - (,eval (or (cdr (assoc :eval ,headers)) - (when (assoc :noeval ,headers) "no"))) - (,eval-no (or (equal ,eval "no") - (equal ,eval "never"))) - (,export (org-bound-and-true-p org-current-export-file)) - (,eval-no-export (and ,export (or (equal ,eval "no-export") - (equal ,eval "never-export")))) - (noeval (or ,eval-no ,eval-no-export)) - (query (or (equal ,eval "query") - (and ,export (equal ,eval "query-export")) - (if (functionp org-confirm-babel-evaluate) - (funcall org-confirm-babel-evaluate - ,lang ,block-body) - org-confirm-babel-evaluate))) - (code-block (if ,info (format " %s " ,lang) " ")) - (block-name (if ,name (format " (%s) " ,name) " "))) - ;; Silence byte-compiler is `body' doesn't use those vars. - (ignore noeval query) - ,@body))) - -(defsubst org-babel-check-evaluate (info) +(defun org-babel-check-evaluate (info) "Check if code block INFO should be evaluated. -Do not query the user." - (org-babel-check-confirm-evaluate info - (not (when noeval - (message "Evaluation of this%scode-block%sis disabled." - code-block block-name))))) - - ;; dynamically scoped for asynchronous export +Do not query the user, but do display an informative message if +evaluation is blocked. Returns non-nil if evaluation is not blocked." + (let ((confirmed (org-babel-check-confirm-evaluate info))) + (unless confirmed + (message "Evaluation of this %s code block%sis disabled." + (nth 0 info) + (let ((name (nth 4 info))) + (if name (format " (%s) " name) " ")))) + confirmed)) + +;; Dynamically scoped for asynchronous export. (defvar org-babel-confirm-evaluate-answer-no) -(defsubst org-babel-confirm-evaluate (info) +(defun org-babel-confirm-evaluate (info) "Confirm evaluation of the code block INFO. -If the variable `org-babel-confirm-evaluate-answer-no' is bound -to a non-nil value, auto-answer with \"no\". - This query can also be suppressed by setting the value of `org-confirm-babel-evaluate' to nil, in which case all future interactive code block evaluations will proceed without any confirmation from the user. Note disabling confirmation may result in accidental evaluation -of potentially harmful code." - (org-babel-check-confirm-evaluate info - (not (when query - (unless - (and (not (org-bound-and-true-p +of potentially harmful code. + +The variable `org-babel-confirm-evaluate-answer-no' is used by +the async export process, which requires a non-interactive +environment, to override this check." + (let* ((evalp (org-babel-check-confirm-evaluate info)) + (lang (nth 0 info)) + (name (nth 4 info)) + (name-string (if name (format " (%s) " name) " "))) + (pcase evalp + (`nil nil) + (`t t) + (`query (or + (and (not (bound-and-true-p org-babel-confirm-evaluate-answer-no)) (yes-or-no-p - (format "Evaluate this%scode block%son your system? " - code-block block-name))) - (message "Evaluation of this%scode-block%sis aborted." - code-block block-name)))))) + (format "Evaluate this %s code block%son your system? " + lang name-string))) + (progn + (message "Evaluation of this %s code block%sis aborted." + lang name-string) + nil))) + (x (error "Unexpected value `%s' from `org-babel-check-confirm-evaluate'" x))))) ;;;###autoload (defun org-babel-execute-safely-maybe () (unless org-babel-no-eval-on-ctrl-c-ctrl-c (org-babel-execute-maybe))) -(add-hook 'org-ctrl-c-ctrl-c-hook 'org-babel-execute-safely-maybe) - ;;;###autoload (defun org-babel-execute-maybe () (interactive) @@ -361,8 +313,8 @@ of potentially harmful code." "Execute BODY if point is in a source block and return t. Otherwise do nothing and return nil." - `(if (or (org-babel-where-is-src-block-head) - (org-babel-get-inline-src-block-matches)) + `(if (memq (org-element-type (org-element-context)) + '(inline-src-block src-block)) (progn ,@body t) @@ -394,12 +346,16 @@ a window into the `org-babel-get-src-block-info' function." (header-args (nth 2 info))) (when name (funcall printf "Name: %s\n" name)) (when lang (funcall printf "Lang: %s\n" lang)) + (funcall printf "Properties:\n") + (funcall printf "\t:header-args \t%s\n" (org-entry-get (point) "header-args" t)) + (funcall printf "\t:header-args:%s \t%s\n" lang (org-entry-get (point) (concat "header-args:" lang) t)) + (when (funcall full switches) (funcall printf "Switches: %s\n" switches)) (funcall printf "Header Arguments:\n") (dolist (pair (sort header-args (lambda (a b) (string< (symbol-name (car a)) (symbol-name (car b)))))) - (when (funcall full (cdr pair)) + (when (funcall full (format "%s" (cdr pair))) (funcall printf "\t%S%s\t%s\n" (car pair) (if (> (length (format "%S" (car pair))) 7) "" "\t") @@ -442,11 +398,13 @@ then run `org-babel-switch-to-session'." (colnames . ((nil no yes))) (comments . ((no link yes org both noweb))) (dir . :any) - (eval . ((never query))) + (eval . ((yes no no-export strip-export never-export eval never + query))) (exports . ((code results both none))) (epilogue . :any) (file . :any) (file-desc . :any) + (file-ext . :any) (hlines . ((no yes))) (mkdirp . ((yes no))) (no-expand) @@ -454,6 +412,7 @@ then run `org-babel-switch-to-session'." (noweb . ((yes no tangle no-export strip-export))) (noweb-ref . :any) (noweb-sep . :any) + (output-dir . :any) (padline . ((yes no))) (post . :any) (prologue . :any) @@ -476,31 +435,76 @@ then run `org-babel-switch-to-session'." Note that individual languages may define their own language specific header arguments as well.") +(defconst org-babel-safe-header-args + '(:cache :colnames :comments :exports :epilogue :hlines :noeval + :noweb :noweb-ref :noweb-sep :padline :prologue :rownames + :sep :session :tangle :wrap + (:eval . ("never" "query")) + (:results . (lambda (str) (not (string-match "file" str))))) + "A list of safe header arguments for babel source blocks. + +The list can have entries of the following forms: +- :ARG -> :ARG is always a safe header arg +- (:ARG . (VAL1 VAL2 ...)) -> :ARG is safe as a header arg if it is + `equal' to one of the VALs. +- (:ARG . FN) -> :ARG is safe as a header arg if the function FN + returns non-nil. FN is passed one + argument, the value of the header arg + (as a string).") + +(defmacro org-babel-header-args-safe-fn (safe-list) + "Return a function that determines whether a list of header args are safe. + +Intended usage is: +\(put \\='org-babel-default-header-args \\='safe-local-variable + (org-babel-header-args-safe-p org-babel-safe-header-args) + +This allows org-babel languages to extend the list of safe values for +their `org-babel-default-header-args:foo' variable. + +For the format of SAFE-LIST, see `org-babel-safe-header-args'." + `(lambda (value) + (and (listp value) + (cl-every + (lambda (pair) + (and (consp pair) + (org-babel-one-header-arg-safe-p pair ,safe-list))) + value)))) + (defvar org-babel-default-header-args '((:session . "none") (:results . "replace") (:exports . "code") (:cache . "no") (:noweb . "no") (:hlines . "no") (:tangle . "no")) "Default arguments to use when evaluating a source block.") +(put 'org-babel-default-header-args 'safe-local-variable + (org-babel-header-args-safe-fn org-babel-safe-header-args)) (defvar org-babel-default-inline-header-args - '((:session . "none") (:results . "replace") (:exports . "results")) + '((:session . "none") (:results . "replace") + (:exports . "results") (:hlines . "yes")) "Default arguments to use when evaluating an inline source block.") - -(defvar org-babel-data-names '("tblname" "results" "name")) - -(defvar org-babel-result-regexp - (concat "^[ \t]*#\\+" - (regexp-opt org-babel-data-names t) - "\\(\\[\\(" - ;; FIXME The string below is `org-ts-regexp' - "<\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} ?[^\r\n>]*?\\)>" - " \\)?\\([[:alnum:]]+\\)\\]\\)?\\:[ \t]*") +(put 'org-babel-default-inline-header-args 'safe-local-variable + (org-babel-header-args-safe-fn org-babel-safe-header-args)) + +(defconst org-babel-name-regexp + (format "^[ \t]*#\\+%s:[ \t]*" + ;; FIXME: TBLNAME is for backward compatibility. + (regexp-opt '("NAME" "TBLNAME"))) + "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\\}\\)>") "Regular expression used to match result lines. If the results are associated with a hash key then the hash will -be saved in the second match data.") +be saved in match group 1.") -(defvar org-babel-result-w-name-regexp - (concat org-babel-result-regexp - "\\([^ ()\f\t\n\r\v]+\\)\\((\\(.*\\))\\|\\)")) +(defconst org-babel-result-w-name-regexp + (concat org-babel-result-regexp "\\(?9:[^ \t\n\r\v\f]+\\)") + "Regexp matching a RESULTS keyword with a name. +Name is saved in match group 9.") (defvar org-babel-min-lines-for-block-output 10 "The minimum number of lines for block output. @@ -510,33 +514,58 @@ block. Otherwise the output is marked as literal by inserting colons at the starts of the lines. This variable only takes effect if the :results output option is in effect.") +(defvar org-babel-noweb-error-all-langs nil + "Raise errors when noweb references don't resolve. +Also see `org-babel-noweb-error-langs' to control noweb errors on +a language by language bases.") + (defvar org-babel-noweb-error-langs nil "Languages for which Babel will raise literate programming errors. List of languages for which errors should be raised when the source code block satisfying a noweb reference in this language -can not be resolved.") +can not be resolved. Also see `org-babel-noweb-error-all-langs' +to raise errors for all languages.") (defvar org-babel-hash-show 4 "Number of initial characters to show of a hidden results hash.") -(defvar org-babel-hash-show-time nil - "Non-nil means show the time the code block was evaluated in the result hash.") - (defvar org-babel-after-execute-hook nil "Hook for functions to be called after `org-babel-execute-src-block'") -(defun org-babel-named-src-block-regexp-for-name (name) - "This generates a regexp used to match a src block named NAME." - (concat org-babel-src-name-regexp (regexp-quote name) - "[ \t(]*[\r\n]\\(?:^#.*[\r\n]\\)*" +(defun org-babel-named-src-block-regexp-for-name (&optional name) + "This generates a regexp used to match a src block named NAME. +If NAME is nil, match any name. Matched name is then put in +match group 9. Other match groups are defined in +`org-babel-src-block-regexp'." + (concat org-babel-src-name-regexp + (concat (if name (regexp-quote name) "\\(?9:.*?\\)") "[ \t]*" ) + "\\(?:\n[ \t]*#\\+\\S-+:.*\\)*?" + "\n" (substring org-babel-src-block-regexp 1))) (defun org-babel-named-data-regexp-for-name (name) "This generates a regexp used to match data named NAME." - (concat org-babel-result-regexp (regexp-quote name) "\\([ \t]\\|$\\)")) + (concat org-babel-name-regexp (regexp-quote name) "[ \t]*$")) + +(defun org-babel--normalize-body (datum) + "Normalize body for element or object DATUM. +DATUM is a source block element or an inline source block object. +Remove final newline character and spurious indentation." + (let* ((value (org-element-property :value datum)) + (body (if (string-suffix-p "\n" value) + (substring value 0 -1) + value))) + (cond ((eq (org-element-type datum) 'inline-src-block) + ;; Newline characters and indentation in an inline + ;; src-block are not meaningful, since they could come from + ;; some paragraph filling. Treat them as a white space. + (replace-regexp-in-string "\n[ \t]*" " " body)) + ((or org-src-preserve-indentation + (org-element-property :preserve-indent datum)) + body) + (t (org-remove-indentation body))))) ;;; functions -(defvar call-process-region) (defvar org-babel-current-src-block-location nil "Marker pointing to the src block currently being executed. This may also point to a call line or an inline code block. If @@ -546,6 +575,56 @@ the outer-most code block.") (defvar *this*) +(defun org-babel-get-src-block-info (&optional light datum) + "Extract information from a source block or inline source block. + +Optional argument LIGHT does not resolve remote variable +references; a process which could likely result in the execution +of other code blocks. + +By default, consider the block at point. However, when optional +argument DATUM is provided, extract information from that parsed +object instead. + +Return nil if point is not on a source block. Otherwise, return +a list with the following pattern: + + (language body arguments switches name start coderef)" + (let* ((datum (or datum (org-element-context))) + (type (org-element-type datum)) + (inline (eq type 'inline-src-block))) + (when (memq type '(inline-src-block src-block)) + (let* ((lang (org-element-property :language datum)) + (lang-headers (intern + (concat "org-babel-default-header-args:" lang))) + (name (org-element-property :name datum)) + (info + (list + lang + (org-babel--normalize-body datum) + (apply #'org-babel-merge-params + (if inline org-babel-default-inline-header-args + org-babel-default-header-args) + (and (boundp lang-headers) (eval lang-headers t)) + (append + ;; If DATUM is provided, make sure we get node + ;; properties applicable to its location within + ;; the document. + (org-with-point-at (org-element-property :begin datum) + (org-babel-params-from-properties lang)) + (mapcar #'org-babel-parse-header-arguments + (cons (org-element-property :parameters datum) + (org-element-property :header datum))))) + (or (org-element-property :switches datum) "") + name + (org-element-property (if inline :begin :post-affiliated) + datum) + (and (not inline) (org-src-coderef-format datum))))) + (unless light + (setf (nth 2 info) (org-babel-process-params (nth 2 info)))) + (setf (nth 2 info) (org-babel-generate-file-param name (nth 2 info))) + info)))) + ;;;###autoload (defun org-babel-execute-src-block (&optional arg info params) "Execute the current source code block. @@ -565,110 +644,91 @@ block." (interactive) (let* ((org-babel-current-src-block-location (or org-babel-current-src-block-location - (nth 6 info) + (nth 5 info) (org-babel-where-is-src-block-head))) - (info (if info - (copy-tree info) - (org-babel-get-src-block-info))) - (merged-params (org-babel-merge-params (nth 2 info) params))) - (when (org-babel-check-evaluate - (let ((i info)) (setf (nth 2 i) merged-params) i)) - (let* ((params (if params - (org-babel-process-params merged-params) - (nth 2 info))) - (cachep (and (not arg) (cdr (assoc :cache params)) - (string= "yes" (cdr (assoc :cache params))))) - (new-hash (when cachep (org-babel-sha1-hash info))) - (old-hash (when cachep (org-babel-current-result-hash))) - (cache-current-p (and (not arg) new-hash - (equal new-hash old-hash)))) + (info (if info (copy-tree info) (org-babel-get-src-block-info)))) + ;; Merge PARAMS with INFO before considering source block + ;; evaluation since both could disagree. + (cl-callf org-babel-merge-params (nth 2 info) params) + (when (org-babel-check-evaluate info) + (cl-callf org-babel-process-params (nth 2 info)) + (let* ((params (nth 2 info)) + (cache (let ((c (cdr (assq :cache params)))) + (and (not arg) c (string= "yes" c)))) + (new-hash (and cache (org-babel-sha1-hash info))) + (old-hash (and cache (org-babel-current-result-hash))) + (current-cache (and new-hash (equal new-hash old-hash)))) (cond - (cache-current-p - (save-excursion ;; return cached result + (current-cache + (save-excursion ;Return cached result. (goto-char (org-babel-where-is-src-block-result nil info)) - (end-of-line 1) (forward-char 1) + (forward-line) + (skip-chars-forward " \t") (let ((result (org-babel-read-result))) - (message (replace-regexp-in-string - "%" "%%" (format "%S" result))) - result))) - ((org-babel-confirm-evaluate - (let ((i info)) (setf (nth 2 i) merged-params) i)) + (message (replace-regexp-in-string "%" "%%" (format "%S" result))) + result))) + ((org-babel-confirm-evaluate info) (let* ((lang (nth 0 info)) - (result-params (cdr (assoc :result-params params))) - (body (setf (nth 1 info) - (if (org-babel-noweb-p params :eval) - (org-babel-expand-noweb-references info) - (nth 1 info)))) - (dir (cdr (assoc :dir params))) + (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)))) + (dir (cdr (assq :dir params))) (default-directory (or (and dir (file-name-as-directory (expand-file-name dir))) default-directory)) - (org-babel-call-process-region-original ;; for tramp handler - (or (org-bound-and-true-p - org-babel-call-process-region-original) - (symbol-function 'call-process-region))) - (indent (nth 5 info)) - result cmd) - (unwind-protect - (let ((call-process-region - (lambda (&rest args) - (apply 'org-babel-tramp-handle-call-process-region - args)))) - (let ((lang-check - (lambda (f) - (let ((f (intern (concat "org-babel-execute:" f)))) - (when (fboundp f) f))))) - (setq cmd - (or (funcall lang-check lang) - (funcall lang-check - (symbol-name - (cdr (assoc lang org-src-lang-modes)))) - (error "No org-babel-execute function for %s!" - lang)))) - (message "executing %s code block%s..." - (capitalize lang) - (if (nth 4 info) (format " (%s)" (nth 4 info)) "")) - (if (member "none" result-params) - (progn - (funcall cmd body params) - (message "result silenced") - (setq result nil)) - (setq result - (let ((result (funcall cmd body params))) - (if (and (eq (cdr (assoc :result-type params)) - 'value) - (or (member "vector" result-params) - (member "table" result-params)) - (not (listp result))) - (list (list result)) result))) - ;; If non-empty result and :file then write to :file. - (when (cdr (assoc :file params)) - (when result - (with-temp-file (cdr (assoc :file params)) - (insert - (org-babel-format-result - result (cdr (assoc :sep (nth 2 info))))))) - (setq result (cdr (assoc :file params)))) - ;; Possibly perform post process provided its appropriate. - (when (cdr (assoc :post params)) - (let ((*this* (if (cdr (assoc :file params)) - (org-babel-result-to-file - (cdr (assoc :file params)) - (when (assoc :file-desc params) - (or (cdr (assoc :file-desc params)) - result))) - result))) - (setq result (org-babel-ref-resolve - (cdr (assoc :post params)))) - (when (cdr (assoc :file params)) - (setq result-params - (remove "file" result-params))))) - (org-babel-insert-result - result result-params info new-hash indent lang)) - (run-hooks 'org-babel-after-execute-hook) - result) - (setq call-process-region - 'org-babel-call-process-region-original))))))))) + (cmd (intern (concat "org-babel-execute:" lang))) + result) + (unless (fboundp cmd) + (error "No org-babel-execute function for %s!" lang)) + (message "executing %s code block%s..." + (capitalize lang) + (let ((name (nth 4 info))) + (if name (format " (%s)" name) ""))) + (if (member "none" result-params) + (progn (funcall cmd body params) + (message "result silenced")) + (setq result + (let ((r (funcall cmd body params))) + (if (and (eq (cdr (assq :result-type params)) 'value) + (or (member "vector" result-params) + (member "table" result-params)) + (not (listp r))) + (list (list r)) + r))) + (let ((file (cdr (assq :file params)))) + ;; If non-empty result and :file then write to :file. + (when file + (when result + (with-temp-file file + (insert (org-babel-format-result + result (cdr (assq :sep params)))))) + (setq result file)) + ;; Possibly perform post process provided its + ;; appropriate. Dynamically bind "*this*" to the + ;; actual results of the block. + (let ((post (cdr (assq :post params)))) + (when post + (let ((*this* (if (not file) result + (org-babel-result-to-file + file + (let ((desc (assq :file-desc params))) + (and desc (or (cdr desc) result))))))) + (setq result (org-babel-ref-resolve post)) + (when file + (setq result-params (remove "file" result-params)))))) + (org-babel-insert-result + result result-params info new-hash lang))) + (run-hooks 'org-babel-after-execute-hook) + result))))))) (defun org-babel-expand-body:generic (body params &optional var-lines) "Expand BODY with PARAMS. @@ -676,8 +736,8 @@ Expand a block of code with org-babel according to its header arguments. This generic implementation of body expansion is called for languages which have not defined their own specific org-babel-expand-body:lang function." - (let ((pro (cdr (assoc :prologue params))) - (epi (cdr (assoc :epilogue params)))) + (let ((pro (cdr (assq :prologue params))) + (epi (cdr (assq :epilogue params)))) (mapconcat #'identity (append (when pro (list pro)) var-lines @@ -708,10 +768,9 @@ arguments and pop open the results in a preview buffer." (org-babel-expand-body:generic body params (and (fboundp assignments-cmd) (funcall assignments-cmd params)))))) - (if (org-called-interactively-p 'any) + (if (called-interactively-p 'any) (org-edit-src-code - nil expanded - (concat "*Org-Babel Preview " (buffer-name) "[ " lang " ]*")) + expanded (concat "*Org-Babel Preview " (buffer-name) "[ " lang " ]*")) expanded))) (defun org-babel-edit-distance (s1 s2) @@ -742,7 +801,7 @@ arguments and pop open the results in a preview buffer." (dolist (arg-pair new-list) (let ((header (car arg-pair))) (setq results - (cons arg-pair (org-remove-if + (cons arg-pair (cl-remove-if (lambda (pair) (equal header (car pair))) results)))))) results)) @@ -770,37 +829,43 @@ arguments and pop open the results in a preview buffer." (message "No suspicious header arguments found."))) ;;;###autoload -(defun org-babel-insert-header-arg () +(defun org-babel-insert-header-arg (&optional header-arg value) "Insert a header argument selecting from lists of common args and values." (interactive) - (let* ((lang (car (org-babel-get-src-block-info 'light))) + (let* ((info (org-babel-get-src-block-info 'light)) + (lang (car info)) + (begin (nth 5 info)) (lang-headers (intern (concat "org-babel-header-args:" lang))) (headers (org-babel-combine-header-arg-lists org-babel-common-header-args-w-values - (when (boundp lang-headers) (eval lang-headers)))) - (arg (org-icompleting-read - "Header Arg: " - (mapcar - (lambda (header-spec) (symbol-name (car header-spec))) - headers)))) - (insert ":" arg) - (let ((vals (cdr (assoc (intern arg) headers)))) - (when vals - (insert - " " - (cond - ((eq vals :any) - (read-from-minibuffer "value: ")) - ((listp vals) - (mapconcat - (lambda (group) - (let ((arg (org-icompleting-read - "value: " - (cons "default" (mapcar #'symbol-name group))))) - (if (and arg (not (string= "default" arg))) - (concat arg " ") - ""))) - vals "")))))))) + (when (boundp lang-headers) (eval lang-headers t)))) + (header-arg (or header-arg + (completing-read + "Header Arg: " + (mapcar + (lambda (header-spec) (symbol-name (car header-spec))) + headers)))) + (vals (cdr (assoc (intern header-arg) headers))) + (value (or value + (cond + ((eq vals :any) + (read-from-minibuffer "value: ")) + ((listp vals) + (mapconcat + (lambda (group) + (let ((arg (completing-read + "Value: " + (cons "default" + (mapcar #'symbol-name group))))) + (if (and arg (not (string= "default" arg))) + (concat arg " ") + ""))) + vals "")))))) + (save-excursion + (goto-char begin) + (goto-char (point-at-eol)) + (unless (= (char-before (point)) ?\ ) (insert " ")) + (insert ":" header-arg) (when value (insert " " value))))) ;; Add support for completing-read insertion of header arguments after ":" (defun org-babel-header-arg-expand () @@ -811,7 +876,7 @@ arguments and pop open the results in a preview buffer." (defun org-babel-enter-header-arg-w-completion (&optional lang) "Insert header argument appropriate for LANG with completion." (let* ((lang-headers-var (intern (concat "org-babel-header-args:" lang))) - (lang-headers (when (boundp lang-headers-var) (eval lang-headers-var))) + (lang-headers (when (boundp lang-headers-var) (eval lang-headers-var t))) (headers-w-values (org-babel-combine-header-arg-lists org-babel-common-header-args-w-values lang-headers)) (headers (mapcar #'symbol-name (mapcar #'car headers-w-values))) @@ -842,8 +907,8 @@ session." (if (org-babel-noweb-p params :eval) (org-babel-expand-noweb-references info) (nth 1 info))))) - (session (cdr (assoc :session params))) - (dir (cdr (assoc :dir params))) + (session (cdr (assq :session params))) + (dir (cdr (assq :dir params))) (default-directory (or (and dir (file-name-as-directory dir)) default-directory)) (cmd (intern (concat "org-babel-load-session:" lang)))) @@ -863,17 +928,17 @@ the session. Copy the body of the code block to the kill ring." (lang (nth 0 info)) (body (nth 1 info)) (params (nth 2 info)) - (session (cdr (assoc :session params))) - (dir (cdr (assoc :dir params))) + (session (cdr (assq :session params))) + (dir (cdr (assq :dir params))) (default-directory (or (and dir (file-name-as-directory dir)) default-directory)) (init-cmd (intern (format "org-babel-%s-initiate-session" lang))) (prep-cmd (intern (concat "org-babel-prep-session:" lang)))) - (if (and (stringp session) (string= session "none")) - (error "This block is not using a session!")) + (when (and (stringp session) (string= session "none")) + (error "This block is not using a session!")) (unless (fboundp init-cmd) (error "No org-babel-initiate-session function for %s!" lang)) - (with-temp-buffer (insert (org-babel-trim body)) + (with-temp-buffer (insert (org-trim body)) (copy-region-as-kill (point-min) (point-max))) (when arg (unless (fboundp prep-cmd) @@ -912,15 +977,15 @@ with a prefix argument then this is passed on to (org-edit-src-code) (funcall swap-windows))) +;;;###autoload (defmacro org-babel-do-in-edit-buffer (&rest body) "Evaluate BODY in edit buffer if there is a code block at point. Return t if a code block was found at point, nil otherwise." `(let ((org-src-window-setup 'switch-invisibly)) (when (and (org-babel-where-is-src-block-head) - (org-edit-src-code nil nil nil)) + (org-edit-src-code)) (unwind-protect (progn ,@body) - (if (org-bound-and-true-p org-edit-src-from-org-mode) - (org-edit-src-exit))) + (org-edit-src-exit)) t))) (def-edebug-spec org-babel-do-in-edit-buffer (body)) @@ -928,10 +993,10 @@ Return t if a code block was found at point, nil otherwise." "Read key sequence and execute the command in edit buffer. Enter a key sequence to be executed in the language major-mode edit buffer. For example, TAB will alter the contents of the -Org-mode code block according to the effect of TAB in the -language major-mode buffer. For languages that support -interactive sessions, this can be used to send code from the Org -buffer to the session for evaluation using the native major-mode +Org code block according to the effect of TAB in the language +major mode buffer. For languages that support interactive +sessions, this can be used to send code from the Org buffer +to the session for evaluation using the native major mode evaluation mechanisms." (interactive "kEnter key-sequence to execute in edit buffer: ") (org-babel-do-in-edit-buffer @@ -941,7 +1006,7 @@ evaluation mechanisms." (defvar org-bracket-link-regexp) (defun org-babel-active-location-p () - (memq (car (save-match-data (org-element-context))) + (memq (org-element-type (save-match-data (org-element-context))) '(babel-call inline-babel-call inline-src-block src-block))) ;;;###autoload @@ -965,7 +1030,7 @@ results already exist." ;; file results (org-open-at-point) (let ((r (org-babel-format-result - (org-babel-read-result) (cdr (assoc :sep (nth 2 info)))))) + (org-babel-read-result) (cdr (assq :sep (nth 2 info)))))) (pop-to-buffer (get-buffer-create "*Org-Babel Results*")) (delete-region (point-min) (point-max)) (insert r))) @@ -995,7 +1060,8 @@ beg-body --------- point at the beginning of the body end-body --------- point at the end of the body" (declare (indent 1)) (let ((tempvar (make-symbol "file"))) - `(let* ((,tempvar ,file) + `(let* ((case-fold-search t) + (,tempvar ,file) (visited-p (or (null ,tempvar) (get-file-buffer (expand-file-name ,tempvar)))) (point (point)) to-be-removed) @@ -1035,80 +1101,91 @@ end-body --------- point at the end of the body" ;;;###autoload (defmacro org-babel-map-inline-src-blocks (file &rest body) - "Evaluate BODY forms on each inline source-block in FILE. + "Evaluate BODY forms on each inline source block in FILE. If FILE is nil evaluate BODY forms on source blocks in current buffer." - (declare (indent 1)) - (let ((tempvar (make-symbol "file"))) - `(let* ((,tempvar ,file) - (visited-p (or (null ,tempvar) + (declare (indent 1) (debug (form body))) + (org-with-gensyms (datum end point tempvar to-be-removed visitedp) + `(let* ((case-fold-search t) + (,tempvar ,file) + (,visitedp (or (null ,tempvar) (get-file-buffer (expand-file-name ,tempvar)))) - (point (point)) to-be-removed) + (,point (point)) + ,to-be-removed) (save-window-excursion (when ,tempvar (find-file ,tempvar)) - (setq to-be-removed (current-buffer)) + (setq ,to-be-removed (current-buffer)) (goto-char (point-min)) - (while (re-search-forward org-babel-inline-src-block-regexp nil t) - (when (org-babel-active-location-p) - (goto-char (match-beginning 1)) - (save-match-data ,@body)) - (goto-char (match-end 0)))) - (unless visited-p (kill-buffer to-be-removed)) - (goto-char point)))) -(def-edebug-spec org-babel-map-inline-src-blocks (form body)) - -(defvar org-babel-lob-one-liner-regexp) + (while (re-search-forward "src_\\S-" nil t) + (let ((,datum (save-match-data (org-element-context)))) + (when (eq (org-element-type ,datum) 'inline-src-block) + (goto-char (match-beginning 0)) + (let ((,end (copy-marker (org-element-property :end ,datum)))) + ,@body + (goto-char ,end) + (set-marker ,end nil)))))) + (unless ,visitedp (kill-buffer ,to-be-removed)) + (goto-char ,point)))) ;;;###autoload (defmacro org-babel-map-call-lines (file &rest body) "Evaluate BODY forms on each call line in FILE. If FILE is nil evaluate BODY forms on source blocks in current buffer." - (declare (indent 1)) - (let ((tempvar (make-symbol "file"))) - `(let* ((,tempvar ,file) - (visited-p (or (null ,tempvar) + (declare (indent 1) (debug (form body))) + (org-with-gensyms (datum end point tempvar to-be-removed visitedp) + `(let* ((case-fold-search t) + (,tempvar ,file) + (,visitedp (or (null ,tempvar) (get-file-buffer (expand-file-name ,tempvar)))) - (point (point)) to-be-removed) + (,point (point)) + ,to-be-removed) (save-window-excursion (when ,tempvar (find-file ,tempvar)) - (setq to-be-removed (current-buffer)) + (setq ,to-be-removed (current-buffer)) (goto-char (point-min)) - (while (re-search-forward org-babel-lob-one-liner-regexp nil t) - (when (org-babel-active-location-p) - (goto-char (match-beginning 1)) - (save-match-data ,@body)) - (goto-char (match-end 0)))) - (unless visited-p (kill-buffer to-be-removed)) - (goto-char point)))) -(def-edebug-spec org-babel-map-call-lines (form body)) + (while (re-search-forward "call_\\S-\\|^[ \t]*#\\+CALL:" nil t) + (let ((,datum (save-match-data (org-element-context)))) + (when (memq (org-element-type ,datum) + '(babel-call inline-babel-call)) + (goto-char (match-beginning 0)) + (let ((,end (copy-marker (org-element-property :end ,datum)))) + ,@body + (goto-char ,end) + (set-marker ,end nil)))))) + (unless ,visitedp (kill-buffer ,to-be-removed)) + (goto-char ,point)))) ;;;###autoload (defmacro org-babel-map-executables (file &rest body) - (declare (indent 1)) - (let ((tempvar (make-symbol "file")) - (rx (make-symbol "rx"))) - `(let* ((,tempvar ,file) - (,rx (concat "\\(" org-babel-src-block-regexp - "\\|" org-babel-inline-src-block-regexp - "\\|" org-babel-lob-one-liner-regexp "\\)")) - (visited-p (or (null ,tempvar) + "Evaluate BODY forms on each active Babel code in FILE. +If FILE is nil evaluate BODY forms on source blocks in current +buffer." + (declare (indent 1) (debug (form body))) + (org-with-gensyms (datum end point tempvar to-be-removed visitedp) + `(let* ((case-fold-search t) + (,tempvar ,file) + (,visitedp (or (null ,tempvar) (get-file-buffer (expand-file-name ,tempvar)))) - (point (point)) to-be-removed) + (,point (point)) + ,to-be-removed) (save-window-excursion (when ,tempvar (find-file ,tempvar)) - (setq to-be-removed (current-buffer)) + (setq ,to-be-removed (current-buffer)) (goto-char (point-min)) - (while (re-search-forward ,rx nil t) - (when (org-babel-active-location-p) - (goto-char (match-beginning 1)) - (when (looking-at org-babel-inline-src-block-regexp) - (forward-char 1)) - (save-match-data ,@body)) - (goto-char (match-end 0)))) - (unless visited-p (kill-buffer to-be-removed)) - (goto-char point)))) -(def-edebug-spec org-babel-map-executables (form body)) + (while (re-search-forward + "\\(call\\|src\\)_\\|^[ \t]*#\\+\\(BEGIN_SRC\\|CALL:\\)" nil t) + (let ((,datum (save-match-data (org-element-context)))) + (when (memq (org-element-type ,datum) + '(babel-call inline-babel-call inline-src-block + src-block)) + (goto-char (match-beginning 0)) + (let ((,end (copy-marker (org-element-property :end ,datum)))) + ,@body + (goto-char ,end) + (set-marker ,end nil)))))) + (unless ,visitedp (kill-buffer ,to-be-removed)) + (goto-char ,point)))) ;;;###autoload (defun org-babel-execute-buffer (&optional arg) @@ -1119,7 +1196,8 @@ the current buffer." (org-babel-eval-wipe-error-buffer) (org-save-outline-visibility t (org-babel-map-executables nil - (if (looking-at org-babel-lob-one-liner-regexp) + (if (memq (org-element-type (org-element-context)) + '(babel-call inline-babel-call)) (org-babel-lob-execute-maybe) (org-babel-execute-src-block arg))))) @@ -1164,7 +1242,20 @@ the current subtree." (member (car arg) '(:results :exports))) (mapconcat #'identity (sort (funcall rm (split-string v)) #'string<) " ")) - (t v))))))) + (t v)))))) + ;; expanded body + (lang (nth 0 info)) + (params (nth 2 info)) + (body (if (org-babel-noweb-p params :eval) + (org-babel-expand-noweb-references info) (nth 1 info))) + (expand-cmd (intern (concat "org-babel-expand-body:" lang))) + (assignments-cmd (intern (concat "org-babel-variable-assignments:" + lang))) + (expanded + (if (fboundp expand-cmd) (funcall expand-cmd body params) + (org-babel-expand-body:generic + body params (and (fboundp assignments-cmd) + (funcall assignments-cmd params)))))) (let* ((it (format "%s-%s" (mapconcat #'identity @@ -1173,26 +1264,32 @@ the current subtree." (when normalized (format "%S" normalized)))) (nth 2 info))) ":") - (nth 1 info))) + expanded)) (hash (sha1 it))) - (when (org-called-interactively-p 'interactive) (message hash)) + (when (called-interactively-p 'interactive) (message hash)) hash)))) -(defun org-babel-current-result-hash () +(defun org-babel-current-result-hash (&optional info) "Return the current in-buffer hash." - (org-babel-where-is-src-block-result) - (org-no-properties (match-string 5))) + (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))))) -(defun org-babel-set-current-result-hash (hash) +(defun org-babel-set-current-result-hash (hash info) "Set the current in-buffer hash to HASH." - (org-babel-where-is-src-block-result) - (save-excursion (goto-char (match-beginning 5)) - (mapc #'delete-overlay (overlays-at (point))) - (forward-char org-babel-hash-show) - (mapc #'delete-overlay (overlays-at (point))) - (replace-match hash nil nil nil 5) - (goto-char (point-at-bol)) - (org-babel-hide-hash))) + (org-with-wide-buffer + (goto-char (org-babel-where-is-src-block-result nil info)) + (looking-at org-babel-result-regexp) + (goto-char (match-beginning 1)) + (mapc #'delete-overlay (overlays-at (point))) + (forward-char org-babel-hash-show) + (mapc #'delete-overlay (overlays-at (point))) + (replace-match hash nil nil nil 1) + (beginning-of-line) + (org-babel-hide-hash))) (defun org-babel-hide-hash () "Hide the hash in the current results line. @@ -1201,11 +1298,11 @@ 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) - (match-string 5)) - (let* ((start (match-beginning 5)) + (match-string 1)) + (let* ((start (match-beginning 1)) (hide-start (+ org-babel-hash-show start)) - (end (match-end 5)) - (hash (match-string 5)) + (end (match-end 1)) + (hash (match-string 1)) ov1 ov2) (setq ov1 (make-overlay start hide-start)) (setq ov2 (make-overlay hide-start end)) @@ -1227,14 +1324,14 @@ the `org-mode-hook'." (defun org-babel-hash-at-point (&optional point) "Return the value of the hash at POINT. +\\<org-mode-map>\ The hash is also added as the last element of the kill ring. -This can be called with C-c C-c." +This can be called with `\\[org-ctrl-c-ctrl-c]'." (interactive) (let ((hash (car (delq nil (mapcar (lambda (ol) (overlay-get ol 'babel-hash)) (overlays-at (or point (point)))))))) (when hash (kill-new hash) (message hash)))) -(add-hook 'org-ctrl-c-ctrl-c-hook 'org-babel-hash-at-point) (defun org-babel-result-hide-spec () "Hide portions of results lines. @@ -1288,15 +1385,15 @@ portions of results lines." (eq (overlay-get overlay 'invisible) 'org-babel-hide-result)) (overlays-at start))) - (if (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))) + (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 @@ -1316,8 +1413,8 @@ portions of results lines." (add-hook 'org-tab-first-hook 'org-babel-hide-result-toggle-maybe) ;; Remove overlays when changing major mode (add-hook 'org-mode-hook - (lambda () (org-add-hook 'change-major-mode-hook - 'org-babel-show-result-all 'append 'local))) + (lambda () (add-hook 'change-major-mode-hook + 'org-babel-show-result-all 'append 'local))) (defvar org-file-properties) (defun org-babel-params-from-properties (&optional lang) @@ -1326,122 +1423,98 @@ Return a list of association lists of source block params specified in the properties of the current outline entry." (save-match-data (list - ;; DEPRECATED header arguments specified as separate property at - ;; point of definition - (let (val sym) - (org-babel-parse-multiple-vars - (delq nil - (mapcar - (lambda (header-arg) - (and (setq val (org-entry-get (point) header-arg t)) - (cons (intern (concat ":" header-arg)) - (org-babel-read val)))) - (mapcar - #'symbol-name - (mapcar - #'car - (org-babel-combine-header-arg-lists - org-babel-common-header-args-w-values - (progn - (setq sym (intern (concat "org-babel-header-args:" lang))) - (and (boundp sym) (eval sym)))))))))) ;; header arguments specified with the header-args property at - ;; point of call + ;; point of call. (org-babel-parse-header-arguments (org-entry-get org-babel-current-src-block-location - "header-args" 'inherit)) - (when lang ;; language-specific header arguments at point of call - (org-babel-parse-header-arguments - (org-entry-get org-babel-current-src-block-location - (concat "header-args:" lang) 'inherit)))))) - -(defvar org-src-preserve-indentation) ;; declare defcustom from org-src -(defun org-babel-parse-src-block-match () - "Parse the results from a match of the `org-babel-src-block-regexp'." - (let* ((block-indentation (length (match-string 1))) - (lang (org-no-properties (match-string 2))) - (lang-headers (intern (concat "org-babel-default-header-args:" lang))) - (switches (match-string 3)) - (body (org-no-properties - (let* ((body (match-string 5)) - (sub-length (- (length body) 1))) - (if (and (> sub-length 0) - (string= "\n" (substring body sub-length))) - (substring body 0 sub-length) - (or body ""))))) - (preserve-indentation (or org-src-preserve-indentation - (save-match-data - (string-match "-i\\>" switches))))) - (list lang - ;; get block body less properties, protective commas, and indentation - (with-temp-buffer - (save-match-data - (insert (org-unescape-code-in-string body)) - (unless preserve-indentation (org-do-remove-indentation)) - (buffer-string))) - (apply #'org-babel-merge-params - org-babel-default-header-args - (when (boundp lang-headers) (eval lang-headers)) - (append - (org-babel-params-from-properties lang) - (list (org-babel-parse-header-arguments - (org-no-properties (or (match-string 4) "")))))) - switches - block-indentation))) - -(defun org-babel-parse-inline-src-block-match () - "Parse the results from a match of the `org-babel-inline-src-block-regexp'." - (let* ((lang (org-no-properties (match-string 2))) - (lang-headers (intern (concat "org-babel-default-header-args:" lang)))) - (list lang - (org-unescape-code-in-string (org-no-properties (match-string 5))) - (apply #'org-babel-merge-params - org-babel-default-inline-header-args - (if (boundp lang-headers) (eval lang-headers) nil) - (append - (org-babel-params-from-properties lang) - (list (org-babel-parse-header-arguments - (org-no-properties (or (match-string 4) ""))))))))) + "header-args" + 'inherit)) + (and lang ; language-specific header arguments at point of call + (org-babel-parse-header-arguments + (org-entry-get org-babel-current-src-block-location + (concat "header-args:" lang) + 'inherit)))))) (defun org-babel-balanced-split (string alts) "Split STRING on instances of ALTS. -ALTS is a cons of two character options where each option may be -either the numeric code of a single character or a list of -character alternatives. For example to split on balanced -instances of \"[ \t]:\" set ALTS to ((32 9) . 58)." - (let* ((matches (lambda (ch spec) (if (listp spec) (member ch spec) (equal spec ch)))) - (matched (lambda (ch last) - (if (consp alts) - (and (funcall matches ch (cdr alts)) - (funcall matches last (car alts))) - (funcall matches ch alts)))) - (balance 0) (last 0) - quote partial lst) - (mapc (lambda (ch) ; split on [], (), "" balanced instances of [ \t]: - (setq balance (+ balance - (cond ((or (equal 91 ch) (equal 40 ch)) 1) - ((or (equal 93 ch) (equal 41 ch)) -1) - (t 0)))) - (when (and (equal 34 ch) (not (equal 92 last))) - (setq quote (not quote))) - (setq partial (cons ch partial)) - (when (and (= balance 0) (not quote) (funcall matched ch last)) - (setq lst (cons (apply #'string (nreverse - (if (consp alts) - (cddr partial) - (cdr partial)))) - lst)) - (setq partial nil)) - (setq last ch)) - (string-to-list string)) - (nreverse (cons (apply #'string (nreverse partial)) lst)))) +ALTS is a character, or cons of two character options where each +option may be either the numeric code of a single character or +a list of character alternatives. For example, to split on +balanced instances of \"[ \t]:\", set ALTS to ((32 9) . 58)." + (with-temp-buffer + (insert string) + (goto-char (point-min)) + (let ((splitp (lambda (past next) + ;; Non-nil when there should be a split after NEXT + ;; character. PAST is the character before NEXT. + (pcase alts + (`(,(and first (pred consp)) . ,(and second (pred consp))) + (and (memq past first) (memq next second))) + (`(,first . ,(and second (pred consp))) + (and (eq past first) (memq next second))) + (`(,(and first (pred consp)) . ,second) + (and (memq past first) (eq next second))) + (`(,first . ,second) + (and (eq past first) (eq next second))) + ((pred (eq next)) t) + (_ nil)))) + (partial nil) + (result nil)) + (while (not (eobp)) + (cond + ((funcall splitp (char-before) (char-after)) + ;; There is a split after point. If ALTS is two-folds, + ;; remove last parsed character as it belongs to ALTS. + (when (consp alts) (pop partial)) + ;; Include elements parsed so far in RESULTS and flush + ;; partial parsing. + (when partial + (push (apply #'string (nreverse partial)) result) + (setq partial nil)) + (forward-char)) + ((memq (char-after) '(?\( ?\[)) + ;; Include everything between balanced brackets. + (let* ((origin (point)) + (after (char-after)) + (openings (list after))) + (forward-char) + (while (and openings (re-search-forward "[]()]" nil t)) + (pcase (char-before) + ((and match (or ?\[ ?\()) (push match openings)) + (?\] (when (eq ?\[ (car openings)) (pop openings))) + (_ (when (eq ?\( (car openings)) (pop openings))))) + (if (null openings) + (setq partial + (nconc (nreverse (string-to-list + (buffer-substring origin (point)))) + partial)) + ;; Un-balanced bracket. Backtrack. + (push after partial) + (goto-char (1+ origin))))) + ((and (eq ?\" (char-after)) (not (eq ?\\ (char-before)))) + ;; Include everything from current double quote to next + ;; non-escaped double quote. + (let ((origin (point))) + (if (re-search-forward "[^\\]\"" nil t) + (setq partial + (nconc (nreverse (string-to-list + (buffer-substring origin (point)))) + partial)) + ;; No closing double quote. Backtrack. + (push ?\" partial) + (forward-char)))) + (t (push (char-after) partial) + (forward-char)))) + ;; Add pending parsing and return result. + (when partial (push (apply #'string (nreverse partial)) result)) + (nreverse result)))) (defun org-babel-join-splits-near-ch (ch list) "Join splits where \"=\" is on either end of the split." (let ((last= (lambda (str) (= ch (aref str (1- (length str)))))) (first= (lambda (str) (= ch (aref str 0))))) (reverse - (org-reduce (lambda (acc el) + (cl-reduce (lambda (acc el) (let ((head (car acc))) (if (and head (or (funcall last= head) (funcall first= el))) (cons (concat head el) (cdr acc)) @@ -1474,7 +1547,7 @@ shown below. (let (results) (mapc (lambda (pair) (if (eq (car pair) :var) - (mapcar (lambda (v) (push (cons :var (org-babel-trim v)) results)) + (mapcar (lambda (v) (push (cons :var (org-trim v)) results)) (org-babel-join-splits-near-ch 61 (org-babel-balanced-split (cdr pair) 32))) (push pair results))) @@ -1484,48 +1557,52 @@ shown below. (defun org-babel-process-params (params) "Expand variables in PARAMS and add summary parameters." (let* ((processed-vars (mapcar (lambda (el) - (if (consp (cdr el)) - (cdr el) - (org-babel-ref-parse (cdr el)))) - (org-babel-get-header params :var))) - (vars-and-names (if (and (assoc :colname-names params) - (assoc :rowname-names params)) + (if (consp el) + el + (org-babel-ref-parse el))) + (org-babel--get-vars params))) + (vars-and-names (if (and (assq :colname-names params) + (assq :rowname-names params)) (list processed-vars) (org-babel-disassemble-tables processed-vars - (cdr (assoc :hlines params)) - (cdr (assoc :colnames params)) - (cdr (assoc :rownames params))))) - (raw-result (or (cdr (assoc :results params)) "")) - (result-params (append - (split-string (if (stringp raw-result) - raw-result - (eval raw-result))) - (cdr (assoc :result-params params))))) + (cdr (assq :hlines params)) + (cdr (assq :colnames params)) + (cdr (assq :rownames params))))) + (raw-result (or (cdr (assq :results params)) "")) + (result-params (delete-dups + (append + (split-string (if (stringp raw-result) + raw-result + (eval raw-result t))) + (cdr (assq :result-params params)))))) (append (mapcar (lambda (var) (cons :var var)) (car vars-and-names)) (list - (cons :colname-names (or (cdr (assoc :colname-names params)) + (cons :colname-names (or (cdr (assq :colname-names params)) (cadr vars-and-names))) - (cons :rowname-names (or (cdr (assoc :rowname-names params)) - (caddr vars-and-names))) + (cons :rowname-names (or (cdr (assq :rowname-names params)) + (cl-caddr vars-and-names))) (cons :result-params result-params) (cons :result-type (cond ((member "output" result-params) 'output) ((member "value" result-params) 'value) (t 'value)))) - (org-babel-get-header params :var 'other)))) + (cl-remove-if + (lambda (x) (memq (car x) '(:colname-names :rowname-names :result-params + :result-type :var))) + params)))) ;; row and column names (defun org-babel-del-hlines (table) "Remove all `hline's from TABLE." - (remove 'hline table)) + (remq 'hline table)) (defun org-babel-get-colnames (table) "Return the column names of TABLE. Return a cons cell, the `car' of which contains the TABLE less colnames, and the `cdr' of which contains a list of the column names." - (if (equal 'hline (nth 1 table)) + (if (eq 'hline (nth 1 table)) (cons (cddr table) (car table)) (cons (cdr table) (car table)))) @@ -1583,7 +1660,7 @@ of the vars, cnames and rnames." (lambda (var) (when (listp (cdr var)) (when (and (not (equal colnames "no")) - (or colnames (and (equal (nth 1 (cdr var)) 'hline) + (or colnames (and (eq (nth 1 (cdr var)) 'hline) (not (member 'hline (cddr (cdr var))))))) (let ((both (org-babel-get-colnames (cdr var)))) (setq cnames (cons (cons (car var) (cdr both)) @@ -1612,35 +1689,26 @@ to the table for reinsertion to org-mode." (org-babel-put-colnames table colnames) table)) table)) -(defun org-babel-where-is-src-block-head () +(defun org-babel-where-is-src-block-head (&optional src-block) "Find where the current source block begins. -Return the point at the beginning of the current source -block. Specifically at the beginning of the #+BEGIN_SRC line. + +If optional argument SRC-BLOCK is `src-block' type element, find +its current beginning instead. + +Return the point at the beginning of the current source block. +Specifically at the beginning of the #+BEGIN_SRC line. Also set +match-data relatively to `org-babel-src-block-regexp', which see. If the point is not on a source block then return nil." - (let ((initial (point)) (case-fold-search t) top bottom) - (or - (save-excursion ;; on a source name line or a #+header line - (beginning-of-line 1) - (and (or (looking-at org-babel-src-name-regexp) - (looking-at org-babel-multi-line-header-regexp)) - (progn - (while (and (forward-line 1) - (or (looking-at org-babel-src-name-regexp) - (looking-at org-babel-multi-line-header-regexp)))) - (looking-at org-babel-src-block-regexp)) - (point))) - (save-excursion ;; on a #+begin_src line - (beginning-of-line 1) - (and (looking-at org-babel-src-block-regexp) - (point))) - (save-excursion ;; inside a src block - (and - (re-search-backward "^[ \t]*#\\+begin_src" nil t) (setq top (point)) - (re-search-forward "^[ \t]*#\\+end_src" nil t) (setq bottom (point)) - (< top initial) (< initial bottom) - (progn (goto-char top) (beginning-of-line 1) - (looking-at org-babel-src-block-regexp)) - (point-marker)))))) + (let ((element (or src-block (org-element-at-point)))) + (when (eq (org-element-type element) 'src-block) + (let ((end (org-element-property :end element))) + (org-with-wide-buffer + ;; Ensure point is not on a blank line after the block. + (beginning-of-line) + (skip-chars-forward " \r\t\n" end) + (when (< (point) end) + (prog1 (goto-char (org-element-property :post-affiliated element)) + (looking-at org-babel-src-block-regexp)))))))) ;;;###autoload (defun org-babel-goto-src-block-head () @@ -1655,90 +1723,90 @@ If the point is not on a source block then return nil." (interactive (let ((completion-ignore-case t) (case-fold-search t) - (under-point (thing-at-point 'line))) - (list (org-icompleting-read - "source-block name: " (org-babel-src-block-names) nil t - (cond - ;; noweb - ((string-match (org-babel-noweb-wrap) under-point) - (let ((block-name (match-string 1 under-point))) - (string-match "[^(]*" block-name) - (match-string 0 block-name))) - ;; #+call: - ((string-match org-babel-lob-one-liner-regexp under-point) - (let ((source-info (car (org-babel-lob-get-info)))) - (if (string-match "^\\([^\\[]+?\\)\\(\\[.*\\]\\)?(" source-info) - (let ((source-name (match-string 1 source-info))) - source-name)))) - ;; #+results: - ((string-match (concat "#\\+" org-babel-results-keyword - "\\:\s+\\([^\\(]*\\)") under-point) - (match-string 1 under-point)) - ;; symbol-at-point - ((and (thing-at-point 'symbol)) - (org-babel-find-named-block (thing-at-point 'symbol)) - (thing-at-point 'symbol)) - ("")))))) + (all-block-names (org-babel-src-block-names))) + (list (completing-read + "source-block name: " all-block-names nil t + (let* ((context (org-element-context)) + (type (org-element-type context)) + (noweb-ref + (and (memq type '(inline-src-block src-block)) + (org-in-regexp (org-babel-noweb-wrap))))) + (cond + (noweb-ref + (buffer-substring + (+ (car noweb-ref) (length org-babel-noweb-wrap-start)) + (- (cdr noweb-ref) (length org-babel-noweb-wrap-end)))) + ((memq type '(babel-call inline-babel-call)) ;#+CALL: + (org-element-property :call context)) + ((car (org-element-property :results context))) ;#+RESULTS: + ((let ((symbol (thing-at-point 'symbol))) ;Symbol. + (and symbol + (member-ignore-case symbol all-block-names) + symbol))) + (t ""))))))) (let ((point (org-babel-find-named-block name))) (if point - ;; taken from `org-open-at-point' + ;; Taken from `org-open-at-point'. (progn (org-mark-ring-push) (goto-char point) (org-show-context)) (message "source-code block `%s' not found in this buffer" name)))) (defun org-babel-find-named-block (name) "Find a named source-code block. Return the location of the source block identified by source -NAME, or nil if no such block exists. Set match data according to -org-babel-named-src-block-regexp." +NAME, or nil if no such block exists. Set match data according +to `org-babel-named-src-block-regexp'." (save-excursion - (let ((case-fold-search t) - (regexp (org-babel-named-src-block-regexp-for-name name))) - (goto-char (point-min)) - (when (or (re-search-forward regexp nil t) - (re-search-backward regexp nil t)) - (match-beginning 0))))) + (goto-char (point-min)) + (let ((regexp (org-babel-named-src-block-regexp-for-name name))) + (or (and (looking-at regexp) + (progn (goto-char (match-beginning 1)) + (line-beginning-position))) + (ignore-errors (org-next-block 1 nil regexp)))))) (defun org-babel-src-block-names (&optional file) "Returns the names of source blocks in FILE or the current buffer." - (save-excursion - (when file (find-file file)) (goto-char (point-min)) - (let ((case-fold-search t) names) - (while (re-search-forward org-babel-src-name-w-name-regexp nil t) - (setq names (cons (match-string 3) names))) - names))) + (with-current-buffer (if file (find-file-noselect file) (current-buffer)) + (org-with-point-at 1 + (let ((regexp "^[ \t]*#\\+begin_src ") + (case-fold-search t) + (names nil)) + (while (re-search-forward regexp nil t) + (let ((element (org-element-at-point))) + (when (eq 'src-block (org-element-type element)) + (let ((name (org-element-property :name element))) + (when name (push name names)))))) + names)))) ;;;###autoload (defun org-babel-goto-named-result (name) "Go to a named result." (interactive (let ((completion-ignore-case t)) - (list (org-icompleting-read "source-block name: " - (org-babel-result-names) nil t)))) + (list (completing-read "Source-block name: " + (org-babel-result-names) nil t)))) (let ((point (org-babel-find-named-result name))) (if point ;; taken from `org-open-at-point' (progn (goto-char point) (org-show-context)) (message "result `%s' not found in this buffer" name)))) -(defun org-babel-find-named-result (name &optional point) +(defun org-babel-find-named-result (name) "Find a named result. Return the location of the result named NAME in the current buffer or nil if no such result exists." (save-excursion - (let ((case-fold-search t)) - (goto-char (or point (point-min))) - (catch 'is-a-code-block - (when (re-search-forward - (concat org-babel-result-regexp - "[ \t]" (regexp-quote name) "[ \t]*[\n\f\v\r]") - nil t) - (when (and (string= "name" (downcase (match-string 1))) - (or (beginning-of-line 1) - (looking-at org-babel-src-block-regexp) - (looking-at org-babel-multi-line-header-regexp) - (looking-at org-babel-lob-one-liner-regexp))) - (throw 'is-a-code-block (org-babel-find-named-result name (point)))) - (beginning-of-line 0) (point)))))) + (goto-char (point-min)) + (let ((case-fold-search t) + (re (format "^[ \t]*#\\+%s.*?:[ \t]*%s[ \t]*$" + org-babel-results-keyword + (regexp-quote name)))) + (catch :found + (while (re-search-forward re nil t) + (let ((element (org-element-at-point))) + (when (or (eq (org-element-type element) 'keyword) + (< (point) + (org-element-property :post-affiliated element))) + (throw :found (line-beginning-position))))))))) (defun org-babel-result-names (&optional file) "Returns the names of results in FILE or the current buffer." @@ -1746,7 +1814,7 @@ buffer or nil if no such result exists." (when file (find-file file)) (goto-char (point-min)) (let ((case-fold-search t) names) (while (re-search-forward org-babel-result-w-name-regexp nil t) - (setq names (cons (match-string 4) names))) + (setq names (cons (match-string-no-properties 9) names))) names))) ;;;###autoload @@ -1784,26 +1852,31 @@ split. When called from outside of a code block a new code block is created. In both cases if the region is demarcated and if the region is not active then the point is demarcated." (interactive "P") - (let ((info (org-babel-get-src-block-info 'light)) - (headers (progn (org-babel-where-is-src-block-head) - (match-string 4))) - (stars (concat (make-string (or (org-current-level) 1) ?*) " "))) + (let* ((info (org-babel-get-src-block-info 'light)) + (start (org-babel-where-is-src-block-head)) + (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 + (let (case-fold-search) + (string-match-p "#\\+begin_src" block))))) (if info (mapc (lambda (place) (save-excursion (goto-char place) (let ((lang (nth 0 info)) - (indent (make-string (nth 5 info) ? ))) + (indent (make-string (org-get-indentation) ?\s))) (when (string-match "^[[:space:]]*$" (buffer-substring (point-at-bol) (point-at-eol))) (delete-region (point-at-bol) (point-at-eol))) (insert (concat (if (looking-at "^") "" "\n") - indent "#+end_src\n" + indent (funcall (if lower-case-p 'downcase 'upcase) "#+end_src\n") (if arg stars indent) "\n" - indent "#+begin_src " lang + indent (funcall (if lower-case-p 'downcase 'upcase) "#+begin_src ") + lang (if (> (length headers) 1) (concat " " headers) headers) (if (looking-at "[\n\r]") @@ -1812,7 +1885,7 @@ region is not active then the point is demarcated." (move-end-of-line 2)) (sort (if (org-region-active-p) (list (mark) (point)) (list (point))) #'>)) (let ((start (point)) - (lang (org-icompleting-read + (lang (completing-read "Lang: " (mapcar #'symbol-name (delete-dups @@ -1823,134 +1896,222 @@ 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") "") - "#+begin_src " lang "\n" + (funcall (if lower-case-p 'downcase 'upcase) "#+begin_src ") + lang "\n" body (if (or (= (length body) 0) - (string-match "[\r\n]$" body)) "" "\n") - "#+end_src\n")) + (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))))) -(defvar org-babel-lob-one-liner-regexp) -(defun org-babel-where-is-src-block-result (&optional insert info hash indent) +(defun org-babel--insert-results-keyword (name hash) + "Insert RESULTS keyword with NAME value at point. +If NAME is nil, results are anonymous. HASH is a string used as +the results hash, or nil. Leave point before the keyword." + (save-excursion (insert "\n")) ;open line to indent. + (org-indent-line) + (delete-char 1) + (insert (concat "#+" org-babel-results-keyword + (cond ((not hash) nil) + (org-babel-hash-show-time + (format "[%s %s]" + (format-time-string "<%F %T>") + hash)) + (t (format "[%s]" hash))) + ":" + (when name (concat " " name)) + "\n")) + ;; Make sure results are going to be followed by at least one blank + ;; line so they do not get merged with the next element, e.g., + ;; + ;; #+results: + ;; : 1 + ;; + ;; : fixed-width area, unrelated to the above. + (unless (looking-at "^[ \t]*$") (save-excursion (insert "\n"))) + (beginning-of-line 0) + (when hash (org-babel-hide-hash))) + +(defun org-babel--clear-results-maybe (hash) + "Clear results when hash doesn't match HASH. + +When results hash does not match HASH, remove RESULTS keyword at +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) + (unless (string= (match-string 1) hash) + (let* ((e (org-element-at-point)) + (post (copy-marker (org-element-property :post-affiliated e)))) + ;; Delete contents. + (delete-region post + (save-excursion + (goto-char (org-element-property :end e)) + (skip-chars-backward " \t\n") + (line-beginning-position 2))) + ;; Delete RESULT keyword. However, if RESULTS keyword is + ;; orphaned, ignore this part. The deletion above already + ;; took care of it. + (unless (= (point) post) + (delete-region (line-beginning-position) + (line-beginning-position 2))) + (goto-char post) + (set-marker post nil) + t)))) + +(defun org-babel-where-is-src-block-result (&optional insert _info hash) "Find where the current source block results begin. + Return the point at the beginning of the result of the current -source block. Specifically at the beginning of the results line. -If no result exists for this block then create a results line -following the source block." - (save-excursion - (let* ((case-fold-search t) - (on-lob-line (save-excursion - (beginning-of-line 1) - (looking-at org-babel-lob-one-liner-regexp))) - (inlinep (when (org-babel-get-inline-src-block-matches) - (match-end 0))) - (name (nth 4 (or info (org-babel-get-src-block-info 'light)))) - (head (unless on-lob-line (org-babel-where-is-src-block-head))) - found beg end) - (when head (goto-char head)) +source block, specifically at the beginning of the results line. + +If no result exists for this block return nil, unless optional +argument INSERT is non-nil. In this case, create a results line +following the source block and return the position at its +beginning. In the case of inline code, remove the results part +instead. + +If optional argument HASH is a string, remove contents related to +RESULTS keyword if its hash is different. Then update the latter +to HASH." + (let ((context (org-element-context))) + (catch :found (org-with-wide-buffer - (setq - found ;; was there a result (before we potentially insert one) - (or - inlinep - (and - ;; named results: - ;; - return t if it is found, else return nil - ;; - if it does not need to be rebuilt, then don't set end - ;; - if it does need to be rebuilt then do set end - name (setq beg (org-babel-find-named-result name)) - (prog1 beg - (when (and hash (not (string= hash (match-string 5)))) - (goto-char beg) (setq end beg) ;; beginning of result - (forward-line 1) - (delete-region end (org-babel-result-end)) nil))) - (and - ;; unnamed results: - ;; - return t if it is found, else return nil - ;; - if it is found, and the hash doesn't match, delete and set end - (or on-lob-line (re-search-forward "^[ \t]*#\\+end_src" nil t)) - (progn (end-of-line 1) - (if (eobp) (insert "\n") (forward-char 1)) - (setq end (point)) - (or (and - (not name) - (progn ;; unnamed results line already exists - (catch 'non-comment - (while (re-search-forward "[^ \f\t\n\r\v]" nil t) - (beginning-of-line 1) - (cond - ((looking-at (concat org-babel-result-regexp "\n")) - (throw 'non-comment t)) - ((looking-at "^[ \t]*#") (end-of-line 1)) - (t (throw 'non-comment nil)))))) - (let ((this-hash (match-string 5))) - (prog1 (point) - ;; must remove and rebuild if hash!=old-hash - (if (and hash (not (string= hash this-hash))) - (prog1 nil - (forward-line 1) - (delete-region - end (org-babel-result-end))) - (setq end nil))))))))))) - (if (not (and insert end)) found - (goto-char end) - (unless beg - (if (looking-at "[\n\r]") (forward-char 1) (insert "\n"))) - (insert (concat - (when (wholenump indent) (make-string indent ? )) - "#+" org-babel-results-keyword - (when hash - (if org-babel-hash-show-time - (concat - "["(format-time-string "<%Y-%m-%d %H:%M:%S>")" "hash"]") - (concat "["hash"]"))) - ":" - (when name (concat " " name)) "\n")) - (unless beg (insert "\n") (backward-char)) - (beginning-of-line 0) - (if hash (org-babel-hide-hash)) - (point))))) - -(defvar org-block-regexp) + (pcase (org-element-type context) + ((or `inline-babel-call `inline-src-block) + ;; Results for inline objects are located right after them. + ;; There is no RESULTS line to insert either. + (let ((limit (org-element-property + :contents-end (org-element-property :parent context)))) + (goto-char (org-element-property :end context)) + (skip-chars-forward " \t\n" limit) + (throw :found + (and + (< (point) limit) + (let ((result (org-element-context))) + (and (eq (org-element-type result) 'macro) + (string= (org-element-property :key result) + "results") + (if (not insert) (point) + (delete-region + (point) + (progn + (goto-char (org-element-property :end result)) + (skip-chars-backward " \t") + (point))) + (point)))))))) + ((or `babel-call `src-block) + (let* ((name (org-element-property :name context)) + (named-results (and name (org-babel-find-named-result name)))) + (goto-char (or named-results (org-element-property :end context))) + (cond + ;; Existing results named after the current source. + (named-results + (when (org-babel--clear-results-maybe hash) + (org-babel--insert-results-keyword name hash)) + (throw :found (point))) + ;; Named results expect but none to be found. + (name) + ;; No possible anonymous results at the very end of + ;; buffer or outside CONTEXT parent. + ((eq (point) + (or (org-element-property + :contents-end (org-element-property :parent context)) + (point-max)))) + ;; Check if next element is an anonymous result below + ;; the current block. + ((let* ((next (org-element-at-point)) + (end (save-excursion + (goto-char + (org-element-property :post-affiliated next)) + (line-end-position))) + (empty-result-re (concat org-babel-result-regexp "$")) + (case-fold-search t)) + (re-search-forward empty-result-re end t)) + (beginning-of-line) + (when (org-babel--clear-results-maybe hash) + (org-babel--insert-results-keyword nil hash)) + (throw :found (point)))))) + ;; Ignore other elements. + (_ (throw :found nil)))) + ;; No result found. Insert a RESULTS keyword below element, if + ;; appropriate. In this case, ensure there is an empty line + ;; after the previous element. + (when insert + (save-excursion + (goto-char (min (org-element-property :end context) (point-max))) + (skip-chars-backward " \t\n") + (forward-line) + (unless (bolp) (insert "\n")) + (insert "\n") + (org-babel--insert-results-keyword + (org-element-property :name context) hash) + (point)))))) + +(defun org-babel-read-element (element) + "Read ELEMENT into emacs-lisp. +Return nil if ELEMENT cannot be read." + (org-with-wide-buffer + (goto-char (org-element-property :post-affiliated element)) + (pcase (org-element-type element) + (`fixed-width + (let ((v (org-trim (org-element-property :value element)))) + (or (org-babel--string-to-number v) v))) + (`table (org-babel-read-table)) + (`plain-list (org-babel-read-list)) + (`example-block + (let ((v (org-element-property :value element))) + (if (or org-src-preserve-indentation + (org-element-property :preserve-indent element)) + v + (org-remove-indentation v)))) + (`export-block + (org-remove-indentation (org-element-property :value element))) + (`paragraph + ;; Treat paragraphs containing a single link specially. + (skip-chars-forward " \t") + (if (and (looking-at org-bracket-link-regexp) + (save-excursion + (goto-char (match-end 0)) + (skip-chars-forward " \r\t\n") + (<= (org-element-property :end element) + (point)))) + (org-babel-read-link) + (buffer-substring-no-properties + (org-element-property :contents-begin element) + (org-element-property :contents-end element)))) + ((or `center-block `quote-block `verse-block `special-block) + (org-remove-indentation + (buffer-substring-no-properties + (org-element-property :contents-begin element) + (org-element-property :contents-end element)))) + (_ nil)))) + (defun org-babel-read-result () - "Read the result at `point' into emacs-lisp." - (let ((case-fold-search t) result-string) - (cond - ((org-at-table-p) (org-babel-read-table)) - ((org-at-item-p) (org-babel-read-list)) - ((looking-at org-bracket-link-regexp) (org-babel-read-link)) - ((looking-at org-block-regexp) (org-babel-trim (match-string 4))) - ((or (looking-at "^[ \t]*: ") (looking-at "^[ \t]*:$")) - (setq result-string - (org-babel-trim - (mapconcat (lambda (line) - (or (and (> (length line) 1) - (string-match "^[ \t]*: ?\\(.+\\)" line) - (match-string 1 line)) - "")) - (split-string - (buffer-substring - (point) (org-babel-result-end)) "[\r\n]+") - "\n"))) - (or (org-babel-number-p result-string) result-string)) - ((looking-at org-babel-result-regexp) - (save-excursion (forward-line 1) (org-babel-read-result)))))) + "Read the result at point into emacs-lisp." + (and (not (save-excursion + (beginning-of-line) + (looking-at-p "[ \t]*$"))) + (org-babel-read-element (org-element-at-point)))) (defun org-babel-read-table () - "Read the table at `point' into emacs-lisp." + "Read the table at point into emacs-lisp." (mapcar (lambda (row) (if (and (symbolp row) (equal row 'hline)) row (mapcar (lambda (el) (org-babel-read el 'inhibit-lisp-eval)) row))) (org-table-to-lisp))) (defun org-babel-read-list () - "Read the list at `point' into emacs-lisp." + "Read the list at point into emacs-lisp." (mapcar (lambda (el) (org-babel-read el 'inhibit-lisp-eval)) - (mapcar #'cadr (cdr (org-list-parse-list))))) + (cdr (org-list-to-lisp)))) (defvar org-link-types-re) (defun org-babel-read-link () - "Read the link at `point' into emacs-lisp. + "Read the link at point into emacs-lisp. If the path of the link is a file path it is expanded using `expand-file-name'." (let* ((case-fold-search t) @@ -1975,225 +2136,344 @@ If the path of the link is a file path it is expanded using ;; scalar result (funcall echo-res result)))) -(defun org-babel-insert-result - (result &optional result-params info hash indent lang) +(defun org-babel-insert-result (result &optional result-params info hash lang) "Insert RESULT into the current buffer. -By default RESULT is inserted after the end of the -current source block. With optional argument RESULT-PARAMS -controls insertion of results in the org-mode file. -RESULT-PARAMS can take the following values: + +By default RESULT is inserted after the end of the current source +block. The RESULT of an inline source block usually will be +wrapped inside a `results' macro and placed on the same line as +the inline source block. The macro is stripped upon export. +Multiline and non-scalar RESULTS from inline source blocks are +not allowed. With optional argument RESULT-PARAMS controls +insertion of results in the Org mode file. RESULT-PARAMS can +take the following values: replace - (default option) insert results after the source block - replacing any previously inserted results + or inline source block replacing any previously + inserted results. -silent -- no results are inserted into the Org-mode buffer but +silent -- no results are inserted into the Org buffer but the results are echoed to the minibuffer and are ingested by Emacs (a potentially time consuming - process) + process). file ---- the results are interpreted as a file path, and are - inserted into the buffer using the Org-mode file syntax + inserted into the buffer using the Org file syntax. -list ---- the results are interpreted as an Org-mode list. +list ---- the results are interpreted as an Org list. -raw ----- results are added directly to the Org-mode file. This - is a good option if you code block will output org-mode +raw ----- results are added directly to the Org file. This is + a good option if you code block will output Org formatted text. -drawer -- results are added directly to the Org-mode file as with - \"raw\", but are wrapped in a RESULTS drawer, allowing - them to later be replaced or removed automatically. +drawer -- results are added directly to the Org file as with + \"raw\", but are wrapped in a RESULTS drawer or results + macro, allowing them to later be replaced or removed + automatically. -org ----- results are added inside of a \"#+BEGIN_SRC org\" block. - They are not comma-escaped when inserted, but Org syntax - here will be discarded when exporting the file. +org ----- results are added inside of a \"src_org{}\" or \"#+BEGIN_SRC + org\" block depending on whether the current source block is + inline or not. They are not comma-escaped when inserted, + but Org syntax here will be discarded when exporting the + file. -html ---- results are added inside of a #+BEGIN_HTML block. This - is a good option if you code block will output html - formatted text. +html ---- results are added inside of a #+BEGIN_EXPORT HTML block + or html export snippet depending on whether the current + source block is inline or not. This is a good option + if your code block will output html formatted text. -latex --- results are added inside of a #+BEGIN_LATEX block. - This is a good option if you code block will output - latex formatted text. +latex --- results are added inside of a #+BEGIN_EXPORT LATEX + block or latex export snippet depending on whether the + current source block is inline or not. This is a good + option if your code block will output latex formatted + text. code ---- the results are extracted in the syntax of the source code of the language being evaluated and are added - inside of a #+BEGIN_SRC block with the source-code - language set appropriately. Note this relies on the - optional LANG argument." - (if (stringp result) - (progn - (setq result (org-no-properties result)) - (when (member "file" result-params) - (setq result (org-babel-result-to-file - result (when (assoc :file-desc (nth 2 info)) - (or (cdr (assoc :file-desc (nth 2 info))) - result)))))) - (unless (listp result) (setq result (format "%S" result)))) + inside of a source block with the source-code language + set appropriately. Also, source block inlining is + preserved in this case. Note this relies on the + optional LANG argument. + +list ---- the results are rendered as a list. This option not + allowed for inline src blocks. + +table --- the results are rendered as a table. This option not + allowed for inline src blocks. + +INFO may provide the values of these header arguments (in the +`header-arguments-alist' see the docstring for +`org-babel-get-src-block-info'): + +:file --- the name of the file to which output should be written. + +:wrap --- the effect is similar to `latex' in RESULT-PARAMS but + using the argument supplied to specify the export block + or snippet type." + (cond ((stringp result) + (setq result (org-no-properties result)) + (when (member "file" result-params) + (setq result (org-babel-result-to-file + result (when (assq :file-desc (nth 2 info)) + (or (cdr (assq :file-desc (nth 2 info))) + result)))))) + ((listp result)) + (t (setq result (format "%S" result)))) (if (and result-params (member "silent" result-params)) - (progn - (message (replace-regexp-in-string "%" "%%" (format "%S" result))) - result) - (save-excursion - (let* ((inlinep - (save-excursion - (when (or (org-babel-get-inline-src-block-matches) - (org-babel-get-lob-one-liner-matches)) - (goto-char (match-end 0)) - (insert (if (listp result) "\n" " ")) - (point)))) - (existing-result (unless inlinep - (org-babel-where-is-src-block-result - t info hash indent))) - (results-switches - (cdr (assoc :results_switches (nth 2 info)))) - (visible-beg (point-min-marker)) - (visible-end (point-max-marker)) - ;; When results exist outside of the current visible - ;; region of the buffer, be sure to widen buffer to - ;; update them. - (outside-scope-p (and existing-result + (progn (message (replace-regexp-in-string "%" "%%" (format "%S" result))) + result) + (let ((inline (let ((context (org-element-context))) + (and (memq (org-element-type context) + '(inline-babel-call inline-src-block)) + context)))) + (when inline + (let ((warning + (or (and (member "table" result-params) "`:results table'") + (and (listp result) "list result") + (and (string-match-p "\n." result) "multiline result") + (and (member "list" result-params) "`:results list'")))) + (when warning + (user-error "Inline error: %s cannot be used" warning)))) + (save-excursion + (let* ((visible-beg (point-min-marker)) + (visible-end (copy-marker (point-max) t)) + (inline (let ((context (org-element-context))) + (and (memq (org-element-type context) + '(inline-babel-call inline-src-block)) + context))) + (existing-result (org-babel-where-is-src-block-result t nil hash)) + (results-switches (cdr (assq :results_switches (nth 2 info)))) + ;; When results exist outside of the current visible + ;; region of the buffer, be sure to widen buffer to + ;; update them. + (outside-scope (and existing-result + (buffer-narrowed-p) (or (> visible-beg existing-result) (<= visible-end existing-result)))) - beg end) - (when (and (stringp result) ; ensure results end in a newline - (not inlinep) - (> (length result) 0) - (not (or (string-equal (substring result -1) "\n") - (string-equal (substring result -1) "\r")))) - (setq result (concat result "\n"))) - (unwind-protect - (progn - (when outside-scope-p (widen)) - (if (not existing-result) - (setq beg (or inlinep (point))) - (goto-char existing-result) - (save-excursion - (re-search-forward "#" nil t) - (setq indent (- (current-column) 1))) - (forward-line 1) + beg end indent) + ;; Ensure non-inline results end in a newline. + (when (and (org-string-nw-p result) + (not inline) + (not (string-equal (substring result -1) "\n"))) + (setq result (concat result "\n"))) + (unwind-protect + (progn + (when outside-scope (widen)) + (if existing-result (goto-char existing-result) + (goto-char (org-element-property :end inline)) + (skip-chars-backward " \t")) + (unless inline + (setq indent (org-get-indentation)) + (forward-line 1)) (setq beg (point)) (cond + (inline + ;; Make sure new results are separated from the + ;; source code by one space. + (unless existing-result + (insert " ") + (setq beg (point)))) ((member "replace" result-params) (delete-region (point) (org-babel-result-end))) ((member "append" result-params) (goto-char (org-babel-result-end)) (setq beg (point-marker))) - ((member "prepend" result-params)))) ; already there - (setq results-switches - (if results-switches (concat " " results-switches) "")) - (let ((wrap (lambda (start finish &optional no-escape) - (goto-char end) (insert (concat finish "\n")) - (goto-char beg) (insert (concat start "\n")) - (unless no-escape - (org-escape-code-in-region (min (point) end) end)) - (goto-char end) (goto-char (point-at-eol)) - (setq end (point-marker)))) - (proper-list-p (lambda (it) (and (listp it) (null (cdr (last it))))))) - ;; insert results based on type - (cond - ;; do nothing for an empty result - ((null result)) - ;; insert a list if preferred - ((member "list" result-params) - (insert - (org-babel-trim - (org-list-to-generic - (cons 'unordered - (mapcar - (lambda (el) (list nil (if (stringp el) el (format "%S" el)))) - (if (listp result) result (split-string result "\n" t)))) - '(:splicep nil :istart "- " :iend "\n"))) - "\n")) - ;; assume the result is a table if it's not a string - ((funcall proper-list-p result) - (goto-char beg) - (insert (concat (orgtbl-to-orgtbl - (if (org-every - (lambda (el) (or (listp el) (eq el 'hline))) - result) - result (list result)) - '(:fmt (lambda (cell) (format "%s" cell)))) "\n")) - (goto-char beg) (when (org-at-table-p) (org-table-align))) - ((and (listp result) (not (funcall proper-list-p result))) - (insert (format "%s\n" result))) - ((member "file" result-params) - (when inlinep (goto-char inlinep)) - (insert result)) - (t (goto-char beg) (insert result))) - (when (funcall proper-list-p result) (goto-char (org-table-end))) - (setq end (point-marker)) - ;; possibly wrap result - (cond - ((assoc :wrap (nth 2 info)) - (let ((name (or (cdr (assoc :wrap (nth 2 info))) "RESULTS"))) - (funcall wrap (concat "#+BEGIN_" name) - (concat "#+END_" (car (org-split-string name)))))) - ((member "html" result-params) - (funcall wrap "#+BEGIN_HTML" "#+END_HTML")) - ((member "latex" result-params) - (funcall wrap "#+BEGIN_LaTeX" "#+END_LaTeX")) - ((member "org" result-params) - (goto-char beg) (if (org-at-table-p) (org-cycle)) - (funcall wrap "#+BEGIN_SRC org" "#+END_SRC")) - ((member "code" result-params) - (funcall wrap (format "#+BEGIN_SRC %s%s" (or lang "none") results-switches) - "#+END_SRC")) - ((member "raw" result-params) - (goto-char beg) (if (org-at-table-p) (org-cycle))) - ((or (member "drawer" result-params) - ;; Stay backward compatible with <7.9.2 - (member "wrap" result-params)) - (goto-char beg) (if (org-at-table-p) (org-cycle)) - (funcall wrap ":RESULTS:" ":END:" 'no-escape)) - ((and (not (funcall proper-list-p result)) - (not (member "file" result-params))) - (org-babel-examplize-region beg end results-switches) - (setq end (point))))) - ;; possibly indent the results to match the #+results line - (when (and (not inlinep) (numberp indent) indent (> indent 0) - ;; in this case `table-align' does the work for us - (not (and (listp result) - (member "append" result-params)))) - (indent-rigidly beg end indent)) - (if (null result) - (if (member "value" result-params) - (message "Code block returned no value.") - (message "Code block produced no output.")) - (message "Code block evaluation complete."))) - (when outside-scope-p (narrow-to-region visible-beg visible-end)) - (set-marker visible-beg nil) - (set-marker visible-end nil)))))) - -(defun org-babel-remove-result (&optional info) + ((member "prepend" result-params))) ; already there + (setq results-switches + (if results-switches (concat " " results-switches) "")) + (let ((wrap + (lambda (start finish &optional no-escape no-newlines + inline-start inline-finish) + (when inline + (setq start inline-start) + (setq finish inline-finish) + (setq no-newlines t)) + (let ((before-finish (marker-position end))) + (goto-char end) + (insert (concat finish (unless no-newlines "\n"))) + (goto-char beg) + (insert (concat start (unless no-newlines "\n"))) + (unless no-escape + (org-escape-code-in-region + (min (point) before-finish) before-finish)) + (goto-char end)))) + (tabulablep + (lambda (r) + ;; Non-nil when result R can be turned into + ;; a table. + (and (listp r) + (null (cdr (last r))) + (cl-every + (lambda (e) (or (atom e) (null (cdr (last e))))) + result))))) + ;; insert results based on type + (cond + ;; Do nothing for an empty result. + ((null result)) + ;; Insert a list if preferred. + ((member "list" result-params) + (insert + (org-trim + (org-list-to-generic + (cons 'unordered + (mapcar + (lambda (e) + (list (if (stringp e) e (format "%S" e)))) + (if (listp result) result + (split-string result "\n" t)))) + '(:splicep nil :istart "- " :iend "\n"))) + "\n")) + ;; Try hard to print RESULT as a table. Give up if + ;; it contains an improper list. + ((funcall tabulablep result) + (goto-char beg) + (insert (concat (orgtbl-to-orgtbl + (if (cl-every + (lambda (e) + (or (eq e 'hline) (listp e))) + result) + result + (list result)) + nil) + "\n")) + (goto-char beg) + (when (org-at-table-p) (org-table-align)) + (goto-char (org-table-end))) + ;; Print verbatim a list that cannot be turned into + ;; a table. + ((listp result) (insert (format "%s\n" result))) + ((member "file" result-params) + (when inline + (setq result (org-macro-escape-arguments result))) + (insert result)) + ((and inline (not (member "raw" result-params))) + (insert (org-macro-escape-arguments + (org-babel-chomp result "\n")))) + (t (goto-char beg) (insert result))) + (setq end (copy-marker (point) t)) + ;; 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 ":") "@@)}}}"))) + ((member "html" result-params) + (funcall wrap "#+BEGIN_EXPORT html" "#+END_EXPORT" nil nil + "{{{results(@@html:" "@@)}}}")) + ((member "latex" result-params) + (funcall wrap "#+BEGIN_EXPORT latex" "#+END_EXPORT" nil nil + "{{{results(@@latex:" "@@)}}}")) + ((member "org" result-params) + (goto-char beg) (when (org-at-table-p) (org-cycle)) + (funcall wrap "#+BEGIN_SRC org" "#+END_SRC" nil nil + "{{{results(src_org{" "})}}}")) + ((member "code" result-params) + (let ((lang (or lang "none"))) + (funcall wrap (format "#+BEGIN_SRC %s%s" lang results-switches) + "#+END_SRC" nil nil + (format "{{{results(src_%s[%s]{" lang results-switches) + "})}}}"))) + ((member "raw" result-params) + (goto-char beg) (when (org-at-table-p) (org-cycle))) + ((or (member "drawer" result-params) + ;; Stay backward compatible with <7.9.2 + (member "wrap" result-params)) + (goto-char beg) (when (org-at-table-p) (org-cycle)) + (funcall wrap ":RESULTS:" ":END:" 'no-escape nil + "{{{results(" ")}}}")) + ((and inline (member "file" result-params)) + (funcall wrap nil nil nil nil "{{{results(" ")}}}")) + ((and (not (funcall tabulablep result)) + (not (member "file" result-params))) + (let ((org-babel-inline-result-wrap + ;; Hard code {{{results(...)}}} on top of + ;; customization. + (format "{{{results(%s)}}}" + org-babel-inline-result-wrap))) + (org-babel-examplify-region + beg end results-switches inline))))) + ;; Possibly indent results in par with #+results line. + (when (and (not inline) (numberp indent) (> indent 0) + ;; In this case `table-align' does the work + ;; for us. + (not (and (listp result) + (member "append" result-params)))) + (indent-rigidly beg end indent)) + (if (null result) + (if (member "value" result-params) + (message "Code block returned no value.") + (message "Code block produced no output.")) + (message "Code block evaluation complete."))) + (set-marker end nil) + (when outside-scope (narrow-to-region visible-beg visible-end)) + (set-marker visible-beg nil) + (set-marker visible-end nil))))))) + +(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)) start) + (let ((location (org-babel-where-is-src-block-result nil info))) (when location - (setq start (- location 1)) (save-excursion - (goto-char location) (forward-line 1) - (delete-region start (org-babel-result-end)))))) + (goto-char location) + (when (looking-at (concat org-babel-result-regexp ".*$")) + (delete-region + (if keep-keyword (line-beginning-position 2) + (save-excursion + (skip-chars-backward " \r\t\n") + (line-beginning-position 2))) + (progn (forward-line) (org-babel-result-end)))))))) + +(defun org-babel-remove-inline-result (&optional datum) + "Remove the result of the current inline-src-block or babel call. +The result must be wrapped in a `results' macro to be removed. +Leading white space is trimmed." + (interactive) + (let* ((el (or datum (org-element-context)))) + (when (memq (org-element-type el) '(inline-src-block inline-babel-call)) + (org-with-wide-buffer + (goto-char (org-element-property :end el)) + (skip-chars-backward " \t") + (let ((result (save-excursion + (skip-chars-forward + " \t\n" + (org-element-property + :contents-end (org-element-property :parent el))) + (org-element-context)))) + (when (and (eq (org-element-type result) 'macro) + (string= (org-element-property :key result) "results")) + (delete-region ; And leading whitespace. + (point) + (progn (goto-char (org-element-property :end result)) + (skip-chars-backward " \t\n") + (point))))))))) + +(defun org-babel-remove-result-one-or-many (x) + "Remove the result of the current source block. +If called with a prefix argument, remove all result blocks +in the buffer." + (interactive "P") + (if x + (org-babel-map-src-blocks nil (org-babel-remove-result)) + (org-babel-remove-result))) (defun org-babel-result-end () "Return the point at the end of the current set of results." - (save-excursion - (cond - ((org-at-table-p) (progn (goto-char (org-table-end)) (point))) - ((org-at-item-p) (let* ((struct (org-list-struct)) - (prvs (org-list-prevs-alist struct))) - (org-list-get-list-end (point-at-bol) struct prvs))) - ((let ((case-fold-search t)) (looking-at "^\\([ \t]*\\):results:")) - (progn (re-search-forward (concat "^" (match-string 1) ":END:")) - (forward-char 1) (point))) - (t - (let ((case-fold-search t)) - (if (looking-at (concat "[ \t]*#\\+begin_\\([^ \t\n\r]+\\)")) - (progn (re-search-forward (concat "[ \t]*#\\+end_" (match-string 1)) - nil t) - (forward-char 1)) - (while (looking-at "[ \t]*\\(: \\|:$\\|\\[\\[\\)") - (forward-line 1)))) - (point))))) + (cond ((looking-at-p "^[ \t]*$") (point)) ;no result + ((looking-at-p (format "^[ \t]*%s[ \t]*$" org-bracket-link-regexp)) + (line-beginning-position 2)) + (t + (let ((element (org-element-at-point))) + (if (memq (org-element-type element) + ;; Possible results types. + '(drawer example-block export-block fixed-width item + plain-list src-block table)) + (save-excursion + (goto-char (min (point-max) ;for narrowed buffers + (org-element-property :end element))) + (skip-chars-backward " \r\t\n") + (line-beginning-position 2)) + (point)))))) (defun org-babel-result-to-file (result &optional description) "Convert RESULT into an `org-mode' link with optional DESCRIPTION. @@ -2210,29 +2490,23 @@ file's directory then expand relative links." result) (if description (concat "[" description "]") "")))) -(defvar org-babel-capitalize-examplize-region-markers nil - "Make true to capitalize begin/end example markers inserted by code blocks.") - -(defun org-babel-examplize-region (beg end &optional results-switches) +(defun org-babel-examplify-region (beg end &optional results-switches inline) "Comment out region using the inline `==' or `: ' org example quote." (interactive "*r") - (let ((chars-between (lambda (b e) - (not (string-match "^[\\s]*$" (buffer-substring b e))))) - (maybe-cap (lambda (str) (if org-babel-capitalize-examplize-region-markers - (upcase str) str)))) - (if (or (funcall chars-between (save-excursion (goto-char beg) (point-at-bol)) beg) - (funcall chars-between end (save-excursion (goto-char end) (point-at-eol)))) + (let ((maybe-cap + (lambda (str) + (if org-babel-uppercase-example-markers (upcase str) str)))) + (if inline (save-excursion (goto-char beg) (insert (format org-babel-inline-result-wrap - (prog1 (buffer-substring beg end) - (delete-region beg end))))) + (delete-and-extract-region beg end)))) (let ((size (count-lines beg end))) (save-excursion (cond ((= size 0)) ; do nothing for an empty result ((< size org-babel-min-lines-for-block-output) (goto-char beg) - (dotimes (n size) + (dotimes (_ size) (beginning-of-line 1) (insert ": ") (forward-line 1))) (t (goto-char beg) @@ -2241,16 +2515,37 @@ file's directory then expand relative links." (funcall maybe-cap "#+begin_example") results-switches) (funcall maybe-cap "#+begin_example\n"))) - (if (markerp end) (goto-char end) (forward-char (- end beg))) + (let ((p (point))) + (if (markerp end) (goto-char end) (forward-char (- end beg))) + (org-escape-code-in-region p (point))) (insert (funcall maybe-cap "#+end_example\n"))))))))) (defun org-babel-update-block-body (new-body) "Update the body of the current code block to NEW-BODY." - (if (not (org-babel-where-is-src-block-head)) - (error "Not in a source block") - (save-match-data - (replace-match (concat (org-babel-trim new-body) "\n") nil t nil 5)) - (indent-rigidly (match-beginning 5) (match-end 5) 2))) + (let ((element (org-element-at-point))) + (unless (eq (org-element-type element) 'src-block) + (error "Not in a source block")) + (goto-char (org-babel-where-is-src-block-head element)) + (let* ((ind (org-get-indentation)) + (body-start (line-beginning-position 2)) + (body (org-element-normalize-string + (if (or org-src-preserve-indentation + (org-element-property :preserve-indent element)) + new-body + (with-temp-buffer + (insert (org-remove-indentation new-body)) + (indent-rigidly + (point-min) + (point-max) + (+ ind org-edit-src-content-indentation)) + (buffer-string)))))) + (delete-region body-start + (org-with-wide-buffer + (goto-char (org-element-property :end element)) + (skip-chars-backward " \t\n") + (line-beginning-position))) + (goto-char body-start) + (insert body)))) (defun org-babel-merge-params (&rest plists) "Combine all parameter association lists in PLISTS. @@ -2259,133 +2554,103 @@ This takes into account some special considerations for certain parameters when merging lists." (let* ((results-exclusive-groups (mapcar (lambda (group) (mapcar #'symbol-name group)) - (cdr (assoc 'results org-babel-common-header-args-w-values)))) + (cdr (assq 'results org-babel-common-header-args-w-values)))) (exports-exclusive-groups (mapcar (lambda (group) (mapcar #'symbol-name group)) - (cdr (assoc 'exports org-babel-common-header-args-w-values)))) - (variable-index 0) - (e-merge (lambda (exclusive-groups &rest result-params) - ;; maintain exclusivity of mutually exclusive parameters - (let (output) - (mapc (lambda (new-params) - (mapc (lambda (new-param) - (mapc (lambda (exclusive-group) - (when (member new-param exclusive-group) - (mapcar (lambda (excluded-param) - (setq output - (delete - excluded-param - output))) - exclusive-group))) - exclusive-groups) - (setq output (org-uniquify - (cons new-param output)))) - new-params)) - result-params) - output))) - params results exports tangle noweb cache vars shebang comments padline - clearnames) - - (mapc - (lambda (plist) - (mapc - (lambda (pair) - (case (car pair) - (:var - (let ((name (if (listp (cdr pair)) - (cadr pair) - (and (string-match "^\\([^= \f\t\n\r\v]+\\)[ \t]*=" - (cdr pair)) - (intern (match-string 1 (cdr pair))))))) - (if name - (setq vars - (append - (if (member name (mapcar #'car vars)) - (progn - (push name clearnames) - (delq nil - (mapcar - (lambda (p) - (unless (equal (car p) name) p)) - vars))) - vars) - (list (cons name pair)))) - ;; if no name is given and we already have named variables - ;; then assign to named variables in order - (if (and vars (nth variable-index vars)) - (let ((name (car (nth variable-index vars)))) - (push name clearnames) ; clear out colnames - ; and rownames - ; for replace vars - (prog1 (setf (cddr (nth variable-index vars)) - (concat (symbol-name name) "=" (cdr pair))) - (incf variable-index))) - (error "Variable \"%s\" must be assigned a default value" - (cdr pair)))))) - (:results - (setq results (funcall e-merge results-exclusive-groups - results - (split-string - (let ((r (cdr pair))) - (if (stringp r) r (eval r))))))) - (:file - (when (cdr pair) - (setq results (funcall e-merge results-exclusive-groups - results '("file"))) - (unless (or (member "both" exports) - (member "none" exports) - (member "code" exports)) - (setq exports (funcall e-merge exports-exclusive-groups - exports '("results")))) - (setq params (cons pair (assq-delete-all (car pair) params))))) - (:exports - (setq exports (funcall e-merge exports-exclusive-groups - exports (split-string (cdr pair))))) - (:tangle ;; take the latest -- always overwrite - (setq tangle (or (list (cdr pair)) tangle))) - (:noweb - (setq noweb (funcall e-merge - '(("yes" "no" "tangle" "no-export" - "strip-export" "eval")) - noweb - (split-string (or (cdr pair) ""))))) - (:cache - (setq cache (funcall e-merge '(("yes" "no")) cache - (split-string (or (cdr pair) ""))))) - (:padline - (setq padline (funcall e-merge '(("yes" "no")) padline - (split-string (or (cdr pair) ""))))) - (:shebang ;; take the latest -- always overwrite - (setq shebang (or (list (cdr pair)) shebang))) - (:comments - (setq comments (funcall e-merge '(("yes" "no")) comments - (split-string (or (cdr pair) ""))))) - (t ;; replace: this covers e.g. :session - (setq params (cons pair (assq-delete-all (car pair) params)))))) - plist)) - plists) - (setq vars (reverse vars)) - (while vars (setq params (cons (cons :var (cddr (pop vars))) params))) - ;; clear out col-names and row-names for replaced variables - (mapc - (lambda (name) - (mapc - (lambda (param) - (when (assoc param params) - (setf (cdr (assoc param params)) - (org-remove-if (lambda (pair) (equal (car pair) name)) - (cdr (assoc param params)))) - (setf params (org-remove-if (lambda (pair) (and (equal (car pair) param) - (null (cdr pair)))) - params)))) - (list :colname-names :rowname-names))) - clearnames) - (mapc - (lambda (hd) - (let ((key (intern (concat ":" (symbol-name hd)))) - (val (eval hd))) - (setf params (cons (cons key (mapconcat 'identity val " ")) params)))) - '(results exports tangle noweb padline cache shebang comments)) + (cdr (assq 'exports org-babel-common-header-args-w-values)))) + (merge + (lambda (exclusive-groups &rest result-params) + ;; Maintain exclusivity of mutually exclusive parameters, + ;; as defined in EXCLUSIVE-GROUPS while merging lists in + ;; RESULT-PARAMS. + (let (output) + (dolist (new-params result-params (delete-dups output)) + (dolist (new-param new-params) + (dolist (exclusive-group exclusive-groups) + (when (member new-param exclusive-group) + (setq output (cl-remove-if + (lambda (o) (member o exclusive-group)) + output)))) + (push new-param output)))))) + (variable-index 0) ;Handle positional arguments. + clearnames + params ;Final parameters list. + ;; Some keywords accept multiple values. We need to treat + ;; them specially. + vars results exports) + (dolist (plist plists) + (dolist (pair plist) + (pcase pair + (`(:var . ,value) + (let ((name (cond + ((listp value) (car value)) + ((string-match "^\\([^= \f\t\n\r\v]+\\)[ \t]*=" value) + (intern (match-string 1 value))) + (t nil)))) + (cond + (name + (setq vars + (append (if (not (assoc name vars)) vars + (push name clearnames) + (cl-remove-if (lambda (p) (equal name (car p))) + vars)) + (list (cons name pair))))) + ((and vars (nth variable-index vars)) + ;; If no name is given and we already have named + ;; variables then assign to named variables in order. + (let ((name (car (nth variable-index vars)))) + ;; Clear out colnames and rownames for replace vars. + (push name clearnames) + (setf (cddr (nth variable-index vars)) + (concat (symbol-name name) "=" value)) + (cl-incf variable-index))) + (t (error "Variable \"%s\" must be assigned a default value" + (cdr pair)))))) + (`(:results . ,value) + (setq results (funcall merge + results-exclusive-groups + results + (split-string + (if (stringp value) value (eval value t)))))) + (`(,(or :file :file-ext) . ,value) + ;; `:file' and `:file-ext' are regular keywords but they + ;; imply a "file" `:results' and a "results" `:exports'. + (when value + (setq results + (funcall merge results-exclusive-groups results '("file"))) + (unless (or (member "both" exports) + (member "none" exports) + (member "code" exports)) + (setq exports + (funcall merge + exports-exclusive-groups exports '("results")))) + (push pair params))) + (`(:exports . ,value) + (setq exports (funcall merge + exports-exclusive-groups + exports + (split-string (or value ""))))) + ;; Regular keywords: any value overwrites the previous one. + (_ (setq params (cons pair (assq-delete-all (car pair) params))))))) + ;; Handle `:var' and clear out colnames and rownames for replaced + ;; variables. + (setq params (nconc (mapcar (lambda (v) (cons :var (cddr v))) vars) + params)) + (dolist (name clearnames) + (dolist (param '(:colname-names :rowname-names)) + (when (assq param params) + (setf (cdr (assq param params)) + (cl-remove-if (lambda (pair) (equal name (car pair))) + (cdr (assq param params)))) + (setq params + (cl-remove-if (lambda (pair) (and (equal (car pair) param) + (null (cdr pair)))) + params))))) + ;; Handle other special keywords, which accept multiple values. + (setq params (nconc (list (cons :results (mapconcat #'identity results " ")) + (cons :exports (mapconcat #'identity exports " "))) + params)) + ;; Return merged params. params)) (defvar org-babel-use-quick-and-dirty-noweb-expansion nil @@ -2397,17 +2662,12 @@ header argument from buffer or subtree wide properties.") (defun org-babel-noweb-p (params context) "Check if PARAMS require expansion in CONTEXT. CONTEXT may be one of :tangle, :export or :eval." - (let* (intersect - (intersect (lambda (as bs) - (when as - (if (member (car as) bs) - (car as) - (funcall intersect (cdr as) bs)))))) - (funcall intersect (case context - (:tangle '("yes" "tangle" "no-export" "strip-export")) - (:eval '("yes" "no-export" "strip-export" "eval")) - (:export '("yes"))) - (split-string (or (cdr (assoc :noweb params)) ""))))) + (let ((allowed-values (cl-case context + (:tangle '("yes" "tangle" "no-export" "strip-export")) + (:eval '("yes" "no-export" "strip-export" "eval")) + (:export '("yes"))))) + (cl-some (lambda (v) (member v allowed-values)) + (split-string (or (cdr (assq :noweb params)) ""))))) (defun org-babel-expand-noweb-references (&optional info parent-buffer) "Expand Noweb references in the body of the current source code block. @@ -2445,7 +2705,7 @@ block but are passed literally to the \"example-block\"." (body (nth 1 info)) (ob-nww-start org-babel-noweb-wrap-start) (ob-nww-end org-babel-noweb-wrap-end) - (comment (string= "noweb" (cdr (assoc :comments (nth 2 info))))) + (comment (string= "noweb" (cdr (assq :comments (nth 2 info))))) (rx-prefix (concat "\\(" org-babel-src-name-regexp "\\|" ":noweb-ref[ \t]+" "\\)")) (new-body "") @@ -2454,11 +2714,11 @@ block but are passed literally to the \"example-block\"." (with-temp-buffer (funcall (intern (concat lang "-mode"))) (comment-region (point) (progn (insert text) (point))) - (org-babel-trim (buffer-string))))) + (org-trim (buffer-string))))) index source-name evaluate prefix) (with-temp-buffer - (org-set-local 'org-babel-noweb-wrap-start ob-nww-start) - (org-set-local 'org-babel-noweb-wrap-end ob-nww-end) + (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)) @@ -2502,7 +2762,7 @@ block but are passed literally to the \"example-block\"." (while (re-search-forward rx nil t) (let* ((i (org-babel-get-src-block-info 'light)) (body (org-babel-expand-noweb-references i)) - (sep (or (cdr (assoc :noweb-sep (nth 2 i))) + (sep (or (cdr (assq :noweb-sep (nth 2 i))) "\n")) (full (if comment (let ((cs (org-babel-tangle-comment-links i))) @@ -2513,11 +2773,11 @@ block but are passed literally to the \"example-block\"." (setq expansion (cons sep (cons full expansion))))) (org-babel-map-src-blocks nil (let ((i (org-babel-get-src-block-info 'light))) - (when (equal (or (cdr (assoc :noweb-ref (nth 2 i))) + (when (equal (or (cdr (assq :noweb-ref (nth 2 i))) (nth 4 i)) source-name) (let* ((body (org-babel-expand-noweb-references i)) - (sep (or (cdr (assoc :noweb-sep (nth 2 i))) + (sep (or (cdr (assq :noweb-sep (nth 2 i))) "\n")) (full (if comment (let ((cs (org-babel-tangle-comment-links i))) @@ -2530,7 +2790,8 @@ block but are passed literally to the \"example-block\"." (and expansion (mapconcat #'identity (nreverse (cdr expansion)) ""))) ;; Possibly raise an error if named block doesn't exist. - (if (member lang org-babel-noweb-error-langs) + (if (or org-babel-noweb-error-all-langs + (member lang org-babel-noweb-error-langs)) (error "%s" (concat (org-babel-noweb-wrap source-name) "could not be resolved (see " @@ -2540,79 +2801,120 @@ block but are passed literally to the \"example-block\"." (funcall nb-add (buffer-substring index (point-max)))) new-body)) +(defun org-babel--script-escape-inner (str) + (let (in-single in-double backslash out) + (mapc + (lambda (ch) + (setq + out + (if backslash + (progn + (setq backslash nil) + (cond + ((and in-single (eq ch ?')) + ;; Escaped single quote inside single quoted string: + ;; emit just a single quote, since we've changed the + ;; outer quotes to double. + (cons ch out)) + ((eq ch ?\") + ;; Escaped double quote + (if in-single + ;; This should be interpreted as backslash+quote, + ;; not an escape. Emit a three backslashes + ;; followed by a quote (because one layer of + ;; quoting will be stripped by `org-babel-read'). + (append (list ch ?\\ ?\\ ?\\) out) + ;; Otherwise we are in a double-quoted string. Emit + ;; a single escaped quote + (append (list ch ?\\) out))) + ((eq ch ?\\) + ;; Escaped backslash: emit a single escaped backslash + (append (list ?\\ ?\\) out)) + ;; Other: emit a quoted backslash followed by whatever + ;; the character was (because one layer of quoting will + ;; be stripped by `org-babel-read'). + (t (append (list ch ?\\ ?\\) out)))) + (cl-case ch + (?\[ (if (or in-double in-single) + (cons ?\[ out) + (cons ?\( out))) + (?\] (if (or in-double in-single) + (cons ?\] out) + (cons ?\) out))) + (?\{ (if (or in-double in-single) + (cons ?\{ out) + (cons ?\( out))) + (?\} (if (or in-double in-single) + (cons ?\} out) + (cons ?\) out))) + (?, (if (or in-double in-single) + (cons ?, out) (cons ?\s out))) + (?\' (if in-double + (cons ?\' out) + (setq in-single (not in-single)) (cons ?\" out))) + (?\" (if in-single + (append (list ?\" ?\\) out) + (setq in-double (not in-double)) (cons ?\" out))) + (?\\ (unless (or in-single in-double) + (error "Can't handle backslash outside string in `org-babel-script-escape'")) + (setq backslash t) + out) + (t (cons ch out)))))) + (string-to-list str)) + (when (or in-single in-double) + (error "Unterminated string in `org-babel-script-escape'")) + (apply #'string (reverse out)))) + (defun org-babel-script-escape (str &optional force) "Safely convert tables into elisp lists." + (unless (stringp str) + (error "`org-babel-script-escape' expects a string")) (let ((escaped - (if (or force - (and (stringp str) - (> (length str) 2) - (or (and (string-equal "[" (substring str 0 1)) - (string-equal "]" (substring str -1))) - (and (string-equal "{" (substring str 0 1)) - (string-equal "}" (substring str -1))) - (and (string-equal "(" (substring str 0 1)) - (string-equal ")" (substring str -1)))))) - (org-babel-read - (concat - "'" - (let (in-single in-double out) - (mapc - (lambda (ch) - (setq - out - (case ch - (91 (if (or in-double in-single) ; [ - (cons 91 out) - (cons 40 out))) - (93 (if (or in-double in-single) ; ] - (cons 93 out) - (cons 41 out))) - (123 (if (or in-double in-single) ; { - (cons 123 out) - (cons 40 out))) - (125 (if (or in-double in-single) ; } - (cons 125 out) - (cons 41 out))) - (44 (if (or in-double in-single) ; , - (cons 44 out) (cons 32 out))) - (39 (if in-double ; ' - (cons 39 out) - (setq in-single (not in-single)) (cons 34 out))) - (34 (if in-single ; " - (append (list 34 32) out) - (setq in-double (not in-double)) (cons 34 out))) - (t (cons ch out))))) - (string-to-list str)) - (apply #'string (reverse out))))) - str))) + (cond + ((and (> (length str) 2) + (or (and (string-equal "[" (substring str 0 1)) + (string-equal "]" (substring str -1))) + (and (string-equal "{" (substring str 0 1)) + (string-equal "}" (substring str -1))) + (and (string-equal "(" (substring str 0 1)) + (string-equal ")" (substring str -1))))) + + (concat "'" (org-babel--script-escape-inner str))) + ((or force + (and (> (length str) 2) + (or (and (string-equal "'" (substring str 0 1)) + (string-equal "'" (substring str -1))) + ;; We need to pass double-quoted strings + ;; through the backslash-twiddling bits, even + ;; though we don't need to change their + ;; delimiters. + (and (string-equal "\"" (substring str 0 1)) + (string-equal "\"" (substring str -1)))))) + (org-babel--script-escape-inner str)) + (t str)))) (condition-case nil (org-babel-read escaped) (error escaped)))) (defun org-babel-read (cell &optional inhibit-lisp-eval) "Convert the string value of CELL to a number if appropriate. -Otherwise if cell looks like lisp (meaning it starts with a -\"(\", \"\\='\", \"\\=`\" or a \"[\") then read it as lisp, -otherwise return it unmodified as a string. Optional argument -NO-LISP-EVAL inhibits lisp evaluation for situations in which is -it not appropriate." - (if (and (stringp cell) (not (equal cell ""))) - (or (org-babel-number-p cell) - (if (and (not inhibit-lisp-eval) - (or (member (substring cell 0 1) '("(" "'" "`" "[")) - (string= cell "*this*"))) - (eval (read cell)) - (if (string= (substring cell 0 1) "\"") - (read cell) - (progn (set-text-properties 0 (length cell) nil cell) cell)))) - cell)) - -(defun org-babel-number-p (string) - "If STRING represents a number return its value." - (if (and (string-match "[0-9]+" string) - (string-match "^-?[0-9]*\\.?[0-9]*$" string) - (= (length (substring string (match-beginning 0) - (match-end 0))) - (length string))) - (string-to-number string))) +Otherwise if CELL looks like lisp (meaning it starts with a +\"(\", \"\\='\", \"\\=`\" or a \"[\") then read and evaluate it as +lisp, otherwise return it unmodified as a string. Optional +argument INHIBIT-LISP-EVAL inhibits lisp evaluation for +situations in which is it not appropriate." + (cond ((not (org-string-nw-p cell)) cell) + ((org-babel--string-to-number cell)) + ((and (not inhibit-lisp-eval) + (or (memq (string-to-char cell) '(?\( ?' ?` ?\[)) + (string= cell "*this*"))) + (eval (read cell) t)) + ((eq (string-to-char cell) ?\") (read cell)) + (t (org-no-properties cell)))) + +(defun org-babel--string-to-number (string) + "If STRING represents a number return its value. +Otherwise return nil." + (and (string-match-p "\\`-?[0-9]*\\.?[0-9]*\\'" string) + (string-to-number string))) (defun org-babel-import-elisp-from-file (file-name &optional separator) "Read the results located at FILE-NAME into an elisp table. @@ -2644,49 +2946,15 @@ If the table is trivial, then return it as a scalar." cell) t)) (defun org-babel-chomp (string &optional regexp) - "Strip trailing spaces and carriage returns from STRING. -Default regexp used is \"[ \f\t\n\r\v]\" but can be -overwritten by specifying a regexp as a second argument." + "Strip a trailing space or carriage return from STRING. +The default regexp used is \"[ \\f\\t\\n\\r\\v]\" but another one +can be specified as the REGEXP argument." (let ((regexp (or regexp "[ \f\t\n\r\v]"))) (while (and (> (length string) 0) (string-match regexp (substring string -1))) (setq string (substring string 0 -1))) string)) -(defun org-babel-trim (string &optional regexp) - "Strip leading and trailing spaces and carriage returns from STRING. -Like `org-babel-chomp' only it runs on both the front and back -of the string." - (org-babel-chomp (org-reverse-string - (org-babel-chomp (org-reverse-string string) regexp)) - regexp)) - -(defun org-babel-tramp-handle-call-process-region - (start end program &optional delete buffer display &rest args) - "Use Tramp to handle `call-process-region'. -Fixes a bug in `tramp-handle-call-process-region'." - (if (file-remote-p default-directory) - (let ((tmpfile (tramp-compat-make-temp-file ""))) - (write-region start end tmpfile) - (when delete (delete-region start end)) - (unwind-protect - ;; (apply 'call-process program tmpfile buffer display args) - ;; bug in tramp - (apply 'process-file program tmpfile buffer display args) - (delete-file tmpfile))) - ;; org-babel-call-process-region-original is the original emacs - ;; definition. It is in scope from the let binding in - ;; org-babel-execute-src-block - (apply org-babel-call-process-region-original - start end program delete buffer display args))) - -(defalias 'org-babel-local-file-name - (if (fboundp 'file-local-name) - 'file-local-name - (lambda (file) - "Return the local name component of FILE." - (or (file-remote-p file 'localname) file)))) - (defun org-babel-process-file-name (name &optional no-quote-p) "Prepare NAME to be used in an external process. If NAME specifies a remote location, the remote portion of the @@ -2694,7 +2962,7 @@ name is removed, since in that case the process will be executing remotely. The file name is then processed by `expand-file-name'. Unless second argument NO-QUOTE-P is non-nil, the file name is additionally processed by `shell-quote-argument'" - (let ((f (expand-file-name (org-babel-local-file-name name)))) + (let ((f (org-babel-local-file-name (expand-file-name name)))) (if no-quote-p f (shell-quote-argument f)))) (defvar org-babel-temporary-directory) @@ -2708,6 +2976,11 @@ additionally processed by `shell-quote-argument'" Used by `org-babel-temp-file'. This directory will be removed on Emacs shutdown.")) +(defcustom org-babel-remote-temporary-directory "/tmp/" + "Directory to hold temporary files on remote hosts." + :group 'org-babel + :type 'string) + (defmacro org-babel-result-cond (result-params scalar-form &rest table-forms) "Call the code to parse raw string results according to RESULT-PARAMS." (declare (indent 1) @@ -2720,6 +2993,7 @@ Emacs shutdown.")) (member "html" ,params) (member "code" ,params) (member "pp" ,params) + (member "file" ,params) (and (or (member "output" ,params) (member "raw" ,params) (member "org" ,params) @@ -2737,7 +3011,8 @@ of `org-babel-temporary-directory'." (if (file-remote-p default-directory) (let ((prefix (concat (file-remote-p default-directory) - (expand-file-name prefix temporary-file-directory)))) + (expand-file-name + prefix org-babel-remote-temporary-directory)))) (make-temp-file prefix nil suffix)) (let ((temporary-file-directory (or (and (boundp 'org-babel-temporary-directory) @@ -2772,6 +3047,96 @@ of `org-babel-temporary-directory'." (add-hook 'kill-emacs-hook 'org-babel-remove-temporary-directory) +(defun org-babel-one-header-arg-safe-p (pair safe-list) + "Determine if the PAIR is a safe babel header arg according to SAFE-LIST. + +For the format of SAFE-LIST, see `org-babel-safe-header-args'." + (and (consp pair) + (keywordp (car pair)) + (stringp (cdr pair)) + (or + (memq (car pair) safe-list) + (let ((entry (assq (car pair) safe-list))) + (and entry + (consp entry) + (cond ((functionp (cdr entry)) + (funcall (cdr entry) (cdr pair))) + ((listp (cdr entry)) + (member (cdr pair) (cdr entry))) + (t nil))))))) + +(defun org-babel-generate-file-param (src-name params) + "Calculate the filename for source block results. + +The directory is calculated from the :output-dir property of the +source block; if not specified, use the current directory. + +If the source block has a #+NAME and the :file parameter does not +contain any period characters, then the :file parameter is +treated as an extension, and the output file name is the +concatenation of the directory (as calculated above), the block +name, a period, and the parameter value as a file extension. +Otherwise, the :file parameter is treated as a full file name, +and the output file name is the directory (as calculated above) +plus the parameter value." + (let* ((file-cons (assq :file params)) + (file-ext-cons (assq :file-ext params)) + (file-ext (cdr-safe file-ext-cons)) + (dir (cdr-safe (assq :output-dir params))) + fname) + ;; create the output-dir if it does not exist + (when dir + (make-directory dir t)) + (if file-cons + ;; :file given; add :output-dir if given + (when dir + (setcdr file-cons (concat (file-name-as-directory dir) (cdr file-cons)))) + ;; :file not given; compute from name and :file-ext if possible + (when (and src-name file-ext) + (if dir + (setq fname (concat (file-name-as-directory (or dir "")) + src-name "." file-ext)) + (setq fname (concat src-name "." file-ext))) + (setq params (cons (cons :file fname) params)))) + params)) + +(defun org-babel-graphical-output-file (params) + "File where a babel block should send graphical output, per PARAMS. +Return nil if no graphical output is expected. Raise an error if +the output file is ill-defined." + (let ((file (cdr (assq :file params)))) + (cond (file (and (member "graphics" (cdr (assq :result-params params))) + file)) + ((assq :file-ext params) + (user-error ":file-ext given but no :file generated; did you forget \ +to name a block?")) + (t (user-error "No :file header argument given; cannot create \ +graphical result"))))) + +(defun org-babel-make-language-alias (new old) + "Make source blocks of type NEW aliases for those of type OLD. + +NEW and OLD should be strings. This function should be called +after the babel API for OLD-type source blocks is fully defined. + +Callers of this function will probably want to add an entry to +`org-src-lang-modes' as well." + (dolist (fn '("execute" "expand-body" "prep-session" + "variable-assignments" "load-session")) + (let ((sym (intern-soft (concat "org-babel-" fn ":" old)))) + (when (and sym (fboundp sym)) + (defalias (intern (concat "org-babel-" fn ":" new)) sym)))) + ;; Technically we don't need a `dolist' for just one variable, but + ;; we keep it for symmetry/ease of future expansion. + (dolist (var '("default-header-args")) + (let ((sym (intern-soft (concat "org-babel-" var ":" old)))) + (when (and sym (boundp sym)) + (defvaralias (intern (concat "org-babel-" var ":" new)) sym))))) + +(defun org-babel-strip-quotes (string) + "Strip \\\"s from around a string, if applicable." + (org-unbracket-string "\"" "\"" string)) + (provide 'ob-core) ;; Local variables: diff --git a/lisp/org/ob-css.el b/lisp/org/ob-css.el index 70c66d46704..b3982db391d 100644 --- a/lisp/org/ob-css.el +++ b/lisp/org/ob-css.el @@ -1,4 +1,4 @@ -;;; ob-css.el --- org-babel functions for css evaluation +;;; ob-css.el --- Babel Functions for CSS -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. @@ -19,24 +19,24 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; Since CSS can't be executed, this file exists solely for tangling -;; CSS from org-mode files. +;; CSS from Org files. ;;; Code: (require 'ob) (defvar org-babel-default-header-args:css '()) -(defun org-babel-execute:css (body params) +(defun org-babel-execute:css (body _params) "Execute a block of CSS code. This function is called by `org-babel-execute-src-block'." body) -(defun org-babel-prep-session:css (session params) +(defun org-babel-prep-session:css (_session _params) "Return an error if the :session header argument is set. CSS does not support sessions." (error "CSS sessions are nonsensical")) diff --git a/lisp/org/ob-ditaa.el b/lisp/org/ob-ditaa.el index 5eb8e2fdb4b..2a7c755676b 100644 --- a/lisp/org/ob-ditaa.el +++ b/lisp/org/ob-ditaa.el @@ -1,4 +1,4 @@ -;;; ob-ditaa.el --- org-babel functions for ditaa evaluation +;;; ob-ditaa.el --- Babel Functions for ditaa -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -81,15 +81,21 @@ Do not leave leading or trailing spaces in this string." (defun org-babel-execute:ditaa (body params) "Execute a block of Ditaa code with org-babel. This function is called by `org-babel-execute-src-block'." - (let* ((result-params (split-string (or (cdr (assoc :results params)) ""))) - (out-file (let ((el (cdr (assoc :file params)))) - (or el - (error - "ditaa code block requires :file header argument")))) - (cmdline (cdr (assoc :cmdline params))) - (java (cdr (assoc :java params))) + (let* ((out-file (or (cdr (assq :file params)) + (error + "ditaa code block requires :file header argument"))) + (cmdline (cdr (assq :cmdline params))) + (java (cdr (assq :java params))) (in-file (org-babel-temp-file "ditaa-")) - (eps (cdr (assoc :eps params))) + (eps (cdr (assq :eps params))) + (eps-file (when eps + (org-babel-process-file-name (concat in-file ".eps")))) + (pdf-cmd (when (and (or (string= (file-name-extension out-file) "pdf") + (cdr (assq :pdf params)))) + (concat + "epstopdf" + " " eps-file + " -o=" (org-babel-process-file-name out-file)))) (cmd (concat org-babel-ditaa-java-cmd " " java " " org-ditaa-jar-option " " (shell-quote-argument @@ -97,13 +103,9 @@ This function is called by `org-babel-execute-src-block'." (if eps org-ditaa-eps-jar-path org-ditaa-jar-path))) " " cmdline " " (org-babel-process-file-name in-file) - " " (org-babel-process-file-name out-file))) - (pdf-cmd (when (and (or (string= (file-name-extension out-file) "pdf") - (cdr (assoc :pdf params)))) - (concat - "epstopdf" - " " (org-babel-process-file-name (concat in-file ".eps")) - " -o=" (org-babel-process-file-name out-file))))) + " " (if pdf-cmd + eps-file + (org-babel-process-file-name out-file))))) (unless (file-exists-p org-ditaa-jar-path) (error "Could not find ditaa.jar at %s" org-ditaa-jar-path)) (with-temp-file in-file (insert body)) @@ -111,7 +113,7 @@ This function is called by `org-babel-execute-src-block'." (when pdf-cmd (message pdf-cmd) (shell-command pdf-cmd)) nil)) ;; signal that output has already been written to file -(defun org-babel-prep-session:ditaa (session params) +(defun org-babel-prep-session:ditaa (_session _params) "Return an error because ditaa does not support sessions." (error "Ditaa does not support sessions")) diff --git a/lisp/org/ob-dot.el b/lisp/org/ob-dot.el index aa0445b4ca4..8c8e2fbd604 100644 --- a/lisp/org/ob-dot.el +++ b/lisp/org/ob-dot.el @@ -1,4 +1,4 @@ -;;; ob-dot.el --- org-babel functions for dot evaluation +;;; ob-dot.el --- Babel Functions for dot -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -46,7 +46,7 @@ (defun org-babel-expand-body:dot (body params) "Expand BODY according to PARAMS, return the expanded body." - (let ((vars (mapcar #'cdr (org-babel-get-header params :var)))) + (let ((vars (org-babel--get-vars params))) (mapc (lambda (pair) (let ((name (symbol-name (car pair))) @@ -55,19 +55,20 @@ (replace-regexp-in-string (concat "$" (regexp-quote name)) (if (stringp value) value (format "%S" value)) - body)))) + body + t + t)))) vars) body)) (defun org-babel-execute:dot (body params) "Execute a block of Dot code with org-babel. This function is called by `org-babel-execute-src-block'." - (let* ((result-params (cdr (assoc :result-params params))) - (out-file (cdr (or (assoc :file params) + (let* ((out-file (cdr (or (assq :file params) (error "You need to specify a :file parameter")))) - (cmdline (or (cdr (assoc :cmdline params)) + (cmdline (or (cdr (assq :cmdline params)) (format "-T%s" (file-name-extension out-file)))) - (cmd (or (cdr (assoc :cmd params)) "dot")) + (cmd (or (cdr (assq :cmd params)) "dot")) (in-file (org-babel-temp-file "dot-"))) (with-temp-file in-file (insert (org-babel-expand-body:dot body params))) @@ -78,7 +79,7 @@ This function is called by `org-babel-execute-src-block'." " -o " (org-babel-process-file-name out-file)) "") nil)) ;; signal that output has already been written to file -(defun org-babel-prep-session:dot (session params) +(defun org-babel-prep-session:dot (_session _params) "Return an error because Dot does not support sessions." (error "Dot does not support sessions")) diff --git a/lisp/org/ob-ebnf.el b/lisp/org/ob-ebnf.el new file mode 100644 index 00000000000..6bb9b81b222 --- /dev/null +++ b/lisp/org/ob-ebnf.el @@ -0,0 +1,81 @@ +;;; ob-ebnf.el --- Babel Functions for EBNF -*- lexical-binding: t; -*- + +;; Copyright (C) 2013-2017 Free Software Foundation, Inc. + +;; Author: Michael Gauland +;; Keywords: literate programming, reproducible research +;; Homepage: http://orgmode.org +;; Version: 1.00 + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. + +;;; 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 '[<file>' 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: + +;;; Code: +(require 'ob) +(require 'ebnf2ps) + +;; optionally declare default header arguments for this language +(defvar org-babel-default-header-args:ebnf '((:style . nil))) + +;; Use ebnf-eps-buffer to produce an encapsulated postscript file. +;; +(defun org-babel-execute:ebnf (body params) + "Execute a block of Ebnf code with org-babel. This function is +called by `org-babel-execute-src-block'" + (save-excursion + (let* ((dest-file (cdr (assq :file params))) + (dest-dir (file-name-directory dest-file)) + (dest-root (file-name-sans-extension + (file-name-nondirectory dest-file))) + (style (cdr (assq :style params))) + (result nil)) + (with-temp-buffer + (when style (ebnf-push-style style)) + (let ((comment-format + (cond ((string= ebnf-syntax 'yacc) "/*%s*/") + ((string= ebnf-syntax 'ebnf) ";%s") + ((string= ebnf-syntax 'iso-ebnf) "(*%s*)") + (t (setq result + (format "EBNF error: format %s not supported." + ebnf-syntax)))))) + (setq ebnf-eps-prefix dest-dir) + (insert (format comment-format (format "[%s" dest-root))) + (newline) + (insert body) + (newline) + (insert (format comment-format (format "]%s" dest-root))) + (ebnf-eps-buffer) + (when style (ebnf-pop-style)))) + 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 d95c475c4ee..4736d895dc5 100644 --- a/lisp/org/ob-emacs-lisp.el +++ b/lisp/org/ob-emacs-lisp.el @@ -1,4 +1,4 @@ -;;; ob-emacs-lisp.el --- org-babel functions for emacs-lisp code evaluation +;;; ob-emacs-lisp.el --- Babel Functions for Emacs-lisp Code -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -28,53 +28,61 @@ ;;; Code: (require 'ob) -(defvar org-babel-default-header-args:emacs-lisp - '((:hlines . "yes") (:colnames . "no")) - "Default arguments for evaluating an emacs-lisp source block.") +(defconst org-babel-header-args:emacs-lisp '((lexical . :any)) + "Emacs-lisp specific header arguments.") -(declare-function orgtbl-to-generic "org-table" - (table params &optional backend)) +(defvar org-babel-default-header-args:emacs-lisp '((:lexical . "no")) + "Default arguments for evaluating an emacs-lisp source block. + +A value of \"yes\" or t causes src blocks to be eval'd using +lexical scoping. It can also be an alist mapping symbols to +their value. It is used as the optional LEXICAL argument to +`eval', which see.") (defun org-babel-expand-body:emacs-lisp (body params) "Expand BODY according to PARAMS, return the expanded body." - (let* ((vars (mapcar #'cdr (org-babel-get-header params :var))) - (result-params (cdr (assoc :result-params params))) - (print-level nil) (print-length nil) - (body (if (> (length vars) 0) - (concat "(let (" - (mapconcat - (lambda (var) - (format "%S" (print `(,(car var) ',(cdr var))))) - vars "\n ") - ")\n" body "\n)") - (concat body "\n")))) - (if (or (member "code" result-params) - (member "pp" result-params)) - (concat "(pp " body ")") body))) + (let ((vars (org-babel--get-vars params)) + (print-level nil) + (print-length nil)) + (if (null vars) (concat body "\n") + (format "(let (%s)\n%s\n)" + (mapconcat + (lambda (var) + (format "%S" (print `(,(car var) ',(cdr var))))) + vars "\n ") + body)))) (defun org-babel-execute:emacs-lisp (body params) "Execute a block of emacs-lisp code with Babel." (save-window-excursion - (let ((result - (eval (read (format (if (member "output" - (cdr (assoc :result-params params))) - "(with-output-to-string %s)" - "(progn %s)") - (org-babel-expand-body:emacs-lisp - body params)))))) - (org-babel-result-cond (cdr (assoc :result-params 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)) + (if (listp lexical) + lexical + (member lexical '("yes" "t")))))) + (org-babel-result-cond result-params (let ((print-level nil) (print-length nil)) - (if (or (member "scalar" (cdr (assoc :result-params params))) - (member "verbatim" (cdr (assoc :result-params params)))) + (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 (assoc :colname-names params)) - (cdr (assoc :colnames params))) - (org-babel-pick-name (cdr (assoc :rowname-names params)) - (cdr (assoc :rownames params)))))))) + (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)))))))) + +(org-babel-make-language-alias "elisp" "emacs-lisp") (provide 'ob-emacs-lisp) diff --git a/lisp/org/ob-eval.el b/lisp/org/ob-eval.el index 46d21c88e85..4ce91c78537 100644 --- a/lisp/org/ob-eval.el +++ b/lisp/org/ob-eval.el @@ -1,4 +1,4 @@ -;;; ob-eval.el --- org-babel functions for external code evaluation +;;; ob-eval.el --- Babel Functions for External Code Evaluation -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -28,7 +28,6 @@ ;;; Code: (require 'org-macs) -(eval-when-compile (require 'cl)) (defvar org-babel-error-buffer-name "*Org-Babel Error Output*") (declare-function org-babel-temp-file "ob-core" (prefix &optional suffix)) @@ -57,6 +56,13 @@ STDERR with `org-babel-eval-error-notify'." (progn (with-current-buffer err-buff (org-babel-eval-error-notify exit-code (buffer-string))) + (save-excursion + (when (get-buffer org-babel-error-buffer-name) + (with-current-buffer org-babel-error-buffer-name + (unless (derived-mode-p 'compilation-mode) + (compilation-mode)) + ;; Compilation-mode enforces read-only, but Babel expects the buffer modifiable. + (setq buffer-read-only nil)))) nil) (buffer-string))))) @@ -114,18 +120,18 @@ function in various versions of Emacs. (delete-file input-file)) (when (and error-file (file-exists-p error-file)) - (if (< 0 (nth 7 (file-attributes error-file))) - (with-current-buffer (get-buffer-create error-buffer) - (let ((pos-from-end (- (point-max) (point)))) - (or (bobp) - (insert "\f\n")) - ;; Do no formatting while reading error file, - ;; because that can run a shell command, and we - ;; don't want that to cause an infinite recursion. - (format-insert-file error-file nil) - ;; Put point after the inserted errors. - (goto-char (- (point-max) pos-from-end))) - (current-buffer))) + (when (< 0 (nth 7 (file-attributes error-file))) + (with-current-buffer (get-buffer-create error-buffer) + (let ((pos-from-end (- (point-max) (point)))) + (or (bobp) + (insert "\f\n")) + ;; Do no formatting while reading error file, + ;; because that can run a shell command, and we + ;; don't want that to cause an infinite recursion. + (format-insert-file error-file nil) + ;; Put point after the inserted errors. + (goto-char (- (point-max) pos-from-end))) + (current-buffer))) (delete-file error-file)) exit-status)) diff --git a/lisp/org/ob-exp.el b/lisp/org/ob-exp.el index 2677fe59cb2..9606d3e474f 100644 --- a/lisp/org/ob-exp.el +++ b/lisp/org/ob-exp.el @@ -1,4 +1,4 @@ -;;; ob-exp.el --- Exportation of org-babel source blocks +;;; ob-exp.el --- Exportation of Babel Source Blocks -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. @@ -20,85 +20,52 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: (require 'ob-core) -(require 'org-src) -(eval-when-compile - (require 'cl)) - -(defvar org-current-export-file) -(defvar org-babel-lob-one-liner-regexp) -(defvar org-babel-ref-split-regexp) -(defvar org-list-forbidden-blocks) - -(declare-function org-babel-lob-get-info "ob-lob" ()) -(declare-function org-babel-eval-wipe-error-buffer "ob-eval" ()) -(declare-function org-between-regexps-p "org" - (start-re end-re &optional lim-up lim-down)) -(declare-function org-get-indentation "org" (&optional line)) -(declare-function org-heading-components "org" ()) -(declare-function org-in-block-p "org" (names)) -(declare-function org-in-verbatim-emphasis "org" ()) -(declare-function org-link-search "org" (s &optional type avoid-pos stealth)) -(declare-function org-fill-template "org" (template alist)) -(declare-function org-split-string "org" (string &optional separators)) -(declare-function org-element-at-point "org-element" (&optional keep-trail)) + +(declare-function org-babel-lob-get-info "ob-lob" (&optional datum)) +(declare-function org-element-at-point "org-element" ()) (declare-function org-element-context "org-element" (&optional element)) (declare-function org-element-property "org-element" (property element)) (declare-function org-element-type "org-element" (element)) (declare-function org-escape-code-in-string "org-src" (s)) +(declare-function org-export-copy-buffer "ox" ()) +(declare-function org-fill-template "org" (template alist)) +(declare-function org-get-indentation "org" (&optional line)) +(declare-function org-in-commented-heading-p "org" (&optional no-inheritance)) + +(defvar org-src-preserve-indentation) -(defcustom org-export-babel-evaluate t - "Switch controlling code evaluation during export. +(defcustom org-export-use-babel t + "Switch controlling code evaluation and header processing during export. When set to nil no code will be evaluated as part of the export -process. When set to `inline-only', only inline code blocks will -be executed." +process and no header arguments will be obeyed. Users who wish +to avoid evaluating code on export should use the header argument +`:eval never-export'." :group 'org-babel :version "24.1" :type '(choice (const :tag "Never" nil) - (const :tag "Only inline code" inline-only) - (const :tag "Always" t))) -(put 'org-export-babel-evaluate 'safe-local-variable (lambda (x) (eq x nil))) - -(defun org-babel-exp-get-export-buffer () - "Return the current export buffer if possible." - (cond - ((bufferp org-current-export-file) org-current-export-file) - (org-current-export-file (get-file-buffer org-current-export-file)) - ('otherwise - (error "Requested export buffer when `org-current-export-file' is nil")))) - -(defvar org-link-search-inhibit-query) - -(defmacro org-babel-exp-in-export-file (lang &rest body) - (declare (indent 1)) - `(let* ((lang-headers (intern (concat "org-babel-default-header-args:" ,lang))) - (heading (nth 4 (ignore-errors (org-heading-components)))) - (export-buffer (current-buffer)) - (original-buffer (org-babel-exp-get-export-buffer)) results) - (when original-buffer - ;; resolve parameters in the original file so that - ;; headline and file-wide parameters are included, attempt - ;; to go to the same heading in the original file - (set-buffer original-buffer) - (save-restriction - (when heading - (condition-case nil - (let ((org-link-search-inhibit-query t)) - (org-link-search heading)) - (error (when heading - (goto-char (point-min)) - (re-search-forward (regexp-quote heading) nil t))))) - (setq results ,@body)) - (set-buffer export-buffer) - results))) -(def-edebug-spec org-babel-exp-in-export-file (form body)) - -(defun org-babel-exp-src-block (&rest headers) + (const :tag "Always" t)) + :safe #'null) + + +(defmacro org-babel-exp--at-source (&rest body) + "Evaluate BODY at the source of the Babel block at point. +Source is located in `org-babel-exp-reference-buffer'. The value +returned is the value of the last form in BODY. Assume that +point is at the beginning of the Babel block." + (declare (indent 1) (debug body)) + `(let ((source (get-text-property (point) 'org-reference))) + (with-current-buffer org-babel-exp-reference-buffer + (org-with-wide-buffer + (goto-char source) + ,@body)))) + +(defun org-babel-exp-src-block () "Process source block for export. -Depending on the `export' headers argument, replace the source +Depending on the \":export\" header argument, replace the source code block like this: both ---- display the code and the results @@ -107,29 +74,36 @@ code ---- the default, display the code inside the block but do not process results - just like none only the block is run on export ensuring - that it's results are present in the org-mode buffer + that its results are present in the Org mode buffer none ---- do not display either code or results upon export -Assume point is at the beginning of block's starting line." +Assume point is at block opening line." (interactive) - (unless noninteractive (message "org-babel-exp processing...")) (save-excursion (let* ((info (org-babel-get-src-block-info 'light)) (lang (nth 0 info)) - (raw-params (nth 2 info)) hash) + (raw-params (nth 2 info)) + hash) ;; bail if we couldn't get any info from the block + (unless noninteractive + (message "org-babel-exp process %s at position %d..." + lang + (line-beginning-position))) (when info ;; if we're actually going to need the parameters - (when (member (cdr (assoc :exports (nth 2 info))) '("both" "results")) - (org-babel-exp-in-export-file lang - (setf (nth 2 info) - (org-babel-process-params - (apply #'org-babel-merge-params - org-babel-default-header-args - (if (boundp lang-headers) (eval lang-headers) nil) - (append (org-babel-params-from-properties lang) - (list raw-params)))))) + (when (member (cdr (assq :exports (nth 2 info))) '("both" "results")) + (let ((lang-headers (intern (concat "org-babel-default-header-args:" + lang)))) + (org-babel-exp--at-source + (setf (nth 2 info) + (org-babel-process-params + (apply #'org-babel-merge-params + org-babel-default-header-args + (and (boundp lang-headers) + (symbol-value lang-headers)) + (append (org-babel-params-from-properties lang) + (list raw-params))))))) (setf hash (org-babel-sha1-hash info))) (org-babel-exp-do-export info 'block hash))))) @@ -150,166 +124,178 @@ this template." :group 'org-babel :type 'string) -(defvar org-babel-default-lob-header-args) (defun org-babel-exp-process-buffer () "Execute all Babel blocks in current buffer." (interactive) - (save-window-excursion - (save-excursion + (when org-export-use-babel + (save-window-excursion (let ((case-fold-search t) - (regexp (concat org-babel-inline-src-block-regexp "\\|" - org-babel-lob-one-liner-regexp "\\|" - "^[ \t]*#\\+BEGIN_SRC"))) - (goto-char (point-min)) - (while (re-search-forward regexp nil t) - (let* ((element (save-excursion - ;; If match is inline, point is at its - ;; end. Move backward so - ;; `org-element-context' can get the - ;; object, not the following one. - (backward-char) - (save-match-data (org-element-context)))) - (type (org-element-type element)) - (begin (copy-marker (org-element-property :begin element))) - (end (copy-marker - (save-excursion - (goto-char (org-element-property :end element)) - (skip-chars-backward " \r\t\n") - (point))))) - (case type - (inline-src-block - (let* ((info (org-babel-parse-inline-src-block-match)) - (params (nth 2 info))) - (setf (nth 1 info) - (if (and (cdr (assoc :noweb params)) - (string= "yes" (cdr (assoc :noweb params)))) - (org-babel-expand-noweb-references - info (org-babel-exp-get-export-buffer)) - (nth 1 info))) - (goto-char begin) - (let ((replacement (org-babel-exp-do-export info 'inline))) - (if (equal replacement "") - ;; Replacement code is empty: remove inline src - ;; block, including extra white space that - ;; might have been created when inserting - ;; results. - (delete-region begin - (progn (goto-char end) - (skip-chars-forward " \t") - (point))) - ;; Otherwise: remove inline src block but - ;; preserve following white spaces. Then insert - ;; value. - (delete-region begin end) - (insert replacement))))) - ((babel-call inline-babel-call) - (let* ((lob-info (org-babel-lob-get-info)) - (results - (org-babel-exp-do-export - (list "emacs-lisp" "results" - (apply #'org-babel-merge-params - org-babel-default-header-args - org-babel-default-lob-header-args - (append - (org-babel-params-from-properties) - (list - (org-babel-parse-header-arguments - (org-no-properties - (concat - ":var results=" - (mapconcat 'identity - (butlast lob-info 2) - " "))))))) - "" (nth 3 lob-info) (nth 2 lob-info)) - 'lob)) - (rep (org-fill-template - org-babel-exp-call-line-template - `(("line" . ,(nth 0 lob-info)))))) - ;; If replacement is empty, completely remove the - ;; object/element, including any extra white space - ;; that might have been created when including - ;; results. - (if (equal rep "") - (delete-region - begin - (progn (goto-char end) - (if (not (eq type 'babel-call)) - (progn (skip-chars-forward " \t") (point)) - (skip-chars-forward " \r\t\n") - (line-beginning-position)))) - ;; Otherwise, preserve following white - ;; spaces/newlines and then, insert replacement - ;; string. - (goto-char begin) - (delete-region begin end) - (insert rep)))) - (src-block - (let* ((match-start (copy-marker (match-beginning 0))) - (ind (org-get-indentation)) - (headers - (cons - (org-element-property :language element) - (let ((params (org-element-property :parameters - element))) - (and params (org-split-string params "[ \t]+")))))) - ;; Take care of matched block: compute replacement - ;; string. In particular, a nil REPLACEMENT means - ;; the block should be left as-is while an empty - ;; string should remove the block. - (let ((replacement (progn (goto-char match-start) - (org-babel-exp-src-block headers)))) - (cond ((not replacement) (goto-char end)) - ((equal replacement "") - (goto-char end) - (skip-chars-forward " \r\t\n") - (beginning-of-line) - (delete-region begin (point))) - (t - (goto-char match-start) - (delete-region (point) - (save-excursion (goto-char end) - (line-end-position))) - (insert replacement) - (if (or org-src-preserve-indentation - (org-element-property :preserve-indent - element)) - ;; Indent only the code block markers. - (save-excursion (skip-chars-backward " \r\t\n") - (indent-line-to ind) - (goto-char match-start) - (indent-line-to ind)) - ;; Indent everything. - (indent-rigidly match-start (point) ind))))) - (set-marker match-start nil)))) - (set-marker begin nil) - (set-marker end nil))))))) - -(defun org-babel-in-example-or-verbatim () - "Return true if point is in example or verbatim code. -Example and verbatim code include escaped portions of -an org-mode buffer code that should be treated as normal -org-mode text." - (or (save-match-data - (save-excursion - (goto-char (point-at-bol)) - (looking-at "[ \t]*:[ \t]"))) - (org-in-verbatim-emphasis) - (org-in-block-p org-list-forbidden-blocks) - (org-between-regexps-p "^[ \t]*#\\+begin_src" "^[ \t]*#\\+end_src"))) + (regexp "\\(call\\|src\\)_\\|^[ \t]*#\\+\\(BEGIN_SRC\\|CALL:\\)") + ;; Get a pristine copy of current buffer so Babel + ;; references are properly resolved and source block + ;; context is preserved. + (org-babel-exp-reference-buffer (org-export-copy-buffer))) + (unwind-protect + (save-excursion + ;; First attach to every source block their original + ;; position, so that they can be retrieved within + ;; `org-babel-exp-reference-buffer', even after heavy + ;; modifications on current buffer. + ;; + ;; False positives are harmless, so we don't check if + ;; we're really at some Babel object. Moreover, + ;; `line-end-position' ensures that we propertize + ;; a noticeable part of the object, without affecting + ;; multiple objects on the same line. + (goto-char (point-min)) + (while (re-search-forward regexp nil t) + (let ((s (match-beginning 0))) + (put-text-property s (line-end-position) 'org-reference s))) + ;; Evaluate from top to bottom every Babel block + ;; encountered. + (goto-char (point-min)) + (while (re-search-forward regexp nil t) + (unless (save-match-data (org-in-commented-heading-p)) + (let* ((object? (match-end 1)) + (element (save-match-data + (if object? (org-element-context) + ;; No deep inspection if we're + ;; just looking for an element. + (org-element-at-point)))) + (type + (pcase (org-element-type element) + ;; Discard block elements if we're looking + ;; for inline objects. False results + ;; happen when, e.g., "call_" syntax is + ;; located within affiliated keywords: + ;; + ;; #+name: call_src + ;; #+begin_src ... + ((and (or `babel-call `src-block) (guard object?)) + nil) + (type type))) + (begin + (copy-marker (org-element-property :begin element))) + (end + (copy-marker + (save-excursion + (goto-char (org-element-property :end element)) + (skip-chars-backward " \r\t\n") + (point))))) + (pcase type + (`inline-src-block + (let* ((info + (org-babel-get-src-block-info nil element)) + (params (nth 2 info))) + (setf (nth 1 info) + (if (and (cdr (assq :noweb params)) + (string= "yes" + (cdr (assq :noweb params)))) + (org-babel-expand-noweb-references + info org-babel-exp-reference-buffer) + (nth 1 info))) + (goto-char begin) + (let ((replacement + (org-babel-exp-do-export info 'inline))) + (if (equal replacement "") + ;; Replacement code is empty: remove + ;; inline source block, including extra + ;; white space that might have been + ;; created when inserting results. + (delete-region begin + (progn (goto-char end) + (skip-chars-forward " \t") + (point))) + ;; Otherwise: remove inline src block but + ;; preserve following white spaces. Then + ;; insert value. + (delete-region begin end) + (insert replacement))))) + ((or `babel-call `inline-babel-call) + (org-babel-exp-do-export (org-babel-lob-get-info element) + 'lob) + (let ((rep + (org-fill-template + org-babel-exp-call-line-template + `(("line" . + ,(org-element-property :value element)))))) + ;; If replacement is empty, completely remove + ;; the object/element, including any extra + ;; white space that might have been created + ;; when including results. + (if (equal rep "") + (delete-region + begin + (progn (goto-char end) + (if (not (eq type 'babel-call)) + (progn (skip-chars-forward " \t") + (point)) + (skip-chars-forward " \r\t\n") + (line-beginning-position)))) + ;; Otherwise, preserve trailing + ;; spaces/newlines and then, insert + ;; replacement string. + (goto-char begin) + (delete-region begin end) + (insert rep)))) + (`src-block + (let ((match-start (copy-marker (match-beginning 0))) + (ind (org-get-indentation))) + ;; Take care of matched block: compute + ;; replacement string. In particular, a nil + ;; REPLACEMENT means the block is left as-is + ;; while an empty string removes the block. + (let ((replacement + (progn (goto-char match-start) + (org-babel-exp-src-block)))) + (cond ((not replacement) (goto-char end)) + ((equal replacement "") + (goto-char end) + (skip-chars-forward " \r\t\n") + (beginning-of-line) + (delete-region begin (point))) + (t + (goto-char match-start) + (delete-region (point) + (save-excursion + (goto-char end) + (line-end-position))) + (insert replacement) + (if (or org-src-preserve-indentation + (org-element-property + :preserve-indent element)) + ;; Indent only code block + ;; markers. + (save-excursion + (skip-chars-backward " \r\t\n") + (indent-line-to ind) + (goto-char match-start) + (indent-line-to ind)) + ;; Indent everything. + (indent-rigidly + match-start (point) ind))))) + (set-marker match-start nil)))) + (set-marker begin nil) + (set-marker end nil))))) + (kill-buffer org-babel-exp-reference-buffer) + (remove-text-properties (point-min) (point-max) '(org-reference))))))) (defun org-babel-exp-do-export (info type &optional hash) "Return a string with the exported content of a code block. The function respects the value of the :exports header argument." - (let ((silently (lambda () (let ((session (cdr (assoc :session (nth 2 info))))) - (when (not (and session (equal "none" session))) - (org-babel-exp-results info type 'silent))))) - (clean (lambda () (unless (eq type 'inline) (org-babel-remove-result info))))) - (case (intern (or (cdr (assoc :exports (nth 2 info))) "code")) - ('none (funcall silently) (funcall clean) "") - ('code (funcall silently) (funcall clean) (org-babel-exp-code info)) - ('results (org-babel-exp-results info type nil hash) "") - ('both (org-babel-exp-results info type nil hash) - (org-babel-exp-code info))))) + (let ((silently (lambda () (let ((session (cdr (assq :session (nth 2 info))))) + (unless (equal "none" session) + (org-babel-exp-results info type 'silent))))) + (clean (lambda () (if (eq type 'inline) + (org-babel-remove-inline-result) + (org-babel-remove-result info))))) + (pcase (or (cdr (assq :exports (nth 2 info))) "code") + ("none" (funcall silently) (funcall clean) "") + ("code" (funcall silently) (funcall clean) (org-babel-exp-code info type)) + ("results" (org-babel-exp-results info type nil hash) "") + ("both" + (org-babel-exp-results info type nil hash) + (org-babel-exp-code info type))))) (defcustom org-babel-exp-code-template "#+BEGIN_SRC %lang%switches%flags\n%body\n#+END_SRC" @@ -331,18 +317,42 @@ replaced with its value." :group 'org-babel :type 'string) -(defun org-babel-exp-code (info) +(defcustom org-babel-exp-inline-code-template + "src_%lang[%switches%flags]{%body}" + "Template used to export the body of inline code blocks. +This template may be customized to include additional information +such as the code block name, or the values of particular header +arguments. The template is filled out using `org-fill-template', +and the following %keys may be used. + + lang ------ the language of the code block + name ------ the name of the code block + body ------ the body of the code block + switches -- the switches associated to the code block + flags ----- the flags passed to the code block + +In addition to the keys mentioned above, every header argument +defined for the code block may be used as a key and will be +replaced with its value." + :group 'org-babel + :type 'string + :version "26.1" + :package-version '(Org . "8.3")) + +(defun org-babel-exp-code (info type) "Return the original code block formatted for export." (setf (nth 1 info) - (if (string= "strip-export" (cdr (assoc :noweb (nth 2 info)))) + (if (string= "strip-export" (cdr (assq :noweb (nth 2 info)))) (replace-regexp-in-string (org-babel-noweb-wrap) "" (nth 1 info)) (if (org-babel-noweb-p (nth 2 info) :export) (org-babel-expand-noweb-references - info (org-babel-exp-get-export-buffer)) + info org-babel-exp-reference-buffer) (nth 1 info)))) (org-fill-template - org-babel-exp-code-template + (if (eq type 'inline) + org-babel-exp-inline-code-template + org-babel-exp-code-template) `(("lang" . ,(nth 0 info)) ("body" . ,(org-escape-code-in-string (nth 1 info))) ("switches" . ,(let ((f (nth 3 info))) @@ -357,48 +367,41 @@ replaced with its value." (defun org-babel-exp-results (info type &optional silent hash) "Evaluate and return the results of the current code block for export. -Results are prepared in a manner suitable for export by org-mode. +Results are prepared in a manner suitable for export by Org mode. This function is called by `org-babel-exp-do-export'. The code block will be evaluated. Optional argument SILENT can be used to inhibit insertion of results into the buffer." - (when (and (or (eq org-export-babel-evaluate t) - (and (eq type 'inline) - (eq org-export-babel-evaluate 'inline-only))) - (not (and hash (equal hash (org-babel-current-result-hash))))) + (unless (and hash (equal hash (org-babel-current-result-hash))) (let ((lang (nth 0 info)) (body (if (org-babel-noweb-p (nth 2 info) :eval) (org-babel-expand-noweb-references - info (org-babel-exp-get-export-buffer)) + info org-babel-exp-reference-buffer) (nth 1 info))) (info (copy-sequence info)) (org-babel-current-src-block-location (point-marker))) - ;; skip code blocks which we can't evaluate + ;; Skip code blocks which we can't evaluate. (when (fboundp (intern (concat "org-babel-execute:" lang))) (org-babel-eval-wipe-error-buffer) - (prog1 nil - (setf (nth 1 info) body) - (setf (nth 2 info) - (org-babel-exp-in-export-file lang - (org-babel-process-params - (org-babel-merge-params - (nth 2 info) - `((:results . ,(if silent "silent" "replace"))))))) - (cond - ((equal type 'block) - (org-babel-execute-src-block nil info)) - ((equal type 'inline) - ;; position the point on the inline source block allowing - ;; `org-babel-insert-result' to check that the block is - ;; inline - (re-search-backward "[ \f\t\n\r\v]" nil t) - (re-search-forward org-babel-inline-src-block-regexp nil t) - (re-search-backward "src_" nil t) + (setf (nth 1 info) body) + (setf (nth 2 info) + (org-babel-exp--at-source + (org-babel-process-params + (org-babel-merge-params + (nth 2 info) + `((:results . ,(if silent "silent" "replace"))))))) + (pcase type + (`block (org-babel-execute-src-block nil info)) + (`inline + ;; Position the point on the inline source block + ;; allowing `org-babel-insert-result' to check that the + ;; block is inline. + (goto-char (nth 5 info)) (org-babel-execute-src-block nil info)) - ((equal type 'lob) - (save-excursion - (re-search-backward org-babel-lob-one-liner-regexp nil t) - (let (org-confirm-babel-evaluate) - (org-babel-execute-src-block nil info)))))))))) + (`lob + (save-excursion + (goto-char (nth 5 info)) + (let (org-confirm-babel-evaluate) + (org-babel-execute-src-block nil info))))))))) (provide 'ob-exp) diff --git a/lisp/org/ob-forth.el b/lisp/org/ob-forth.el new file mode 100644 index 00000000000..bb8d9af4789 --- /dev/null +++ b/lisp/org/ob-forth.el @@ -0,0 +1,87 @@ +;;; ob-forth.el --- Babel Functions for Forth -*- lexical-binding: t; -*- + +;; Copyright (C) 2014-2017 Free Software Foundation, Inc. + +;; Author: Eric Schulte +;; Keywords: literate programming, reproducible research, forth +;; Homepage: http://orgmode.org + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Requires the gforth forth compiler and `forth-mode' (see below). +;; https://www.gnu.org/software/gforth/ + +;;; Requirements: + +;; Session evaluation requires the gforth forth compiler as well as +;; `forth-mode' which is distributed with gforth (in gforth.el). + +;;; Code: +(require 'ob) + +(declare-function forth-proc "ext:gforth" ()) +(declare-function org-trim "org" (s &optional keep-lead)) + +(defvar org-babel-default-header-args:forth '((:session . "yes")) + "Default header arguments for forth code blocks.") + +(defun org-babel-execute:forth (body params) + "Execute a block of Forth code with org-babel. +This function is called by `org-babel-execute-src-block'" + (if (string= "none" (cdr (assq :session params))) + (error "Non-session evaluation not supported for Forth code blocks") + (let ((all-results (org-babel-forth-session-execute body params))) + (if (member "output" (cdr (assq :result-params params))) + (mapconcat #'identity all-results "\n") + (car (last all-results)))))) + +(defun org-babel-forth-session-execute (body params) + (require 'forth-mode) + (let ((proc (forth-proc)) + (rx " \\(\n:\\|compiled\n\\\|ok\n\\)") + (result-start)) + (with-current-buffer (process-buffer (forth-proc)) + (mapcar (lambda (line) + (setq result-start (progn (goto-char (process-mark proc)) + (point))) + (comint-send-string proc (concat line "\n")) + ;; wait for forth to say "ok" + (while (not (progn (goto-char result-start) + (re-search-forward rx nil t))) + (accept-process-output proc 0.01)) + (let ((case (match-string 1))) + (cond + ((string= "ok\n" case) + ;; Collect intermediate output. + (buffer-substring (+ result-start 1 (length line)) + (match-beginning 0))) + ((string= "compiled\n" case)) + ;; Ignore partial compilation. + ((string= "\n:" case) + ;; Report errors. + (org-babel-eval-error-notify 1 + (buffer-substring + (+ (match-beginning 0) 1) (point-max))) nil)))) + (split-string (org-trim + (org-babel-expand-body:generic body params)) + "\n" + 'omit-nulls))))) + +(provide 'ob-forth) + +;;; ob-forth.el ends here diff --git a/lisp/org/ob-fortran.el b/lisp/org/ob-fortran.el index 6a6112df9bd..50b12fc256a 100644 --- a/lisp/org/ob-fortran.el +++ b/lisp/org/ob-fortran.el @@ -1,4 +1,4 @@ -;;; ob-fortran.el --- org-babel functions for fortran +;;; ob-fortran.el --- Babel Functions for Fortran -*- lexical-binding: t; -*- ;; Copyright (C) 2011-2017 Free Software Foundation, Inc. @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -29,10 +29,12 @@ ;;; Code: (require 'ob) (require 'cc-mode) +(require 'cl-lib) (declare-function org-entry-get "org" (pom property &optional inherit literal-nil)) -(declare-function org-every "org" (pred seq)) +(declare-function org-remove-indentation "org" (code &optional n)) +(declare-function org-trim "org" (s &optional keep-lead)) (defvar org-babel-tangle-lang-exts) (add-to-list 'org-babel-tangle-lang-exts '("fortran" . "F90")) @@ -47,43 +49,42 @@ "This function should only be called by `org-babel-execute:fortran'" (let* ((tmp-src-file (org-babel-temp-file "fortran-src-" ".F90")) (tmp-bin-file (org-babel-temp-file "fortran-bin-" org-babel-exeext)) - (cmdline (cdr (assoc :cmdline params))) - (flags (cdr (assoc :flags params))) - (full-body (org-babel-expand-body:fortran body params)) - (compile - (progn - (with-temp-file tmp-src-file (insert full-body)) - (org-babel-eval - (format "%s -o %s %s %s" - org-babel-fortran-compiler - (org-babel-process-file-name tmp-bin-file) - (mapconcat 'identity - (if (listp flags) flags (list flags)) " ") - (org-babel-process-file-name tmp-src-file)) "")))) + (cmdline (cdr (assq :cmdline params))) + (flags (cdr (assq :flags params))) + (full-body (org-babel-expand-body:fortran body params))) + (with-temp-file tmp-src-file (insert full-body)) + (org-babel-eval + (format "%s -o %s %s %s" + org-babel-fortran-compiler + (org-babel-process-file-name tmp-bin-file) + (mapconcat 'identity + (if (listp flags) flags (list flags)) " ") + (org-babel-process-file-name tmp-src-file)) "") (let ((results - (org-babel-trim - (org-babel-eval - (concat tmp-bin-file (if cmdline (concat " " cmdline) "")) "")))) + (org-trim + (org-remove-indentation + (org-babel-eval + (concat tmp-bin-file (if cmdline (concat " " cmdline) "")) ""))))) (org-babel-reassemble-table - (org-babel-result-cond (cdr (assoc :result-params params)) + (org-babel-result-cond (cdr (assq :result-params params)) (org-babel-read results) (let ((tmp-file (org-babel-temp-file "f-"))) (with-temp-file tmp-file (insert results)) (org-babel-import-elisp-from-file tmp-file))) (org-babel-pick-name - (cdr (assoc :colname-names params)) (cdr (assoc :colnames params))) + (cdr (assq :colname-names params)) (cdr (assq :colnames params))) (org-babel-pick-name - (cdr (assoc :rowname-names params)) (cdr (assoc :rownames params))))))) + (cdr (assq :rowname-names params)) (cdr (assq :rownames params))))))) (defun org-babel-expand-body:fortran (body params) "Expand a block of fortran or fortran code with org-babel according to -it's header arguments." - (let ((vars (mapcar #'cdr (org-babel-get-header params :var))) - (main-p (not (string= (cdr (assoc :main params)) "no"))) - (includes (or (cdr (assoc :includes params)) +its header arguments." + (let ((vars (org-babel--get-vars params)) + (main-p (not (string= (cdr (assq :main params)) "no"))) + (includes (or (cdr (assq :includes params)) (org-babel-read (org-entry-get nil "includes" t)))) (defines (org-babel-read - (or (cdr (assoc :defines params)) + (or (cdr (assq :defines params)) (org-babel-read (org-entry-get nil "defines" t)))))) (mapconcat 'identity (list @@ -107,17 +108,17 @@ it's header arguments." (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)) - (let ((vars (mapcar #'cdr (org-babel-get-header params :var)))) + (let ((vars (org-babel--get-vars params))) (if vars (error "Cannot use :vars if `program' statement is present")) body) (format "program main\n%s\nend program main\n" body))) -(defun org-babel-prep-session:fortran (session params) +(defun org-babel-prep-session:fortran (_session _params) "This function does nothing as fortran is a compiled language with no support for sessions" (error "Fortran is a compiled languages -- no support for sessions")) -(defun org-babel-load-session:fortran (session body params) +(defun org-babel-load-session:fortran (_session _body _params) "This function does nothing as fortran is a compiled language with no support for sessions" (error "Fortran is a compiled languages -- no support for sessions")) @@ -145,7 +146,7 @@ of the same value." (format "character(len=%d), parameter :: %S = '%s'\n" (length val) var val)) ;; val is a matrix - ((and (listp val) (org-every #'listp val)) + ((and (listp val) (cl-every #'listp val)) (format "real, parameter :: %S(%d,%d) = transpose( reshape( %s , (/ %d, %d /) ) )\n" var (length val) (length (car val)) (org-babel-fortran-transform-list val) diff --git a/lisp/org/ob-gnuplot.el b/lisp/org/ob-gnuplot.el index 82b103e52cd..b0743f60475 100644 --- a/lisp/org/ob-gnuplot.el +++ b/lisp/org/ob-gnuplot.el @@ -1,4 +1,4 @@ -;;; ob-gnuplot.el --- org-babel functions for gnuplot evaluation +;;; ob-gnuplot.el --- Babel Functions for Gnuplot -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -39,12 +39,10 @@ ;;; Code: (require 'ob) -(eval-when-compile (require 'cl)) -(declare-function org-time-string-to-time "org" (s &optional buffer pos)) +(declare-function org-time-string-to-time "org" (s)) (declare-function org-combine-plists "org" (&rest plists)) -(declare-function orgtbl-to-generic "org-table" - (table params &optional backend)) +(declare-function orgtbl-to-generic "org-table" (table params)) (declare-function gnuplot-mode "ext:gnuplot-mode" ()) (declare-function gnuplot-send-string-to-gnuplot "ext:gnuplot-mode" (str txt)) (declare-function gnuplot-send-buffer-to-gnuplot "ext:gnuplot-mode" ()) @@ -65,7 +63,7 @@ (term . :any)) "Gnuplot specific header args.") -(defvar org-babel-gnuplot-timestamp-fmt nil) +(defvar org-babel-gnuplot-timestamp-fmt nil) ; Dynamically scoped. (defvar *org-babel-gnuplot-missing* nil) @@ -81,7 +79,7 @@ Dumps all vectors into files and returns an association list of variable names and the related value to be used in the gnuplot code." - (let ((*org-babel-gnuplot-missing* (cdr (assoc :missing params)))) + (let ((*org-babel-gnuplot-missing* (cdr (assq :missing params)))) (mapcar (lambda (pair) (cons @@ -95,38 +93,33 @@ code." (if tablep val (mapcar 'list val))) (org-babel-temp-file "gnuplot-") params) val)))) - (mapcar #'cdr (org-babel-get-header params :var))))) + (org-babel--get-vars params)))) (defun org-babel-expand-body:gnuplot (body params) "Expand BODY according to PARAMS, return the expanded body." (save-window-excursion (let* ((vars (org-babel-gnuplot-process-vars params)) - (out-file (cdr (assoc :file params))) - (prologue (cdr (assoc :prologue params))) - (epilogue (cdr (assoc :epilogue params))) - (term (or (cdr (assoc :term params)) + (out-file (cdr (assq :file params))) + (prologue (cdr (assq :prologue params))) + (epilogue (cdr (assq :epilogue params))) + (term (or (cdr (assq :term params)) (when out-file (let ((ext (file-name-extension out-file))) (or (cdr (assoc (intern (downcase ext)) *org-babel-gnuplot-terms*)) ext))))) - (cmdline (cdr (assoc :cmdline params))) - (title (cdr (assoc :title params))) - (lines (cdr (assoc :line params))) - (sets (cdr (assoc :set params))) - (x-labels (cdr (assoc :xlabels params))) - (y-labels (cdr (assoc :ylabels params))) - (timefmt (cdr (assoc :timefmt params))) - (time-ind (or (cdr (assoc :timeind params)) + (title (cdr (assq :title params))) + (lines (cdr (assq :line params))) + (sets (cdr (assq :set params))) + (x-labels (cdr (assq :xlabels params))) + (y-labels (cdr (assq :ylabels params))) + (timefmt (cdr (assq :timefmt params))) + (time-ind (or (cdr (assq :timeind params)) (when timefmt 1))) - (missing (cdr (assoc :missing params))) - (add-to-body (lambda (text) (setq body (concat text "\n" body)))) - output) + (add-to-body (lambda (text) (setq body (concat text "\n" body))))) ;; append header argument settings to body (when title (funcall add-to-body (format "set title '%s'" title))) (when lines (mapc (lambda (el) (funcall add-to-body el)) lines)) - (when missing - (funcall add-to-body (format "set datafile missing '%s'" missing))) (when sets (mapc (lambda (el) (funcall add-to-body (format "set %s" el))) sets)) (when x-labels @@ -175,9 +168,8 @@ code." "Execute a block of Gnuplot code. This function is called by `org-babel-execute-src-block'." (require 'gnuplot) - (let ((session (cdr (assoc :session params))) - (result-type (cdr (assoc :results params))) - (out-file (cdr (assoc :file params))) + (let ((session (cdr (assq :session params))) + (result-type (cdr (assq :results params))) (body (org-babel-expand-body:gnuplot body params)) output) (save-window-excursion @@ -195,7 +187,7 @@ This function is called by `org-babel-execute-src-block'." script-file (if (member system-type '(cygwin windows-nt ms-dos)) t nil))))) - (message output)) + (message "%s" output)) (with-temp-buffer (insert (concat body "\n")) (gnuplot-mode) @@ -210,10 +202,12 @@ This function is called by `org-babel-execute-src-block'." (var-lines (org-babel-variable-assignments:gnuplot params))) (message "%S" session) (org-babel-comint-in-buffer session - (mapc (lambda (var-line) - (insert var-line) (comint-send-input nil t) - (org-babel-comint-wait-for-output session) - (sit-for .1) (goto-char (point-max))) var-lines)) + (dolist (var-line var-lines) + (insert var-line) + (comint-send-input nil t) + (org-babel-comint-wait-for-output session) + (sit-for .1) + (goto-char (point-max)))) session)) (defun org-babel-load-session:gnuplot (session body params) @@ -232,7 +226,7 @@ This function is called by `org-babel-execute-src-block'." (org-babel-gnuplot-process-vars params))) (defvar gnuplot-buffer) -(defun org-babel-gnuplot-initiate-session (&optional session params) +(defun org-babel-gnuplot-initiate-session (&optional session _params) "Initiate a gnuplot session. If there is not a current inferior-process-buffer in SESSION then create one. Return the initialized session. The current @@ -268,15 +262,13 @@ then create one. Return the initialized session. The current "Export TABLE to DATA-FILE in a format readable by gnuplot. Pass PARAMS through to `orgtbl-to-generic' when exporting TABLE." (with-temp-file data-file - (make-local-variable 'org-babel-gnuplot-timestamp-fmt) - (setq org-babel-gnuplot-timestamp-fmt (or - (plist-get params :timefmt) - "%Y-%m-%d-%H:%M:%S")) - (insert (orgtbl-to-generic - table - (org-combine-plists - '(:sep "\t" :fmt org-babel-gnuplot-quote-tsv-field) - params)))) + (insert (let ((org-babel-gnuplot-timestamp-fmt + (or (plist-get params :timefmt) "%Y-%m-%d-%H:%M:%S"))) + (orgtbl-to-generic + table + (org-combine-plists + '(:sep "\t" :fmt org-babel-gnuplot-quote-tsv-field) + params))))) data-file) (provide 'ob-gnuplot) diff --git a/lisp/org/ob-groovy.el b/lisp/org/ob-groovy.el new file mode 100644 index 00000000000..1e602dd0cf1 --- /dev/null +++ b/lisp/org/ob-groovy.el @@ -0,0 +1,116 @@ +;;; ob-groovy.el --- Babel Functions for Groovy -*- lexical-binding: t; -*- + +;; Copyright (C) 2013-2017 Free Software Foundation, Inc. + +;; Author: Miro Bezjak +;; Keywords: literate programming, reproducible research +;; Homepage: http://orgmode.org + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: +;; Currently only supports the external execution. No session support yet. + +;;; Requirements: +;; - Groovy language :: http://groovy.codehaus.org +;; - Groovy major mode :: Can be installed from MELPA or +;; https://github.com/russel/Emacs-Groovy-Mode + +;;; Code: +(require 'ob) + +(defvar org-babel-tangle-lang-exts) ;; Autoloaded +(add-to-list 'org-babel-tangle-lang-exts '("groovy" . "groovy")) +(defvar org-babel-default-header-args:groovy '()) +(defcustom org-babel-groovy-command "groovy" + "Name of the command to use for executing Groovy code. +May be either a command in the path, like groovy +or an absolute path name, like /usr/local/bin/groovy +parameters may be used, like groovy -v" + :group 'org-babel + :version "24.3" + :type 'string) + +(defun org-babel-execute:groovy (body params) + "Execute a block of Groovy code with org-babel. This function is +called by `org-babel-execute-src-block'" + (message "executing Groovy source code block") + (let* ((processed-params (org-babel-process-params params)) + (session (org-babel-groovy-initiate-session (nth 0 processed-params))) + (result-params (nth 2 processed-params)) + (result-type (cdr (assq :result-type params))) + (full-body (org-babel-expand-body:generic + body params)) + (result (org-babel-groovy-evaluate + session full-body result-type result-params))) + + (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)))))) + +(defvar org-babel-groovy-wrapper-method + + "class Runner extends Script { + def out = new PrintWriter(new ByteArrayOutputStream()) + def run() { %s } +} + +println(new Runner().run()) +") + + +(defun org-babel-groovy-evaluate + (session body &optional result-type result-params) + "Evaluate BODY in external Groovy process. +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." + (when session (error "Sessions are not (yet) supported for Groovy")) + (pcase result-type + (`output + (let ((src-file (org-babel-temp-file "groovy-"))) + (progn (with-temp-file src-file (insert body)) + (org-babel-eval + (concat org-babel-groovy-command " " src-file) "")))) + (`value + (let* ((src-file (org-babel-temp-file "groovy-")) + (wrapper (format org-babel-groovy-wrapper-method body))) + (with-temp-file src-file (insert wrapper)) + (let ((raw (org-babel-eval + (concat org-babel-groovy-command " " src-file) ""))) + (org-babel-result-cond result-params + raw + (org-babel-script-escape raw))))))) + + +(defun org-babel-prep-session:groovy (_session _params) + "Prepare SESSION according to the header arguments specified in PARAMS." + (error "Sessions are not (yet) supported for Groovy")) + +(defun org-babel-groovy-initiate-session (&optional _session) + "If there is not a current inferior-process-buffer in SESSION +then create. Return the initialized session. Sessions are not +supported in Groovy." + nil) + +(provide 'ob-groovy) + + + +;;; ob-groovy.el ends here diff --git a/lisp/org/ob-haskell.el b/lisp/org/ob-haskell.el index ce6b8edbeb8..cc78bec33d6 100644 --- a/lisp/org/ob-haskell.el +++ b/lisp/org/ob-haskell.el @@ -1,4 +1,4 @@ -;;; ob-haskell.el --- org-babel functions for haskell evaluation +;;; ob-haskell.el --- Babel Functions for Haskell -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -41,9 +41,9 @@ ;;; Code: (require 'ob) (require 'comint) -(eval-when-compile (require 'cl)) (declare-function org-remove-indentation "org" (code &optional n)) +(declare-function org-trim "org" (s &optional keep-lead)) (declare-function haskell-mode "ext:haskell-mode" ()) (declare-function run-haskell "ext:inf-haskell" (&optional arg)) (declare-function inferior-haskell-load-file @@ -61,42 +61,35 @@ (defun org-babel-execute:haskell (body params) "Execute a block of Haskell code." - (let* ((session (cdr (assoc :session params))) - (vars (mapcar #'cdr (org-babel-get-header params :var))) - (result-type (cdr (assoc :result-type params))) + (let* ((session (cdr (assq :session params))) + (result-type (cdr (assq :result-type params))) (full-body (org-babel-expand-body:generic body params (org-babel-variable-assignments:haskell params))) (session (org-babel-haskell-initiate-session session params)) (raw (org-babel-comint-with-output (session org-babel-haskell-eoe t full-body) - (insert (org-babel-trim full-body)) + (insert (org-trim full-body)) (comint-send-input nil t) (insert org-babel-haskell-eoe) (comint-send-input nil t))) (results (mapcar - #'org-babel-haskell-read-string + #'org-babel-strip-quotes (cdr (member org-babel-haskell-eoe - (reverse (mapcar #'org-babel-trim raw))))))) + (reverse (mapcar #'org-trim raw))))))) (org-babel-reassemble-table (let ((result - (case result-type - (output (mapconcat #'identity (reverse (cdr results)) "\n")) - (value (car results))))) - (org-babel-result-cond (cdr (assoc :result-params params)) - result (org-babel-haskell-table-or-string result))) - (org-babel-pick-name (cdr (assoc :colname-names params)) - (cdr (assoc :colname-names params))) - (org-babel-pick-name (cdr (assoc :rowname-names params)) - (cdr (assoc :rowname-names params)))))) - -(defun org-babel-haskell-read-string (string) - "Strip \\\"s from around a haskell string." - (if (string-match "^\"\\([^\000]+\\)\"$" string) - (match-string 1 string) - string)) - -(defun org-babel-haskell-initiate-session (&optional session params) + (pcase result-type + (`output (mapconcat #'identity (reverse (cdr results)) "\n")) + (`value (car results))))) + (org-babel-result-cond (cdr (assq :result-params params)) + result (org-babel-script-escape result))) + (org-babel-pick-name (cdr (assq :colname-names params)) + (cdr (assq :colname-names params))) + (org-babel-pick-name (cdr (assq :rowname-names params)) + (cdr (assq :rowname-names 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 then create one. Return the initialized session." @@ -131,13 +124,7 @@ then create one. Return the initialized session." (format "let %s = %s" (car pair) (org-babel-haskell-var-to-haskell (cdr pair)))) - (mapcar #'cdr (org-babel-get-header params :var)))) - -(defun org-babel-haskell-table-or-string (results) - "Convert RESULTS to an Emacs-lisp table or string. -If RESULTS look like a table, then convert them into an -Emacs-lisp table, otherwise return the results as a string." - (org-babel-script-escape results)) + (org-babel--get-vars params))) (defun org-babel-haskell-var-to-haskell (var) "Convert an elisp value VAR into a haskell variable. @@ -157,7 +144,7 @@ specifying a variable of the same value." When called with a prefix argument the resulting .lhs file will be exported to a .tex file. This function will create two new files, base-name.lhs and base-name.tex where -base-name is the name of the current org-mode file. +base-name is the name of the current Org file. Note that all standard Babel literate programming constructs (header arguments, no-web syntax etc...) are ignored." @@ -185,12 +172,12 @@ constructs (header arguments, no-web syntax etc...) are ignored." (save-match-data (setq indentation (length (match-string 1)))) (replace-match (save-match-data (concat - "#+begin_latex\n\\begin{code}\n" + "#+begin_export latex\n\\begin{code}\n" (if (or preserve-indentp (string-match "-i" (match-string 2))) (match-string 3) (org-remove-indentation (match-string 3))) - "\n\\end{code}\n#+end_latex\n")) + "\n\\end{code}\n#+end_export\n")) t t) (indent-code-rigidly (match-beginning 0) (match-end 0) indentation))) (save-excursion diff --git a/lisp/org/ob-hledger.el b/lisp/org/ob-hledger.el new file mode 100644 index 00000000000..57ab8af4f30 --- /dev/null +++ b/lisp/org/ob-hledger.el @@ -0,0 +1,70 @@ +;; ob-hledger.el --- Babel Functions for hledger -*- lexical-binding: t; -*- + +;; Copyright (C) 2010-2017 Free Software Foundation, Inc. + +;; Author: Simon Michael +;; Keywords: literate programming, reproducible research, plain text accounting +;; Homepage: http://orgmode.org + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Babel support for evaluating hledger entries. +;; +;; Based on ob-ledger.el. +;; If the source block is empty, hledger will use a default journal file, +;; 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. + +;;; Code: +(require 'ob) + +(defvar org-babel-default-header-args:hledger + '((:results . "output") (:exports . "results") (:cmdline . "bal")) + "Default arguments to use when evaluating a hledger source block.") + +(defun org-babel-execute:hledger (body params) + "Execute a block of hledger entries with org-babel. +This function is called by `org-babel-execute-src-block'." + (message "executing hledger source code block") + (letrec ( ;(result-params (split-string (or (cdr (assq :results params)) ""))) + (cmdline (cdr (assq :cmdline params))) + (in-file (org-babel-temp-file "hledger-")) + (out-file (org-babel-temp-file "hledger-output-")) + (hledgercmd (concat "hledger" + (if (> (length body) 0) + (concat " -f " (org-babel-process-file-name in-file)) + "") + " " cmdline))) + (with-temp-file in-file (insert body)) +;; TODO This is calling for some refactoring: +;; (concat "hledger" (if ...) " " cmdline) +;; could be built only once and bound to a symbol. + (message "%s" hledgercmd) + (with-output-to-string + (shell-command (concat hledgercmd " > " (org-babel-process-file-name out-file)))) + (with-temp-buffer (insert-file-contents out-file) (buffer-string)))) + +(defun org-babel-prep-session:hledger (_session _params) + (error "hledger does not support sessions")) + +(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 1d3a42aa38a..35b92ef62f5 100644 --- a/lisp/org/ob-io.el +++ b/lisp/org/ob-io.el @@ -1,4 +1,4 @@ -;;; ob-io.el --- org-babel functions for Io evaluation +;;; ob-io.el --- Babel Functions for Io -*- lexical-binding: t; -*- ;; Copyright (C) 2012-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; Currently only supports the external execution. No session support yet. @@ -33,7 +33,6 @@ ;;; Code: (require 'ob) -(eval-when-compile (require 'cl)) (defvar org-babel-tangle-lang-exts) ;; Autoloaded (add-to-list 'org-babel-tangle-lang-exts '("io" . "io")) @@ -47,9 +46,8 @@ called by `org-babel-execute-src-block'" (message "executing Io source code block") (let* ((processed-params (org-babel-process-params params)) (session (org-babel-io-initiate-session (nth 0 processed-params))) - (vars (nth 1 processed-params)) (result-params (nth 2 processed-params)) - (result-type (cdr (assoc :result-type params))) + (result-type (cdr (assq :result-type params))) (full-body (org-babel-expand-body:generic body params)) (result (org-babel-io-evaluate @@ -58,17 +56,9 @@ called by `org-babel-execute-src-block'" (org-babel-reassemble-table result (org-babel-pick-name - (cdr (assoc :colname-names params)) (cdr (assoc :colnames params))) + (cdr (assq :colname-names params)) (cdr (assq :colnames params))) (org-babel-pick-name - (cdr (assoc :rowname-names params)) (cdr (assoc :rownames params)))))) - - -(defun org-babel-io-table-or-string (results) - "Convert RESULTS into an appropriate elisp value. -If RESULTS look like a table, then convert them into an -Emacs-lisp table, otherwise return the results as a string." - (org-babel-script-escape results)) - + (cdr (assq :rowname-names params)) (cdr (assq :rownames params)))))) (defvar org-babel-io-wrapper-method "( @@ -79,33 +69,33 @@ Emacs-lisp table, otherwise return the results as a string." (defun org-babel-io-evaluate (session body &optional result-type result-params) "Evaluate BODY in external Io process. -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 +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." (when session (error "Sessions are not (yet) supported for Io")) - (case result-type - (output + (pcase result-type + (`output (if (member "repl" result-params) (org-babel-eval org-babel-io-command body) (let ((src-file (org-babel-temp-file "io-"))) (progn (with-temp-file src-file (insert body)) (org-babel-eval (concat org-babel-io-command " " src-file) ""))))) - (value (let* ((src-file (org-babel-temp-file "io-")) - (wrapper (format org-babel-io-wrapper-method body))) - (with-temp-file src-file (insert wrapper)) - (let ((raw (org-babel-eval - (concat org-babel-io-command " " src-file) ""))) - (org-babel-result-cond result-params - raw - (org-babel-io-table-or-string raw))))))) + (`value (let* ((src-file (org-babel-temp-file "io-")) + (wrapper (format org-babel-io-wrapper-method body))) + (with-temp-file src-file (insert wrapper)) + (let ((raw (org-babel-eval + (concat org-babel-io-command " " src-file) ""))) + (org-babel-result-cond result-params + raw + (org-babel-script-escape raw))))))) -(defun org-babel-prep-session:io (session params) +(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")) -(defun org-babel-io-initiate-session (&optional session) +(defun org-babel-io-initiate-session (&optional _session) "If there is not a current inferior-process-buffer in SESSION then create. Return the initialized session. Sessions are not supported in Io." diff --git a/lisp/org/ob-java.el b/lisp/org/ob-java.el index 70a10e0131a..608e2e8858a 100644 --- a/lisp/org/ob-java.el +++ b/lisp/org/ob-java.el @@ -1,4 +1,4 @@ -;;; ob-java.el --- org-babel functions for java evaluation +;;; ob-java.el --- Babel Functions for Java -*- lexical-binding: t; -*- ;; Copyright (C) 2011-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -32,41 +32,51 @@ (defvar org-babel-tangle-lang-exts) (add-to-list 'org-babel-tangle-lang-exts '("java" . "java")) -(defvar org-babel-java-command "java" - "Name of the java command.") - -(defvar org-babel-java-compiler "javac" - "Name of the java compiler.") +(defcustom org-babel-java-command "java" + "Name of the java command. +May be either a command in the path, like java +or an absolute path name, like /usr/local/bin/java +parameters may be used, like java -verbose" + :group 'org-babel + :version "24.3" + :type 'string) + +(defcustom org-babel-java-compiler "javac" + "Name of the java compiler. +May be either a command in the path, like javac +or an absolute path name, like /usr/local/bin/javac +parameters may be used, like javac -verbose" + :group 'org-babel + :version "24.3" + :type 'string) (defun org-babel-execute:java (body params) - (let* ((classname (or (cdr (assoc :classname params)) + (let* ((classname (or (cdr (assq :classname params)) (error "Can't compile a java block without a classname"))) (packagename (file-name-directory classname)) (src-file (concat classname ".java")) - (cmpflag (or (cdr (assoc :cmpflag params)) "")) - (cmdline (or (cdr (assoc :cmdline params)) "")) - (full-body (org-babel-expand-body:generic body params)) - (compile - (progn (with-temp-file src-file (insert full-body)) - (org-babel-eval - (concat org-babel-java-compiler - " " cmpflag " " src-file) "")))) + (cmpflag (or (cdr (assq :cmpflag params)) "")) + (cmdline (or (cdr (assq :cmdline params)) "")) + (full-body (org-babel-expand-body:generic body params))) + (with-temp-file src-file (insert full-body)) + (org-babel-eval + (concat org-babel-java-compiler " " cmpflag " " src-file) "") ;; created package-name directories if missing (unless (or (not packagename) (file-exists-p packagename)) (make-directory packagename 'parents)) (let ((results (org-babel-eval (concat org-babel-java-command " " cmdline " " classname) ""))) (org-babel-reassemble-table - (org-babel-result-cond (cdr (assoc :result-params params)) + (org-babel-result-cond (cdr (assq :result-params params)) (org-babel-read results) (let ((tmp-file (org-babel-temp-file "c-"))) (with-temp-file tmp-file (insert results)) (org-babel-import-elisp-from-file tmp-file))) (org-babel-pick-name - (cdr (assoc :colname-names params)) (cdr (assoc :colnames params))) + (cdr (assq :colname-names params)) (cdr (assq :colnames params))) (org-babel-pick-name - (cdr (assoc :rowname-names params)) (cdr (assoc :rownames params))))))) + (cdr (assq :rowname-names params)) (cdr (assq :rownames params))))))) (provide 'ob-java) diff --git a/lisp/org/ob-js.el b/lisp/org/ob-js.el index f4f8116dfd7..e344b7a53c5 100644 --- a/lisp/org/ob-js.el +++ b/lisp/org/ob-js.el @@ -1,4 +1,4 @@ -;;; ob-js.el --- org-babel functions for Javascript +;;; ob-js.el --- Babel Functions for Javascript -*- lexical-binding: t; -*- ;; Copyright (C) 2010-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -39,7 +39,6 @@ ;;; Code: (require 'ob) -(eval-when-compile (require 'cl)) (declare-function run-mozilla "ext:moz" (arg)) @@ -56,20 +55,20 @@ :type 'string) (defvar org-babel-js-function-wrapper - "require('sys').print(require('sys').inspect(function(){%s}()));" + "require('sys').print(require('sys').inspect(function(){\n%s\n}()));" "Javascript code to print value of body.") (defun org-babel-execute:js (body params) "Execute a block of Javascript code with org-babel. This function is called by `org-babel-execute-src-block'" - (let* ((org-babel-js-cmd (or (cdr (assoc :cmd params)) org-babel-js-cmd)) - (result-type (cdr (assoc :result-type params))) + (let* ((org-babel-js-cmd (or (cdr (assq :cmd params)) org-babel-js-cmd)) + (result-type (cdr (assq :result-type params))) (full-body (org-babel-expand-body:generic body params (org-babel-variable-assignments:js params))) - (result (if (not (string= (cdr (assoc :session params)) "none")) + (result (if (not (string= (cdr (assq :session params)) "none")) ;; session evaluation (let ((session (org-babel-prep-session:js - (cdr (assoc :session params)) params))) + (cdr (assq :session params)) params))) (nth 1 (org-babel-comint-with-output (session (format "%S" org-babel-js-eoe) t body) @@ -89,7 +88,7 @@ This function is called by `org-babel-execute-src-block'" (org-babel-eval (format "%s %s" org-babel-js-cmd (org-babel-process-file-name script-file)) ""))))) - (org-babel-result-cond (cdr (assoc :result-params params)) + (org-babel-result-cond (cdr (assq :result-params params)) result (org-babel-js-read result)))) (defun org-babel-js-read (results) @@ -97,14 +96,17 @@ This function is called by `org-babel-execute-src-block'" If RESULTS look like a table, then convert them into an Emacs-lisp table, otherwise return the results as a string." (org-babel-read - (if (and (stringp results) (string-match "^\\[.+\\]$" results)) + (if (and (stringp results) + (string-prefix-p "[" results) + (string-suffix-p "]" results)) (org-babel-read (concat "'" (replace-regexp-in-string "\\[" "(" (replace-regexp-in-string "\\]" ")" (replace-regexp-in-string - ", " " " (replace-regexp-in-string - "'" "\"" results)))))) + ",[[:space:]]" " " + (replace-regexp-in-string + "'" "\"" results)))))) results))) (defun org-babel-js-var-to-js (var) @@ -113,7 +115,7 @@ Convert an elisp value into a string of js source code specifying a variable of the same value." (if (listp var) (concat "[" (mapconcat #'org-babel-js-var-to-js var ", ") "]") - (format "%S" var))) + (replace-regexp-in-string "\n" "\\\\n" (format "%S" var)))) (defun org-babel-prep-session:js (session params) "Prepare SESSION according to the header arguments specified in PARAMS." @@ -133,7 +135,7 @@ specifying a variable of the same value." (mapcar (lambda (pair) (format "var %s=%s;" (car pair) (org-babel-js-var-to-js (cdr pair)))) - (mapcar #'cdr (org-babel-get-header params :var)))) + (org-babel--get-vars params))) (defun org-babel-js-initiate-session (&optional session) "If there is not a current inferior-process-buffer in SESSION diff --git a/lisp/org/ob-keys.el b/lisp/org/ob-keys.el index b71fba416f7..170c00636c3 100644 --- a/lisp/org/ob-keys.el +++ b/lisp/org/ob-keys.el @@ -1,4 +1,4 @@ -;;; ob-keys.el --- key bindings for org-babel +;;; ob-keys.el --- Key Bindings for Babel -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. @@ -19,12 +19,12 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: -;; Add org-babel keybindings to the org-mode keymap for exposing -;; org-babel functions. These will all share a common prefix. See +;; Add Org Babel keybindings to the Org mode keymap for exposing +;; Org Babel functions. These will all share a common prefix. See ;; the value of `org-babel-key-bindings' for a list of interactive ;; functions and their associated keys. @@ -89,6 +89,7 @@ functions which are assigned key bindings, and see ("h" . org-babel-describe-bindings) ("\C-x" . org-babel-do-key-sequence-in-edit-buffer) ("x" . org-babel-do-key-sequence-in-edit-buffer) + ("k" . org-babel-remove-result-one-or-many) ("\C-\M-h" . org-babel-mark-block)) "Alist of key bindings and interactive Babel functions. This list associates interactive Babel functions diff --git a/lisp/org/ob-latex.el b/lisp/org/ob-latex.el index d00827645ef..6964fde5ac6 100644 --- a/lisp/org/ob-latex.el +++ b/lisp/org/ob-latex.el @@ -1,4 +1,4 @@ -;;; ob-latex.el --- org-babel functions for latex "evaluation" +;;; ob-latex.el --- Babel Functions for LaTeX -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -32,12 +32,11 @@ ;;; Code: (require 'ob) -(declare-function org-create-formula-image "org" - (string tofile options buffer &optional type)) -(declare-function org-splice-latex-header "org" - (tpl def-pkg pkg snippets-p &optional extra)) -(declare-function org-latex-guess-inputenc "ox-latex" (header)) +(declare-function org-create-formula-image "org" (string tofile options buffer &optional type)) (declare-function org-latex-compile "ox-latex" (texfile &optional snippet)) +(declare-function org-latex-guess-inputenc "ox-latex" (header)) +(declare-function org-splice-latex-header "org" (tpl def-pkg pkg snippets-p &optional extra)) +(declare-function org-trim "org" (s &optional keep-lead)) (defvar org-babel-tangle-lang-exts) (add-to-list 'org-babel-tangle-lang-exts '("latex" . "tex")) @@ -51,7 +50,22 @@ '((:results . "latex") (:exports . "results")) "Default arguments to use when evaluating a LaTeX source block.") -(defcustom org-babel-latex-htlatex "" +(defconst org-babel-header-args:latex + '((border . :any) + (fit . :any) + (imagemagick . ((nil t))) + (iminoptions . :any) + (imoutoptions . :any) + (packages . :any) + (pdfheight . :any) + (pdfpng . :any) + (pdfwidth . :any) + (headers . :any) + (packages . :any) + (buffer . ((yes no)))) + "LaTeX-specific header arguments.") + +(defcustom org-babel-latex-htlatex "htlatex" "The htlatex command to enable conversion of latex to SVG or HTML." :group 'org-babel :type 'string) @@ -70,37 +84,82 @@ (regexp-quote (format "%S" (car pair))) (if (stringp (cdr pair)) (cdr pair) (format "%S" (cdr pair))) - body))) (mapcar #'cdr (org-babel-get-header params :var))) - (org-babel-trim body)) + body))) (org-babel--get-vars params)) + (org-trim body)) (defun org-babel-execute:latex (body params) "Execute a block of Latex code with Babel. This function is called by `org-babel-execute-src-block'." (setq body (org-babel-expand-body:latex body params)) - (if (cdr (assoc :file params)) - (let* ((out-file (cdr (assoc :file params))) + (if (cdr (assq :file params)) + (let* ((out-file (cdr (assq :file params))) + (extension (file-name-extension out-file)) (tex-file (org-babel-temp-file "latex-" ".tex")) - (border (cdr (assoc :border params))) - (imagemagick (cdr (assoc :imagemagick params))) - (im-in-options (cdr (assoc :iminoptions params))) - (im-out-options (cdr (assoc :imoutoptions params))) - (pdfpng (cdr (assoc :pdfpng params))) - (fit (or (cdr (assoc :fit params)) border)) - (height (and fit (cdr (assoc :pdfheight params)))) - (width (and fit (cdr (assoc :pdfwidth params)))) - (headers (cdr (assoc :headers params))) - (in-buffer (not (string= "no" (cdr (assoc :buffer params))))) + (border (cdr (assq :border params))) + (imagemagick (cdr (assq :imagemagick params))) + (im-in-options (cdr (assq :iminoptions params))) + (im-out-options (cdr (assq :imoutoptions params))) + (fit (or (cdr (assq :fit params)) border)) + (height (and fit (cdr (assq :pdfheight params)))) + (width (and fit (cdr (assq :pdfwidth params)))) + (headers (cdr (assq :headers params))) + (in-buffer (not (string= "no" (cdr (assq :buffer params))))) (org-latex-packages-alist - (append (cdr (assoc :packages params)) org-latex-packages-alist))) + (append (cdr (assq :packages params)) org-latex-packages-alist))) (cond - ((and (string-match "\\.png$" out-file) (not imagemagick)) + ((and (string-suffix-p ".png" out-file) (not imagemagick)) (org-create-formula-image body out-file org-format-latex-options in-buffer)) - ((string-match "\\.tikz$" out-file) + ((string-suffix-p ".tikz" out-file) (when (file-exists-p out-file) (delete-file out-file)) (with-temp-file out-file (insert body))) - ((or (string-match "\\.pdf$" out-file) imagemagick) + ((and (or (string= "svg" extension) + (string= "html" extension)) + (executable-find org-babel-latex-htlatex)) + ;; TODO: this is a very different way of generating the + ;; frame latex document than in the pdf case. Ideally, both + ;; would be unified. This would prevent bugs creeping in + ;; such as the one fixed on Aug 16 2014 whereby :headers was + ;; not included in the SVG/HTML case. + (with-temp-file tex-file + (insert (concat + "\\documentclass[preview]{standalone} +\\def\\pgfsysdriver{pgfsys-tex4ht.def} +" + (mapconcat (lambda (pkg) + (concat "\\usepackage" pkg)) + org-babel-latex-htlatex-packages + "\n") + (if headers + (concat "\n" + (if (listp headers) + (mapconcat #'identity headers "\n") + headers) "\n") + "") + "\\begin{document}" + body + "\\end{document}"))) + (when (file-exists-p out-file) (delete-file out-file)) + (let ((default-directory (file-name-directory tex-file))) + (shell-command (format "%s %s" org-babel-latex-htlatex tex-file))) + (cond + ((file-exists-p (concat (file-name-sans-extension tex-file) "-1.svg")) + (if (string-suffix-p ".svg" out-file) + (progn + (shell-command "pwd") + (shell-command (format "mv %s %s" + (concat (file-name-sans-extension tex-file) "-1.svg") + out-file))) + (error "SVG file produced but HTML file requested"))) + ((file-exists-p (concat (file-name-sans-extension tex-file) ".html")) + (if (string-suffix-p ".html" out-file) + (shell-command "mv %s %s" + (concat (file-name-sans-extension tex-file) + ".html") + out-file) + (error "HTML file produced but SVG file requested"))))) + ((or (string= "pdf" extension) imagemagick) (with-temp-file tex-file (require 'ox-latex) (insert @@ -133,54 +192,20 @@ This function is called by `org-babel-execute-src-block'." (when (file-exists-p out-file) (delete-file out-file)) (let ((transient-pdf-file (org-babel-latex-tex-to-pdf tex-file))) (cond - ((string-match "\\.pdf$" out-file) + ((string= "pdf" extension) (rename-file transient-pdf-file out-file)) (imagemagick - (convert-pdf + (org-babel-latex-convert-pdf transient-pdf-file out-file im-in-options im-out-options) (when (file-exists-p transient-pdf-file) - (delete-file transient-pdf-file)))))) - ((and (or (string-match "\\.svg$" out-file) - (string-match "\\.html$" out-file)) - (not (string= "" org-babel-latex-htlatex))) - (with-temp-file tex-file - (insert (concat - "\\documentclass[preview]{standalone} -\\def\\pgfsysdriver{pgfsys-tex4ht.def} -" - (mapconcat (lambda (pkg) - (concat "\\usepackage" pkg)) - org-babel-latex-htlatex-packages - "\n") - "\\begin{document}" - body - "\\end{document}"))) - (when (file-exists-p out-file) (delete-file out-file)) - (let ((default-directory (file-name-directory tex-file))) - (shell-command (format "%s %s" org-babel-latex-htlatex tex-file))) - (cond - ((file-exists-p (concat (file-name-sans-extension tex-file) "-1.svg")) - (if (string-match "\\.svg$" out-file) - (progn - (shell-command "pwd") - (shell-command (format "mv %s %s" - (concat (file-name-sans-extension tex-file) "-1.svg") - out-file))) - (error "SVG file produced but HTML file requested."))) - ((file-exists-p (concat (file-name-sans-extension tex-file) ".html")) - (if (string-match "\\.html$" out-file) - (shell-command "mv %s %s" - (concat (file-name-sans-extension tex-file) - ".html") - out-file) - (error "HTML file produced but SVG file requested."))))) - ((string-match "\\.\\([^\\.]+\\)$" out-file) - (error "Can not create %s files, please specify a .png or .pdf file or try the :imagemagick header argument" - (match-string 1 out-file)))) + (delete-file transient-pdf-file))) + (t + (error "Can not create %s files, please specify a .png or .pdf file or try the :imagemagick header argument" + extension)))))) nil) ;; signal that output has already been written to file body)) -(defun convert-pdf (pdffile out-file im-in-options im-out-options) +(defun org-babel-latex-convert-pdf (pdffile out-file im-in-options im-out-options) "Generate a file from a pdf file using imagemagick." (let ((cmd (concat "convert " im-in-options " " pdffile " " im-out-options " " out-file))) @@ -192,7 +217,7 @@ This function is called by `org-babel-execute-src-block'." (require 'ox-latex) (org-latex-compile file)) -(defun org-babel-prep-session:latex (session params) +(defun org-babel-prep-session:latex (_session _params) "Return an error because LaTeX doesn't support sessions." (error "LaTeX does not support sessions")) diff --git a/lisp/org/ob-ledger.el b/lisp/org/ob-ledger.el index 154e75c0e05..4f10ebe08aa 100644 --- a/lisp/org/ob-ledger.el +++ b/lisp/org/ob-ledger.el @@ -1,4 +1,4 @@ -;;; ob-ledger.el --- org-babel functions for ledger evaluation +;;; ob-ledger.el --- Babel Functions for Ledger -*- lexical-binding: t; -*- ;; Copyright (C) 2010-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -46,8 +46,7 @@ "Execute a block of Ledger entries with org-babel. This function is called by `org-babel-execute-src-block'." (message "executing Ledger source code block") - (let ((result-params (split-string (or (cdr (assoc :results params)) ""))) - (cmdline (cdr (assoc :cmdline params))) + (let ((cmdline (cdr (assq :cmdline params))) (in-file (org-babel-temp-file "ledger-")) (out-file (org-babel-temp-file "ledger-output-"))) (with-temp-file in-file (insert body)) @@ -61,7 +60,7 @@ called by `org-babel-execute-src-block'." " > " (org-babel-process-file-name out-file)))) (with-temp-buffer (insert-file-contents out-file) (buffer-string)))) -(defun org-babel-prep-session:ledger (session params) +(defun org-babel-prep-session:ledger (_session _params) (error "Ledger does not support sessions")) (provide 'ob-ledger) diff --git a/lisp/org/ob-lilypond.el b/lisp/org/ob-lilypond.el index b37ecd87a7b..0cc85685e91 100644 --- a/lisp/org/ob-lilypond.el +++ b/lisp/org/ob-lilypond.el @@ -1,4 +1,4 @@ -;;; ob-lilypond.el --- org-babel functions for lilypond evaluation +;;; ob-lilypond.el --- Babel Functions for Lilypond -*- lexical-binding: t; -*- ;; Copyright (C) 2010-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -28,6 +28,8 @@ ;; ;; Lilypond documentation can be found at ;; http://lilypond.org/manuals.html +;; +;; This depends on epstopdf --- See http://www.ctan.org/pkg/epstopdf. ;;; Code: (require 'ob) @@ -60,51 +62,68 @@ org-babel-lilypond-play-midi-post-tangle determines whether to automate the playing of the resultant midi file. If the value is nil, the midi file is not automatically played. Default value is t") -(defvar org-babel-lilypond-OSX-ly-path - "/Applications/lilypond.app/Contents/Resources/bin/lilypond") -(defvar org-babel-lilypond-OSX-pdf-path "open") -(defvar org-babel-lilypond-OSX-midi-path "open") - -(defvar org-babel-lilypond-nix-ly-path "/usr/bin/lilypond") -(defvar org-babel-lilypond-nix-pdf-path "evince") -(defvar org-babel-lilypond-nix-midi-path "timidity") - -(defvar org-babel-lilypond-w32-ly-path "lilypond") -(defvar org-babel-lilypond-w32-pdf-path "") -(defvar org-babel-lilypond-w32-midi-path "") +(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) + '("/Applications/lilypond.app/Contents/Resources/bin/lilypond" "open" "open")) + ((eq system-type 'windows-nt) + '("lilypond" "" "")) + (t + '("lilypond" "xdg-open" "xdg-open"))) + "Commands to run lilypond and view or play the results. +These should be executables that take a filename as an argument. +On some system it is possible to specify the filename directly +and the viewer or player will be determined from the file type; +you can leave the string empty on this case." + :group 'org-babel + :type '(list + (string :tag "Lilypond ") + (string :tag "PDF Viewer ") + (string :tag "MIDI Player")) + :version "24.4" + :package-version '(Org . "8.2.7") + :set + (lambda (_symbol value) + (setq + org-babel-lilypond-ly-command (nth 0 value) + org-babel-lilypond-pdf-command (nth 1 value) + org-babel-lilypond-midi-command (nth 2 value)))) (defvar org-babel-lilypond-gen-png nil - "Image generation (png) can be turned on by default by setting -ORG-BABEL-LILYPOND-GEN-PNG to t") + "Non-nil means image generation (PNG) is turned on by default.") (defvar org-babel-lilypond-gen-svg nil - "Image generation (SVG) can be turned on by default by setting -ORG-BABEL-LILYPOND-GEN-SVG to t") + "Non-nil means image generation (SVG) is be turned on by default.") (defvar org-babel-lilypond-gen-html nil - "HTML generation can be turned on by default by setting -ORG-BABEL-LILYPOND-GEN-HTML to t") + "Non-nil means HTML generation is turned on by default.") (defvar org-babel-lilypond-gen-pdf nil - "PDF generation can be turned on by default by setting -ORG-BABEL-LILYPOND-GEN-PDF to t") + "Non-nil means PDF generation is be turned on by default.") (defvar org-babel-lilypond-use-eps nil - "You can force the compiler to use the EPS backend by setting -ORG-BABEL-LILYPOND-USE-EPS to t") + "Non-nil forces the compiler to use the EPS backend.") (defvar org-babel-lilypond-arrange-mode nil - "Arrange mode is turned on by setting ORG-BABEL-LILYPOND-ARRANGE-MODE -to t. In Arrange mode the following settings are altered -from default... + "Non-nil turns Arrange mode on. +In Arrange mode the following settings are altered from default: :tangle yes, :noweb yes :results silent :comments yes. In addition lilypond block execution causes tangling of all lilypond -blocks") +blocks.") (defun org-babel-expand-body:lilypond (body params) "Expand BODY according to PARAMS, return the expanded body." - (let ((vars (mapcar #'cdr (org-babel-get-header params :var)))) + (let ((vars (org-babel--get-vars params))) (mapc (lambda (pair) (let ((name (symbol-name (car pair))) @@ -138,9 +157,8 @@ specific arguments to =org-babel-tangle=" (defun org-babel-lilypond-process-basic (body params) "Execute a lilypond block in basic mode." - (let* ((result-params (cdr (assoc :result-params params))) - (out-file (cdr (assoc :file params))) - (cmdline (or (cdr (assoc :cmdline params)) + (let* ((out-file (cdr (assq :file params))) + (cmdline (or (cdr (assq :cmdline params)) "")) (in-file (org-babel-temp-file "lilypond-"))) @@ -148,7 +166,7 @@ specific arguments to =org-babel-tangle=" (insert (org-babel-expand-body:generic body params))) (org-babel-eval (concat - (org-babel-lilypond-determine-ly-path) + org-babel-lilypond-ly-command " -dbackend=eps " "-dno-gs-load-fonts " "-dinclude-eps-fonts " @@ -163,7 +181,7 @@ specific arguments to =org-babel-tangle=" cmdline in-file) "")) nil) -(defun org-babel-prep-session:lilypond (session params) +(defun org-babel-prep-session:lilypond (_session _params) "Return an error because LilyPond exporter does not support sessions." (error "Sorry, LilyPond does not currently support sessions!")) @@ -175,29 +193,27 @@ If error in compilation, attempt to mark the error in lilypond org file" (buffer-file-name) ".lilypond")) (org-babel-lilypond-temp-file (org-babel-lilypond-switch-extension (buffer-file-name) ".ly"))) - (if (file-exists-p org-babel-lilypond-tangled-file) - (progn - (when (file-exists-p org-babel-lilypond-temp-file) - (delete-file org-babel-lilypond-temp-file)) - (rename-file org-babel-lilypond-tangled-file - org-babel-lilypond-temp-file)) - (error "Error: Tangle Failed!") t) + (if (not (file-exists-p org-babel-lilypond-tangled-file)) + (error "Error: Tangle Failed!") + (when (file-exists-p org-babel-lilypond-temp-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*") (erase-buffer) (org-babel-lilypond-compile-lilyfile org-babel-lilypond-temp-file) (goto-char (point-min)) - (if (not (org-babel-lilypond-check-for-compile-error org-babel-lilypond-temp-file)) - (progn - (other-window -1) - (org-babel-lilypond-attempt-to-open-pdf org-babel-lilypond-temp-file) - (org-babel-lilypond-attempt-to-play-midi org-babel-lilypond-temp-file)) - (error "Error in Compilation!")))) nil) + (if (org-babel-lilypond-check-for-compile-error org-babel-lilypond-temp-file) + (error "Error in Compilation!") + (other-window -1) + (org-babel-lilypond-attempt-to-open-pdf org-babel-lilypond-temp-file) + (org-babel-lilypond-attempt-to-play-midi org-babel-lilypond-temp-file))))) (defun org-babel-lilypond-compile-lilyfile (file-name &optional test) "Compile lilypond file and check for compile errors FILE-NAME is full path to lilypond (.ly) file" (message "Compiling LilyPond...") - (let ((arg-1 (org-babel-lilypond-determine-ly-path)) ;program + (let ((arg-1 org-babel-lilypond-ly-command) ;program (arg-2 nil) ;infile (arg-3 "*lilypond*") ;buffer (arg-4 t) ;display @@ -223,11 +239,10 @@ FILE-NAME is full path to lilypond file. If TEST is t just return nil if no error found, and pass nil as file-name since it is unused in this context" (let ((is-error (search-forward "error:" nil t))) - (if (not test) - (if (not is-error) - nil - (org-babel-lilypond-process-compile-error file-name)) - is-error))) + (if test + is-error + (when is-error + (org-babel-lilypond-process-compile-error file-name))))) (defun org-babel-lilypond-process-compile-error (file-name) "Process the compilation error that has occurred. @@ -249,32 +264,26 @@ LINE is the erroneous line" (setq case-fold-search nil) (if (search-forward line nil t) (progn - (show-all) + (outline-show-all) (set-mark (point)) (goto-char (- (point) (length line)))) (goto-char temp)))) (defun org-babel-lilypond-parse-line-num (&optional buffer) "Extract error line number." - (when buffer - (set-buffer buffer)) + (when buffer (set-buffer buffer)) (let ((start (and (search-backward ":" nil t) (search-backward ":" nil t) (search-backward ":" nil t) - (search-backward ":" nil t))) - (num nil)) - (if start - (progn - (forward-char) - (let ((num (buffer-substring - (+ 1 start) - (- (search-forward ":" nil t) 1)))) - (setq num (string-to-number num)) - (if (numberp num) - num - nil))) - nil))) + (search-backward ":" nil t)))) + (when start + (forward-char) + (let ((num (string-to-number + (buffer-substring + (+ 1 start) + (- (search-forward ":" nil t) 1))))) + (and (numberp num) num))))) (defun org-babel-lilypond-parse-error-line (file-name lineNo) "Extract the erroneous line from the tangled .ly file @@ -298,13 +307,13 @@ If TEST is non-nil, the shell command is returned and is not run" (let ((pdf-file (org-babel-lilypond-switch-extension file-name ".pdf"))) (if (file-exists-p pdf-file) (let ((cmd-string - (concat (org-babel-lilypond-determine-pdf-path) " " pdf-file))) + (concat org-babel-lilypond-pdf-command " " pdf-file))) (if test cmd-string (start-process "\"Audition pdf\"" "*lilypond*" - (org-babel-lilypond-determine-pdf-path) + org-babel-lilypond-pdf-command pdf-file))) (message "No pdf file generated so can't display!"))))) @@ -316,49 +325,16 @@ If TEST is non-nil, the shell command is returned and is not run" (let ((midi-file (org-babel-lilypond-switch-extension file-name ".midi"))) (if (file-exists-p midi-file) (let ((cmd-string - (concat (org-babel-lilypond-determine-midi-path) " " midi-file))) + (concat org-babel-lilypond-midi-command " " midi-file))) (if test cmd-string (start-process "\"Audition midi\"" "*lilypond*" - (org-babel-lilypond-determine-midi-path) + org-babel-lilypond-midi-command midi-file))) (message "No midi file generated so can't play!"))))) -(defun org-babel-lilypond-determine-ly-path (&optional test) - "Return correct path to ly binary depending on OS -If TEST is non-nil, it contains a simulation of the OS for test purposes" - (let ((sys-type - (or test system-type))) - (cond ((string= sys-type "darwin") - org-babel-lilypond-OSX-ly-path) - ((string= sys-type "windows-nt") - org-babel-lilypond-w32-ly-path) - (t org-babel-lilypond-nix-ly-path)))) - -(defun org-babel-lilypond-determine-pdf-path (&optional test) - "Return correct path to pdf viewer depending on OS -If TEST is non-nil, it contains a simulation of the OS for test purposes" - (let ((sys-type - (or test system-type))) - (cond ((string= sys-type "darwin") - org-babel-lilypond-OSX-pdf-path) - ((string= sys-type "windows-nt") - org-babel-lilypond-w32-pdf-path) - (t org-babel-lilypond-nix-pdf-path)))) - -(defun org-babel-lilypond-determine-midi-path (&optional test) - "Return correct path to midi player depending on OS -If TEST is non-nil, it contains a simulation of the OS for test purposes" - (let ((sys-type - (or test test system-type))) - (cond ((string= sys-type "darwin") - org-babel-lilypond-OSX-midi-path) - ((string= sys-type "windows-nt") - org-babel-lilypond-w32-midi-path) - (t org-babel-lilypond-nix-midi-path)))) - (defun org-babel-lilypond-toggle-midi-play () "Toggle whether midi will be played following a successful compilation." (interactive) diff --git a/lisp/org/ob-lisp.el b/lisp/org/ob-lisp.el index 2f66549fc3d..d98098e1361 100644 --- a/lisp/org/ob-lisp.el +++ b/lisp/org/ob-lisp.el @@ -1,4 +1,4 @@ -;;; ob-lisp.el --- org-babel functions for common lisp evaluation +;;; ob-lisp.el --- Babel Functions for Common Lisp -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. @@ -21,21 +21,26 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: -;;; support for evaluating common lisp code, relies on slime for all eval +;;; Support for evaluating Common Lisp code, relies on SLY or SLIME +;;; for all eval. ;;; Requirements: -;; Requires SLIME (Superior Lisp Interaction Mode for Emacs.) -;; See http://common-lisp.net/project/slime/ +;; Requires SLY (Sylvester the Cat's Common Lisp IDE) or SLIME +;; (Superior Lisp Interaction Mode for Emacs). See: +;; - https://github.com/capitaomorte/sly +;; - http://common-lisp.net/project/slime/ ;;; Code: (require 'ob) +(declare-function sly-eval "ext:sly" (sexp &optional package)) (declare-function slime-eval "ext:slime" (sexp &optional package)) +(declare-function org-trim "org" (s &optional keep-lead)) (defvar org-babel-tangle-lang-exts) (add-to-list 'org-babel-tangle-lang-exts '("lisp" . "lisp")) @@ -43,8 +48,16 @@ (defvar org-babel-default-header-args:lisp '()) (defvar org-babel-header-args:lisp '((package . :any))) +(defcustom org-babel-lisp-eval-fn #'slime-eval + "The function to be called to evaluate code on the Lisp side. +Valid values include `slime-eval' and `sly-eval'." + :group 'org-babel + :version "26.1" + :package-version '(Org . "9.0") + :type 'function) + (defcustom org-babel-lisp-dir-fmt - "(let ((*default-pathname-defaults* #P%S)) %%s)" + "(let ((*default-pathname-defaults* #P%S\n)) %%s\n)" "Format string used to wrap code bodies to set the current directory. For example a value of \"(progn ;; %s\\n %%s)\" would ignore the current directory string." @@ -54,49 +67,54 @@ current directory string." (defun org-babel-expand-body:lisp (body params) "Expand BODY according to PARAMS, return the expanded body." - (let* ((vars (mapcar #'cdr (org-babel-get-header params :var))) - (result-params (cdr (assoc :result-params params))) + (let* ((vars (org-babel--get-vars params)) + (result-params (cdr (assq :result-params params))) (print-level nil) (print-length nil) - (body (org-babel-trim - (if (> (length vars) 0) - (concat "(let (" - (mapconcat - (lambda (var) - (format "(%S (quote %S))" (car var) (cdr var))) - vars "\n ") - ")\n" body ")") - body)))) + (body (if (null vars) (org-trim body) + (concat "(let (" + (mapconcat + (lambda (var) + (format "(%S (quote %S))" (car var) (cdr var))) + vars "\n ") + ")\n" body ")")))) (if (or (member "code" result-params) (member "pp" result-params)) (format "(pprint %s)" body) body))) (defun org-babel-execute:lisp (body params) - "Execute a block of Common Lisp code with Babel." - (require 'slime) + "Execute a block of Common Lisp code with Babel. +BODY is the contents of the block, as a string. PARAMS is +a property list containing the parameters of the block." + (require (pcase org-babel-lisp-eval-fn + (`slime-eval 'slime) + (`sly-eval 'sly))) (org-babel-reassemble-table (let ((result - (with-temp-buffer - (insert (org-babel-expand-body:lisp body params)) - (slime-eval `(swank:eval-and-grab-output - ,(let ((dir (if (assoc :dir params) - (cdr (assoc :dir params)) - default-directory))) - (format - (if dir (format org-babel-lisp-dir-fmt dir) - "(progn %s)") - (buffer-substring-no-properties - (point-min) (point-max))))) - (cdr (assoc :package params)))))) - (org-babel-result-cond (cdr (assoc :result-params params)) - (car result) + (funcall (if (member "output" (cdr (assq :result-params params))) + #'car #'cadr) + (with-temp-buffer + (insert (org-babel-expand-body:lisp body params)) + (funcall org-babel-lisp-eval-fn + `(swank:eval-and-grab-output + ,(let ((dir (if (assq :dir params) + (cdr (assq :dir params)) + default-directory))) + (format + (if dir (format org-babel-lisp-dir-fmt dir) + "(progn %s\n)") + (buffer-substring-no-properties + (point-min) (point-max))))) + (cdr (assq :package params))))))) + (org-babel-result-cond (cdr (assq :result-params params)) + result (condition-case nil - (read (org-babel-lisp-vector-to-list (cadr result))) - (error (cadr result))))) - (org-babel-pick-name (cdr (assoc :colname-names params)) - (cdr (assoc :colnames params))) - (org-babel-pick-name (cdr (assoc :rowname-names params)) - (cdr (assoc :rownames params))))) + (read (org-babel-lisp-vector-to-list result)) + (error 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-lisp-vector-to-list (results) ;; TODO: better would be to replace #(...) with [...] diff --git a/lisp/org/ob-lob.el b/lisp/org/ob-lob.el index ddfac2afeed..13f728f37f6 100644 --- a/lisp/org/ob-lob.el +++ b/lisp/org/ob-lob.el @@ -1,4 +1,4 @@ -;;; ob-lob.el --- functions supporting the Library of Babel +;;; ob-lob.el --- Functions Supporting the Library of Babel -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. @@ -20,30 +20,30 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: -(eval-when-compile - (require 'cl)) +(require 'cl-lib) (require 'ob-core) (require 'ob-table) -(declare-function org-babel-in-example-or-verbatim "ob-exp" nil) +(declare-function org-babel-ref-split-args "ob-ref" (arg-string)) +(declare-function org-element-at-point "org-element" ()) +(declare-function org-element-context "org-element" (&optional element)) +(declare-function org-element-property "org-element" (property element)) +(declare-function org-element-type "org-element" (element)) (defvar org-babel-library-of-babel nil "Library of source-code blocks. -This is an association list. Populate the library by adding -files to `org-babel-lob-files'.") - -(defcustom org-babel-lob-files nil - "Files used to populate the `org-babel-library-of-babel'. -To add files to this list use the `org-babel-lob-ingest' command." - :group 'org-babel - :version "24.1" - :type '(repeat file)) +This is an association list. Populate the library by calling +`org-babel-lob-ingest' on files containing source blocks.") (defvar org-babel-default-lob-header-args '((:exports . "results")) - "Default header arguments to use when exporting #+lob/call lines.") + "Default header arguments to use when exporting Babel calls. +By default, a Babel call inherits its arguments from the source +block being called. Header arguments defined in this variable +take precedence over these. It is useful for properties that +should not be inherited from a source block.") (defun org-babel-lob-ingest (&optional file) "Add all named source blocks defined in FILE to `org-babel-library-of-babel'." @@ -53,33 +53,20 @@ To add files to this list use the `org-babel-lob-ingest' command." (let* ((info (org-babel-get-src-block-info 'light)) (source-name (nth 4 info))) (when source-name - (setq source-name (intern source-name) - org-babel-library-of-babel - (cons (cons source-name info) - (assq-delete-all source-name org-babel-library-of-babel)) - lob-ingest-count (1+ lob-ingest-count))))) + (setf (nth 1 info) + (if (org-babel-noweb-p (nth 2 info) :eval) + (org-babel-expand-noweb-references info) + (nth 1 info))) + (let ((source (intern source-name))) + (setq org-babel-library-of-babel + (cons (cons source info) + (assq-delete-all source org-babel-library-of-babel)))) + (cl-incf lob-ingest-count)))) (message "%d src block%s added to Library of Babel" lob-ingest-count (if (> lob-ingest-count 1) "s" "")) lob-ingest-count)) -(defconst org-babel-block-lob-one-liner-regexp - (concat - "^\\([ \t]*?\\)#\\+call:[ \t]+\\([^()\n]+?\\)\\(\\[\\(.*\\)\\]\\|\\(\\)\\)" - "(\\([^\n]*?\\))\\(\\[.+\\]\\|\\)[ \t]*\\(\\([^\n]*\\)\\)?") - "Regexp to match non-inline calls to predefined source block functions.") - -(defconst org-babel-inline-lob-one-liner-regexp - (concat - "\\([^\n]*?\\)call_\\([^()\n]+?\\)\\(\\[\\(.*?\\)\\]\\|\\(\\)\\)" - "(\\([^\n]*?\\))\\(\\[\\(.*?\\)\\]\\)?") - "Regexp to match inline calls to predefined source block functions.") - -(defconst org-babel-lob-one-liner-regexp - (concat "\\(" org-babel-block-lob-one-liner-regexp - "\\|" org-babel-inline-lob-one-liner-regexp "\\)") - "Regexp to match calls to predefined source block functions.") - -;; functions for executing lob one-liners +;; Functions for executing lob one-liners. ;;;###autoload (defun org-babel-lob-execute-maybe () @@ -88,72 +75,76 @@ Detect if this is context for a Library Of Babel source block and if so then run the appropriate source block from the Library." (interactive) (let ((info (org-babel-lob-get-info))) - (if (and (nth 0 info) (not (org-babel-in-example-or-verbatim))) - (progn (org-babel-lob-execute info) t) - nil))) + (when info + (org-babel-execute-src-block nil info) + t))) + +(defun org-babel-lob--src-info (name) + "Return internal representation for Babel data named NAME. +NAME is a string. This function looks into the current document +for a Babel call or source block. If none is found, it looks +after NAME in the Library of Babel. Eventually, if that also +fails, it returns nil." + ;; During export, look into the pristine copy of the document being + ;; exported instead of the current one, which could miss some data. + (with-current-buffer (or org-babel-exp-reference-buffer (current-buffer)) + (org-with-wide-buffer + (goto-char (point-min)) + (catch :found + (let ((case-fold-search t) + (regexp (org-babel-named-data-regexp-for-name name))) + (while (re-search-forward regexp nil t) + (let ((element (org-element-at-point))) + (when (equal name (org-element-property :name element)) + (throw :found + (pcase (org-element-type element) + (`src-block (org-babel-get-src-block-info t element)) + (`babel-call (org-babel-lob-get-info element)) + ;; Non-executable data found. Since names are + ;; supposed to be unique throughout a document, + ;; bail out. + (_ nil)))))) + ;; No element named NAME in buffer. Try Library of Babel. + (cdr (assoc-string name org-babel-library-of-babel))))))) ;;;###autoload -(defun org-babel-lob-get-info () - "Return a Library of Babel function call as a string." - (let ((case-fold-search t) - (nonempty (lambda (a b) - (let ((it (match-string a))) - (if (= (length it) 0) (match-string b) it))))) - (save-excursion - (beginning-of-line 1) - (when (looking-at org-babel-lob-one-liner-regexp) - (append - (mapcar #'org-no-properties - (list - (format "%s%s(%s)%s" - (funcall nonempty 3 12) - (if (not (= 0 (length (funcall nonempty 5 14)))) - (concat "[" (funcall nonempty 5 14) "]") "") - (or (funcall nonempty 7 16) "") - (or (funcall nonempty 8 19) "")) - (funcall nonempty 9 18))) - (list (length (if (= (length (match-string 12)) 0) - (match-string 2) (match-string 11))) - (save-excursion - (forward-line -1) - (and (looking-at (concat org-babel-src-name-regexp - "\\([^\n]*\\)$")) - (org-no-properties (match-string 1)))))))))) - -(defvar org-babel-default-header-args:emacs-lisp) ; Defined in ob-emacs-lisp.el -(defun org-babel-lob-execute (info) - "Execute the lob call specified by INFO." - (let* ((mkinfo (lambda (p) - (list "emacs-lisp" "results" p nil - (nth 3 info) ;; name - (nth 2 info)))) - (pre-params (apply #'org-babel-merge-params - org-babel-default-header-args - org-babel-default-header-args:emacs-lisp - (append - (org-babel-params-from-properties) - (list - (org-babel-parse-header-arguments - (org-no-properties - (concat - ":var results=" - (mapconcat #'identity (butlast info 2) - " ")))))))) - (pre-info (funcall mkinfo pre-params)) - (cache-p (and (cdr (assoc :cache pre-params)) - (string= "yes" (cdr (assoc :cache pre-params))))) - (new-hash (when cache-p (org-babel-sha1-hash pre-info))) - (old-hash (when cache-p (org-babel-current-result-hash))) - (org-babel-current-src-block-location (point-marker))) - (if (and cache-p (equal new-hash old-hash)) - (save-excursion (goto-char (org-babel-where-is-src-block-result)) - (forward-line 1) - (message "%S" (org-babel-read-result))) - (prog1 (let* ((proc-params (org-babel-process-params pre-params)) - org-confirm-babel-evaluate) - (org-babel-execute-src-block nil (funcall mkinfo proc-params))) - ;; update the hash - (when new-hash (org-babel-set-current-result-hash new-hash)))))) +(defun org-babel-lob-get-info (&optional datum) + "Return internal representation for Library of Babel function call. +Consider DATUM, when provided, or element at point. Return nil +when not on an appropriate location. Otherwise return a list +compatible with `org-babel-get-src-block-info', which see." + (let* ((context (or datum (org-element-context))) + (type (org-element-type context))) + (when (memq type '(babel-call inline-babel-call)) + (pcase (org-babel-lob--src-info (org-element-property :call context)) + (`(,language ,body ,header ,_ ,_ ,_ ,coderef) + (let ((begin (org-element-property (if (eq type 'inline-babel-call) + :begin + :post-affiliated) + context))) + (list language + body + (apply #'org-babel-merge-params + header + org-babel-default-lob-header-args + (append + (org-with-wide-buffer + (goto-char begin) + (org-babel-params-from-properties language)) + (list + (org-babel-parse-header-arguments + (org-element-property :inside-header context)) + (let ((args (org-element-property :arguments context))) + (and args + (mapcar (lambda (ref) (cons :var ref)) + (org-babel-ref-split-args args)))) + (org-babel-parse-header-arguments + (org-element-property :end-header context))))) + nil + (org-element-property :name context) + begin + coderef))) + (_ nil))))) (provide 'ob-lob) diff --git a/lisp/org/ob-lua.el b/lisp/org/ob-lua.el new file mode 100644 index 00000000000..fc9d9f2f0e2 --- /dev/null +++ b/lisp/org/ob-lua.el @@ -0,0 +1,403 @@ +;;; ob-lua.el --- Org Babel functions for Lua evaluation -*- lexical-binding: t; -*- + +;; Copyright (C) 2014, 2016-2017 Free Software Foundation, Inc. + +;; Authors: Dieter Schoen +;; Keywords: literate programming, reproducible research +;; Homepage: http://orgmode.org + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. + +;; Requirements: +;; for session support, lua-mode is needed. +;; lua-mode is not part of GNU Emacs/orgmode, but can be obtained +;; from marmalade or melpa. +;; The source repository is here: +;; https://github.com/immerrr/lua-mode + +;; However, sessions are not yet working. + +;; Org-Babel support for evaluating lua source code. + +;;; Code: +(require 'ob) +(require 'cl-lib) + +(declare-function org-remove-indentation "org" (code &optional n)) +(declare-function org-trim "org" (s &optional keep-lead)) +(declare-function lua-shell "ext:lua-mode" (&optional argprompt)) +(declare-function lua-toggle-shells "ext:lua-mode" (arg)) +(declare-function run-lua "ext:lua" (cmd &optional dedicated show)) + +(defvar org-babel-tangle-lang-exts) +(add-to-list 'org-babel-tangle-lang-exts '("lua" . "lua")) + +(defvar org-babel-default-header-args:lua '()) + +(defcustom org-babel-lua-command "lua" + "Name of the command for executing Lua code." + :version "26.1" + :package-version '(Org . "8.3") + :group 'org-babel + :type 'string) + +(defcustom org-babel-lua-mode 'lua-mode + "Preferred lua mode for use in running lua interactively. +This will typically be 'lua-mode." + :group 'org-babel + :version "26.1" + :package-version '(Org . "8.3") + :type 'symbol) + +(defcustom org-babel-lua-hline-to "None" + "Replace hlines in incoming tables with this when translating to lua." + :group 'org-babel + :version "26.1" + :package-version '(Org . "8.3") + :type 'string) + +(defcustom org-babel-lua-None-to 'hline + "Replace 'None' in lua tables with this before returning." + :group 'org-babel + :version "26.1" + :package-version '(Org . "8.3") + :type 'symbol) + +(defun org-babel-execute:lua (body params) + "Execute a block of Lua code with Babel. +This function is called by `org-babel-execute-src-block'." + (let* ((session (org-babel-lua-initiate-session + (cdr (assq :session params)))) + (result-params (cdr (assq :result-params params))) + (result-type (cdr (assq :result-type params))) + (return-val (when (and (eq result-type 'value) (not session)) + (cdr (assq :return params)))) + (preamble (cdr (assq :preamble params))) + (full-body + (org-babel-expand-body:generic + (concat body (if return-val (format "\nreturn %s" return-val) "")) + params (org-babel-variable-assignments:lua params))) + (result (org-babel-lua-evaluate + session full-body result-type result-params preamble))) + (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-prep-session:lua (session params) + "Prepare SESSION according to the header arguments in PARAMS. +VARS contains resolved variable references" + (let* ((session (org-babel-lua-initiate-session session)) + (var-lines + (org-babel-variable-assignments:lua params))) + (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)) + session)) + +(defun org-babel-load-session:lua (session body params) + "Load BODY into SESSION." + (save-window-excursion + (let ((buffer (org-babel-prep-session:lua session params))) + (with-current-buffer buffer + (goto-char (process-mark (get-buffer-process (current-buffer)))) + (insert (org-babel-chomp body))) + buffer))) + +;; helper functions + +(defun org-babel-variable-assignments:lua (params) + "Return a list of Lua statements assigning the block's variables." + (mapcar + (lambda (pair) + (format "%s=%s" + (car pair) + (org-babel-lua-var-to-lua (cdr pair)))) + (org-babel--get-vars params))) + +(defun org-babel-lua-var-to-lua (var) + "Convert an elisp value to a lua variable. +Convert an elisp value, VAR, into a string of lua source code +specifying a variable of the same value." + (if (listp var) + (if (and (= 1 (length var)) (not (listp (car var)))) + (org-babel-lua-var-to-lua (car var)) + (if (and + (= 2 (length var)) + (not (listp (car var)))) + (concat + (substring-no-properties (car var)) + "=" + (org-babel-lua-var-to-lua (cdr var))) + (concat "{" (mapconcat #'org-babel-lua-var-to-lua var ", ") "}"))) + (if (eq var 'hline) + org-babel-lua-hline-to + (format + (if (and (stringp var) (string-match "[\n\r]" var)) "\"\"%S\"\"" "%S") + (if (stringp var) (substring-no-properties var) var))))) + +(defun org-babel-lua-table-or-string (results) + "Convert RESULTS into an appropriate elisp value. +If the results look like a list or tuple, then convert them into an +Emacs-lisp table, otherwise return the results as a string." + (let ((res (org-babel-script-escape results))) + (if (listp res) + (mapcar (lambda (el) (if (eq el 'None) + org-babel-lua-None-to el)) + res) + res))) + +(defvar org-babel-lua-buffers '((:default . "*Lua*"))) + +(defun org-babel-lua-session-buffer (session) + "Return the buffer associated with SESSION." + (cdr (assoc session org-babel-lua-buffers))) + +(defun org-babel-lua-with-earmuffs (session) + (let ((name (if (stringp session) session (format "%s" session)))) + (if (and (string= "*" (substring name 0 1)) + (string= "*" (substring name (- (length name) 1)))) + name + (format "*%s*" name)))) + +(defun org-babel-lua-without-earmuffs (session) + (let ((name (if (stringp session) session (format "%s" session)))) + (if (and (string= "*" (substring name 0 1)) + (string= "*" (substring name (- (length name) 1)))) + (substring name 1 (- (length name) 1)) + name))) + +(defvar lua-default-interpreter) +(defvar lua-which-bufname) +(defvar lua-shell-buffer-name) +(defun org-babel-lua-initiate-session-by-key (&optional session) + "Initiate a lua session. +If there is not a current inferior-process-buffer in SESSION +then create. Return the initialized session." + ;; (require org-babel-lua-mode) + (save-window-excursion + (let* ((session (if session (intern session) :default)) + (lua-buffer (org-babel-lua-session-buffer session)) + ;; (cmd (if (member system-type '(cygwin windows-nt ms-dos)) + ;; (concat org-babel-lua-command " -i") + ;; org-babel-lua-command)) + ) + (cond + ((and (eq 'lua-mode org-babel-lua-mode) + (fboundp 'lua-start-process)) ; lua-mode.el + ;; Make sure that lua-which-bufname is initialized, as otherwise + ;; it will be overwritten the first time a Lua buffer is + ;; created. + ;;(lua-toggle-shells lua-default-interpreter) + ;; `lua-shell' creates a buffer whose name is the value of + ;; `lua-which-bufname' with '*'s at the beginning and end + (let* ((bufname (if (and lua-buffer (buffer-live-p lua-buffer)) + (replace-regexp-in-string ;; zap surrounding * + "^\\*\\([^*]+\\)\\*$" "\\1" (buffer-name lua-buffer)) + (concat "Lua-" (symbol-name session)))) + (lua-which-bufname bufname)) + (lua-start-process) + (setq lua-buffer (org-babel-lua-with-earmuffs bufname)))) + (t + (error "No function available for running an inferior Lua"))) + (setq org-babel-lua-buffers + (cons (cons session lua-buffer) + (assq-delete-all session org-babel-lua-buffers))) + session))) + +(defun org-babel-lua-initiate-session (&optional session _params) + "Create a session named SESSION according to PARAMS." + (unless (string= session "none") + (error "Sessions currently not supported, work in progress") + (org-babel-lua-session-buffer + (org-babel-lua-initiate-session-by-key session)))) + +(defvar org-babel-lua-eoe-indicator "--eoe" + "A string to indicate that evaluation has completed.") + +(defvar org-babel-lua-wrapper-method + " +function main() +%s +end + +fd=io.open(\"%s\", \"w\") +fd:write( main() ) +fd:close()") +(defvar org-babel-lua-pp-wrapper-method + " +-- table to string +function t2s(t, indent) + if indent == nil then + indent = \"\" + end + if type(t) == \"table\" then + ts = \"\" + for k,v in pairs(t) do + if type(v) == \"table\" then + ts = ts .. indent .. t2s(k,indent .. \" \") .. \" = \\n\" .. + t2s(v, indent .. \" \") + else + ts = ts .. indent .. t2s(k,indent .. \" \") .. \" = \" .. + t2s(v, indent .. \" \") .. \"\\n\" + end + end + return ts + else + return tostring(t) + end +end + + +function main() +%s +end + +fd=io.open(\"%s\", \"w\") +fd:write(t2s(main())) +fd:close()") + +(defun org-babel-lua-evaluate + (session body &optional result-type result-params preamble) + "Evaluate BODY as Lua code." + (if session + (org-babel-lua-evaluate-session + session body result-type result-params) + (org-babel-lua-evaluate-external-process + body result-type result-params preamble))) + +(defun org-babel-lua-evaluate-external-process + (body &optional result-type result-params preamble) + "Evaluate BODY in external lua process. +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 ((raw + (pcase result-type + (`output (org-babel-eval org-babel-lua-command + (concat (if preamble (concat preamble "\n")) + body))) + (`value (let ((tmp-file (org-babel-temp-file "lua-"))) + (org-babel-eval + org-babel-lua-command + (concat + (if preamble (concat preamble "\n") "") + (format + (if (member "pp" result-params) + org-babel-lua-pp-wrapper-method + org-babel-lua-wrapper-method) + (mapconcat + (lambda (line) (format "\t%s" line)) + (split-string + (org-remove-indentation + (org-trim body)) + "[\r\n]") "\n") + (org-babel-process-file-name tmp-file 'noquote)))) + (org-babel-eval-read-file tmp-file)))))) + (org-babel-result-cond result-params + raw + (org-babel-lua-table-or-string (org-trim raw))))) + +(defun org-babel-lua-evaluate-session + (session body &optional result-type result-params) + "Pass BODY to the Lua 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 + "-- table to string +function t2s(t, indent) + if indent == nil then + indent = \"\" + end + if type(t) == \"table\" then + ts = \"\" + for k,v in pairs(t) do + if type(v) == \"table\" then + ts = ts .. indent .. t2s(k,indent .. \" \") .. \" = \\n\" .. + t2s(v, indent .. \" \") + else + ts = ts .. indent .. t2s(k,indent .. \" \") .. \" = \" .. + t2s(v, indent .. \" \") .. \"\\n\" + end + end + return ts + else + return tostring(t) + end +end +" + (concat "fd:write(_)) +fd:close()" + (org-babel-process-file-name tmp-file 'noquote))) + (list (format "fd=io.open(\"%s\", \"w\") +fd:write( _ ) +fd:close()" + (org-babel-process-file-name tmp-file + 'noquote))))))) + (input-body (lambda (body) + (mapc (lambda (line) (insert line) (funcall send-wait)) + (split-string body "[\r\n]")) + (funcall send-wait))) + (results + (pcase result-type + (`output + (mapconcat + #'org-trim + (butlast + (org-babel-comint-with-output + (session org-babel-lua-eoe-indicator t body) + (funcall input-body body) + (funcall send-wait) (funcall send-wait) + (insert org-babel-lua-eoe-indicator) + (funcall send-wait)) + 2) "\n")) + (`value + (let ((tmp-file (org-babel-temp-file "lua-"))) + (org-babel-comint-with-output + (session org-babel-lua-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-lua-eoe-indicator) + (funcall send-wait))) + (org-babel-eval-read-file tmp-file)))))) + (unless (string= (substring org-babel-lua-eoe-indicator 1 -1) results) + (org-babel-result-cond result-params + results + (org-babel-lua-table-or-string results))))) + +(defun org-babel-lua-read-string (string) + "Strip 's from around Lua string." + (org-unbracket-string "'" "'" string)) + +(provide 'ob-lua) + + + +;;; ob-lua.el ends here diff --git a/lisp/org/ob-makefile.el b/lisp/org/ob-makefile.el index a292800dc17..14190ac1be8 100644 --- a/lisp/org/ob-makefile.el +++ b/lisp/org/ob-makefile.el @@ -1,4 +1,4 @@ -;;; ob-makefile.el --- org-babel functions for makefile evaluation +;;; ob-makefile.el --- Babel Functions for Makefile -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. @@ -20,23 +20,23 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: -;; This file exists solely for tangling a Makefile from org-mode files. +;; This file exists solely for tangling a Makefile from Org files. ;;; Code: (require 'ob) (defvar org-babel-default-header-args:makefile '()) -(defun org-babel-execute:makefile (body params) +(defun org-babel-execute:makefile (body _params) "Execute a block of makefile code. This function is called by `org-babel-execute-src-block'." body) -(defun org-babel-prep-session:makefile (session params) +(defun org-babel-prep-session:makefile (_session _params) "Return an error if the :session header argument is set. Make does not support sessions." (error "Makefile sessions are nonsensical")) diff --git a/lisp/org/ob-matlab.el b/lisp/org/ob-matlab.el index 42bbd2b9074..e30ce8dae0f 100644 --- a/lisp/org/ob-matlab.el +++ b/lisp/org/ob-matlab.el @@ -1,4 +1,4 @@ -;;; ob-matlab.el --- org-babel support for matlab evaluation +;;; ob-matlab.el --- Babel support for Matlab -*- lexical-binding: t; -*- ;; Copyright (C) 2010-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/org/ob-maxima.el b/lisp/org/ob-maxima.el index b567fd484a9..224b3605035 100644 --- a/lisp/org/ob-maxima.el +++ b/lisp/org/ob-maxima.el @@ -1,4 +1,4 @@ -;;; ob-maxima.el --- org-babel functions for maxima evaluation +;;; ob-maxima.el --- Babel Functions for Maxima -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -48,11 +48,15 @@ (defun org-babel-maxima-expand (body params) "Expand a block of Maxima code according to its header arguments." - (let ((vars (mapcar #'cdr (org-babel-get-header params :var)))) + (let ((vars (org-babel--get-vars params)) + (epilogue (cdr (assq :epilogue params))) + (prologue (cdr (assq :prologue params)))) (mapconcat 'identity (list + ;; Any code from the specified prologue at the start. + prologue ;; graphic output - (let ((graphic-file (org-babel-maxima-graphical-output-file params))) + (let ((graphic-file (ignore-errors (org-babel-graphical-output-file params)))) (if graphic-file (format "set_plot_option ([gnuplot_term, png]); set_plot_option ([gnuplot_out_file, %S]);" @@ -62,6 +66,8 @@ (mapconcat 'org-babel-maxima-var-to-maxima vars "\n") ;; body body + ;; Any code from the specified epilogue at the end. + epilogue "gnuplot_close ()$") "\n"))) @@ -69,9 +75,9 @@ "Execute a block of Maxima entries with org-babel. This function is called by `org-babel-execute-src-block'." (message "executing Maxima source code block") - (let ((result-params (split-string (or (cdr (assoc :results params)) ""))) + (let ((result-params (split-string (or (cdr (assq :results params)) ""))) (result - (let* ((cmdline (or (cdr (assoc :cmdline params)) "")) + (let* ((cmdline (or (cdr (assq :cmdline params)) "")) (in-file (org-babel-temp-file "maxima-" ".max")) (cmd (format "%s --very-quiet -r 'batchload(%S)$' %s" org-babel-maxima-command in-file cmdline))) @@ -89,7 +95,7 @@ This function is called by `org-babel-execute-src-block'." (= 0 (length line))) line)) (split-string raw "[\r\n]"))) "\n"))))) - (if (org-babel-maxima-graphical-output-file params) + (if (ignore-errors (org-babel-graphical-output-file params)) nil (org-babel-result-cond result-params result @@ -98,7 +104,7 @@ This function is called by `org-babel-execute-src-block'." (org-babel-import-elisp-from-file tmp-file)))))) -(defun org-babel-prep-session:maxima (session params) +(defun org-babel-prep-session:maxima (_session _params) (error "Maxima does not support sessions")) (defun org-babel-maxima-var-to-maxima (pair) @@ -113,11 +119,6 @@ of the same value." (format "%S: %s$" var (org-babel-maxima-elisp-to-maxima val)))) -(defun org-babel-maxima-graphical-output-file (params) - "Name of file to which maxima should send graphical output." - (and (member "graphics" (cdr (assq :result-params params))) - (cdr (assq :file params)))) - (defun org-babel-maxima-elisp-to-maxima (val) "Return a string of maxima code which evaluates to VAL." (if (listp val) diff --git a/lisp/org/ob-mscgen.el b/lisp/org/ob-mscgen.el index b764475cb2f..784e0a94697 100644 --- a/lisp/org/ob-mscgen.el +++ b/lisp/org/ob-mscgen.el @@ -1,4 +1,4 @@ -;;; ob-msc.el --- org-babel functions for mscgen evaluation +;;; ob-msc.el --- Babel Functions for Mscgen -*- lexical-binding: t; -*- ;; Copyright (C) 2010-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -65,15 +65,15 @@ This function is called by `org-babel-execute-src-block'. Default filetype is png. Modify by setting :filetype parameter to mscgen supported formats." - (let* ((out-file (or (cdr (assoc :file params)) "output.png" )) - (filetype (or (cdr (assoc :filetype params)) "png" ))) - (unless (cdr (assoc :file params)) + (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")) (org-babel-eval (concat "mscgen -T " filetype " -o " out-file) body) nil)) ;; signal that output has already been written to file -(defun org-babel-prep-session:mscgen (session params) +(defun org-babel-prep-session:mscgen (_session _params) "Raise an error because Mscgen doesn't support sessions." (error "Mscgen does not support sessions")) diff --git a/lisp/org/ob-ocaml.el b/lisp/org/ob-ocaml.el index 31f0d01d7f6..fd0ddf8ab7f 100644 --- a/lisp/org/ob-ocaml.el +++ b/lisp/org/ob-ocaml.el @@ -1,4 +1,4 @@ -;;; ob-ocaml.el --- org-babel functions for ocaml evaluation +;;; ob-ocaml.el --- Babel Functions for Ocaml -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -37,11 +37,11 @@ ;;; Code: (require 'ob) (require 'comint) -(eval-when-compile (require 'cl)) (declare-function tuareg-run-caml "ext:tuareg" ()) (declare-function tuareg-run-ocaml "ext:tuareg" ()) (declare-function tuareg-interactive-send-input "ext:tuareg" ()) +(declare-function org-trim "org" (s &optional keep-lead)) (defvar org-babel-tangle-lang-exts) (add-to-list 'org-babel-tangle-lang-exts '("ocaml" . "ml")) @@ -60,17 +60,17 @@ (defun org-babel-execute:ocaml (body params) "Execute a block of Ocaml code with Babel." - (let* ((vars (mapcar #'cdr (org-babel-get-header params :var))) - (full-body (org-babel-expand-body:generic + (let* ((full-body (org-babel-expand-body:generic body params (org-babel-variable-assignments:ocaml params))) (session (org-babel-prep-session:ocaml - (cdr (assoc :session params)) params)) + (cdr (assq :session params)) params)) (raw (org-babel-comint-with-output - (session org-babel-ocaml-eoe-output t full-body) + (session org-babel-ocaml-eoe-output nil full-body) (insert (concat - (org-babel-chomp full-body)";;\n"org-babel-ocaml-eoe-indicator)) + (org-babel-chomp full-body) ";;\n" + org-babel-ocaml-eoe-indicator)) (tuareg-interactive-send-input))) (clean (car (let ((re (regexp-quote org-babel-ocaml-eoe-output)) out) @@ -79,23 +79,31 @@ (progn (setq out nil) line) (when (string-match re line) (progn (setq out t) nil)))) - (mapcar #'org-babel-trim (reverse raw)))))))) - (org-babel-reassemble-table - (let ((raw (org-babel-trim clean)) - (result-params (cdr (assoc :result-params params)))) + (mapcar #'org-trim (reverse raw))))))) + (raw (org-trim clean)) + (result-params (cdr (assq :result-params params)))) + (string-match + "\\(\\(.*\n\\)*\\)[^:\n]+ : \\([^=\n]+\\) =\\(\n\\| \\)\\(.+\\)$" + raw) + (let ((output (match-string 1 raw)) + (type (match-string 3 raw)) + (value (match-string 5 raw))) + (org-babel-reassemble-table (org-babel-result-cond result-params - ;; strip type information from output unless verbatim is specified - (if (and (not (member "verbatim" result-params)) - (string-match "= \\(.+\\)$" raw)) - (match-string 1 raw) raw) - (org-babel-ocaml-parse-output raw))) - (org-babel-pick-name - (cdr (assoc :colname-names params)) (cdr (assoc :colnames params))) - (org-babel-pick-name - (cdr (assoc :rowname-names params)) (cdr (assoc :rownames params)))))) + (cond + ((member "verbatim" result-params) raw) + ((member "output" result-params) output) + (t raw)) + (if (and value type) + (org-babel-ocaml-parse-output value type) + raw)) + (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))))))) (defvar tuareg-interactive-buffer-name) -(defun org-babel-prep-session:ocaml (session params) +(defun org-babel-prep-session:ocaml (session _params) "Prepare SESSION according to the header arguments in PARAMS." (require 'tuareg) (let ((tuareg-interactive-buffer-name (if (and (not (string= session "none")) @@ -113,7 +121,7 @@ (mapcar (lambda (pair) (format "let %s = %s;;" (car pair) (org-babel-ocaml-elisp-to-ocaml (cdr pair)))) - (mapcar #'cdr (org-babel-get-header params :var)))) + (org-babel--get-vars params))) (defun org-babel-ocaml-elisp-to-ocaml (val) "Return a string of ocaml code which evaluates to VAL." @@ -121,26 +129,29 @@ (concat "[|" (mapconcat #'org-babel-ocaml-elisp-to-ocaml val "; ") "|]") (format "%S" val))) -(defun org-babel-ocaml-parse-output (output) - "Parse OUTPUT. -OUTPUT is string output from an ocaml process." - (let ((regexp "[^:]+ : %s = \\(.+\\)$")) - (cond - ((string-match (format regexp "string") output) - (org-babel-read (match-string 1 output))) - ((or (string-match (format regexp "int") output) - (string-match (format regexp "float") output)) - (string-to-number (match-string 1 output))) - ((string-match (format regexp "list") output) - (org-babel-ocaml-read-list (match-string 1 output))) - ((string-match (format regexp "array") output) - (org-babel-ocaml-read-array (match-string 1 output))) - (t (message "don't recognize type of %s" output) output)))) +(defun org-babel-ocaml-parse-output (value type) + "Parse VALUE of type TYPE. +VALUE and TYPE are string output from an ocaml process." + (cond + ((string= "string" type) + (org-babel-read value)) + ((or (string= "int" type) + (string= "float" type)) + (string-to-number value)) + ((string-match "list" type) + (org-babel-ocaml-read-list value)) + ((string-match "array" type) + (org-babel-ocaml-read-array value)) + (t (message "don't recognize type %s" type) value))) (defun org-babel-ocaml-read-list (results) "Convert RESULTS into an elisp table or string. If the results look like a table, then convert them into an Emacs-lisp table, otherwise return the results as a string." + ;; XXX: This probably does not behave as expected when a semicolon + ;; is in a string in a list. The same comment applies to + ;; `org-babel-ocaml-read-array' below (with even more failure + ;; modes). (org-babel-script-escape (replace-regexp-in-string ";" "," results))) (defun org-babel-ocaml-read-array (results) diff --git a/lisp/org/ob-octave.el b/lisp/org/ob-octave.el index 4a96cdbf033..0f516062904 100644 --- a/lisp/org/ob-octave.el +++ b/lisp/org/ob-octave.el @@ -1,4 +1,4 @@ -;;; ob-octave.el --- org-babel functions for octave and matlab evaluation +;;; ob-octave.el --- Babel Functions for Octave and Matlab -*- lexical-binding: t; -*- ;; Copyright (C) 2010-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -30,10 +30,10 @@ ;;; Code: (require 'ob) -(eval-when-compile (require 'cl)) (declare-function matlab-shell "ext:matlab-mode") (declare-function matlab-shell-run-region "ext:matlab-mode") +(declare-function org-trim "org" (s &optional keep-lead)) (defvar org-babel-default-header-args:matlab '()) (defvar org-babel-default-header-args:octave '()) @@ -74,33 +74,31 @@ end") (let* ((session (funcall (intern (format "org-babel-%s-initiate-session" (if matlabp "matlab" "octave"))) - (cdr (assoc :session params)) params)) - (vars (mapcar #'cdr (org-babel-get-header params :var))) - (result-params (cdr (assoc :result-params params))) - (result-type (cdr (assoc :result-type params))) - (out-file (cdr (assoc :file params))) + (cdr (assq :session params)) params)) + (result-type (cdr (assq :result-type params))) (full-body (org-babel-expand-body:generic body params (org-babel-variable-assignments:octave params))) + (gfx-file (ignore-errors (org-babel-graphical-output-file params))) (result (org-babel-octave-evaluate session - (if (org-babel-octave-graphical-output-file params) + (if gfx-file (mapconcat 'identity (list "set (0, \"defaultfigurevisible\", \"off\");" full-body - (format "print -dpng %s" (org-babel-octave-graphical-output-file params))) + (format "print -dpng %s" gfx-file)) "\n") full-body) result-type matlabp))) - (if (org-babel-octave-graphical-output-file params) + (if gfx-file nil (org-babel-reassemble-table result (org-babel-pick-name - (cdr (assoc :colname-names params)) (cdr (assoc :colnames params))) + (cdr (assq :colname-names params)) (cdr (assq :colnames params))) (org-babel-pick-name - (cdr (assoc :rowname-names params)) (cdr (assoc :rownames params))))))) + (cdr (assq :rowname-names params)) (cdr (assq :rownames params))))))) (defun org-babel-prep-session:matlab (session params) "Prepare SESSION according to PARAMS." @@ -113,7 +111,7 @@ end") (format "%s=%s;" (car pair) (org-babel-octave-var-to-octave (cdr pair)))) - (mapcar #'cdr (org-babel-get-header params :var)))) + (org-babel--get-vars params))) (defalias 'org-babel-variable-assignments:matlab 'org-babel-variable-assignments:octave) @@ -147,7 +145,7 @@ If there is not a current inferior-process-buffer in SESSION then create. Return the initialized session." (org-babel-octave-initiate-session session params 'matlab)) -(defun org-babel-octave-initiate-session (&optional session params matlabp) +(defun org-babel-octave-initiate-session (&optional session _params matlabp) "Create an octave inferior process buffer. If there is not a current inferior-process-buffer in SESSION then create. Return the initialized session." @@ -167,8 +165,8 @@ create. Return the initialized session." (defun org-babel-octave-evaluate (session body result-type &optional matlabp) "Pass BODY to the octave process in SESSION. -If RESULT-TYPE equals 'output then return the outputs of the -statements in BODY, if RESULT-TYPE equals 'value then return the +If RESULT-TYPE equals `output' then return the outputs of the +statements in BODY, if RESULT-TYPE equals `value' then return the value of the last statement in BODY, as elisp." (if session (org-babel-octave-evaluate-session session body result-type matlabp) @@ -179,9 +177,9 @@ value of the last statement in BODY, as elisp." (let ((cmd (if matlabp org-babel-matlab-shell-command org-babel-octave-shell-command))) - (case result-type - (output (org-babel-eval cmd body)) - (value (let ((tmp-file (org-babel-temp-file "octave-"))) + (pcase result-type + (`output (org-babel-eval cmd body)) + (`value (let ((tmp-file (org-babel-temp-file "octave-"))) (org-babel-eval cmd (format org-babel-octave-wrapper-method body @@ -190,17 +188,17 @@ value of the last statement in BODY, as elisp." (org-babel-octave-import-elisp-from-file tmp-file)))))) (defun org-babel-octave-evaluate-session - (session body result-type &optional matlabp) + (session body result-type &optional matlabp) "Evaluate BODY in SESSION." (let* ((tmp-file (org-babel-temp-file (if matlabp "matlab-" "octave-"))) (wait-file (org-babel-temp-file "matlab-emacs-link-wait-signal-")) (full-body - (case result-type - (output + (pcase result-type + (`output (mapconcat #'org-babel-chomp (list body org-babel-octave-eoe-indicator) "\n")) - (value + (`value (if (and matlabp org-babel-matlab-with-emacs-link) (concat (format org-babel-matlab-emacs-link-wrapper-method @@ -233,21 +231,20 @@ value of the last statement in BODY, as elisp." org-babel-octave-eoe-output) t full-body) (insert full-body) (comint-send-input nil t)))) results) - (case result-type - (value + (pcase result-type + (`value (org-babel-octave-import-elisp-from-file tmp-file)) - (output - (progn - (setq results - (if matlabp - (cdr (reverse (delq "" (mapcar - #'org-babel-octave-read-string - (mapcar #'org-babel-trim raw))))) - (cdr (member org-babel-octave-eoe-output - (reverse (mapcar - #'org-babel-octave-read-string - (mapcar #'org-babel-trim raw))))))) - (mapconcat #'identity (reverse results) "\n")))))) + (`output + (setq results + (if matlabp + (cdr (reverse (delq "" (mapcar + #'org-babel-strip-quotes + (mapcar #'org-trim raw))))) + (cdr (member org-babel-octave-eoe-output + (reverse (mapcar + #'org-babel-strip-quotes + (mapcar #'org-trim raw))))))) + (mapconcat #'identity (reverse results) "\n"))))) (defun org-babel-octave-import-elisp-from-file (file-name) "Import data from FILE-NAME. @@ -262,17 +259,6 @@ This removes initial blank and comment lines and then calls (delete-region beg end))) (org-babel-import-elisp-from-file temp-file '(16)))) -(defun org-babel-octave-read-string (string) - "Strip \\\"s from around octave string." - (if (string-match "^\"\\([^\000]+\\)\"$" string) - (match-string 1 string) - string)) - -(defun org-babel-octave-graphical-output-file (params) - "Name of file to which maxima should send graphical output." - (and (member "graphics" (cdr (assq :result-params params))) - (cdr (assq :file params)))) - (provide 'ob-octave) diff --git a/lisp/org/ob-org.el b/lisp/org/ob-org.el index 3535891613e..232c2d0117c 100644 --- a/lisp/org/ob-org.el +++ b/lisp/org/ob-org.el @@ -1,4 +1,4 @@ -;;; ob-org.el --- org-babel functions for org code block evaluation +;;; ob-org.el --- Babel Functions for Org Code Blocks -*- lexical-binding: t; -*- ;; Copyright (C) 2010-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -41,7 +41,7 @@ "Default header inserted during export of org blocks.") (defun org-babel-expand-body:org (body params) - (dolist (var (mapcar #'cdr (org-babel-get-header params :var))) + (dolist (var (org-babel--get-vars params)) (setq body (replace-regexp-in-string (regexp-quote (format "$%s" (car var))) (format "%s" (cdr var)) @@ -51,7 +51,7 @@ (defun org-babel-execute:org (body params) "Execute a block of Org code with. This function is called by `org-babel-execute-src-block'." - (let ((result-params (split-string (or (cdr (assoc :results params)) ""))) + (let ((result-params (split-string (or (cdr (assq :results params)) ""))) (body (org-babel-expand-body:org (replace-regexp-in-string "^," "" body) params))) (cond @@ -61,7 +61,7 @@ This function is called by `org-babel-execute-src-block'." ((member "ascii" result-params) (org-export-string-as body 'ascii t)) (t body)))) -(defun org-babel-prep-session:org (session params) +(defun org-babel-prep-session:org (_session _params) "Return an error because org does not support sessions." (error "Org does not support sessions")) diff --git a/lisp/org/ob-perl.el b/lisp/org/ob-perl.el index 4e4407d1762..2f462cf4140 100644 --- a/lisp/org/ob-perl.el +++ b/lisp/org/ob-perl.el @@ -1,4 +1,4 @@ -;;; ob-perl.el --- org-babel functions for perl evaluation +;;; ob-perl.el --- Babel Functions for Perl -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -28,7 +28,6 @@ ;;; Code: (require 'ob) -(eval-when-compile (require 'cl)) (defvar org-babel-tangle-lang-exts) (add-to-list 'org-babel-tangle-lang-exts '("perl" . "pl")) @@ -41,20 +40,20 @@ (defun org-babel-execute:perl (body params) "Execute a block of Perl code with Babel. This function is called by `org-babel-execute-src-block'." - (let* ((session (cdr (assoc :session params))) - (result-params (cdr (assoc :result-params params))) - (result-type (cdr (assoc :result-type params))) + (let* ((session (cdr (assq :session params))) + (result-params (cdr (assq :result-params params))) + (result-type (cdr (assq :result-type params))) (full-body (org-babel-expand-body:generic body params (org-babel-variable-assignments:perl params))) (session (org-babel-perl-initiate-session session))) (org-babel-reassemble-table (org-babel-perl-evaluate session full-body result-type result-params) (org-babel-pick-name - (cdr (assoc :colname-names params)) (cdr (assoc :colnames params))) + (cdr (assq :colname-names params)) (cdr (assq :colnames params))) (org-babel-pick-name - (cdr (assoc :rowname-names params)) (cdr (assoc :rownames params)))))) + (cdr (assq :rowname-names params)) (cdr (assq :rownames params)))))) -(defun org-babel-prep-session:perl (session params) +(defun org-babel-prep-session:perl (_session _params) "Prepare SESSION according to the header arguments in PARAMS." (error "Sessions are not supported for Perl")) @@ -63,7 +62,7 @@ This function is called by `org-babel-execute-src-block'." (mapcar (lambda (pair) (org-babel-perl--var-to-perl (cdr pair) (car pair))) - (mapcar #'cdr (org-babel-get-header params :var)))) + (org-babel--get-vars params))) ;; helper functions @@ -76,7 +75,7 @@ This function is called by `org-babel-execute-src-block'." The elisp value, VAR, is converted to a string of perl source code specifying a var of the same value." (if varn - (let ((org-babel-perl--lvl 0) (lvar (listp var)) prefix) + (let ((org-babel-perl--lvl 0) (lvar (listp var))) (concat "my $" (symbol-name varn) "=" (when lvar "\n") (org-babel-perl--var-to-perl var) ";\n")) @@ -92,7 +91,7 @@ specifying a var of the same value." (defvar org-babel-perl-buffers '(:default . nil)) -(defun org-babel-perl-initiate-session (&optional session params) +(defun org-babel-perl-initiate-session (&optional _session _params) "Return nil because sessions are not supported by perl." nil) @@ -127,8 +126,8 @@ specifying a var of the same value." (defun org-babel-perl-evaluate (session ibody &optional result-type result-params) "Pass BODY to the Perl process in SESSION. -If RESULT-TYPE equals 'output then return a list of the outputs -of the statements in BODY, if RESULT-TYPE equals 'value then +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, as elisp." (when session (error "Sessions are not supported for Perl")) (let* ((body (concat org-babel-perl-preface ibody)) @@ -136,13 +135,13 @@ return the value of the last statement in BODY, as elisp." (tmp-babel-file (org-babel-process-file-name tmp-file 'noquote))) (let ((results - (case result-type - (output + (pcase result-type + (`output (with-temp-file tmp-file (insert (org-babel-eval org-babel-perl-command body)) (buffer-string))) - (value + (`value (org-babel-eval org-babel-perl-command (format org-babel-perl-wrapper-method body tmp-babel-file)))))) diff --git a/lisp/org/ob-picolisp.el b/lisp/org/ob-picolisp.el index a87c15ea977..a1dbe6de2a6 100644 --- a/lisp/org/ob-picolisp.el +++ b/lisp/org/ob-picolisp.el @@ -1,4 +1,4 @@ -;;; ob-picolisp.el --- org-babel functions for picolisp evaluation +;;; ob-picolisp.el --- Babel Functions for Picolisp -*- lexical-binding: t; -*- ;; Copyright (C) 2010-2017 Free Software Foundation, Inc. @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -55,7 +55,6 @@ ;;; Code: (require 'ob) (require 'comint) -(eval-when-compile (require 'cl)) (declare-function run-picolisp "ext:inferior-picolisp" (cmd)) (defvar org-babel-tangle-lang-exts) ;; Autoloaded @@ -80,9 +79,9 @@ (defun org-babel-expand-body:picolisp (body params) "Expand BODY according to PARAMS, return the expanded body." - (let ((vars (mapcar #'cdr (org-babel-get-header params :var))) - (result-params (cdr (assoc :result-params params))) - (print-level nil) (print-length nil)) + (let ((vars (org-babel--get-vars params)) + (print-level nil) + (print-length nil)) (if (> (length vars) 0) (concat "(prog (let (" (mapconcat @@ -100,12 +99,11 @@ (message "executing Picolisp source code block") (let* ( ;; Name of the session or "none". - (session-name (cdr (assoc :session params))) + (session-name (cdr (assq :session params))) ;; Set the session if the session variable is non-nil. (session (org-babel-picolisp-initiate-session session-name)) ;; Either OUTPUT or VALUE which should behave as described above. - (result-type (cdr (assoc :result-type params))) - (result-params (cdr (assoc :result-params params))) + (result-params (cdr (assq :result-params params))) ;; Expand the body with `org-babel-expand-body:picolisp'. (full-body (org-babel-expand-body:picolisp body params)) ;; Wrap body appropriately for the type of evaluation and results. diff --git a/lisp/org/ob-plantuml.el b/lisp/org/ob-plantuml.el index e05565e32ce..8093100edaf 100644 --- a/lisp/org/ob-plantuml.el +++ b/lisp/org/ob-plantuml.el @@ -1,4 +1,4 @@ -;;; ob-plantuml.el --- org-babel functions for plantuml evaluation +;;; ob-plantuml.el --- Babel Functions for Plantuml -*- lexical-binding: t; -*- ;; Copyright (C) 2010-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -46,35 +46,76 @@ :version "24.1" :type '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 +contain multiple entries for the key `:var'. `:var' entries in PARAMS +are expected to be scalar variables." + (mapcar + (lambda (pair) + (format "!define %s %s" + (car pair) + (replace-regexp-in-string "\"" "" (cdr pair)))) + (org-babel--get-vars params))) + +(defun org-babel-plantuml-make-body (body params) + "Return PlantUML input string. +BODY is the content of the source block and PARAMS is a property list +of source block parameters. This function relies on the +`org-babel-expand-body:generic' function to extract `:var' entries +from PARAMS and on the `org-babel-variable-assignments:plantuml' +function to convert variables to PlantUML assignments." + (concat + "@startuml\n" + (org-babel-expand-body:generic + body params (org-babel-variable-assignments:plantuml params)) + "\n@enduml")) + (defun org-babel-execute:plantuml (body params) "Execute a block of plantuml code with org-babel. This function is called by `org-babel-execute-src-block'." - (let* ((result-params (split-string (or (cdr (assoc :results params)) ""))) - (out-file (or (cdr (assoc :file params)) + (let* ((out-file (or (cdr (assq :file params)) (error "PlantUML requires a \":file\" header argument"))) - (cmdline (cdr (assoc :cmdline params))) + (cmdline (cdr (assq :cmdline params))) (in-file (org-babel-temp-file "plantuml-")) - (java (or (cdr (assoc :java params)) "")) + (java (or (cdr (assq :java params)) "")) + (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) "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)) - (with-temp-file in-file (insert (concat "@startuml\n" body "\n@enduml"))) + (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 -(defun org-babel-prep-session:plantuml (session params) +(defun org-babel-prep-session:plantuml (_session _params) "Return an error because plantuml does not support sessions." (error "Plantuml does not support sessions")) diff --git a/lisp/org/ob-processing.el b/lisp/org/ob-processing.el new file mode 100644 index 00000000000..4e09abc98bc --- /dev/null +++ b/lisp/org/ob-processing.el @@ -0,0 +1,195 @@ +;;; ob-processing.el --- Babel functions for processing -*- lexical-binding: t; -*- + +;; Copyright (C) 2015-2017 Free Software Foundation, Inc. + +;; Author: Jarmo Hurri (adapted from ob-asymptote.el written by Eric Schulte) +;; Keywords: literate programming, reproducible research +;; Homepage: http://orgmode.org + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Babel support for evaluating processing source code. +;; +;; This differs from most standard languages in that +;; +;; 1) there is no such thing as a "session" in processing +;; +;; 2) results can only be exported as html; in this case, the +;; processing code is embedded via a file into a javascript block +;; using the processing.js module; the script then draws the +;; resulting output when the web page is viewed in a browser; note +;; that the user is responsible for making sure that processing.js +;; is available on the website +;; +;; 3) it is possible to interactively view the sketch of the +;; Processing code block via Processing 2.0 Emacs mode, using +;; `org-babel-processing-view-sketch'. You can bind this command +;; to, e.g., C-c C-v C-k with +;; +;; (define-key org-babel-map (kbd "C-k") 'org-babel-processing-view-sketch) + + +;;; Requirements: + +;; - processing2-emacs mode :: https://github.com/ptrv/processing2-emacs +;; - Processing.js module :: http://processingjs.org/ + +;;; Code: +(require 'ob) +(require 'sha1) + +(declare-function processing-sketch-run "ext:processing-mode" ()) + +(defvar org-babel-temporary-directory) + +(defvar org-babel-tangle-lang-exts) +(add-to-list 'org-babel-tangle-lang-exts '("processing" . "pde")) + +;; Default header tags depend on whether exporting html or not; if not +;; exporting html, then no results are produced; otherwise results are +;; HTML. +(defvar org-babel-default-header-args:processing + '((:results . "html") (:exports . "results")) + "Default arguments when evaluating a Processing source block.") + +(defvar org-babel-processing-processing-js-filename "processing.js" + "Filename of the processing.js file.") + +(defun org-babel-processing-view-sketch () + "Show the sketch of the Processing block under point in an external viewer." + (interactive) + (require 'processing-mode) + (let ((info (org-babel-get-src-block-info))) + (if (string= (nth 0 info) "processing") + (let* ((body (nth 1 info)) + (params (org-babel-process-params (nth 2 info))) + (sketch-code + (org-babel-expand-body:generic + body + params + (org-babel-variable-assignments:processing params)))) + ;; Note: sketch filename can not contain a hyphen, since it + ;; has to be a valid java class name; for this reason + ;; make-temp-file is repeated until no hyphen is in the + ;; name; also sketch dir name must be the same as the + ;; basename of the sketch file. + (let* ((temporary-file-directory org-babel-temporary-directory) + (sketch-dir + (let (sketch-dir-candidate) + (while + (progn + (setq sketch-dir-candidate + (make-temp-file "processing" t)) + (when (string-match-p + "-" + (file-name-nondirectory sketch-dir-candidate)) + (delete-directory sketch-dir-candidate) + t))) + sketch-dir-candidate)) + (sketch-filename + (concat sketch-dir + "/" + (file-name-nondirectory sketch-dir) + ".pde"))) + (with-temp-file sketch-filename (insert sketch-code)) + (find-file sketch-filename) + (processing-sketch-run) + (kill-buffer))) + (message "Not inside a Processing source block.")))) + +(defun org-babel-execute:processing (body params) + "Execute a block of Processing code. +This function is called by `org-babel-execute-src-block'." + (let ((sketch-code + (org-babel-expand-body:generic + body + params + (org-babel-variable-assignments:processing params)))) + ;; Results are HTML. + (let ((sketch-canvas-id (concat "ob-" (sha1 sketch-code)))) + (concat "<script src=\"" + org-babel-processing-processing-js-filename + "\"></script>\n <script type=\"text/processing\"" + " data-processing-target=\"" + sketch-canvas-id + "\">\n" + sketch-code + "\n</script> <canvas id=\"" + sketch-canvas-id + "\"></canvas>")))) + +(defun org-babel-prep-session:processing (_session _params) + "Return an error if the :session header argument is set. +Processing does not support sessions" + (error "Processing does not support sessions")) + +(defun org-babel-variable-assignments:processing (params) + "Return list of processing statements assigning the block's variables." + (mapcar #'org-babel-processing-var-to-processing + (org-babel--get-vars params))) + +(defun org-babel-processing-var-to-processing (pair) + "Convert an elisp value into a Processing variable. +The elisp value PAIR is converted into Processing code specifying +a variable of the same value." + (let ((var (car pair)) + (val (let ((v (cdr pair))) + (if (symbolp v) (symbol-name v) v)))) + (cond + ((integerp val) + (format "int %S=%S;" var val)) + ((floatp val) + (format "float %S=%S;" var val)) + ((stringp val) + (format "String %S=\"%s\";" var val)) + ((and (listp val) (not (listp (car val)))) + (let* ((type (org-babel-processing-define-type val)) + (fmt (if (eq 'String type) "\"%s\"" "%s")) + (vect (mapconcat (lambda (e) (format fmt e)) val ", "))) + (format "%s[] %S={%s};" type var vect))) + ((listp val) + (let* ((type (org-babel-processing-define-type val)) + (fmt (if (eq 'String type) "\"%s\"" "%s")) + (array (mapconcat (lambda (row) + (concat "{" + (mapconcat (lambda (e) (format fmt e)) + row ", ") + "}")) + val ","))) + (format "%S[][] %S={%s};" type var array)))))) + +(defun org-babel-processing-define-type (data) + "Determine type of DATA. + +DATA is a list. Return type as a symbol. + +The type is `String' if any element in DATA is a string. +Otherwise, it is either `float', if some elements are floats, or +`int'." + (letrec ((type 'int) + (find-type + (lambda (row) + (dolist (e row type) + (cond ((listp e) (setq type (funcall find-type e))) + ((stringp e) (throw 'exit 'String)) + ((floatp e) (setq type 'float))))))) + (catch 'exit (funcall find-type data)))) + +(provide 'ob-processing) + +;;; ob-processing.el ends here diff --git a/lisp/org/ob-python.el b/lisp/org/ob-python.el index dfad47bf9e0..60ec5fa4752 100644 --- a/lisp/org/ob-python.el +++ b/lisp/org/ob-python.el @@ -1,4 +1,4 @@ -;;; ob-python.el --- org-babel functions for python evaluation +;;; ob-python.el --- Babel Functions for Python -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -28,9 +28,9 @@ ;;; Code: (require 'ob) -(eval-when-compile (require 'cl)) (declare-function org-remove-indentation "org" ) +(declare-function org-trim "org" (s &optional keep-lead)) (declare-function py-shell "ext:python-mode" (&optional argprompt)) (declare-function py-toggle-shells "ext:python-mode" (arg)) (declare-function run-python "ext:python" (&optional cmd dedicated show)) @@ -48,9 +48,9 @@ :type 'string) (defcustom org-babel-python-mode - (if (or (featurep 'xemacs) (featurep 'python-mode)) 'python-mode 'python) + (if (featurep 'python-mode) 'python-mode 'python) "Preferred python mode for use in running python interactively. -This will typically be either 'python or 'python-mode." +This will typically be either `python' or `python-mode'." :group 'org-babel :version "24.4" :package-version '(Org . "8.0") @@ -73,13 +73,16 @@ This will typically be either 'python or 'python-mode." (defun org-babel-execute:python (body params) "Execute a block of Python code with Babel. This function is called by `org-babel-execute-src-block'." - (let* ((session (org-babel-python-initiate-session - (cdr (assoc :session params)))) - (result-params (cdr (assoc :result-params params))) - (result-type (cdr (assoc :result-type params))) + (let* ((org-babel-python-command + (or (cdr (assq :python params)) + org-babel-python-command)) + (session (org-babel-python-initiate-session + (cdr (assq :session params)))) + (result-params (cdr (assq :result-params params))) + (result-type (cdr (assq :result-type params))) (return-val (when (and (eq result-type 'value) (not session)) - (cdr (assoc :return params)))) - (preamble (cdr (assoc :preamble params))) + (cdr (assq :return params)))) + (preamble (cdr (assq :preamble params))) (full-body (org-babel-expand-body:generic (concat body (if return-val (format "\nreturn %s" return-val) "")) @@ -88,10 +91,10 @@ This function is called by `org-babel-execute-src-block'." session full-body result-type result-params preamble))) (org-babel-reassemble-table result - (org-babel-pick-name (cdr (assoc :colname-names params)) - (cdr (assoc :colnames params))) - (org-babel-pick-name (cdr (assoc :rowname-names params)) - (cdr (assoc :rownames params)))))) + (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-prep-session:python (session params) "Prepare SESSION according to the header arguments in PARAMS. @@ -123,7 +126,7 @@ VARS contains resolved variable references" (format "%s=%s" (car pair) (org-babel-python-var-to-python (cdr pair)))) - (mapcar #'cdr (org-babel-get-header params :var)))) + (org-babel--get-vars params))) (defun org-babel-python-var-to-python (var) "Convert an elisp value to a python variable. @@ -131,7 +134,7 @@ Convert an elisp value, VAR, into a string of python source code specifying a variable of the same value." (if (listp var) (concat "[" (mapconcat #'org-babel-python-var-to-python var ", ") "]") - (if (equal var 'hline) + (if (eq var 'hline) org-babel-python-hline-to (format (if (and (stringp var) (string-match "[\n\r]" var)) "\"\"%S\"\"" "%S") @@ -143,7 +146,7 @@ If the results look like a list or tuple, then convert them into an Emacs-lisp table, otherwise return the results as a string." (let ((res (org-babel-script-escape results))) (if (listp res) - (mapcar (lambda (el) (if (equal el 'None) + (mapcar (lambda (el) (if (eq el 'None) org-babel-python-None-to el)) res) res))) @@ -214,7 +217,7 @@ then create. Return the initialized session." (assq-delete-all session org-babel-python-buffers))) session))) -(defun org-babel-python-initiate-session (&optional session params) +(defun org-babel-python-initiate-session (&optional session _params) "Create a session named SESSION according to PARAMS." (unless (string= session "none") (org-babel-python-session-buffer @@ -222,13 +225,13 @@ then create. Return the initialized session." (defvar org-babel-python-eoe-indicator "'org_babel_python_eoe'" "A string to indicate that evaluation has completed.") -(defvar org-babel-python-wrapper-method +(defconst org-babel-python-wrapper-method " def main(): %s open('%s', 'w').write( str(main()) )") -(defvar org-babel-python-pp-wrapper-method +(defconst org-babel-python-pp-wrapper-method " import pprint def main(): @@ -246,42 +249,41 @@ open('%s', 'w').write( pprint.pformat(main()) )") body result-type result-params preamble))) (defun org-babel-python-evaluate-external-process - (body &optional result-type result-params preamble) + (body &optional result-type result-params preamble) "Evaluate BODY in external python process. -If RESULT-TYPE equals 'output then return standard output as a -string. If RESULT-TYPE equals 'value then return the value of the +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 ((raw - (case result-type - (output (org-babel-eval org-babel-python-command - (concat (if preamble (concat preamble "\n")) - body))) - (value (let ((tmp-file (org-babel-temp-file "python-"))) - (org-babel-eval - org-babel-python-command - (concat - (if preamble (concat preamble "\n") "") - (format - (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-babel-trim body)) - "[\r\n]") "\n") - (org-babel-process-file-name tmp-file 'noquote)))) - (org-babel-eval-read-file tmp-file)))))) + (pcase result-type + (`output (org-babel-eval org-babel-python-command + (concat (if preamble (concat preamble "\n")) + body))) + (`value (let ((tmp-file (org-babel-temp-file "python-"))) + (org-babel-eval + org-babel-python-command + (concat + (if preamble (concat preamble "\n") "") + (format + (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-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-babel-trim raw))))) + (org-babel-python-table-or-string (org-trim raw))))) (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 +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 @@ -302,10 +304,10 @@ last statement in BODY, as elisp." (split-string body "[\r\n]")) (funcall send-wait))) (results - (case result-type - (output + (pcase result-type + (`output (mapconcat - #'org-babel-trim + #'org-trim (butlast (org-babel-comint-with-output (session org-babel-python-eoe-indicator t body) @@ -314,7 +316,7 @@ last statement in BODY, as elisp." (insert org-babel-python-eoe-indicator) (funcall send-wait)) 2) "\n")) - (value + (`value (let ((tmp-file (org-babel-temp-file "python-"))) (org-babel-comint-with-output (session org-babel-python-eoe-indicator nil body) @@ -332,9 +334,10 @@ last statement in BODY, as elisp." (org-babel-python-table-or-string results))))) (defun org-babel-python-read-string (string) - "Strip 's from around Python string." - (if (string-match "^'\\([^\000]+\\)'$" string) - (match-string 1 string) + "Strip \\='s from around Python string." + (if (and (string-prefix-p "'" string) + (string-suffix-p "'" string)) + (substring string 1 -1) string)) (provide 'ob-python) diff --git a/lisp/org/ob-ref.el b/lisp/org/ob-ref.el index 1d26403035f..323cdc7ef72 100644 --- a/lisp/org/ob-ref.el +++ b/lisp/org/ob-ref.el @@ -1,4 +1,4 @@ -;;; ob-ref.el --- org-babel functions for referencing external data +;;; ob-ref.el --- Babel Functions for Referencing External Data -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -50,19 +50,20 @@ ;;; Code: (require 'ob-core) -(eval-when-compile - (require 'cl)) - -(declare-function org-remove-if-not "org" (predicate seq)) -(declare-function org-at-table-p "org" (&optional table-type)) -(declare-function org-count "org" (CL-ITEM CL-SEQ)) -(declare-function org-at-item-p "org-list" ()) -(declare-function org-narrow-to-subtree "org" ()) -(declare-function org-id-find-id-in-file "org-id" (id file &optional markerp)) +(require 'cl-lib) + +(declare-function org-babel-lob-get-info "ob-lob" (&optional datum)) +(declare-function org-element-at-point "org-element" ()) +(declare-function org-element-property "org-element" (property element)) +(declare-function org-element-type "org-element" (element)) +(declare-function org-end-of-meta-data "org" (&optional full)) +(declare-function org-find-property "org" (property &optional value)) (declare-function org-id-find-id-file "org-id" (id)) +(declare-function org-id-find-id-in-file "org-id" (id file &optional markerp)) +(declare-function org-in-commented-heading-p "org" (&optional no-inheritance)) +(declare-function org-narrow-to-subtree "org" ()) (declare-function org-show-context "org" (&optional key)) -(declare-function org-pop-to-buffer-same-window - "org-compat" (&optional buffer-or-name norecord label)) +(declare-function org-trim "org" (s &optional keep-lead)) (defvar org-babel-ref-split-regexp "[ \f\t\n\r\v]*\\(.+?\\)[ \f\t\n\r\v]*=[ \f\t\n\r\v]*\\(.+\\)[ \f\t\n\r\v]*") @@ -90,35 +91,31 @@ the variable." org-babel-current-src-block-location))) (org-babel-read ref)))) (if (equal out ref) - (if (string-match "^\".*\"$" ref) + (if (and (string-prefix-p "\"" ref) + (string-suffix-p "\"" ref)) (read ref) (org-babel-ref-resolve ref)) out)))))) (defun org-babel-ref-goto-headline-id (id) - (goto-char (point-min)) - (let ((rx (regexp-quote id))) - (or (re-search-forward - (concat "^[ \t]*:CUSTOM_ID:[ \t]+" rx "[ \t]*$") nil t) - (let* ((file (org-id-find-id-file id)) - (m (when file (org-id-find-id-in-file id file 'marker)))) - (when (and file m) - (message "file:%S" file) - (org-pop-to-buffer-same-window (marker-buffer m)) - (goto-char m) - (move-marker m nil) - (org-show-context) - t))))) + (or (let ((h (org-find-property "CUSTOM_ID" id))) + (when h (goto-char h))) + (let* ((file (org-id-find-id-file id)) + (m (when file (org-id-find-id-in-file id file 'marker)))) + (when (and file m) + (message "file:%S" file) + (pop-to-buffer-same-window (marker-buffer m)) + (goto-char m) + (move-marker m nil) + (org-show-context) + t)))) (defun org-babel-ref-headline-body () (save-restriction (org-narrow-to-subtree) (buffer-substring (save-excursion (goto-char (point-min)) - (forward-line 1) - (when (looking-at "[ \t]*:PROPERTIES:") - (re-search-forward ":END:" nil) - (forward-char)) + (org-end-of-meta-data) (point)) (point-max)))) @@ -126,89 +123,82 @@ the variable." (defun org-babel-ref-resolve (ref) "Resolve the reference REF and return its value." (save-window-excursion - (save-excursion - (let ((case-fold-search t) - type args new-refere new-header-args new-referent result - lob-info split-file split-ref index index-row index-col id) - ;; if ref is indexed grab the indices -- beware nested indices - (when (and (string-match "\\[\\([^\\[]+\\)\\]$" ref) - (let ((str (substring ref 0 (match-beginning 0)))) - (= (org-count ?\( str) (org-count ?\) str)))) - (setq index (match-string 1 ref)) - (setq ref (substring ref 0 (match-beginning 0)))) - ;; assign any arguments to pass to source block - (when (string-match - "^\\(.+?\\)\\(\\[\\(.*\\)\\]\\|\\(\\)\\)(\\(.*\\))$" ref) - (setq new-refere (match-string 1 ref)) - (setq new-header-args (match-string 3 ref)) - (setq new-referent (match-string 5 ref)) - (when (> (length new-refere) 0) - (when (> (length new-referent) 0) - (setq args (mapcar (lambda (ref) (cons :var ref)) - (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))) - (setq ref new-refere))) - (when (string-match "^\\(.+\\):\\(.+\\)$" ref) - (setq split-file (match-string 1 ref)) - (setq split-ref (match-string 2 ref)) - (find-file split-file) (setq ref split-ref)) - (save-restriction - (widen) - (goto-char (point-min)) - (if (let ((src-rx (org-babel-named-src-block-regexp-for-name ref)) - (res-rx (org-babel-named-data-regexp-for-name ref))) - ;; goto ref in the current buffer - (or - ;; check for code blocks - (re-search-forward src-rx nil t) - ;; check for named data - (re-search-forward res-rx nil t) - ;; check for local or global headlines by id - (setq id (org-babel-ref-goto-headline-id ref)) - ;; check the Library of Babel - (setq lob-info (cdr (assoc (intern ref) - org-babel-library-of-babel))))) - (unless (or lob-info id) (goto-char (match-beginning 0))) - ;; ;; TODO: allow searching for names in other buffers - ;; (setq id-loc (org-id-find ref 'marker) - ;; buffer (marker-buffer id-loc) - ;; loc (marker-position id-loc)) - ;; (move-marker id-loc nil) - (error "Reference `%s' not found in this buffer" ref)) - (cond - (lob-info (setq type 'lob)) - (id (setq type 'id)) - ((and (looking-at org-babel-src-name-regexp) - (save-excursion - (forward-line 1) - (or (looking-at org-babel-src-block-regexp) - (looking-at org-babel-multi-line-header-regexp)))) - (setq type 'source-block)) - (t (while (not (setq type (org-babel-ref-at-ref-p))) - (forward-line 1) - (beginning-of-line) - (if (or (= (point) (point-min)) (= (point) (point-max))) - (error "Reference not found"))))) - (let ((params (append args '((:results . "silent"))))) - (setq result - (case type - (results-line (org-babel-read-result)) - (table (org-babel-read-table)) - (list (org-babel-read-list)) - (file (org-babel-read-link)) - (source-block (org-babel-execute-src-block - nil nil (if org-babel-update-intermediate - nil params))) - (lob (org-babel-execute-src-block - nil lob-info params)) - (id (org-babel-ref-headline-body))))) - (if (symbolp result) - (format "%S" result) - (if (and index (listp result)) - (org-babel-ref-index-list index result) - result))))))) + (with-current-buffer (or org-babel-exp-reference-buffer (current-buffer)) + (save-excursion + (let ((case-fold-search t) + args new-refere new-header-args new-referent split-file split-ref + index) + ;; if ref is indexed grab the indices -- beware nested indices + (when (and (string-match "\\[\\([^\\[]+\\)\\]$" ref) + (let ((str (substring ref 0 (match-beginning 0)))) + (= (cl-count ?\( str) (cl-count ?\) str)))) + (setq index (match-string 1 ref)) + (setq ref (substring ref 0 (match-beginning 0)))) + ;; assign any arguments to pass to source block + (when (string-match + "^\\(.+?\\)\\(\\[\\(.*\\)\\]\\|\\(\\)\\)(\\(.*\\))$" ref) + (setq new-refere (match-string 1 ref)) + (setq new-header-args (match-string 3 ref)) + (setq new-referent (match-string 5 ref)) + (when (> (length new-refere) 0) + (when (> (length new-referent) 0) + (setq args (mapcar (lambda (ref) (cons :var ref)) + (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))) + (setq ref new-refere))) + (when (string-match "^\\(.+\\):\\(.+\\)$" ref) + (setq split-file (match-string 1 ref)) + (setq split-ref (match-string 2 ref)) + (find-file split-file) + (setq ref split-ref)) + (org-with-wide-buffer + (goto-char (point-min)) + (let* ((params (append args '((:results . "silent")))) + (regexp (org-babel-named-data-regexp-for-name ref)) + (result + (catch :found + ;; Check for code blocks or named data. + (while (re-search-forward regexp nil t) + ;; Ignore COMMENTed headings and orphaned + ;; affiliated keywords. + (unless (org-in-commented-heading-p) + (let ((e (org-element-at-point))) + (when (equal (org-element-property :name e) ref) + (goto-char + (org-element-property :post-affiliated e)) + (pcase (org-element-type e) + (`babel-call + (throw :found + (org-babel-execute-src-block + nil (org-babel-lob-get-info e) params))) + (`src-block + (throw :found + (org-babel-execute-src-block + nil nil + (and + (not org-babel-update-intermediate) + params)))) + ((and (let v (org-babel-read-element e)) + (guard v)) + (throw :found v)) + (_ (error "Reference not found"))))))) + ;; Check for local or global headlines by ID. + (when (org-babel-ref-goto-headline-id ref) + (throw :found (org-babel-ref-headline-body))) + ;; Check the Library of Babel. + (let ((info (cdr (assq (intern ref) + org-babel-library-of-babel)))) + (when info + (throw :found + (org-babel-execute-src-block nil info params)))) + (error "Reference `%s' not found in this buffer" ref)))) + (cond + ((symbolp result) (format "%S" result)) + ((and index (listp result)) + (org-babel-ref-index-list index result)) + (t result))))))))) (defun org-babel-ref-index-list (index lis) "Return the subset of LIS indexed by INDEX. @@ -251,21 +241,9 @@ to \"0:-1\"." (defun org-babel-ref-split-args (arg-string) "Split ARG-STRING into top-level arguments of balanced parenthesis." - (mapcar #'org-babel-trim (org-babel-balanced-split arg-string 44))) + (mapcar #'org-trim (org-babel-balanced-split arg-string 44))) -(defvar org-bracket-link-regexp) -(defun org-babel-ref-at-ref-p () - "Return the type of reference located at point. -Return nil if none of the supported reference types are found. -Supported reference types are tables and source blocks." - (cond ((org-at-table-p) 'table) - ((org-at-item-p) 'list) - ((looking-at "^[ \t]*#\\+BEGIN_SRC") 'source-block) - ((looking-at org-bracket-link-regexp) 'file) - ((looking-at org-babel-result-regexp) 'results-line))) (provide 'ob-ref) - - ;;; ob-ref.el ends here diff --git a/lisp/org/ob-ruby.el b/lisp/org/ob-ruby.el index 88a99876964..d9525ea3d4c 100644 --- a/lisp/org/ob-ruby.el +++ b/lisp/org/ob-ruby.el @@ -1,4 +1,4 @@ -;;; ob-ruby.el --- org-babel functions for ruby evaluation +;;; ob-ruby.el --- Babel Functions for Ruby -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -37,11 +37,14 @@ ;;; Code: (require 'ob) -(eval-when-compile (require 'cl)) +(declare-function org-trim "org" (s &optional keep-lead)) (declare-function run-ruby "ext:inf-ruby" (&optional command name)) (declare-function xmp "ext:rcodetools" (&optional option)) +(defvar inf-ruby-default-implementation) +(defvar inf-ruby-implementations) + (defvar org-babel-tangle-lang-exts) (add-to-list 'org-babel-tangle-lang-exts '("ruby" . "rb")) @@ -68,16 +71,16 @@ "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 (assoc :session params)))) - (result-params (cdr (assoc :result-params params))) - (result-type (cdr (assoc :result-type params))) + (cdr (assq :session params)))) + (result-params (cdr (assq :result-params params))) + (result-type (cdr (assq :result-type params))) (full-body (org-babel-expand-body:generic body params (org-babel-variable-assignments:ruby params))) (result (if (member "xmp" result-params) (with-temp-buffer (require 'rcodetools) (insert full-body) - (xmp (cdr (assoc :xmp-option params))) + (xmp (cdr (assq :xmp-option params))) (buffer-string)) (org-babel-ruby-evaluate session full-body result-type result-params)))) @@ -85,10 +88,10 @@ This function is called by `org-babel-execute-src-block'." (org-babel-result-cond result-params result (org-babel-ruby-table-or-string result)) - (org-babel-pick-name (cdr (assoc :colname-names params)) - (cdr (assoc :colnames params))) - (org-babel-pick-name (cdr (assoc :rowname-names params)) - (cdr (assoc :rownames params)))))) + (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-prep-session:ruby (session params) "Prepare SESSION according to the header arguments specified in PARAMS." @@ -121,7 +124,7 @@ This function is called by `org-babel-execute-src-block'." (format "%s=%s" (car pair) (org-babel-ruby-var-to-ruby (cdr pair)))) - (mapcar #'cdr (org-babel-get-header params :var)))) + (org-babel--get-vars params))) (defun org-babel-ruby-var-to-ruby (var) "Convert VAR into a ruby variable. @@ -129,7 +132,7 @@ Convert an elisp value into a string of ruby source code specifying a variable of the same value." (if (listp var) (concat "[" (mapconcat #'org-babel-ruby-var-to-ruby var ", ") "]") - (if (equal var 'hline) + (if (eq var 'hline) org-babel-ruby-hline-to (format "%S" var)))) @@ -139,23 +142,27 @@ If RESULTS look like a table, then convert them into an Emacs-lisp table, otherwise return the results as a string." (let ((res (org-babel-script-escape results))) (if (listp res) - (mapcar (lambda (el) (if (equal el 'nil) - org-babel-ruby-nil-to el)) + (mapcar (lambda (el) (if (not el) + org-babel-ruby-nil-to el)) 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 ((session-buffer (save-window-excursion - (run-ruby nil session) (current-buffer)))) + (let* ((cmd (cdr (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) + (current-buffer))))) (if (org-babel-comint-buffer-livep session-buffer) (progn (sit-for .25) session-buffer) - (sit-for .5) - (org-babel-ruby-initiate-session session))))) + (sit-for .5) + (org-babel-ruby-initiate-session session))))) (defvar org-babel-ruby-eoe-indicator ":org_babel_ruby_eoe" "String to indicate that evaluation has completed.") @@ -185,46 +192,53 @@ end ") (defun org-babel-ruby-evaluate - (buffer body &optional result-type result-params) + (buffer body &optional result-type result-params) "Pass BODY to the Ruby process in BUFFER. -If RESULT-TYPE equals 'output then return a list of the outputs -of the statements in BODY, if RESULT-TYPE equals 'value then +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, as elisp." (if (not buffer) ;; external process evaluation - (case result-type - (output (org-babel-eval org-babel-ruby-command body)) - (value (let ((tmp-file (org-babel-temp-file "ruby-"))) - (org-babel-eval - org-babel-ruby-command - (format (if (member "pp" result-params) - org-babel-ruby-pp-wrapper-method - org-babel-ruby-wrapper-method) - body (org-babel-process-file-name tmp-file 'noquote))) - (let ((raw (org-babel-eval-read-file tmp-file))) - (if (or (member "code" result-params) - (member "pp" result-params)) - raw - (org-babel-ruby-table-or-string raw)))))) + (pcase result-type + (`output (org-babel-eval org-babel-ruby-command body)) + (`value (let ((tmp-file (org-babel-temp-file "ruby-"))) + (org-babel-eval + org-babel-ruby-command + (format (if (member "pp" result-params) + org-babel-ruby-pp-wrapper-method + org-babel-ruby-wrapper-method) + body (org-babel-process-file-name tmp-file 'noquote))) + (org-babel-eval-read-file tmp-file)))) ;; comint session evaluation - (case result-type - (output - (mapconcat - #'identity - (butlast - (split-string - (mapconcat - #'org-babel-trim - (butlast - (org-babel-comint-with-output - (buffer org-babel-ruby-eoe-indicator t body) - (mapc - (lambda (line) - (insert (org-babel-chomp line)) (comint-send-input nil t)) - (list body org-babel-ruby-eoe-indicator)) - (comint-send-input nil t)) 2) - "\n") "[\r\n]")) "\n")) - (value + (pcase result-type + (`output + (let ((eoe-string (format "puts \"%s\"" org-babel-ruby-eoe-indicator))) + ;; Force the session to be ready before the actual session + ;; code is run. There is some problem in comint that will + ;; sometimes show the prompt after the the input has already + ;; been inserted and that throws off the extraction of the + ;; result for Babel. + (org-babel-comint-with-output + (buffer org-babel-ruby-eoe-indicator t eoe-string) + (insert eoe-string) (comint-send-input nil t)) + ;; Now we can start the evaluation. + (mapconcat + #'identity + (butlast + (split-string + (mapconcat + #'org-trim + (org-babel-comint-with-output + (buffer org-babel-ruby-eoe-indicator t body) + (mapc + (lambda (line) + (insert (org-babel-chomp line)) (comint-send-input nil t)) + (list "conf.echo=false;_org_prompt_mode=conf.prompt_mode;conf.prompt_mode=:NULL" + body + "conf.prompt_mode=_org_prompt_mode;conf.echo=true" + eoe-string))) + "\n") "[\r\n]") 4) "\n"))) + (`value (let* ((tmp-file (org-babel-temp-file "ruby-")) (ppp (or (member "code" result-params) (member "pp" result-params)))) @@ -247,12 +261,6 @@ return the value of the last statement in BODY, as elisp." (comint-send-input nil t)) (org-babel-eval-read-file tmp-file)))))) -(defun org-babel-ruby-read-string (string) - "Strip \\\"s from around a ruby string." - (if (string-match "^\"\\([^\000]+\\)\"$" string) - (match-string 1 string) - string)) - (provide 'ob-ruby) diff --git a/lisp/org/ob-sass.el b/lisp/org/ob-sass.el index 847c144e80a..769c9011f82 100644 --- a/lisp/org/ob-sass.el +++ b/lisp/org/ob-sass.el @@ -1,4 +1,4 @@ -;;; ob-sass.el --- org-babel functions for the sass css generation language +;;; ob-sass.el --- Babel Functions for the Sass CSS generation language -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -45,10 +45,9 @@ (defun org-babel-execute:sass (body params) "Execute a block of Sass code with Babel. This function is called by `org-babel-execute-src-block'." - (let* ((result-params (split-string (or (cdr (assoc :results params)) ""))) - (file (cdr (assoc :file params))) + (let* ((file (cdr (assq :file params))) (out-file (or file (org-babel-temp-file "sass-out-"))) - (cmdline (cdr (assoc :cmdline params))) + (cmdline (cdr (assq :cmdline params))) (in-file (org-babel-temp-file "sass-in-")) (cmd (concat "sass " (or cmdline "") " " (org-babel-process-file-name in-file) @@ -60,7 +59,7 @@ This function is called by `org-babel-execute-src-block'." nil ;; signal that output has already been written to file (with-temp-buffer (insert-file-contents out-file) (buffer-string))))) -(defun org-babel-prep-session:sass (session params) +(defun org-babel-prep-session:sass (_session _params) "Raise an error because sass does not support sessions." (error "Sass does not support sessions")) diff --git a/lisp/org/ob-scala.el b/lisp/org/ob-scala.el deleted file mode 100644 index 9bddeed6e6f..00000000000 --- a/lisp/org/ob-scala.el +++ /dev/null @@ -1,124 +0,0 @@ -;;; ob-scala.el --- org-babel functions for Scala evaluation - -;; Copyright (C) 2012-2017 Free Software Foundation, Inc. - -;; Author: Andrzej Lichnerowicz -;; Keywords: literate programming, reproducible research -;; Homepage: http://orgmode.org - -;; This file is part of GNU Emacs. - -;; GNU Emacs is free software: you can redistribute it and/or modify -;; it under the terms of the GNU General Public License as published by -;; the Free Software Foundation, either version 3 of the License, or -;; (at your option) any later version. - -;; GNU Emacs is distributed in the hope that it will be useful, -;; but WITHOUT ANY WARRANTY; without even the implied warranty of -;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -;; GNU General Public License for more details. - -;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. - -;;; Commentary: -;; Currently only supports the external execution. No session support yet. - -;;; Requirements: -;; - Scala language :: http://www.scala-lang.org/ -;; - Scala major mode :: Can be installed from Scala sources -;; https://github.com/scala/scala-dist/blob/master/tool-support/src/emacs/scala-mode.el - -;;; Code: -(require 'ob) -(eval-when-compile (require 'cl)) - -(defvar org-babel-tangle-lang-exts) ;; Autoloaded -(add-to-list 'org-babel-tangle-lang-exts '("scala" . "scala")) -(defvar org-babel-default-header-args:scala '()) -(defvar org-babel-scala-command "scala" - "Name of the command to use for executing Scala code.") - -(defun org-babel-execute:scala (body params) - "Execute a block of Scala code with org-babel. This function is -called by `org-babel-execute-src-block'" - (message "executing Scala source code block") - (let* ((processed-params (org-babel-process-params params)) - (session (org-babel-scala-initiate-session (nth 0 processed-params))) - (vars (nth 1 processed-params)) - (result-params (nth 2 processed-params)) - (result-type (cdr (assoc :result-type params))) - (full-body (org-babel-expand-body:generic - body params)) - (result (org-babel-scala-evaluate - session full-body result-type result-params))) - - (org-babel-reassemble-table - result - (org-babel-pick-name - (cdr (assoc :colname-names params)) (cdr (assoc :colnames params))) - (org-babel-pick-name - (cdr (assoc :rowname-names params)) (cdr (assoc :rownames params)))))) - - -(defun org-babel-scala-table-or-string (results) - "Convert RESULTS into an appropriate elisp value. -If RESULTS look like a table, then convert them into an -Emacs-lisp table, otherwise return the results as a string." - (org-babel-script-escape results)) - - -(defvar org-babel-scala-wrapper-method - -"var str_result :String = null; - -Console.withOut(new java.io.OutputStream() {def write(b: Int){ -}}) { - str_result = { -%s - }.toString -} - -print(str_result) -") - - -(defun org-babel-scala-evaluate - (session body &optional result-type result-params) - "Evaluate BODY in external Scala process. -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." - (when session (error "Sessions are not (yet) supported for Scala")) - (case result-type - (output - (let ((src-file (org-babel-temp-file "scala-"))) - (progn (with-temp-file src-file (insert body)) - (org-babel-eval - (concat org-babel-scala-command " " src-file) "")))) - (value - (let* ((src-file (org-babel-temp-file "scala-")) - (wrapper (format org-babel-scala-wrapper-method body))) - (with-temp-file src-file (insert wrapper)) - (let ((raw (org-babel-eval - (concat org-babel-scala-command " " src-file) ""))) - (org-babel-result-cond result-params - raw - (org-babel-scala-table-or-string raw))))))) - - -(defun org-babel-prep-session:scala (session params) - "Prepare SESSION according to the header arguments specified in PARAMS." - (error "Sessions are not (yet) supported for Scala")) - -(defun org-babel-scala-initiate-session (&optional session) - "If there is not a current inferior-process-buffer in SESSION -then create. Return the initialized session. Sessions are not -supported in Scala." - nil) - -(provide 'ob-scala) - - - -;;; ob-scala.el ends here diff --git a/lisp/org/ob-scheme.el b/lisp/org/ob-scheme.el index ae77c7c3edf..f67080adfd3 100644 --- a/lisp/org/ob-scheme.el +++ b/lisp/org/ob-scheme.el @@ -1,4 +1,4 @@ -;;; ob-scheme.el --- org-babel functions for Scheme +;;; ob-scheme.el --- Babel Functions for Scheme -*- lexical-binding: t; -*- ;; Copyright (C) 2010-2017 Free Software Foundation, Inc. @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -32,7 +32,7 @@ ;;; Requirements: ;; - a working scheme implementation -;; (e.g. guile http://www.gnu.org/software/guile/guile.html) +;; (e.g. guile https://www.gnu.org/software/guile/guile.html) ;; ;; - for session based evaluation geiser is required, which is available from ;; ELPA. @@ -44,37 +44,51 @@ (defvar geiser-impl--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 +(defvar geiser-debug-jump-to-debug-p) ; Defined in geiser-debug.el +(defvar geiser-repl-use-other-window) ; Defined in geiser-repl.el +(defvar geiser-repl-window-allow-split) ; Defined in geiser-repl.el (declare-function run-geiser "ext:geiser-repl" (impl)) (declare-function geiser-mode "ext:geiser-mode" ()) (declare-function geiser-eval-region "ext:geiser-mode" (start end &optional and-go raw nomsg)) (declare-function geiser-repl-exit "ext:geiser-repl" (&optional arg)) +(declare-function geiser-eval--retort-output "ext:geiser-eval" (ret)) +(declare-function geiser-eval--retort-result-str "ext:geiser-eval" (ret prefix)) + +(defcustom org-babel-scheme-null-to 'hline + "Replace `null' and empty lists in scheme tables with this before returning." + :group 'org-babel + :version "26.1" + :package-version '(Org . "9.1") + :type 'symbol) (defvar org-babel-default-header-args:scheme '() "Default header arguments for scheme code blocks.") (defun org-babel-expand-body:scheme (body params) "Expand BODY according to PARAMS, return the expanded body." - (let ((vars (mapcar #'cdr (org-babel-get-header params :var)))) - (if (> (length vars) 0) - (concat "(let (" - (mapconcat - (lambda (var) (format "%S" (print `(,(car var) ',(cdr var))))) - vars "\n ") - ")\n" body ")") - body))) - - -(defvar org-babel-scheme-repl-map (make-hash-table :test 'equal) + (let ((vars (org-babel--get-vars params)) + (prepends (cdr (assq :prologue params)))) + (concat (and prepends (concat prepends "\n")) + (if (null vars) body + (format "(let (%s)\n%s\n)" + (mapconcat + (lambda (var) + (format "%S" (print `(,(car var) ',(cdr var))))) + vars + "\n ") + body))))) + + +(defvar org-babel-scheme-repl-map (make-hash-table :test #'equal) "Map of scheme sessions to session names.") (defun org-babel-scheme-cleanse-repl-map () "Remove dead buffers from the REPL map." (maphash - (lambda (x y) - (when (not (buffer-name y)) - (remhash x org-babel-scheme-repl-map))) + (lambda (x y) (unless (buffer-name y) (remhash x org-babel-scheme-repl-map))) org-babel-scheme-repl-map)) (defun org-babel-scheme-get-session-buffer (session-name) @@ -112,12 +126,25 @@ If the session is unnamed (nil), generate a name. If the session is `none', use nil for the session name, and org-babel-scheme-execute-with-geiser will use a temporary session." - (let ((result - (cond ((not name) - (concat buffer " " (symbol-name impl) " REPL")) - ((string= name "none") nil) - (name)))) - result)) + (cond ((not name) (concat buffer " " (symbol-name impl) " REPL")) + ((string= name "none") nil) + (name))) + +(defmacro org-babel-scheme-capture-current-message (&rest body) + "Capture current message in both interactive and noninteractive mode" + `(if noninteractive + (let ((original-message (symbol-function 'message)) + (current-message nil)) + (unwind-protect + (progn + (defun message (&rest args) + (setq current-message (apply original-message args))) + ,@body + current-message) + (fset 'message original-message))) + (progn + ,@body + (current-message)))) (defun org-babel-scheme-execute-with-geiser (code output impl repl) "Execute code in specified REPL. If the REPL doesn't exist, create it @@ -129,36 +156,46 @@ is true; otherwise returns the last value." (with-temp-buffer (insert (format ";; -*- geiser-scheme-implementation: %s -*-" impl)) (newline) - (insert (if output - (format "(with-output-to-string (lambda () %s))" code) - code)) + (insert code) (geiser-mode) - (let ((repl-buffer (save-current-buffer - (org-babel-scheme-get-repl impl repl)))) - (when (not (eq impl (org-babel-scheme-get-buffer-impl - (current-buffer)))) - (message "Implementation mismatch: %s (%s) %s (%s)" impl (symbolp impl) - (org-babel-scheme-get-buffer-impl (current-buffer)) - (symbolp (org-babel-scheme-get-buffer-impl - (current-buffer))))) - (setq geiser-repl--repl repl-buffer) - (setq geiser-impl--implementation nil) - (geiser-eval-region (point-min) (point-max)) - (setq result - (if (equal (substring (current-message) 0 3) "=> ") - (replace-regexp-in-string "^=> " "" (current-message)) - "\"An error occurred.\"")) - (when (not repl) - (save-current-buffer (set-buffer repl-buffer) - (geiser-repl-exit)) - (set-process-query-on-exit-flag (get-buffer-process repl-buffer) nil) - (kill-buffer repl-buffer)) - (setq result (if (or (string= result "#<void>") - (string= result "#<unspecified>")) - nil - (read result))))) + (let ((geiser-repl-window-allow-split nil) + (geiser-repl-use-other-window nil)) + (let ((repl-buffer (save-current-buffer + (org-babel-scheme-get-repl impl repl)))) + (when (not (eq impl (org-babel-scheme-get-buffer-impl + (current-buffer)))) + (message "Implementation mismatch: %s (%s) %s (%s)" impl (symbolp impl) + (org-babel-scheme-get-buffer-impl (current-buffer)) + (symbolp (org-babel-scheme-get-buffer-impl + (current-buffer))))) + (setq geiser-repl--repl repl-buffer) + (setq geiser-impl--implementation nil) + (let ((geiser-debug-jump-to-debug-p nil) + (geiser-debug-show-debug-p nil)) + (let ((ret (geiser-eval-region (point-min) (point-max)))) + (setq result (if output + (geiser-eval--retort-output ret) + (geiser-eval--retort-result-str ret ""))))) + (when (not repl) + (save-current-buffer (set-buffer repl-buffer) + (geiser-repl-exit)) + (set-process-query-on-exit-flag (get-buffer-process repl-buffer) nil) + (kill-buffer repl-buffer))))) result)) +(defun org-babel-scheme--table-or-string (results) + "Convert RESULTS into an appropriate elisp value. +If the results look like a list or tuple, then convert them into an +Emacs-lisp table, otherwise return the results as a string." + (let ((res (org-babel-script-escape results))) + (cond ((listp res) + (mapcar (lambda (el) + (if (or (null el) (eq el 'null)) + org-babel-scheme-null-to + el)) + res)) + (t res)))) + (defun org-babel-execute:scheme (body params) "Execute a block of Scheme code with org-babel. This function is called by `org-babel-execute-src-block'" @@ -167,24 +204,28 @@ This function is called by `org-babel-execute-src-block'" "^ ?\\*\\([^*]+\\)\\*" "\\1" (buffer-name source-buffer)))) (save-excursion - (org-babel-reassemble-table - (let* ((result-type (cdr (assoc :result-type params))) - (impl (or (when (cdr (assoc :scheme params)) - (intern (cdr (assoc :scheme params)))) - geiser-default-implementation - (car geiser-active-implementations))) - (session (org-babel-scheme-make-session-name - source-buffer-name (cdr (assoc :session params)) impl)) - (full-body (org-babel-expand-body:scheme body params))) - (org-babel-scheme-execute-with-geiser - full-body ; code - (string= result-type "output") ; output? - impl ; implementation - (and (not (string= session "none")) session))) ; session - (org-babel-pick-name (cdr (assoc :colname-names params)) - (cdr (assoc :colnames params))) - (org-babel-pick-name (cdr (assoc :rowname-names params)) - (cdr (assoc :rownames params))))))) + (let* ((result-type (cdr (assq :result-type params))) + (impl (or (when (cdr (assq :scheme params)) + (intern (cdr (assq :scheme params)))) + geiser-default-implementation + (car geiser-active-implementations))) + (session (org-babel-scheme-make-session-name + source-buffer-name (cdr (assq :session params)) impl)) + (full-body (org-babel-expand-body:scheme body params)) + (result + (org-babel-scheme-execute-with-geiser + full-body ; code + (string= result-type "output") ; output? + impl ; implementation + (and (not (string= session "none")) session)))) ; session + (let ((table + (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)))))) + (org-babel-scheme--table-or-string table)))))) (provide 'ob-scheme) diff --git a/lisp/org/ob-screen.el b/lisp/org/ob-screen.el index a15f7f7bd86..fbf167e0e41 100644 --- a/lisp/org/ob-screen.el +++ b/lisp/org/ob-screen.el @@ -1,4 +1,4 @@ -;;; ob-screen.el --- org-babel support for interactive terminal +;;; ob-screen.el --- Babel Support for Interactive Terminal -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -48,18 +48,17 @@ In case you want to use a different screen than one selected by your $PATH") \"default\" session is used when none is specified." (message "Sending source code block to interactive terminal session...") (save-window-excursion - (let* ((session (cdr (assoc :session params))) + (let* ((session (cdr (assq :session params))) (socket (org-babel-screen-session-socketname session))) (unless socket (org-babel-prep-session:screen session params)) (org-babel-screen-session-execute-string session (org-babel-expand-body:generic body params))))) -(defun org-babel-prep-session:screen (session params) +(defun org-babel-prep-session:screen (_session params) "Prepare SESSION according to the header arguments specified in PARAMS." - (let* ((session (cdr (assoc :session params))) - (socket (org-babel-screen-session-socketname session)) - (cmd (cdr (assoc :cmd params))) - (terminal (cdr (assoc :terminal params))) + (let* ((session (cdr (assq :session params))) + (cmd (cdr (assq :cmd params))) + (terminal (cdr (assq :terminal 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 @@ -104,7 +103,7 @@ In case you want to use a different screen than one selected by your $PATH") sockets))))) (when match-socket (car (split-string match-socket))))) -(defun org-babel-screen-session-write-temp-file (session body) +(defun org-babel-screen-session-write-temp-file (_session body) "Save BODY in a temp file that is named after SESSION." (let ((tmpfile (org-babel-temp-file "screen-"))) (with-temp-file tmpfile @@ -119,11 +118,10 @@ In case you want to use a different screen than one selected by your $PATH") "Test if the default setup works. The terminal should shortly flicker." (interactive) - (let* ((session "org-babel-testing") - (random-string (format "%s" (random 99999))) + (let* ((random-string (format "%s" (random 99999))) (tmpfile (org-babel-temp-file "ob-screen-test-")) (body (concat "echo '" random-string "' > " tmpfile "\nexit\n")) - process tmp-string) + tmp-string) (org-babel-execute:screen body org-babel-default-header-args:screen) ;; XXX: need to find a better way to do the following (while (not (file-readable-p tmpfile)) diff --git a/lisp/org/ob-sed.el b/lisp/org/ob-sed.el new file mode 100644 index 00000000000..7bd0bfb77c6 --- /dev/null +++ b/lisp/org/ob-sed.el @@ -0,0 +1,105 @@ +;;; ob-sed.el --- Babel Functions for Sed Scripts -*- lexical-binding: t; -*- + +;; Copyright (C) 2015-2017 Free Software Foundation, Inc. + +;; Author: Bjarte Johansen +;; Keywords: literate programming, reproducible research +;; Version: 0.1.0 + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Provides a way to evaluate sed scripts in Org mode. + +;;; Usage: + +;; Add to your Emacs config: + +;; (org-babel-do-load-languages +;; 'org-babel-load-languages +;; '((sed . t))) + +;; In addition to the normal header arguments, ob-sed also provides +;; :cmd-line and :in-file. :cmd-line allows one to pass other flags to +;; the sed command like the "--in-place" flag which makes sed edit the +;; file pass to it instead of outputting to standard out or to a +;; different file. :in-file is a header arguments that allows one to +;; tell Org Babel which file the sed script to act on. + +;;; Code: +(require 'ob) + +(defvar org-babel-sed-command "sed" + "Name of the sed executable command.") + +(defvar org-babel-tangle-lang-exts) +(add-to-list 'org-babel-tangle-lang-exts '("sed" . "sed")) + +(defconst org-babel-header-args:sed + '((:cmd-line . :any) + (:in-file . :any)) + "Sed specific header arguments.") + +(defvar org-babel-default-header-args:sed '() + "Default arguments for evaluating a sed source block.") + +(defun org-babel-execute:sed (body params) + "Execute a block of sed code with Org Babel. +BODY is the source inside a sed source block and PARAMS is an +association list over the source block configurations. This +function is called by `org-babel-execute-src-block'." + (message "executing sed source code block") + (let* ((result-params (cdr (assq :result-params params))) + (cmd-line (cdr (assq :cmd-line params))) + (in-file (cdr (assq :in-file params))) + (code-file (let ((file (org-babel-temp-file "sed-"))) + (with-temp-file file + (insert body)) file)) + (stdin (let ((stdin (cdr (assq :stdin params)))) + (when stdin + (let ((tmp (org-babel-temp-file "sed-stdin-")) + (res (org-babel-ref-resolve stdin))) + (with-temp-file tmp + (insert res)) + tmp)))) + (cmd (mapconcat #'identity + (remq nil + (list org-babel-sed-command + (format "--file=\"%s\"" code-file) + cmd-line + in-file)) + " "))) + (org-babel-reassemble-table + (let ((results + (cond + (stdin (with-temp-buffer + (call-process-shell-command cmd stdin (current-buffer)) + (buffer-string))) + (t (org-babel-eval cmd ""))))) + (when results + (org-babel-result-cond result-params + results + (let ((tmp (org-babel-temp-file "sed-results-"))) + (with-temp-file tmp (insert results)) + (org-babel-import-elisp-from-file tmp))))) + (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)))))) + +(provide 'ob-sed) +;;; ob-sed.el ends here diff --git a/lisp/org/ob-sh.el b/lisp/org/ob-sh.el deleted file mode 100644 index 47dbab3f6d9..00000000000 --- a/lisp/org/ob-sh.el +++ /dev/null @@ -1,217 +0,0 @@ -;;; ob-sh.el --- org-babel functions for shell evaluation - -;; Copyright (C) 2009-2017 Free Software Foundation, Inc. - -;; Author: Eric Schulte -;; Keywords: literate programming, reproducible research -;; Homepage: http://orgmode.org - -;; This file is part of GNU Emacs. - -;; GNU Emacs is free software: you can redistribute it and/or modify -;; it under the terms of the GNU General Public License as published by -;; the Free Software Foundation, either version 3 of the License, or -;; (at your option) any later version. - -;; GNU Emacs is distributed in the hope that it will be useful, -;; but WITHOUT ANY WARRANTY; without even the implied warranty of -;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -;; GNU General Public License for more details. - -;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. - -;;; Commentary: - -;; Org-Babel support for evaluating shell source code. - -;;; Code: -(require 'ob) -(require 'shell) -(eval-when-compile (require 'cl)) - -(declare-function org-babel-comint-wait-for-output "ob-comint" (buffer)) -(declare-function org-babel-comint-buffer-livep "ob-comint" (buffer)) -(declare-function orgtbl-to-generic "org-table" - (table params &optional backend)) - -(defvar org-babel-default-header-args:sh '()) - -(defvar org-babel-sh-command "sh" - "Command used to invoke a shell. -This will be passed to `shell-command-on-region'") - -(defcustom org-babel-sh-var-quote-fmt - "$(cat <<'BABEL_TABLE'\n%s\nBABEL_TABLE\n)" - "Format string used to escape variables when passed to shell scripts." - :group 'org-babel - :type 'string) - -(defun org-babel-execute:sh (body params) - "Execute a block of Shell commands with Babel. -This function is called by `org-babel-execute-src-block'." - (let* ((session (org-babel-sh-initiate-session - (cdr (assoc :session params)))) - (stdin (let ((stdin (cdr (assoc :stdin params)))) - (when stdin (org-babel-sh-var-to-string - (org-babel-ref-resolve stdin))))) - (full-body (org-babel-expand-body:generic - body params (org-babel-variable-assignments:sh params)))) - (org-babel-reassemble-table - (org-babel-sh-evaluate session full-body params stdin) - (org-babel-pick-name - (cdr (assoc :colname-names params)) (cdr (assoc :colnames params))) - (org-babel-pick-name - (cdr (assoc :rowname-names params)) (cdr (assoc :rownames params)))))) - -(defun org-babel-prep-session:sh (session params) - "Prepare SESSION according to the header arguments specified in PARAMS." - (let* ((session (org-babel-sh-initiate-session session)) - (var-lines (org-babel-variable-assignments:sh params))) - (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)) - session)) - -(defun org-babel-load-session:sh (session body params) - "Load BODY into SESSION." - (save-window-excursion - (let ((buffer (org-babel-prep-session:sh session params))) - (with-current-buffer buffer - (goto-char (process-mark (get-buffer-process (current-buffer)))) - (insert (org-babel-chomp body))) - buffer))) - -;; helper functions - -(defun org-babel-variable-assignments:sh (params) - "Return list of shell statements assigning the block's variables." - (let ((sep (cdr (assoc :separator params)))) - (mapcar - (lambda (pair) - (format "%s=%s" - (car pair) - (org-babel-sh-var-to-sh (cdr pair) sep))) - (mapcar #'cdr (org-babel-get-header params :var))))) - -(defun org-babel-sh-var-to-sh (var &optional sep) - "Convert an elisp value to a shell variable. -Convert an elisp var into a string of shell commands specifying a -var of the same value." - (format org-babel-sh-var-quote-fmt (org-babel-sh-var-to-string var sep))) - -(defun org-babel-sh-var-to-string (var &optional sep) - "Convert an elisp value to a string." - (let ((echo-var (lambda (v) (if (stringp v) v (format "%S" v))))) - (cond - ((and (listp var) (or (listp (car var)) (equal (car var) 'hline))) - (orgtbl-to-generic var (list :sep (or sep "\t") :fmt echo-var))) - ((listp var) - (mapconcat echo-var var "\n")) - (t (funcall echo-var var))))) - -(defun org-babel-sh-table-or-results (results) - "Convert RESULTS to an appropriate elisp value. -If the results look like a table, then convert them into an -Emacs-lisp table, otherwise return the results as a string." - (org-babel-script-escape results)) - -(defun org-babel-sh-initiate-session (&optional session params) - "Initiate a session named SESSION according to PARAMS." - (when (and session (not (string= session "none"))) - (save-window-excursion - (or (org-babel-comint-buffer-livep session) - (progn - (shell session) - ;; Needed for Emacs 23 since the marker is initially - ;; undefined and the filter functions try to use it without - ;; checking. - (set-marker comint-last-output-start (point)) - (get-buffer (current-buffer))))))) - -(defvar org-babel-sh-eoe-indicator "echo 'org_babel_sh_eoe'" - "String to indicate that evaluation has completed.") -(defvar org-babel-sh-eoe-output "org_babel_sh_eoe" - "String to indicate that evaluation has completed.") - -(defun org-babel-sh-evaluate (session body &optional params stdin) - "Pass BODY to the Shell process in BUFFER. -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 ((results - (cond - (stdin ; external shell script w/STDIN - (let ((script-file (org-babel-temp-file "sh-script-")) - (stdin-file (org-babel-temp-file "sh-stdin-")) - (shebang (cdr (assoc :shebang params))) - (padline (not (string= "no" (cdr (assoc :padline params)))))) - (with-temp-file script-file - (when shebang (insert (concat shebang "\n"))) - (when padline (insert "\n")) - (insert body)) - (set-file-modes script-file #o755) - (with-temp-file stdin-file (insert stdin)) - (with-temp-buffer - (call-process-shell-command - (if shebang - script-file - (format "%s %s" org-babel-sh-command script-file)) - stdin-file - (current-buffer)) - (buffer-string)))) - (session ; session evaluation - (mapconcat - #'org-babel-sh-strip-weird-long-prompt - (mapcar - #'org-babel-trim - (butlast - (org-babel-comint-with-output - (session org-babel-sh-eoe-output t body) - (mapc - (lambda (line) - (insert line) - (comint-send-input nil t) - (while (save-excursion - (goto-char comint-last-input-end) - (not (re-search-forward - comint-prompt-regexp nil t))) - (accept-process-output - (get-buffer-process (current-buffer))))) - (append - (split-string (org-babel-trim body) "\n") - (list org-babel-sh-eoe-indicator)))) - 2)) "\n")) - ('otherwise ; external shell script - (if (and (cdr (assoc :shebang params)) - (> (length (cdr (assoc :shebang params))) 0)) - (let ((script-file (org-babel-temp-file "sh-script-")) - (shebang (cdr (assoc :shebang params))) - (padline (not (equal "no" (cdr (assoc :padline params)))))) - (with-temp-file script-file - (when shebang (insert (concat shebang "\n"))) - (when padline (insert "\n")) - (insert body)) - (set-file-modes script-file #o755) - (org-babel-eval script-file "")) - (org-babel-eval org-babel-sh-command (org-babel-trim body))))))) - (when results - (let ((result-params (cdr (assoc :result-params params)))) - (org-babel-result-cond result-params - results - (let ((tmp-file (org-babel-temp-file "sh-"))) - (with-temp-file tmp-file (insert results)) - (org-babel-import-elisp-from-file tmp-file))))))) - -(defun org-babel-sh-strip-weird-long-prompt (string) - "Remove prompt cruft from a string of shell output." - (while (string-match "^% +[\r\n$]+ *" string) - (setq string (substring string (match-end 0)))) - string) - -(provide 'ob-sh) - - - -;;; ob-sh.el ends here diff --git a/lisp/org/ob-shell.el b/lisp/org/ob-shell.el new file mode 100644 index 00000000000..af64adb8923 --- /dev/null +++ b/lisp/org/ob-shell.el @@ -0,0 +1,283 @@ +;;; ob-shell.el --- Babel Functions for Shell Evaluation -*- lexical-binding: t; -*- + +;; Copyright (C) 2009-2017 Free Software Foundation, Inc. + +;; Author: Eric Schulte +;; Keywords: literate programming, reproducible research +;; Homepage: http://orgmode.org + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Org-Babel support for evaluating shell source code. + +;;; Code: +(require 'ob) +(require 'shell) +(require 'cl-lib) + +(declare-function org-babel-comint-in-buffer "ob-comint" (buffer &rest body) + t) +(declare-function org-babel-comint-wait-for-output "ob-comint" (buffer)) +(declare-function org-babel-comint-buffer-livep "ob-comint" (buffer)) +(declare-function org-babel-comint-with-output "ob-comint" (meta &rest body) + t) +(declare-function org-trim "org" (s &optional keep-lead)) +(declare-function orgtbl-to-generic "org-table" (table params)) + +(defvar org-babel-default-header-args:shell '()) +(defvar org-babel-shell-names) + +(defun org-babel-shell-initialize () + "Define execution functions associated to shell names. +This function has to be called whenever `org-babel-shell-names' +is modified outside the Customize interface." + (interactive) + (dolist (name org-babel-shell-names) + (eval `(defun ,(intern (concat "org-babel-execute:" name)) + (body params) + ,(format "Execute a block of %s commands with Babel." name) + (let ((shell-file-name ,name)) + (org-babel-execute:shell body params)))) + (eval `(defalias ',(intern (concat "org-babel-variable-assignments:" name)) + 'org-babel-variable-assignments:shell + ,(format "Return list of %s statements assigning to the block's \ +variables." + name))))) + +(defcustom org-babel-shell-names + '("sh" "bash" "csh" "ash" "dash" "ksh" "mksh" "posh") + "List of names of shell supported by babel shell code blocks. +Call `org-babel-shell-initialize' when modifying this variable +outside the Customize interface." + :group 'org-babel + :type '(repeat (string :tag "Shell name: ")) + :set (lambda (symbol value) + (set-default symbol value) + (org-babel-shell-initialize))) + +(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'." + (let* ((session (org-babel-sh-initiate-session + (cdr (assq :session params)))) + (stdin (let ((stdin (cdr (assq :stdin params)))) + (when stdin (org-babel-sh-var-to-string + (org-babel-ref-resolve stdin))))) + (cmdline (cdr (assq :cmdline params))) + (full-body (org-babel-expand-body:generic + body params (org-babel-variable-assignments:shell params)))) + (org-babel-reassemble-table + (org-babel-sh-evaluate session full-body params stdin cmdline) + (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-prep-session:shell (session params) + "Prepare SESSION according to the header arguments specified in PARAMS." + (let* ((session (org-babel-sh-initiate-session session)) + (var-lines (org-babel-variable-assignments:shell params))) + (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)) + session)) + +(defun org-babel-load-session:shell (session body params) + "Load BODY into SESSION." + (save-window-excursion + (let ((buffer (org-babel-prep-session:shell session params))) + (with-current-buffer buffer + (goto-char (process-mark (get-buffer-process (current-buffer)))) + (insert (org-babel-chomp body))) + buffer))) + + +;;; Helper functions +(defun org-babel--variable-assignments:sh-generic + (varname values &optional sep hline) + "Returns a list of statements declaring the values as a generic variable." + (format "%s=%s" varname (org-babel-sh-var-to-sh values sep hline))) + +(defun org-babel--variable-assignments:bash_array + (varname values &optional sep hline) + "Returns a list of statements declaring the values as a bash array." + (format "unset %s\ndeclare -a %s=( %s )" + varname varname + (mapconcat + (lambda (value) (org-babel-sh-var-to-sh value sep hline)) + values + " "))) + +(defun org-babel--variable-assignments:bash_assoc + (varname values &optional sep hline) + "Returns 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"))) + +(defun org-babel--variable-assignments:bash (varname values &optional sep hline) + "Represents the parameters as useful Bash shell variables." + (pcase values + (`((,_ ,_ . ,_) . ,_) ;two-dimensional array + (org-babel--variable-assignments:bash_assoc varname values sep hline)) + (`(,_ . ,_) ;simple list + (org-babel--variable-assignments:bash_array varname values sep hline)) + (_ ;scalar value + (org-babel--variable-assignments:sh-generic varname values sep hline)))) + +(defun org-babel-variable-assignments:shell (params) + "Return list of shell statements assigning the block's variables." + (let ((sep (cdr (assq :separator params))) + (hline (when (string= "yes" (cdr (assq :hlines params))) + (or (cdr (assq :hline-string params)) + "hline")))) + (mapcar + (lambda (pair) + (if (string-suffix-p "bash" shell-file-name) + (org-babel--variable-assignments:bash + (car pair) (cdr pair) sep hline) + (org-babel--variable-assignments:sh-generic + (car pair) (cdr pair) sep hline))) + (org-babel--get-vars params)))) + +(defun org-babel-sh-var-to-sh (var &optional sep hline) + "Convert an elisp value to a shell variable. +Convert an elisp var into a string of shell commands specifying a +var of the same value." + (concat "'" (replace-regexp-in-string + "'" "'\"'\"'" + (org-babel-sh-var-to-string var sep hline)) + "'")) + +(defun org-babel-sh-var-to-string (var &optional sep hline) + "Convert an elisp value to a string." + (let ((echo-var (lambda (v) (if (stringp v) v (format "%S" v))))) + (cond + ((and (listp var) (or (listp (car var)) (eq (car var) 'hline))) + (orgtbl-to-generic var (list :sep (or sep "\t") :fmt echo-var + :hline hline))) + ((listp var) + (mapconcat echo-var var "\n")) + (t (funcall echo-var var))))) + +(defun org-babel-sh-initiate-session (&optional session _params) + "Initiate a session named SESSION according to PARAMS." + (when (and session (not (string= session "none"))) + (save-window-excursion + (or (org-babel-comint-buffer-livep session) + (progn + (shell session) + ;; Needed for Emacs 23 since the marker is initially + ;; undefined and the filter functions try to use it without + ;; checking. + (set-marker comint-last-output-start (point)) + (get-buffer (current-buffer))))))) + +(defvar org-babel-sh-eoe-indicator "echo 'org_babel_sh_eoe'" + "String to indicate that evaluation has completed.") +(defvar org-babel-sh-eoe-output "org_babel_sh_eoe" + "String to indicate that evaluation has completed.") + +(defun org-babel-sh-evaluate (session body &optional params stdin cmdline) + "Pass BODY to the Shell process in BUFFER. +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 ((results + (cond + ((or stdin cmdline) ; external shell script w/STDIN + (let ((script-file (org-babel-temp-file "sh-script-")) + (stdin-file (org-babel-temp-file "sh-stdin-")) + (shebang (cdr (assq :shebang params))) + (padline (not (string= "no" (cdr (assq :padline params)))))) + (with-temp-file script-file + (when shebang (insert (concat shebang "\n"))) + (when padline (insert "\n")) + (insert body)) + (set-file-modes script-file #o755) + (with-temp-file stdin-file (insert (or stdin ""))) + (with-temp-buffer + (call-process-shell-command + (concat (if shebang script-file + (format "%s %s" shell-file-name script-file)) + (and cmdline (concat " " cmdline))) + stdin-file + (current-buffer)) + (buffer-string)))) + (session ; session evaluation + (mapconcat + #'org-babel-sh-strip-weird-long-prompt + (mapcar + #'org-trim + (butlast + (org-babel-comint-with-output + (session org-babel-sh-eoe-output t body) + (mapc + (lambda (line) + (insert line) + (comint-send-input nil t) + (while (save-excursion + (goto-char comint-last-input-end) + (not (re-search-forward + comint-prompt-regexp nil t))) + (accept-process-output + (get-buffer-process (current-buffer))))) + (append + (split-string (org-trim body) "\n") + (list org-babel-sh-eoe-indicator)))) + 2)) "\n")) + ('otherwise ; external shell script + (if (and (cdr (assq :shebang params)) + (> (length (cdr (assq :shebang params))) 0)) + (let ((script-file (org-babel-temp-file "sh-script-")) + (shebang (cdr (assq :shebang params))) + (padline (not (equal "no" (cdr (assq :padline params)))))) + (with-temp-file script-file + (when shebang (insert (concat shebang "\n"))) + (when padline (insert "\n")) + (insert body)) + (set-file-modes script-file #o755) + (org-babel-eval script-file "")) + (org-babel-eval shell-file-name (org-trim body))))))) + (when results + (let ((result-params (cdr (assq :result-params params)))) + (org-babel-result-cond result-params + results + (let ((tmp-file (org-babel-temp-file "sh-"))) + (with-temp-file tmp-file (insert results)) + (org-babel-import-elisp-from-file tmp-file))))))) + +(defun org-babel-sh-strip-weird-long-prompt (string) + "Remove prompt cruft from a string of shell output." + (while (string-match "^% +[\r\n$]+ *" string) + (setq string (substring string (match-end 0)))) + string) + +(provide 'ob-shell) + + + +;;; ob-shell.el ends here diff --git a/lisp/org/ob-shen.el b/lisp/org/ob-shen.el index d44a48a6382..6a4a3f18de1 100644 --- a/lisp/org/ob-shen.el +++ b/lisp/org/ob-shen.el @@ -1,4 +1,4 @@ -;;; ob-shen.el --- org-babel functions for Shen +;;; ob-shen.el --- Babel Functions for Shen -*- lexical-binding: t; -*- ;; Copyright (C) 2010-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -43,7 +43,7 @@ (defun org-babel-expand-body:shen (body params) "Expand BODY according to PARAMS, return the expanded body." - (let ((vars (mapcar #'cdr (org-babel-get-header params :var)))) + (let ((vars (org-babel--get-vars params))) (if (> (length vars) 0) (concat "(let " (mapconcat (lambda (var) @@ -63,14 +63,13 @@ "Execute a block of Shen code with org-babel. This function is called by `org-babel-execute-src-block'" (require 'inf-shen) - (let* ((result-type (cdr (assoc :result-type params))) - (result-params (cdr (assoc :result-params params))) + (let* ((result-params (cdr (assq :result-params params))) (full-body (org-babel-expand-body:shen body params))) (let ((results (with-temp-buffer (insert full-body) (call-interactively #'shen-eval-defun)))) - (org-babel-result-cond result-params + (org-babel-result-cond result-params results (condition-case nil (org-babel-script-escape results) (error results)))))) diff --git a/lisp/org/ob-sql.el b/lisp/org/ob-sql.el index 17775829cba..1b1d2dc09d3 100644 --- a/lisp/org/ob-sql.el +++ b/lisp/org/ob-sql.el @@ -1,4 +1,4 @@ -;;; ob-sql.el --- org-babel functions for sql evaluation +;;; ob-sql.el --- Babel Functions for SQL -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -36,31 +36,42 @@ ;; - engine ;; - cmdline ;; - dbhost +;; - dbport ;; - dbuser ;; - dbpassword ;; - database ;; - colnames (default, nil, means "yes") ;; - result-params ;; - out-file +;; ;; The following are used but not really implemented for SQL: ;; - colname-names ;; - rownames ;; - rowname-names ;; +;; Engines supported: +;; - mysql +;; - dbi +;; - mssql +;; - sqsh +;; - postgresql +;; - oracle +;; - vertica +;; ;; TODO: ;; ;; - support for sessions -;; - support for more engines (currently only supports mysql) +;; - support for more engines ;; - what's a reasonable way to drop table data into SQL? ;; ;;; Code: (require 'ob) -(eval-when-compile (require 'cl)) (declare-function org-table-import "org-table" (file arg)) (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)) (defvar org-babel-default-header-args:sql '()) @@ -68,6 +79,7 @@ '((engine . :any) (out-file . :any) (dbhost . :any) + (dbport . :any) (dbuser . :any) (dbpassword . :any) (database . :any)) @@ -76,109 +88,217 @@ (defun org-babel-expand-body:sql (body params) "Expand BODY according to the values of PARAMS." (org-babel-sql-expand-vars - body (mapcar #'cdr (org-babel-get-header params :var)))) + body (org-babel--get-vars params))) -(defun dbstring-mysql (host user password database) +(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 - (remq nil + (delq nil (list (when host (concat "-h" host)) + (when port (format "-P%d" port)) (when user (concat "-u" user)) (when password (concat "-p" password)) (when database (concat "-D" database)))))) +(defun org-babel-sql-dbstring-postgresql (host port user database) + "Make PostgreSQL command line args for database connection. +Pass nil to omit that arg." + (combine-and-quote-strings + (delq nil + (list (when host (concat "-h" host)) + (when port (format "-p%d" port)) + (when user (concat "-U" user)) + (when database (concat "-d" database)))))) + +(defun org-babel-sql-dbstring-oracle (host port user password database) + "Make Oracle command line args for database connection." + (format "%s/%s@%s:%s/%s" user password host port database)) + +(defun org-babel-sql-dbstring-mssql (host user password database) + "Make sqlcmd command line args for database connection. +`sqlcmd' is the preferred command line tool to access Microsoft +SQL Server on Windows and Linux platform." + (mapconcat #'identity + (delq nil + (list (when host (format "-S \"%s\"" host)) + (when user (format "-U \"%s\"" user)) + (when password (format "-P \"%s\"" password)) + (when database (format "-d \"%s\"" database)))) + " ")) + +(defun org-babel-sql-dbstring-sqsh (host user password database) + "Make sqsh command line args for database connection. +\"sqsh\" is one method to access Sybase or MS SQL via Linux platform" + (mapconcat #'identity + (delq nil + (list (when host (format "-S \"%s\"" host)) + (when user (format "-U \"%s\"" user)) + (when password (format "-P \"%s\"" password)) + (when database (format "-D \"%s\"" database)))) + " ")) + +(defun org-babel-sql-dbstring-vertica (host port user password database) + "Make Vertica command line args for database connection. Pass nil to omit that arg." + (mapconcat #'identity + (delq nil + (list (when host (format "-h %s" host)) + (when port (format "-p %d" port)) + (when user (format "-U %s" user)) + (when password (format "-w %s" (shell-quote-argument password) )) + (when database (format "-d %s" database)))) + " ")) + +(defun org-babel-sql-convert-standard-filename (file) + "Convert FILE to OS standard file name. +If in Cygwin environment, uses Cygwin specific function to +convert the file name. In a Windows-NT environment, do nothing. +Otherwise, use Emacs' standard conversion function." + (cond ((fboundp 'cygwin-convert-file-name-to-windows) + (format "%S" (cygwin-convert-file-name-to-windows file))) + ((string= "windows-nt" system-type) file) + (t (format "%S" (convert-standard-filename file))))) + (defun org-babel-execute:sql (body params) "Execute a block of Sql code with Babel. This function is called by `org-babel-execute-src-block'." - (let* ((result-params (cdr (assoc :result-params params))) - (cmdline (cdr (assoc :cmdline params))) - (dbhost (cdr (assoc :dbhost params))) - (dbuser (cdr (assoc :dbuser params))) - (dbpassword (cdr (assoc :dbpassword params))) - (database (cdr (assoc :database params))) - (engine (cdr (assoc :engine params))) - (colnames-p (not (equal "no" (cdr (assoc :colnames params))))) + (let* ((result-params (cdr (assq :result-params params))) + (cmdline (cdr (assq :cmdline params))) + (dbhost (cdr (assq :dbhost params))) + (dbport (cdr (assq :dbport params))) + (dbuser (cdr (assq :dbuser params))) + (dbpassword (cdr (assq :dbpassword params))) + (database (cdr (assq :database params))) + (engine (cdr (assq :engine params))) + (colnames-p (not (equal "no" (cdr (assq :colnames params))))) (in-file (org-babel-temp-file "sql-in-")) - (out-file (or (cdr (assoc :out-file params)) + (out-file (or (cdr (assq :out-file params)) (org-babel-temp-file "sql-out-"))) (header-delim "") - (command (case (intern engine) - ('dbi (format "dbish --batch %s < %s | sed '%s' > %s" + (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))) - ('msosql (format "osql %s -s \"\t\" -i %s -o %s" - (or cmdline "") - (org-babel-process-file-name in-file) - (org-babel-process-file-name out-file))) - ('mysql (format "mysql %s %s %s < %s > %s" - (dbstring-mysql dbhost dbuser dbpassword database) + (`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 "") + (or cmdline "") (org-babel-process-file-name in-file) (org-babel-process-file-name out-file))) - ('postgresql (format - "psql -A -P footer=off -F \"\t\" -f %s -o %s %s" + (`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 ""))) - (t (error "No support for the %s SQL engine" engine))))) + (`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))) + (_ (error "No support for the %s SQL engine" engine))))) (with-temp-file in-file (insert - (case (intern engine) - ('dbi "/format partbox\n") - (t "")) - (org-babel-expand-body:sql body params))) - (message command) + (pcase (intern engine) + (`dbi "/format partbox\n") + (`oracle "SET PAGESIZE 50000 +SET NEWPAGE 0 +SET TAB OFF +SET SPACE 0 +SET LINESIZE 9999 +SET ECHO OFF +SET FEEDBACK OFF +SET VERIFY OFF +SET HEADING ON +SET MARKUP HTML OFF SPOOL OFF +SET COLSEP '|' + +") + ((or `mssql `sqsh) "SET NOCOUNT ON + +") + (`vertica "\\a\n") + (_ "")) + (org-babel-expand-body:sql body params) + ;; "sqsh" requires "go" inserted at EOF. + (if (string= engine "sqsh") "\ngo" ""))) (org-babel-eval command "") (org-babel-result-cond result-params (with-temp-buffer - (progn (insert-file-contents-literally out-file) (buffer-string))) + (progn (insert-file-contents-literally out-file) (buffer-string))) (with-temp-buffer (cond - ((or (eq (intern engine) 'mysql) - (eq (intern engine) 'dbi) - (eq (intern engine) 'postgresql)) - ;; Add header row delimiter after column-names header in first line - (cond - (colnames-p - (with-temp-buffer - (insert-file-contents out-file) - (goto-char (point-min)) - (forward-line 1) - (insert "-\n") - (setq header-delim "-") - (write-file out-file))))) - (t - ;; Need to figure out the delimiter for the header row - (with-temp-buffer - (insert-file-contents out-file) - (goto-char (point-min)) - (when (re-search-forward "^\\(-+\\)[^-]" nil t) - (setq header-delim (match-string-no-properties 1))) - (goto-char (point-max)) - (forward-char -1) - (while (looking-at "\n") - (delete-char 1) - (goto-char (point-max)) - (forward-char -1)) - (write-file out-file)))) - (org-table-import out-file '(16)) + ((memq (intern engine) '(dbi mysql postgresql sqsh vertica)) + ;; Add header row delimiter after column-names header in first line + (cond + (colnames-p + (with-temp-buffer + (insert-file-contents out-file) + (goto-char (point-min)) + (forward-line 1) + (insert "-\n") + (setq header-delim "-") + (write-file out-file))))) + (t + ;; Need to figure out the delimiter for the header row + (with-temp-buffer + (insert-file-contents out-file) + (goto-char (point-min)) + (when (re-search-forward "^\\(-+\\)[^-]" nil t) + (setq header-delim (match-string-no-properties 1))) + (goto-char (point-max)) + (forward-char -1) + (while (looking-at "\n") + (delete-char 1) + (goto-char (point-max)) + (forward-char -1)) + (write-file out-file)))) + (org-table-import out-file (if (string= engine "sqsh") '(4) '(16))) (org-babel-reassemble-table (mapcar (lambda (x) (if (string= (car x) header-delim) 'hline x)) (org-table-to-lisp)) - (org-babel-pick-name (cdr (assoc :colname-names params)) - (cdr (assoc :colnames params))) - (org-babel-pick-name (cdr (assoc :rowname-names params)) - (cdr (assoc :rownames params)))))))) + (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-sql-expand-vars (body vars) "Expand the variables held in VARS in BODY." @@ -201,7 +321,7 @@ This function is called by `org-babel-execute-src-block'." vars) body) -(defun org-babel-prep-session:sql (session params) +(defun org-babel-prep-session:sql (_session _params) "Raise an error because Sql sessions aren't implemented." (error "SQL sessions not yet implemented")) diff --git a/lisp/org/ob-sqlite.el b/lisp/org/ob-sqlite.el index 4b165dc4762..38058274a9a 100644 --- a/lisp/org/ob-sqlite.el +++ b/lisp/org/ob-sqlite.el @@ -1,4 +1,4 @@ -;;; ob-sqlite.el --- org-babel functions for sqlite database interaction +;;; ob-sqlite.el --- Babel Functions for SQLite Databases -*- lexical-binding: t; -*- ;; Copyright (C) 2010-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -53,23 +53,22 @@ (defun org-babel-expand-body:sqlite (body params) "Expand BODY according to the values of PARAMS." (org-babel-sqlite-expand-vars - body (mapcar #'cdr (org-babel-get-header params :var)))) + body (org-babel--get-vars params))) (defvar org-babel-sqlite3-command "sqlite3") (defun org-babel-execute:sqlite (body params) "Execute a block of Sqlite code with Babel. This function is called by `org-babel-execute-src-block'." - (let ((result-params (split-string (or (cdr (assoc :results params)) ""))) - (db (cdr (assoc :db params))) - (separator (cdr (assoc :separator params))) - (nullvalue (cdr (assoc :nullvalue params))) - (headers-p (equal "yes" (cdr (assoc :colnames params)))) + (let ((result-params (split-string (or (cdr (assq :results params)) ""))) + (db (cdr (assq :db params))) + (separator (cdr (assq :separator params))) + (nullvalue (cdr (assq :nullvalue params))) + (headers-p (equal "yes" (cdr (assq :colnames params)))) (others (delq nil (mapcar - (lambda (arg) (car (assoc arg params))) + (lambda (arg) (car (assq arg params))) (list :header :echo :bail :column - :csv :html :line :list)))) - exit-code) + :csv :html :line :list))))) (unless db (error "ob-sqlite: can't evaluate without a database")) (with-temp-buffer (insert @@ -124,10 +123,7 @@ This function is called by `org-babel-execute-src-block'." (if (listp val) (let ((data-file (org-babel-temp-file "sqlite-data-"))) (with-temp-file data-file - (insert (orgtbl-to-csv - val '(:fmt (lambda (el) (if (stringp el) - el - (format "%S" el))))))) + (insert (orgtbl-to-csv val nil))) data-file) (if (stringp val) val (format "%S" val)))) body))) @@ -140,7 +136,7 @@ This function is called by `org-babel-execute-src-block'." (equal 1 (length (car result)))) (org-babel-read (caar result)) (mapcar (lambda (row) - (if (equal 'hline row) + (if (eq 'hline row) 'hline (mapcar #'org-babel-string-read row))) result))) @@ -150,7 +146,7 @@ This function is called by `org-babel-execute-src-block'." (cons (car table) (cons 'hline (cdr table))) table)) -(defun org-babel-prep-session:sqlite (session params) +(defun org-babel-prep-session:sqlite (_session _params) "Raise an error because support for SQLite sessions isn't implemented. Prepare SESSION according to the header arguments specified in PARAMS." (error "SQLite sessions not yet implemented")) diff --git a/lisp/org/ob-stan.el b/lisp/org/ob-stan.el new file mode 100644 index 00000000000..40fd8d9ccea --- /dev/null +++ b/lisp/org/ob-stan.el @@ -0,0 +1,84 @@ +;;; ob-stan.el --- Babel Functions for Stan -*- lexical-binding: t; -*- + +;; Copyright (C) 2015-2017 Free Software Foundation, Inc. + +;; Author: Kyle Meyer +;; Keywords: literate programming, reproducible research +;; Homepage: http://orgmode.org + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Org-Babel support for evaluating Stan [1] source code. +;; +;; Evaluating a Stan block can produce two different results. +;; +;; 1) Dump the source code contents to a file. +;; +;; This file can then be used as a variable in other blocks, which +;; allows interfaces like RStan to use the model. +;; +;; 2) Compile the contents to a model file. +;; +;; This provides access to the CmdStan interface. To use this, set +;; `org-babel-stan-cmdstan-directory' and provide a :file argument +;; that does not end in ".stan". +;; +;; For more information and usage examples, visit +;; http://orgmode.org/worg/org-contrib/babel/languages/ob-doc-stan.html +;; +;; [1] http://mc-stan.org/ + +;;; Code: +(require 'ob) +(require 'org-compat) + +(defcustom org-babel-stan-cmdstan-directory nil + "CmdStan source directory. +'make' will be called from this directory to compile the Stan +block. When nil, executing Stan blocks dumps the content to a +plain text file." + :group 'org-babel + :type 'string) + +(defvar org-babel-default-header-args:stan + '((:results . "file"))) + +(defun org-babel-execute:stan (body params) + "Generate Stan file from BODY according to PARAMS. +A :file header argument must be given. If +`org-babel-stan-cmdstan-directory' is non-nil and the file name +does not have a \".stan\" extension, save an intermediate +\".stan\" file and compile the block to the named file. +Otherwise, write the Stan code directly to the named file." + (let ((file (expand-file-name + (or (cdr (assq :file params)) + (user-error "Set :file argument to execute Stan blocks"))))) + (if (or (not org-babel-stan-cmdstan-directory) + (string-match-p "\\.stan\\'" file)) + (with-temp-file file (insert body)) + (with-temp-file (concat file ".stan") (insert body)) + (let ((default-directory org-babel-stan-cmdstan-directory)) + (call-process-shell-command (concat "make " file)))) + nil)) ; Signal that output has been written to file. + +(defun org-babel-prep-session:stan (_session _params) + "Return an error because Stan does not support sessions." + (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 1fa9105ee2b..3169f3d3bef 100644 --- a/lisp/org/ob-table.el +++ b/lisp/org/ob-table.el @@ -1,4 +1,4 @@ -;;; ob-table.el --- support for calling org-babel functions from tables +;;; ob-table.el --- Support for Calling Babel Functions from Tables -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. @@ -19,12 +19,12 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: -;; Should allow calling functions from org-mode tables using the -;; function `org-sbe' as so... +;; Should allow calling functions from Org tables using the function +;; `org-sbe' as so... ;; #+begin_src emacs-lisp :results silent ;; (defun fibbd (n) (if (< n 2) 1 (+ (fibbd (- n 1)) (fibbd (- n 2))))) @@ -47,38 +47,50 @@ ;; | 7 | | ;; | 8 | | ;; | 9 | | -;; #+TBLFM: $2='(org-sbe 'fibbd (n $1)) +;; #+TBLFM: $2='(org-sbe "fibbd" (n $1)) + +;; NOTE: The quotation marks around the function name, 'fibbd' here, +;; are optional. ;;; Code: (require 'ob-core) +(declare-function org-trim "org" (s &optional keep-lead)) + (defun org-babel-table-truncate-at-newline (string) "Replace newline character with ellipses. 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)) - (if (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. -Each element of VARIABLES should be a two -element list, whose first element is the name of the variable and -second element is a string of its value. The following call to -`org-sbe' would be equivalent to the following source code block. - (org-sbe \\='source-block (n $2) (m 3)) +Each element of VARIABLES should be a list of two elements: the +first element is the name of the variable and second element is a +string of its value. + +So this `org-sbe' construct + + (org-sbe \"source-block\" (n $2) (m 3)) + +is the equivalent of the following source code block: + + #+begin_src emacs-lisp :var results=source-block(n=val_at_col_2, m=3) :results silent + results + #+end_src -#+begin_src emacs-lisp :var results=source-block(n=val_at_col_2, m=3) :results silent -results -#+end_src +NOTE: The quotation marks around the function name, +'source-block', are optional. -NOTE: by default string variable names are interpreted as +NOTE: By default, string variable names are interpreted as references to source-code blocks, to force interpretation of a cell's value as a string, prefix the identifier a \"$\" (e.g., \"$$2\" instead of \"$2\" or \"$@2$2\" instead of \"@2$2\"). -NOTE: it is also possible to pass header arguments to the code +NOTE: It is also possible to pass header arguments to the code block. In this case a table cell should hold the string value of the header argument which can then be passed before all variables as shown in the example below. @@ -132,7 +144,7 @@ as shown in the example below. nil (list "emacs-lisp" "results" params) '((:results . "silent")))) ""))) - (org-babel-trim (if (stringp result) result (format "%S" result))))))) + (org-trim (if (stringp result) result (format "%S" result))))))) (provide 'ob-table) diff --git a/lisp/org/ob-tangle.el b/lisp/org/ob-tangle.el index 437e0a296c1..09d011fc35e 100644 --- a/lisp/org/ob-tangle.el +++ b/lisp/org/ob-tangle.el @@ -1,4 +1,4 @@ -;;; ob-tangle.el --- extract source code from org-mode files +;;; ob-tangle.el --- Extract Source Code From Org Files -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. @@ -19,29 +19,41 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; Extract the code from source blocks out into raw source-code files. ;;; Code: + +(require 'cl-lib) (require 'org-src) -(eval-when-compile - (require 'cl)) +(require 'org-macs) -(declare-function org-edit-special "org" (&optional arg)) -(declare-function org-link-escape "org" (text &optional table merge)) -(declare-function org-store-link "org" (arg)) -(declare-function org-open-link-from-string "org" (s &optional arg reference-buffer)) -(declare-function org-heading-components "org" ()) +(declare-function make-directory "files" (dir &optional parents)) +(declare-function org-at-heading-p "org" (&optional ignored)) +(declare-function org-babel-update-block-body "ob-core" (new-body)) (declare-function org-back-to-heading "org" (&optional invisible-ok)) +(declare-function org-before-first-heading-p "org" ()) +(declare-function org-element-at-point "org-element" ()) +(declare-function org-element-type "org-element" (element)) (declare-function org-fill-template "org" (template alist)) -(declare-function org-babel-update-block-body "ob-core" (new-body)) -(declare-function make-directory "files" (dir &optional parents)) +(declare-function org-heading-components "org" ()) +(declare-function org-in-commented-heading-p "org" (&optional no-inheritance)) +(declare-function org-link-escape "org" (text &optional table merge)) +(declare-function org-open-link-from-string "org" (s &optional arg reference-buffer)) +(declare-function org-remove-indentation "org" (code &optional n)) +(declare-function org-store-link "org" (arg)) +(declare-function org-trim "org" (s &optional keep-lead)) +(declare-function outline-previous-heading "outline" ()) +(declare-function org-id-find "org-id" (id &optional markerp)) + +(defvar org-link-types-re) (defcustom org-babel-tangle-lang-exts - '(("emacs-lisp" . "el")) + '(("emacs-lisp" . "el") + ("elisp" . "el")) "Alist mapping languages to their file extensions. The key is the language name, the value is the string that should be inserted as the extension commonly used to identify files @@ -54,6 +66,11 @@ then the name of the language is used." (string "Language name") (string "File Extension")))) +(defcustom org-babel-tangle-use-relative-file-links t + "Use relative path names in links from tangled source back the Org file." + :group 'org-babel-tangle + :type 'boolean) + (defcustom org-babel-post-tangle-hook nil "Hook run in code files tangled by `org-babel-tangle'." :group 'org-babel @@ -78,9 +95,14 @@ The following format strings can be used to insert special information into the output using `org-fill-template'. %start-line --- the line number at the start of the code block %file --------- the file from which the code block was tangled -%link --------- Org-mode style link to the code block +%link --------- Org style link to the code block %source-name -- name of the code block +Upon insertion the formatted comment will be commented out, and +followed by a newline. To inhibit this post-insertion processing +set the `org-babel-tangle-uncomment-comments' variable to a +non-nil value. + Whether or not comments are inserted during tangling is controlled by the :comments header argument." :group 'org-babel @@ -93,20 +115,33 @@ The following format strings can be used to insert special information into the output using `org-fill-template'. %start-line --- the line number at the start of the code block %file --------- the file from which the code block was tangled -%link --------- Org-mode style link to the code block +%link --------- Org style link to the code block %source-name -- name of the code block +Upon insertion the formatted comment will be commented out, and +followed by a newline. To inhibit this post-insertion processing +set the `org-babel-tangle-uncomment-comments' variable to a +non-nil value. + Whether or not comments are inserted during tangling is controlled by the :comments header argument." :group 'org-babel :version "24.1" :type 'string) -(defcustom org-babel-process-comment-text #'org-babel-trim - "Function called to process raw Org-mode text collected to be +(defcustom org-babel-tangle-uncomment-comments nil + "Inhibits automatic commenting and addition of trailing newline +of tangle comments. Use `org-babel-tangle-comment-format-beg' +and `org-babel-tangle-comment-format-end' to customize the format +of tangled comments." + :group 'org-babel + :type 'boolean) + +(defcustom org-babel-process-comment-text 'org-remove-indentation + "Function called to process raw Org text collected to be inserted as comments in tangled source-code files. The function should take a single string argument and return a string -result. The default value is `org-babel-trim'." +result. The default value is `org-remove-indentation'." :group 'org-babel :version "24.1" :type 'function) @@ -153,12 +188,15 @@ Return a list whose CAR is the tangled file name." (save-window-excursion (find-file file) (setq to-be-removed (current-buffer)) - (org-babel-tangle nil target-file lang)) + (mapcar #'expand-file-name (org-babel-tangle nil target-file lang))) (unless visited-p (kill-buffer to-be-removed))))) (defun org-babel-tangle-publish (_ filename pub-dir) "Tangle FILENAME and place the results in PUB-DIR." + (unless (file-exists-p pub-dir) + (make-directory pub-dir t)) + (setq pub-dir (file-name-as-directory pub-dir)) (mapc (lambda (el) (copy-file el pub-dir t)) (org-babel-tangle-file filename))) ;;;###autoload @@ -176,12 +214,12 @@ used to limit the exported source code blocks by language." (run-hooks 'org-babel-pre-tangle-hook) ;; Possibly Restrict the buffer to the current code block (save-restriction - (when (equal arg '(4)) - (let ((head (org-babel-where-is-src-block-head))) + (save-excursion + (when (equal arg '(4)) + (let ((head (org-babel-where-is-src-block-head))) (if head (goto-char head) (user-error "Point is not in a source code block")))) - (save-excursion (let ((block-counter 0) (org-babel-default-header-args (if target-file @@ -190,7 +228,7 @@ used to limit the exported source code blocks by language." org-babel-default-header-args)) (tangle-file (when (equal arg '(16)) - (or (cdr (assoc :tangle (nth 2 (org-babel-get-src-block-info 'light)))) + (or (cdr (assq :tangle (nth 2 (org-babel-get-src-block-info 'light)))) (user-error "Point is not in a source code block")))) path-collector) (mapc ;; map over all languages @@ -216,7 +254,7 @@ used to limit the exported source code blocks by language." (base-name (cond ((string= "yes" tangle) (file-name-sans-extension - (buffer-file-name))) + (nth 1 spec))) ((string= "no" tangle) nil) ((> (length tangle) 0) tangle))) (file-name (when base-name @@ -243,9 +281,13 @@ used to limit the exported source code blocks by language." ;; We avoid append-to-file as it does not work with tramp. (let ((content (buffer-string))) (with-temp-buffer - (if (file-exists-p file-name) - (insert-file-contents file-name)) + (when (file-exists-p file-name) + (insert-file-contents file-name)) (goto-char (point-max)) + ;; Handle :padlines unless first line in file + (unless (or (string= "no" (cdr (assq :padline (nth 4 spec)))) + (= (point) (point-min))) + (insert "\n")) (insert content) (write-region nil nil file-name)))) ;; if files contain she-bangs, then make the executable @@ -253,10 +295,8 @@ used to limit the exported source code blocks by language." (unless tangle-mode (setq tangle-mode #o755))) ;; update counter (setq block-counter (+ 1 block-counter)) - (add-to-list 'path-collector - (cons file-name tangle-mode) - nil - (lambda (a b) (equal (car a) (car b)))))))) + (unless (assoc file-name path-collector) + (push (cons file-name tangle-mode) path-collector)))))) specs))) (if (equal arg '(4)) (org-babel-tangle-single-block 1 t) @@ -284,7 +324,7 @@ used to limit the exported source code blocks by language." Call this function inside of a source-code file generated by `org-babel-tangle' to remove all comments inserted automatically by `org-babel-tangle'. Warning, this comment removes any lines -containing constructs which resemble org-mode file links or noweb +containing constructs which resemble Org file links or noweb references." (interactive) (goto-char (point-min)) @@ -303,153 +343,134 @@ code file. This function uses `comment-region' which assumes that the appropriate major-mode is set. SPEC has the form: (start-line file link source-name params body comment)" - (let* ((start-line (nth 0 spec)) - (file (nth 1 spec)) - (link (nth 2 spec)) - (source-name (nth 3 spec)) - (body (nth 5 spec)) - (comment (nth 6 spec)) - (comments (cdr (assoc :comments (nth 4 spec)))) - (padline (not (string= "no" (cdr (assoc :padline (nth 4 spec)))))) - (link-p (or (string= comments "both") (string= comments "link") - (string= comments "yes") (string= comments "noweb"))) - (link-data (mapcar (lambda (el) - (cons (symbol-name el) - (let ((le (eval el))) - (if (stringp le) le (format "%S" le))))) - '(start-line file link source-name))) - (insert-comment (lambda (text) - (when (and comments (not (string= comments "no")) - (> (length text) 0)) - (when padline (insert "\n")) - (comment-region (point) (progn (insert text) (point))) - (end-of-line nil) (insert "\n"))))) + (pcase-let* + ((`(,start ,file ,link ,source ,info ,body ,comment) spec) + (comments (cdr (assq :comments info))) + (link? (or (string= comments "both") (string= comments "link") + (string= comments "yes") (string= comments "noweb"))) + (link-data `(("start-line" . ,(number-to-string start)) + ("file" . ,file) + ("link" . ,link) + ("source-name" . ,source))) + (insert-comment (lambda (text) + (when (and comments + (not (string= comments "no")) + (org-string-nw-p text)) + (if org-babel-tangle-uncomment-comments + ;; Plain comments: no processing. + (insert text) + ;; Ensure comments are made to be + ;; comments, and add a trailing newline. + ;; Also ignore invisible characters when + ;; commenting. + (comment-region + (point) + (progn (insert (org-no-properties text)) + (point))) + (end-of-line) + (insert "\n")))))) (when comment (funcall insert-comment comment)) - (when link-p - (funcall - insert-comment - (org-fill-template org-babel-tangle-comment-format-beg link-data))) - (when padline (insert "\n")) - (insert - (format - "%s\n" - (org-unescape-code-in-string - (org-babel-trim body (if org-src-preserve-indentation "[\f\n\r\v]"))))) - (when link-p - (funcall - insert-comment - (org-fill-template org-babel-tangle-comment-format-end link-data))))) - -(defvar org-comment-string) ;; Defined in org.el + (when link? + (funcall insert-comment + (org-fill-template + org-babel-tangle-comment-format-beg link-data))) + (insert body "\n") + (when link? + (funcall insert-comment + (org-fill-template + org-babel-tangle-comment-format-end link-data))))) + (defun org-babel-tangle-collect-blocks (&optional language tangle-file) - "Collect source blocks in the current Org-mode 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." - (let ((block-counter 1) (current-heading "") blocks by-lang) + (let ((counter 0) last-heading-pos blocks) (org-babel-map-src-blocks (buffer-file-name) - (lambda (new-heading) - (if (not (string= new-heading current-heading)) - (progn - (setq block-counter 1) - (setq current-heading new-heading)) - (setq block-counter (+ 1 block-counter)))) - (replace-regexp-in-string "[ \t]" "-" - (condition-case nil - (or (nth 4 (org-heading-components)) - "(dummy for heading without text)") - (error (buffer-file-name)))) - (let* ((info (org-babel-get-src-block-info 'light)) - (src-lang (nth 0 info)) - (src-tfile (cdr (assoc :tangle (nth 2 info))))) - (unless (or (string-match (concat "^" org-comment-string) current-heading) - (string= (cdr (assoc :tangle (nth 2 info))) "no") - (and tangle-file (not (equal tangle-file src-tfile)))) - (unless (and language (not (string= language src-lang))) - ;; Add the spec for this block to blocks under it's language - (setq by-lang (cdr (assoc src-lang blocks))) - (setq blocks (delq (assoc src-lang blocks) blocks)) - (setq blocks (cons - (cons src-lang - (cons - (org-babel-tangle-single-block - block-counter) - by-lang)) blocks)))))) - ;; Ensure blocks are in the correct order - (setq blocks - (mapcar - (lambda (by-lang) (cons (car by-lang) (reverse (cdr by-lang)))) - blocks)) - blocks)) - -(defun org-babel-tangle-single-block - (block-counter &optional only-this-block) + (let ((current-heading-pos + (org-with-wide-buffer + (org-with-limited-levels (outline-previous-heading))))) + (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) + (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)))) + ;; Add the spec for this block to blocks under its + ;; language. + (let ((by-lang (assoc src-lang blocks)) + (block (org-babel-tangle-single-block counter))) + (if by-lang (setcdr by-lang (cons block (cdr by-lang))) + (push (cons src-lang (list block)) blocks))))))) + ;; Ensure blocks are in the correct order. + (mapcar (lambda (b) (cons (car b) (nreverse (cdr b)))) blocks))) + +(defun org-babel-tangle-single-block (block-counter &optional only-this-block) "Collect the tangled source for current block. Return the list of block attributes needed by -`org-babel-tangle-collect-blocks'. -When ONLY-THIS-BLOCK is non-nil, return the full association -list to be used by `org-babel-tangle' directly." +`org-babel-tangle-collect-blocks'. When ONLY-THIS-BLOCK is +non-nil, return the full association list to be used by +`org-babel-tangle' directly." (let* ((info (org-babel-get-src-block-info)) (start-line (save-restriction (widen) (+ 1 (line-number-at-pos (point))))) - (file (buffer-file-name)) + (file (buffer-file-name (buffer-base-buffer))) (src-lang (nth 0 info)) (params (nth 2 info)) (extra (nth 3 info)) (cref-fmt (or (and (string-match "-l \"\\(.+\\)\"" extra) (match-string 1 extra)) org-coderef-label-format)) - (link (let ((link (org-no-properties - (org-store-link nil)))) - (and (string-match org-bracket-link-regexp link) - (match-string 1 link)))) + (link (let ((l (org-no-properties (org-store-link nil)))) + (and (string-match org-bracket-link-regexp l) + (match-string 1 l)))) (source-name - (intern (or (nth 4 info) - (format "%s:%d" - (or (ignore-errors (nth 4 (org-heading-components))) - "No heading") - block-counter)))) - (expand-cmd - (intern (concat "org-babel-expand-body:" src-lang))) + (or (nth 4 info) + (format "%s:%d" + (or (ignore-errors (nth 4 (org-heading-components))) + "No heading") + block-counter))) + (expand-cmd (intern (concat "org-babel-expand-body:" src-lang))) (assignments-cmd (intern (concat "org-babel-variable-assignments:" src-lang))) (body ;; Run the tangle-body-hook. - (let* ((body ;; Expand the body in language specific manner. - (if (org-babel-noweb-p params :tangle) - (org-babel-expand-noweb-references info) - (nth 1 info))) - (body - (if (assoc :no-expand params) - body - (if (fboundp expand-cmd) - (funcall expand-cmd body params) - (org-babel-expand-body:generic - body params - (and (fboundp assignments-cmd) - (funcall assignments-cmd params))))))) - (with-temp-buffer - (insert body) - (when (string-match "-r" extra) - (goto-char (point-min)) - (while (re-search-forward - (replace-regexp-in-string "%s" ".+" cref-fmt) nil t) - (replace-match ""))) - (run-hooks 'org-babel-tangle-body-hook) - (buffer-string)))) + (let ((body (if (org-babel-noweb-p params :tangle) + (org-babel-expand-noweb-references info) + (nth 1 info)))) + (with-temp-buffer + (insert + ;; Expand body in language specific manner. + (cond ((assq :no-expand params) body) + ((fboundp expand-cmd) (funcall expand-cmd body params)) + (t + (org-babel-expand-body:generic + body params (and (fboundp assignments-cmd) + (funcall assignments-cmd params)))))) + (when (string-match "-r" extra) + (goto-char (point-min)) + (while (re-search-forward + (replace-regexp-in-string "%s" ".+" cref-fmt) nil t) + (replace-match ""))) + (run-hooks 'org-babel-tangle-body-hook) + (buffer-string)))) (comment - (when (or (string= "both" (cdr (assoc :comments params))) - (string= "org" (cdr (assoc :comments params)))) + (when (or (string= "both" (cdr (assq :comments params))) + (string= "org" (cdr (assq :comments params)))) ;; From the previous heading or code-block end (funcall org-babel-process-comment-text (buffer-substring (max (condition-case nil (save-excursion - (org-back-to-heading t) ; Sets match data + (org-back-to-heading t) ; Sets match data (match-end 0)) (error (point-min))) (save-excursion @@ -459,31 +480,47 @@ list to be used by `org-babel-tangle' directly." (point-min)))) (point))))) (result - (list start-line file link source-name params body comment))) + (list start-line + (if org-babel-tangle-use-relative-file-links + (file-relative-name file) + file) + (if (and org-babel-tangle-use-relative-file-links + (string-match org-link-types-re link) + (string= (match-string 0 link) "file")) + (concat "file:" + (file-relative-name (match-string 1 link) + (file-name-directory + (cdr (assq :tangle params))))) + link) + source-name + params + (if org-src-preserve-indentation + (org-trim body t) + (org-trim (org-remove-indentation body))) + comment))) (if only-this-block (list (cons src-lang (list result))) result))) -(defun org-babel-tangle-comment-links ( &optional info) +(defun org-babel-tangle-comment-links (&optional info) "Return a list of begin and end link comments for the code block at point." - (let* ((start-line (org-babel-where-is-src-block-head)) - (file (buffer-file-name)) - (link (org-link-escape (progn (call-interactively 'org-store-link) - (org-no-properties - (car (pop org-stored-links)))))) - (source-name (nth 4 (or info (org-babel-get-src-block-info 'light)))) - (link-data (mapcar (lambda (el) - (cons (symbol-name el) - (let ((le (eval el))) - (if (stringp le) le (format "%S" le))))) - '(start-line file link source-name)))) + (let ((link-data + `(("start-line" . ,(number-to-string + (org-babel-where-is-src-block-head))) + ("file" . ,(buffer-file-name)) + ("link" . ,(org-link-escape + (progn + (call-interactively #'org-store-link) + (org-no-properties (car (pop org-stored-links)))))) + ("source-name" . + ,(nth 4 (or info (org-babel-get-src-block-info 'light))))))) (list (org-fill-template org-babel-tangle-comment-format-beg link-data) (org-fill-template org-babel-tangle-comment-format-end link-data)))) ;; de-tangling functions (defvar org-bracket-link-analytic-regexp) (defun org-babel-detangle (&optional source-code-file) - "Propagate changes in source file back original to Org-mode file. + "Propagate changes in source file back original to Org file. This requires that code blocks were tangled with link comments which enable the original code blocks to be found." (interactive) @@ -504,18 +541,17 @@ which enable the original code blocks to be found." (prog1 counter (message "Detangled %d code blocks" counter))))) (defun org-babel-tangle-jump-to-org () - "Jump from a tangled code file to the related Org-mode file." + "Jump from a tangled code file to the related Org mode file." (interactive) (let ((mid (point)) - start body-start end done + start body-start end target-buffer target-char link path block-name body) (save-window-excursion (save-excursion (while (and (re-search-backward org-bracket-link-analytic-regexp nil t) (not ; ever wider searches until matching block comments - (and (setq start (point-at-eol)) - (setq body-start (save-excursion - (forward-line 2) (point-at-bol))) + (and (setq start (line-beginning-position)) + (setq body-start (line-beginning-position 2)) (setq link (match-string 0)) (setq path (match-string 3)) (setq block-name (match-string 5)) @@ -524,32 +560,37 @@ which enable the original code blocks to be found." (re-search-forward (concat " " (regexp-quote block-name) " ends here") nil t) - (setq end (point-at-bol)))))))) + (setq end (line-beginning-position)))))))) (unless (and start (< start mid) (< mid end)) (error "Not in tangled code")) - (setq body (org-babel-trim (buffer-substring start end)))) + (setq body (buffer-substring body-start end))) (when (string-match "::" path) (setq path (substring path 0 (match-beginning 0)))) - (find-file path) (setq target-buffer (current-buffer)) - (goto-char start) (org-open-link-from-string link) + (find-file (or (car (org-id-find path)) path)) + (setq target-buffer (current-buffer)) + ;; Go to the beginning of the relative block in Org file. + (org-open-link-from-string link) (if (string-match "[^ \t\n\r]:\\([[:digit:]]+\\)" block-name) - (org-babel-next-src-block - (string-to-number (match-string 1 block-name))) + (let ((n (string-to-number (match-string 1 block-name)))) + (if (org-before-first-heading-p) (goto-char (point-min)) + (org-back-to-heading t)) + ;; Do not skip the first block if it begins at point min. + (cond ((or (org-at-heading-p) + (not (eq (org-element-type (org-element-at-point)) + 'src-block))) + (org-babel-next-src-block n)) + ((= n 1)) + (t (org-babel-next-src-block (1- n))))) (org-babel-goto-named-src-block block-name)) - ;; position at the beginning of the code block body (goto-char (org-babel-where-is-src-block-head)) + ;; Preserve location of point within the source code in tangled + ;; code file. (forward-line 1) - ;; Use org-edit-special to isolate the code. - (org-edit-special) - ;; Then move forward the correct number of characters in the - ;; code buffer. (forward-char (- mid body-start)) - ;; And return to the Org-mode buffer with the point in the right - ;; place. - (org-edit-src-exit) (setq target-char (point))) (org-src-switch-to-buffer target-buffer t) - (prog1 body (goto-char target-char)))) + (goto-char target-char) + body)) (provide 'ob-tangle) diff --git a/lisp/org/ob-vala.el b/lisp/org/ob-vala.el new file mode 100644 index 00000000000..580e27246d3 --- /dev/null +++ b/lisp/org/ob-vala.el @@ -0,0 +1,117 @@ +;;; ob-vala.el --- Babel functions for Vala evaluation -*- lexical-binding: t; -*- + +;; Copyright (C) 2017 Free Software Foundation, Inc. + +;; Author: Christian Garbs <mitch@cgarbs.de> +;; Keywords: literate programming, reproducible research +;; Homepage: http://orgmode.org + +;;; License: + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; ob-vala.el provides Babel support for the Vala language +;; (see http://live.gnome.org/Vala for details) + +;;; Requirements: + +;; - Vala compiler binary (valac) +;; - Vala development environment (Vala libraries etc.) +;; +;; vala-mode.el is nice to have for code formatting, but is not needed +;; for ob-vala.el + +;;; Code: + +(require 'ob) + +(declare-function org-trim "org" (s &optional keep-lead)) + +;; File extension. +(add-to-list 'org-babel-tangle-lang-exts '("vala" . "vala")) + +;; Header arguments empty by default. +(defvar org-babel-default-header-args:vala '()) + +(defcustom org-babel-vala-compiler "valac" + "Command used to compile a C source code file into an executable. +May be either a command in the path, like \"valac\" +or an absolute path name, like \"/usr/local/bin/valac\". +Parameters may be used like this: \"valac -v\"" + :group 'org-babel + :version "26.1" + :package-version '(Org . "9.1") + :type 'string) + +;; This is the main function which is called to evaluate a code +;; block. +;; +;; - run Vala compiler and create a binary in a temporary file +;; - compiler/linker flags can be set via :flags header argument +;; - if compilation succeeded, run the binary +;; - commandline parameters to the binary can be set via :cmdline +;; header argument +;; - stdout will be parsed as RESULT (control via :result-params +;; header argument) +;; +;; There is no session support because Vala is a compiled language. +;; +;; This function is heavily based on ob-C.el +(defun org-babel-execute:vala (body params) + "Execute a block of Vala code with Babel. +This function is called by `org-babel-execute-src-block'." + (message "executing Vala source code block") + (let* ((tmp-src-file (org-babel-temp-file + "vala-src-" + ".vala")) + (tmp-bin-file (org-babel-temp-file "vala-bin-" org-babel-exeext)) + (cmdline (cdr (assq :cmdline params))) + (flags (cdr (assq :flags params)))) + (with-temp-file tmp-src-file (insert body)) + (org-babel-eval + (format "%s %s -o %s %s" + org-babel-vala-compiler + (mapconcat #'identity + (if (listp flags) flags (list flags)) " ") + (org-babel-process-file-name tmp-bin-file) + (org-babel-process-file-name tmp-src-file)) "") + (when (file-executable-p tmp-bin-file) + (let ((results + (org-trim + (org-babel-eval + (concat tmp-bin-file (if cmdline (concat " " cmdline) "")) "")))) + (org-babel-reassemble-table + (org-babel-result-cond (cdr (assq :result-params params)) + (org-babel-read results) + (let ((tmp-file (org-babel-temp-file "vala-"))) + (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-prep-session:vala (_session _params) + "Prepare a session. +This function does nothing as Vala is a compiled language with no +support for sessions." + (error "Vala is a compiled language -- no support for sessions")) + +(provide 'ob-vala) + +;;; ob-vala.el ends here diff --git a/lisp/org/ob.el b/lisp/org/ob.el index b0c3d521c54..c5ce0c03667 100644 --- a/lisp/org/ob.el +++ b/lisp/org/ob.el @@ -1,4 +1,4 @@ -;;; ob.el --- working with code blocks in org-mode +;;; ob.el --- Working with Code Blocks in Org -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: (require 'org-macs) diff --git a/lisp/org/org-agenda.el b/lisp/org/org-agenda.el index 409c93abedc..ad811ce3193 100644 --- a/lisp/org/org-agenda.el +++ b/lisp/org/org-agenda.el @@ -19,12 +19,12 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Commentary: -;; This file contains the code for creating and using the Agenda for Org-mode. +;; This file contains the code for creating and using the Agenda for Org. ;; ;; The functions `org-batch-agenda', `org-batch-agenda-csv', and ;; `org-batch-store-agenda-views' are implemented as macros to provide @@ -45,10 +45,9 @@ ;;; Code: +(require 'cl-lib) (require 'org) (require 'org-macs) -(eval-when-compile - (require 'cl)) (declare-function diary-add-to-list "diary-lib" (date string specifier &optional marker globcolor literal)) @@ -69,6 +68,7 @@ (declare-function calendar-persian-date-string "cal-persia" (&optional date)) (declare-function calendar-check-holidays "holidays" (date)) +(declare-function org-columns-remove-overlays "org-colview" ()) (declare-function org-datetree-find-date-create "org-datetree" (date &optional keep-restriction)) (declare-function org-columns-quit "org-colview" ()) @@ -79,16 +79,15 @@ (declare-function org-is-habit-p "org-habit" (&optional pom)) (declare-function org-habit-parse-todo "org-habit" (&optional pom)) (declare-function org-habit-get-priority "org-habit" (habit &optional moment)) -(declare-function org-pop-to-buffer-same-window "org-compat" - (&optional buffer-or-name norecord label)) (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)) -(defvar calendar-mode-map) ; defined in calendar.el -(defvar org-clock-current-task nil) ; defined in org-clock.el -(defvar org-mobile-force-id-on-agenda-items) ; defined in org-mobile.el -(defvar org-habit-show-habits) ; defined in org-habit.el +(defvar calendar-mode-map) +(defvar org-clock-current-task) +(defvar org-current-tag-alist) +(defvar org-mobile-force-id-on-agenda-items) +(defvar org-habit-show-habits) (defvar org-habit-show-habits-only-for-today) (defvar org-habit-show-all-today) @@ -96,8 +95,8 @@ (defvar org-agenda-buffer-name "*Org Agenda*") (defvar org-agenda-overriding-header nil) (defvar org-agenda-title-append nil) -(org-no-warnings (defvar entry)) ;; unprefixed, from calendar.el -(org-no-warnings (defvar date)) ;; unprefixed, from calendar.el +(with-no-warnings (defvar entry)) ;; unprefixed, from calendar.el +(with-no-warnings (defvar date)) ;; unprefixed, from calendar.el (defvar original-date) ; dynamically scoped, calendar.el does scope this (defvar org-agenda-undo-list nil @@ -135,7 +134,7 @@ addresses the separator between the current and the previous block." (string))) (defgroup org-agenda-export nil - "Options concerning exporting agenda views in Org-mode." + "Options concerning exporting agenda views in Org mode." :tag "Org Agenda Export" :group 'org-agenda) @@ -152,7 +151,7 @@ before assigned to the variables. So make sure to quote values you do *not* want evaluated, for example (setq org-agenda-exporter-settings - '((ps-print-color-p 'black-white)))" + \\='((ps-print-color-p \\='black-white)))" :group 'org-agenda-export :type '(repeat (list @@ -237,7 +236,7 @@ you can \"misuse\" it to also add other text to the header." :type 'boolean) (defgroup org-agenda-custom-commands nil - "Options concerning agenda views in Org-mode." + "Options concerning agenda views in Org mode." :tag "Org Agenda Custom Commands" :group 'org-agenda) @@ -261,8 +260,8 @@ you can \"misuse\" it to also add other text to the header." ;; Keep custom values for `org-agenda-filter-preset' compatible with ;; the new variable `org-agenda-tag-filter-preset'. -(org-defvaralias 'org-agenda-filter-preset 'org-agenda-tag-filter-preset) -(org-defvaralias 'org-agenda-filter 'org-agenda-tag-filter) +(defvaralias 'org-agenda-filter-preset 'org-agenda-tag-filter-preset) +(defvaralias 'org-agenda-filter 'org-agenda-tag-filter) (defvar org-agenda-entry-types '(:deadline :scheduled :timestamp :sexp) "List of types searched for when creating the daily/weekly agenda. @@ -278,10 +277,7 @@ list are are :deadline List deadline due on that date. When the date is today, also list any deadlines past due, or due within - `org-deadline-warning-days'. `:deadline' must appear before - `:scheduled' if the setting of - `org-agenda-skip-scheduled-if-deadline-is-shown' is to have - any effect. + `org-deadline-warning-days'. :deadline* Same as above, but only include the deadline if it has an hour specification as [h]h:mm. @@ -328,12 +324,14 @@ the daily/weekly agenda, see `org-agenda-skip-function'.") (string)) (list :tag "Number of days in agenda" (const org-agenda-span) - (choice (const :tag "Day" day) - (const :tag "Week" week) - (const :tag "Fortnight" fortnight) - (const :tag "Month" month) - (const :tag "Year" year) - (integer :tag "Custom"))) + (list + (const :format "" quote) + (choice (const :tag "Day" day) + (const :tag "Week" week) + (const :tag "Fortnight" fortnight) + (const :tag "Month" month) + (const :tag "Year" year) + (integer :tag "Custom")))) (list :tag "Fixed starting date" (const org-agenda-start-day) (string :value "2007-11-01")) @@ -360,6 +358,12 @@ the daily/weekly agenda, see `org-agenda-skip-function'.") (const :format "" quote) (repeat (string :tag "+tag or -tag")))) + (list :tag "Effort filter preset" + (const org-agenda-effort-filter-preset) + (list + (const :format "" quote) + (repeat + (string :tag "+=10 or -=10 or +<10 or ->10")))) (list :tag "Regexp filter preset" (const org-agenda-regexp-filter-preset) (list @@ -435,8 +439,9 @@ This will be spliced into the custom type of (defcustom org-agenda-custom-commands '(("n" "Agenda and all TODOs" ((agenda "") (alltodo "")))) "Custom commands for the agenda. +\\<org-mode-map> These commands will be offered on the splash screen displayed by the -agenda dispatcher \\[org-agenda]. Each entry is a list like this: +agenda dispatcher `\\[org-agenda]'. Each entry is a list like this: (key desc type match settings files) @@ -463,8 +468,8 @@ match What to search for: settings A list of option settings, similar to that in a let form, so like this: ((opt1 val1) (opt2 val2) ...). The values will be evaluated at the moment of execution, so quote them when needed. -files A list of files file to write the produced agenda buffer to - with the command `org-store-agenda-views'. +files A list of files to write the produced agenda buffer to with + the command `org-store-agenda-views'. If a file name ends in \".html\", an HTML version of the buffer is written out. If it ends in \".ps\", a postscript version is produced. Otherwise, only the plain text is written to the file. @@ -601,23 +606,17 @@ subtree to see if any of the subtasks have project status. See also the variable `org-tags-match-list-sublevels' which applies to projects matched by this search as well. -After defining this variable, you may use \\[org-agenda-list-stuck-projects] -or `C-c a #' to produce the list." +After defining this variable, you may use `org-agenda-list-stuck-projects' +\(bound to `\\[org-agenda] #') to produce the list." :group 'org-agenda-custom-commands :type '(list (string :tag "Tags/TODO match to identify a project") - (repeat :tag "Projects are *not* stuck if they have an entry with TODO keyword any of" (string)) - (repeat :tag "Projects are *not* stuck if they have an entry with TAG being any of" (string)) - (regexp :tag "Projects are *not* stuck if this regexp matches inside the subtree"))) - -(defcustom org-agenda-filter-effort-default-operator "<" - "The default operator for effort estimate filtering. -If you select an effort estimate limit without first pressing an operator, -this one will be used." - :group 'org-agenda-custom-commands - :type '(choice (const :tag "less or equal" "<") - (const :tag "greater or equal"">") - (const :tag "equal" "="))) + (repeat :tag "Projects are *not* stuck if they have an entry with \ +TODO keyword any of" (string)) + (repeat :tag "Projects are *not* stuck if they have an entry with \ +TAG being any of" (string)) + (regexp :tag "Projects are *not* stuck if this regexp matches inside \ +the subtree"))) (defgroup org-agenda-skip nil "Options concerning skipping parts of agenda files." @@ -769,10 +768,12 @@ to make his option also apply to the tags-todo list." (defcustom org-agenda-todo-ignore-deadlines nil "Non-nil means ignore some deadline TODO items when making TODO list. + There are different motivations for using different values, please think carefully when configuring this variable. -This applies when creating the global todo list. +This applies when creating the global TODO list. + Valid values are: near Don't show near deadline entries. A deadline is near when it is @@ -780,8 +781,8 @@ near Don't show near deadline entries. A deadline is near when it is is that such items will appear in the agenda anyway. far Don't show TODO entries where a deadline has been defined, but - the deadline is not near. This is useful if you don't want to - use the todo list to figure out what to do now. + is not going to happen anytime soon. This is useful if you want to use + the TODO list to figure out what to do now. past Don't show entries with a deadline timestamp for today or in the past. @@ -842,10 +843,9 @@ restricted to unfinished TODO entries only." (defcustom org-agenda-skip-scheduled-if-done nil "Non-nil means don't show scheduled items in agenda when they are done. -This is relevant for the daily/weekly agenda, not for the TODO list. And -it applies only to the actual date of the scheduling. Warnings about -an item with a past scheduling dates are always turned off when the item -is DONE." +This is relevant for the daily/weekly agenda, not for the TODO list. It +applies only to the actual date of the scheduling. Warnings about an item +with a past scheduling dates are always turned off when the item is DONE." :group 'org-agenda-skip :group 'org-agenda-daily/weekly :type 'boolean) @@ -894,8 +894,8 @@ several times." (defcustom org-agenda-skip-deadline-if-done nil "Non-nil means don't show deadlines when the corresponding item is done. When nil, the deadline is still shown and should give you a happy feeling. -This is relevant for the daily/weekly agenda. And it applied only to the -actually date of the deadline. Warnings about approaching and past-due +This is relevant for the daily/weekly agenda. It applies only to the +actual date of the deadline. Warnings about approaching and past-due deadlines are always turned off when the item is DONE." :group 'org-agenda-skip :group 'org-agenda-daily/weekly @@ -974,18 +974,6 @@ will only be dimmed." (const :tag "Dim to a gray face" t) (const :tag "Make invisible" invisible))) -(defcustom org-timeline-show-empty-dates 3 - "Non-nil means `org-timeline' also shows dates without an entry. -When nil, only the days which actually have entries are shown. -When t, all days between the first and the last date are shown. -When an integer, show also empty dates, but if there is a gap of more than -N days, just insert a special line indicating the size of the gap." - :group 'org-agenda-skip - :type '(choice - (const :tag "None" nil) - (const :tag "All" t) - (integer :tag "at most"))) - (defgroup org-agenda-startup nil "Options concerning initial settings in the Agenda in Org Mode." :tag "Org Agenda Startup" @@ -1001,8 +989,6 @@ you want to use two-columns display (see `org-agenda-menu-two-columns')." :version "24.1" :type 'boolean) -(define-obsolete-variable-alias 'org-agenda-menu-two-column 'org-agenda-menu-two-columns "24.3") - (defcustom org-agenda-menu-two-columns nil "Non-nil means, use two columns to show custom commands in the dispatcher. If you use this, you probably want to set `org-agenda-menu-show-matcher' @@ -1011,7 +997,6 @@ to nil." :version "24.1" :type 'boolean) -(define-obsolete-variable-alias 'org-finalize-agenda-hook 'org-agenda-finalize-hook "24.3") (defcustom org-agenda-finalize-hook nil "Hook run just before displaying an agenda buffer. The buffer is still writable when the hook is called. @@ -1024,8 +1009,8 @@ headlines as the agenda display heavily relies on them." (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. Does not work on XEmacs. -Needs to be set before org.el is loaded." +A longer mouse click will still set point. Needs to be set +before org.el is loaded." :group 'org-agenda-startup :type 'boolean) @@ -1054,9 +1039,9 @@ current item's tree, in an indirect buffer." (defcustom org-agenda-entry-text-maxlines 5 "Number of text lines to be added when `E' is pressed in the agenda. -Note that this variable only used during agenda display. Add add entry text +Note that this variable only used during agenda display. To add entry text when exporting the agenda, configure the variable -`org-agenda-add-entry-ext-maxlines'." +`org-agenda-add-entry-text-maxlines'." :group 'org-agenda :type 'integer) @@ -1083,7 +1068,7 @@ have been removed when this is called, as will any matches for regular expressions listed in `org-agenda-entry-text-exclude-regexps'.") (defvar org-agenda-include-inactive-timestamps nil - "Non-nil means include inactive time stamps in agenda and timeline. + "Non-nil means include inactive time stamps in agenda. Dynamically scoped.") (defgroup org-agenda-windows nil @@ -1097,6 +1082,7 @@ Possible values for this option are: current-window Show agenda in the current window, keeping all other windows. other-window Use `switch-to-buffer-other-window' to display agenda. +only-window Show agenda, deleting all other windows. 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. @@ -1107,6 +1093,7 @@ See also the variable `org-agenda-restore-windows-after-quit'." (const current-window) (const other-frame) (const other-window) + (const only-window) (const reorganize-frame))) (defcustom org-agenda-window-frame-fractions '(0.5 . 0.75) @@ -1126,16 +1113,6 @@ option will be ignored." :group 'org-agenda-windows :type 'boolean) -(defcustom org-agenda-ndays nil - "Number of days to include in overview display. -Should be 1 or 7. -Obsolete, see `org-agenda-span'." - :group 'org-agenda-daily/weekly - :type '(choice (const nil) - (integer))) - -(make-obsolete-variable 'org-agenda-ndays 'org-agenda-span "24.1") - (defcustom org-agenda-span 'week "Number of days to include in overview display. Can be day, week, month, year, or any number of days. @@ -1165,17 +1142,17 @@ When nil, only the days which actually have entries are shown." (defcustom org-agenda-format-date 'org-agenda-format-date-aligned "Format string for displaying dates in the agenda. -Used by the daily/weekly agenda and by the timeline. This should be -a format string understood by `format-time-string', or a function returning -the formatted date as a string. The function must take a single argument, -a calendar-style date list like (month day year)." +Used by the daily/weekly agenda. This should be a format string +understood by `format-time-string', or a function returning the +formatted date as a string. The function must take a single +argument, a calendar-style date list like (month day year)." :group 'org-agenda-daily/weekly :type '(choice (string :tag "Format string") (function :tag "Function"))) (defun org-agenda-format-date-aligned (date) - "Format a DATE string for display in the daily/weekly agenda, or timeline. + "Format a DATE string for display in the daily/weekly agenda. This function makes sure that dates are aligned for easy reading." (require 'cal-iso) (let* ((dayname (calendar-day-name date)) @@ -1211,7 +1188,7 @@ For example, 9:30am would become 09:30 rather than 9:30." :type 'boolean) (defun org-agenda-time-of-day-to-ampm (time) - "Convert TIME of a string like `13:45' to an AM/PM style time string." + "Convert TIME of a string like \"13:45\" to an AM/PM style time string." (let* ((hour-number (string-to-number (substring time 0 -3))) (minute (substring time -2)) (ampm "am")) @@ -1235,8 +1212,7 @@ For example, 9:30am would become 09:30 rather than 9:30." (defcustom org-agenda-weekend-days '(6 0) "Which days are weekend? -These days get the special face `org-agenda-date-weekend' in the agenda -and timeline buffers." +These days get the special face `org-agenda-date-weekend' in the agenda." :group 'org-agenda-daily/weekly :type '(set :greedy t (const :tag "Monday" 1) @@ -1270,34 +1246,74 @@ Custom commands can set this variable in the options section." :version "24.1" :type 'boolean) -(defcustom org-agenda-repeating-timestamp-show-all t - "Non-nil means show all occurrences of a repeating stamp in the agenda. -When set to a list of strings, only show occurrences of repeating -stamps for these TODO keywords. When nil, only one occurrence is -shown, either today or the nearest into the future." +(defcustom org-agenda-show-future-repeats t + "Non-nil shows repeated entries in the future part of the agenda. +When set to the symbol `next' only the first future repeat is shown." + :group 'org-agenda-daily/weekly + :type '(choice + (const :tag "Show all repeated entries" t) + (const :tag "Show next repeated entry" next) + (const :tag "Do not show repeated entries" nil)) + :version "26.1" + :package-version '(Org . "9.1") + :safe #'symbolp) + +(defcustom org-agenda-prefer-last-repeat nil + "Non-nil sets date for repeated entries to their last repeat. + +When nil, display SCHEDULED and DEADLINE dates at their base +date, and in today's agenda, as a reminder. Display plain +time-stamps, on the other hand, at every repeat date in the past +in addition to the base date. + +When non-nil, show a repeated entry at its latest repeat date, +possibly being today even if it wasn't marked as done. This +setting is useful if you do not always mark repeated entries as +done and, yet, consider that reaching repeat date starts the task +anew. + +When set to a list of strings, prefer last repeats only for +entries with these TODO keywords." :group 'org-agenda-daily/weekly :type '(choice - (const :tag "Show repeating stamps" t) - (repeat :tag "Show repeating stamps for these TODO keywords" - (string :tag "TODO Keyword")) - (const :tag "Don't show repeating stamps" nil))) + (const :tag "Prefer last repeat" t) + (const :tag "Prefer base date" nil) + (repeat :tag "Prefer last repeat for entries with these TODO keywords" + (string :tag "TODO keyword"))) + :version "26.1" + :package-version '(Org . "9.1") + :safe (lambda (x) (or (booleanp x) (consp x)))) (defcustom org-scheduled-past-days 10000 "Number of days to continue listing scheduled items not marked DONE. -When an item is scheduled on a date, it shows up in the agenda on this -day and will be listed until it is marked done for the number of days -given here." +When an item is scheduled on a date, it shows up in the agenda on +this day and will be listed until it is marked done or for the +number of days given here." :group 'org-agenda-daily/weekly - :type 'integer) + :type 'integer + :safe 'integerp) + +(defcustom org-deadline-past-days 10000 + "Number of days to warn about missed deadlines. +When an item has deadline on a date, it shows up in the agenda on +this day and will appear as a reminder until it is marked DONE or +for the number of days given here." + :group 'org-agenda-daily/weekly + :type 'integer + :version "26.1" + :package-version '(Org . "9.1") + :safe 'integerp) (defcustom org-agenda-log-mode-items '(closed clock) "List of items that should be shown in agenda log mode. +\\<org-agenda-mode-map>\ This list may contain the following symbols: closed Show entries that have been closed on that day. clock Show entries that have received clocked time on that day. state Show all logged state changes. -Note that instead of changing this variable, you can also press `C-u l' in +Note that instead of changing this variable, you can also press \ +`\\[universal-argument] \\[org-agenda-log-mode]' in the agenda to display all available LOG items temporarily." :group 'org-agenda-daily/weekly :type '(set :greedy t (const closed) (const clock) (const state))) @@ -1413,7 +1429,7 @@ boolean search." :version "24.1" :type 'boolean) -(org-defvaralias 'org-agenda-search-view-search-words-only +(defvaralias 'org-agenda-search-view-search-words-only 'org-agenda-search-view-always-boolean) (defcustom org-agenda-search-view-force-full-words nil @@ -1429,12 +1445,12 @@ E.g. when this is set to 1, the search view will only show headlines of level 1. When set to 0, the default value, don't limit agenda view by outline level." :group 'org-agenda-search-view - :version "24.4" + :version "26.1" :package-version '(Org . "8.3") :type 'integer) (defgroup org-agenda-time-grid nil - "Options concerning the time grid in the Org-mode Agenda." + "Options concerning the time grid in the Org Agenda." :tag "Org Agenda Time Grid" :group 'org-agenda) @@ -1461,11 +1477,12 @@ the variable `org-agenda-time-grid'." (defcustom org-agenda-time-grid '((daily today require-timed) - "----------------" - (800 1000 1200 1400 1600 1800 2000)) + (800 1000 1200 1400 1600 1800 2000) + "......" + "----------------") "The settings for time grid for agenda display. -This is a list of three items. The first item is again a list. It contains +This is a list of four items. The first item is again a list. It contains symbols specifying conditions when the grid should be displayed: daily if the agenda shows a single day @@ -1474,10 +1491,14 @@ symbols specifying conditions when the grid should be displayed: require-timed show grid only if at least one item has a time specification remove-match skip grid times already present in an entry -The second item is a string which will be placed behind the grid time. +The second item is a list of integers, indicating the times that +should have a grid line. -The third item is a list of integers, indicating the times that should have -a grid line." +The third item is a string which will be placed right after the +times that have a grid line. + +The fourth item is a string placed after the grid times. This +will align with agenda items" :group 'org-agenda-time-grid :type '(list @@ -1489,8 +1510,9 @@ a grid line." require-timed) (const :tag "Skip grid times already present in an entry" remove-match)) - (string :tag "Grid String") - (repeat :tag "Grid Times" (integer :tag "Time")))) + (repeat :tag "Grid Times" (integer :tag "Time")) + (string :tag "Grid String (after agenda times)") + (string :tag "Grid String (aligns with agenda items)"))) (defcustom org-agenda-show-current-time-in-grid t "Non-nil means show the current time in the time grid." @@ -1506,7 +1528,7 @@ a grid line." :type 'string) (defgroup org-agenda-sorting nil - "Options concerning sorting in the Org-mode Agenda." + "Options concerning sorting in the Org Agenda." :tag "Org Agenda Sorting" :group 'org-agenda) @@ -1612,19 +1634,18 @@ When nil, such items are sorted as 0 minutes effort." :type 'boolean) (defgroup org-agenda-line-format nil - "Options concerning the entry prefix in the Org-mode agenda display." + "Options concerning the entry prefix in the Org agenda display." :tag "Org Agenda Line Format" :group 'org-agenda) (defcustom org-agenda-prefix-format '((agenda . " %i %-12:c%?-12t% s") - (timeline . " % s") (todo . " %i %-12:c") (tags . " %i %-12:c") (search . " %i %-12:c")) "Format specifications for the prefix of items in the agenda views. An alist with five entries, each for the different agenda types. The -keys of the sublists are `agenda', `timeline', `todo', `search' and `tags'. +keys of the sublists are `agenda', `todo', `search' and `tags'. The values are format strings. This format works similar to a printf format, with the following meaning: @@ -1677,11 +1698,12 @@ Custom commands can set this variable in the options section." (string :tag "General format") (list :greedy t :tag "View dependent" (cons (const agenda) (string :tag "Format")) - (cons (const timeline) (string :tag "Format")) (cons (const todo) (string :tag "Format")) (cons (const tags) (string :tag "Format")) (cons (const search) (string :tag "Format")))) - :group 'org-agenda-line-format) + :group 'org-agenda-line-format + :version "26.1" + :package-version '(Org . "9.1")) (defvar org-prefix-format-compiled nil "The compiled prefix format and associated variables. @@ -1792,17 +1814,18 @@ When non-nil, this must be the number of minutes, e.g. 60 for one hour." (defcustom org-agenda-show-inherited-tags t "Non-nil means show inherited tags in each agenda line. -When this option is set to 'always, it take precedences over +When this option is set to `always', it takes precedence over `org-agenda-use-tag-inheritance' and inherited tags are shown in every agenda. When this option is set to t (the default), inherited tags are shown when they are available, i.e. when the value of -`org-agenda-use-tag-inheritance' has been taken into account. +`org-agenda-use-tag-inheritance' enables tag inheritance for the +given agenda type. This can be set to a list of agenda types in which the agenda -must display the inherited tags. Available types are 'todo, -'agenda, 'search and 'timeline. +must display the inherited tags. Available types are `todo', +`agenda' and `search'. When set to nil, never show inherited tags in agenda lines." :group 'org-agenda-line-format @@ -1814,7 +1837,7 @@ When set to nil, never show inherited tags in agenda lines." (repeat :tag "Show inherited tags only in selected agenda types" (symbol :tag "Agenda type")))) -(defcustom org-agenda-use-tag-inheritance '(todo search timeline agenda) +(defcustom org-agenda-use-tag-inheritance '(todo search agenda) "List of agenda view types where to use tag inheritance. In tags/tags-todo/tags-tree agenda views, tag inheritance is @@ -1823,16 +1846,17 @@ controlled by `org-use-tag-inheritance'. In other agenda types, agenda entries. Still, you may want the agenda to be aware of the inherited tags anyway, e.g. for later tag filtering. -Allowed value are 'todo, 'search, 'timeline and 'agenda. +Allowed value are `todo', `search' and `agenda'. This variable has no effect if `org-agenda-show-inherited-tags' -is set to 'always. In that case, the agenda is aware of those +is set to `always'. In that case, the agenda is aware of those tags. The default value sets tags in every agenda type. Setting this option to nil will speed up non-tags agenda view a lot." :group 'org-agenda - :version "24.3" + :version "26.1" + :package-version '(Org . "9.1") :type '(choice (const :tag "Use tag inheritance in all agenda types" t) (repeat :tag "Use tag inheritance in selected agenda types" @@ -1858,18 +1882,26 @@ When this is the symbol `prefix', only remove tags when (const :tag "Never" nil) (const :tag "When prefix format contains %T" prefix))) -(org-defvaralias 'org-agenda-remove-tags-when-in-prefix +(defvaralias 'org-agenda-remove-tags-when-in-prefix 'org-agenda-remove-tags) -(defcustom org-agenda-tags-column (if (featurep 'xemacs) -79 -80) +(defcustom org-agenda-tags-column 'auto "Shift tags in agenda items to this column. -If this number is positive, it specifies the column. If it is negative, -it means that the tags should be flushright to that column. For example, --80 works well for a normal 80 character screen." +If set to `auto', tags will be automatically aligned to the right +edge of the window. + +If set to a positive number, tags will be left-aligned to that +column. If set to a negative number, tags will be right-aligned +to that column. For example, -80 works well for a normal 80 +character screen." :group 'org-agenda-line-format - :type 'integer) + :type '(choice + (const :tag "Automatically align to right edge of window" auto) + (integer :tag "Specific column" -80)) + :package-version '(Org . "9.1") + :version "26.1") -(org-defvaralias 'org-agenda-align-tags-to-column 'org-agenda-tags-column) +(defvaralias 'org-agenda-align-tags-to-column 'org-agenda-tags-column) (defcustom org-agenda-fontify-priorities 'cookies "Non-nil means highlight low and high priorities in agenda. @@ -1948,6 +1980,14 @@ category, you can use: :tag "Org Agenda Column View" :group 'org-agenda) +(defcustom org-agenda-view-columns-initially nil + "When non-nil, switch to columns view right after creating the agenda." + :group 'org-agenda-column-view + :type 'boolean + :version "26.1" + :package-version '(Org . "9.0") + :safe #'booleanp) + (defcustom org-agenda-columns-show-summaries t "Non-nil means show summaries for columns displayed in the agenda view." :group 'org-agenda-column-view @@ -1975,7 +2015,8 @@ estimate." :type 'boolean) (defcustom org-agenda-auto-exclude-function nil - "A function called with a tag to decide if it is filtered on `/ RET'. + "A function called with a tag to decide if it is filtered on \ +\\<org-agenda-mode-map>`\\[org-agenda-filter-by-tag] RET'. The sole argument to the function, which is called once for each possible tag, is a string giving the name of the tag. The function should return either nil if the tag should be included @@ -1990,13 +2031,13 @@ the lower-case version of all tags." "Alist of characters and custom functions for bulk actions. For example, this value makes those two functions available: - ((?R set-category) - (?C bulk-cut)) + \\='((?R set-category) + (?C bulk-cut)) With selected entries in an agenda buffer, `B R' will call the custom function `set-category' on the selected entries. Note that functions in this alist don't need to be quoted." - :type 'alist + :type '(alist :key-type character :value-type (group function)) :version "24.1" :group 'org-agenda) @@ -2006,7 +2047,7 @@ If STRING is non-nil, the text property will be fetched from position 0 in that string. If STRING is nil, it will be fetched from the beginning of the current line." (org-with-gensyms (marker) - `(let ((,marker (get-text-property (if string 0 (point-at-bol)) + `(let ((,marker (get-text-property (if ,string 0 (point-at-bol)) 'org-hd-marker ,string))) (with-current-buffer (marker-buffer ,marker) (save-excursion @@ -2027,7 +2068,7 @@ works you probably want to add it to `org-agenda-custom-commands' for good." (defvar org-agenda-mode-map (make-sparse-keymap) "Keymap for `org-agenda-mode'.") -(org-defvaralias 'org-agenda-keymap 'org-agenda-mode-map) +(defvaralias 'org-agenda-keymap 'org-agenda-mode-map) (defvar org-agenda-menu) ; defined later in this file. (defvar org-agenda-restrict nil) ; defined later in this file. @@ -2044,6 +2085,8 @@ The buffer is still writable when this hook is called.") (defvar org-agenda-force-single-file nil) (defvar org-agenda-bulk-marked-entries nil "List of markers that refer to marked entries in the agenda.") +(defvar org-agenda-current-date nil + "Active date when building the agenda.") ;;; Multiple agenda buffers support @@ -2064,13 +2107,13 @@ When nil, `q' will kill the single agenda buffer." (> (prefix-numeric-value arg) 0) (not org-agenda-sticky)))) (if (equal new-value org-agenda-sticky) - (and (org-called-interactively-p 'interactive) + (and (called-interactively-p 'interactive) (message "Sticky agenda was already %s" (if org-agenda-sticky "enabled" "disabled"))) (setq org-agenda-sticky new-value) (org-agenda-kill-all-agenda-buffers) - (and (org-called-interactively-p 'interactive) - (message "Sticky agenda was %s" + (and (called-interactively-p 'interactive) + (message "Sticky agenda %s" (if org-agenda-sticky "enabled" "disabled")))))) (defvar org-agenda-buffer nil @@ -2080,6 +2123,8 @@ When nil, `q' will kill the single agenda buffer." (defvar org-agenda-this-buffer-name nil) (defvar org-agenda-doing-sticky-redo nil) (defvar org-agenda-this-buffer-is-sticky nil) +(defvar org-agenda-last-indirect-buffer nil + "Last buffer loaded by `org-agenda-tree-to-indirect-buffer'.") (defconst org-agenda-local-vars '(org-agenda-this-buffer-name @@ -2101,8 +2146,10 @@ When nil, `q' will kill the single agenda buffer." org-agenda-category-filter org-agenda-top-headline-filter org-agenda-regexp-filter + org-agenda-effort-filter org-agenda-markers org-agenda-last-search-view-search-was-boolean + org-agenda-last-indirect-buffer org-agenda-filtered-by-category org-agenda-filter-form org-agenda-cycle-counter @@ -2110,7 +2157,7 @@ When nil, `q' will kill the single agenda buffer." "Variables that must be local in agenda buffers to allow multiple buffers.") (defun org-agenda-mode () - "Mode for time-sorted view on action items in Org-mode files. + "Mode for time-sorted view on action items in Org files. The following commands are available: @@ -2123,42 +2170,41 @@ The following commands are available: ;; while letting `kill-all-local-variables' kill the rest (let ((save (buffer-local-variables))) (kill-all-local-variables) - (mapc 'make-local-variable org-agenda-local-vars) + (mapc #'make-local-variable org-agenda-local-vars) (dolist (elem save) - (let ((var (car elem)) - (val (cdr elem))) - (when (and val - (member var org-agenda-local-vars)) - (set var val))))) - (set (make-local-variable 'org-agenda-this-buffer-is-sticky) t)) + (pcase elem + (`(,var . ,val) ;ignore unbound variables + (when (and val (memq var org-agenda-local-vars)) + (set var val)))))) + (setq-local org-agenda-this-buffer-is-sticky t)) (org-agenda-sticky ;; Creating a sticky Agenda buffer for the first time (kill-all-local-variables) (mapc 'make-local-variable org-agenda-local-vars) - (set (make-local-variable 'org-agenda-this-buffer-is-sticky) t)) + (setq-local org-agenda-this-buffer-is-sticky t)) (t ;; Creating a non-sticky agenda buffer (kill-all-local-variables) - (set (make-local-variable 'org-agenda-this-buffer-is-sticky) nil))) + (setq-local org-agenda-this-buffer-is-sticky nil))) (setq org-agenda-undo-list nil org-agenda-pending-undo-list nil org-agenda-bulk-marked-entries nil) (setq major-mode 'org-agenda-mode) ;; Keep global-font-lock-mode from turning on font-lock-mode - (org-set-local 'font-lock-global-modes (list 'not major-mode)) + (setq-local font-lock-global-modes (list 'not major-mode)) (setq mode-name "Org-Agenda") (setq indent-tabs-mode nil) (use-local-map org-agenda-mode-map) (easy-menu-add org-agenda-menu) (if org-startup-truncated (setq truncate-lines t)) - (org-set-local 'line-move-visual nil) - (org-add-hook 'post-command-hook 'org-agenda-update-agenda-type nil 'local) - (org-add-hook 'pre-command-hook 'org-unhighlight nil 'local) + (setq-local line-move-visual nil) + (add-hook 'post-command-hook 'org-agenda-update-agenda-type nil 'local) + (add-hook 'pre-command-hook 'org-unhighlight nil 'local) ;; Make sure properties are removed when copying text - (org-add-hook 'filter-buffer-substring-functions - (lambda (fun start end delete) - (substring-no-properties (funcall fun start end delete))) - nil t) + (add-hook 'filter-buffer-substring-functions + (lambda (fun start end delete) + (substring-no-properties (funcall fun start end delete))) + nil t) (unless org-agenda-keep-modes (setq org-agenda-follow-mode org-agenda-start-with-follow-mode org-agenda-entry-text-mode org-agenda-start-with-entry-text-mode)) @@ -2303,25 +2349,31 @@ The following commands are available: (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-remove-all) -(org-defkey org-agenda-mode-map "\\" 'org-agenda-filter-by-tag-refine) (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 [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) + (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" @@ -2329,7 +2381,7 @@ The following commands are available: ("Agenda Files") "--" ("Agenda Dates" - ["Goto Today" org-agenda-goto-today (org-agenda-check-type nil 'agenda 'timeline)] + ["Goto Today" org-agenda-goto-today (org-agenda-check-type nil 'agenda)] ["Next Dates" org-agenda-later (org-agenda-check-type nil 'agenda)] ["Previous Dates" org-agenda-earlier (org-agenda-check-type nil 'agenda)] ["Jump to date" org-agenda-goto-date (org-agenda-check-type nil 'agenda)]) @@ -2346,7 +2398,7 @@ The following commands are available: ["Fortnight View" org-agenda-fortnight-view :active (org-agenda-check-type nil 'agenda) :style radio :selected (eq org-agenda-current-span 'fortnight) - :keys "v f"] + :keys "v t"] ["Month View" org-agenda-month-view :active (org-agenda-check-type nil 'agenda) :style radio :selected (eq org-agenda-current-span 'month) @@ -2375,7 +2427,7 @@ The following commands are available: "--" ["Show Logbook entries" org-agenda-log-mode :style toggle :selected org-agenda-show-log - :active (org-agenda-check-type nil 'agenda 'timeline) + :active (org-agenda-check-type nil 'agenda) :keys "v l (or just l)"] ["Include archived trees" org-agenda-archives-mode :style toggle :selected org-agenda-archives-mode :active t @@ -2387,7 +2439,7 @@ The following commands are available: ["Remove Restriction" org-agenda-remove-restriction-lock org-agenda-restrict]) ["Write view to file" org-agenda-write t] ["Rebuild buffer" org-agenda-redo t] - ["Save all Org-mode Buffers" org-save-all-org-buffers t] + ["Save all Org buffers" org-save-all-org-buffers t] "--" ["Show original entry" org-agenda-show t] ["Go To (other window)" org-agenda-goto t] @@ -2432,13 +2484,13 @@ The following commands are available: ["Schedule" org-agenda-schedule t] ["Set Deadline" org-agenda-deadline t] "--" - ["Change Date +1 day" org-agenda-date-later (org-agenda-check-type nil 'agenda 'timeline)] - ["Change Date -1 day" org-agenda-date-earlier (org-agenda-check-type nil 'agenda 'timeline)] - ["Change Time +1 hour" org-agenda-do-date-later :active (org-agenda-check-type nil 'agenda 'timeline) :keys "C-u S-right"] - ["Change Time -1 hour" org-agenda-do-date-earlier :active (org-agenda-check-type nil 'agenda 'timeline) :keys "C-u S-left"] - ["Change Time + min" org-agenda-date-later :active (org-agenda-check-type nil 'agenda 'timeline) :keys "C-u C-u S-right"] - ["Change Time - min" org-agenda-date-earlier :active (org-agenda-check-type nil 'agenda 'timeline) :keys "C-u C-u S-left"] - ["Change Date to ..." org-agenda-date-prompt (org-agenda-check-type nil 'agenda 'timeline)]) + ["Change Date +1 day" org-agenda-date-later (org-agenda-check-type nil 'agenda)] + ["Change Date -1 day" org-agenda-date-earlier (org-agenda-check-type nil 'agenda)] + ["Change Time +1 hour" org-agenda-do-date-later :active (org-agenda-check-type nil 'agenda) :keys "C-u S-right"] + ["Change Time -1 hour" org-agenda-do-date-earlier :active (org-agenda-check-type nil 'agenda) :keys "C-u S-left"] + ["Change Time + min" org-agenda-date-later :active (org-agenda-check-type nil 'agenda) :keys "C-u C-u S-right"] + ["Change Time - min" org-agenda-date-earlier :active (org-agenda-check-type nil 'agenda) :keys "C-u C-u S-left"] + ["Change Date to ..." org-agenda-date-prompt (org-agenda-check-type nil 'agenda)]) ("Clock and Effort" ["Clock in" org-agenda-clock-in t] ["Clock out" org-agenda-clock-out t] @@ -2454,12 +2506,12 @@ The following commands are available: ["Decrease Priority" org-agenda-priority-down t] ["Show Priority" org-show-priority t]) ("Calendar/Diary" - ["New Diary Entry" org-agenda-diary-entry (org-agenda-check-type nil 'agenda 'timeline)] - ["Goto Calendar" org-agenda-goto-calendar (org-agenda-check-type nil 'agenda 'timeline)] - ["Phases of the Moon" org-agenda-phases-of-moon (org-agenda-check-type nil 'agenda 'timeline)] - ["Sunrise/Sunset" org-agenda-sunrise-sunset (org-agenda-check-type nil 'agenda 'timeline)] - ["Holidays" org-agenda-holidays (org-agenda-check-type nil 'agenda 'timeline)] - ["Convert" org-agenda-convert-date (org-agenda-check-type nil 'agenda 'timeline)] + ["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)] + ["Phases of the Moon" org-agenda-phases-of-moon (org-agenda-check-type nil 'agenda)] + ["Sunrise/Sunset" org-agenda-sunrise-sunset (org-agenda-check-type nil 'agenda)] + ["Holidays" org-agenda-holidays (org-agenda-check-type nil 'agenda)] + ["Convert" org-agenda-convert-date (org-agenda-check-type nil 'agenda)] "--" ["Create iCalendar File" org-icalendar-combine-agenda-files t]) "--" @@ -2468,7 +2520,7 @@ The following commands are available: ("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 "C-c a ?"] + ["Find FLAGGED Tasks" (org-agenda nil "?") :active t :keys "\\[org-agenda] ?"] ["Show note / unflag" org-agenda-show-the-flagging-note t] "--" ["Setup" (progn (require 'org-mobile) (customize-group 'org-mobile)) t]) @@ -2538,7 +2590,7 @@ For example, if you have a custom agenda command \"p\" and you want this command to be accessible only from plain text files, use this: - \\='((\"p\" ((in-file . \"\\.txt\")))) + \\='((\"p\" ((in-file . \"\\\\.txt\\\\'\")))) Here are the available contexts definitions: @@ -2556,7 +2608,7 @@ accessible if there is at least one valid check. You can also bind a key to another agenda custom command depending on contextual rules. - \\='((\"p\" \"q\" ((in-file . \"\\.txt\")))) + \\='((\"p\" \"q\" ((in-file . \"\\\\.txt\\\\'\")))) Here it means: in .txt files, use \"p\" as the key for the agenda command otherwise associated with \"q\". (The command @@ -2595,8 +2647,7 @@ type." (const agenda) (const todo) (const tags) - (const search) - (const timeline)) + (const search)) (integer :tag "Max number of entries"))))) (defcustom org-agenda-max-todos nil @@ -2614,8 +2665,7 @@ type." (const agenda) (const todo) (const tags) - (const search) - (const timeline)) + (const search)) (integer :tag "Max number of TODOs"))))) (defcustom org-agenda-max-tags nil @@ -2633,8 +2683,7 @@ type." (const agenda) (const todo) (const tags) - (const search) - (const timeline)) + (const search)) (integer :tag "Max number of tagged entries"))))) (defcustom org-agenda-max-effort nil @@ -2652,10 +2701,10 @@ to limit entries to in this type." (const agenda) (const todo) (const tags) - (const search) - (const timeline)) + (const search)) (integer :tag "Max number of minutes"))))) +(defvar org-agenda-keep-restricted-file-list nil) (defvar org-keys nil) (defvar org-match nil) ;;;###autoload @@ -2671,7 +2720,6 @@ T Call `org-todo-list' to display the global todo list, select only m Call `org-tags-view' to display headlines with tags matching a condition (the user is prompted for the condition). M Like `m', but select only TODO entries, no ordinary headlines. -L Create a timeline for the current buffer. e Export views to associated files. s Search entries for keywords. S Search entries for keywords, only with TODO keywords. @@ -2688,9 +2736,9 @@ More commands can be added by configuring the variable `org-agenda-custom-commands'. In particular, specific tags and TODO keyword searches can be pre-defined in this way. -If the current buffer is in Org-mode and visiting a file, you can also +If the current buffer is in Org mode and visiting a file, you can also first press `<' once to indicate that the agenda should be temporarily -\(until the next use of \\[org-agenda]) restricted to the current file. +\(until the next use of `\\[org-agenda]') restricted to the current file. Pressing `<' twice means to restrict to the current subtree or region \(if active)." (interactive "P") @@ -2722,7 +2770,7 @@ Pressing `<' twice means to restrict to the current subtree or region entry key type org-match lprops ans) ;; Turn off restriction unless there is an overriding one, (unless org-agenda-overriding-restriction - (unless (org-bound-and-true-p org-agenda-keep-restricted-file-list) + (unless org-agenda-keep-restricted-file-list ;; There is a request to keep the file list in place (put 'org-agenda-files 'org-restrict nil)) (setq org-agenda-restrict nil) @@ -2819,7 +2867,7 @@ Pressing `<' twice means to restrict to the current subtree or region ((equal org-keys "M") (org-call-with-arg 'org-tags-view (or arg '(4)))) ((equal org-keys "e") (call-interactively 'org-store-agenda-views)) ((equal org-keys "?") (org-tags-view nil "+FLAGGED") - (org-add-hook + (add-hook 'post-command-hook (lambda () (unless (current-message) @@ -2834,12 +2882,6 @@ Pressing `<' twice means to restrict to the current subtree or region (copy-sequence note)) nil 'face 'org-warning))))))) t t)) - ((equal org-keys "L") - (unless (derived-mode-p 'org-mode) - (user-error "This is not an Org-mode file")) - (unless restriction - (put 'org-agenda-files 'org-restrict (list bfn)) - (org-call-with-arg 'org-timeline arg))) ((equal org-keys "#") (call-interactively 'org-agenda-list-stuck-projects)) ((equal org-keys "/") (call-interactively 'org-occur-in-agenda-files)) ((equal org-keys "!") (customize-variable 'org-stuck-projects)) @@ -2889,15 +2931,15 @@ Agenda views are separated by `org-agenda-block-separator'." (erase-buffer) (insert (eval-when-compile (let ((header - "Press key for an agenda command: < Buffer, subtree/region restriction --------------------------------- > Remove restriction -a Agenda for current week or day e Export agenda views -t List of all TODO entries T Entries with special TODO kwd -m Match a TAGS/PROP/TODO query M Like m, but only TODO entries -s Search for keywords S Like s, but only TODO entries -L Timeline for current buffer # List stuck projects (!=configure) -/ Multi-occur C Configure custom agenda commands -? Find :FLAGGED: entries * Toggle sticky agenda views + "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 +m Match a TAGS/PROP/TODO query T Entries with special TODO kwd +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 @@ -2928,7 +2970,7 @@ L Timeline for current buffer # List stuck projects (!=configure) type (nth 2 entry) match (nth 3 entry)) (if (> (length key) 1) - (pushnew (string-to-char key) prefixes :test #'equal) + (cl-pushnew (string-to-char key) prefixes :test #'equal) (setq line (format "%-4s%-14s" @@ -3034,7 +3076,7 @@ L Timeline for current buffer # List stuck projects (!=configure) (call-interactively 'org-toggle-sticky-agenda) (sit-for 2)) ((and (not restrict-ok) (memq c '(?1 ?0 ?<))) - (message "Restriction is only possible in Org-mode buffers") + (message "Restriction is only possible in Org buffers") (ding) (sit-for 1)) ((eq c ?1) (org-agenda-remove-restriction-lock 'noupdate) @@ -3067,10 +3109,13 @@ L Timeline for current buffer # List stuck projects (!=configure) "Fit the window to the buffer size." (and (memq org-agenda-window-setup '(reorganize-frame)) (fboundp 'fit-window-to-buffer) - (org-fit-window-to-buffer - nil - (floor (* (frame-height) (cdr org-agenda-window-frame-fractions))) - (floor (* (frame-height) (car org-agenda-window-frame-fractions)))))) + (if (and (= (cdr org-agenda-window-frame-fractions) 1.0) + (= (car org-agenda-window-frame-fractions) 1.0)) + (delete-other-windows) + (org-fit-window-to-buffer + nil + (floor (* (frame-height) (cdr org-agenda-window-frame-fractions))) + (floor (* (frame-height) (car org-agenda-window-frame-fractions))))))) (defvar org-cmd nil) (defvar org-agenda-overriding-cmd nil) @@ -3089,9 +3134,9 @@ L Timeline for current buffer # List stuck projects (!=configure) match ;; The byte compiler incorrectly complains about this. Keep it! org-cmd type lprops) (while (setq org-cmd (pop cmds)) - (setq type (car org-cmd) - match (eval (nth 1 org-cmd)) - lprops (nth 2 org-cmd)) + (setq type (car org-cmd)) + (setq match (eval (nth 1 org-cmd))) + (setq lprops (nth 2 org-cmd)) (let ((org-agenda-overriding-arguments (if (eq org-agenda-overriding-cmd org-cmd) (or org-agenda-overriding-arguments @@ -3144,7 +3189,7 @@ Parameters are alternating variable names and values that will be bound before running the agenda command." (org-eval-in-environment (org-make-parameter-alist parameters) (let (org-agenda-sticky) - (if (> (length cmd-key) 2) + (if (> (length cmd-key) 1) (org-tags-view nil cmd-key) (org-agenda nil cmd-key)))) (set-buffer org-agenda-buffer-name) @@ -3232,7 +3277,7 @@ This ensures the export commands can easily use it." (setq tmp (replace-match "" t t tmp))) (when (and (setq re (plist-get props 'org-todo-regexp)) (setq re (concat "\\`\\.*" re " ?")) - (string-match re tmp)) + (let ((case-fold-search nil)) (string-match re tmp))) (plist-put props 'todo (match-string 1 tmp)) (setq tmp (replace-match "" t t tmp))) (plist-put props 'txt tmp))) @@ -3245,9 +3290,7 @@ This ensures the export commands can easily use it." ((not res) "") ((stringp res) res) (t (prin1-to-string res)))) - (while (string-match "," res) - (setq res (replace-match ";" t t res))) - (org-trim res))) + (org-trim (replace-regexp-in-string "," ";" res nil t)))) ;;;###autoload (defun org-store-agenda-views (&rest parameters) @@ -3306,39 +3349,43 @@ This ensures the export commands can easily use it." (defvar org-agenda-write-buffer-name "Agenda View") (defun org-agenda-write (file &optional open nosettings agenda-bufname) "Write the current buffer (an agenda view) as a file. + Depending on the extension of the file name, plain text (.txt), HTML (.html or .htm), PDF (.pdf) or Postscript (.ps) is produced. -If the extension is .ics, run icalendar export over all files used -to construct the agenda and limit the export to entries listed in the -agenda now. -If the extension is .org, collect all subtrees corresponding to the -agenda entries and add them in an .org file. -With prefix argument OPEN, open the new file immediately. -If NOSETTINGS is given, do not scope the settings of -`org-agenda-exporter-settings' into the export commands. This is used when -the settings have already been scoped and we do not wish to overrule other, -higher priority settings. -If AGENDA-BUFFER-NAME, use this as the buffer name for the agenda to write." +If the extension is .ics, translate visible agenda into iCalendar +format. If the extension is .org, collect all subtrees +corresponding to the agenda entries and add them in an .org file. + +With prefix argument OPEN, open the new file immediately. If +NOSETTINGS is given, do not scope the settings of +`org-agenda-exporter-settings' into the export commands. This is +used when the settings have already been scoped and we do not +wish to overrule other, higher priority settings. If +AGENDA-BUFFER-NAME is provided, use this as the buffer name for +the agenda to write." (interactive "FWrite agenda to file: \nP") (if (or (not (file-writable-p file)) (and (file-exists-p file) - (if (org-called-interactively-p 'any) + (if (called-interactively-p 'any) (not (y-or-n-p (format "Overwrite existing file %s? " file)))))) (user-error "Cannot write agenda to file %s" file)) (org-let (if nosettings nil org-agenda-exporter-settings) '(save-excursion (save-window-excursion - (let ((bs (copy-sequence (buffer-string))) beg content) + (let ((bs (copy-sequence (buffer-string))) + (extension (file-name-extension file)) + (default-directory (file-name-directory file)) + beg content) (with-temp-buffer (rename-buffer org-agenda-write-buffer-name t) (set-buffer-modified-p nil) (insert bs) - (org-agenda-remove-marked-text 'org-filtered) + (org-agenda-remove-marked-text 'invisible 'org-filtered) (run-hooks 'org-agenda-before-write-hook) (cond - ((org-bound-and-true-p org-mobile-creating-agendas) + ((bound-and-true-p org-mobile-creating-agendas) (org-mobile-write-agenda-for-mobile file)) - ((string-match "\\.org\\'" file) + ((string= "org" extension) (let (content p m message-log-max) (goto-char (point-min)) (while (setq p (next-single-property-change (point) 'org-hd-marker nil)) @@ -3357,8 +3404,9 @@ If AGENDA-BUFFER-NAME, use this as the buffer name for the agenda to write." (write-file file) (kill-buffer (current-buffer)) (message "Org file written to %s" file))) - ((string-match "\\.html?\\'" file) - (require 'htmlize) + ((member extension '("html" "htm")) + (or (require 'htmlize nil t) + (error "Please install htmlize from https://github.com/hniksic/emacs-htmlize")) (set-buffer (htmlize-buffer (current-buffer))) (when org-agenda-export-html-style ;; replace <style> section with org-agenda-export-html-style @@ -3369,11 +3417,11 @@ If AGENDA-BUFFER-NAME, use this as the buffer name for the agenda to write." (write-file file) (kill-buffer (current-buffer)) (message "HTML written to %s" file)) - ((string-match "\\.ps\\'" file) + ((string= "ps" extension) (require 'ps-print) (ps-print-buffer-with-faces file) (message "Postscript written to %s" file)) - ((string-match "\\.pdf\\'" file) + ((string= "pdf" extension) (require 'ps-print) (ps-print-buffer-with-faces (concat (file-name-sans-extension file) ".ps")) @@ -3383,7 +3431,7 @@ If AGENDA-BUFFER-NAME, use this as the buffer name for the agenda to write." (expand-file-name file)) (delete-file (concat (file-name-sans-extension file) ".ps")) (message "PDF written to %s" file)) - ((string-match "\\.ics\\'" file) + ((string= "ics" extension) (require 'ox-icalendar) (org-icalendar-export-current-agenda (expand-file-name file))) (t @@ -3395,7 +3443,7 @@ If AGENDA-BUFFER-NAME, use this as the buffer name for the agenda to write." (kill-buffer (current-buffer)) (message "Plain text written to %s" file)))))))) (set-buffer (or agenda-bufname - (and (org-called-interactively-p 'any) (buffer-name)) + (and (called-interactively-p 'any) (buffer-name)) org-agenda-buffer-name))) (when open (org-open-file file))) @@ -3416,7 +3464,7 @@ This will add a maximum of `org-agenda-add-entry-text-maxlines' lines of the entry text following headings shown in the agenda. Drawers will be excluded, also the line with scheduling/deadline info." (when (and (> org-agenda-add-entry-text-maxlines 0) - (not (org-bound-and-true-p org-mobile-creating-agendas))) + (not (bound-and-true-p org-mobile-creating-agendas))) (let (m txt) (goto-char (point-min)) (while (not (eobp)) @@ -3441,85 +3489,83 @@ removed from the entry content. Currently only `planning' is allowed here." (with-current-buffer (marker-buffer marker) (if (not (derived-mode-p 'org-mode)) (setq txt "") - (save-excursion - (save-restriction - (widen) - (goto-char marker) - (end-of-line 1) - (setq txt (buffer-substring - (min (1+ (point)) (point-max)) - (progn (outline-next-heading) (point))) - drawer-re org-drawer-regexp - kwd-time-re (concat "^[ \t]*" org-keyword-time-regexp - ".*\n?")) - (with-temp-buffer - (insert txt) - (when org-agenda-add-entry-text-descriptive-links - (goto-char (point-min)) - (while (org-activate-bracket-links (point-max)) - (add-text-properties (match-beginning 0) (match-end 0) - '(face org-link)))) - (goto-char (point-min)) - (while (re-search-forward org-bracket-link-regexp (point-max) t) - (set-text-properties (match-beginning 0) (match-end 0) - nil)) - (goto-char (point-min)) - (while (re-search-forward drawer-re nil t) - (delete-region - (match-beginning 0) - (progn (re-search-forward - "^[ \t]*:END:.*\n?" nil 'move) - (point)))) - (unless (member 'planning keep) - (goto-char (point-min)) - (while (re-search-forward kwd-time-re nil t) - (replace-match ""))) - (goto-char (point-min)) - (when org-agenda-entry-text-exclude-regexps - (let ((re-list org-agenda-entry-text-exclude-regexps) re) - (while (setq re (pop re-list)) - (goto-char (point-min)) - (while (re-search-forward re nil t) - (replace-match ""))))) - (goto-char (point-max)) - (skip-chars-backward " \t\n") - (if (looking-at "[ \t\n]+\\'") (replace-match "")) - - ;; find and remove min common indentation - (goto-char (point-min)) - (untabify (point-min) (point-max)) - (setq ind (org-get-indentation)) - (while (not (eobp)) - (unless (looking-at "[ \t]*$") - (setq ind (min ind (org-get-indentation)))) - (beginning-of-line 2)) - (goto-char (point-min)) - (while (not (eobp)) - (unless (looking-at "[ \t]*$") - (move-to-column ind) - (delete-region (point-at-bol) (point))) - (beginning-of-line 2)) - - (run-hooks 'org-agenda-entry-text-cleanup-hook) - - (goto-char (point-min)) - (when indent - (while (and (not (eobp)) (re-search-forward "^" nil t)) - (replace-match indent t t))) - (goto-char (point-min)) - (while (looking-at "[ \t]*\n") (replace-match "")) - (goto-char (point-max)) - (when (> (org-current-line) - n-lines) - (org-goto-line (1+ n-lines)) - (backward-char 1)) - (setq txt (buffer-substring (point-min) (point))))))))) + (org-with-wide-buffer + (goto-char marker) + (end-of-line 1) + (setq txt (buffer-substring + (min (1+ (point)) (point-max)) + (progn (outline-next-heading) (point))) + drawer-re org-drawer-regexp + kwd-time-re (concat "^[ \t]*" org-keyword-time-regexp + ".*\n?")) + (with-temp-buffer + (insert txt) + (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 (point-min)) + (while (re-search-forward org-bracket-link-regexp (point-max) t) + (set-text-properties (match-beginning 0) (match-end 0) + nil)) + (goto-char (point-min)) + (while (re-search-forward drawer-re nil t) + (delete-region + (match-beginning 0) + (progn (re-search-forward + "^[ \t]*:END:.*\n?" nil 'move) + (point)))) + (unless (member 'planning keep) + (goto-char (point-min)) + (while (re-search-forward kwd-time-re nil t) + (replace-match ""))) + (goto-char (point-min)) + (when org-agenda-entry-text-exclude-regexps + (let ((re-list org-agenda-entry-text-exclude-regexps) re) + (while (setq re (pop re-list)) + (goto-char (point-min)) + (while (re-search-forward re nil t) + (replace-match ""))))) + (goto-char (point-max)) + (skip-chars-backward " \t\n") + (if (looking-at "[ \t\n]+\\'") (replace-match "")) + + ;; find and remove min common indentation + (goto-char (point-min)) + (untabify (point-min) (point-max)) + (setq ind (org-get-indentation)) + (while (not (eobp)) + (unless (looking-at "[ \t]*$") + (setq ind (min ind (org-get-indentation)))) + (beginning-of-line 2)) + (goto-char (point-min)) + (while (not (eobp)) + (unless (looking-at "[ \t]*$") + (move-to-column ind) + (delete-region (point-at-bol) (point))) + (beginning-of-line 2)) + + (run-hooks 'org-agenda-entry-text-cleanup-hook) + + (goto-char (point-min)) + (when indent + (while (and (not (eobp)) (re-search-forward "^" nil t)) + (replace-match indent t t))) + (goto-char (point-min)) + (while (looking-at "[ \t]*\n") (replace-match "")) + (goto-char (point-max)) + (when (> (org-current-line) + n-lines) + (org-goto-line (1+ n-lines)) + (backward-char 1)) + (setq txt (buffer-substring (point-min) (point)))))))) txt)) (defun org-check-for-org-mode () "Make sure current buffer is in org-mode. Error if not." (or (derived-mode-p 'org-mode) - (error "Cannot execute org-mode agenda command on buffer in %s" + (error "Cannot execute Org agenda command on buffer in %s" major-mode))) ;;; Agenda prepare and finalize @@ -3531,6 +3577,7 @@ removed from the entry content. Currently only `planning' is allowed here." (defvar org-agenda-tag-filter nil) (defvar org-agenda-category-filter nil) (defvar org-agenda-regexp-filter nil) +(defvar org-agenda-effort-filter nil) (defvar org-agenda-top-headline-filter nil) (defvar org-agenda-tag-filter-preset nil "A preset of the tags filter used for secondary agenda filtering. @@ -3562,6 +3609,16 @@ the entire agenda view. In a block agenda, it will not work reliably to define a filter for one of the individual blocks. You need to set it in the global options and expect it to be applied to the entire view.") +(defvar org-agenda-effort-filter-preset nil + "A preset of the effort condition used for secondary agenda filtering. +This must be a list of strings, each string must be a single regexp +preceded by \"+\" or \"-\". +This variable should not be set directly, but agenda custom commands can +bind it in the options section. The preset filter is a global property of +the entire agenda view. In a block agenda, it will not work reliably to +define a filter for one of the individual blocks. You need to set it in +the global options and expect it to be applied to the entire view.") + (defun org-agenda-use-sticky-p () "Return non-nil if an agenda buffer named `org-agenda-buffer-name' exists and should be shown instead of @@ -3593,30 +3650,37 @@ FILTER-ALIST is an alist of filters we need to apply when ((equal (current-buffer) abuf) nil) (awin (select-window awin)) ((not (setq wconf (current-window-configuration)))) - ((equal org-agenda-window-setup 'current-window) - (org-pop-to-buffer-same-window abuf)) - ((equal org-agenda-window-setup 'other-window) + ((eq org-agenda-window-setup 'current-window) + (pop-to-buffer-same-window abuf)) + ((eq org-agenda-window-setup 'other-window) (org-switch-to-buffer-other-window abuf)) - ((equal org-agenda-window-setup 'other-frame) + ((eq org-agenda-window-setup 'other-frame) (switch-to-buffer-other-frame abuf)) - ((equal org-agenda-window-setup 'reorganize-frame) + ((eq org-agenda-window-setup 'only-window) + (delete-other-windows) + (pop-to-buffer-same-window abuf)) + ((eq org-agenda-window-setup 'reorganize-frame) (delete-other-windows) (org-switch-to-buffer-other-window abuf))) - (setq org-agenda-tag-filter (cdr (assoc 'tag filter-alist))) - (setq org-agenda-category-filter (cdr (assoc 'cat filter-alist))) - (setq org-agenda-regexp-filter (cdr (assoc 're filter-alist))) + (setq org-agenda-tag-filter (cdr (assq 'tag filter-alist))) + (setq org-agenda-category-filter (cdr (assq 'cat filter-alist))) + (setq org-agenda-effort-filter (cdr (assq 'effort filter-alist))) + (setq org-agenda-regexp-filter (cdr (assq 're filter-alist))) ;; Additional test in case agenda is invoked from within agenda ;; buffer via elisp link. (unless (equal (current-buffer) abuf) - (org-pop-to-buffer-same-window abuf)) + (pop-to-buffer-same-window abuf)) (setq org-agenda-pre-window-conf - (or org-agenda-pre-window-conf wconf)))) + (or wconf org-agenda-pre-window-conf)))) (defun org-agenda-prepare (&optional name) (let ((filter-alist (if org-agenda-persistent-filter - (list `(tag . ,org-agenda-tag-filter) - `(re . ,org-agenda-regexp-filter) - `(car . ,org-agenda-category-filter))))) + (with-current-buffer + (get-buffer-create org-agenda-buffer-name) + (list `(tag . ,org-agenda-tag-filter) + `(re . ,org-agenda-regexp-filter) + `(effort . ,org-agenda-effort-filter) + `(cat . ,org-agenda-category-filter)))))) (if (org-agenda-use-sticky-p) (progn (put 'org-agenda-tag-filter :preset-filter nil) @@ -3629,13 +3693,14 @@ FILTER-ALIST is an alist of filters we need to apply when (or org-agenda-multi (org-agenda-fit-window-to-buffer)) (throw 'exit "Sticky Agenda buffer, use `r' to refresh")) (setq org-todo-keywords-for-agenda nil) - (setq org-drawers-for-agenda nil) (put 'org-agenda-tag-filter :preset-filter org-agenda-tag-filter-preset) (put 'org-agenda-category-filter :preset-filter org-agenda-category-filter-preset) (put 'org-agenda-regexp-filter :preset-filter org-agenda-regexp-filter-preset) + (put 'org-agenda-effort-filter :preset-filter + org-agenda-effort-filter-preset) (if org-agenda-multi (progn (setq buffer-read-only nil) @@ -3649,7 +3714,6 @@ FILTER-ALIST is an alist of filters we need to apply when "\n")) (narrow-to-region (point) (point-max))) (setq org-done-keywords-for-agenda nil) - ;; Setting any org variables that are in org-agenda-local-vars ;; list need to be done after the prepare call (org-agenda-prepare-window @@ -3666,11 +3730,10 @@ FILTER-ALIST is an alist of filters we need to apply when (org-uniquify org-todo-keywords-for-agenda)) (setq org-done-keywords-for-agenda (org-uniquify org-done-keywords-for-agenda)) - (setq org-drawers-for-agenda (org-uniquify org-drawers-for-agenda)) (setq org-agenda-last-prefix-arg current-prefix-arg) (setq org-agenda-this-buffer-name org-agenda-buffer-name) (and name (not org-agenda-name) - (org-set-local 'org-agenda-name name))) + (setq-local org-agenda-name name))) (setq buffer-read-only nil)))) (defvar org-agenda-overriding-columns-format) ; From org-colview.el @@ -3681,11 +3744,7 @@ FILTER-ALIST is an alist of filters we need to apply when (let ((inhibit-read-only t)) (goto-char (point-min)) (save-excursion - (while (org-activate-bracket-links (point-max)) - (add-text-properties (match-beginning 0) (match-end 0) - '(face org-link)))) - (save-excursion - (while (org-activate-plain-links (point-max)) + (while (org-activate-links (point-max)) (add-text-properties (match-beginning 0) (match-end 0) '(face org-link)))) (unless (eq org-agenda-remove-tags t) @@ -3694,8 +3753,8 @@ FILTER-ALIST is an alist of filters we need to apply when (remove-text-properties (point-min) (point-max) '(face nil))) (if (and (boundp 'org-agenda-overriding-columns-format) org-agenda-overriding-columns-format) - (org-set-local 'org-agenda-overriding-columns-format - org-agenda-overriding-columns-format)) + (setq-local org-agenda-overriding-columns-format + org-agenda-overriding-columns-format)) (if (and (boundp 'org-agenda-view-columns-initially) org-agenda-view-columns-initially) (org-agenda-columns)) @@ -3733,10 +3792,10 @@ FILTER-ALIST is an alist of filters we need to apply when (org-agenda-filter-top-headline-apply org-agenda-top-headline-filter)) (when org-agenda-tag-filter - (org-agenda-filter-apply org-agenda-tag-filter 'tag)) + (org-agenda-filter-apply org-agenda-tag-filter 'tag t)) (when (get 'org-agenda-tag-filter :preset-filter) (org-agenda-filter-apply - (get 'org-agenda-tag-filter :preset-filter) 'tag)) + (get 'org-agenda-tag-filter :preset-filter) 'tag t)) (when org-agenda-category-filter (org-agenda-filter-apply org-agenda-category-filter 'category)) (when (get 'org-agenda-category-filter :preset-filter) @@ -3747,13 +3806,18 @@ FILTER-ALIST is an alist of filters we need to apply when (when (get 'org-agenda-regexp-filter :preset-filter) (org-agenda-filter-apply (get 'org-agenda-regexp-filter :preset-filter) 'regexp)) - (org-add-hook 'kill-buffer-hook 'org-agenda-reset-markers 'append 'local))))) + (when org-agenda-effort-filter + (org-agenda-filter-apply org-agenda-effort-filter 'effort)) + (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))))) (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') - (when org-clock-current-task + (when (bound-and-true-p org-clock-current-task) (save-restriction (widen) (org-agenda-unmark-clocking-task) @@ -3782,7 +3846,7 @@ FILTER-ALIST is an alist of filters we need to apply when "Make highest priority lines bold, and lowest italic." (interactive) (mapc (lambda (o) (if (eq (overlay-get o 'org-type) 'org-priority) - (delete-overlay o))) + (delete-overlay o))) (overlays-in (point-min) (point-max))) (save-excursion (let (b e p ov h l) @@ -3800,16 +3864,17 @@ FILTER-ALIST is an alist of filters we need to apply when ov (make-overlay b e)) (overlay-put ov 'face - (cons (cond ((org-face-from-face-or-color - 'priority nil - (cdr (assoc p org-priority-faces)))) - ((and (listp org-agenda-fontify-priorities) - (org-face-from-face-or-color - 'priority nil - (cdr (assoc p org-agenda-fontify-priorities))))) - ((equal p l) 'italic) - ((equal p h) 'bold)) - 'org-priority)) + (let ((special-face + (cond ((org-face-from-face-or-color + 'priority 'org-priority + (cdr (assoc p org-priority-faces)))) + ((and (listp org-agenda-fontify-priorities) + (org-face-from-face-or-color + 'priority 'org-priority + (cdr (assoc p org-agenda-fontify-priorities))))) + ((equal p l) 'italic) + ((equal p h) 'bold)))) + (if special-face (list special-face 'org-priority) 'org-priority))) (overlay-put ov 'org-type 'org-priority))))) (defvar org-depend-tag-blocked) @@ -3819,41 +3884,59 @@ FILTER-ALIST is an alist of filters we need to apply when When INVISIBLE is non-nil, hide currently blocked TODO instead of dimming them." (interactive "P") - (when (org-called-interactively-p 'interactive) + (when (called-interactively-p 'interactive) (message "Dim or hide blocked tasks...")) - (mapc (lambda (o) (if (eq (overlay-get o 'org-type) 'org-blocked-todo) - (delete-overlay o))) - (overlays-in (point-min) (point-max))) + (dolist (o (overlays-in (point-min) (point-max))) + (when (eq (overlay-get o 'org-type) 'org-blocked-todo) + (delete-overlay o))) (save-excursion - (let ((inhibit-read-only t) - (org-depend-tag-blocked nil) - (invis (or (not (null invisible)) - (eq org-agenda-dim-blocked-tasks 'invisible))) - org-blocked-by-checkboxes - invis1 b e p ov h l) + (let ((inhibit-read-only t)) (goto-char (point-min)) - (while (let ((pos (next-single-property-change (point) 'todo-state))) - (and pos (goto-char (1+ pos)))) - (setq org-blocked-by-checkboxes nil invis1 invis) - (let ((marker (org-get-at-bol 'org-hd-marker))) - (when (and marker - (with-current-buffer (marker-buffer marker) - (save-excursion (goto-char marker) - (org-entry-blocked-p)))) - (if org-blocked-by-checkboxes (setq invis1 nil)) - (setq b (if invis1 - (max (point-min) (1- (point-at-bol))) - (point-at-bol)) - e (point-at-eol) - ov (make-overlay b e)) - (if invis1 - (progn (overlay-put ov 'invisible t) - (overlay-put ov 'intangible t)) - (overlay-put ov 'face 'org-agenda-dimmed-todo-face)) - (overlay-put ov 'org-type 'org-blocked-todo)))))) - (when (org-called-interactively-p 'interactive) + (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)) + (ov (make-overlay (if invisible + (line-end-position 0) + (line-beginning-position)) + (line-end-position)))) + (if invisible + (overlay-put ov 'invisible t) + (overlay-put ov 'face 'org-agenda-dimmed-todo-face)) + (overlay-put ov 'org-type 'org-blocked-todo)) + (forward-line)))) + (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 +`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 +to t." + (when (get-text-property 0 'todo-state entry) + (let ((entry-marker (get-text-property 0 'org-hd-marker entry)) + (org-blocked-by-checkboxes nil) + ;; Necessary so that `org-entry-blocked-p' does not change + ;; the buffer. + (org-depend-tag-blocked nil)) + (when entry-marker + (let ((blocked + (with-current-buffer (marker-buffer entry-marker) + (save-excursion + (goto-char entry-marker) + (org-entry-blocked-p))))) + (when blocked + (let ((really-invisible + (and (not org-blocked-by-checkboxes) + (eq org-agenda-dim-blocked-tasks 'invisible)))) + (put-text-property + 0 (length entry) 'org-todo-blocked + (if really-invisible 'invisible t) + entry))))))) + entry) + (defvar org-agenda-skip-function nil "Function to be called at each match during agenda construction. If this function returns nil, the current match should not be skipped. @@ -3908,9 +3991,9 @@ functions do." (defun org-agenda-new-marker (&optional pos) "Return a new agenda marker. -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))))) +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." + (let ((m (copy-marker (or pos (point)) t))) (setq org-agenda-last-marker-time (float-time)) (if org-agenda-buffer (with-current-buffer org-agenda-buffer @@ -3972,156 +4055,14 @@ This check for agenda markers in all agenda buffers currently active." (defun org-agenda-get-day-face (date) "Return the face DATE should be displayed with." - (or (and (functionp org-agenda-day-face-function) - (funcall org-agenda-day-face-function date)) - (cond ((org-agenda-todayp date) - 'org-agenda-date-today) - ((member (calendar-day-of-week date) org-agenda-weekend-days) - 'org-agenda-date-weekend) - (t 'org-agenda-date)))) - -;;; Agenda timeline - -(defvar org-agenda-only-exact-dates nil) ; dynamically scoped -(defvar org-agenda-show-log-scoped) ;; dynamically scope in `org-timeline' or `org-agenda-list' - -(defun org-timeline (&optional dotodo) - "Show a time-sorted view of the entries in the current org file. -Only entries with a time stamp of today or later will be listed. With -\\[universal-argument] prefix, all unfinished TODO items will also be shown, -under the current date. -If the buffer contains an active region, only check the region for -dates." - (interactive "P") - (let* ((dopast t) - (org-agenda-show-log-scoped org-agenda-show-log) - (org-agenda-show-log org-agenda-show-log-scoped) - (entry (buffer-file-name (or (buffer-base-buffer (current-buffer)) - (current-buffer)))) - (date (calendar-current-date)) - (beg (if (org-region-active-p) (region-beginning) (point-min))) - (end (if (org-region-active-p) (region-end) (point-max))) - (day-numbers (org-get-all-dates - beg end 'no-ranges - t org-agenda-show-log-scoped ; always include today - org-timeline-show-empty-dates)) - (org-deadline-warning-days 0) - (org-agenda-only-exact-dates t) - (today (org-today)) - (past t) - args - s e rtn d emptyp) - (setq org-agenda-redo-command - (list 'let - (list (list 'org-agenda-show-log 'org-agenda-show-log)) - (list 'org-switch-to-buffer-other-window (current-buffer)) - (list 'org-timeline (list 'quote dotodo)))) - (put 'org-agenda-redo-command 'org-lprops nil) - (if (not dopast) - ;; Remove past dates from the list of dates. - (setq day-numbers (delq nil (mapcar (lambda(x) - (if (>= x today) x nil)) - day-numbers)))) - (org-agenda-prepare (concat "Timeline " (file-name-nondirectory entry))) - (org-compile-prefix-format 'timeline) - (org-set-sorting-strategy 'timeline) - (if org-agenda-show-log-scoped (push :closed args)) - (push :timestamp args) - (push :deadline args) - (push :scheduled args) - (push :sexp args) - (if dotodo (push :todo args)) - (insert "Timeline of file " entry "\n") - (add-text-properties (point-min) (point) - (list 'face 'org-agenda-structure)) - (org-agenda-mark-header-line (point-min)) - (while (setq d (pop day-numbers)) - (if (and (listp d) (eq (car d) :omitted)) - (progn - (setq s (point)) - (insert (format "\n[... %d empty days omitted]\n\n" (cdr d))) - (put-text-property s (1- (point)) 'face 'org-agenda-structure)) - (if (listp d) (setq d (car d) emptyp t) (setq emptyp nil)) - (if (and (>= d today) - dopast - past) - (progn - (setq past nil) - (insert (make-string 79 ?-) "\n"))) - (setq date (calendar-gregorian-from-absolute d)) - (setq s (point)) - (setq rtn (and (not emptyp) - (apply 'org-agenda-get-day-entries entry - date args))) - (if (or rtn (equal d today) org-timeline-show-empty-dates) - (progn - (insert - (if (stringp org-agenda-format-date) - (format-time-string org-agenda-format-date - (org-time-from-absolute date)) - (funcall org-agenda-format-date date)) - "\n") - (put-text-property s (1- (point)) 'face - (org-agenda-get-day-face date)) - (put-text-property s (1- (point)) 'org-date-line t) - (put-text-property s (1- (point)) 'org-agenda-date-header t) - (if (equal d today) - (put-text-property s (1- (point)) 'org-today t)) - (and rtn (insert (org-agenda-finalize-entries rtn 'timeline) "\n")) - (put-text-property s (1- (point)) 'day d))))) - (goto-char (or (text-property-any (point-min) (point-max) 'org-today t) - (point-min))) - (add-text-properties - (point-min) (point-max) - `(org-agenda-type timeline org-redo-cmd ,org-agenda-redo-command)) - (org-agenda-finalize) - (setq buffer-read-only t))) - -(defun org-get-all-dates (beg end &optional no-ranges force-today inactive empty pre-re) - "Return a list of all relevant day numbers from BEG to END buffer positions. -If NO-RANGES is non-nil, include only the start and end dates of a range, -not every single day in the range. If FORCE-TODAY is non-nil, make -sure that TODAY is included in the list. If INACTIVE is non-nil, also -inactive time stamps (those in square brackets) are included. -When EMPTY is non-nil, also include days without any entries." - (let ((re (concat - (if pre-re pre-re "") - (if inactive org-ts-regexp-both org-ts-regexp))) - dates dates1 date day day1 day2 ts1 ts2 pos) - (if force-today - (setq dates (list (org-today)))) - (save-excursion - (goto-char beg) - (while (re-search-forward re end t) - (setq day (time-to-days (org-time-string-to-time - (substring (match-string 1) 0 10) - (current-buffer) (match-beginning 0)))) - (or (memq day dates) (push day dates))) - (unless no-ranges - (goto-char beg) - (while (re-search-forward org-tr-regexp end t) - (setq pos (match-beginning 0)) - (setq ts1 (substring (match-string 1) 0 10) - ts2 (substring (match-string 2) 0 10) - day1 (time-to-days (org-time-string-to-time - ts1 (current-buffer) pos)) - day2 (time-to-days (org-time-string-to-time - ts2 (current-buffer) pos))) - (while (< (setq day1 (1+ day1)) day2) - (or (memq day1 dates) (push day1 dates))))) - (setq dates (sort dates '<)) - (when empty - (while (setq day (pop dates)) - (setq day2 (car dates)) - (push day dates1) - (when (and day2 empty) - (if (or (eq empty t) - (and (numberp empty) (<= (- day2 day) empty))) - (while (< (setq day (1+ day)) day2) - (push (list day) dates1)) - (push (cons :omitted (- day2 day)) dates1)))) - (setq dates (nreverse dates1))) - dates))) + (cond ((and (functionp org-agenda-day-face-function) + (funcall org-agenda-day-face-function date))) + ((org-agenda-today-p date) 'org-agenda-date-today) + ((memq (calendar-day-of-week date) org-agenda-weekend-days) + 'org-agenda-date-weekend) + (t 'org-agenda-date))) + +(defvar org-agenda-show-log-scoped) ;;; Agenda Daily/Weekly @@ -4160,13 +4101,14 @@ items if they have an hour specification like [h]h:mm." (catch 'exit (setq org-agenda-buffer-name (or org-agenda-buffer-tmp-name + (and org-agenda-doing-sticky-redo org-agenda-buffer-name) (if 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-buffer-name)) + "*Org Agenda*")) (org-agenda-prepare "Day/Week") (setq start-day (or start-day org-agenda-start-day)) (if (stringp start-day) @@ -4174,8 +4116,7 @@ items if they have an hour specification like [h]h:mm." (setq start-day (time-to-days (org-read-date nil t start-day)))) (org-compile-prefix-format 'agenda) (org-set-sorting-strategy 'agenda) - (let* ((span (org-agenda-ndays-to-span - (or span org-agenda-ndays org-agenda-span))) + (let* ((span (org-agenda-ndays-to-span (or span org-agenda-span))) (today (org-today)) (sd (or start-day today)) (ndays (org-agenda-span-to-ndays span sd)) @@ -4205,9 +4146,9 @@ items if they have an hour specification like [h]h:mm." (setq day-numbers (nreverse day-numbers)) (setq clocktable-start (car day-numbers) clocktable-end (1+ (or (org-last day-numbers) 0))) - (org-set-local 'org-starting-day (car day-numbers)) - (org-set-local 'org-arg-loc arg) - (org-set-local 'org-agenda-current-span (org-agenda-ndays-to-span span)) + (setq-local org-starting-day (car day-numbers)) + (setq-local org-arg-loc arg) + (setq-local org-agenda-current-span (org-agenda-ndays-to-span span)) (unless org-agenda-compact-blocks (let* ((d1 (car day-numbers)) (d2 (org-last day-numbers)) @@ -4353,10 +4294,10 @@ START-DAY is an absolute time value." ((eq span 'fortnight) 14) ((eq span 'month) (let ((date (calendar-gregorian-from-absolute start-day))) - (calendar-last-day-of-month (car date) (caddr date)))) + (calendar-last-day-of-month (car date) (cl-caddr date)))) ((eq span 'year) (let ((date (calendar-gregorian-from-absolute start-day))) - (if (calendar-leap-year-p (caddr date)) 366 365))))) + (if (calendar-leap-year-p (cl-caddr date)) 366 365))))) (defun org-agenda-span-name (span) "Return a SPAN name." @@ -4371,7 +4312,7 @@ START-DAY is an absolute time value." (defvar org-agenda-search-history nil) (defvar org-search-syntax-table nil - "Special syntax table for org-mode search. + "Special syntax table for Org search. In this table, we have single quotes not as word constituents, to that when \"+Ameli\" is searched as a work, it will also match \"Ameli's\"") @@ -4427,8 +4368,9 @@ as a whole, to include whitespace. with a colon, this will mean that the (non-regexp) snippets of the Boolean search must match as full words. -This command searches the agenda files, and in addition the files listed -in `org-agenda-text-search-extra-files'." +This command searches the agenda files, and in addition the files +listed in `org-agenda-text-search-extra-files' unless a restriction lock +is active." (interactive "P") (if org-agenda-overriding-arguments (setq todo-only (car org-agenda-overriding-arguments) @@ -4444,7 +4386,7 @@ in `org-agenda-text-search-extra-files'." (full-words org-agenda-search-view-force-full-words) (org-agenda-text-search-extra-files org-agenda-text-search-extra-files) regexp rtn rtnall files file pos inherited-tags - marker category category-pos level tags c neg re boolean + marker category level tags c neg re boolean ee txt beg end words regexps+ regexps- hdl-only buffer beg1 str) (unless (and (not edit-at) (stringp string) @@ -4484,7 +4426,7 @@ in `org-agenda-text-search-extra-files'." (if (or org-agenda-search-view-always-boolean (member (string-to-char words) '(?- ?+ ?\{))) (setq boolean t)) - (setq words (org-split-string words)) + (setq words (split-string words)) (let (www w) (while (setq w (pop words)) (while (and (string-match "\\\\\\'" w) words) @@ -4538,10 +4480,20 @@ in `org-agenda-text-search-extra-files'." (if hdl-only (setq regexp (concat org-outline-regexp-bol ".*?" regexp)))) (setq files (org-agenda-files nil 'ifmode)) - (when (eq (car org-agenda-text-search-extra-files) 'agenda-archives) - (pop org-agenda-text-search-extra-files) - (setq files (org-add-archive-files files))) - (setq files (append files org-agenda-text-search-extra-files) + ;; Add `org-agenda-text-search-extra-files' unless there is some + ;; restriction. + (unless (get 'org-agenda-files 'org-restrict) + (when (eq (car org-agenda-text-search-extra-files) 'agenda-archives) + (pop org-agenda-text-search-extra-files) + (setq files (org-add-archive-files files)))) + ;; Uniquify files. However, let `org-check-agenda-file' handle + ;; non-existent ones. + (setq files (cl-remove-duplicates + (append files org-agenda-text-search-extra-files) + :test (lambda (a b) + (and (file-exists-p a) + (file-exists-p b) + (file-equal-p a b)))) rtnall nil) (while (setq file (pop files)) (setq ee nil) @@ -4576,7 +4528,7 @@ in `org-agenda-text-search-extra-files'." (> (org-reduced-level (org-outline-level)) org-agenda-search-view-max-outline-level) (forward-line -1) - (outline-back-to-heading t))) + (org-back-to-heading t))) (skip-chars-forward "* ") (setq beg (point-at-bol) beg1 (point) @@ -4596,12 +4548,12 @@ in `org-agenda-text-search-extra-files'." (point-at-bol) (if hdl-only (point-at-eol) end))) (mapc (lambda (wr) (when (string-match wr str) - (goto-char (1- end)) - (throw :skip t))) + (goto-char (1- end)) + (throw :skip t))) regexps-) (mapc (lambda (wr) (unless (string-match wr str) - (goto-char (1- end)) - (throw :skip t))) + (goto-char (1- end)) + (throw :skip t))) (if todo-only (cons (concat "^\\*+[ \t]+" org-not-done-regexp) @@ -4611,7 +4563,6 @@ in `org-agenda-text-search-extra-files'." (setq marker (org-agenda-new-marker (point)) category (org-get-category) level (make-string (org-reduced-level (org-outline-level)) ? ) - category-pos (get-text-property (point) 'org-category-position) inherited-tags (or (eq org-agenda-show-inherited-tags 'always) (and (listp org-agenda-show-inherited-tags) @@ -4630,8 +4581,7 @@ in `org-agenda-text-search-extra-files'." 'org-todo-regexp org-todo-regexp 'level level 'org-complex-heading-regexp org-complex-heading-regexp - 'priority 1000 'org-category category - 'org-category-position category-pos + 'priority 1000 'type "search") (push txt ee) (goto-char (1- end)))))))))) @@ -4648,8 +4598,12 @@ in `org-agenda-text-search-extra-files'." (add-text-properties pos (1- (point)) (list 'face 'org-warning)) (setq pos (point)) (unless org-agenda-multi - (insert (substitute-command-keys - "Press `[', `]' to add/sub word, `{', `}' to add/sub regexp, `C-u r' to edit\n")) + (insert (substitute-command-keys "\ +Press `\\[org-agenda-manipulate-query-add]', \ +`\\[org-agenda-manipulate-query-subtract]' to add/sub word, \ +`\\[org-agenda-manipulate-query-add-re]', \ +`\\[org-agenda-manipulate-query-subtract-re]' to add/sub regexp, \ +`\\[universal-argument] \\[org-agenda-redo]' to edit\n")) (add-text-properties pos (1- (point)) (list 'face 'org-agenda-structure)))) (org-agenda-mark-header-line (point-min)) @@ -4686,7 +4640,7 @@ in `org-agenda-text-search-extra-files'." (defun org-todo-list (&optional arg) "Show all (not done) TODO entries from all agenda file in a single list. The prefix arg can be used to select a specific TODO keyword and limit -the list to these. When using \\[universal-argument], you will be prompted +the list to these. When using `\\[universal-argument]', you will be prompted for a keyword. A numeric prefix directly selects the Nth keyword in `org-todo-keywords-1'." (interactive "P") @@ -4704,8 +4658,8 @@ for a keyword. A numeric prefix directly selects the Nth keyword in rtn rtnall files file pos) (when (equal arg '(4)) (setq org-select-this-todo-keyword - (org-icompleting-read "Keyword (or KWD1|K2D2|...): " - (mapcar 'list kwds) nil nil))) + (completing-read "Keyword (or KWD1|K2D2|...): " + (mapcar #'list kwds) nil nil))) (and (equal 0 arg) (setq org-select-this-todo-keyword nil)) (catch 'exit (if org-agenda-sticky @@ -4743,7 +4697,8 @@ for a keyword. A numeric prefix directly selects the Nth keyword in org-select-this-todo-keyword)) (setq pos (point)) (unless org-agenda-multi - (insert (substitute-command-keys "Available with `N r': (0)[ALL]")) + (insert (substitute-command-keys "Available with \ +`N \\[org-agenda-redo]': (0)[ALL]")) (let ((n 0) s) (mapc (lambda (x) (setq s (format "(%d)%s" (setq n (1+ n)) x)) @@ -4779,6 +4734,7 @@ The prefix arg TODO-ONLY limits the search to TODO entries." (let* ((org-tags-match-list-sublevels org-tags-match-list-sublevels) (completion-ignore-case t) + (org--matcher-tags-todo-only todo-only) rtn rtnall files file pos matcher buffer) (when (and (stringp match) (not (string-match "\\S-" match))) @@ -4794,13 +4750,15 @@ The prefix arg TODO-ONLY limits the search to TODO entries." ;; expanding tags within `org-make-tags-matcher' (org-agenda-prepare (concat "TAGS " match)) (setq matcher (org-make-tags-matcher match) - match (car matcher) matcher (cdr matcher)) + match (car matcher) + matcher (cdr matcher)) (org-compile-prefix-format 'tags) (org-set-sorting-strategy 'tags) (setq org-agenda-query-string match) (setq org-agenda-redo-command - (list 'org-tags-view `(quote ,todo-only) - (list 'if 'current-prefix-arg nil `(quote ,org-agenda-query-string)))) + (list 'org-tags-view + `(quote ,org--matcher-tags-todo-only) + `(if current-prefix-arg nil ,org-agenda-query-string))) (setq files (org-agenda-files nil 'ifmode) rtnall nil) (while (setq file (pop files)) @@ -4823,7 +4781,9 @@ The prefix arg TODO-ONLY limits the search to TODO entries." (narrow-to-region org-agenda-restrict-begin org-agenda-restrict-end) (widen)) - (setq rtn (org-scan-tags 'agenda matcher todo-only)) + (setq rtn (org-scan-tags 'agenda + matcher + org--matcher-tags-todo-only)) (setq rtnall (append rtnall rtn)))))))) (if org-agenda-overriding-header (insert (org-add-props (copy-sequence org-agenda-overriding-header) @@ -4839,18 +4799,21 @@ The prefix arg TODO-ONLY limits the search to TODO entries." (setq pos (point)) (unless org-agenda-multi (insert (substitute-command-keys - "Press `C-u r' to search again with new search string\n"))) - (add-text-properties pos (1- (point)) (list 'face 'org-agenda-structure))) + "Press `\\[universal-argument] \\[org-agenda-redo]' \ +to search again with new search string\n"))) + (add-text-properties pos (1- (point)) + (list 'face 'org-agenda-structure))) (org-agenda-mark-header-line (point-min)) (when rtnall (insert (org-agenda-finalize-entries rtnall 'tags) "\n")) (goto-char (point-min)) (or org-agenda-multi (org-agenda-fit-window-to-buffer)) - (add-text-properties (point-min) (point-max) - `(org-agenda-type tags - org-last-args (,todo-only ,match) - org-redo-cmd ,org-agenda-redo-command - org-series-cmd ,org-cmd)) + (add-text-properties + (point-min) (point-max) + `(org-agenda-type tags + org-last-args (,org--matcher-tags-todo-only ,match) + org-redo-cmd ,org-agenda-redo-command + org-series-cmd ,org-cmd)) (org-agenda-finalize) (setq buffer-read-only t)))) @@ -4866,43 +4829,6 @@ used by user-defined selections using `org-agenda-skip-function'.") This variable should not be set directly, but custom commands can bind it in the options section.") -(defun org-agenda-skip-entry-when-regexp-matches () - "Check if the current entry contains match for `org-agenda-skip-regexp'. -If yes, it returns the end position of this entry, causing agenda commands -to skip the entry but continuing the search in the subtree. This is a -function that can be put into `org-agenda-skip-function' for the duration -of a command." - (let ((end (save-excursion (org-end-of-subtree t))) - skip) - (save-excursion - (setq skip (re-search-forward org-agenda-skip-regexp end t))) - (and skip end))) - -(defun org-agenda-skip-subtree-when-regexp-matches () - "Check if the current subtree contains match for `org-agenda-skip-regexp'. -If yes, it returns the end position of this tree, causing agenda commands -to skip this subtree. This is a function that can be put into -`org-agenda-skip-function' for the duration of a command." - (let ((end (save-excursion (org-end-of-subtree t))) - skip) - (save-excursion - (setq skip (re-search-forward org-agenda-skip-regexp end t))) - (and skip end))) - -(defun org-agenda-skip-entry-when-regexp-matches-in-subtree () - "Check if the current subtree contains match for `org-agenda-skip-regexp'. -If yes, it returns the end position of the current entry (NOT the tree), -causing agenda commands to skip the entry but continuing the search in -the subtree. This is a function that can be put into -`org-agenda-skip-function' for the duration of a command. An important -use of this function is for the stuck project list." - (let ((end (save-excursion (org-end-of-subtree t))) - (entry-end (save-excursion (outline-next-heading) (1- (point)))) - skip) - (save-excursion - (setq skip (re-search-forward org-agenda-skip-regexp end t))) - (and skip entry-end))) - (defun org-agenda-skip-entry-if (&rest conditions) "Skip entry if any of CONDITIONS is true. See `org-agenda-skip-if' for details." @@ -4952,39 +4878,41 @@ keywords. Possible classes are: `todo', `done', `any'. If any of these conditions is met, this function returns the end point of the entity, causing the search to continue from there. This is a function that can be put into `org-agenda-skip-function' for the duration of a command." - (let (beg end m) - (org-back-to-heading t) - (setq beg (point) - end (if subtree - (progn (org-end-of-subtree t) (point)) - (progn (outline-next-heading) (1- (point))))) - (goto-char beg) + (org-back-to-heading t) + (let* ((beg (point)) + (end (if subtree (save-excursion (org-end-of-subtree t) (point)) + (org-entry-end-position))) + (planning-end (if subtree end (line-end-position 2))) + m) (and - (or - (and (memq 'scheduled conditions) - (re-search-forward org-scheduled-time-regexp end t)) - (and (memq 'notscheduled conditions) - (not (re-search-forward org-scheduled-time-regexp end t))) - (and (memq 'deadline conditions) - (re-search-forward org-deadline-time-regexp end t)) - (and (memq 'notdeadline conditions) - (not (re-search-forward org-deadline-time-regexp end t))) - (and (memq 'timestamp conditions) - (re-search-forward org-ts-regexp end t)) - (and (memq 'nottimestamp conditions) - (not (re-search-forward org-ts-regexp end t))) - (and (setq m (memq 'regexp conditions)) - (stringp (nth 1 m)) - (re-search-forward (nth 1 m) end t)) - (and (setq m (memq 'notregexp conditions)) - (stringp (nth 1 m)) - (not (re-search-forward (nth 1 m) end t))) - (and (or - (setq m (memq 'nottodo conditions)) - (setq m (memq 'todo-unblocked conditions)) - (setq m (memq 'nottodo-unblocked conditions)) - (setq m (memq 'todo conditions))) - (org-agenda-skip-if-todo m end))) + (or (and (memq 'scheduled conditions) + (re-search-forward org-scheduled-time-regexp planning-end t)) + (and (memq 'notscheduled conditions) + (not + (save-excursion + (re-search-forward org-scheduled-time-regexp planning-end t)))) + (and (memq 'deadline conditions) + (re-search-forward org-deadline-time-regexp planning-end t)) + (and (memq 'notdeadline conditions) + (not + (save-excursion + (re-search-forward org-deadline-time-regexp planning-end t)))) + (and (memq 'timestamp conditions) + (re-search-forward org-ts-regexp end t)) + (and (memq 'nottimestamp conditions) + (not (save-excursion (re-search-forward org-ts-regexp end t)))) + (and (setq m (memq 'regexp conditions)) + (stringp (nth 1 m)) + (re-search-forward (nth 1 m) end t)) + (and (setq m (memq 'notregexp conditions)) + (stringp (nth 1 m)) + (not (save-excursion (re-search-forward (nth 1 m) end t)))) + (and (or + (setq m (memq 'nottodo conditions)) + (setq m (memq 'todo-unblocked conditions)) + (setq m (memq 'nottodo-unblocked conditions)) + (setq m (memq 'todo conditions))) + (org-agenda-skip-if-todo m end))) end))) (defun org-agenda-skip-if-todo (args end) @@ -4993,43 +4921,36 @@ ARGS is a list with first element either `todo', `nottodo', `todo-unblocked' or `nottodo-unblocked'. The remainder is either a list of TODO keywords, or a state symbol `todo' or `done' or `any'." - (let ((kw (car args)) - (arg (cadr args)) - todo-wds todo-re) - (setq todo-wds - (org-uniquify - (cond - ((listp arg) ;; list of keywords - (if (member "*" arg) - (mapcar 'substring-no-properties org-todo-keywords-1) - arg)) - ((symbolp arg) ;; keyword class name - (cond - ((eq arg 'todo) - (org-delete-all org-done-keywords - (mapcar 'substring-no-properties - org-todo-keywords-1))) - ((eq arg 'done) org-done-keywords) - ((eq arg 'any) - (mapcar 'substring-no-properties org-todo-keywords-1))))))) - (setq todo-re - (concat "^\\*+[ \t]+\\<\\(" - (mapconcat 'identity todo-wds "\\|") - "\\)\\>")) - (cond - ((eq kw 'todo) (re-search-forward todo-re end t)) - ((eq kw 'nottodo) (not (re-search-forward todo-re end t))) - ((eq kw 'todo-unblocked) - (catch 'unblocked - (while (re-search-forward todo-re end t) - (or (org-entry-blocked-p) (throw 'unblocked t))) - nil)) - ((eq kw 'nottodo-unblocked) - (catch 'unblocked - (while (re-search-forward todo-re end t) - (or (org-entry-blocked-p) (throw 'unblocked nil))) - t)) - ))) + (let ((todo-re + (concat "^\\*+[ \t]+" + (regexp-opt + (pcase args + (`(,_ todo) + (org-delete-all org-done-keywords + (copy-sequence org-todo-keywords-1))) + (`(,_ done) org-done-keywords) + (`(,_ any) org-todo-keywords-1) + (`(,_ ,(pred atom)) + (error "Invalid TODO class or type: %S" args)) + (`(,_ ,(pred (member "*"))) org-todo-keywords-1) + (`(,_ ,todo-list) todo-list)) + 'words)))) + (pcase args + (`(todo . ,_) + (let (case-fold-search) (re-search-forward todo-re end t))) + (`(nottodo . ,_) + (not (let (case-fold-search) (re-search-forward todo-re end t)))) + (`(todo-unblocked . ,_) + (catch :unblocked + (while (let (case-fold-search) (re-search-forward todo-re end t)) + (when (org-entry-blocked-p) (throw :unblocked t))) + nil)) + (`(nottodo-unblocked . ,_) + (catch :unblocked + (while (let (case-fold-search) (re-search-forward todo-re end t)) + (when (org-entry-blocked-p) (throw :unblocked nil))) + t)) + (`(,type . ,_) (error "Unknown TODO skip type: %S" type))))) ;;;###autoload (defun org-agenda-list-stuck-projects (&rest ignore) @@ -5038,50 +4959,53 @@ Stuck projects are project that have no next actions. For the definitions of what a project is and how to check if it stuck, customize the variable `org-stuck-projects'." (interactive) - (let* ((org-agenda-skip-function - 'org-agenda-skip-entry-when-regexp-matches-in-subtree) - ;; We could have used org-agenda-skip-if here. - (org-agenda-overriding-header + (let* ((org-agenda-overriding-header (or org-agenda-overriding-header "List of stuck projects: ")) (matcher (nth 0 org-stuck-projects)) (todo (nth 1 org-stuck-projects)) - (todo-wds (if (member "*" todo) - (progn - (org-agenda-prepare-buffers (org-agenda-files - nil 'ifmode)) - (org-delete-all - org-done-keywords-for-agenda - (copy-sequence org-todo-keywords-for-agenda))) - todo)) - (todo-re (concat "^\\*+[ \t]+\\(" - (mapconcat 'identity todo-wds "\\|") - "\\)\\>")) (tags (nth 2 org-stuck-projects)) - (tags-re (if (member "*" tags) - (concat org-outline-regexp-bol - (org-re ".*:[[:alnum:]_@#%]+:[ \t]*$")) - (if tags - (concat org-outline-regexp-bol - ".*:\\(" - (mapconcat 'identity tags "\\|") - (org-re "\\):[[:alnum:]_@#%:]*[ \t]*$"))))) - (gen-re (nth 3 org-stuck-projects)) - (re-list - (delq nil - (list - (if todo todo-re) - (if tags tags-re) - (and gen-re (stringp gen-re) (string-match "\\S-" gen-re) - gen-re))))) - (setq org-agenda-skip-regexp - (if re-list - (mapconcat 'identity re-list "\\|") - (error "No information how to identify unstuck projects"))) + (gen-re (org-string-nw-p (nth 3 org-stuck-projects))) + (todo-wds + (if (not (member "*" todo)) todo + (org-agenda-prepare-buffers (org-agenda-files nil 'ifmode)) + (org-delete-all org-done-keywords-for-agenda + (copy-sequence org-todo-keywords-for-agenda)))) + (todo-re (and todo + (format "^\\*+[ \t]+\\(%s\\)\\>" + (mapconcat #'identity todo-wds "\\|")))) + (tags-re (cond ((null tags) nil) + ((member "*" tags) + (eval-when-compile + (concat org-outline-regexp-bol + ".*:[[:alnum:]_@#%]+:[ \t]*$"))) + (tags (concat org-outline-regexp-bol + ".*:\\(" + (mapconcat #'identity tags "\\|") + "\\):[[:alnum:]_@#%:]*[ \t]*$")) + (t nil))) + (re-list (delq nil (list todo-re tags-re gen-re))) + (skip-re + (if (null re-list) + (error "Missing information to identify unstuck projects") + (mapconcat #'identity re-list "\\|"))) + (org-agenda-skip-function + ;; Skip entry if `org-agenda-skip-regexp' matches anywhere + ;; in the subtree. + `(lambda () + (and (save-excursion + (let ((case-fold-search nil)) + (re-search-forward + ,skip-re (save-excursion (org-end-of-subtree t)) t))) + (progn (outline-next-heading) (point)))))) (org-tags-view nil matcher) (setq org-agenda-buffer-name (buffer-name)) (with-current-buffer org-agenda-buffer-name (setq org-agenda-redo-command - `(org-agenda-list-stuck-projects ,current-prefix-arg))))) + `(org-agenda-list-stuck-projects ,current-prefix-arg)) + (let ((inhibit-read-only t)) + (add-text-properties + (point-min) (point-max) + `(org-redo-cmd ,org-agenda-redo-command)))))) ;;; Diary integration @@ -5159,7 +5083,7 @@ date. It also removes lines that contain only whitespace." (while (re-search-forward "^ +\n" nil t) (replace-match "")) (goto-char (point-min)) - (if (re-search-forward "^Org-mode dummy\n?" nil t) + (if (re-search-forward "^Org mode dummy\n?" nil t) (replace-match "")) (run-hooks 'org-agenda-cleanup-fancy-diary-hook)) @@ -5177,7 +5101,7 @@ date. It also removes lines that contain only whitespace." (setq string (org-modify-diary-entry-string string)))))) (defun org-modify-diary-entry-string (string) - "Add text properties to string, allowing org-mode to act on it." + "Add text properties to string, allowing Org to act on it." (org-add-props string nil 'mouse-face 'highlight 'help-echo (if buffer-file-name @@ -5193,9 +5117,9 @@ Needed to avoid empty dates which mess up holiday display." ;; Catch the error if dealing with the new add-to-diary-alist (when org-disable-agenda-to-diary (condition-case nil - (org-add-to-diary-list original-date "Org-mode dummy" "") + (org-add-to-diary-list original-date "Org mode dummy" "") (error - (org-add-to-diary-list original-date "Org-mode dummy" "" nil))))) + (org-add-to-diary-list original-date "Org mode dummy" "" nil))))) (defun org-add-to-diary-list (&rest args) (if (fboundp 'diary-add-to-list) @@ -5265,67 +5189,77 @@ function from a program - use `org-agenda-get-day-entries' instead." ;;; Agenda entry finders +(defun org-agenda--timestamp-to-absolute (&rest args) + "Call `org-time-string-to-absolute' with ARGS. +However, throw `:skip' whenever an error is raised." + (condition-case e + (apply #'org-time-string-to-absolute args) + (org-diary-sexp-no-match (throw :skip nil)) + (error + (message "%s; Skipping entry" (error-message-string e)) + (throw :skip nil)))) + (defun org-agenda-get-day-entries (file date &rest args) "Does the work for `org-diary' and `org-agenda'. FILE is the path to a file to be checked for entries. DATE is date like the one returned by `calendar-current-date'. ARGS are symbols indicating which kind of entries should be extracted. For details about these, see the documentation of `org-diary'." - (setq args (or args org-agenda-entry-types)) (let* ((org-startup-folded nil) (org-startup-align-all-tables nil) - (buffer (if (file-exists-p file) - (org-get-agenda-file-buffer file) - (error "No such file %s" file))) - arg results rtn deadline-results) + (buffer (if (file-exists-p file) (org-get-agenda-file-buffer file) + (error "No such file %s" file)))) (if (not buffer) - ;; If file does not exist, make sure an error message ends up in diary + ;; If file does not exist, signal it in diary nonetheless. (list (format "ORG-AGENDA-ERROR: No such org-file %s" file)) (with-current-buffer buffer (unless (derived-mode-p 'org-mode) (error "Agenda file %s is not in `org-mode'" file)) (setq org-agenda-buffer (or org-agenda-buffer buffer)) - (let ((case-fold-search nil)) - (save-excursion - (save-restriction - (if (eq buffer org-agenda-restrict) - (narrow-to-region org-agenda-restrict-begin - org-agenda-restrict-end) - (widen)) - ;; The way we repeatedly append to `results' makes it O(n^2) :-( - (while (setq arg (pop args)) - (cond - ((and (eq arg :todo) - (equal date (calendar-gregorian-from-absolute - (org-today)))) - (setq rtn (org-agenda-get-todos)) - (setq results (append results rtn))) - ((eq arg :timestamp) - (setq rtn (org-agenda-get-blocks)) - (setq results (append results rtn)) - (setq rtn (org-agenda-get-timestamps deadline-results)) - (setq results (append results rtn))) - ((eq arg :sexp) - (setq rtn (org-agenda-get-sexps)) - (setq results (append results rtn))) - ((eq arg :scheduled) - (setq rtn (org-agenda-get-scheduled deadline-results)) - (setq results (append results rtn))) - ((eq arg :scheduled*) - (setq rtn (org-agenda-get-scheduled deadline-results t)) - (setq results (append results rtn))) - ((eq arg :closed) - (setq rtn (org-agenda-get-progress)) - (setq results (append results rtn))) - ((eq arg :deadline) - (setq rtn (org-agenda-get-deadlines)) - (setq deadline-results (copy-sequence rtn)) - (setq results (append results rtn))) - ((eq arg :deadline*) - (setq rtn (org-agenda-get-deadlines t)) - (setq deadline-results (copy-sequence rtn)) - (setq results (append results rtn)))))))) - results)))) + (setf org-agenda-current-date date) + (save-excursion + (save-restriction + (if (eq buffer org-agenda-restrict) + (narrow-to-region org-agenda-restrict-begin + org-agenda-restrict-end) + (widen)) + ;; Rationalize ARGS. Also make sure `:deadline' comes + ;; first in order to populate DEADLINES before passing it. + ;; + ;; We use `delq' since `org-uniquify' duplicates ARGS, + ;; guarding us from modifying `org-agenda-entry-types'. + (setf args (org-uniquify (or args org-agenda-entry-types))) + (when (and (memq :scheduled args) (memq :scheduled* args)) + (setf args (delq :scheduled* args))) + (cond + ((memq :deadline args) + (setf args (cons :deadline + (delq :deadline (delq :deadline* args))))) + ((memq :deadline* args) + (setf args (cons :deadline* (delq :deadline* args))))) + ;; Collect list of headlines. Return them flattened. + (let ((case-fold-search nil) results deadlines) + (dolist (arg args (apply #'nconc (nreverse results))) + (pcase arg + ((and :todo (guard (org-agenda-today-p date))) + (push (org-agenda-get-todos) results)) + (:timestamp + (push (org-agenda-get-blocks) results) + (push (org-agenda-get-timestamps deadlines) results)) + (:sexp + (push (org-agenda-get-sexps) results)) + (:scheduled + (push (org-agenda-get-scheduled deadlines) results)) + (:scheduled* + (push (org-agenda-get-scheduled deadlines t) results)) + (:closed + (push (org-agenda-get-progress) results)) + (:deadline + (setf deadlines (org-agenda-get-deadlines)) + (push deadlines results)) + (:deadline* + (setf deadlines (org-agenda-get-deadlines t)) + (push deadlines results))))))))))) (defsubst org-em (x y list) "Is X or Y a member of LIST?" @@ -5334,6 +5268,40 @@ the documentation of `org-diary'." (defvar org-heading-keyword-regexp-format) ; defined in org.el (defvar org-agenda-sorting-strategy-selected nil) +(defun org-agenda-entry-get-agenda-timestamp (pom) + "Retrieve timestamp information for sorting agenda views. +Given a point or marker POM, returns a cons cell of the timestamp +and the timestamp type relevant for the sorting strategy in +`org-agenda-sorting-strategy-selected'." + (let (ts ts-date-type) + (save-match-data + (cond ((org-em 'scheduled-up 'scheduled-down + org-agenda-sorting-strategy-selected) + (setq ts (org-entry-get pom "SCHEDULED") + ts-date-type " scheduled")) + ((org-em 'deadline-up 'deadline-down + org-agenda-sorting-strategy-selected) + (setq ts (org-entry-get pom "DEADLINE") + ts-date-type " deadline")) + ((org-em 'ts-up 'ts-down + org-agenda-sorting-strategy-selected) + (setq ts (org-entry-get pom "TIMESTAMP") + ts-date-type " timestamp")) + ((org-em 'tsia-up 'tsia-down + org-agenda-sorting-strategy-selected) + (setq ts (org-entry-get pom "TIMESTAMP_IA") + ts-date-type " timestamp_ia")) + ((org-em 'timestamp-up 'timestamp-down + org-agenda-sorting-strategy-selected) + (setq ts (or (org-entry-get pom "SCHEDULED") + (org-entry-get pom "DEADLINE") + (org-entry-get pom "TIMESTAMP") + (org-entry-get pom "TIMESTAMP_IA")) + ts-date-type "")) + (t (setq ts-date-type ""))) + (cons (when ts (ignore-errors (org-time-string-to-absolute ts))) + ts-date-type)))) + (defun org-agenda-get-todos () "Return the TODO information for agenda display." (let* ((props (list 'face nil @@ -5345,6 +5313,7 @@ the documentation of `org-diary'." 'help-echo (format "mouse-2 or RET jump to org file %s" (abbreviate-file-name buffer-file-name)))) + (case-fold-search nil) (regexp (format org-heading-keyword-regexp-format (cond ((and org-select-this-todo-keyword @@ -5358,7 +5327,8 @@ the documentation of `org-diary'." "|") "\\|") "\\)")) (t org-not-done-regexp)))) - marker priority category category-pos level tags todo-state ts-date ts-date-type + marker priority category level tags todo-state + ts-date ts-date-type ts-date-pair ee txt beg end inherited-tags todo-state-end-pos) (goto-char (point-min)) (while (re-search-forward regexp nil t) @@ -5378,36 +5348,10 @@ the documentation of `org-diary'." (goto-char (match-beginning 2)) (setq marker (org-agenda-new-marker (match-beginning 0)) category (org-get-category) - ts-date (let (ts) - (save-match-data - (cond ((org-em 'scheduled-up 'scheduled-down - org-agenda-sorting-strategy-selected) - (setq ts (org-entry-get (point) "SCHEDULED") - ts-date-type " scheduled")) - ((org-em 'deadline-up 'deadline-down - org-agenda-sorting-strategy-selected) - (setq ts (org-entry-get (point) "DEADLINE") - ts-date-type " deadline")) - ((org-em 'ts-up 'ts-down - org-agenda-sorting-strategy-selected) - (setq ts (org-entry-get (point) "TIMESTAMP") - ts-date-type " timestamp")) - ((org-em 'tsia-up 'tsia-down - org-agenda-sorting-strategy-selected) - (setq ts (org-entry-get (point) "TIMESTAMP_IA") - ts-date-type " timestamp_ia")) - ((org-em 'timestamp-up 'timestamp-down - org-agenda-sorting-strategy-selected) - (setq ts (or (org-entry-get (point) "SCHEDULED") - (org-entry-get (point) "DEADLINE") - (org-entry-get (point) "TIMESTAMP") - (org-entry-get (point) "TIMESTAMP_IA")) - ts-date-type "")) - (t (setq ts-date-type ""))) - (when ts (ignore-errors (org-time-string-to-absolute ts))))) - category-pos (get-text-property (point) 'org-category-position) - txt (org-trim - (buffer-substring (match-beginning 2) (match-end 0))) + ts-date-pair (org-agenda-entry-get-agenda-timestamp (point)) + ts-date (car ts-date-pair) + ts-date-type (cdr ts-date-pair) + txt (org-trim (buffer-substring (match-beginning 2) (match-end 0))) inherited-tags (or (eq org-agenda-show-inherited-tags 'always) (and (listp org-agenda-show-inherited-tags) @@ -5421,10 +5365,9 @@ the documentation of `org-diary'." priority (1+ (org-get-priority txt))) (org-add-props txt props 'org-marker marker 'org-hd-marker marker - 'priority priority 'org-category category + 'priority priority 'level level 'ts-date ts-date - 'org-category-position category-pos 'type (concat "todo" ts-date-type) 'todo-state todo-state) (push txt ee) (if org-agenda-todo-list-sublevels @@ -5473,7 +5416,7 @@ This function is invoked if `org-agenda-todo-ignore-deadlines', (cond ((memq org-agenda-todo-ignore-deadlines '(t all)) t) ((eq org-agenda-todo-ignore-deadlines 'far) - (not (org-deadline-close (match-string 1)))) + (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)) @@ -5483,7 +5426,7 @@ This function is invoked if `org-agenda-todo-ignore-deadlines', ((numberp org-agenda-todo-ignore-deadlines) (org-agenda-todo-custom-ignore-p (match-string 1) org-agenda-todo-ignore-deadlines)) - (t (org-deadline-close (match-string 1))))) + (t (org-deadline-close-p (match-string 1))))) (and org-agenda-todo-ignore-timestamp (let ((buffer (current-buffer)) (regexp @@ -5512,24 +5455,27 @@ This function is invoked if `org-agenda-todo-ignore-deadlines', (match-string 1) org-agenda-todo-ignore-timestamp)) (t)))))))))) -(defun org-agenda-get-timestamps (&optional deadline-results) - "Return the date stamp information for agenda display." +(defun org-agenda-get-timestamps (&optional deadlines) + "Return the date stamp information for agenda display. +Optional argument DEADLINES is a list of deadline items to be +displayed in agenda view." (let* ((props (list 'face 'org-agenda-calendar-event 'org-not-done-regexp org-not-done-regexp 'org-todo-regexp org-todo-regexp 'org-complex-heading-regexp org-complex-heading-regexp 'mouse-face 'highlight 'help-echo - (format "mouse-2 or RET jump to org file %s" + (format "mouse-2 or RET jump to Org file %s" (abbreviate-file-name buffer-file-name)))) - (d1 (calendar-absolute-from-gregorian date)) - mm + (current (calendar-absolute-from-gregorian date)) + (today (org-today)) (deadline-position-alist - (mapcar (lambda (a) (and (setq mm (get-text-property - 0 'org-hd-marker a)) - (cons (marker-position mm) a))) - deadline-results)) - (remove-re org-ts-regexp) + (mapcar (lambda (d) + (let ((m (get-text-property 0 'org-hd-marker d))) + (and m (marker-position m)))) + deadlines)) + ;; Match time-stamps set to current date, time-stamps with + ;; a repeater, and S-exp time-stamps. (regexp (concat (if org-agenda-include-inactive-timestamps "[[<]" "<") @@ -5537,97 +5483,120 @@ This function is invoked if `org-agenda-todo-ignore-deadlines', (substring (format-time-string (car org-time-stamp-formats) - (apply 'encode-time ; DATE bound by calendar + (apply #'encode-time ; DATE bound by calendar (list 0 0 0 (nth 1 date) (car date) (nth 2 date)))) 1 11)) "\\|\\(<[0-9]+-[0-9]+-[0-9]+[^>\n]+?\\+[0-9]+[hdwmy]>\\)" "\\|\\(<%%\\(([^>\n]+)\\)>\\)")) - marker hdmarker deadlinep scheduledp clockp closedp inactivep - donep tmp priority category category-pos level ee txt timestr tags - b0 b3 e3 head todo-state end-of-match show-all warntime habitp - inherited-tags ts-date) + timestamp-items) (goto-char (point-min)) - (while (setq end-of-match (re-search-forward regexp nil t)) - (setq b0 (match-beginning 0) - b3 (match-beginning 3) e3 (match-end 3) - todo-state (save-match-data (ignore-errors (org-get-todo-state))) - habitp (and (functionp 'org-is-habit-p) (save-match-data (org-is-habit-p))) - show-all (or (eq org-agenda-repeating-timestamp-show-all t) - (member todo-state - org-agenda-repeating-timestamp-show-all))) + (while (re-search-forward regexp nil t) + ;; Skip date ranges, scheduled and deadlines, which are handled + ;; specially. Also skip time-stamps before first headline as + ;; there would be no entry to add to the agenda. Eventually, + ;; ignore clock entries. (catch :skip - (and (org-at-date-range-p) (throw :skip nil)) - (org-agenda-skip) - (if (and (match-end 1) - (not (= d1 (org-time-string-to-absolute - (match-string 1) d1 nil show-all - (current-buffer) b0)))) - (throw :skip nil)) - (if (and e3 - (not (org-diary-sexp-entry (buffer-substring b3 e3) "" date))) + (save-match-data + (when (or (org-at-date-range-p) + (org-at-planning-p) + (org-before-first-heading-p) + (and org-agenda-include-inactive-timestamps + (org-at-clock-log-p))) (throw :skip nil)) - (setq tmp (buffer-substring (max (point-min) - (- b0 org-ds-keyword-length)) - b0) - timestr (if b3 "" (buffer-substring b0 (point-at-eol))) - inactivep (= (char-after b0) ?\[) - deadlinep (string-match org-deadline-regexp tmp) - scheduledp (string-match org-scheduled-regexp tmp) - closedp (and org-agenda-include-inactive-timestamps - (string-match org-closed-string tmp)) - clockp (and org-agenda-include-inactive-timestamps - (or (string-match org-clock-string tmp) - (string-match "]-+\\'" tmp))) - warntime (get-text-property (point) 'org-appt-warntime) - donep (member todo-state org-done-keywords)) - (if (or scheduledp deadlinep closedp clockp - (and donep org-agenda-skip-timestamp-if-done)) + (org-agenda-skip)) + (let* ((pos (match-beginning 0)) + (repeat (match-string 1)) + (sexp-entry (match-string 3)) + (time-stamp (if (or repeat sexp-entry) (match-string 0) + (save-excursion + (goto-char pos) + (looking-at org-ts-regexp-both) + (match-string 0)))) + (todo-state (org-get-todo-state)) + (warntime (get-text-property (point) 'org-appt-warntime)) + (done? (member todo-state org-done-keywords))) + ;; Possibly skip done tasks. + (when (and done? org-agenda-skip-timestamp-if-done) (throw :skip t)) - (if (string-match ">" timestr) - ;; substring should only run to end of time stamp - (setq timestr (substring timestr 0 (match-end 0)))) - (setq marker (org-agenda-new-marker b0) - category (org-get-category b0) - category-pos (get-text-property b0 'org-category-position)) - (save-excursion - (if (not (re-search-backward org-outline-regexp-bol nil t)) - (throw :skip nil) - (goto-char (match-beginning 0)) - (if (and (eq t org-agenda-skip-timestamp-if-deadline-is-shown) - (assoc (point) deadline-position-alist)) - (throw :skip nil)) - (setq hdmarker (org-agenda-new-marker) - inherited-tags - (or (eq org-agenda-show-inherited-tags 'always) - (and (listp org-agenda-show-inherited-tags) - (memq 'agenda org-agenda-show-inherited-tags)) - (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-at nil (not inherited-tags)) - level (make-string (org-reduced-level (org-outline-level)) ? )) - (looking-at "\\*+[ \t]+\\([^\r\n]+\\)") - (setq head (or (match-string 1) "")) - (setq txt (org-agenda-format-item - (if inactivep org-agenda-inactive-leader nil) - head level category tags timestr - remove-re habitp))) - (setq priority (org-get-priority txt)) - (org-add-props txt props 'priority priority - 'org-marker marker 'org-hd-marker hdmarker - 'org-category category 'date date - 'level level - 'ts-date - (ignore-errors (org-time-string-to-absolute timestr)) - 'org-category-position category-pos - 'todo-state todo-state - 'warntime warntime - 'type "timestamp") - (push txt ee)) - (if org-agenda-skip-additional-timestamps-same-entry - (outline-next-heading) - (goto-char end-of-match)))) - (nreverse ee))) + ;; S-exp entry doesn't match current day: skip it. + (when (and sexp-entry (not (org-diary-sexp-entry sexp-entry "" date))) + (throw :skip nil)) + (when repeat + (let* ((past + ;; A repeating time stamp is shown at its base + ;; date and every repeated date up to TODAY. If + ;; `org-agenda-prefer-last-repeat' is non-nil, + ;; however, only the last repeat before today + ;; (inclusive) is shown. + (org-agenda--timestamp-to-absolute + repeat + (if (or (> current today) + (eq org-agenda-prefer-last-repeat t) + (member todo-state org-agenda-prefer-last-repeat)) + today + current) + 'past (current-buffer) pos)) + (future + ;; Display every repeated date past TODAY + ;; (exclusive) unless + ;; `org-agenda-show-future-repeats' is nil. If + ;; this variable is set to `next', only display + ;; the first repeated date after TODAY + ;; (exclusive). + (cond + ((<= current today) past) + ((not org-agenda-show-future-repeats) past) + (t + (let ((base (if (eq org-agenda-show-future-repeats 'next) + (1+ today) + current))) + (org-agenda--timestamp-to-absolute + repeat base 'future (current-buffer) pos)))))) + (when (and (/= current past) (/= current future)) + (throw :skip nil)))) + (save-excursion + (re-search-backward org-outline-regexp-bol nil t) + ;; Possibly skip time-stamp when a deadline is set. + (when (and org-agenda-skip-timestamp-if-deadline-is-shown + (assq (point) deadline-position-alist)) + (throw :skip nil)) + (let* ((category (org-get-category pos)) + (inherited-tags + (or (eq org-agenda-show-inherited-tags 'always) + (and (consp org-agenda-show-inherited-tags) + (memq 'agenda org-agenda-show-inherited-tags)) + (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-at nil (not inherited-tags))) + (level (make-string (org-reduced-level (org-outline-level)) + ?\s)) + (head (and (looking-at "\\*+[ \t]+\\(.*\\)") + (match-string 1))) + (inactive? (= (char-after pos) ?\[)) + (habit? (and (fboundp 'org-is-habit-p) (org-is-habit-p))) + (item + (org-agenda-format-item + (and inactive? org-agenda-inactive-leader) + head level category tags time-stamp org-ts-regexp habit?))) + (org-add-props item props + 'priority (if habit? + (org-habit-get-priority (org-habit-parse-todo)) + (org-get-priority item)) + 'org-marker (org-agenda-new-marker pos) + 'org-hd-marker (org-agenda-new-marker) + 'date date + 'level level + 'ts-date (if repeat (org-agenda--timestamp-to-absolute repeat) + current) + 'todo-state todo-state + 'warntime warntime + 'type "timestamp") + (push item timestamp-items)))) + (when org-agenda-skip-additional-timestamps-same-entry + (outline-next-heading)))) + (nreverse timestamp-items))) (defun org-agenda-get-sexps () "Return the sexp information for agenda display." @@ -5638,7 +5607,7 @@ This function is invoked if `org-agenda-todo-ignore-deadlines', (format "mouse-2 or RET jump to org file %s" (abbreviate-file-name buffer-file-name)))) (regexp "^&?%%(") - marker category extra category-pos level ee txt tags entry + marker category extra level ee txt tags entry result beg b sexp sexp-entry todo-state warntime inherited-tags) (goto-char (point-min)) (while (re-search-forward regexp nil t) @@ -5657,7 +5626,6 @@ This function is invoked if `org-agenda-todo-ignore-deadlines', (setq marker (org-agenda-new-marker beg) level (make-string (org-reduced-level (org-outline-level)) ? ) category (org-get-category beg) - category-pos (get-text-property beg 'org-category-position) inherited-tags (or (eq org-agenda-show-inherited-tags 'always) (and (listp org-agenda-show-inherited-tags) @@ -5682,38 +5650,33 @@ This function is invoked if `org-agenda-todo-ignore-deadlines', (setq txt "SEXP entry returned empty string")) (setq txt (org-agenda-format-item extra txt level category tags 'time)) (org-add-props txt props 'org-marker marker - 'org-category category 'date date 'todo-state todo-state - 'org-category-position category-pos - 'level level - 'type "sexp" 'warntime warntime) + 'date date 'todo-state todo-state + 'level level 'type "sexp" 'warntime warntime) (push txt ee))))) (nreverse ee))) ;; Calendar sanity: define some functions that are independent of ;; `calendar-date-style'. -;; Normally I would like to use ISO format when calling the diary functions, -;; but to make sure we still have Emacs 22 compatibility we bind -;; also `european-calendar-style' and use european format (defun org-anniversary (year month day &optional mark) "Like `diary-anniversary', but with fixed (ISO) order of arguments." - (org-no-warnings - (let ((calendar-date-style 'european) (european-calendar-style t)) - (diary-anniversary day month year mark)))) + (with-no-warnings + (let ((calendar-date-style 'iso)) + (diary-anniversary year month day mark)))) (defun org-cyclic (N year month day &optional mark) "Like `diary-cyclic', but with fixed (ISO) order of arguments." - (org-no-warnings - (let ((calendar-date-style 'european) (european-calendar-style t)) - (diary-cyclic N day month year mark)))) + (with-no-warnings + (let ((calendar-date-style 'iso)) + (diary-cyclic N year month day mark)))) (defun org-block (Y1 M1 D1 Y2 M2 D2 &optional mark) "Like `diary-block', but with fixed (ISO) order of arguments." - (org-no-warnings - (let ((calendar-date-style 'european) (european-calendar-style t)) - (diary-block D1 M1 Y1 D2 M2 Y2 mark)))) + (with-no-warnings + (let ((calendar-date-style 'iso)) + (diary-block Y1 M1 D1 Y2 M2 D2 mark)))) (defun org-date (year month day &optional mark) "Like `diary-date', but with fixed (ISO) order of arguments." - (org-no-warnings - (let ((calendar-date-style 'european) (european-calendar-style t)) - (diary-date day month year mark)))) + (with-no-warnings + (let ((calendar-date-style 'iso)) + (diary-date year month day mark)))) ;; Define the `org-class' function (defun org-class (y1 m1 d1 y2 m2 d2 dayname &rest skip-weeks) @@ -5740,26 +5703,6 @@ then those holidays will be skipped." (delq nil (mapcar (lambda(g) (member g skip-weeks)) h)))) entry))) -(defun org-diary-class (m1 d1 y1 m2 d2 y2 dayname &rest skip-weeks) - "Like `org-class', but honor `calendar-date-style'. -The order of the first 2 times 3 arguments depends on the variable -`calendar-date-style' or, if that is not defined, on `european-calendar-style'. -So for American calendars, give this as MONTH DAY YEAR, for European as -DAY MONTH YEAR, and for ISO as YEAR MONTH DAY. -DAYNAME is a number between 0 (Sunday) and 6 (Saturday). SKIP-WEEKS -is any number of ISO weeks in the block period for which the item should -be skipped. - -This function is here only for backward compatibility and it is deprecated, -please use `org-class' instead." - (let* ((date1 (org-order-calendar-date-args m1 d1 y1)) - (date2 (org-order-calendar-date-args m2 d2 y2))) - (org-class - (nth 2 date1) (car date1) (nth 1 date1) - (nth 2 date2) (car date2) (nth 1 date2) - dayname skip-weeks))) -(make-obsolete 'org-diary-class 'org-class "") - (defalias 'org-get-closed 'org-agenda-get-progress) (defun org-agenda-get-progress () "Return the logged TODO entries for agenda display." @@ -5780,7 +5723,8 @@ please use `org-class' instead." (list (if (memq 'closed items) (concat "\\<" org-closed-string)) (if (memq 'clock items) (concat "\\<" org-clock-string)) - (if (memq 'state items) "- State \"\\([a-zA-Z0-9]+\\)\".*?")))) + (if (memq 'state items) + (format "- State \"%s\".*?" org-todo-regexp))))) (parts-re (if parts (mapconcat 'identity parts "\\|") (error "`org-agenda-log-mode-items' is empty"))) (regexp (concat @@ -5794,7 +5738,7 @@ please use `org-class' instead." (list 0 0 0 (nth 1 date) (car date) (nth 2 date)))) 1 11)))) (org-agenda-search-headline-for-time nil) - marker hdmarker priority category category-pos level tags closedp + marker hdmarker priority category level tags closedp statep clockp state ee txt extra timestr rest clocked inherited-tags) (goto-char (point-min)) (while (re-search-forward regexp nil t) @@ -5806,7 +5750,6 @@ please use `org-class' instead." clockp (not (or closedp statep)) state (and statep (match-string 2)) category (org-get-category (match-beginning 0)) - category-pos (get-text-property (match-beginning 0) 'org-category-position) timestr (buffer-substring (match-beginning 0) (point-at-eol))) (when (string-match "\\]" timestr) ;; substring should only run to end of time stamp @@ -5858,9 +5801,7 @@ please use `org-class' instead." (setq priority 100000) (org-add-props txt props 'org-marker marker 'org-hd-marker hdmarker 'face 'org-agenda-done - 'priority priority 'org-category category - 'org-category-position category-pos - 'level level + 'priority priority 'level level 'type "closed" 'date date 'undone-face 'org-warning 'done-face 'org-agenda-done) (push txt ee)) @@ -5871,23 +5812,22 @@ please use `org-class' instead." "Add overlays, showing issues with clocking. See also the user option `org-agenda-clock-consistency-checks'." (interactive) - (let* ((org-time-clocksum-use-effort-durations nil) - (pl org-agenda-clock-consistency-checks) + (let* ((pl org-agenda-clock-consistency-checks) (re (concat "^[ \t]*" org-clock-string "[ \t]+" - "\\(\\[.*?\\]\\)" ; group 1 is first stamp + "\\(\\[.*?\\]\\)" ; group 1 is first stamp "\\(-\\{1,3\\}\\(\\[.*?\\]\\)\\)?")) ; group 3 is second (tlstart 0.) (tlend 0.) - (maxtime (org-hh:mm-string-to-minutes + (maxtime (org-duration-to-minutes (or (plist-get pl :max-duration) "24:00"))) - (mintime (org-hh:mm-string-to-minutes + (mintime (org-duration-to-minutes (or (plist-get pl :min-duration) 0))) - (maxgap (org-hh:mm-string-to-minutes + (maxgap (org-duration-to-minutes ;; default 30:00 means never complain (or (plist-get pl :max-gap) "30:00"))) - (gapok (mapcar 'org-hh:mm-string-to-minutes + (gapok (mapcar #'org-duration-to-minutes (plist-get pl :gap-ok-around))) (def-face (or (plist-get pl :default-face) '((:background "DarkRed") (:foreground "white")))) @@ -5913,22 +5853,20 @@ See also the user option `org-agenda-clock-consistency-checks'." (setq ts (match-string 1) te (match-string 3) ts (float-time - (apply 'encode-time (org-parse-time-string ts))) + (apply #'encode-time (org-parse-time-string ts))) te (float-time - (apply 'encode-time (org-parse-time-string te))) + (apply #'encode-time (org-parse-time-string te))) dt (- te ts)))) (cond ((> dt (* 60 maxtime)) ;; a very long clocking chunk (setq issue (format "Clocking interval is very long: %s" - (org-minutes-to-clocksum-string - (floor (/ (float dt) 60.)))) + (org-duration-from-minutes (floor (/ dt 60.)))) face (or (plist-get pl :long-face) face))) ((< dt (* 60 mintime)) ;; a very short clocking chunk (setq issue (format "Clocking interval is very short: %s" - (org-minutes-to-clocksum-string - (floor (/ (float dt) 60.)))) + (org-duration-from-minutes (floor (/ dt 60.)))) face (or (plist-get pl :short-face) face))) ((and (> tlend 0) (< ts tlend)) ;; Two clock entries are overlapping @@ -6001,312 +5939,342 @@ specification like [h]h:mm." (regexp (if with-hour org-deadline-time-hour-regexp org-deadline-time-regexp)) - (todayp (org-agenda-todayp date)) ; DATE bound by calendar - (d1 (calendar-absolute-from-gregorian date)) ; DATE bound by calendar - (dl0 (car org-agenda-deadline-leaders)) - (dl1 (nth 1 org-agenda-deadline-leaders)) - (dl2 (or (nth 2 org-agenda-deadline-leaders) dl1)) - d2 diff dfrac wdays pos pos1 category category-pos level - tags suppress-prewarning ee txt head face s todo-state - show-all upcomingp donep timestr warntime inherited-tags ts-date) + (today (org-today)) + (today? (org-agenda-today-p date)) ; DATE bound by calendar. + (current (calendar-absolute-from-gregorian date)) + deadline-items) (goto-char (point-min)) (while (re-search-forward regexp nil t) (catch :skip + (unless (save-match-data (org-at-planning-p)) (throw :skip nil)) (org-agenda-skip) - (setq s (match-string 1) - txt nil - pos (1- (match-beginning 1)) - todo-state (save-match-data (org-get-todo-state)) - show-all (or (eq org-agenda-repeating-timestamp-show-all t) - (member todo-state - org-agenda-repeating-timestamp-show-all)) - d2 (org-time-string-to-absolute - s d1 'past show-all (current-buffer) pos) - diff (- d2 d1)) - (setq suppress-prewarning - (let ((ds (and org-agenda-skip-deadline-prewarning-if-scheduled - (let ((item (buffer-substring (point-at-bol) - (point-at-eol)))) - (save-match-data - (and (string-match - org-scheduled-time-regexp item) - (match-string 1 item))))))) + (let* ((s (match-string 1)) + (pos (1- (match-beginning 1))) + (todo-state (save-match-data (org-get-todo-state))) + (done? (member todo-state org-done-keywords)) + (sexp? (string-prefix-p "%%" s)) + ;; DEADLINE is the deadline date for the entry. It is + ;; either the base date or the last repeat, according + ;; to `org-agenda-prefer-last-repeat'. + (deadline (cond - ((not ds) nil) - ;; The current item has a scheduled date (in ds), so - ;; evaluate its prewarning lead time. - ((integerp org-agenda-skip-deadline-prewarning-if-scheduled) - ;; Use global prewarning-restart lead time. - org-agenda-skip-deadline-prewarning-if-scheduled) - ((eq org-agenda-skip-deadline-prewarning-if-scheduled - 'pre-scheduled) - ;; Set prewarning to no earlier than scheduled. - (min (- d2 (org-time-string-to-absolute - ds d1 'past show-all (current-buffer) pos)) - org-deadline-warning-days)) - ;; Set prewarning to deadline. - (t 0)))) - (setq wdays (if suppress-prewarning - (let ((org-deadline-warning-days suppress-prewarning)) - (org-get-wdays s)) - (org-get-wdays s)) - dfrac (- 1 (/ (* 1.0 diff) (max wdays 1))) - upcomingp (and todayp (> diff 0))) - ;; When to show a deadline in the calendar: - ;; If the expiration is within wdays warning time. - ;; Past-due deadlines are only shown on the current date - (if (and (or (and (<= diff wdays) - (and todayp (not org-agenda-only-exact-dates))) - (= diff 0))) - (save-excursion - ;; (setq todo-state (org-get-todo-state)) - (setq donep (member todo-state org-done-keywords)) - (if (and donep - (or org-agenda-skip-deadline-if-done - (not (= diff 0)))) - (setq txt nil) - (setq category (org-get-category) - warntime (get-text-property (point) 'org-appt-warntime) - category-pos (get-text-property (point) 'org-category-position)) - (if (not (re-search-backward "^\\*+[ \t]+" nil t)) - (throw :skip nil) - (goto-char (match-end 0)) - (setq pos1 (match-beginning 0)) - (setq level (make-string (org-reduced-level (org-outline-level)) ? )) - (setq inherited-tags - (or (eq org-agenda-show-inherited-tags 'always) - (and (listp org-agenda-show-inherited-tags) - (memq 'agenda org-agenda-show-inherited-tags)) - (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-at pos1 (not inherited-tags))) - (setq head (buffer-substring - (point) - (progn (skip-chars-forward "^\r\n") - (point)))) - (if (string-match " \\([012]?[0-9]:[0-9][0-9]\\)" s) - (setq timestr - (concat (substring s (match-beginning 1)) " ")) - (setq timestr 'time)) - (setq txt (org-agenda-format-item - (cond ((= diff 0) dl0) - ((> diff 0) - (if (functionp dl1) - (funcall dl1 diff date) - (format dl1 diff))) - (t - (if (functionp dl2) - (funcall dl2 diff date) - (format dl2 (if (string= dl2 dl1) - diff (abs diff)))))) - head level category tags - (if (not (= diff 0)) nil timestr))))) - (when txt - (setq face (org-agenda-deadline-face dfrac)) - (org-add-props txt props - 'org-marker (org-agenda-new-marker pos) - 'warntime warntime - 'level level - 'ts-date d2 - 'org-hd-marker (org-agenda-new-marker pos1) - 'priority (+ (- diff) - (org-get-priority txt)) - 'org-category category - 'org-category-position category-pos - 'todo-state todo-state - 'type (if upcomingp "upcoming-deadline" "deadline") - 'date (if upcomingp date d2) - 'face (if donep 'org-agenda-done face) - 'undone-face face 'done-face 'org-agenda-done) - (push txt ee)))))) - (nreverse ee))) + (sexp? (org-agenda--timestamp-to-absolute s current)) + ((or (eq org-agenda-prefer-last-repeat t) + (member todo-state org-agenda-prefer-last-repeat)) + (org-agenda--timestamp-to-absolute + s today 'past (current-buffer) pos)) + (t (org-agenda--timestamp-to-absolute s)))) + ;; REPEAT is the future repeat closest from CURRENT, + ;; according to `org-agenda-show-future-repeats'. If + ;; the latter is nil, or if the time stamp has no + ;; repeat part, default to DEADLINE. + (repeat + (cond + (sexp? deadline) + ((<= current today) deadline) + ((not org-agenda-show-future-repeats) deadline) + (t + (let ((base (if (eq org-agenda-show-future-repeats 'next) + (1+ today) + current))) + (org-agenda--timestamp-to-absolute + s base 'future (current-buffer) pos))))) + (diff (- deadline current)) + (suppress-prewarning + (let ((scheduled + (and org-agenda-skip-deadline-prewarning-if-scheduled + (org-entry-get nil "SCHEDULED")))) + (cond + ((not scheduled) nil) + ;; The current item has a scheduled date, so + ;; evaluate its prewarning lead time. + ((integerp org-agenda-skip-deadline-prewarning-if-scheduled) + ;; Use global prewarning-restart lead time. + org-agenda-skip-deadline-prewarning-if-scheduled) + ((eq org-agenda-skip-deadline-prewarning-if-scheduled + 'pre-scheduled) + ;; Set pre-warning to no earlier than SCHEDULED. + (min (- deadline + (org-agenda--timestamp-to-absolute scheduled)) + org-deadline-warning-days)) + ;; Set pre-warning to deadline. + (t 0)))) + (wdays (if suppress-prewarning + (let ((org-deadline-warning-days suppress-prewarning)) + (org-get-wdays s)) + (org-get-wdays s)))) + (cond + ;; Only display deadlines at their base date, at future + ;; repeat occurrences or in today agenda. + ((= current deadline) nil) + ((= current repeat) nil) + ((not today?) (throw :skip nil)) + ;; Upcoming deadline: display within warning period WDAYS. + ((> deadline current) (when (> diff wdays) (throw :skip nil))) + ;; Overdue deadline: warn about it for + ;; `org-deadline-past-days' duration. + (t (when (< org-deadline-past-days (- diff)) (throw :skip nil)))) + ;; Possibly skip done tasks. + (when (and done? + (or org-agenda-skip-deadline-if-done + (/= deadline current))) + (throw :skip nil)) + (save-excursion + (re-search-backward "^\\*+[ \t]+" nil t) + (goto-char (match-end 0)) + (let* ((category (org-get-category)) + (level (make-string (org-reduced-level (org-outline-level)) + ?\s)) + (head (buffer-substring (point) (line-end-position))) + (inherited-tags + (or (eq org-agenda-show-inherited-tags 'always) + (and (listp org-agenda-show-inherited-tags) + (memq 'agenda org-agenda-show-inherited-tags)) + (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-at nil (not inherited-tags))) + (time + (cond + ;; No time of day designation if it is only + ;; a reminder. + ((and (/= current deadline) (/= current repeat)) nil) + ((string-match " \\([012]?[0-9]:[0-9][0-9]\\)" s) + (concat (substring s (match-beginning 1)) " ")) + (t 'time))) + (item + (org-agenda-format-item + ;; Insert appropriate suffixes before deadlines. + ;; Those only apply to today agenda. + (pcase-let ((`(,now ,future ,past) + org-agenda-deadline-leaders)) + (cond + ((and today? (< deadline today)) (format past (- diff))) + ((and today? (> deadline today)) (format future diff)) + (t now))) + head level category tags time)) + (face (org-agenda-deadline-face + (- 1 (/ (float diff) (max wdays 1))))) + (upcoming? (and today? (> deadline today))) + (warntime (get-text-property (point) 'org-appt-warntime))) + (org-add-props item props + 'org-marker (org-agenda-new-marker pos) + 'org-hd-marker (org-agenda-new-marker (line-beginning-position)) + 'warntime warntime + 'level level + 'ts-date deadline + 'priority + ;; Adjust priority to today reminders about deadlines. + ;; Overdue deadlines get the highest priority + ;; increase, then imminent deadlines and eventually + ;; more distant deadlines. + (let ((adjust (if today? (- diff) 0))) + (+ adjust (org-get-priority item))) + 'todo-state todo-state + 'type (if upcoming? "upcoming-deadline" "deadline") + 'date (if upcoming? date deadline) + 'face (if done? 'org-agenda-done face) + 'undone-face face + 'done-face 'org-agenda-done) + (push item deadline-items)))))) + (nreverse deadline-items))) (defun org-agenda-deadline-face (fraction) "Return the face to displaying a deadline item. FRACTION is what fraction of the head-warning time has passed." - (let ((faces org-agenda-deadline-faces) f) - (catch 'exit - (while (setq f (pop faces)) - (if (>= fraction (car f)) (throw 'exit (cdr f))))))) + (assoc-default fraction org-agenda-deadline-faces #'<=)) -(defun org-agenda-get-scheduled (&optional deadline-results with-hour) +(defun org-agenda-get-scheduled (&optional deadlines with-hour) "Return the scheduled information for agenda display. -When WITH-HOUR is non-nil, only return scheduled items with -an hour specification like [h]h:mm." +Optional argument DEADLINES is a list of deadline items to be +displayed in agenda view. When WITH-HOUR is non-nil, only return +scheduled items with an hour specification like [h]h:mm." (let* ((props (list 'org-not-done-regexp org-not-done-regexp 'org-todo-regexp org-todo-regexp 'org-complex-heading-regexp org-complex-heading-regexp 'done-face 'org-agenda-done 'mouse-face 'highlight 'help-echo - (format "mouse-2 or RET jump to org file %s" + (format "mouse-2 or RET jump to Org file %s" (abbreviate-file-name buffer-file-name)))) (regexp (if with-hour org-scheduled-time-hour-regexp org-scheduled-time-regexp)) - (todayp (org-agenda-todayp date)) ; DATE bound by calendar - (d1 (calendar-absolute-from-gregorian date)) ; DATE bound by calendar - mm - (deadline-position-alist - (mapcar (lambda (a) (and (setq mm (get-text-property - 0 'org-hd-marker a)) - (cons (marker-position mm) a))) - deadline-results)) - d2 diff pos pos1 category category-pos level tags donep - ee txt head pastschedp todo-state face timestr s habitp show-all - did-habit-check-p warntime inherited-tags ts-date suppress-delay - ddays) + (today (org-today)) + (todayp (org-agenda-today-p date)) ; DATE bound by calendar. + (current (calendar-absolute-from-gregorian date)) + (deadline-pos + (mapcar (lambda (d) + (let ((m (get-text-property 0 'org-hd-marker d))) + (and m (marker-position m)))) + deadlines)) + scheduled-items) (goto-char (point-min)) (while (re-search-forward regexp nil t) (catch :skip + (unless (save-match-data (org-at-planning-p)) (throw :skip nil)) (org-agenda-skip) - (setq s (match-string 1) - txt nil - pos (1- (match-beginning 1)) - todo-state (save-match-data (org-get-todo-state)) - show-all (or (eq org-agenda-repeating-timestamp-show-all t) - (member todo-state - org-agenda-repeating-timestamp-show-all)) - d2 (org-time-string-to-absolute - s d1 'past show-all (current-buffer) pos) - diff (- d2 d1) - warntime (get-text-property (point) 'org-appt-warntime)) - (setq pastschedp (and todayp (< diff 0))) - (setq did-habit-check-p nil) - (setq suppress-delay - (let ((ds (and org-agenda-skip-scheduled-delay-if-deadline - (let ((item (buffer-substring (point-at-bol) (point-at-eol)))) - (save-match-data - (and (string-match - org-deadline-time-regexp item) - (match-string 1 item))))))) + (let* ((s (match-string 1)) + (pos (1- (match-beginning 1))) + (todo-state (save-match-data (org-get-todo-state))) + (donep (member todo-state org-done-keywords)) + (sexp? (string-prefix-p "%%" s)) + ;; SCHEDULE is the scheduled date for the entry. It is + ;; either the bare date or the last repeat, according + ;; to `org-agenda-prefer-last-repeat'. + (schedule (cond - ((not ds) nil) - ;; The current item has a deadline date (in ds), so - ;; evaluate its delay time. - ((integerp org-agenda-skip-scheduled-delay-if-deadline) - ;; Use global delay time. - (- org-agenda-skip-scheduled-delay-if-deadline)) - ((eq org-agenda-skip-scheduled-delay-if-deadline - 'post-deadline) - ;; Set delay to no later than deadline. - (min (- d2 (org-time-string-to-absolute - ds d1 'past show-all (current-buffer) pos)) - org-scheduled-delay-days)) - (t 0)))) - (setq ddays (if suppress-delay - (let ((org-scheduled-delay-days suppress-delay)) - (org-get-wdays s t t)) - (org-get-wdays s t))) - ;; Use a delay of 0 when there is a repeater and the delay is - ;; of the form --3d - (when (and (save-match-data (string-match "--[0-9]+[hdwmy]" s)) - (< (org-time-string-to-absolute s) - (org-time-string-to-absolute - s d2 'past nil (current-buffer) pos))) - (setq ddays 0)) - ;; When to show a scheduled item in the calendar: - ;; If it is on or past the date. - (when (or (and (> ddays 0) (= diff (- ddays))) - (and (zerop ddays) (= diff 0)) - (and (< (+ diff ddays) 0) - (< (abs diff) org-scheduled-past-days) - (and todayp (not org-agenda-only-exact-dates))) - ;; org-is-habit-p uses org-entry-get, which is expansive - ;; so we go extra mile to only call it once - (and todayp - (boundp 'org-habit-show-all-today) - org-habit-show-all-today - (setq did-habit-check-p t) - (setq habitp (and (functionp 'org-is-habit-p) - (org-is-habit-p))))) - (save-excursion - (setq donep (member todo-state org-done-keywords)) - (if (and donep + (sexp? (org-agenda--timestamp-to-absolute s current)) + ((or (eq org-agenda-prefer-last-repeat t) + (member todo-state org-agenda-prefer-last-repeat)) + (org-agenda--timestamp-to-absolute + s today 'past (current-buffer) pos)) + (t (org-agenda--timestamp-to-absolute s)))) + ;; REPEAT is the future repeat closest from CURRENT, + ;; according to `org-agenda-show-future-repeats'. If + ;; the latter is nil, or if the time stamp has no + ;; repeat part, default to SCHEDULE. + (repeat + (cond + (sexp? schedule) + ((<= current today) schedule) + ((not org-agenda-show-future-repeats) schedule) + (t + (let ((base (if (eq org-agenda-show-future-repeats 'next) + (1+ today) + current))) + (org-agenda--timestamp-to-absolute + s base 'future (current-buffer) pos))))) + (diff (- current schedule)) + (warntime (get-text-property (point) 'org-appt-warntime)) + (pastschedp (< schedule today)) + (habitp (and (fboundp 'org-is-habit-p) (org-is-habit-p))) + (suppress-delay + (let ((deadline (and org-agenda-skip-scheduled-delay-if-deadline + (org-entry-get nil "DEADLINE")))) + (cond + ((not deadline) nil) + ;; The current item has a deadline date, so + ;; evaluate its delay time. + ((integerp org-agenda-skip-scheduled-delay-if-deadline) + ;; Use global delay time. + (- org-agenda-skip-scheduled-delay-if-deadline)) + ((eq org-agenda-skip-scheduled-delay-if-deadline + 'post-deadline) + ;; Set delay to no later than DEADLINE. + (min (- schedule + (org-agenda--timestamp-to-absolute deadline)) + org-scheduled-delay-days)) + (t 0)))) + (ddays + (cond + ;; Nullify delay when a repeater triggered already + ;; and the delay is of the form --Xd. + ((and (string-match-p "--[0-9]+[hdwmy]" s) + (> current schedule)) + 0) + (suppress-delay + (let ((org-scheduled-delay-days suppress-delay)) + (org-get-wdays s t t))) + (t (org-get-wdays s t))))) + ;; Display scheduled items at base date (SCHEDULE), today if + ;; scheduled before the current date, and at any repeat past + ;; today. However, skip delayed items and items that have + ;; been displayed for more than `org-scheduled-past-days'. + (unless (and todayp + habitp + (bound-and-true-p org-habit-show-all-today)) + (when (or (and (> ddays 0) (< diff ddays)) + (> diff org-scheduled-past-days) + (> schedule current) + (and (/= current schedule) + (/= current today) + (/= current repeat))) + (throw :skip nil))) + ;; Possibly skip done tasks. + (when (and donep (or org-agenda-skip-scheduled-if-done - (not (= diff 0)) - (and (functionp 'org-is-habit-p) - (org-is-habit-p)))) - (setq txt nil) - (setq habitp (if did-habit-check-p habitp - (and (functionp 'org-is-habit-p) - (org-is-habit-p)))) - (setq category (org-get-category) - category-pos (get-text-property (point) 'org-category-position)) - (if (and (eq org-agenda-skip-scheduled-if-deadline-is-shown - 'repeated-after-deadline) - (org-get-deadline-time (point)) - (<= 0 (- d2 (time-to-days (org-get-deadline-time (point)))))) - (throw :skip nil)) - (if (not (re-search-backward "^\\*+[ \t]+" nil t)) - (throw :skip nil) - (goto-char (match-end 0)) - (setq pos1 (match-beginning 0)) - (if habitp - (if (or (not org-habit-show-habits) - (and (not todayp) - (boundp 'org-habit-show-habits-only-for-today) - org-habit-show-habits-only-for-today)) - (throw :skip nil)) - (if (and - (or (eq t org-agenda-skip-scheduled-if-deadline-is-shown) - (and (eq org-agenda-skip-scheduled-if-deadline-is-shown 'not-today) - pastschedp)) - (setq mm (assoc pos1 deadline-position-alist))) - (throw :skip nil))) - (setq inherited-tags - (or (eq org-agenda-show-inherited-tags 'always) - (and (listp org-agenda-show-inherited-tags) - (memq 'agenda org-agenda-show-inherited-tags)) - (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-at nil (not inherited-tags))) - (setq level (make-string (org-reduced-level (org-outline-level)) ? )) - (setq head (buffer-substring - (point) - (progn (skip-chars-forward "^\r\n") (point)))) - (if (string-match " \\([012]?[0-9]:[0-9][0-9]\\)" s) - (setq timestr - (concat (substring s (match-beginning 1)) " ")) - (setq timestr 'time)) - (setq txt (org-agenda-format-item - (if (= diff 0) - (car org-agenda-scheduled-leaders) - (format (nth 1 org-agenda-scheduled-leaders) - (- 1 diff))) - head level category tags - (if (not (= diff 0)) nil timestr) - nil habitp)))) - (when txt - (setq face + (/= schedule current))) + (throw :skip nil)) + ;; Skip entry if it already appears as a deadline, per + ;; `org-agenda-skip-scheduled-if-deadline-is-shown'. This + ;; doesn't apply to habits. + (when (pcase org-agenda-skip-scheduled-if-deadline-is-shown + ((guard + (or (not (memq (line-beginning-position 0) deadline-pos)) + habitp)) + nil) + (`repeated-after-deadline + (let ((deadline (time-to-days + (org-get-deadline-time (point))))) + (and (<= schedule deadline) (> current deadline)))) + (`not-today pastschedp) + (`t t) + (_ nil)) + (throw :skip nil)) + ;; Skip habits if `org-habit-show-habits' is nil, or if we + ;; only show them for today. Also skip done habits. + (when (and habitp + (or donep + (not (bound-and-true-p org-habit-show-habits)) + (and (not todayp) + (bound-and-true-p + org-habit-show-habits-only-for-today)))) + (throw :skip nil)) + (save-excursion + (re-search-backward "^\\*+[ \t]+" nil t) + (goto-char (match-end 0)) + (let* ((category (org-get-category)) + (inherited-tags + (or (eq org-agenda-show-inherited-tags 'always) + (and (listp org-agenda-show-inherited-tags) + (memq 'agenda org-agenda-show-inherited-tags)) + (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-at nil (not inherited-tags))) + (level (make-string (org-reduced-level (org-outline-level)) + ?\s)) + (head (buffer-substring (point) (line-end-position))) + (time (cond - ((and (not habitp) pastschedp) - 'org-scheduled-previously) - (todayp 'org-scheduled-today) - (t 'org-scheduled)) - habitp (and habitp (org-habit-parse-todo))) - (org-add-props txt props + ;; No time of day designation if it is only + ;; a reminder. + ((and (/= current schedule) (/= current repeat)) nil) + ((string-match " \\([012]?[0-9]:[0-9][0-9]\\)" s) + (concat (substring s (match-beginning 1)) " ")) + (t 'time))) + (item + (org-agenda-format-item + (pcase-let ((`(,first ,past) org-agenda-scheduled-leaders)) + ;; Show a reminder of a past scheduled today. + (if (and todayp pastschedp) + (format past diff) + first)) + head level category tags time nil habitp)) + (face (cond ((and (not habitp) pastschedp) + 'org-scheduled-previously) + (todayp 'org-scheduled-today) + (t 'org-scheduled))) + (habitp (and habitp (org-habit-parse-todo)))) + (org-add-props item props 'undone-face face 'face (if donep 'org-agenda-done face) 'org-marker (org-agenda-new-marker pos) - 'org-hd-marker (org-agenda-new-marker pos1) + 'org-hd-marker (org-agenda-new-marker (line-beginning-position)) 'type (if pastschedp "past-scheduled" "scheduled") - 'date (if pastschedp d2 date) - 'ts-date d2 + 'date (if pastschedp schedule date) + 'ts-date schedule 'warntime warntime 'level level - 'priority (if habitp - (org-habit-get-priority habitp) - (+ 94 (- 5 diff) (org-get-priority txt))) - 'org-category category - 'category-position category-pos + 'priority (if habitp (org-habit-get-priority habitp) + (+ 99 diff (org-get-priority item))) 'org-habit-p habitp 'todo-state todo-state) - (push txt ee)))))) - (nreverse ee))) + (push item scheduled-items)))))) + (nreverse scheduled-items))) (defun org-agenda-get-blocks () "Return the date-range information for agenda display." @@ -6320,7 +6288,7 @@ an hour specification like [h]h:mm." (abbreviate-file-name buffer-file-name)))) (regexp org-tr-regexp) (d0 (calendar-absolute-from-gregorian date)) - marker hdmarker ee txt d1 d2 s1 s2 category category-pos + marker hdmarker ee txt d1 d2 s1 s2 category level todo-state tags pos head donep inherited-tags) (goto-char (point-min)) (while (re-search-forward regexp nil t) @@ -6331,8 +6299,26 @@ an hour specification like [h]h:mm." (end-time (match-string 2))) (setq s1 (match-string 1) s2 (match-string 2) - d1 (time-to-days (org-time-string-to-time s1 (current-buffer) pos)) - d2 (time-to-days (org-time-string-to-time s2 (current-buffer) pos))) + d1 (time-to-days + (condition-case err + (org-time-string-to-time s1) + (error + (error + "Bad timestamp %S at %d in buffer %S\nError was: %s" + s1 + pos + (current-buffer) + (error-message-string err))))) + d2 (time-to-days + (condition-case err + (org-time-string-to-time s2) + (error + (error + "Bad timestamp %S at %d in buffer %S\nError was: %s" + s2 + pos + (current-buffer) + (error-message-string err)))))) (if (and (> (- d0 d1) -1) (> (- d2 d0) -1)) ;; Only allow days between the limits, because the normal ;; date stamps will catch the limits. @@ -6341,9 +6327,8 @@ an hour specification like [h]h:mm." (setq donep (member todo-state org-done-keywords)) (if (and donep org-agenda-skip-timestamp-if-done) (throw :skip t)) - (setq marker (org-agenda-new-marker (point))) - (setq category (org-get-category) - category-pos (get-text-property (point) 'org-category-position)) + (setq marker (org-agenda-new-marker (point)) + category (org-get-category)) (if (not (re-search-backward org-outline-regexp-bol nil t)) (throw :skip nil) (goto-char (match-beginning 0)) @@ -6358,7 +6343,7 @@ an hour specification like [h]h:mm." tags (org-get-tags-at nil (not inherited-tags))) (setq level (make-string (org-reduced-level (org-outline-level)) ? )) - (looking-at "\\*+[ \t]+\\([^\r\n]+\\)") + (looking-at "\\*+[ \t]+\\(.*\\)") (setq head (match-string 1)) (let ((remove-re (if org-agenda-remove-timeranges-from-blocks @@ -6385,8 +6370,7 @@ an hour specification like [h]h:mm." 'type "block" 'date date 'level level 'todo-state todo-state - 'priority (org-get-priority txt) 'org-category category - 'org-category-position category-pos) + 'priority (org-get-priority txt)) (push txt ee)))) (goto-char pos))) ;; Sort the entries by expiration date. @@ -6413,11 +6397,11 @@ The flag is set if the currently compiled format contains a `%b'.") (defun org-agenda-get-category-icon (category) "Return an image for CATEGORY according to `org-agenda-category-icon-alist'." - (dolist (entry org-agenda-category-icon-alist) - (when (org-string-match-p (car entry) category) + (cl-dolist (entry org-agenda-category-icon-alist) + (when (string-match-p (car entry) category) (if (listp (cadr entry)) - (return (cadr entry)) - (return (apply 'create-image (cdr entry))))))) + (cl-return (cadr entry)) + (cl-return (apply #'create-image (cdr entry))))))) (defun org-agenda-format-item (extra txt &optional level category tags dotime remove-re habitp) @@ -6444,8 +6428,8 @@ Any match of REMOVE-RE will be removed from TXT." ;; buffer (let* ((bindings (car org-prefix-format-compiled)) (formatter (cadr org-prefix-format-compiled))) - (loop for (var value) in bindings - do (set var value)) + (cl-loop for (var value) in bindings + do (set var value)) (save-match-data ;; Diary entries sometimes have extra whitespace at the beginning (setq txt (org-trim txt)) @@ -6457,9 +6441,6 @@ Any match of REMOVE-RE will be removed from TXT." org-agenda-hide-tags-regexp)) (let* ((category (or category - (if (stringp org-category) - org-category - (and org-category (symbol-name org-category))) (if buffer-file-name (file-name-sans-extension (file-name-nondirectory buffer-file-name)) @@ -6468,15 +6449,18 @@ Any match of REMOVE-RE will be removed from TXT." (category-icon (if category-icon (propertize " " 'display category-icon) "")) + (effort (and (not (string= txt "")) + (get-text-property 1 'effort txt))) ;; time, tag, effort are needed for the eval of the prefix format (tag (if tags (nth (1- (length tags)) tags) "")) - time effort neffort + (time-grid-trailing-characters (nth 2 org-agenda-time-grid)) + time (ts (if dotime (concat (if (stringp dotime) dotime "") (and org-agenda-search-headline-for-time txt)))) (time-of-day (and dotime (org-get-time-of-day ts))) stamp plain s0 s1 s2 rtn srp l - duration thecategory breadcrumbs) + duration breadcrumbs) (and (derived-mode-p 'org-mode) buffer-file-name (add-to-list 'org-agenda-contributing-files buffer-file-name)) (when (and dotime time-of-day) @@ -6503,21 +6487,21 @@ Any match of REMOVE-RE will be removed from TXT." (if s1 (setq s1 (org-get-time-of-day s1 'string t))) (if s2 (setq s2 (org-get-time-of-day s2 'string t))) - ;; Try to set s2 if s1 and `org-agenda-default-appointment-duration' are set - (let (org-time-clocksum-use-effort-durations) - (when (and s1 (not s2) org-agenda-default-appointment-duration) - (setq s2 - (org-minutes-to-clocksum-string - (+ (org-hh:mm-string-to-minutes s1) - org-agenda-default-appointment-duration))))) + ;; Try to set s2 if s1 and + ;; `org-agenda-default-appointment-duration' are set + (when (and s1 (not s2) org-agenda-default-appointment-duration) + (setq s2 + (org-duration-from-minutes + (+ (org-duration-to-minutes s1 t) + org-agenda-default-appointment-duration) + nil t))) ;; Compute the duration (when s2 - (setq duration (- (org-hh:mm-string-to-minutes s2) - (org-hh:mm-string-to-minutes s1))))) + (setq duration (- (org-duration-to-minutes s2) + (org-duration-to-minutes s1))))) - (when (string-match (org-re "\\([ \t]+\\)\\(:[[:alnum:]_@#%:]+:\\)[ \t]*$") - txt) + (when (string-match "\\([ \t]+\\)\\(:[[:alnum:]_@#%:]+:\\)[ \t]*$" txt) ;; Tags are in the string (if (or (eq org-agenda-remove-tags t) (and org-agenda-remove-tags @@ -6527,16 +6511,6 @@ Any match of REMOVE-RE will be removed from TXT." (concat (make-string (max (- 50 (length txt)) 1) ?\ ) (match-string 2 txt)) t t txt)))) - (when (derived-mode-p 'org-mode) - (setq effort (ignore-errors (get-text-property 0 'org-effort txt)))) - - ;; org-agenda-add-time-grid-maybe calls us with *Agenda* as - ;; current buffer, so move this check outside of above - (if effort - (setq neffort (org-duration-string-to-minutes effort) - effort (setq effort (concat "[" effort "]"))) - ;; prevent erroring out with %e format when there is no effort - (setq effort "")) (when remove-re (while (string-match remove-re txt) @@ -6558,12 +6532,11 @@ Any match of REMOVE-RE will be removed from TXT." (s1 (concat (org-agenda-time-of-day-to-ampm-maybe s1) (if org-agenda-timegrid-use-ampm - "........ " - "......"))) + (concat time-grid-trailing-characters " ") + time-grid-trailing-characters))) (t "")) extra (or (and (not habitp) extra) "") category (if (symbolp category) (symbol-name category) category) - thecategory (copy-sequence category) level (or level "")) (if (string-match org-bracket-link-regexp category) (progn @@ -6584,14 +6557,12 @@ Any match of REMOVE-RE will be removed from TXT." ;; And finally add the text properties (remove-text-properties 0 (length rtn) '(line-prefix t wrap-prefix t) rtn) (org-add-props rtn nil - 'org-category (if thecategory (downcase thecategory) category) + 'org-category category 'tags (mapcar 'org-downcase-keep-props tags) 'org-highest-priority org-highest-priority 'org-lowest-priority org-lowest-priority 'time-of-day time-of-day 'duration duration - 'effort effort - 'effort-minutes neffort 'breadcrumbs breadcrumbs 'txt txt 'level level @@ -6605,7 +6576,7 @@ Any match of REMOVE-RE will be removed from TXT." The modified list may contain inherited tags, and tags matched by `org-agenda-hide-tags-regexp' will be removed." (when (or add-inherited hide-re) - (if (string-match (org-re "\\([ \t]+\\)\\(:[[:alnum:]_@#%:]+:\\)[ \t]*$") txt) + (if (string-match "\\([ \t]+\\)\\(:[[:alnum:]_@#%:]+:\\)[ \t]*$" txt) (setq txt (substring txt 0 (match-beginning 0)))) (setq tags (delq nil @@ -6655,8 +6626,8 @@ TODAYP is t when the current agenda view is on today." (let* ((have (delq nil (mapcar (lambda (x) (get-text-property 1 'time-of-day x)) list))) - (string (nth 1 org-agenda-time-grid)) - (gridtimes (nth 2 org-agenda-time-grid)) + (string (nth 3 org-agenda-time-grid)) + (gridtimes (nth 1 org-agenda-time-grid)) (req (car org-agenda-time-grid)) (remove (member 'remove-match req)) new time) @@ -6710,12 +6681,12 @@ and stored in the variable `org-prefix-format-compiled'." c (or (match-string 3 s) "") opt (match-beginning 1) start (1+ (match-beginning 0))) - (if (equal var 'time) (setq org-prefix-has-time t)) - (if (equal var 'tag) (setq org-prefix-has-tag t)) - (if (equal var 'effort) (setq org-prefix-has-effort t)) - (if (equal var 'breadcrumbs) (setq org-prefix-has-breadcrumbs t)) + (if (eq var 'time) (setq org-prefix-has-time t)) + (if (eq var 'tag) (setq org-prefix-has-tag t)) + (if (eq var 'effort) (setq org-prefix-has-effort t)) + (if (eq var 'breadcrumbs) (setq org-prefix-has-breadcrumbs t)) (setq f (concat "%" (match-string 2 s) "s")) - (when (equal var 'category) + (when (eq var 'category) (setq org-prefix-category-length (floor (abs (string-to-number (match-string 2 s))))) (setq org-prefix-category-max-length @@ -6727,10 +6698,13 @@ and stored in the variable `org-prefix-format-compiled'." (setq varform `(format ,f (org-eval ,(read (match-string 4 s))))) (if opt (setq varform - `(if (equal "" ,var) + `(if (or (equal "" ,var) (equal nil ,var)) "" - (format ,f (if (equal "" ,var) "" (concat ,var ,c))))) - (setq varform `(format ,f (if (equal ,var "") "" (concat ,var ,c (get-text-property 0 'extra-space ,var))))))) + (format ,f (concat ,var ,c)))) + (setq varform + `(format ,f (if (or (equal ,var "") + (equal ,var nil)) "" + (concat ,var ,c (get-text-property 0 'extra-space ,var))))))) (setq s (replace-match "%s" t nil s)) (push varform vars)) (setq vars (nreverse vars)) @@ -6817,7 +6791,7 @@ The optional argument TYPE tells the agenda type." (t org-agenda-max-tags))) (max-entries (cond ((listp org-agenda-max-entries) (cdr (assoc type org-agenda-max-entries))) - (t org-agenda-max-entries))) l) + (t org-agenda-max-entries)))) (when org-agenda-before-sorting-filter-function (setq list (delq nil @@ -6827,13 +6801,17 @@ The optional argument TYPE tells the agenda type." list (mapcar 'identity (sort list 'org-entries-lessp))) (when max-effort (setq list (org-agenda-limit-entries - list 'effort-minutes max-effort 'identity))) + list 'effort-minutes max-effort + (lambda (e) (or e (if org-sort-agenda-noeffort-is-high + 32767 -1)))))) (when max-todo (setq list (org-agenda-limit-entries list 'todo-state max-todo))) (when max-tags (setq list (org-agenda-limit-entries list 'tags max-tags))) (when max-entries (setq list (org-agenda-limit-entries list 'org-hd-marker max-entries))) + (when (and org-agenda-dim-blocked-tasks org-blocker-hook) + (setq list (mapcar #'org-agenda--mark-blocked-entry list))) (mapconcat 'identity list "\n"))) (defun org-agenda-limit-entries (list prop limit &optional fn) @@ -6845,26 +6823,39 @@ The optional argument TYPE tells the agenda type." (delq nil (mapcar (lambda (e) - (let ((pval (funcall fun (get-text-property 1 prop e)))) + (let ((pval (funcall + fun (get-text-property (1- (length e)) + prop e)))) (if pval (setq lim (+ lim pval))) (cond ((and pval (<= lim (abs limit))) e) ((and include (not pval)) e)))) list))) list))) -(defun org-agenda-limit-interactively () +(defun org-agenda-limit-interactively (remove) "In agenda, interactively limit entries to various maximums." - (interactive) - (let* ((max (read-char "Number of [e]ntries [t]odos [T]ags [E]ffort? ")) - (num (string-to-number (read-from-minibuffer "How many? ")))) - (cond ((equal max ?e) - (let ((org-agenda-max-entries num)) (org-agenda-redo))) - ((equal max ?t) - (let ((org-agenda-max-todos num)) (org-agenda-redo))) - ((equal max ?T) - (let ((org-agenda-max-tags num)) (org-agenda-redo))) - ((equal max ?E) - (let ((org-agenda-max-effort num)) (org-agenda-redo))))) + (interactive "P") + (if remove + (progn (setq org-agenda-max-entries nil + org-agenda-max-todos nil + org-agenda-max-tags nil + org-agenda-max-effort nil) + (org-agenda-redo)) + (let* ((max (read-char "Number of [e]ntries [t]odos [T]ags [E]ffort? ")) + (msg (cond ((= max ?E) "How many minutes? ") + ((= max ?e) "How many entries? ") + ((= max ?t) "How many TODO entries? ") + ((= max ?T) "How many tagged entries? ") + (t (user-error "Wrong input")))) + (num (string-to-number (read-from-minibuffer msg)))) + (cond ((equal max ?e) + (let ((org-agenda-max-entries num)) (org-agenda-redo))) + ((equal max ?t) + (let ((org-agenda-max-todos num)) (org-agenda-redo))) + ((equal max ?T) + (let ((org-agenda-max-tags num)) (org-agenda-redo))) + ((equal max ?E) + (let ((org-agenda-max-effort num)) (org-agenda-redo)))))) (org-agenda-fit-window-to-buffer)) (defun org-agenda-highlight-todo (x) @@ -6903,32 +6894,43 @@ The optional argument TYPE tells the agenda type." (list 'face (org-get-todo-face (match-string 2 x))) x) (when (match-end 1) - (setq x (concat (substring x 0 (match-end 1)) - (format org-agenda-todo-keyword-format - (match-string 2 x)) - (org-add-props " " (text-properties-at 0 x)) - (substring x (match-end 3))))))) + (setq x + (concat + (substring x 0 (match-end 1)) + (format org-agenda-todo-keyword-format + (match-string 2 x)) + ;; Remove `display' property as the icon could leak + ;; on the white space. + (org-add-props " " (org-plist-delete (text-properties-at 0 x) + 'display)) + (substring x (match-end 3))))))) x))) -(defsubst org-cmp-priority (a b) - "Compare the priorities of string A and B." - (let ((pa (or (get-text-property 1 'priority a) 0)) - (pb (or (get-text-property 1 'priority b) 0))) +(defsubst org-cmp-values (a b property) + "Compare the numeric value of text PROPERTY for string A and B." + (let ((pa (or (get-text-property (1- (length a)) property a) 0)) + (pb (or (get-text-property (1- (length b)) property b) 0))) (cond ((> pa pb) +1) ((< pa pb) -1)))) (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)) - (ea (or (get-text-property 1 'effort-minutes a) def)) - (eb (or (get-text-property 1 'effort-minutes b) def))) + ;; `effort-minutes' property is not directly accessible from + ;; the strings, but is stored as a property in `txt'. + (ea (or (get-text-property + 0 'effort-minutes (get-text-property 0 'txt a)) + def)) + (eb (or (get-text-property + 0 'effort-minutes (get-text-property 0 'txt b)) + def))) (cond ((> ea eb) +1) ((< ea eb) -1)))) (defsubst org-cmp-category (a b) "Compare the string values of categories of strings A and B." - (let ((ca (or (get-text-property 1 'org-category a) "")) - (cb (or (get-text-property 1 'org-category b) ""))) + (let ((ca (or (get-text-property (1- (length a)) 'org-category a) "")) + (cb (or (get-text-property (1- (length b)) 'org-category b) ""))) (cond ((string-lessp ca cb) -1) ((string-lessp cb ca) +1)))) @@ -6959,7 +6961,8 @@ The optional argument TYPE tells the agenda type." (let* ((pla (text-property-any 0 (length a) 'org-heading t a)) (plb (text-property-any 0 (length b) 'org-heading t b)) (ta (and pla (substring a pla))) - (tb (and plb (substring b plb)))) + (tb (and plb (substring b plb))) + (case-fold-search nil)) (when pla (if (string-match (concat "\\`[ \t]*" (or (get-text-property 0 'org-todo-regexp a) "") "\\([ \t]*\\[[a-zA-Z0-9]\\]\\)? *") ta) @@ -7038,8 +7041,11 @@ their type." (time-up (and (org-em 'time-up 'time-down ss) (org-cmp-time a b))) (time-down (if time-up (- time-up) nil)) + (stats-up (and (org-em 'stats-up 'stats-down ss) + (org-cmp-values a b 'org-stats))) + (stats-down (if stats-up (- stats-up) nil)) (priority-up (and (org-em 'priority-up 'priority-down ss) - (org-cmp-priority a b))) + (org-cmp-values a b 'priority))) (priority-down (if priority-up (- priority-up) nil)) (effort-up (and (org-em 'effort-up 'effort-down ss) (org-cmp-effort a b))) @@ -7080,15 +7086,32 @@ their type." 'face 'org-agenda-restriction-lock) (overlay-put org-agenda-restriction-lock-overlay 'help-echo "Agendas are currently limited to this subtree.") -(org-detach-overlay org-agenda-restriction-lock-overlay) +(delete-overlay org-agenda-restriction-lock-overlay) + +(defun org-agenda-set-restriction-lock-from-agenda (arg) + "Set the restriction lock to the agenda item at point from within the agenda. +When called with a `\\[universal-argument]' prefix, restrict to +the file which contains the item. +Argument ARG is the prefix argument." + (interactive "P") + (unless (derived-mode-p 'org-agenda-mode) + (user-error "Not in an Org agenda buffer")) + (let* ((marker (or (org-get-at-bol 'org-marker) + (org-agenda-error))) + (buffer (marker-buffer marker)) + (pos (marker-position marker))) + (with-current-buffer buffer + (goto-char pos) + (org-agenda-set-restriction-lock arg)))) ;;;###autoload (defun org-agenda-set-restriction-lock (&optional type) "Set restriction lock for agenda, to current subtree or file. -Restriction will be the file if TYPE is `file', or if TYPE is the -universal prefix `(4)', or if the cursor is before the first headline +Restriction will be the file if TYPE is `file', or if type is the +universal prefix \\='(4), or if the cursor is before the first headline in the file. Otherwise, restriction will be to the current subtree." (interactive "P") + (org-agenda-remove-restriction-lock 'noupdate) (and (equal type '(4)) (setq type 'file)) (setq type (cond (type type) @@ -7125,8 +7148,8 @@ in the file. Otherwise, restriction will be to the current subtree." (defun org-agenda-remove-restriction-lock (&optional noupdate) "Remove the agenda restriction lock." (interactive "P") - (org-detach-overlay org-agenda-restriction-lock-overlay) - (org-detach-overlay org-speedbar-restriction-lock-overlay) + (delete-overlay org-agenda-restriction-lock-overlay) + (delete-overlay org-speedbar-restriction-lock-overlay) (setq org-agenda-overriding-restriction nil) (setq org-agenda-restrict nil) (put 'org-agenda-files 'org-restrict nil) @@ -7138,7 +7161,9 @@ in the file. Otherwise, restriction will be to the current subtree." (defun org-agenda-maybe-redo () "If there is any window showing the agenda view, update it." - (let ((w (get-buffer-window org-agenda-buffer-name t)) + (let ((w (get-buffer-window (or org-agenda-this-buffer-name + org-agenda-buffer-name) + t)) (w0 (selected-window))) (when w (select-window w) @@ -7154,87 +7179,85 @@ in the file. Otherwise, restriction will be to the current subtree." (defun org-agenda-check-type (error &rest types) "Check if agenda buffer is of allowed type. If ERROR is non-nil, throw an error, otherwise just return nil. -Allowed types are 'agenda 'timeline 'todo 'tags 'search." - (if (not org-agenda-type) - (error "No Org agenda currently displayed") - (if (memq org-agenda-type types) - t - (if error - (error "Not allowed in %s-type agenda buffers" org-agenda-type) - nil)))) +Allowed types are `agenda' `todo' `tags' `search'." + (cond ((not org-agenda-type) + (error "No Org agenda currently displayed")) + ((memq org-agenda-type types) t) + (error + (error "Not allowed in %s-type agenda buffers" org-agenda-type)) + (t nil))) (defun org-agenda-Quit () - "Exit the agenda and kill buffers loaded by `org-agenda'. -Also restore the window configuration." + "Exit the agenda, killing the agenda buffer. +Like `org-agenda-quit', but kill the buffer even when +`org-agenda-sticky' is non-nil." (interactive) - (if org-agenda-columns-active - (org-columns-quit) - (let ((buf (current-buffer))) - (if (eq org-agenda-window-setup 'other-frame) - (progn - (org-agenda-reset-markers) - (kill-buffer buf) - (org-columns-remove-overlays) - (setq org-agenda-archives-mode nil) - (delete-frame)) - (and (not (eq org-agenda-window-setup 'current-window)) - (not (one-window-p)) - (delete-window)) - (org-agenda-reset-markers) - (kill-buffer buf) - (org-columns-remove-overlays) - (setq org-agenda-archives-mode nil))) - (setq org-agenda-buffer nil) - ;; Maybe restore the pre-agenda window configuration. - (and org-agenda-restore-windows-after-quit - (not (eq org-agenda-window-setup 'other-frame)) - org-agenda-pre-window-conf - (set-window-configuration org-agenda-pre-window-conf) - (setq org-agenda-pre-window-conf nil)))) + (org-agenda--quit)) (defun org-agenda-quit () - "Exit the agenda and restore the window configuration. -When `org-agenda-sticky' is non-nil, only bury the agenda." + "Exit the agenda. + +When `org-agenda-sticky' is non-nil, bury the agenda buffer +instead of killing it. + +When `org-agenda-restore-windows-after-quit' is non-nil, restore +the pre-agenda window configuration. + +When column view is active, exit column view instead of the +agenda." (interactive) - (if (and (eq org-indirect-buffer-display 'other-window) - org-last-indirect-buffer) - (let ((org-last-indirect-window - (get-buffer-window org-last-indirect-buffer))) - (if org-last-indirect-window - (delete-window org-last-indirect-window)))) + (org-agenda--quit org-agenda-sticky)) + +(defun org-agenda--quit (&optional bury) (if org-agenda-columns-active (org-columns-quit) - (if org-agenda-sticky - (let ((buf (current-buffer))) - (if (eq org-agenda-window-setup 'other-frame) - (progn - (delete-frame)) - (and (not (eq org-agenda-window-setup 'current-window)) - (not (one-window-p)) - (delete-window))) + (let ((wconf org-agenda-pre-window-conf) + (buf (current-buffer)) + (org-agenda-last-indirect-window + (and (eq org-indirect-buffer-display 'other-window) + org-agenda-last-indirect-buffer + (get-buffer-window org-agenda-last-indirect-buffer)))) + (cond + ((eq org-agenda-window-setup 'other-frame) + (delete-frame)) + ((and org-agenda-restore-windows-after-quit + wconf) + ;; Maybe restore the pre-agenda window configuration. Reset + ;; `org-agenda-pre-window-conf' before running + ;; `set-window-configuration', which loses the current buffer. + (setq org-agenda-pre-window-conf nil) + (set-window-configuration wconf)) + (t + (when org-agenda-last-indirect-window + (delete-window org-agenda-last-indirect-window)) + (and (not (eq org-agenda-window-setup 'current-window)) + (not (one-window-p)) + (delete-window)))) + (if bury + ;; Set the agenda buffer as the current buffer instead of + ;; passing it as an argument to `bury-buffer' so that + ;; `bury-buffer' removes it from the window. (with-current-buffer buf - (bury-buffer) - ;; Maybe restore the pre-agenda window configuration. - (and org-agenda-restore-windows-after-quit - (not (eq org-agenda-window-setup 'other-frame)) - org-agenda-pre-window-conf - (set-window-configuration org-agenda-pre-window-conf) - (setq org-agenda-pre-window-conf nil)))) - (org-agenda-Quit)))) + (bury-buffer)) + (kill-buffer buf) + (setq org-agenda-archives-mode nil + org-agenda-buffer nil))))) (defun org-agenda-exit () - "Exit the agenda and restore the window configuration. -Also kill Org-mode buffers loaded by `org-agenda'. Org-mode -buffers visited directly by the user will not be touched." + "Exit the agenda, killing Org buffers loaded by the agenda. +Like `org-agenda-Quit', but kill any buffers that were created by +the agenda. Org buffers visited directly by the user will not be +touched. Also, exit the agenda even if it is in column view." (interactive) + (when org-agenda-columns-active + (org-columns-quit)) (org-release-buffers org-agenda-new-buffers) (setq org-agenda-new-buffers nil) (org-agenda-Quit)) (defun org-agenda-kill-all-agenda-buffers () "Kill all buffers in `org-agenda-mode'. -This is used when toggling sticky agendas. -You can also explicitly invoke it with `C-c a C-k'." +This is used when toggling sticky agendas." (interactive) (let (blist) (dolist (buf (buffer-list)) @@ -7267,6 +7290,9 @@ in the agenda." (cat-preset (get 'org-agenda-category-filter :preset-filter)) (re-filter org-agenda-regexp-filter) (re-preset (get 'org-agenda-regexp-filter :preset-filter)) + (effort-filter org-agenda-effort-filter) + (effort-preset (get 'org-agenda-effort-filter :preset-filter)) + (org-agenda-tag-filter-while-redo (or tag-filter tag-preset)) (cols org-agenda-columns-active) (line (org-current-line)) (window-line (- line (org-current-line (window-start)))) @@ -7284,6 +7310,7 @@ in the agenda." (put 'org-agenda-tag-filter :preset-filter nil) (put 'org-agenda-category-filter :preset-filter nil) (put 'org-agenda-regexp-filter :preset-filter nil) + (put 'org-agenda-effort-filter :preset-filter nil) (and cols (org-columns-quit)) (message "Rebuilding agenda buffer...") (if series-redo-cmd @@ -7294,19 +7321,23 @@ in the agenda." org-agenda-tag-filter tag-filter org-agenda-category-filter cat-filter org-agenda-regexp-filter re-filter + org-agenda-effort-filter effort-filter org-agenda-top-headline-filter top-hl-filter) (message "Rebuilding agenda buffer...done") (put 'org-agenda-tag-filter :preset-filter tag-preset) (put 'org-agenda-category-filter :preset-filter cat-preset) (put 'org-agenda-regexp-filter :preset-filter re-preset) + (put 'org-agenda-effort-filter :preset-filter effort-preset) (let ((tag (or tag-filter tag-preset)) (cat (or cat-filter cat-preset)) - (re (or re-filter re-preset))) - (when tag (org-agenda-filter-apply tag 'tag)) + (effort (or effort-filter effort-preset)) + (re (or re-filter re-preset))) + (when tag (org-agenda-filter-apply tag 'tag t)) (when cat (org-agenda-filter-apply cat 'category)) + (when effort (org-agenda-filter-apply effort 'effort)) (when re (org-agenda-filter-apply re 'regexp))) (and top-hl-filter (org-agenda-filter-top-headline-apply top-hl-filter)) - (and cols (org-called-interactively-p 'any) (org-agenda-columns)) + (and cols (called-interactively-p 'any) (org-agenda-columns)) (org-goto-line line) (recenter window-line))) @@ -7326,32 +7357,36 @@ With a prefix argument, do so in all agenda buffers." (defvar org-agenda-filtered-by-category nil) (defun org-agenda-filter-by-category (strip) - "Keep only those lines in the agenda buffer that have a specific category. -The category is that of the current line." + "Filter lines in the agenda buffer that have a specific category. +The category is that of the current line. +Without prefix argument, keep only the lines of that category. +With a prefix argument, exclude the lines of that category. +" (interactive "P") (if (and org-agenda-filtered-by-category org-agenda-category-filter) (org-agenda-filter-show-all-cat) - (let ((cat (org-no-properties (get-text-property (point) 'org-category)))) + (let ((cat (org-no-properties (org-get-at-eol 'org-category 1)))) (cond ((and cat strip) (org-agenda-filter-apply (push (concat "-" cat) org-agenda-category-filter) 'category)) - ((and cat) + (cat (org-agenda-filter-apply (setq org-agenda-category-filter (list (concat "+" cat))) 'category)) (t (error "No category at point")))))) (defun org-find-top-headline (&optional pos) - "Find the topmost parent headline and return it." + "Find the topmost parent headline and return it. +POS when non-nil is the marker or buffer position to start the +search from." (save-excursion - (with-current-buffer (if pos (marker-buffer pos) (current-buffer)) - (if pos (goto-char pos)) - ;; Skip up to the topmost parent - (while (ignore-errors (outline-up-heading 1) t)) - (ignore-errors - (nth 4 (org-heading-components)))))) + (with-current-buffer (if (markerp pos) (marker-buffer pos) (current-buffer)) + (when pos (goto-char pos)) + ;; Skip up to the topmost parent. + (while (org-up-heading-safe)) + (ignore-errors (nth 4 (org-heading-components)))))) (defvar org-agenda-filtered-by-top-headline nil) (defun org-agenda-filter-by-top-headline (strip) @@ -7386,6 +7421,49 @@ With two prefix arguments, remove the regexp filters." (org-agenda-filter-show-all-re) (message "Regexp filter removed"))) +(defvar org-agenda-effort-filter nil) +(defun org-agenda-filter-by-effort (strip) + "Filter agenda entries by effort. +With no prefix argument, keep entries matching the effort condition. +With one prefix argument, filter out entries matching the condition. +With two prefix arguments, remove the effort filters." + (interactive "P") + (cond + ((member strip '(nil 4)) + (let* ((efforts (split-string + (or (cdr (assoc (concat org-effort-property "_ALL") + org-global-properties)) + "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. + (allowed-keys (if (null efforts) nil + (mapcar (lambda (n) (mod n 10)) ;turn 10 into 0 + (number-sequence 1 (length efforts))))) + (op nil)) + (while (not (memq op '(?< ?> ?=))) + (setq op (read-char-exclusive "Effort operator? (> = or <)"))) + ;; Select appropriate duration. Ignore non-digit characters. + (let ((prompt + (apply #'format + (concat "Effort %c " + (mapconcat (lambda (s) (concat "[%d]" s)) + efforts + " ")) + op allowed-keys)) + (eff -1)) + (while (not (memq eff allowed-keys)) + (message prompt) + (setq eff (- (read-char-exclusive) 48))) + (setq org-agenda-effort-filter + (list (concat (if strip "-" "+") + (char-to-string op) + ;; Numbering is 1 2 3 ... 9 0, but we want + ;; 0 1 2 ... 8 9. + (nth (mod (1- eff) 10) efforts))))) + (org-agenda-filter-apply org-agenda-effort-filter 'effort))) + (t (org-agenda-filter-show-all-effort) + (message "Effort filter removed")))) + (defun org-agenda-filter-remove-all () "Remove all filters from the current agenda buffer." (interactive) @@ -7397,15 +7475,24 @@ With two prefix arguments, remove the regexp filters." (org-agenda-filter-show-all-re)) (when org-agenda-top-headline-filter (org-agenda-filter-show-all-top-filter)) + (when org-agenda-effort-filter + (org-agenda-filter-show-all-effort)) (org-agenda-finalize)) -(defun org-agenda-filter-by-tag (strip &optional char narrow) +(defun org-agenda-filter-by-tag (arg &optional char exclude) "Keep only those lines in the agenda buffer that have a specific tag. + The tag is selected with its fast selection letter, as configured. -With prefix argument STRIP, remove all lines that do have the tag. -A lisp caller can specify CHAR. NARROW means that the new tag should be -used to narrow the search - the interactive user can also press `-' or `+' -to switch to narrowing." + +With a `\\[universal-argument]' prefix, exclude the agenda search. + +With a `\\[universal-argument] \\[universal-argument]' prefix, filter the literal tag, \ +i.e. don't +filter on all its group members. + +A lisp caller can specify CHAR. EXCLUDE means that the new tag +should be used to exclude the search - the interactive user can +also press `-' or `+' to switch between filtering and excluding." (interactive "P") (let* ((alist org-tag-alist-for-agenda) (tag-chars (mapconcat @@ -7413,54 +7500,35 @@ to switch to narrowing." (cdr x)) (char-to-string (cdr x)) "")) - alist "")) - (efforts (org-split-string - (or (cdr (assoc (concat org-effort-property "_ALL") - org-global-properties)) - "0 0:10 0:30 1:00 2:00 3:00 4:00 5:00 6:00 7:00 8:00" - ""))) - (effort-op org-agenda-filter-effort-default-operator) - (effort-prompt "") + org-tag-alist-for-agenda "")) + (valid-char-list (append '(?\t ?\r ?/ ?. ?\s ?q) + (string-to-list tag-chars))) + (exclude (or exclude (equal arg '(4)))) + (expand (not (equal arg '(16)))) (inhibit-read-only t) (current org-agenda-tag-filter) - maybe-refresh a n tag) + a n tag) (unless char - (message - "%s by tag [%s ], [TAB], %s[/]:off, [+-]:narrow, [>=<?]:effort: " - (if narrow "Narrow" "Filter") tag-chars - (if org-agenda-auto-exclude-function "[RET], " "")) - (setq char (read-char-exclusive))) - (when (member char '(?+ ?-)) - ;; Narrowing down - (cond ((equal char ?-) (setq strip t narrow t)) - ((equal char ?+) (setq strip nil narrow t))) - (message - "Narrow by tag [%s ], [TAB], [/]:off, [>=<]:effort: " tag-chars) - (setq char (read-char-exclusive))) - (when (member char '(?< ?> ?= ??)) - ;; An effort operator - (setq effort-op (char-to-string char)) - (setq alist nil) ; to make sure it will be interpreted as effort. - (unless (equal char ??) - (loop for i from 0 to 9 do - (setq effort-prompt - (concat - effort-prompt " [" - (if (= i 9) "0" (int-to-string (1+ i))) - "]" (nth i efforts)))) - (message "Effort%s: %s " effort-op effort-prompt) + (while (not (memq char valid-char-list)) + (message + "%s by tag [%s ]:tag-char, [TAB]:tag, %s[/]:off, [+/-]:filter/exclude%s, [q]:quit" + (if exclude "Exclude" "Filter") + tag-chars + (if org-agenda-auto-exclude-function "[RET], " "") + (if expand "" ", no grouptag expand")) (setq char (read-char-exclusive)) - (when (or (< char ?0) (> char ?9)) - (error "Need 1-9,0 to select effort")))) - (when (equal char ?\t) + ;; Excluding or filtering down + (cond ((eq char ?-) (setq exclude t)) + ((eq char ?+) (setq exclude nil))))) + (when (eq char ?\t) (unless (local-variable-p 'org-global-tags-completion-table (current-buffer)) - (org-set-local 'org-global-tags-completion-table - (org-global-tags-completion-table))) + (setq-local org-global-tags-completion-table + (org-global-tags-completion-table))) (let ((completion-ignore-case t)) - (setq tag (org-icompleting-read - "Tag: " org-global-tags-completion-table)))) + (setq tag (completing-read + "Tag: " org-global-tags-completion-table nil t)))) (cond - ((equal char ?\r) + ((eq char ?\r) (org-agenda-filter-show-all-tag) (when org-agenda-auto-exclude-function (setq org-agenda-tag-filter nil) @@ -7469,39 +7537,27 @@ to switch to narrowing." (if modifier (push modifier org-agenda-tag-filter)))) (if (not (null org-agenda-tag-filter)) - (org-agenda-filter-apply org-agenda-tag-filter 'tag))) - (setq maybe-refresh t)) - ((equal char ?/) + (org-agenda-filter-apply org-agenda-tag-filter 'tag expand)))) + ((eq char ?/) (org-agenda-filter-show-all-tag) (when (get 'org-agenda-tag-filter :preset-filter) - (org-agenda-filter-apply org-agenda-tag-filter 'tag)) - (setq maybe-refresh t)) - ((equal char ?. ) + (org-agenda-filter-apply org-agenda-tag-filter 'tag expand))) + ((eq char ?.) (setq org-agenda-tag-filter (mapcar (lambda(tag) (concat "+" tag)) (org-get-at-bol 'tags))) - (org-agenda-filter-apply org-agenda-tag-filter 'tag) - (setq maybe-refresh t)) - ((or (equal char ?\ ) + (org-agenda-filter-apply org-agenda-tag-filter 'tag expand)) + ((eq char ?q)) ;If q, abort (even if there is a q-key for a tag...) + ((or (eq char ?\s) (setq a (rassoc char alist)) - (and (>= char ?0) (<= char ?9) - (setq n (if (= char ?0) 9 (- char ?0 1)) - tag (concat effort-op (nth n efforts)) - a (cons tag nil))) - (and (= char ??) - (setq tag "?eff") - a (cons tag nil)) (and tag (setq a (cons tag nil)))) (org-agenda-filter-show-all-tag) (setq tag (car a)) (setq org-agenda-tag-filter - (cons (concat (if strip "-" "+") tag) - (if narrow current nil))) - (org-agenda-filter-apply org-agenda-tag-filter 'tag) - (setq maybe-refresh t)) - (t (error "Invalid tag selection character %c" char))) - (when maybe-refresh - (org-agenda-redo)))) + (cons (concat (if exclude "-" "+") tag) + current)) + (org-agenda-filter-apply org-agenda-tag-filter 'tag expand)) + (t (error "Invalid tag selection character %c" char))))) (defun org-agenda-get-represented-tags () "Get a list of all tags currently represented in the agenda." @@ -7514,13 +7570,11 @@ to switch to narrowing." (get-text-property (point) 'tags)))) tags)) -(defun org-agenda-filter-by-tag-refine (strip &optional char) - "Refine the current filter. See `org-agenda-filter-by-tag'." - (interactive "P") - (org-agenda-filter-by-tag strip char 'refine)) -(defun org-agenda-filter-make-matcher (filter type) - "Create the form that tests a line for agenda filter." +(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) (cond ;; Tag filter @@ -7530,28 +7584,11 @@ to switch to narrowing." (append (get 'org-agenda-tag-filter :preset-filter) filter))) (dolist (x filter) - (let ((nfilter (org-agenda-filter-expand-tags filter)) nf nf1 - (ffunc - (lambda (nf0 nf01 fltr notgroup op) - (dolist (x fltr) - (if (member x '("-" "+")) - (setq nf01 (if (equal x "-") 'tags '(not tags))) - (if (string-match "[<=>?]" x) - (setq nf01 (org-agenda-filter-effort-form x)) - (setq nf01 (list 'member (downcase (substring x 1)) - 'tags))) - (when (equal (string-to-char x) ?-) - (setq nf01 (list 'not nf01)) - (when (not notgroup) (setq op 'and)))) - (push nf01 nf0)) - (if notgroup - (push (cons 'and nf0) f) - (push (cons (or op 'or) nf0) f))))) - (cond ((equal filter '("+")) - (setq f (list (list 'not 'tags)))) - ((equal nfilter filter) - (funcall ffunc f1 f filter t nil)) - (t (funcall ffunc nf1 nf nfilter nil nil)))))) + (let ((op (string-to-char x))) + (if expand (setq x (org-agenda-filter-expand-tags (list x) t)) + (setq x (list x))) + (setq f1 (org-agenda-filter-make-matcher-tag-exp x op)) + (push f1 f)))) ;; Category filter ((eq type 'category) (setq filter @@ -7573,9 +7610,35 @@ to switch to narrowing." (if (equal "-" (substring x 0 1)) (setq f1 (list 'not (list 'string-match (substring x 1) 'txt))) (setq f1 (list 'string-match (substring x 1) 'txt))) - (push f1 f)))) + (push f1 f))) + ;; Effort filter + ((eq type 'effort) + (setq filter + (delete-dups + (append (get 'org-agenda-effort-filter :preset-filter) + filter))) + (dolist (x filter) + (push (org-agenda-filter-effort-form x) f)))) (cons 'and (nreverse f)))) +(defun org-agenda-filter-make-matcher-tag-exp (tags op) + "Return a form associated to tag-expression TAGS. +Build a form testing a line for agenda filter for +tag-expressions. OP is an operator of type CHAR that allows the +function to set the right switches in the returned form." + (let (form) + ;; Any of the expressions can match if OP is +, all must match if + ;; the operator is -. + (dolist (x tags (cons (if (eq op ?-) 'and 'or) form)) + (let* ((tag (substring x 1)) + (f (cond + ((string= "" tag) '(not tags)) + ((and (string-match-p "\\`{" tag) (string-match-p "}\\'" tag)) + ;; TAG is a regexp. + (list 'org-match-any-p (substring tag 1 -1) 'tags)) + (t (list 'member (downcase tag) 'tags))))) + (push (if (eq op ?-) (list 'not f) f) form))))) + (defun org-agenda-filter-effort-form (e) "Return the form to compare the effort of the current line with what E says. E looks like \"+<2:25\"." @@ -7587,16 +7650,17 @@ E looks like \"+<2:25\"." ((equal op ??) op) (t '=))) (list 'org-agenda-compare-effort (list 'quote op) - (org-duration-string-to-minutes e)))) + (org-duration-to-minutes e)))) (defun org-agenda-compare-effort (op value) "Compare the effort of the current line with VALUE, using OP. If the line does not have an effort defined, return nil." - (let ((eff (org-get-at-bol 'effort-minutes))) - (if (equal op ??) - (not eff) - (funcall op (or eff (if org-sort-agenda-noeffort-is-high 32767 0)) - value)))) + ;; `effort-minutes' property cannot be extracted directly from + ;; 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)) + value))) (defun org-agenda-filter-expand-tags (filter &optional no-operator) "Expand group tags in FILTER for the agenda. @@ -7616,12 +7680,14 @@ When NO-OPERATOR is non-nil, do not add the + operator to returned tags." (reverse rtn)) filter)) -(defun org-agenda-filter-apply (filter type) - "Set FILTER as the new agenda filter and apply it." +(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." ;; Deactivate `org-agenda-entry-text-mode' when filtering (if 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)) + (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 @@ -7633,13 +7699,9 @@ When NO-OPERATOR is non-nil, do not add the + operator to returned tags." (while (not (eobp)) (if (org-get-at-bol 'org-marker) (progn - (setq tags ; used in eval - (apply 'append - (mapcar (lambda (f) - (org-agenda-filter-expand-tags (list f) t)) - (org-get-at-bol 'tags))) - cat (get-text-property (point) 'org-category) - txt (get-text-property (point) 'txt)) + (setq tags (org-get-at-bol 'tags) + cat (org-get-at-eol 'org-category 1) + txt (org-get-at-bol 'txt)) (if (not (eval org-agenda-filter-form)) (org-agenda-filter-hide-line type)) (beginning-of-line 2)) @@ -7692,6 +7754,8 @@ When NO-OPERATOR is non-nil, do not add the + operator to returned tags." (org-agenda-remove-filter 'tag)) (defun org-agenda-filter-show-all-re nil (org-agenda-remove-filter 'regexp)) +(defun org-agenda-filter-show-all-effort nil + (org-agenda-remove-filter 'effort)) (defun org-agenda-filter-show-all-cat nil (org-agenda-remove-filter 'category)) (defun org-agenda-filter-show-all-top-filter nil @@ -7719,7 +7783,7 @@ Negative selection means regexp must not match for selection of an entry." (org-agenda-manipulate-query ?\})) (defun org-agenda-manipulate-query (char) (cond - ((memq org-agenda-type '(timeline agenda)) + ((eq org-agenda-type 'agenda) (let ((org-agenda-include-inactive-timestamps t)) (org-agenda-redo)) (message "Display now includes inactive timestamps as well")) @@ -7782,7 +7846,7 @@ Negative selection means regexp must not match for selection of an entry." (defun org-agenda-goto-today () "Go to today." (interactive) - (org-agenda-check-type t 'timeline 'agenda) + (org-agenda-check-type t 'agenda) (let* ((args (get-text-property (min (1- (point-max)) (point)) 'org-last-args)) (curspan (nth 2 args)) (tdpos (text-property-any (point-min) (point-max) 'org-today t))) @@ -7790,7 +7854,7 @@ Negative selection means regexp must not match for selection of an entry." (tdpos (goto-char tdpos)) ((eq org-agenda-type 'agenda) (let* ((sd (org-agenda-compute-starting-span - (org-today) (or curspan org-agenda-ndays org-agenda-span))) + (org-today) (or curspan org-agenda-span))) (org-agenda-overriding-arguments args)) (setf (nth 1 org-agenda-overriding-arguments) sd) (org-agenda-redo) @@ -7803,27 +7867,40 @@ Negative selection means regexp must not match for selection of an entry." (text-property-any (point-min) (point-max) 'org-today t) (text-property-any (point-min) (point-max) 'org-agenda-type 'agenda) (and (get-text-property (min (1- (point-max)) (point)) 'org-series) - (org-agenda-goto-block-beginning)) + (org-agenda-backward-block)) (point-min)))) -(defun org-agenda-goto-block-beginning () - "Go the agenda block beginning." +(defun org-agenda-backward-block () + "Move backward by one agenda block." (interactive) - (if (not (derived-mode-p 'org-agenda-mode)) - (error "Cannot execute this command outside of org-agenda-mode buffers") - (let (dest) - (save-excursion - (unless (looking-at "\\'") - (forward-char)) - (let* ((prop 'org-agenda-structural-header) - (p (previous-single-property-change (point) prop)) - (n (next-single-property-change (or (and (looking-at "\\`") 1) - (1- (point))) prop))) - (setq dest (cond ((eq n (point-at-eol)) (1- n)) (p (1- p)))))) - (if (not dest) - (error "Cannot find the beginning of the blog") - (goto-char dest) - (move-beginning-of-line 1))))) + (org-agenda-forward-block 'backward)) + +(defun org-agenda-forward-block (&optional backward) + "Move forward by one agenda block. +When optional argument BACKWARD is set, go backward" + (interactive) + (cond ((not (derived-mode-p 'org-agenda-mode)) + (user-error + "Cannot execute this command outside of org-agenda-mode buffers")) + ((looking-at (if backward "\\`" "\\'")) + (message "Already at the %s block" (if backward "first" "last"))) + (t (let ((pos (prog1 (point) + (ignore-errors (if backward (backward-char 1) + (move-end-of-line 1))))) + (f (if backward + 'previous-single-property-change + 'next-single-property-change)) + moved dest) + (while (and (setq dest (funcall + f (point) 'org-agenda-structural-header)) + (not (get-text-property + (point) 'org-agenda-structural-header))) + (setq moved t) + (goto-char dest)) + (if moved (move-beginning-of-line 1) + (goto-char (if backward (point-min) (point-max))) + (move-beginning-of-line 1) + (message "No %s block" (if backward "previous" "further"))))))) (defun org-agenda-later (arg) "Go forward in time by the current span. @@ -7877,71 +7954,77 @@ With prefix ARG, go backward that many times the current span." (message "View: [d]ay [w]eek for[t]night [m]onth [y]ear [SPC]reset [q]uit/abort time[G]rid [[]inactive [f]ollow [l]og [L]og-all [c]lockcheck [a]rch-trees [A]rch-files clock[R]eport include[D]iary [E]ntryText") - (let ((a (read-char-exclusive))) - (case a - (?\ (call-interactively 'org-agenda-reset-view)) - (?d (call-interactively 'org-agenda-day-view)) - (?w (call-interactively 'org-agenda-week-view)) - (?t (call-interactively 'org-agenda-fortnight-view)) - (?m (call-interactively 'org-agenda-month-view)) - (?y (call-interactively 'org-agenda-year-view)) - (?l (call-interactively 'org-agenda-log-mode)) - (?L (org-agenda-log-mode '(4))) - (?c (org-agenda-log-mode 'clockcheck)) - ((?F ?f) (call-interactively 'org-agenda-follow-mode)) - (?a (call-interactively 'org-agenda-archives-mode)) - (?A (org-agenda-archives-mode 'files)) - ((?R ?r) (call-interactively 'org-agenda-clockreport-mode)) - ((?E ?e) (call-interactively 'org-agenda-entry-text-mode)) - (?G (call-interactively 'org-agenda-toggle-time-grid)) - (?D (call-interactively 'org-agenda-toggle-diary)) - (?\! (call-interactively 'org-agenda-toggle-deadlines)) - (?\[ (let ((org-agenda-include-inactive-timestamps t)) - (org-agenda-check-type t 'timeline 'agenda) - (org-agenda-redo)) - (message "Display now includes inactive timestamps as well")) - (?q (message "Abort")) - (otherwise (error "Invalid key" ))))) + (pcase (read-char-exclusive) + (?\ (call-interactively 'org-agenda-reset-view)) + (?d (call-interactively 'org-agenda-day-view)) + (?w (call-interactively 'org-agenda-week-view)) + (?t (call-interactively 'org-agenda-fortnight-view)) + (?m (call-interactively 'org-agenda-month-view)) + (?y (call-interactively 'org-agenda-year-view)) + (?l (call-interactively 'org-agenda-log-mode)) + (?L (org-agenda-log-mode '(4))) + (?c (org-agenda-log-mode 'clockcheck)) + ((or ?F ?f) (call-interactively 'org-agenda-follow-mode)) + (?a (call-interactively 'org-agenda-archives-mode)) + (?A (org-agenda-archives-mode 'files)) + ((or ?R ?r) (call-interactively 'org-agenda-clockreport-mode)) + ((or ?E ?e) (call-interactively 'org-agenda-entry-text-mode)) + (?G (call-interactively 'org-agenda-toggle-time-grid)) + (?D (call-interactively 'org-agenda-toggle-diary)) + (?\! (call-interactively 'org-agenda-toggle-deadlines)) + (?\[ (let ((org-agenda-include-inactive-timestamps t)) + (org-agenda-check-type t 'agenda) + (org-agenda-redo)) + (message "Display now includes inactive timestamps as well")) + (?q (message "Abort")) + (key (user-error "Invalid key: %s" key)))) (defun org-agenda-reset-view () "Switch to default view for agenda." (interactive) - (org-agenda-change-time-span (or org-agenda-ndays org-agenda-span))) + (org-agenda-change-time-span org-agenda-span)) + (defun org-agenda-day-view (&optional day-of-month) "Switch to daily view for agenda. With argument DAY-OF-MONTH, switch to that day of the month." (interactive "P") (org-agenda-change-time-span 'day day-of-month)) + (defun org-agenda-week-view (&optional iso-week) - "Switch to daily view for agenda. + "Switch to weekly view for agenda. With argument ISO-WEEK, switch to the corresponding ISO week. -If ISO-WEEK has more then 2 digits, only the last two encode the -week. Any digits before this encode a year. So 200712 means -week 12 of year 2007. Years in the range 1938-2037 can also be -written as 2-digit years." +If ISO-WEEK has more then 2 digits, only the last two encode +the week. Any digits before this encode a year. So 200712 +means week 12 of year 2007. Years ranging from 70 years ago +to 30 years in the future can also be written as 2-digit years." (interactive "P") (org-agenda-change-time-span 'week iso-week)) + (defun org-agenda-fortnight-view (&optional iso-week) - "Switch to daily view for agenda. + "Switch to fortnightly view for agenda. With argument ISO-WEEK, switch to the corresponding ISO week. -If ISO-WEEK has more then 2 digits, only the last two encode the -week. Any digits before this encode a year. So 200712 means -week 12 of year 2007. Years in the range 1938-2037 can also be -written as 2-digit years." +If ISO-WEEK has more then 2 digits, only the last two encode +the week. Any digits before this encode a year. So 200712 +means week 12 of year 2007. Years ranging from 70 years ago +to 30 years in the future can also be written as 2-digit years." (interactive "P") (org-agenda-change-time-span 'fortnight iso-week)) + (defun org-agenda-month-view (&optional month) "Switch to monthly view for agenda. -With argument MONTH, switch to that month." +With argument MONTH, switch to that month. If MONTH has more +then 2 digits, only the last two encode the month. Any digits +before this encode a year. So 200712 means December year 2007. +Years ranging from 70 years ago to 30 years in the future can +also be written as 2-digit years." (interactive "P") (org-agenda-change-time-span 'month month)) + (defun org-agenda-year-view (&optional year) "Switch to yearly view for agenda. -With argument YEAR, switch to that year. -If MONTH has more then 2 digits, only the last two encode the -month. Any digits before this encode a year. So 200712 means -December year 2007. Years in the range 1938-2037 can also be -written as 2-digit years." +With argument YEAR, switch to that year. Years ranging from 70 +years ago to 30 years in the future can also be written as +2-digit years." (interactive "P") (when year (setq year (org-small-year-to-year year))) @@ -7999,7 +8082,7 @@ so that the date SD will be in that range." (setq y1 (org-small-year-to-year (/ n 100)) n (mod n 100))) (setq sd - (calendar-absolute-from-iso + (calendar-iso-to-absolute (list n 1 (or y1 (nth 2 (calendar-iso-from-absolute sd))))))))) ((eq span 'month) @@ -8017,7 +8100,7 @@ so that the date SD will be in that range." (defun org-agenda-next-date-line (&optional arg) "Jump to the next line indicating a date in agenda buffer." (interactive "p") - (org-agenda-check-type t 'agenda 'timeline) + (org-agenda-check-type t 'agenda) (beginning-of-line 1) ;; This does not work if user makes date format that starts with a blank (if (looking-at "^\\S-") (forward-char 1)) @@ -8030,7 +8113,7 @@ so that the date SD will be in that range." (defun org-agenda-previous-date-line (&optional arg) "Jump to the previous line indicating a date in agenda buffer." (interactive "p") - (org-agenda-check-type t 'agenda 'timeline) + (org-agenda-check-type t 'agenda) (beginning-of-line 1) (if (not (re-search-backward "^\\S-" nil t arg)) (error "No previous date before this line in this buffer"))) @@ -8045,7 +8128,7 @@ so that the date SD will be in that range." (defun org-unhighlight () "Detach overlay INDEX." - (org-detach-overlay org-hl)) + (delete-overlay org-hl)) (defun org-unhighlight-once () "Remove the highlight from its position, and this function from the hook." @@ -8102,11 +8185,14 @@ so that the date SD will be in that range." (defun org-agenda-log-mode (&optional special) "Toggle log mode in an agenda buffer. + With argument SPECIAL, show all possible log items, not only the ones configured in `org-agenda-log-mode-items'. -With a double `C-u' prefix arg, show *only* log items, nothing else." + +With a `\\[universal-argument] \\[universal-argument]' prefix, show *only* \ +log items, nothing else." (interactive "P") - (org-agenda-check-type t 'agenda 'timeline) + (org-agenda-check-type t 'agenda) (setq org-agenda-show-log (cond ((equal special '(16)) 'only) @@ -8118,8 +8204,7 @@ With a double `C-u' prefix arg, show *only* log items, nothing else." (setq org-agenda-start-with-log-mode org-agenda-show-log) (org-agenda-set-mode-name) (org-agenda-redo) - (message "Log mode is %s" - (if org-agenda-show-log "on" "off"))) + (message "Log mode is %s" (if org-agenda-show-log "on" "off"))) (defun org-agenda-archives-mode (&optional with-files) "Toggle inclusion of items in trees marked with :ARCHIVE:. @@ -8191,7 +8276,7 @@ When called with a prefix argument, include all archive files as well." (t "")) (if (or org-agenda-category-filter (get 'org-agenda-category-filter :preset-filter)) - '(:eval (org-propertize + '(:eval (propertize (concat " <" (mapconcat 'identity @@ -8204,7 +8289,7 @@ When called with a prefix argument, include all archive files as well." 'help-echo "Category used in filtering")) "") (if (or org-agenda-tag-filter (get 'org-agenda-tag-filter :preset-filter)) - '(:eval (org-propertize + '(:eval (propertize (concat " {" (mapconcat 'identity @@ -8215,9 +8300,22 @@ When called with a prefix argument, include all archive files as well." "}") 'face 'org-agenda-filter-tags 'help-echo "Tags used in filtering")) "") + (if (or org-agenda-effort-filter + (get 'org-agenda-effort-filter :preset-filter)) + '(:eval (propertize + (concat " {" + (mapconcat + 'identity + (append + (get 'org-agenda-effort-filter :preset-filter) + org-agenda-effort-filter) + "") + "}") + 'face 'org-agenda-filter-effort + 'help-echo "Effort conditions used in filtering")) "") (if (or org-agenda-regexp-filter (get 'org-agenda-regexp-filter :preset-filter)) - '(:eval (org-propertize + '(:eval (propertize (concat " [" (mapconcat 'identity @@ -8236,9 +8334,6 @@ When called with a prefix argument, include all archive files as well." (if org-agenda-clockreport-mode " Clock" ""))) (force-mode-line-update)) -(define-obsolete-function-alias - 'org-agenda-post-command-hook 'org-agenda-update-agenda-type "24.3") - (defun org-agenda-update-agenda-type () "Update the agenda type after each command." (setq org-agenda-type @@ -8301,7 +8396,7 @@ When called with a prefix argument, include all archive files as well." (message "No tags associated with this line")))) (defun org-agenda-goto (&optional highlight) - "Go to the Org-mode file which contains the item at point." + "Go to the entry at point in the corresponding Org file." (interactive) (let* ((marker (or (org-get-at-bol 'org-marker) (org-agenda-error))) @@ -8313,12 +8408,11 @@ When called with a prefix argument, include all archive files as well." (goto-char pos) (when (derived-mode-p 'org-mode) (org-show-context 'agenda) - (save-excursion - (and (outline-next-heading) - (org-flag-heading nil)))) ; show the next heading - (when (outline-invisible-p) - (show-entry)) ; display invisible text - (recenter (/ (window-height) 2)) + (recenter (/ (window-height) 2)) + (org-back-to-heading t) + (let ((case-fold-search nil)) + (when (re-search-forward org-complex-heading-regexp nil t) + (goto-char (match-beginning 4))))) (run-hooks 'org-agenda-after-show-hook) (and highlight (org-highlight (point-at-bol) (point-at-eol))))) @@ -8405,7 +8499,7 @@ Point is in the buffer where the item originated.") (org-remove-subtree-entries-from-agenda)) (org-back-to-heading t) (funcall cmd))) - (error "Archiving works only in Org-mode files")))))) + (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. @@ -8435,9 +8529,16 @@ If this information is not given, the function uses the tree at point." (defun org-agenda-refile (&optional goto rfloc no-update) "Refile the item at point. -When GOTO is 0 or '(64), clear the refile cache. -When GOTO is '(16), go to the location of the last refiled item. +When called with `\\[universal-argument] \\[universal-argument]', \ +go to the location of the last +refiled item. + +When called with `\\[universal-argument] \\[universal-argument] \ +\\[universal-argument]' prefix or when GOTO is 0, clear +the refile cache. + RFLOC can be a refile location obtained in a different way. + When NO-UPDATE is non-nil, don't redo the agenda buffer." (interactive "P") (cond @@ -8456,13 +8557,11 @@ When NO-UPDATE is non-nil, don't redo the agenda buffer." (if goto "Goto" "Refile to") buffer org-refile-allow-creating-parent-nodes)))) (with-current-buffer buffer - (save-excursion - (save-restriction - (widen) - (goto-char marker) - (let ((org-agenda-buffer-name buffer-orig)) - (org-remove-subtree-entries-from-agenda)) - (org-refile goto buffer rfloc))))) + (org-with-wide-buffer + (goto-char marker) + (let ((org-agenda-buffer-name buffer-orig)) + (org-remove-subtree-entries-from-agenda)) + (org-refile goto buffer rfloc)))) (unless no-update (org-agenda-redo))))) (defun org-agenda-open-link (&optional arg) @@ -8487,13 +8586,11 @@ It also looks at the text of the entry itself." (setq trg (and (string-match org-bracket-link-regexp l) (match-string 1 l))) (if (or (not trg) (string-match org-any-link-re trg)) - (save-excursion - (save-restriction - (widen) - (goto-char marker) - (when (search-forward l nil lkend) - (goto-char (match-beginning 0)) - (org-open-at-point)))) + (org-with-wide-buffer + (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 (switch-to-buffer-other-window buffer) (widen) @@ -8513,11 +8610,14 @@ It also looks at the text of the entry itself." "Get a variable from a referenced buffer and install it here." (let ((m (org-get-at-bol 'org-marker))) (when (and m (buffer-live-p (marker-buffer m))) - (org-set-local var (with-current-buffer (marker-buffer m) - (symbol-value var)))))) + (set (make-local-variable var) + (with-current-buffer (marker-buffer m) + (symbol-value var)))))) (defun org-agenda-switch-to (&optional delete-other-windows) - "Go to the Org-mode file which contains the item at point." + "Go to the Org mode file which contains the item at point. +When optional argument DELETE-OTHER-WINDOWS is non-nil, the +displayed Org file fills the frame." (interactive) (if (and org-return-follows-link (not (org-get-at-bol 'org-marker)) @@ -8527,44 +8627,40 @@ It also looks at the text of the entry itself." (org-agenda-error))) (buffer (marker-buffer marker)) (pos (marker-position marker))) - (org-pop-to-buffer-same-window buffer) - (and delete-other-windows (delete-other-windows)) + (unless buffer (user-error "Trying to switch to non-existent buffer")) + (pop-to-buffer-same-window buffer) + (when delete-other-windows (delete-other-windows)) (widen) (goto-char pos) (when (derived-mode-p 'org-mode) (org-show-context 'agenda) - (save-excursion - (and (outline-next-heading) - (org-flag-heading nil))) ; show the next heading - (when (outline-invisible-p) - (show-entry)) ; display invisible text (run-hooks 'org-agenda-after-show-hook))))) (defun org-agenda-goto-mouse (ev) - "Go to the Org-mode file which contains the item at the mouse click." + "Go to the Org file which contains the item at the mouse click." (interactive "e") (mouse-set-point ev) (org-agenda-goto)) (defun org-agenda-show (&optional full-entry) - "Display the Org-mode file which contains the item at point. + "Display the Org file which contains the item at point. With prefix argument FULL-ENTRY, make the entire entry visible if it was hidden in the outline." (interactive "P") (let ((win (selected-window))) - (if full-entry - (let ((org-show-entry-below t)) - (org-agenda-goto t)) - (org-agenda-goto t)) + (org-agenda-goto t) + (when full-entry (org-show-entry)) (select-window win))) (defvar org-agenda-show-window nil) (defun org-agenda-show-and-scroll-up (&optional arg) - "Display the Org-mode file which contains the item at point. + "Display the Org file which contains the item at point. + When called repeatedly, scroll the window that is displaying the buffer. -With a \\[universal-argument] prefix, use `org-show-entry' instead of -`show-subtree' to display the item, so that drawers and logbooks stay -folded." + +With a `\\[universal-argument]' prefix, use `org-show-entry' instead of \ +`outline-show-subtree' +to display the item, so that drawers and logbooks stay folded." (interactive "P") (let ((win (selected-window))) (if (and (window-live-p org-agenda-show-window) @@ -8573,7 +8669,7 @@ folded." (select-window org-agenda-show-window) (ignore-errors (scroll-up))) (org-agenda-goto t) - (if arg (org-show-entry) (show-subtree)) + (if arg (org-show-entry) (outline-show-subtree)) (setq org-agenda-show-window (selected-window))) (select-window win))) @@ -8587,7 +8683,7 @@ folded." (select-window win)))) (defun org-agenda-show-1 (&optional more) - "Display the Org-mode file which contains the item at point. + "Display the Org file which contains the item at point. The prefix arg selects the amount of information to display: 0 hide the subtree @@ -8605,50 +8701,46 @@ if it was hidden in the outline." (set-window-start (selected-window) (point-at-bol)) (cond ((= more 0) - (hide-subtree) + (outline-hide-subtree) (save-excursion (org-back-to-heading) (run-hook-with-args 'org-cycle-hook 'folded)) (message "Remote: FOLDED")) - ((and (org-called-interactively-p 'any) (= more 1)) + ((and (called-interactively-p 'any) (= more 1)) (message "Remote: show with default settings")) ((= more 2) - (show-entry) - (show-children) + (outline-show-entry) + (org-show-children) (save-excursion (org-back-to-heading) (run-hook-with-args 'org-cycle-hook 'children)) (message "Remote: CHILDREN")) ((= more 3) - (show-subtree) + (outline-show-subtree) (save-excursion (org-back-to-heading) (run-hook-with-args 'org-cycle-hook 'subtree)) (message "Remote: SUBTREE")) ((= more 4) - (let* ((org-drawers (delete "LOGBOOK" (copy-sequence org-drawers))) - (org-drawer-regexp - (concat "^[ \t]*:\\(" - (mapconcat 'regexp-quote org-drawers "\\|") - "\\):[ \t]*$"))) - (show-subtree) - (save-excursion - (org-back-to-heading) - (org-cycle-hide-drawers 'subtree))) + (outline-show-subtree) + (save-excursion + (org-back-to-heading) + (org-cycle-hide-drawers 'subtree '("LOGBOOK"))) (message "Remote: SUBTREE AND LOGBOOK")) ((> more 4) - (show-subtree) + (outline-show-subtree) (message "Remote: SUBTREE AND ALL DRAWERS"))) (select-window win))) (defvar org-agenda-cycle-counter nil) (defun org-agenda-cycle-show (&optional n) "Show the current entry in another window, with default settings. -Default settings are taken from `org-show-hierarchy-above' and siblings. -When use repeatedly in immediate succession, the remote entry will cycle -through visibility -children -> subtree -> folded +Default settings are taken from `org-show-context-detail'. When +use repeatedly in immediate succession, the remote entry will +cycle through visibility + + children -> subtree -> folded When called with a numeric prefix arg, that arg will be passed through to `org-agenda-show-1'. For the interpretation of that argument, see the @@ -8666,7 +8758,7 @@ docstring of `org-agenda-show-1'." (org-agenda-show-1 org-agenda-cycle-counter)) (defun org-agenda-recenter (arg) - "Display the Org-mode file which contains the item at point and recenter." + "Display the Org file which contains the item at point and recenter." (interactive "P") (let ((win (selected-window))) (org-agenda-goto t) @@ -8674,7 +8766,7 @@ docstring of `org-agenda-show-1'." (select-window win))) (defun org-agenda-show-mouse (ev) - "Display the Org-mode file which contains the item at the mouse click." + "Display the Org file which contains the item at the mouse click." (interactive "e") (mouse-set-point ev) (org-agenda-show)) @@ -8685,7 +8777,8 @@ docstring of `org-agenda-show-1'." (org-agenda-error))) (defun org-agenda-error () - (error "Command not allowed in this line")) + "Throw an error when a command is not allowed in the agenda." + (user-error "Command not allowed in this line")) (defun org-agenda-tree-to-indirect-buffer (arg) "Show the subtree corresponding to the current entry in an indirect buffer. @@ -8693,8 +8786,10 @@ This calls the command `org-tree-to-indirect-buffer' from the original buffer. With a numerical prefix ARG, go up to this level and then take that tree. With a negative numeric ARG, go up by this number of levels. -With a \\[universal-argument] prefix, make a separate frame for this tree (i.e. don't -use the dedicated frame)." + +With a `\\[universal-argument]' prefix, make a separate frame for this tree, \ +i.e. don't use +the dedicated frame." (interactive "P") (if current-prefix-arg (org-agenda-do-tree-to-indirect-buffer arg) @@ -8712,7 +8807,8 @@ use the dedicated frame)." (and indirect-window (select-window indirect-window)) (switch-to-buffer org-last-indirect-buffer :norecord) (fit-window-to-buffer indirect-window))) - (select-window (get-buffer-window agenda-buffer))))) + (select-window (get-buffer-window agenda-buffer)) + (setq org-agenda-last-indirect-buffer org-last-indirect-buffer)))) (defun org-agenda-do-tree-to-indirect-buffer (arg) "Same as `org-agenda-tree-to-indirect-buffer' without saving window." @@ -8741,9 +8837,9 @@ by a remote command from the agenda.") (org-agenda-todo 'previousset)) (defun org-agenda-todo (&optional arg) - "Cycle TODO state of line at point, also in Org-mode file. + "Cycle TODO state of line at point, also in Org file. 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-mode file." +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)) @@ -8752,7 +8848,7 @@ the same tree node, and the headline of the tree node in the Org-mode file." (buffer (marker-buffer marker)) (pos (marker-position marker)) (hdmarker (org-get-at-bol 'org-hd-marker)) - (todayp (org-agenda-todayp (org-get-at-bol 'day))) + (todayp (org-agenda-today-p (org-get-at-bol 'day))) (inhibit-read-only t) org-agenda-headline-snapshot-before-repeat newhead just-one) (org-with-remote-undo buffer @@ -8760,14 +8856,11 @@ the same tree node, and the headline of the tree node in the Org-mode file." (widen) (goto-char pos) (org-show-context 'agenda) - (save-excursion - (and (outline-next-heading) - (org-flag-heading nil))) ; show the next heading (let ((current-prefix-arg arg)) (call-interactively 'org-todo)) (and (bolp) (forward-char 1)) (setq newhead (org-get-heading)) - (when (and (org-bound-and-true-p + (when (and (bound-and-true-p org-agenda-headline-snapshot-before-repeat) (not (equal org-agenda-headline-snapshot-before-repeat newhead)) @@ -8780,11 +8873,12 @@ the same tree node, and the headline of the tree node in the Org-mode file." (beginning-of-line 1) (save-window-excursion (org-agenda-change-all-lines newhead hdmarker 'fixface just-one)) - (when (org-bound-and-true-p org-clock-out-when-done) + (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-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." @@ -8800,9 +8894,6 @@ the same tree node, and the headline of the tree node in the Org-mode file." (widen) (goto-char pos) (org-show-context 'agenda) - (save-excursion - (and (outline-next-heading) - (org-flag-heading nil))) ; show the next heading (org-add-note)))) (defun org-agenda-change-all-lines (newhead hdmarker @@ -8819,9 +8910,9 @@ If FORCE-TAGS is non nil, the car of it returns the new tags." (line (org-current-line)) (org-agenda-buffer (current-buffer)) (thetags (with-current-buffer (marker-buffer hdmarker) - (save-excursion (save-restriction (widen) - (goto-char hdmarker) - (org-get-tags-at))))) + (org-with-wide-buffer + (goto-char hdmarker) + (org-get-tags-at)))) props m pl undone-face done-face finish new dotime level cat tags) (save-excursion (goto-char (point-max)) @@ -8833,7 +8924,7 @@ If FORCE-TAGS is non nil, the car of it returns the new tags." (equal m hdmarker)) (setq props (text-properties-at (point)) dotime (org-get-at-bol 'dotime) - cat (org-get-at-bol 'org-category) + cat (org-get-at-eol 'org-category 1) level (org-get-at-bol 'level) tags thetags new @@ -8842,20 +8933,25 @@ If FORCE-TAGS is non nil, the car of it returns the new tags." org-prefix-format-compiled)) (extra (org-get-at-bol 'extra))) (with-current-buffer (marker-buffer hdmarker) - (save-excursion - (save-restriction - (widen) - (org-agenda-format-item extra newhead level cat tags dotime))))) + (org-with-wide-buffer + (org-agenda-format-item extra newhead level cat tags dotime)))) pl (text-property-any (point-at-bol) (point-at-eol) 'org-heading t) undone-face (org-get-at-bol 'undone-face) done-face (org-get-at-bol 'done-face)) (beginning-of-line 1) (cond - ((equal new "") - (and (looking-at ".*\n?") (replace-match ""))) + ((equal new "") (delete-region (point) (line-beginning-position 2))) ((looking-at ".*") - (replace-match new t t) - (beginning-of-line 1) + ;; When replacing the whole line, preserve bulk mark + ;; overlay, if any. + (let ((mark (catch :overlay + (dolist (o (overlays-in (point) (+ 2 (point)))) + (when (eq (overlay-get o 'type) + 'org-marked-entry-overlay) + (throw :overlay o)))))) + (replace-match new t t) + (beginning-of-line) + (when mark (move-overlay mark (point) (+ 2 (point))))) (add-text-properties (point-at-bol) (point-at-eol) props) (when fixface (add-text-properties @@ -8873,10 +8969,14 @@ If FORCE-TAGS is non nil, the car of it returns the new tags." (defun org-agenda-align-tags (&optional line) "Align all tags in agenda items to `org-agenda-tags-column'." - (let ((inhibit-read-only t) l c) + (let ((inhibit-read-only t) + (org-agenda-tags-column (if (eq 'auto org-agenda-tags-column) + (- (window-text-width)) + org-agenda-tags-column)) + l c) (save-excursion (goto-char (if line (point-at-bol) (point-min))) - (while (re-search-forward (org-re "\\([ \t]+\\)\\(:[[:alnum:]_@#%:]+:\\)[ \t]*$") + (while (re-search-forward "\\([ \t]+\\)\\(:[[:alnum:]_@#%:]+:\\)[ \t]*$" (if line (point-at-eol) nil) t) (add-text-properties (match-beginning 2) (match-end 2) @@ -8900,19 +9000,19 @@ If FORCE-TAGS is non nil, the car of it returns the new tags." (org-font-lock-add-tag-faces (point-max))))) (defun org-agenda-priority-up () - "Increase the priority of line at point, also in Org-mode file." + "Increase the priority of line at point, also in Org file." (interactive) (org-agenda-priority 'up)) (defun org-agenda-priority-down () - "Decrease the priority of line at point, also in Org-mode file." + "Decrease the priority of line at point, also in Org file." (interactive) (org-agenda-priority 'down)) (defun org-agenda-priority (&optional force-direction) - "Set the priority of line at point, also in Org-mode file. + "Set the priority of line at point, also in Org file. 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-mode file. +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)) @@ -8933,9 +9033,6 @@ Called with a universal prefix arg, show the priority instead of setting it." (widen) (goto-char pos) (org-show-context 'agenda) - (save-excursion - (and (outline-next-heading) - (org-flag-heading nil))) ; show the next heading (funcall 'org-priority force-direction) (end-of-line 1) (setq newhead (org-get-heading))) @@ -8947,7 +9044,7 @@ Called with a universal prefix arg, show the priority instead of setting it." "Set tags for the current headline." (interactive) (org-agenda-check-no-diary) - (if (and (org-region-active-p) (org-called-interactively-p 'any)) + (if (and (org-region-active-p) (called-interactively-p 'any)) (call-interactively 'org-change-tag-in-region) (let* ((hdmarker (or (org-get-at-bol 'org-hd-marker) (org-agenda-error))) @@ -8959,12 +9056,7 @@ Called with a universal prefix arg, show the priority instead of setting it." (with-current-buffer buffer (widen) (goto-char pos) - (save-excursion - (org-show-context 'agenda)) - (save-excursion - (and (outline-next-heading) - (org-flag-heading nil))) ; show the next heading - (goto-char pos) + (org-show-context 'agenda) (if tag (org-toggle-tag tag onoff) (call-interactively 'org-set-tags)) @@ -8987,12 +9079,7 @@ Called with a universal prefix arg, show the priority instead of setting it." (with-current-buffer buffer (widen) (goto-char pos) - (save-excursion - (org-show-context 'agenda)) - (save-excursion - (and (outline-next-heading) - (org-flag-heading nil))) ; show the next heading - (goto-char pos) + (org-show-context 'agenda) (call-interactively 'org-set-property))))) (defun org-agenda-set-effort () @@ -9009,12 +9096,7 @@ Called with a universal prefix arg, show the priority instead of setting it." (with-current-buffer buffer (widen) (goto-char pos) - (save-excursion - (org-show-context 'agenda)) - (save-excursion - (and (outline-next-heading) - (org-flag-heading nil))) ; show the next heading - (goto-char pos) + (org-show-context 'agenda) (call-interactively 'org-set-effort) (end-of-line 1) (setq newhead (org-get-heading))) @@ -9035,9 +9117,6 @@ Called with a universal prefix arg, show the priority instead of setting it." (widen) (goto-char pos) (org-show-context 'agenda) - (save-excursion - (and (outline-next-heading) - (org-flag-heading nil))) ; show the next heading (call-interactively 'org-toggle-archive-tag) (end-of-line 1) (setq newhead (org-get-heading))) @@ -9079,7 +9158,7 @@ Called with a universal prefix arg, show the priority instead of setting it." (defun org-agenda-date-later (arg &optional what) "Change the date of this item to ARG day(s) later." (interactive "p") - (org-agenda-check-type t 'agenda 'timeline) + (org-agenda-check-type t 'agenda) (org-agenda-check-no-diary) (let* ((marker (or (org-get-at-bol 'org-marker) (org-agenda-error))) @@ -9090,8 +9169,7 @@ Called with a universal prefix arg, show the priority instead of setting it." (with-current-buffer buffer (widen) (goto-char pos) - (if (not (org-at-timestamp-p)) - (error "Cannot find time stamp")) + (unless (org-at-timestamp-p 'lax) (error "Cannot find time stamp")) (when (and org-agenda-move-date-from-past-immediately-to-today (equal arg 1) (or (not what) (eq what 'day)) @@ -9151,18 +9229,10 @@ Called with a universal prefix arg, show the priority instead of setting it." (when (equal marker (org-get-at-bol 'org-marker)) (remove-text-properties (point-at-bol) (point-at-eol) '(display)) (org-move-to-column (- (window-width) (length stamp)) t) - (if (featurep 'xemacs) - ;; Use `duplicable' property to trigger undo recording - (let ((ex (make-extent nil nil)) - (gl (make-glyph stamp))) - (set-glyph-face gl 'secondary-selection) - (set-extent-properties - ex (list 'invisible t 'end-glyph gl 'duplicable t)) - (insert-extent ex (1- (point)) (point-at-eol))) - (add-text-properties - (1- (point)) (point-at-eol) - (list 'display (org-add-props stamp nil - 'face '(secondary-selection default))))) + (add-text-properties + (1- (point)) (point-at-eol) + (list 'display (org-add-props stamp nil + 'face '(secondary-selection default)))) (beginning-of-line 1)) (beginning-of-line 0))))) @@ -9171,7 +9241,7 @@ Called with a universal prefix arg, show the priority instead of setting it." The prefix ARG is passed to the `org-time-stamp' command and can therefore be used to request time specification in the time stamp." (interactive "P") - (org-agenda-check-type t 'agenda 'timeline) + (org-agenda-check-type t 'agenda) (org-agenda-check-no-diary) (let* ((marker (or (org-get-at-bol 'org-marker) (org-agenda-error))) @@ -9181,8 +9251,7 @@ be used to request time specification in the time stamp." (with-current-buffer buffer (widen) (goto-char pos) - (if (not (org-at-timestamp-p t)) - (error "Cannot find time stamp")) + (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))) @@ -9191,14 +9260,13 @@ be used to request time specification in the time stamp." "Schedule the item at point. ARG is passed through to `org-schedule'." (interactive "P") - (org-agenda-check-type t 'agenda 'timeline 'todo 'tags 'search) + (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)) - (org-insert-labeled-timestamps-at-point nil) ts) (set-marker-insertion-type marker t) (org-with-remote-undo buffer @@ -9213,13 +9281,12 @@ ARG is passed through to `org-schedule'." "Schedule the item at point. ARG is passed through to `org-deadline'." (interactive "P") - (org-agenda-check-type t 'agenda 'timeline 'todo 'tags 'search) + (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)) - (org-insert-labeled-timestamps-at-point nil) ts) (org-with-remote-undo buffer (with-current-buffer buffer @@ -9246,7 +9313,6 @@ ARG is passed through to `org-deadline'." (widen) (goto-char pos) (org-show-context 'agenda) - (org-show-entry) (org-cycle-hide-drawers 'children) (org-clock-in arg) (setq newhead (org-get-heading))) @@ -9261,14 +9327,12 @@ ARG is passed through to `org-deadline'." (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) - (save-excursion - (save-restriction - (widen) - (goto-char org-clock-marker) - (org-back-to-heading t) - (move-marker marker (point)) - (org-clock-out) - (setq newhead (org-get-heading)))))) + (org-with-wide-buffer + (goto-char org-clock-marker) + (org-back-to-heading t) + (move-marker marker (point)) + (org-clock-out) + (setq newhead (org-get-heading))))) (org-agenda-change-all-lines newhead marker) (move-marker marker nil) (org-move-to-column col) @@ -9295,7 +9359,7 @@ buffer, display it in another window." (cond (pos (goto-char pos)) ;; If the currently clocked entry is not in the agenda ;; buffer, we visit it in another window: - (org-clock-current-task + ((bound-and-true-p org-clock-current-task) (org-switch-to-buffer-other-window (org-clock-goto))) (t (message "No running clock, use `C-c C-x C-j' to jump to the most recent one"))))) @@ -9345,11 +9409,13 @@ buffer, display it in another window." "Where in `org-agenda-diary-file' should new entries be added? Valid values: -date-tree in the date tree, as child of the date -top-level as top-level entries at the end of the file." +date-tree in the date tree, as first child of the date +date-tree-last in the date tree, as last child of the date +top-level as top-level entries at the end of the file." :group 'org-agenda :type '(choice - (const :tag "in a date tree" date-tree) + (const :tag "first in a date tree" date-tree) + (const :tag "last in a date tree" date-tree-last) (const :tag "as top level at end of file" top-level))) (defcustom org-agenda-insert-diary-extract-time nil @@ -9445,40 +9511,43 @@ Add TEXT as headline, and position the cursor in the second line so that a timestamp can be added there." (widen) (goto-char (point-max)) - (or (bolp) (insert "\n")) - (insert "* " text "\n") - (if org-adapt-indentation (org-indent-to-column 2))) + (unless (bolp) (insert "\n")) + (org-insert-heading nil t t) + (insert text) + (org-end-of-meta-data) + (unless (bolp) (insert "\n")) + (when org-adapt-indentation (indent-to-column 2))) (defun org-agenda-insert-diary-make-new-entry (text) - "Make a new entry with TEXT as the first child of the current subtree. -Position the point in the line right after the new heading so -that a timestamp can be added there." - (let ((org-show-following-heading t) - (org-show-siblings t) - (org-show-hierarchy-above t) - (org-show-entry-below t) - col) + "Make a new entry with TEXT as a child of the current subtree. +Position the point in the heading's first body line so that +a timestamp can be added there." + (cond + ((eq org-agenda-insert-diary-strategy 'date-tree-last) + (end-of-line) + (org-insert-heading '(4) t) + (org-do-demote)) + (t (outline-next-heading) (org-back-over-empty-lines) - (or (looking-at "[ \t]*$") - (progn (insert "\n") (backward-char 1))) + (unless (looking-at "[ \t]*$") (save-excursion (insert "\n"))) (org-insert-heading nil t) - (org-do-demote) - (setq col (current-column)) - (insert text "\n") - (if org-adapt-indentation (org-indent-to-column col)) - (let ((org-show-following-heading t) - (org-show-siblings t) - (org-show-hierarchy-above t) - (org-show-entry-below t)) - (org-show-context)))) + (org-do-demote))) + (let ((col (current-column))) + (insert text) + (org-end-of-meta-data) + ;; Ensure point is left on a blank line, at proper indentation. + (unless (bolp) (insert "\n")) + (unless (looking-at-p "^[ \t]*$") (save-excursion (insert "\n"))) + (when org-adapt-indentation (indent-to-column col))) + (org-show-set-visibility 'lineage)) (defun org-agenda-diary-entry () "Make a diary entry, like the `i' command from the calendar. All the standard commands work: block, weekly etc. When `org-agenda-diary-file' points to a file, `org-agenda-diary-entry-in-org-file' is called instead to create -entries in that Org-mode file." +entries in that Org file." (interactive) (if (not (eq org-agenda-diary-file 'diary-file)) (org-agenda-diary-entry-in-org-file) @@ -9487,13 +9556,13 @@ entries in that Org-mode file." (message "Diary entry: [d]ay [w]eekly [m]onthly [y]early [a]nniversary [b]lock [c]yclic") (read-char-exclusive))) (cmd (cdr (assoc char - '((?d . insert-diary-entry) - (?w . insert-weekly-diary-entry) - (?m . insert-monthly-diary-entry) - (?y . insert-yearly-diary-entry) - (?a . insert-anniversary-diary-entry) - (?b . insert-block-diary-entry) - (?c . insert-cyclic-diary-entry))))) + '((?d . diary-insert-entry) + (?w . diary-insert-weekly-entry) + (?m . diary-insert-monthly-entry) + (?y . diary-insert-yearly-entry) + (?a . diary-insert-anniversary-entry) + (?b . diary-insert-block-entry) + (?c . diary-insert-cyclic-entry))))) (oldf (symbol-function 'calendar-cursor-to-date)) ;; (buf (get-file-buffer (substitute-in-file-name diary-file))) (point (point)) @@ -9521,7 +9590,7 @@ entries in that Org-mode file." (defun org-agenda-execute-calendar-command (cmd) "Execute a calendar command from the agenda with date from cursor." - (org-agenda-check-type t 'agenda 'timeline) + (org-agenda-check-type t 'agenda) (require 'diary-lib) (unless (get-text-property (min (1- (point-max)) (point)) 'day) (user-error "Don't know which date to use for the calendar command")) @@ -9549,7 +9618,7 @@ entries in that Org-mode file." (defun org-agenda-holidays () "Display the holidays for the 3 months around the cursor date." (interactive) - (org-agenda-execute-calendar-command 'list-calendar-holidays)) + (org-agenda-execute-calendar-command 'calendar-list-holidays)) (defvar calendar-longitude) ; defined in calendar.el (defvar calendar-latitude) ; defined in calendar.el @@ -9571,7 +9640,7 @@ argument, latitude and longitude will be prompted for." (defun org-agenda-goto-calendar () "Open the Emacs calendar with the date at the cursor." (interactive) - (org-agenda-check-type t 'agenda 'timeline) + (org-agenda-check-type t 'agenda) (let* ((day (or (get-text-property (min (1- (point-max)) (point)) 'day) (user-error "Don't know which date to open in calendar"))) (date (calendar-gregorian-from-absolute day)) @@ -9583,16 +9652,20 @@ argument, latitude and longitude will be prompted for." ;;;###autoload (defun org-calendar-goto-agenda () - "Compute the Org-mode agenda for the calendar date displayed at the cursor. + "Compute the Org agenda for the calendar date displayed at the cursor. This is a command that has to be installed in `calendar-mode-map'." (interactive) - (org-agenda-list nil (calendar-absolute-from-gregorian - (calendar-cursor-to-date)) - nil)) + ;; Temporarily disable sticky agenda since user clearly wants to + ;; refresh view anyway. + (let ((org-agenda-buffer-tmp-name "*Org Agenda(a)*") + (org-agenda-sticky nil)) + (org-agenda-list nil (calendar-absolute-from-gregorian + (calendar-cursor-to-date)) + nil))) (defun org-agenda-convert-date () (interactive) - (org-agenda-check-type t 'agenda 'timeline) + (org-agenda-check-type t 'agenda) (let ((day (get-text-property (min (1- (point-max)) (point)) 'day)) date s) (unless day @@ -9621,6 +9694,7 @@ This is a command that has to be installed in `calendar-mode-map'." ;;; Bulk commands (defun org-agenda-bulk-marked-p () + "Non-nil when current entry is marked for bulk action." (eq (get-char-property (point-at-bol) 'type) 'org-marked-entry-overlay)) @@ -9662,9 +9736,12 @@ This is a command that has to be installed in `calendar-mode-map'." (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))) - (when (string-match regexp txt-at-point) - (setq entries-marked (1+ entries-marked)) - (call-interactively 'org-agenda-bulk-mark)))) + (if (get-char-property (point) 'invisible) + (beginning-of-line 2) + (when (string-match regexp txt-at-point) + (setq entries-marked (1+ entries-marked)) + (call-interactively 'org-agenda-bulk-mark))))) + (if (not entries-marked) (message "No entry matching this regexp.")))) @@ -9723,7 +9800,6 @@ This will remove the markers and the overlays." (interactive) (if (null org-agenda-bulk-marked-entries) (message "No entry to unmark") - (mapc (lambda (m) (move-marker m nil)) org-agenda-bulk-marked-entries) (setq org-agenda-bulk-marked-entries nil) (org-agenda-bulk-remove-overlays (point-min) (point-max)))) @@ -9739,164 +9815,191 @@ bulk action." "Execute an remote-editing action on all marked entries. The prefix arg is passed through to the command if possible." (interactive "P") - ;; Make sure we have markers, and only valid ones + ;; Make sure we have markers, and only valid ones. (unless org-agenda-bulk-marked-entries (user-error "No entries are marked")) - (mapc - (lambda (m) - (unless (and (markerp m) - (marker-buffer m) - (buffer-live-p (marker-buffer m)) - (marker-position m)) - (user-error "Marker %s for bulk command is invalid" m))) - org-agenda-bulk-marked-entries) - - ;; Prompt for the bulk command - (let* ((msg (if org-agenda-persistent-marks "Bulk (persistent): " "Bulk: "))) - (message (concat msg "[$]arch [A]rch->sib [t]odo [+/-]tag [s]chd [d]eadline [r]efile " - "[S]catter [f]unction " - (when org-agenda-bulk-custom-functions - (concat " Custom: [" - (mapconcat (lambda(f) (char-to-string (car f))) - org-agenda-bulk-custom-functions "") - "]")))) - (catch 'exit - (let* ((action (read-char-exclusive)) - (org-log-refile (if org-log-refile 'time nil)) - (entries (reverse org-agenda-bulk-marked-entries)) - (org-overriding-default-time - (if (get-text-property (point) 'org-agenda-date-header) - (org-get-cursor-date))) - redo-at-end - cmd rfloc state e tag pos (cnt 0) (cntskip 0)) - (cond - ((equal action ?p) - (let ((org-agenda-persistent-marks - (not org-agenda-persistent-marks))) - (org-agenda-bulk-action) - (throw 'exit nil))) - - ((equal action ?$) - (setq cmd '(org-agenda-archive))) - - ((equal action ?A) - (setq cmd '(org-agenda-archive-to-archive-sibling))) - - ((member action '(?r ?w)) - (setq rfloc (org-refile-get-location - "Refile to" - (marker-buffer (car entries)) - org-refile-allow-creating-parent-nodes)) - (if (nth 3 rfloc) - (setcar (nthcdr 3 rfloc) - (move-marker (make-marker) (nth 3 rfloc) - (or (get-file-buffer (nth 1 rfloc)) - (find-buffer-visiting (nth 1 rfloc)) - (error "This should not happen"))))) - - (setq cmd (list 'org-agenda-refile nil (list 'quote rfloc) t) - redo-at-end t)) - - ((equal action ?t) - (setq state (org-icompleting-read + (dolist (m org-agenda-bulk-marked-entries) + (unless (and (markerp m) + (marker-buffer m) + (buffer-live-p (marker-buffer m)) + (marker-position m)) + (user-error "Marker %s for bulk command is invalid" m))) + + ;; Prompt for the bulk command. + (message + (concat (if org-agenda-persistent-marks "Bulk (persistent): " "Bulk: ") + "[$]arch [A]rch->sib [t]odo [+/-]tag [s]chd [d]eadline [r]efile " + "[S]catter [f]unction " + (and org-agenda-bulk-custom-functions + (format " Custom: [%s]" + (mapconcat (lambda (f) (char-to-string (car f))) + org-agenda-bulk-custom-functions + ""))))) + (catch 'exit + (let* ((org-log-refile (if org-log-refile 'time nil)) + (entries (reverse org-agenda-bulk-marked-entries)) + (org-overriding-default-time + (and (get-text-property (point) 'org-agenda-date-header) + (org-get-cursor-date))) + redo-at-end + cmd) + (pcase (read-char-exclusive) + (?p + (let ((org-agenda-persistent-marks + (not org-agenda-persistent-marks))) + (org-agenda-bulk-action) + (throw 'exit nil))) + + (?$ + (setq cmd #'org-agenda-archive)) + + (?A + (setq cmd #'org-agenda-archive-to-archive-sibling)) + + ((or ?r ?w) + (let ((refile-location + (org-refile-get-location + "Refile to" + (marker-buffer (car entries)) + org-refile-allow-creating-parent-nodes))) + (when (nth 3 refile-location) + (setcar (nthcdr 3 refile-location) + (move-marker + (make-marker) + (nth 3 refile-location) + (or (get-file-buffer (nth 1 refile-location)) + (find-buffer-visiting (nth 1 refile-location)) + (error "This should not happen"))))) + + (setq cmd `(lambda () (org-agenda-refile nil ',refile-location t))) + (setq redo-at-end t))) + + (?t + (let ((state (completing-read "Todo state: " (with-current-buffer (marker-buffer (car entries)) - (mapcar 'list org-todo-keywords-1)))) - (setq cmd `(let ((org-inhibit-blocking t) - (org-inhibit-logging 'note)) - (org-agenda-todo ,state)))) - - ((memq action '(?- ?+)) - (setq tag (org-icompleting-read + (mapcar #'list org-todo-keywords-1))))) + (setq cmd `(lambda () + (let ((org-inhibit-blocking t) + (org-inhibit-logging 'note)) + (org-agenda-todo ,state)))))) + + ((and (or ?- ?+) action) + (let ((tag (completing-read (format "Tag to %s: " (if (eq action ?+) "add" "remove")) (with-current-buffer (marker-buffer (car entries)) (delq nil - (mapcar (lambda (x) - (if (stringp (car x)) x)) org-tag-alist))))) - (setq cmd `(org-agenda-set-tags ,tag ,(if (eq action ?+) ''on ''off)))) - - ((memq action '(?s ?d)) - (let* ((time - (unless arg - (org-read-date - nil nil nil - (if (eq action ?s) "(Re)Schedule to" "(Re)Set Deadline to") - org-overriding-default-time))) - (c1 (if (eq action ?s) 'org-agenda-schedule 'org-agenda-deadline))) - (setq cmd `(eval '(,c1 arg ,time))))) - - ((equal action ?S) - (if (not (org-agenda-check-type nil 'agenda 'timeline 'todo)) - (user-error "Can't scatter tasks in \"%s\" agenda view" org-agenda-type) - (let ((days (read-number - (format "Scatter tasks across how many %sdays: " - (if arg "week" "")) 7))) - (setq cmd - `(let ((distance (1+ (random ,days)))) - (if arg - (let ((dist distance) - (day-of-week - (calendar-day-of-week - (calendar-gregorian-from-absolute (org-today))))) - (dotimes (i (1+ dist)) - (while (member day-of-week org-agenda-weekend-days) - (incf distance) - (incf day-of-week) - (if (= day-of-week 7) - (setq day-of-week 0))) - (incf day-of-week) - (if (= day-of-week 7) - (setq day-of-week 0))))) - ;; silently fail when try to replan a sexp entry - (condition-case nil - (let* ((date (calendar-gregorian-from-absolute - (+ (org-today) distance))) - (time (encode-time 0 0 0 (nth 1 date) (nth 0 date) - (nth 2 date)))) - (org-agenda-schedule nil time)) - (error nil))))))) - - ((assoc action org-agenda-bulk-custom-functions) - (setq cmd (list (cadr (assoc action org-agenda-bulk-custom-functions))) - redo-at-end t)) - - ((equal action ?f) - (setq cmd (list (intern - (org-icompleting-read "Function: " - obarray 'fboundp t nil nil))))) - - (t (user-error "Invalid bulk action"))) - - ;; Sort the markers, to make sure that parents are handled before children - (setq entries (sort entries - (lambda (a b) - (cond - ((equal (marker-buffer a) (marker-buffer b)) - (< (marker-position a) (marker-position b))) - (t - (string< (buffer-name (marker-buffer a)) - (buffer-name (marker-buffer b)))))))) - - ;; Now loop over all markers and apply cmd - (while (setq e (pop entries)) - (setq pos (text-property-any (point-min) (point-max) 'org-hd-marker e)) - (if (not pos) - (progn (message "Skipping removed entry at %s" e) - (setq cntskip (1+ cntskip))) - (goto-char pos) - (let (org-loop-over-headlines-in-active-region) - (eval cmd)) - (setq cnt (1+ cnt)))) + (mapcar (lambda (x) (and (stringp (car x)) x)) + org-current-tag-alist)))))) + (setq cmd + `(lambda () + (org-agenda-set-tags ,tag + ,(if (eq action ?+) ''on ''off)))))) + + (?s + (let ((time + (and (not arg) + (org-read-date nil nil nil "(Re)Schedule to" + org-overriding-default-time)))) + ;; Make sure to not prompt for a note when bulk + ;; rescheduling as Org cannot cope with simultaneous notes. + ;; Besides, it could be annoying depending on the number of + ;; items re-scheduled. + (setq cmd + `(lambda () + (let ((org-log-reschedule (and org-log-reschedule 'time))) + (org-agenda-schedule arg ,time)))))) + (?d + (let ((time + (and (not arg) + (org-read-date nil nil nil "(Re)Set Deadline to" + org-overriding-default-time)))) + ;; Make sure to not prompt for a note when bulk + ;; rescheduling as Org cannot cope with simultaneous + ;; notes. Besides, it could be annoying depending on the + ;; number of items re-scheduled. + (setq cmd + `(lambda () + (let ((org-log-redeadline (and org-log-redeadline 'time))) + (org-agenda-deadline arg ,time)))))) + + (?S + (unless (org-agenda-check-type nil 'agenda 'todo) + (user-error "Can't scatter tasks in \"%s\" agenda view" org-agenda-type)) + (let ((days (read-number + (format "Scatter tasks across how many %sdays: " + (if arg "week" "")) + 7))) + (setq cmd + `(lambda () + (let ((distance (1+ (random ,days)))) + (when arg + (let ((dist distance) + (day-of-week + (calendar-day-of-week + (calendar-gregorian-from-absolute (org-today))))) + (dotimes (i (1+ dist)) + (while (member day-of-week org-agenda-weekend-days) + (cl-incf distance) + (cl-incf day-of-week) + (when (= day-of-week 7) + (setq day-of-week 0))) + (cl-incf day-of-week) + (when (= day-of-week 7) + (setq day-of-week 0))))) + ;; Silently fail when try to replan a sexp entry. + (ignore-errors + (let* ((date (calendar-gregorian-from-absolute + (+ (org-today) distance))) + (time (encode-time 0 0 0 (nth 1 date) (nth 0 date) + (nth 2 date)))) + (org-agenda-schedule nil time)))))))) + + (?f + (setq cmd + (intern + (completing-read "Function: " obarray #'fboundp t nil nil)))) + + (action + (pcase (assoc action org-agenda-bulk-custom-functions) + (`(,_ ,f) (setq cmd f) (setq redo-at-end t)) + (_ (user-error "Invalid bulk action: %c" action))))) + + ;; Sort the markers, to make sure that parents are handled + ;; before children. + (setq entries (sort entries + (lambda (a b) + (cond + ((eq (marker-buffer a) (marker-buffer b)) + (< (marker-position a) (marker-position b))) + (t + (string< (buffer-name (marker-buffer a)) + (buffer-name (marker-buffer b)))))))) + + ;; Now loop over all markers and apply CMD. + (let ((processed 0) + (skipped 0)) + (dolist (e entries) + (let ((pos (text-property-any (point-min) (point-max) 'org-hd-marker e))) + (if (not pos) + (progn (message "Skipping removed entry at %s" e) + (cl-incf skipped)) + (goto-char pos) + (let (org-loop-over-headlines-in-active-region) (funcall cmd)) + ;; `post-command-hook' is not run yet. We make sure any + ;; pending log note is processed. + (when (or (memq 'org-add-log-note (default-value 'post-command-hook)) + (memq 'org-add-log-note post-command-hook)) + (org-add-log-note)) + (cl-incf processed)))) (when redo-at-end (org-agenda-redo)) - (unless org-agenda-persistent-marks - (org-agenda-bulk-unmark-all)) + (unless org-agenda-persistent-marks (org-agenda-bulk-unmark-all)) (message "Acted on %d entries%s%s" - cnt - (if (= cntskip 0) + processed + (if (= skipped 0) "" (format ", skipped %d (disappeared before their turn)" - cntskip)) - (if (not org-agenda-persistent-marks) - "" " (kept marked)")))))) + skipped)) + (if (not org-agenda-persistent-marks) "" " (kept marked)")))))) (defun org-agenda-capture (&optional with-time) "Call `org-capture' with the date at point. @@ -9914,12 +10017,14 @@ current HH:MM time." (defun org-agenda-reapply-filters () "Re-apply all agenda filters." (mapcar - (lambda(f) (when (car f) (org-agenda-filter-apply (car f) (cadr f)))) + (lambda(f) (when (car f) (org-agenda-filter-apply (car f) (cadr f) t))) `((,org-agenda-tag-filter tag) (,org-agenda-category-filter category) (,org-agenda-regexp-filter regexp) + (,org-agenda-effort-filter effort) (,(get 'org-agenda-tag-filter :preset-filter) tag) (,(get 'org-agenda-category-filter :preset-filter) category) + (,(get 'org-agenda-effort-filter :preset-filter) effort) (,(get 'org-agenda-regexp-filter :preset-filter) regexp)))) (defun org-agenda-drag-line-forward (arg &optional backward) @@ -9980,7 +10085,9 @@ tag and (if present) the flagging note." (replace-match "\n" t t)) (goto-char (point-min)) (select-window win) - (message "Flagging note pushed to kill ring. Press [?] again to remove tag and note")))) + (message "%s" (substitute-command-keys "Flagging note pushed to \ +kill ring. Press `\\[org-agenda-show-the-flagging-note]' again to remove \ +tag and note"))))) (defun org-agenda-remove-flag (marker) "Remove the FLAGGED tag and any flagging note in the entry." @@ -10003,7 +10110,8 @@ tag and (if present) the flagging note." ;;;###autoload (defun org-agenda-to-appt (&optional refresh filter &rest args) "Activate appointments found in `org-agenda-files'. -With a \\[universal-argument] prefix, refresh the list of + +With a `\\[universal-argument]' prefix, refresh the list of \ appointments. If FILTER is t, interactively prompt the user for a regular @@ -10019,8 +10127,8 @@ argument: an entry from `org-agenda-get-day-entries'. FILTER can also be an alist with the car of each cell being either `headline' or `category'. For example: - ((headline \"IMPORTANT\") - (category \"Work\")) + \\='((headline \"IMPORTANT\") + (category \"Work\")) will only add headlines containing IMPORTANT or headlines belonging to the \"Work\" category. @@ -10037,75 +10145,76 @@ to override `appt-message-warning-time'." (if refresh (setq appt-time-msg-list nil)) (if (eq filter t) (setq filter (read-from-minibuffer "Regexp filter: "))) - (let* ((cnt 0) ; count added events - (scope (or args '(:deadline* :scheduled* :timestamp))) - (org-agenda-new-buffers nil) - (org-deadline-warning-days 0) - ;; Do not use `org-today' here because appt only takes - ;; time and without date as argument, so it may pass wrong - ;; information otherwise - (today (org-date-to-gregorian - (time-to-days (current-time)))) - (org-agenda-restrict nil) - (files (org-agenda-files 'unrestricted)) entries file - (org-agenda-buffer nil)) + (let* ((cnt 0) ; count added events + (scope (or args '(:deadline* :scheduled* :timestamp))) + (org-agenda-new-buffers nil) + (org-deadline-warning-days 0) + ;; Do not use `org-today' here because appt only takes + ;; time and without date as argument, so it may pass wrong + ;; information otherwise + (today (org-date-to-gregorian + (time-to-days (current-time)))) + (org-agenda-restrict nil) + (files (org-agenda-files 'unrestricted)) entries file + (org-agenda-buffer nil)) ;; Get all entries which may contain an appt (org-agenda-prepare-buffers files) (while (setq file (pop files)) (setq entries - (delq nil - (append entries - (apply 'org-agenda-get-day-entries - file today scope))))) + (delq nil + (append entries + (apply 'org-agenda-get-day-entries + file today scope))))) ;; Map thru entries and find if we should filter them out (mapc - (lambda(x) + (lambda (x) (let* ((evt (org-trim - (replace-regexp-in-string - org-bracket-link-regexp "\\3" - (or (get-text-property 1 'txt x) "")))) - (cat (get-text-property 1 'org-category x)) - (tod (get-text-property 1 'time-of-day x)) - (ok (or (null filter) - (and (stringp filter) (string-match filter evt)) - (and (functionp filter) (funcall filter x)) - (and (listp filter) - (let ((cat-filter (cadr (assoc 'category filter))) - (evt-filter (cadr (assoc 'headline filter)))) - (or (and (stringp cat-filter) - (string-match cat-filter cat)) - (and (stringp evt-filter) - (string-match evt-filter evt))))))) - (wrn (get-text-property 1 'warntime x))) - ;; FIXME: Shall we remove text-properties for the appt text? - ;; (setq evt (set-text-properties 0 (length evt) nil evt)) - (when (and ok tod) - (setq tod (concat "00" (number-to-string tod)) - tod (when (string-match - "\\([0-9]\\{1,2\\}\\)\\([0-9]\\{2\\}\\)\\'" tod) - (concat (match-string 1 tod) ":" - (match-string 2 tod)))) - (if (version< emacs-version "23.3") - (appt-add tod evt) - (appt-add tod evt wrn)) - (setq cnt (1+ cnt))))) entries) + (replace-regexp-in-string + org-bracket-link-regexp "\\3" + (or (get-text-property 1 'txt x) "")))) + (cat (get-text-property (1- (length x)) 'org-category x)) + (tod (get-text-property 1 'time-of-day x)) + (ok (or (null filter) + (and (stringp filter) (string-match filter evt)) + (and (functionp filter) (funcall filter x)) + (and (listp filter) + (let ((cat-filter (cadr (assq 'category filter))) + (evt-filter (cadr (assq 'headline filter)))) + (or (and (stringp cat-filter) + (string-match cat-filter cat)) + (and (stringp evt-filter) + (string-match evt-filter evt))))))) + (wrn (get-text-property 1 'warntime x))) + ;; FIXME: Shall we remove text-properties for the appt text? + ;; (setq evt (set-text-properties 0 (length evt) nil evt)) + (when (and ok tod (not (string-match "\\`DONE\\|CANCELLED" evt))) + (setq tod (concat "00" (number-to-string tod))) + (setq tod (when (string-match + "\\([0-9]\\{1,2\\}\\)\\([0-9]\\{2\\}\\)\\'" tod) + (concat (match-string 1 tod) ":" + (match-string 2 tod)))) + (when (appt-add tod evt wrn) + (setq cnt (1+ cnt)))))) + entries) (org-release-buffers org-agenda-new-buffers) (if (eq cnt 0) - (message "No event to add") + (message "No event to add") (message "Added %d event%s for today" cnt (if (> cnt 1) "s" ""))))) -(defun org-agenda-todayp (date) - "Does DATE mean today, when considering `org-extend-today-until'?" - (let ((today (org-today)) - (date (if (and date (listp date)) (calendar-absolute-from-gregorian date) - date))) - (eq date today))) +(defun org-agenda-today-p (date) + "Non nil when DATE means today. +DATE is either a list of the form (month day year) or a number of +days as returned by `calendar-absolute-from-gregorian' or +`org-today'. This function considers `org-extend-today-until' +when defining today." + (eq (org-today) + (if (consp date) (calendar-absolute-from-gregorian date) date))) (defun org-agenda-todo-yesterday (&optional arg) "Like `org-agenda-todo' but the time of change will be 23:59 of yesterday." (interactive "P") - (let* ((hour (third (decode-time - (org-current-time)))) + (let* ((org-use-effective-time t) + (hour (nth 2 (decode-time (org-current-time)))) (org-extend-today-until (1+ hour))) (org-agenda-todo arg))) diff --git a/lisp/org/org-archive.el b/lisp/org/org-archive.el index 39a6581046a..01514d75652 100644 --- a/lisp/org/org-archive.el +++ b/lisp/org/org-archive.el @@ -1,4 +1,4 @@ -;;; org-archive.el --- Archiving for Org-mode +;;; org-archive.el --- Archiving for Org -*- lexical-binding: t; -*- ;; Copyright (C) 2004-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Commentary: @@ -29,10 +29,10 @@ ;;; Code: (require 'org) -(eval-when-compile (require 'cl)) -(declare-function org-inlinetask-remove-END-maybe "org-inlinetask" ()) +(declare-function org-element-type "org-element" (element)) (declare-function org-datetree-find-date-create "org-datetree" (date &optional keep-restriction)) +(declare-function org-inlinetask-remove-END-maybe "org-inlinetask" ()) (defcustom org-archive-default-command 'org-archive-subtree "The default archiving command." @@ -57,7 +57,7 @@ See `org-archive-to-archive-sibling' for more information." (defcustom org-archive-mark-done nil "Non-nil means mark entries as DONE when they are moved to the archive file. -This can be a string to set the keyword to use. When t, Org-mode will +This can be a string to set the keyword to use. When non-nil, Org will use the first keyword in its list that means done." :group 'org-archive :type '(choice @@ -120,9 +120,15 @@ information." (const :tag "Outline path" olpath) (const :tag "Local tags" ltags))) +(defvar org-archive-hook nil + "Hook run after successfully archiving a subtree. +Hook functions are called with point on the subtree in the +original file. At this stage, the subtree has been added to the +archive location, but not yet deleted from the original file.") + (defun org-get-local-archive-location () "Get the archive location applicable at point." - (let ((re "^#\\+ARCHIVE:[ \t]+\\(\\S-.*\\S-\\)[ \t]*$") + (let ((re "^[ \t]*#\\+ARCHIVE:[ \t]+\\(\\S-.*\\S-\\)[ \t]*$") prop) (save-excursion (save-restriction @@ -154,21 +160,24 @@ archive file is." (defun org-all-archive-files () "Get a list of all archive files used in the current buffer." - (let (file files) - (save-excursion - (save-restriction - (goto-char (point-min)) - (while (re-search-forward - "^\\(#\\+\\|[ \t]*:\\)ARCHIVE:[ \t]+\\(.*\\)" - nil t) - (setq file (org-extract-archive-file - (org-match-string-no-properties 2))) - (and file (> (length file) 0) (file-exists-p file) - (pushnew file files :test #'equal))))) + (let ((case-fold-search t) + files) + (org-with-wide-buffer + (goto-char (point-min)) + (while (re-search-forward + "^[ \t]*\\(#\\+\\|:\\)ARCHIVE:[ \t]+\\(.*\\)" + nil t) + (when (save-match-data + (if (eq (match-string 1) ":") (org-at-property-p) + (eq (org-element-type (org-element-at-point)) 'keyword))) + (let ((file (org-extract-archive-file + (match-string-no-properties 2)))) + (when (and (org-string-nw-p file) (file-exists-p file)) + (push file files)))))) (setq files (nreverse files)) - (setq file (org-extract-archive-file)) - (and file (> (length file) 0) (file-exists-p file) - (pushnew file files :test #'equal)) + (let ((file (org-extract-archive-file))) + (when (and (org-string-nw-p file) (file-exists-p file)) + (push file files))) files)) (defun org-extract-archive-file (&optional location) @@ -195,15 +204,19 @@ if LOCATION is not given, the value of `org-archive-location' is used." ;;;###autoload (defun org-archive-subtree (&optional find-done) "Move the current subtree to the archive. -The archive can be a certain top-level heading in the current file, or in -a different file. The tree will be moved to that location, the subtree -heading be marked DONE, and the current time will be added. - -When called with prefix argument FIND-DONE, find whole trees without any -open TODO items and archive them (after getting confirmation from the user). -If the cursor is not at a headline when this command is called, try all level -1 trees. If the cursor is on a headline, only try the direct children of -this heading." +The archive can be a certain top-level heading in the current +file, or in a different file. The tree will be moved to that +location, the subtree heading be marked DONE, and the current +time will be added. + +When called with a single prefix argument FIND-DONE, find whole +trees without any open TODO items and archive them (after getting +confirmation from the user). When called with a double prefix +argument, find whole trees with timestamps before today and +archive them (after getting confirmation from the user). If the +cursor is not at a headline when these commands are called, try +all level 1 trees. If the cursor is on a headline, only try the +direct children of this heading." (interactive "P") (if (and (org-region-active-p) org-loop-over-headlines-in-active-region) (let ((cl (if (eq org-loop-over-headlines-in-active-region 'start-level) @@ -213,46 +226,36 @@ this heading." `(progn (setq org-map-continue-from (progn (org-back-to-heading) (point))) (org-archive-subtree ,find-done)) org-loop-over-headlines-in-active-region - cl (if (outline-invisible-p) (org-end-of-subtree nil t)))) - (if find-done - (org-archive-all-done) + cl (if (org-invisible-p) (org-end-of-subtree nil t)))) + (cond + ((equal find-done '(4)) (org-archive-all-done)) + ((equal find-done '(16)) (org-archive-all-old)) + (t ;; Save all relevant TODO keyword-relatex variables - (let ((tr-org-todo-line-regexp org-todo-line-regexp) ; keep despite compiler - (tr-org-todo-keywords-1 org-todo-keywords-1) - (tr-org-todo-kwd-alist org-todo-kwd-alist) - (tr-org-done-keywords org-done-keywords) - (tr-org-todo-regexp org-todo-regexp) - (tr-org-todo-line-regexp org-todo-line-regexp) - (tr-org-odd-levels-only org-odd-levels-only) - (this-buffer (current-buffer)) - ;; start of variables that will be used for saving context - ;; The compiler complains about them - keep them anyway! - (file (abbreviate-file-name - (or (buffer-file-name (buffer-base-buffer)) - (error "No file associated to buffer")))) - (olpath (mapconcat 'identity (org-get-outline-path) "/")) - (time (format-time-string - (substring (cdr org-time-stamp-formats) 1 -1))) - category todo priority ltags itags atags - ;; end of variables that will be used for saving context - location afile heading buffer level newfile-p infile-p visiting - datetree-date datetree-subheading-p) - - ;; Find the local archive location - (setq location (org-get-local-archive-location) - afile (org-extract-archive-file location) - heading (org-extract-archive-heading location) - infile-p (equal file (abbreviate-file-name (or afile "")))) - (unless afile - (error "Invalid `org-archive-location'")) - - (if (> (length afile) 0) - (setq newfile-p (not (file-exists-p afile)) - visiting (find-buffer-visiting afile) - buffer (or visiting (find-file-noselect afile))) - (setq buffer (current-buffer))) - (unless buffer - (error "Cannot access file \"%s\"" afile)) + (let* ((tr-org-todo-keywords-1 org-todo-keywords-1) + (tr-org-todo-kwd-alist org-todo-kwd-alist) + (tr-org-done-keywords org-done-keywords) + (tr-org-todo-regexp org-todo-regexp) + (tr-org-todo-line-regexp org-todo-line-regexp) + (tr-org-odd-levels-only org-odd-levels-only) + (this-buffer (current-buffer)) + (time (format-time-string + (substring (cdr org-time-stamp-formats) 1 -1))) + (file (abbreviate-file-name + (or (buffer-file-name (buffer-base-buffer)) + (error "No file associated to buffer")))) + (location (org-get-local-archive-location)) + (afile (or (org-extract-archive-file location) + (error "Invalid `org-archive-location'"))) + (heading (org-extract-archive-heading location)) + (infile-p (equal file (abbreviate-file-name (or afile "")))) + (newfile-p (and (org-string-nw-p afile) + (not (file-exists-p afile)))) + (buffer (cond ((not (org-string-nw-p afile)) this-buffer) + ((find-buffer-visiting afile)) + ((find-file-noselect afile)) + (t (error "Cannot access file \"%s\"" afile)))) + level datetree-date datetree-subheading-p) (when (string-match "\\`datetree/" heading) ;; Replace with ***, to represent the 3 levels of headings the ;; datetree has. @@ -266,108 +269,123 @@ this heading." (setq heading nil level 0)) (save-excursion (org-back-to-heading t) - ;; Get context information that will be lost by moving the tree - (setq category (org-get-category nil 'force-refresh) - todo (and (looking-at org-todo-line-regexp) - (match-string 2)) - priority (org-get-priority - (if (match-end 3) (match-string 3) "")) - ltags (org-get-tags) - itags (org-delete-all ltags (org-get-tags-at)) - atags (org-get-tags-at)) - (setq ltags (mapconcat 'identity ltags " ") - itags (mapconcat 'identity itags " ")) - ;; We first only copy, in case something goes wrong - ;; we need to protect `this-command', to avoid kill-region sets it, - ;; which would lead to duplication of subtrees - (let (this-command) (org-copy-subtree 1 nil t)) - (set-buffer buffer) - ;; Enforce org-mode for the archive buffer - (if (not (derived-mode-p 'org-mode)) - ;; Force the mode for future visits. - (let ((org-insert-mode-line-in-empty-file t) - (org-inhibit-startup t)) - (call-interactively 'org-mode))) - (when (and newfile-p org-archive-file-header-format) - (goto-char (point-max)) - (insert (format org-archive-file-header-format - (buffer-file-name this-buffer)))) - (when datetree-date - (require 'org-datetree) - (org-datetree-find-date-create datetree-date) - (org-narrow-to-subtree)) - ;; Force the TODO keywords of the original buffer - (let ((org-todo-line-regexp tr-org-todo-line-regexp) - (org-todo-keywords-1 tr-org-todo-keywords-1) - (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))) - (goto-char (point-min)) - (show-all) - (if (and heading (not (and datetree-date (not datetree-subheading-p)))) - (progn - (if (re-search-forward - (concat "^" (regexp-quote heading) - (org-re "[ \t]*\\(:[[:alnum:]_@#%:]+:\\)?[ \t]*\\($\\|\r\\)")) - nil t) - (goto-char (match-end 0)) - ;; Heading not found, just insert it at the end - (goto-char (point-max)) - (or (bolp) (insert "\n")) - ;; datetrees don't need too much spacing - (insert (if datetree-date "" "\n") heading "\n") - (end-of-line 0)) - ;; Make the subtree visible - (show-subtree) - (if org-archive-reversed-order - (progn - (org-back-to-heading t) - (outline-next-heading)) - (org-end-of-subtree t)) - (skip-chars-backward " \t\r\n") - (and (looking-at "[ \t\r\n]*") - ;; datetree archives don't need so much spacing. - (replace-match (if datetree-date "\n" "\n\n")))) - ;; No specific heading, just go to end of file. - (goto-char (point-max)) (unless datetree-date (insert "\n"))) - ;; Paste - (org-paste-subtree (org-get-valid-level level (and heading 1))) - ;; Shall we append inherited tags? - (and itags - (or (and (eq org-archive-subtree-add-inherited-tags 'infile) - infile-p) - (eq org-archive-subtree-add-inherited-tags t)) - (org-set-tags-to atags)) - ;; Mark the entry as done - (when (and org-archive-mark-done - (looking-at org-todo-line-regexp) - (or (not (match-end 2)) - (not (member (match-string 2) org-done-keywords)))) - (let (org-log-done org-todo-log-states) - (org-todo - (car (or (member org-archive-mark-done org-done-keywords) - org-done-keywords))))) - - ;; Add the context info - (when org-archive-save-context-info - (let ((l org-archive-save-context-info) e n v) - (while (setq e (pop l)) - (when (and (setq v (symbol-value e)) - (stringp v) (string-match "\\S-" v)) - (setq n (concat "ARCHIVE_" (upcase (symbol-name e)))) - (org-entry-put (point) n v))))) - - (widen) - ;; Save and kill the buffer, if it is not the same buffer. - (when (not (eq this-buffer buffer)) - (save-buffer)))) - ;; Here we are back in the original buffer. Everything seems to have - ;; worked. So now cut the tree and finish up. + ;; Get context information that will be lost by moving the + ;; tree. See `org-archive-save-context-info'. + (let* ((all-tags (org-get-tags-at)) + (local-tags (org-get-tags)) + (inherited-tags (org-delete-all local-tags all-tags)) + (context + `((category . ,(org-get-category nil 'force-refresh)) + (file . ,file) + (itags . ,(mapconcat #'identity inherited-tags " ")) + (ltags . ,(mapconcat #'identity local-tags " ")) + (olpath . ,(mapconcat #'identity + (org-get-outline-path) + "/")) + (time . ,time) + (todo . ,(org-entry-get (point) "TODO"))))) + ;; We first only copy, in case something goes wrong + ;; we need to protect `this-command', to avoid kill-region sets it, + ;; which would lead to duplication of subtrees + (let (this-command) (org-copy-subtree 1 nil t)) + (set-buffer buffer) + ;; Enforce Org mode for the archive buffer + (if (not (derived-mode-p 'org-mode)) + ;; Force the mode for future visits. + (let ((org-insert-mode-line-in-empty-file t) + (org-inhibit-startup t)) + (call-interactively 'org-mode))) + (when (and newfile-p org-archive-file-header-format) + (goto-char (point-max)) + (insert (format org-archive-file-header-format + (buffer-file-name this-buffer)))) + (when datetree-date + (require 'org-datetree) + (org-datetree-find-date-create datetree-date) + (org-narrow-to-subtree)) + ;; Force the TODO keywords of the original buffer + (let ((org-todo-line-regexp tr-org-todo-line-regexp) + (org-todo-keywords-1 tr-org-todo-keywords-1) + (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))) + (goto-char (point-min)) + (outline-show-all) + (if (and heading (not (and datetree-date (not datetree-subheading-p)))) + (progn + (if (re-search-forward + (concat "^" (regexp-quote heading) + "[ \t]*\\(:[[:alnum:]_@#%:]+:\\)?[ \t]*\\($\\|\r\\)") + nil t) + (goto-char (match-end 0)) + ;; Heading not found, just insert it at the end + (goto-char (point-max)) + (or (bolp) (insert "\n")) + ;; datetrees don't need too much spacing + (insert (if datetree-date "" "\n") heading "\n") + (end-of-line 0)) + ;; Make the subtree visible + (outline-show-subtree) + (if org-archive-reversed-order + (progn + (org-back-to-heading t) + (outline-next-heading)) + (org-end-of-subtree t)) + (skip-chars-backward " \t\r\n") + (and (looking-at "[ \t\r\n]*") + ;; datetree archives don't need so much spacing. + (replace-match (if datetree-date "\n" "\n\n")))) + ;; No specific heading, just go to end of file, or to the + ;; beginning, depending on `org-archive-reversed-order'. + (if org-archive-reversed-order + (progn + (goto-char (point-min)) + (unless (org-at-heading-p) (outline-next-heading)) + (insert "\n") (backward-char 1)) + (goto-char (point-max)) + ;; Subtree narrowing can let the buffer end on + ;; a headline. `org-paste-subtree' then deletes it. + ;; To prevent this, make sure visible part of buffer + ;; always terminates on a new line, while limiting + ;; number of blank lines in a date tree. + (unless (and datetree-date (bolp)) (insert "\n")))) + ;; Paste + (org-paste-subtree (org-get-valid-level level (and heading 1))) + ;; Shall we append inherited tags? + (and inherited-tags + (or (and (eq org-archive-subtree-add-inherited-tags 'infile) + infile-p) + (eq org-archive-subtree-add-inherited-tags t)) + (org-set-tags-to all-tags)) + ;; Mark the entry as done + (when (and org-archive-mark-done + (let ((case-fold-search nil)) + (looking-at org-todo-line-regexp)) + (or (not (match-end 2)) + (not (member (match-string 2) org-done-keywords)))) + (let (org-log-done org-todo-log-states) + (org-todo + (car (or (member org-archive-mark-done org-done-keywords) + org-done-keywords))))) + + ;; Add the context info. + (dolist (item org-archive-save-context-info) + (let ((value (cdr (assq item context)))) + (when (org-string-nw-p value) + (org-entry-put + (point) + (concat "ARCHIVE_" (upcase (symbol-name item))) + value)))) + (widen)))) + ;; Here we are back in the original buffer. Everything seems + ;; to have worked. So now run hooks, cut the tree and finish + ;; up. + (run-hooks 'org-archive-hook) (let (this-command) (org-cut-subtree)) (when (featurep 'org-inlinetask) (org-inlinetask-remove-END-maybe)) @@ -375,7 +393,7 @@ this heading." (message "Subtree archived %s" (if (eq this-buffer buffer) (concat "under heading: " heading) - (concat "in file: " (abbreviate-file-name afile)))))) + (concat "in file: " (abbreviate-file-name afile))))))) (org-reveal) (if (looking-at "^[ \t]*$") (outline-next-visible-heading 1)))) @@ -383,9 +401,12 @@ this heading." ;;;###autoload (defun org-archive-to-archive-sibling () "Archive the current heading by moving it under the archive sibling. + The archive sibling is a sibling of the heading with the heading name `org-archive-sibling-heading' and an `org-archive-tag' tag. If this -sibling does not exist, it will be created at the end of the subtree." +sibling does not exist, it will be created at the end of the subtree. + +Archiving time is retained in the ARCHIVE_TIME node property." (interactive) (if (and (org-region-active-p) org-loop-over-headlines-in-active-region) (let ((cl (when (eq org-loop-over-headlines-in-active-region 'start-level) @@ -400,7 +421,7 @@ sibling does not exist, it will be created at the end of the subtree." (when (org-at-heading-p) (org-archive-to-archive-sibling))) org-loop-over-headlines-in-active-region - cl (if (outline-invisible-p) (org-end-of-subtree nil t)))) + cl (if (org-invisible-p) (org-end-of-subtree nil t)))) (save-restriction (widen) (let (b e pos leader level) @@ -443,7 +464,7 @@ sibling does not exist, it will be created at the end of the subtree." (format-time-string (substring (cdr org-time-stamp-formats) 1 -1))) (outline-up-heading 1 t) - (hide-subtree) + (outline-hide-subtree) (org-cycle-show-empty-lines 'folded) (goto-char pos))) (org-reveal) @@ -455,13 +476,51 @@ sibling does not exist, it will be created at the end of the subtree." If the cursor is not on a headline, try all level 1 trees. If it is on a headline, try all direct children. When TAG is non-nil, don't move trees, but mark them with the ARCHIVE tag." - (let ((re org-not-done-heading-regexp) re1 - (rea (concat ".*:" org-archive-tag ":")) + (org-archive-all-matches + (lambda (_beg end) + (let ((case-fold-search nil)) + (unless (re-search-forward org-not-done-heading-regexp end t) + "no open TODO items"))) + tag)) + +(defun org-archive-all-old (&optional tag) + "Archive sublevels of the current tree with timestamps prior to today. +If the cursor is not on a headline, try all level 1 trees. If +it is on a headline, try all direct children. +When TAG is non-nil, don't move trees, but mark them with the ARCHIVE tag." + (org-archive-all-matches + (lambda (_beg end) + (let (ts) + (and (re-search-forward org-ts-regexp end t) + (setq ts (match-string 0)) + (< (org-time-stamp-to-now ts) 0) + (if (not (looking-at + (concat "--\\(" org-ts-regexp "\\)"))) + (concat "old timestamp " ts) + (setq ts (concat "old timestamp " ts (match-string 0))) + (and (< (org-time-stamp-to-now (match-string 1)) 0) + ts))))) + tag)) + +(defun org-archive-all-matches (predicate &optional tag) + "Archive sublevels of the current tree that match PREDICATE. + +PREDICATE is a function of two arguments, BEG and END, which +specify the beginning and end of the headline being considered. +It is called with point positioned at BEG. The headline will be +archived if PREDICATE returns non-nil. If the return value of +PREDICATE is a string, it should describe the reason for +archiving the heading. + +If the cursor is not on a headline, try all level 1 trees. If it +is on a headline, try all direct children. When TAG is non-nil, +don't move trees, but mark them with the ARCHIVE tag." + (let ((rea (concat ".*:" org-archive-tag ":")) re1 (begm (make-marker)) (endm (make-marker)) - (question (if tag "Set ARCHIVE tag (no open TODO items)? " - "Move subtree to archive (no open TODO items)? ")) - beg end (cntarch 0)) + (question (if tag "Set ARCHIVE tag? " + "Move subtree to archive? ")) + reason beg end (cntarch 0)) (if (org-at-heading-p) (progn (setq re1 (concat "^" (regexp-quote @@ -481,11 +540,14 @@ When TAG is non-nil, don't move trees, but mark them with the ARCHIVE tag." (setq beg (match-beginning 0) end (save-excursion (org-end-of-subtree t) (point))) (goto-char beg) - (if (re-search-forward re end t) + (if (not (setq reason (funcall predicate beg end))) (goto-char end) (goto-char beg) (if (and (or (not tag) (not (looking-at rea))) - (y-or-n-p question)) + (y-or-n-p + (if (stringp reason) + (concat question "(" reason ")") + question))) (progn (if tag (org-toggle-tag org-archive-tag 'on) @@ -507,14 +569,14 @@ the children that do not contain any open TODO items." (org-map-entries `(org-toggle-archive-tag ,find-done) org-loop-over-headlines-in-active-region - cl (if (outline-invisible-p) (org-end-of-subtree nil t)))) + cl (if (org-invisible-p) (org-end-of-subtree nil t)))) (if find-done (org-archive-all-done 'tag) (let (set) (save-excursion (org-back-to-heading t) (setq set (org-toggle-tag org-archive-tag)) - (when set (hide-subtree))) + (when set (org-flag-subtree t))) (and set (beginning-of-line 1)) (message "Subtree %s" (if set "archived" "unarchived")))))) @@ -528,7 +590,7 @@ the children that do not contain any open TODO items." (org-map-entries 'org-archive-set-tag org-loop-over-headlines-in-active-region - cl (if (outline-invisible-p) (org-end-of-subtree nil t)))) + cl (if (org-invisible-p) (org-end-of-subtree nil t)))) (org-toggle-tag org-archive-tag 'on))) ;;;###autoload diff --git a/lisp/org/org-attach.el b/lisp/org/org-attach.el index 7d25437d9f5..cd6b4136233 100644 --- a/lisp/org/org-attach.el +++ b/lisp/org/org-attach.el @@ -1,4 +1,4 @@ -;;; org-attach.el --- Manage file attachments to org-mode tasks +;;; org-attach.el --- Manage file attachments to Org tasks -*- lexical-binding: t; -*- ;; Copyright (C) 2008-2017 Free Software Foundation, Inc. @@ -18,11 +18,11 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: -;; See the Org-mode manual for information on how to use it. +;; See the Org manual for information on how to use it. ;; ;; Attachments are managed in a special directory called "data", which ;; lives in the same directory as the org file itself. If this data @@ -37,14 +37,15 @@ ;;; Code: -(eval-when-compile - (require 'cl)) -(require 'org-id) +(require 'cl-lib) (require 'org) +(require 'org-id) (require 'vc-git) +(declare-function dired-dwim-target-directory "dired-aux") + (defgroup org-attach nil - "Options concerning entry attachments in Org-mode." + "Options concerning entry attachments in Org mode." :tag "Org Attach" :group 'org) @@ -55,6 +56,14 @@ where the Org file lives." :group 'org-attach :type 'directory) +(defcustom org-attach-commit t + "If non-nil commit attachments with git. +This is only done if the Org file is in a git repository." + :group 'org-attach + :type 'boolean + :version "26.1" + :package-version '(Org . "9.0")) + (defcustom org-attach-git-annex-cutoff (* 32 1024) "If non-nil, files larger than this will be annexed instead of stored." :group 'org-attach @@ -120,6 +129,28 @@ lns create a symbol link. Note that this is not supported (const :tag "Link to origin location" t) (const :tag "Link to the attach-dir location" attached))) +(defcustom org-attach-archive-delete nil + "Non-nil means attachments are deleted upon archiving a subtree. +When set to `query', ask the user instead." + :group 'org-attach + :version "26.1" + :package-version '(Org . "8.3") + :type '(choice + (const :tag "Never delete attachments" nil) + (const :tag "Always delete attachments" t) + (const :tag "Query the user" query))) + +(defcustom org-attach-annex-auto-get 'ask + "Confirmation preference for automatically getting annex files. +If \\='ask, prompt using `y-or-n-p'. If t, always get. If nil, never get." + :group 'org-attach + :package-version '(Org . "9.0") + :version "26.1" + :type '(choice + (const :tag "confirm with `y-or-n-p'" ask) + (const :tag "always get from annex if necessary" t) + (const :tag "never get from annex" nil))) + ;;;###autoload (defun org-attach () "The dispatcher for attachment commands. @@ -144,6 +175,7 @@ Shows a list of commands and prompts for another key to execute a command." a Select a file and attach it to the task, using `org-attach-method'. c/m/l/y Attach a file using copy/move/link/symbolic-link method. +u Attach a file from URL (downloading it). n Create a new attachment, as an Emacs buffer. z Synchronize the current task with its attachment directory, in case you added attachments yourself. @@ -157,7 +189,7 @@ d Delete one attachment, you will be prompted for a file name. D Delete all of a task's attachments. A safer way is to open the directory in dired and delete from there. -s Set a specific attachment directory for this entry. +s Set a specific attachment directory for this entry or reset to default. i Make children of the current entry inherit its attachment directory."))) (org-fit-window-to-buffer (get-buffer-window "*Org Attach*")) (message "Select command: [acmlzoOfFdD]") @@ -173,6 +205,8 @@ i Make children of the current entry inherit its attachment directory."))) (let ((org-attach-method 'ln)) (call-interactively 'org-attach-attach))) ((memq c '(?y ?\C-y)) (let ((org-attach-method 'lns)) (call-interactively 'org-attach-attach))) + ((memq c '(?u ?\C-u)) + (let ((org-attach-method 'url)) (call-interactively 'org-attach-url))) ((memq c '(?n ?\C-n)) (call-interactively 'org-attach-new)) ((memq c '(?z ?\C-z)) (call-interactively 'org-attach-sync)) ((memq c '(?o ?\C-o)) (call-interactively 'org-attach-open)) @@ -197,25 +231,23 @@ using the entry ID will be invoked to access the unique directory for the current entry. If the directory does not exist and CREATE-IF-NOT-EXISTS-P is non-nil, the directory and (if necessary) the corresponding ID will be created." - (let (attach-dir uuid inherit) + (let (attach-dir uuid) (setq org-attach-inherited (org-entry-get nil "ATTACH_DIR_INHERIT")) (cond ((setq attach-dir (org-entry-get nil "ATTACH_DIR")) (org-attach-check-absolute-path attach-dir)) ((and org-attach-allow-inheritance - (setq inherit (org-entry-get nil "ATTACH_DIR_INHERIT" t))) + (org-entry-get nil "ATTACH_DIR_INHERIT" t)) (setq attach-dir - (save-excursion - (save-restriction - (widen) - (if (marker-position org-entry-property-inherited-from) - (goto-char org-entry-property-inherited-from) - (org-back-to-heading t)) - (let (org-attach-allow-inheritance) - (org-attach-dir create-if-not-exists-p))))) + (org-with-wide-buffer + (if (marker-position org-entry-property-inherited-from) + (goto-char org-entry-property-inherited-from) + (org-back-to-heading t)) + (let (org-attach-allow-inheritance) + (org-attach-dir create-if-not-exists-p)))) (org-attach-check-absolute-path attach-dir) (setq org-attach-inherited t)) - (t ; use the ID + (t ; use the ID (org-attach-check-absolute-path nil) (setq uuid (org-id-get (point) create-if-not-exists-p)) (when (or uuid create-if-not-exists-p) @@ -243,14 +275,30 @@ Throw an error if we cannot root the directory." (buffer-file-name (buffer-base-buffer)) (error "Need absolute `org-attach-directory' to attach in buffers without filename"))) -(defun org-attach-set-directory () - "Set the ATTACH_DIR property of the current entry. +(defun org-attach-set-directory (&optional arg) + "Set the ATTACH_DIR node property and ask to move files there. The property defines the directory that is used for attachments -of the entry." - (interactive) - (let ((dir (org-entry-get nil "ATTACH_DIR"))) - (setq dir (read-directory-name "Attachment directory: " dir)) - (org-entry-put nil "ATTACH_DIR" dir))) +of the entry. When called with `\\[universal-argument]', reset \ +the directory to +the default ID based one." + (interactive "P") + (let ((old (org-attach-dir)) + (new + (progn + (if arg (org-entry-delete nil "ATTACH_DIR") + (let ((dir (read-directory-name + "Attachment directory: " + (org-entry-get nil + "ATTACH_DIR" + (and org-attach-allow-inheritance t))))) + (org-entry-put nil "ATTACH_DIR" dir))) + (org-attach-dir t)))) + (unless (or (string= old new) + (not old)) + (when (yes-or-no-p "Copy over attachments from old directory? ") + (copy-directory old new t nil t)) + (when (yes-or-no-p (concat "Delete " old)) + (delete-directory old t))))) (defun org-attach-set-inherit () "Set the ATTACH_DIR_INHERIT property of the current entry. @@ -261,33 +309,59 @@ the ATTACH_DIR property) their own attachment directory." (org-entry-put nil "ATTACH_DIR_INHERIT" "t") (message "Children will inherit attachment directory")) +(defun org-attach-use-annex () + "Return non-nil if git annex can be used." + (let ((git-dir (vc-git-root (expand-file-name org-attach-directory)))) + (and org-attach-git-annex-cutoff + (or (file-exists-p (expand-file-name "annex" git-dir)) + (file-exists-p (expand-file-name ".git/annex" git-dir)))))) + +(defun org-attach-annex-get-maybe (path) + "Call git annex get PATH (via shell) if using git annex. +Signals an error if the file content is not available and it was not retrieved." + (let ((path-relative (file-relative-name path))) + (when (and (org-attach-use-annex) + (not + (string-equal + "found" + (shell-command-to-string + (format "git annex find --format=found --in=here %s" + (shell-quote-argument path-relative)))))) + (let ((should-get + (if (eq org-attach-annex-auto-get 'ask) + (y-or-n-p (format "Run git annex get %s? " path-relative)) + org-attach-annex-auto-get))) + (if should-get + (progn (message "Running git annex get \"%s\"." path-relative) + (call-process "git" nil nil nil "annex" "get" path-relative)) + (error "File %s stored in git annex but it is not available, and was not retrieved" + path)))))) + (defun org-attach-commit () "Commit changes to git if `org-attach-directory' is properly initialized. This checks for the existence of a \".git\" directory in that directory." (let* ((dir (expand-file-name org-attach-directory)) (git-dir (vc-git-root dir)) + (use-annex (org-attach-use-annex)) (changes 0)) (when (and git-dir (executable-find "git")) (with-temp-buffer (cd dir) - (let ((have-annex - (and org-attach-git-annex-cutoff - (file-exists-p (expand-file-name "annex" git-dir))))) - (dolist (new-or-modified - (split-string - (shell-command-to-string - "git ls-files -zmo --exclude-standard") "\0" t)) - (if (and have-annex - (>= (nth 7 (file-attributes new-or-modified)) - org-attach-git-annex-cutoff)) - (call-process "git" nil nil nil "annex" "add" new-or-modified) - (call-process "git" nil nil nil "add" new-or-modified)) - (incf changes))) + (dolist (new-or-modified + (split-string + (shell-command-to-string + "git ls-files -zmo --exclude-standard") "\0" t)) + (if (and use-annex + (>= (nth 7 (file-attributes new-or-modified)) + org-attach-git-annex-cutoff)) + (call-process "git" nil nil nil "annex" "add" new-or-modified) + (call-process "git" nil nil nil "add" new-or-modified)) + (cl-incf changes)) (dolist (deleted (split-string (shell-command-to-string "git ls-files -z --deleted") "\0" t)) (call-process "git" nil nil nil "rm" deleted) - (incf changes)) + (cl-incf changes)) (when (> changes 0) (shell-command "git commit -m 'Synchronized attachments'")))))) @@ -310,33 +384,47 @@ Only do this when `org-attach-store-link-p' is non-nil." (file-name-nondirectory file)) org-stored-links))) +(defun org-attach-url (url) + (interactive "MURL of the file to attach: \n") + (org-attach-attach url)) + (defun org-attach-attach (file &optional visit-dir method) "Move/copy/link FILE into the attachment directory of the current task. If VISIT-DIR is non-nil, visit the directory with dired. -METHOD may be `cp', `mv', `ln', or `lns' default taken from +METHOD may be `cp', `mv', `ln', `lns' or `url' default taken from `org-attach-method'." - (interactive "fFile to keep as an attachment: \nP") + (interactive + (list + (read-file-name "File to keep as an attachment:" + (or (progn + (require 'dired-aux) + (dired-dwim-target-directory)) + default-directory)) + current-prefix-arg + nil)) (setq method (or method org-attach-method)) (let ((basename (file-name-nondirectory file))) (when (and org-attach-file-list-property (not org-attach-inherited)) (org-entry-add-to-multivalued-property (point) org-attach-file-list-property basename)) (let* ((attach-dir (org-attach-dir t)) - (fname (expand-file-name basename attach-dir))) + (fname (expand-file-name basename attach-dir))) (cond - ((eq method 'mv) (rename-file file fname)) - ((eq method 'cp) (copy-file file fname)) + ((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))) - (org-attach-commit) + ((eq method 'lns) (make-symbolic-link file fname)) + ((eq method 'url) (url-copy-file file fname))) + (when org-attach-commit + (org-attach-commit)) (org-attach-tag) (cond ((eq org-attach-store-link-p 'attached) - (org-attach-store-link fname)) - ((eq org-attach-store-link-p t) - (org-attach-store-link file))) + (org-attach-store-link fname)) + ((eq org-attach-store-link-p t) + (org-attach-store-link file))) (if visit-dir - (dired attach-dir) - (message "File \"%s\" is now a task attachment." basename))))) + (dired attach-dir) + (message "File %S is now a task attachment." basename))))) (defun org-attach-attach-cp () "Attach a file by copying it." @@ -378,7 +466,7 @@ The attachment is created as an Emacs buffer." (let* ((attach-dir (org-attach-dir t)) (files (org-attach-file-list attach-dir)) (file (or file - (org-icompleting-read + (completing-read "Delete attachment: " (mapcar (lambda (f) (list (file-name-nondirectory f))) @@ -387,7 +475,8 @@ The attachment is created as an Emacs buffer." (unless (file-exists-p file) (error "No such attachment: %s" file)) (delete-file file) - (org-attach-commit))) + (when org-attach-commit + (org-attach-commit)))) (defun org-attach-delete-all (&optional force) "Delete all attachments from the current task. @@ -403,31 +492,33 @@ A safer way is to open the directory in dired and delete from there." (y-or-n-p "Are you sure you want to remove all attachments of this entry? "))) (shell-command (format "rm -fr %s" attach-dir)) (message "Attachment directory removed") - (org-attach-commit) + (when org-attach-commit + (org-attach-commit)) (org-attach-untag)))) (defun org-attach-sync () "Synchronize the current tasks with its attachments. This can be used after files have been added externally." (interactive) - (org-attach-commit) + (when org-attach-commit + (org-attach-commit)) (when (and org-attach-file-list-property (not org-attach-inherited)) (org-entry-delete (point) org-attach-file-list-property)) (let ((attach-dir (org-attach-dir))) (when attach-dir (let ((files (org-attach-file-list attach-dir))) - (and files (org-attach-tag)) + (org-attach-tag (not files)) (when org-attach-file-list-property (dolist (file files) - (unless (string-match "^\\." file) + (unless (string-match "^\\.\\.?\\'" file) (org-entry-add-to-multivalued-property (point) org-attach-file-list-property file)))))))) (defun org-attach-file-list (dir) "Return a list of files in the attachment directory. -This ignores files starting with a \".\", and files ending in \"~\"." +This ignores files ending in \"~\"." (delq nil - (mapcar (lambda (x) (if (string-match "^\\." x) nil x)) + (mapcar (lambda (x) (if (string-match "^\\.\\.?\\'" x) nil x)) (directory-files dir nil "[^~]\\'")))) (defun org-attach-reveal (&optional if-exists) @@ -454,9 +545,11 @@ If IN-EMACS is non-nil, force opening in Emacs." (files (org-attach-file-list attach-dir)) (file (if (= (length files) 1) (car files) - (org-icompleting-read "Open attachment: " - (mapcar 'list files) nil t)))) - (org-open-file (expand-file-name file attach-dir) in-emacs))) + (completing-read "Open attachment: " + (mapcar #'list files) nil t))) + (path (expand-file-name file attach-dir))) + (org-attach-annex-get-maybe path) + (org-open-file path in-emacs))) (defun org-attach-open-in-emacs () "Open attachment, force opening in Emacs. @@ -475,6 +568,17 @@ Basically, this adds the path to the attachment directory, and a \"file:\" prefix." (concat "file:" (org-attach-expand file))) +(defun org-attach-archive-delete-maybe () + "Maybe delete subtree attachments when archiving. +This function is called by `org-archive-hook'. The option +`org-attach-archive-delete' controls its behavior." + (when (if (eq org-attach-archive-delete 'query) + (yes-or-no-p "Delete all attachments? ") + org-attach-archive-delete) + (org-attach-delete-all t))) + +(add-hook 'org-archive-hook 'org-attach-archive-delete-maybe) + (provide 'org-attach) ;; Local variables: diff --git a/lisp/org/org-bbdb.el b/lisp/org/org-bbdb.el index e41bda47dbf..889271affea 100644 --- a/lisp/org/org-bbdb.el +++ b/lisp/org/org-bbdb.el @@ -1,4 +1,4 @@ -;;; org-bbdb.el --- Support for links to BBDB entries from within Org-mode +;;; org-bbdb.el --- Support for links to BBDB entries -*- lexical-binding: t; -*- ;; Copyright (C) 2004-2017 Free Software Foundation, Inc. @@ -20,17 +20,17 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Commentary: -;; This file implements links to BBDB database entries from within Org-mode. -;; Org-mode loads this module by default - if this is not what you want, +;; This file implements links to BBDB database entries from within Org. +;; Org mode loads this module by default - if this is not what you want, ;; configure the variable `org-modules'. ;; It also implements an interface (based on Ivar Rummelhoff's -;; bbdb-anniv.el) for those org-mode users, who do not use the diary +;; bbdb-anniv.el) for those Org users, who do not use the diary ;; but who do want to include the anniversaries stored in the BBDB ;; into the org-agenda. If you already include the `diary' into the ;; agenda, you might want to prefer to include the anniversaries in @@ -94,8 +94,7 @@ ;;; Code: (require 'org) -(eval-when-compile - (require 'cl)) +(require 'cl-lib) ;; Declare external functions and variables @@ -106,6 +105,7 @@ (declare-function bbdb-name "ext:bbdb-com" (string elidep)) (declare-function bbdb-completing-read-record "ext:bbdb-com" (prompt &optional omit-records)) +(declare-function bbdb-record-field "ext:bbdb" (record field)) (declare-function bbdb-record-getprop "ext:bbdb" (record property)) (declare-function bbdb-record-name "ext:bbdb" (record)) (declare-function bbdb-records "ext:bbdb" @@ -124,7 +124,7 @@ (declare-function calendar-leap-year-p "calendar" (year)) (declare-function diary-ordinal-suffix "diary-lib" (n)) -(org-no-warnings (defvar date)) ;; unprefixed, from calendar.el +(with-no-warnings (defvar date)) ;; unprefixed, from calendar.el ;; Customization @@ -138,6 +138,24 @@ :group 'org-bbdb-anniversaries :require 'bbdb) +(defcustom org-bbdb-general-anniversary-description-after 7 + "When to switch anniversary descriptions to a more general format. + +Anniversary descriptions include the point in time, when the +anniversary appears. This is, in its most general form, just the +date of the anniversary. Or more specific terms, like \"today\", +\"tomorrow\" or \"in n days\" are used to describe the time span. + +If the anniversary happens in less than that number of days, the +specific description is used. Otherwise, the general one is +used." + :group 'org-bbdb-anniversaries + :version "26.1" + :package-version '(Org . "9.1") + :type 'integer + :require 'bbdb + :safe #'integerp) + (defcustom org-bbdb-anniversary-format-alist '(("birthday" . (lambda (name years suffix) @@ -194,10 +212,12 @@ date year)." :group 'org-bbdb-anniversaries :require 'bbdb) - ;; Install the link type -(org-add-link-type "bbdb" 'org-bbdb-open 'org-bbdb-export) -(add-hook 'org-store-link-functions 'org-bbdb-store-link) +(org-link-set-parameters "bbdb" + :follow #'org-bbdb-open + :export #'org-bbdb-export + :complete #'org-bbdb-complete-link + :store #'org-bbdb-store-link) ;; Implementation (defun org-bbdb-store-link () @@ -208,7 +228,7 @@ date year)." (name (bbdb-record-name rec)) (company (if (fboundp 'bbdb-record-getprop) (bbdb-record-getprop rec 'company) - (car (bbdb-record-get-field rec 'organization)))) + (car (bbdb-record-field rec 'organization)))) (link (concat "bbdb:" name))) (org-store-link-props :type "bbdb" :name name :company company :link link :description name) @@ -230,10 +250,9 @@ italicized, in all other cases it is left unchanged." (defun org-bbdb-open (name) "Follow a BBDB link to NAME." (require 'bbdb-com) - (let ((inhibit-redisplay (not debug-on-error)) - (bbdb-electric-p nil)) + (let ((inhibit-redisplay (not debug-on-error))) (if (fboundp 'bbdb-name) - (org-bbdb-open-old name) + (org-bbdb-open-old name) (org-bbdb-open-new name)))) (defun org-bbdb-open-old (name) @@ -280,14 +299,11 @@ italicized, in all other cases it is left unchanged." "Convert YYYY-MM-DD to (month date year). Argument TIME-STR is the value retrieved from BBDB. If YYYY- is omitted it will be considered unknown." - (multiple-value-bind (a b c) (values-list (org-split-string time-str "-")) - (if (eq c nil) - (list (string-to-number a) - (string-to-number b) - nil) - (list (string-to-number b) - (string-to-number c) - (string-to-number a))))) + (pcase (org-split-string time-str "-") + (`(,a ,b nil) (list (string-to-number a) (string-to-number b) nil)) + (`(,a ,b ,c) (list (string-to-number b) + (string-to-number c) + (string-to-number a))))) (defun org-bbdb-anniv-split (str) "Split multiple entries in the BBDB anniversary field. @@ -325,9 +341,9 @@ The anniversaries are assumed to be stored `org-bbdb-anniversary-field'." (bbdb-split "\n" annivs))) (while annivs (setq split (org-bbdb-anniv-split (pop annivs))) - (multiple-value-bind (m d y) - (values-list (funcall org-bbdb-extract-date-fun (car split))) - (setq tmp (gethash (list m d) org-bbdb-anniv-hash)) + (pcase-let ((`(,m ,d ,y) (funcall org-bbdb-extract-date-fun + (car split)))) + (setq tmp (gethash (list m d) org-bbdb-anniv-hash)) (puthash (list m d) (cons (list y (bbdb-record-name rec) (cadr split)) @@ -335,7 +351,7 @@ The anniversaries are assumed to be stored `org-bbdb-anniversary-field'." org-bbdb-anniv-hash)))))) (setq org-bbdb-updated-p nil)) -(defun org-bbdb-updated (rec) +(defun org-bbdb-updated (_rec) "Record the fact that BBDB has been updated. This is used by Org to re-create the anniversary hash table." (setq org-bbdb-updated-p t)) @@ -397,6 +413,83 @@ 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: +;;; +;;; %%(org-bbdb-anniversaries-future) +;;; +;;; or +;;; +;;; %%(org-bbdb-anniversaries-future 3) +;;; +;;; to override the 7-day default. + +(defun org-bbdb-date-list (d n) + "Return a list of dates in (m d y) format from the given date D to n-1 days hence." + (let ((abs (calendar-absolute-from-gregorian d))) + (mapcar (lambda (i) (calendar-gregorian-from-absolute (+ abs i))) + (number-sequence 0 (1- n))))) + +(defun org-bbdb-anniversary-description (agenda-date anniv-date) + "Return a string used to incorporate into an agenda anniversary entry. +The calculation of the anniversary description string is based on +the difference between the anniversary date, given as ANNIV-DATE, +and the date on which the entry appears in the agenda, given as +AGENDA-DATE. This makes it possible to have different entries +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)) + ((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))) + (when (<= n 0) + (error "The (optional) argument of `org-bbdb-anniversaries-future' \ +must be positive")) + (let ( + ;; List of relevant dates. + (dates (org-bbdb-date-list date n)) + ;; Function to annotate text of each element of l with the + ;; anniversary date d. + (annotate-descriptions + (lambda (agenda-date d l) + (mapcar (lambda (x) + ;; The assumption here is that x is a bbdb link + ;; of the form [[bbdb:name][description]]. + ;; This function rather arbitrarily modifies + ;; the description by adding the date to it in + ;; a fixed format. + (let ((desc (org-bbdb-anniversary-description + agenda-date d))) + (string-match "]]" x) + (replace-match desc nil nil x))) + l)))) + ;; Map a function that generates anniversaries for each date + ;; over the dates and nconc the results into a single list. When + ;; it is no longer necessary to support older versions of Emacs, + ;; this can be done with a cl-mapcan; for now, we use the (apply + ;; #'nconc ...) method for compatibility. + (apply #'nconc + (mapcar + (lambda (d) + (let ((agenda-date date) + (date d)) + ;; Rebind 'date' so that org-bbdb-anniversaries will + ;; be fooled into giving us the list for the given + ;; date and then annotate the descriptions for that + ;; date. + (funcall annotate-descriptions agenda-date d (org-bbdb-anniversaries)))) + dates))))) + (defun org-bbdb-complete-link () "Read a bbdb link with name completion." (require 'bbdb-com) diff --git a/lisp/org/org-bibtex.el b/lisp/org/org-bibtex.el index f8b376daa18..8876085fd77 100644 --- a/lisp/org/org-bibtex.el +++ b/lisp/org/org-bibtex.el @@ -1,4 +1,4 @@ -;;; org-bibtex.el --- Org links to BibTeX entries +;;; org-bibtex.el --- Org links to BibTeX entries -*- lexical-binding: t; -*- ;; ;; Copyright (C) 2007-2017 Free Software Foundation, Inc. ;; @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; ;;; Commentary: ;; @@ -73,7 +73,7 @@ ;; ===================================================================== ;; ;; Additionally, the following functions are now available for storing -;; bibtex entries within Org-mode documents. +;; bibtex entries within Org documents. ;; ;; - Run `org-bibtex' to export the current file to a .bib. ;; @@ -92,27 +92,28 @@ ;; ;;; History: ;; -;; The link creation part has been part of Org-mode for a long time. +;; 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 ;; and then implemented by Bastien Guerry. ;; ;; Eric Schulte eventually added the functions for translating between -;; Org-mode headlines and Bibtex entries, and for fleshing out the Bibtex -;; fields of existing Org-mode headlines. +;; Org headlines and Bibtex entries, and for fleshing out the Bibtex +;; fields of existing Org headlines. ;; -;; Org-mode loads this module by default - if this is not what you want, +;; Org mode loads this module by default - if this is not what you want, ;; configure the variable `org-modules'. ;;; Code: (require 'org) (require 'bibtex) -(eval-when-compile - (require 'cl)) +(require 'cl-lib) (require 'org-compat) +(defvar org-agenda-overriding-header) +(defvar org-agenda-search-view-always-boolean) (defvar org-bibtex-description nil) ; dynamically scoped from org.el (defvar org-id-locations) @@ -120,7 +121,6 @@ (declare-function bibtex-generate-autokey "bibtex" ()) (declare-function bibtex-parse-entry "bibtex" (&optional content)) (declare-function bibtex-url "bibtex" (&optional pos no-browse)) -(declare-function org-babel-trim "ob-core" (string &optional regexp)) ;;; Bibtex data @@ -237,6 +237,17 @@ a missing title field." :version "24.1" :type 'boolean) +(defcustom org-bibtex-headline-format-function + (lambda (entry) (cdr (assq :title entry))) + "Function returning the headline text for `org-bibtex-write'. +It should take a single argument, the bibtex entry (an alist as +returned by `org-bibtex-read'). The default value simply returns +the entry title." + :group 'org-bibtex + :version "26.1" + :package-version '(Org . "9.1") + :type 'function) + (defcustom org-bibtex-export-arbitrary-fields nil "When converting to bibtex allow fields not defined in `org-bibtex-fields'. This only has effect if `org-bibtex-prefix' is defined, so as to @@ -264,26 +275,39 @@ IDs must be unique." (defcustom org-bibtex-tags-are-keywords nil "Convert the value of the keywords field to tags and vice versa. -If set to t, comma-separated entries in a bibtex entry's keywords -field will be converted to org tags. Note: spaces will be escaped -with underscores, and characters that are not permitted in org + +When non-nil, comma-separated entries in a bibtex entry's keywords +field will be converted to Org tags. Note: spaces will be escaped +with underscores, and characters that are not permitted in Org tags will be removed. -If t, local tags in an org entry will be exported as a -comma-separated string of keywords when exported to bibtex. Tags -defined in `org-bibtex-tags' or `org-bibtex-no-export-tags' will -not be exported." +When non-nil, local tags in an Org entry will be exported as +a comma-separated string of keywords when exported to bibtex. +If `org-bibtex-inherit-tags' is non-nil, inherited tags will also +be exported as keywords. Tags defined in `org-bibtex-tags' or +`org-bibtex-no-export-tags' will not be exported." :group 'org-bibtex :version "24.1" :type 'boolean) (defcustom org-bibtex-no-export-tags nil "List of tag(s) that should not be converted to keywords. -This variable is relevant only if `org-bibtex-tags-are-keywords' is t." +This variable is relevant only if `org-bibtex-tags-are-keywords' +is non-nil." :group 'org-bibtex :version "24.1" :type '(repeat :tag "Tag" (string))) +(defcustom org-bibtex-inherit-tags nil + "Controls whether inherited tags are converted to bibtex keywords. +It is relevant only if `org-bibtex-tags-are-keywords' is non-nil. +Tag inheritance itself is controlled by `org-use-tag-inheritance' +and `org-exclude-tags-from-inheritance'." + :group 'org-bibtex + :version "26.1" + :package-version '(Org . "8.3") + :type 'boolean) + (defcustom org-bibtex-type-property-name "btype" "Property in which to store bibtex entry type (e.g., article)." :group 'org-bibtex @@ -299,7 +323,7 @@ This variable is relevant only if `org-bibtex-tags-are-keywords' is t." (org-entry-get (point) (upcase property)) (org-entry-get (point) (concat org-bibtex-prefix (upcase property))))))) - (when it (org-babel-trim it)))) + (when it (org-trim it)))) (defun org-bibtex-put (property value) (let ((prop (upcase (if (keywordp property) @@ -312,27 +336,27 @@ This variable is relevant only if `org-bibtex-tags-are-keywords' is t." (defun org-bibtex-headline () "Return a bibtex entry of the given headline as a string." - (let* ((val (lambda (key lst) (cdr (assoc key lst)))) - (to (lambda (string) (intern (concat ":" string)))) - (from (lambda (key) (substring (symbol-name key) 1))) - flatten ; silent compiler warning - (flatten (lambda (&rest lsts) - (apply #'append (mapcar - (lambda (e) - (if (listp e) (apply flatten e) (list e))) - lsts)))) - (notes (buffer-string)) - (id (org-bibtex-get org-bibtex-key-property)) - (type (org-bibtex-get org-bibtex-type-property-name)) - (tags (when org-bibtex-tags-are-keywords - (delq nil - (mapcar - (lambda (tag) - (unless (member tag - (append org-bibtex-tags - org-bibtex-no-export-tags)) - tag)) - (org-get-local-tags-at)))))) + (letrec ((val (lambda (key lst) (cdr (assoc key lst)))) + (to (lambda (string) (intern (concat ":" string)))) + (from (lambda (key) (substring (symbol-name key) 1))) + (flatten (lambda (&rest lsts) + (apply #'append (mapcar + (lambda (e) + (if (listp e) (apply flatten e) (list e))) + lsts)))) + (id (org-bibtex-get org-bibtex-key-property)) + (type (org-bibtex-get org-bibtex-type-property-name)) + (tags (when org-bibtex-tags-are-keywords + (delq nil + (mapcar + (lambda (tag) + (unless (member tag + (append org-bibtex-tags + org-bibtex-no-export-tags)) + tag)) + (if org-bibtex-inherit-tags + (org-get-tags-at) + (org-get-local-tags-at))))))) (when type (let ((entry (format "@%s{%s,\n%s\n}\n" type id @@ -358,7 +382,7 @@ This variable is relevant only if `org-bibtex-tags-are-keywords' is t." (mapcar (lambda (field) (let ((value (or (org-bibtex-get (funcall from field)) - (and (equal :title field) + (and (eq :title field) (nth 4 (org-heading-components)))))) (when value (cons (funcall from field) value)))) (funcall flatten @@ -421,13 +445,14 @@ With optional argument OPTIONAL, also prompt for optional fields." (funcall val :required (funcall val type org-bibtex-types))) (when optional (funcall val :optional (funcall val type org-bibtex-types))))) (when (consp field) ; or'd pair of fields e.g., (:editor :author) - (let ((present (first (remove + (let ((present (nth 0 (remove nil (mapcar - (lambda (f) (when (org-bibtex-get (funcall name f)) f)) + (lambda (f) + (when (org-bibtex-get (funcall name f)) f)) field))))) (setf field (or present (funcall keyword - (org-icompleting-read + (completing-read "Field: " (mapcar name field))))))) (let ((name (funcall name field))) (unless (org-bibtex-get name) @@ -439,8 +464,9 @@ With optional argument OPTIONAL, also prompt for optional fields." ;;; Bibtex link functions -(org-add-link-type "bibtex" 'org-bibtex-open) -(add-hook 'org-store-link-functions 'org-bibtex-store-link) +(org-link-set-parameters "bibtex" + :follow #'org-bibtex-open + :store #'org-bibtex-store-link) (defun org-bibtex-open (path) "Visit the bibliography entry on PATH." @@ -533,21 +559,23 @@ With optional argument OPTIONAL, also prompt for optional fields." (add-hook 'org-execute-file-search-functions 'org-execute-file-search-in-bibtex) -;;; Bibtex <-> Org-mode headline translation functions -(defun org-bibtex (&optional filename) +;;; Bibtex <-> Org headline translation functions +(defun org-bibtex (filename) "Export each headline in the current file to a bibtex entry. Headlines are exported using `org-bibtex-headline'." (interactive (list (read-file-name "Bibtex file: " nil nil nil - (file-name-nondirectory - (concat (file-name-sans-extension (buffer-file-name)) ".bib"))))) + (let ((file (buffer-file-name (buffer-base-buffer)))) + (and file + (file-name-nondirectory + (concat (file-name-sans-extension file) ".bib"))))))) (let ((error-point (catch 'bib (let ((bibtex-entries (remove nil (org-map-entries (lambda () - (condition-case foo + (condition-case nil (org-bibtex-headline) (error (throw 'bib (point))))))))) (with-temp-file filename @@ -578,7 +606,7 @@ With prefix argument OPTIONAL also prompt for optional fields." With a prefix arg, query for optional fields as well. If nonew is t, add data to the headline of the entry at point." (interactive "P") - (let* ((type (org-icompleting-read + (let* ((type (completing-read "Type: " (mapcar (lambda (type) (substring (symbol-name (car type)) 1)) org-bibtex-types) @@ -597,7 +625,7 @@ If nonew is t, add data to the headline of the entry at point." (org-bibtex-put org-bibtex-type-property-name (substring (symbol-name type) 1)) (org-bibtex-fleshout type arg) - (mapc (lambda (tag) (org-toggle-tag tag 'on)) org-bibtex-tags))) + (dolist (tag org-bibtex-tags) (org-toggle-tag tag 'on)))) (defun org-bibtex-create-in-current-entry (&optional arg) "Add bibliographical data to the current entry. @@ -611,10 +639,10 @@ This uses `bibtex-parse-entry'." (interactive) (let ((keyword (lambda (str) (intern (concat ":" (downcase str))))) (clean-space (lambda (str) (replace-regexp-in-string - "[[:space:]\n\r]+" " " str))) + "[[:space:]\n\r]+" " " str))) (strip-delim - (lambda (str) ; strip enclosing "..." and {...} - (dolist (pair '((34 . 34) (123 . 125) (123 . 125))) + (lambda (str) ; strip enclosing "..." and {...} + (dolist (pair '((34 . 34) (123 . 125))) (when (and (> (length str) 1) (= (aref str 0) (car pair)) (= (aref str (1- (length str))) (cdr pair))) @@ -622,10 +650,10 @@ This uses `bibtex-parse-entry'." (push (mapcar (lambda (pair) (cons (let ((field (funcall keyword (car pair)))) - (case field + (pcase field (:=type= :type) (:=key= :key) - (otherwise field))) + (_ field))) (funcall clean-space (funcall strip-delim (cdr pair))))) (save-excursion (bibtex-beginning-of-entry) (bibtex-parse-entry))) org-bibtex-entries))) @@ -633,7 +661,7 @@ This uses `bibtex-parse-entry'." (defun org-bibtex-read-buffer (buffer) "Read all bibtex entries in BUFFER and save to `org-bibtex-entries'. Return the number of saved entries." - (interactive "bbuffer: ") + (interactive "bBuffer: ") (let ((start-length (length org-bibtex-entries))) (with-current-buffer buffer (save-excursion @@ -643,12 +671,12 @@ Return the number of saved entries." (org-bibtex-read) (bibtex-beginning-of-entry)))) (let ((added (- (length org-bibtex-entries) start-length))) - (message "parsed %d entries" added) + (message "Parsed %d entries" added) added))) (defun org-bibtex-read-file (file) "Read FILE with `org-bibtex-read-buffer'." - (interactive "ffile: ") + (interactive "fFile: ") (org-bibtex-read-buffer (find-file-noselect file 'nowarn 'rawfile))) (defun org-bibtex-write () @@ -661,30 +689,28 @@ Return the number of saved entries." (val (lambda (field) (cdr (assoc field entry)))) (togtag (lambda (tag) (org-toggle-tag tag 'on)))) (org-insert-heading) - (insert (funcall val :title)) + (insert (funcall org-bibtex-headline-format-function entry)) (org-bibtex-put "TITLE" (funcall val :title)) (org-bibtex-put org-bibtex-type-property-name (downcase (funcall val :type))) (dolist (pair entry) - (case (car pair) + (pcase (car pair) (:title nil) (:type nil) (:key (org-bibtex-put org-bibtex-key-property (cdr pair))) (:keywords (if org-bibtex-tags-are-keywords - (mapc - (lambda (kw) - (funcall - togtag - (replace-regexp-in-string - "[^[:alnum:]_@#%]" "" - (replace-regexp-in-string "[ \t]+" "_" kw)))) - (split-string (cdr pair) ", *")) + (dolist (kw (split-string (cdr pair) ", *")) + (funcall + togtag + (replace-regexp-in-string + "[^[:alnum:]_@#%]" "" + (replace-regexp-in-string "[ \t]+" "_" kw)))) (org-bibtex-put (car pair) (cdr pair)))) - (otherwise (org-bibtex-put (car pair) (cdr pair))))) + (_ (org-bibtex-put (car pair) (cdr pair))))) (mapc togtag org-bibtex-tags))) (defun org-bibtex-yank () - "If kill ring holds a bibtex entry yank it as an Org-mode headline." + "If kill ring holds a bibtex entry yank it as an Org headline." (interactive) (let (entry) (with-temp-buffer (yank 1) (setf entry (org-bibtex-read))) @@ -693,8 +719,8 @@ Return the number of saved entries." (error "Yanked text does not appear to contain a BibTeX entry")))) (defun org-bibtex-import-from-file (file) - "Read bibtex entries from FILE and insert as Org-mode headlines after point." - (interactive "ffile: ") + "Read bibtex entries from FILE and insert as Org headlines after point." + (interactive "fFile: ") (dotimes (_ (org-bibtex-read-file file)) (save-excursion (org-bibtex-write)) (re-search-forward org-property-end-re) diff --git a/lisp/org/org-capture.el b/lisp/org/org-capture.el index b302113f3e8..03210210864 100644 --- a/lisp/org/org-capture.el +++ b/lisp/org/org-capture.el @@ -1,4 +1,4 @@ -;;; org-capture.el --- Fast note taking in Org-mode +;;; org-capture.el --- Fast note taking in Org -*- lexical-binding: t; -*- ;; Copyright (C) 2010-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Commentary: @@ -47,23 +47,23 @@ ;;; Code: -(eval-when-compile - (require 'cl)) +(require 'cl-lib) (require 'org) +(declare-function org-at-encrypted-entry-p "org-crypt" ()) (declare-function org-datetree-find-date-create "org-datetree" (date &optional keep-restriction)) -(declare-function org-table-get-specials "org-table" ()) -(declare-function org-table-goto-line "org-table" (N)) -(declare-function org-pop-to-buffer-same-window "org-compat" - (&optional buffer-or-name norecord label)) -(declare-function org-at-encrypted-entry-p "org-crypt" ()) -(declare-function org-encrypt-entry "org-crypt" ()) (declare-function org-decrypt-entry "org-crypt" ()) +(declare-function org-encrypt-entry "org-crypt" ()) +(declare-function org-table-analyze "org-table" ()) +(declare-function org-table-current-dline "org-table" ()) +(declare-function org-table-goto-line "org-table" (N)) +(defvar org-end-time-was-given) (defvar org-remember-default-headline) (defvar org-remember-templates) (defvar org-table-hlines) +(defvar org-table-current-begin-pos) (defvar dired-buffers) (defvar org-capture-clock-was-started nil @@ -76,11 +76,50 @@ ;; to indicate that the link properties have already been stored (defvar org-capture-link-is-already-stored nil) +(defvar org-capture-is-refiling nil + "Non-nil when capture process is refiling an entry.") + +(defvar org-capture--prompt-history-table (make-hash-table :test #'equal) + "Hash table for all history lists per prompt.") + +(defvar org-capture--prompt-history nil + "History list for prompt placeholders.") + (defgroup org-capture nil "Options concerning capturing new entries." :tag "Org Capture" :group 'org) +(defun org-capture-upgrade-templates (templates) + "Update the template list to the new format. +TEMPLATES is a template list, as in `org-capture-templates'. The +new format unifies all the date/week tree targets into one that +also allows for an optional outline path to specify a target." + (let ((modified-templates + (mapcar + (lambda (entry) + (pcase entry + ;; Match templates with an obsolete "tree" target type. Replace + ;; it with common `file+olp-datetree'. Add new properties + ;; (i.e., `:time-prompt' and `:tree-type') if needed. + (`(,key ,desc ,type (file+datetree . ,path) ,tpl . ,props) + `(,key ,desc ,type (file+olp+datetree ,@path) ,tpl ,@props)) + (`(,key ,desc ,type (file+datetree+prompt . ,path) ,tpl . ,props) + `(,key ,desc ,type (file+olp+datetree ,@path) ,tpl + :time-prompt t ,@props)) + (`(,key ,desc ,type (file+weektree . ,path) ,tpl . ,props) + `(,key ,desc ,type (file+olp+datetree ,@path) ,tpl + :tree-type week ,@props)) + (`(,key ,desc ,type (file+weektree+prompt . ,path) ,tpl . ,props) + `(,key ,desc ,type (file+olp+datetree ,@path) ,tpl + :tree-type week :time-prompt t ,@props)) + ;; Other templates are left unchanged. + (_ entry))) + templates))) + (unless (equal modified-templates templates) + (message "Deprecated date/weektree capture templates changed to `file+olp+datetree'.")) + modified-templates)) + (defcustom org-capture-templates nil "Templates for the creation of new entries. @@ -103,9 +142,9 @@ description A short string describing the template, will be shown during selection. type The type of entry. Valid types are: - entry an Org-mode node, with a headline. Will be - filed as the child of the target entry or as - a top-level entry. + entry an Org node, with a headline. Will be filed + as the child of the target entry or as a + top-level entry. item a plain list item, will be placed in the first plain list at the target location. @@ -116,37 +155,39 @@ type The type of entry. Valid types are: plain text to be inserted as it is. target Specification of where the captured item should be placed. - In Org-mode files, targets usually define a node. Entries will + In Org files, targets usually define a node. Entries will become children of this node, other types will be added to the table or list in the body of this node. Most target specifications contain a file name. If that file name is the empty string, it defaults to `org-default-notes-file'. - A file can also be given as a variable, function, or Emacs Lisp - form. + A file can also be given as a variable or as a function called + with no argument. When an absolute path is not specified for a + target, it is taken as relative to `org-directory'. Valid values are: (file \"path/to/file\") Text will be placed at the beginning or end of that file - (id \"id of existing org entry\") + (id \"id of existing Org entry\") File as child of this entry, or in the body of the entry (file+headline \"path/to/file\" \"node headline\") Fast configuration if the target heading is unique in the file (file+olp \"path/to/file\" \"Level 1 heading\" \"Level 2\" ...) - For non-unique headings, the full path is safer + For non-unique headings, the full outline path is safer (file+regexp \"path/to/file\" \"regexp to find location\") File to the entry matching regexp - (file+datetree \"path/to/file\") - Will create a heading in a date tree for today's date - - (file+datetree+prompt \"path/to/file\") - Will create a heading in a date tree, prompts for date + (file+olp+datetree \"path/to/file\" \"Level 1 heading\" ...) + Will create a heading in a date tree for today's date. + If no heading is given, the tree will be on top level. + To prompt for date instead of using TODAY, use the + :time-prompt property. To create a week-tree, use the + :tree-type property. (file+function \"path/to/file\" function-finding-location) A function to find the right location in the file @@ -155,8 +196,8 @@ target Specification of where the captured item should be placed. File to the entry that is currently being clocked (function function-finding-location) - Most general way, write your own function to find both - file and location + 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 @@ -204,6 +245,11 @@ properties are: When setting both to t, the current clock will run and the previous one will not be resumed. + :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. + :unnarrowed Do not narrow the target buffer, simply show the full buffer. Default is to narrow it so that you only see the new stuff. @@ -218,18 +264,25 @@ properties are: is finalized. The template defines the text to be inserted. Often this is an -org-mode entry (so the first line should start with a star) that +Org mode entry (so the first line should start with a star) that will be filed as a child of the target headline. It can also be freely formatted text. Furthermore, the following %-escapes will -be replaced with content and expanded in this order: +be replaced with content and expanded: - %[pathname] Insert the contents of the file given by `pathname'. + %[pathname] Insert the contents of the file given by + `pathname'. These placeholders are expanded at the very + beginning of the process so they can be used to extend the + current template. %(sexp) Evaluate elisp `(sexp)' and replace it with the results. - For convenience, %:keyword (see below) placeholders within - the expression will be expanded prior to this. + Only placeholders pre-existing within the template, or + introduced with %[pathname] are expanded this way. Since this + happens after expanding non-interactive %-escapes, those can + be used to fill the expression. %<...> The result of format-time-string on the ... format specification. - %t Time stamp, date only. - %T Time stamp with date and time. + %t Time stamp, date only. The time stamp is the current time, + except when called from agendas with `\\[org-agenda-capture]' or + with `org-capture-use-agenda-date' set. + %T Time stamp as above, with date and time. %u, %U Like the above, but inactive time stamps. %i Initial content, copied from the active region. If %i is indented, the entire inserted text will be indented as well. @@ -247,7 +300,8 @@ be replaced with content and expanded in this order: %^g Prompt for tags, with completion on tags in target file. %^G Prompt for tags, with completion on all tags in all agenda files. %^t Like %t, but prompt for date. Similarly %^T, %^u, %^U. - You may define a prompt like: %^{Please specify birthday}t + You may define a prompt like: %^{Please specify birthday}t. + The default date is that of %t, see above. %^C Interactive selection of which kill or clip to use. %^L Like %^C, but insert as link. %^{prop}p Prompt the user for a value for property `prop'. @@ -255,8 +309,8 @@ be replaced with content and expanded in this order: A default value and a completion table ca be specified like this: %^{prompt|default|completion2|completion3|...}. %? After completing the template, position cursor here. - %\\n Insert the text entered at the nth %^{prompt}, where `n' is - a number, starting from 1. + %\\1 ... %\\N Insert the text entered at the nth %^{prompt}, where N + is a number, starting from 1. Apart from these general escapes, you can access information specific to the link type that is created. For example, calling `org-capture' in emails @@ -274,13 +328,22 @@ gnus | %:from %:fromname %:fromaddress | %:date %:date-timestamp (as active timestamp) | %:date-timestamp-inactive (as inactive timestamp) gnus | %:group, for messages also all email fields -w3, w3m | %:type %:url +eww, w3, w3m | %:type %:url info | %:type %:file %:node -calendar | %:type %:date" +calendar | %:type %:date + +When you need to insert a literal percent sign in the template, +you can escape ambiguous cases with a backward slash, e.g., \\%i." :group 'org-capture :version "24.1" + :set (lambda (s v) (set s (org-capture-upgrade-templates v))) :type - '(repeat + (let ((file-variants '(choice :tag "Filename " + (file :tag "Literal") + (function :tag "Function") + (variable :tag "Variable") + (sexp :tag "Form")))) + `(repeat (choice :value ("" "" entry (file "~/org/notes.org") "") (list :tag "Multikey description" (string :tag "Keys ") @@ -297,39 +360,38 @@ calendar | %:type %:date" (choice :tag "Target location" (list :tag "File" (const :format "" file) - (file :tag " File")) + ,file-variants) (list :tag "ID" (const :format "" id) (string :tag " ID")) (list :tag "File & Headline" (const :format "" file+headline) - (file :tag " File ") + ,file-variants (string :tag " Headline")) (list :tag "File & Outline path" (const :format "" file+olp) - (file :tag " File ") + ,file-variants (repeat :tag "Outline path" :inline t (string :tag "Headline"))) (list :tag "File & Regexp" (const :format "" file+regexp) - (file :tag " File ") + ,file-variants (regexp :tag " Regexp")) - (list :tag "File & Date tree" - (const :format "" file+datetree) - (file :tag " File")) - (list :tag "File & Date tree, prompt for date" - (const :format "" file+datetree+prompt) - (file :tag " File")) + (list :tag "File [ & Outline path ] & Date tree" + (const :format "" file+olp+datetree) + ,file-variants + (option (repeat :tag "Outline path" :inline t + (string :tag "Headline")))) (list :tag "File & function" (const :format "" file+function) - (file :tag " File ") + ,file-variants (sexp :tag " Function")) (list :tag "Current clocking task" (const :format "" clock)) (list :tag "Function" (const :format "" function) (sexp :tag " Function"))) - (choice :tag "Template" + (choice :tag "Template " (string) (list :tag "File" (const :format "" file) @@ -348,9 +410,11 @@ calendar | %:type %:date" ((const :format "%v " :clock-in) (const t)) ((const :format "%v " :clock-keep) (const t)) ((const :format "%v " :clock-resume) (const t)) + ((const :format "%v " :time-prompt) (const t)) + ((const :format "%v " :tree-type) (const week)) ((const :format "%v " :unnarrowed) (const t)) - ((const :format "%v " :table-line-pos) (const t)) - ((const :format "%v " :kill-buffer) (const t)))))))) + ((const :format "%v " :table-line-pos) (string)) + ((const :format "%v " :kill-buffer) (const t))))))))) (defcustom org-capture-before-finalize-hook nil "Hook that is run right before a capture process is finalized. @@ -421,7 +485,7 @@ to avoid conflicts with other active capture processes." (defvar org-capture-mode-map (make-sparse-keymap) "Keymap for `org-capture-mode', a minor mode. -Use this map to set additional keybindings for when Org-mode is used +Use this map to set additional keybindings for when Org mode is used for a capture buffer.") (defvar org-capture-mode-hook nil @@ -432,10 +496,12 @@ for a capture buffer.") Turning on this mode runs the normal hook `org-capture-mode-hook'." nil " Rem" org-capture-mode-map - (org-set-local - 'header-line-format + (setq-local + header-line-format (substitute-command-keys - "Capture buffer. Finish `C-c C-c', refile `C-c C-w', abort `C-c C-k'."))) + "\\<org-capture-mode-map>Capture buffer. Finish \ +`\\[org-capture-finalize]', refile `\\[org-capture-refile]', \ +abort `\\[org-capture-kill]'."))) (define-key org-capture-mode-map "\C-c\C-c" 'org-capture-finalize) (define-key org-capture-mode-map "\C-c\C-k" 'org-capture-kill) (define-key org-capture-mode-map "\C-c\C-w" 'org-capture-refile) @@ -460,7 +526,7 @@ For example, if you have a capture template \"c\" and you want this template to be accessible only from `message-mode' buffers, use this: - ((\"c\" ((in-mode . \"message-mode\")))) + \\='((\"c\" ((in-mode . \"message-mode\")))) Here are the available contexts definitions: @@ -478,7 +544,7 @@ accessible if there is at least one valid check. You can also bind a key to another agenda custom command depending on contextual rules. - ((\"c\" \"d\" ((in-mode . \"message-mode\")))) + \\='((\"c\" \"d\" ((in-mode . \"message-mode\")))) Here it means: in `message-mode buffers', use \"c\" as the key for the capture template otherwise associated with \"d\". @@ -504,7 +570,8 @@ to avoid duplicates.)" (defcustom org-capture-use-agenda-date nil "Non-nil means use the date at point when capturing from agendas. -When nil, you can still capture using the date at point with \\[org-agenda-capture]." +When nil, you can still capture using the date at point with +`\\[org-agenda-capture]'." :group 'org-capture :version "24.3" :type 'boolean) @@ -513,20 +580,26 @@ When nil, you can still capture using the date at point with \\[org-agenda-captu (defun org-capture (&optional goto keys) "Capture something. \\<org-capture-mode-map> -This will let you select a template from `org-capture-templates', and then -file the newly captured information. The text is immediately inserted -at the target location, and an indirect buffer is shown where you can -edit it. Pressing \\[org-capture-finalize] brings you back to the previous state -of Emacs, so that you can continue your work. - -When called interactively with a \\[universal-argument] prefix argument GOTO, don't capture -anything, just go to the file/headline where the selected template -stores its notes. With a double prefix argument \ -\\[universal-argument] \\[universal-argument], go to the last note -stored. +This will let you select a template from `org-capture-templates', and +then file the newly captured information. The text is immediately +inserted at the target location, and an indirect buffer is shown where +you can edit it. Pressing `\\[org-capture-finalize]' brings you back to the \ +previous +state of Emacs, so that you can continue your work. + +When called interactively with a `\\[universal-argument]' prefix argument \ +GOTO, don't +capture anything, just go to the file/headline where the selected +template stores its notes. + +With a `\\[universal-argument] \\[universal-argument]' prefix argument, go to \ +the last note stored. When called with a `C-0' (zero) prefix, insert a template at point. +When called with a `C-1' (one) prefix, force prompting for a date when +a datetree entry is made. + ELisp programs can set KEYS to a string associated with a template in `org-capture-templates'. In this case, interactive selection will be bypassed. @@ -544,7 +617,6 @@ of the day at point (if any) or the current HH:MM time." ((equal goto '(4)) (org-capture-goto-target)) ((equal goto '(16)) (org-capture-goto-last-stored)) (t - ;; FIXME: Are these needed? (let* ((orig-buf (current-buffer)) (annotation (if (and (boundp 'org-capture-link-is-already-stored) org-capture-link-is-already-stored) @@ -564,7 +636,7 @@ of the day at point (if any) or the current HH:MM time." ((equal entry "C") (customize-variable 'org-capture-templates)) ((equal entry "q") - (error "Abort")) + (user-error "Abort")) (t (org-capture-set-plist entry) (org-capture-get-template) @@ -596,10 +668,10 @@ of the day at point (if any) or the current HH:MM time." (org-capture-insert-template-here) (condition-case error (org-capture-place-template - (equal (car (org-capture-get :target)) 'function)) + (eq (car (org-capture-get :target)) 'function)) ((error quit) (if (and (buffer-base-buffer (current-buffer)) - (string-match "\\`CAPTURE-" (buffer-name))) + (string-prefix-p "CAPTURE-" (buffer-name))) (kill-buffer (current-buffer))) (set-window-configuration (org-capture-get :return-to-wconf)) (error "Capture template `%s': %s" @@ -613,7 +685,7 @@ of the day at point (if any) or the current HH:MM time." (org-capture-put :interrupted-clock (copy-marker org-clock-marker))) (org-clock-in) - (org-set-local 'org-capture-clock-was-started t)) + (setq-local org-capture-clock-was-started t)) (error "Could not start the clock in this capture buffer"))) (if (org-capture-get :immediate-finish) @@ -646,7 +718,7 @@ captured item after finalizing." (setq stay-with-capture t)) (unless (and org-capture-mode (buffer-base-buffer (current-buffer))) - (error "This does not seem to be a capture buffer for Org-mode")) + (error "This does not seem to be a capture buffer for Org mode")) (run-hooks 'org-capture-prepare-finalize-hook) @@ -682,23 +754,13 @@ captured item after finalizing." (m2 (org-capture-get :end-marker 'local))) (if (and m1 m2 (= m1 beg) (= m2 end)) (progn - (setq m2 (if (cdr (assoc 'heading org-blank-before-new-entry)) + (setq m2 (if (cdr (assq 'heading org-blank-before-new-entry)) m2 (1+ m2)) m2 (if (< (point-max) m2) (point-max) m2)) (setq abort-note 'clean) (kill-region m1 m2)) (setq abort-note 'dirty))) - ;; Make sure that the empty lines after are correct - (when (and (> (point-max) end) ; indeed, the buffer was still narrowed - (member (org-capture-get :type 'local) - '(entry item checkitem plain))) - (save-excursion - (goto-char end) - (or (bolp) (newline)) - (org-capture-empty-lines-after - (or (org-capture-get :empty-lines-after 'local) - (org-capture-get :empty-lines 'local) 0)))) ;; Postprocessing: Update Statistics cookies, do the sorting (when (derived-mode-p 'org-mode) (save-excursion @@ -715,8 +777,7 @@ captured item after finalizing." ;; Store this place as the last one where we stored something ;; Do the marking in the base buffer, so that it makes sense after ;; the indirect buffer has been killed. - (when org-capture-bookmark - (org-capture-bookmark-last-stored-position)) + (org-capture-store-last-position) ;; Run the hook (run-hooks 'org-capture-before-finalize-hook)) @@ -770,11 +831,12 @@ captured item after finalizing." ;; Special cases (cond (abort-note - (cond - ((equal abort-note 'clean) - (message "Capture process aborted and target buffer cleaned up")) - ((equal abort-note 'dirty) - (error "Capture process aborted, but target buffer could not be cleaned up correctly")))) + (cl-case abort-note + (clean + (message "Capture process aborted and target buffer cleaned up")) + (dirty + (error "Capture process aborted, but target buffer could not be \ +cleaned up correctly")))) (stay-with-capture (org-capture-goto-last-stored))) ;; Return if we did store something @@ -786,19 +848,33 @@ Refiling is done from the base buffer, because the indirect buffer is then already gone. Any prefix argument will be passed to the refile command." (interactive) (unless (eq (org-capture-get :type 'local) 'entry) - (error - "Refiling from a capture buffer makes only sense for `entry'-type templates")) - (let ((pos (point)) - (base (buffer-base-buffer (current-buffer))) - (org-refile-for-capture t)) - (save-window-excursion - (with-current-buffer (or base (current-buffer)) - (save-excursion - (save-restriction - (widen) - (goto-char pos) - (call-interactively 'org-refile))))) - (org-capture-finalize))) + (user-error "Refiling from a capture buffer makes only sense \ +for `entry'-type templates")) + (let* ((base (or (buffer-base-buffer) (current-buffer))) + (pos (make-marker)) + (org-capture-is-refiling t) + (kill-buffer (org-capture-get :kill-buffer 'local)) + (jump-to-captured (org-capture-get :jump-to-captured 'local))) + ;; Since `org-capture-finalize' may alter buffer contents (e.g., + ;; empty lines) around entry, use a marker to refer to the + ;; headline to be refiled. Place the marker in the base buffer, + ;; as the current indirect one is going to be killed. + (set-marker pos (save-excursion (org-back-to-heading t) (point)) base) + ;; `org-capture-finalize' calls `org-capture-goto-last-stored' too + ;; early. We want to wait for the refiling to be over, so we + ;; control when the latter function is called. + (org-capture-put :kill-buffer nil :jump-to-captured nil) + (unwind-protect + (progn + (org-capture-finalize) + (save-window-excursion + (with-current-buffer base + (org-with-wide-buffer + (goto-char pos) + (call-interactively 'org-refile)))) + (when kill-buffer (kill-buffer base)) + (when jump-to-captured (org-capture-goto-last-stored))) + (set-marker pos nil)))) (defun org-capture-kill () "Abort the current capture process." @@ -813,7 +889,8 @@ already gone. Any prefix argument will be passed to the refile command." "Go to the location where the last capture note was stored." (interactive) (org-goto-marker-or-bmk org-capture-last-stored-marker - "org-capture-last-stored") + (plist-get org-bookmark-names-plist + :last-capture)) (message "This is the last note stored by a capture process")) ;;; Supporting functions for handling the process @@ -823,7 +900,7 @@ already gone. Any prefix argument will be passed to the refile command." (org-capture-put :initial-target-region ;; Check if the buffer is currently narrowed - (when (/= (buffer-size) (- (point-max) (point-min))) + (when (org-buffer-narrowed-p) (cons (point-min) (point-max)))) ;; store the current point (org-capture-put :initial-target-position (point))) @@ -832,163 +909,174 @@ already gone. Any prefix argument will be passed to the refile command." (defun org-capture-set-target-location (&optional target) "Find TARGET buffer and position. Store them in the capture property list." - (let ((target-entry-p t) decrypted-hl-pos) - (setq target (or target (org-capture-get :target))) + (let ((target-entry-p t)) (save-excursion - (cond - ((eq (car target) 'file) - (set-buffer (org-capture-target-buffer (nth 1 target))) - (org-capture-put-target-region-and-position) - (widen) - (setq target-entry-p nil)) - - ((eq (car target) 'id) - (let ((loc (org-id-find (nth 1 target)))) - (if (not loc) - (error "Cannot find target ID \"%s\"" (nth 1 target)) - (set-buffer (org-capture-target-buffer (car loc))) + (pcase (or target (org-capture-get :target)) + (`(file ,path) + (set-buffer (org-capture-target-buffer path)) + (org-capture-put-target-region-and-position) + (widen) + (setq target-entry-p nil)) + (`(id ,id) + (pcase (org-id-find id) + (`(,path . ,position) + (set-buffer (org-capture-target-buffer path)) (widen) (org-capture-put-target-region-and-position) - (goto-char (cdr loc))))) - - ((eq (car target) 'file+headline) - (set-buffer (org-capture-target-buffer (nth 1 target))) - (org-capture-put-target-region-and-position) - (widen) - (let ((hd (nth 2 target))) - (goto-char (point-min)) - (unless (derived-mode-p 'org-mode) - (error - "Target buffer \"%s\" for file+headline should be in Org mode" - (current-buffer))) - (if (re-search-forward - (format org-complex-heading-regexp-format (regexp-quote hd)) - nil t) - (goto-char (point-at-bol)) - (goto-char (point-max)) - (or (bolp) (insert "\n")) - (insert "* " hd "\n") - (beginning-of-line 0)))) - - ((eq (car target) 'file+olp) - (let ((m (org-find-olp - (cons (org-capture-expand-file (nth 1 target)) - (cddr target))))) - (set-buffer (marker-buffer m)) - (org-capture-put-target-region-and-position) - (widen) - (goto-char m))) - - ((eq (car target) 'file+regexp) - (set-buffer (org-capture-target-buffer (nth 1 target))) - (org-capture-put-target-region-and-position) - (widen) - (goto-char (point-min)) - (if (re-search-forward (nth 2 target) nil t) - (progn - (goto-char (if (org-capture-get :prepend) - (match-beginning 0) (match-end 0))) - (org-capture-put :exact-position (point)) - (setq target-entry-p (and (derived-mode-p 'org-mode) (org-at-heading-p)))) - (error "No match for target regexp in file %s" (nth 1 target)))) - - ((memq (car target) '(file+datetree file+datetree+prompt)) - (require 'org-datetree) - (set-buffer (org-capture-target-buffer (nth 1 target))) - (org-capture-put-target-region-and-position) - (widen) - ;; Make a date tree entry, with the current date (or yesterday, - ;; if we are extending dates for a couple of hours) - (org-datetree-find-date-create - (calendar-gregorian-from-absolute - (cond - (org-overriding-default-time - ;; use the overriding default time - (time-to-days org-overriding-default-time)) - - ((eq (car target) 'file+datetree+prompt) - ;; prompt for date - (let ((prompt-time (org-read-date - nil t nil "Date for tree entry:" - (current-time)))) - (org-capture-put - :default-time - (cond ((and (or (not (boundp 'org-time-was-given)) - (not org-time-was-given)) - (not (= (time-to-days prompt-time) (org-today)))) - ;; Use 00:00 when no time is given for another date than today? - (apply 'encode-time (append '(0 0 0) (cdddr (decode-time prompt-time))))) - ((string-match "\\([^ ]+\\)--?[^ ]+[ ]+\\(.*\\)" org-read-date-final-answer) - ;; Replace any time range by its start - (apply 'encode-time - (org-read-date-analyze - (replace-match "\\1 \\2" nil nil org-read-date-final-answer) - prompt-time (decode-time prompt-time)))) - (t prompt-time))) - (time-to-days prompt-time))) - (t - ;; current date, possibly corrected for late night workers - (org-today)))))) - - ((eq (car target) 'file+function) - (set-buffer (org-capture-target-buffer (nth 1 target))) - (org-capture-put-target-region-and-position) - (widen) - (funcall (nth 2 target)) - (org-capture-put :exact-position (point)) - (setq target-entry-p (and (derived-mode-p 'org-mode) (org-at-heading-p)))) - - ((eq (car target) 'function) - (funcall (nth 1 target)) - (org-capture-put :exact-position (point)) - (setq target-entry-p (and (derived-mode-p 'org-mode) (org-at-heading-p)))) - - ((eq (car target) 'clock) - (if (and (markerp org-clock-hd-marker) - (marker-buffer org-clock-hd-marker)) - (progn (set-buffer (marker-buffer org-clock-hd-marker)) - (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"))) - - (t (error "Invalid capture target specification"))) - - (when (and (featurep 'org-crypt) (org-at-encrypted-entry-p)) - (org-decrypt-entry) - (setq decrypted-hl-pos - (save-excursion (and (org-back-to-heading t) (point))))) - - (org-capture-put :buffer (current-buffer) :pos (point) + (goto-char position)) + (_ (error "Cannot find target ID \"%s\"" id)))) + (`(file+headline ,path ,headline) + (set-buffer (org-capture-target-buffer path)) + (unless (derived-mode-p 'org-mode) + (error "Target buffer \"%s\" for file+headline not in Org mode" + (current-buffer))) + (org-capture-put-target-region-and-position) + (widen) + (goto-char (point-min)) + (if (re-search-forward (format org-complex-heading-regexp-format + (regexp-quote headline)) + nil t) + (goto-char (line-beginning-position)) + (goto-char (point-max)) + (or (bolp) (insert "\n")) + (insert "* " headline "\n") + (beginning-of-line 0))) + (`(file+olp ,path . ,outline-path) + (let ((m (org-find-olp (cons (org-capture-expand-file path) + outline-path)))) + (set-buffer (marker-buffer m)) + (org-capture-put-target-region-and-position) + (widen) + (goto-char m) + (set-marker m nil))) + (`(file+regexp ,path ,regexp) + (set-buffer (org-capture-target-buffer path)) + (org-capture-put-target-region-and-position) + (widen) + (goto-char (point-min)) + (if (not (re-search-forward regexp nil t)) + (error "No match for target regexp in file %s" path) + (goto-char (if (org-capture-get :prepend) + (match-beginning 0) + (match-end 0))) + (org-capture-put :exact-position (point)) + (setq target-entry-p + (and (derived-mode-p 'org-mode) (org-at-heading-p))))) + (`(file+olp+datetree ,path . ,outline-path) + (let ((m (if outline-path + (org-find-olp (cons (org-capture-expand-file path) + outline-path)) + (set-buffer (org-capture-target-buffer path)) + (point-marker)))) + (set-buffer (marker-buffer m)) + (org-capture-put-target-region-and-position) + (widen) + (goto-char m) + (set-marker m nil) + (require 'org-datetree) + (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) + (funcall + (if (eq (org-capture-get :tree-type) 'week) + #'org-datetree-find-iso-week-create + #'org-datetree-find-date-create) + (calendar-gregorian-from-absolute + (cond + (org-overriding-default-time + ;; Use the overriding default time. + (time-to-days org-overriding-default-time)) + ((or (org-capture-get :time-prompt) + (equal current-prefix-arg 1)) + ;; Prompt for date. + (let ((prompt-time (org-read-date + nil t nil "Date for tree entry:" + (current-time)))) + (org-capture-put + :default-time + (cond ((and (or (not (boundp 'org-time-was-given)) + (not org-time-was-given)) + (not (= (time-to-days prompt-time) (org-today)))) + ;; Use 00:00 when no time is given for another + ;; date than today? + (apply #'encode-time + (append '(0 0 0) + (cl-cdddr (decode-time prompt-time))))) + ((string-match "\\([^ ]+\\)--?[^ ]+[ ]+\\(.*\\)" + org-read-date-final-answer) + ;; Replace any time range by its start. + (apply #'encode-time + (org-read-date-analyze + (replace-match "\\1 \\2" nil nil + org-read-date-final-answer) + prompt-time (decode-time prompt-time)))) + (t prompt-time))) + (time-to-days prompt-time))) + (t + ;; Current date, possibly corrected for late night + ;; workers. + (org-today)))) + ;; the following is the keep-restriction argument for + ;; org-datetree-find-date-create + (if outline-path 'subtree-at-point)))) + (`(file+function ,path ,function) + (set-buffer (org-capture-target-buffer path)) + (org-capture-put-target-region-and-position) + (widen) + (funcall function) + (org-capture-put :exact-position (point)) + (setq target-entry-p + (and (derived-mode-p 'org-mode) (org-at-heading-p)))) + (`(function ,fun) + (funcall fun) + (org-capture-put :exact-position (point)) + (setq target-entry-p + (and (derived-mode-p 'org-mode) (org-at-heading-p)))) + (`(clock) + (if (and (markerp org-clock-hd-marker) + (marker-buffer org-clock-hd-marker)) + (progn (set-buffer (marker-buffer org-clock-hd-marker)) + (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"))) + (target (error "Invalid capture target specification: %S" target))) + + (org-capture-put :buffer (current-buffer) + :pos (point) :target-entry-p target-entry-p - :decrypted decrypted-hl-pos)))) + :decrypted + (and (featurep 'org-crypt) + (org-at-encrypted-entry-p) + (save-excursion + (org-decrypt-entry) + (and (org-back-to-heading t) (point)))))))) (defun org-capture-expand-file (file) - "Expand functions and symbols for FILE. + "Expand functions, symbols and file names for FILE. When FILE is a function, call it. When it is a form, evaluate -it. When it is a variable, retrieve the value. Return whatever we get." - (cond - ((org-string-nw-p file) file) - ((functionp file) (funcall file)) - ((and (symbolp file) (boundp file)) (symbol-value file)) - ((and file (consp file)) (eval file)) - (t file))) +it. When it is a variable, return its value. When it is +a string, treat it as a file name, possibly expanding it +according to `org-directory', and return it. If it is the empty +string, however, return `org-default-notes-file'. In any other +case, raise an error." + (let ((location (cond ((equal file "") org-default-notes-file) + ((stringp file) (expand-file-name file org-directory)) + ((functionp file) (funcall file)) + ((and (symbolp file) (boundp file)) (symbol-value file)) + (t nil)))) + (or (org-string-nw-p location) + (error "Invalid file location: %S" location)))) (defun org-capture-target-buffer (file) - "Get a buffer for FILE." - (setq file (org-capture-expand-file file)) - (setq file (or (org-string-nw-p file) - org-default-notes-file - (error "No notes file specified, and no default available"))) - (or (org-find-base-buffer-visiting file) - (progn (org-capture-put :new-buffer t) - (find-file-noselect (expand-file-name file org-directory))))) - -(defun org-capture-steal-local-variables (buffer) - "Install Org-mode local variables of BUFFER." - (mapc (lambda (v) - (ignore-errors (org-set-local (car v) (cdr v)))) - (buffer-local-variables buffer))) + "Get a buffer for FILE. +FILE is a generalized file location, as handled by +`org-capture-expand-file'." + (let ((file (org-capture-expand-file file))) + (or (org-find-base-buffer-visiting file) + (progn (org-capture-put :new-buffer t) + (find-file-noselect file))))) (defun org-capture-place-template (&optional inhibit-wconf-store) "Insert the template at the target location, and display the buffer. @@ -1000,65 +1088,52 @@ may have been stored before." (org-switch-to-buffer-other-window (org-capture-get-indirect-buffer (org-capture-get :buffer) "CAPTURE")) (widen) - (show-all) + (outline-show-all) (goto-char (org-capture-get :pos)) - (org-set-local 'org-capture-target-marker - (point-marker)) - (org-set-local 'outline-level 'org-outline-level) - (let* ((template (org-capture-get :template)) - (type (org-capture-get :type))) - (case type - ((nil entry) (org-capture-place-entry)) - (table-line (org-capture-place-table-line)) - (plain (org-capture-place-plain-text)) - (item (org-capture-place-item)) - (checkitem (org-capture-place-item)))) + (setq-local outline-level 'org-outline-level) + (pcase (org-capture-get :type) + ((or `nil `entry) (org-capture-place-entry)) + (`table-line (org-capture-place-table-line)) + (`plain (org-capture-place-plain-text)) + (`item (org-capture-place-item)) + (`checkitem (org-capture-place-item))) (org-capture-mode 1) - (org-set-local 'org-capture-current-plist org-capture-plist)) + (setq-local org-capture-current-plist org-capture-plist)) (defun org-capture-place-entry () "Place the template as a new Org entry." - (let* ((txt (org-capture-get :template)) - (reversed (org-capture-get :prepend)) - (target-entry-p (org-capture-get :target-entry-p)) - level beg end file) - - (cond - ((org-capture-get :exact-position) + (let ((reversed? (org-capture-get :prepend)) + (level 1)) + (when (org-capture-get :exact-position) (goto-char (org-capture-get :exact-position))) - ((not target-entry-p) - ;; Insert as top-level entry, either at beginning or at end of file - (setq level 1) - (if reversed - (progn (goto-char (point-min)) - (or (org-at-heading-p) - (outline-next-heading))) - (goto-char (point-max)) - (or (bolp) (insert "\n")))) - (t - ;; Insert as a child of the current entry - (and (looking-at "\\*+") - (setq level (- (match-end 0) (match-beginning 0)))) - (setq level (org-get-valid-level (or level 1) 1)) - (if reversed - (progn - (outline-next-heading) - (or (bolp) (insert "\n"))) - (org-end-of-subtree t nil) - (or (bolp) (insert "\n"))))) + (cond + ;; Insert as a child of the current entry. + ((org-capture-get :target-entry-p) + (setq level (org-get-valid-level + (if (org-at-heading-p) (org-outline-level) 1) + 1)) + (if reversed? (outline-next-heading) (org-end-of-subtree t))) + ;; Insert as a top-level entry at the beginning of the file. + (reversed? + (goto-char (point-min)) + (unless (org-at-heading-p) (outline-next-heading))) + ;; Otherwise, insert as a top-level entry at the end of the file. + (t (goto-char (point-max)))) + (unless (bolp) (insert "\n")) (org-capture-empty-lines-before) - (setq beg (point)) - (org-capture-verify-tree txt) - (org-paste-subtree level txt 'for-yank) - (org-capture-empty-lines-after 1) - (org-capture-position-for-last-stored beg) - (outline-next-heading) - (setq end (point)) - (org-capture-mark-kill-region beg (1- end)) - (org-capture-narrow beg (1- end)) - (if (or (re-search-backward "%\\?" beg t) - (re-search-forward "%\\?" end t)) - (replace-match "")))) + (let ((beg (point)) + (template (org-capture-get :template))) + (org-capture-verify-tree template) + (org-paste-subtree level template 'for-yank) + (org-capture-empty-lines-after) + (org-capture-position-for-last-stored beg) + (unless (org-at-heading-p) (outline-next-heading)) + (let ((end (point))) + (org-capture-mark-kill-region beg end) + (org-capture-narrow beg end) + (when (or (re-search-backward "%\\?" beg t) + (re-search-forward "%\\?" end t)) + (replace-match "")))))) (defun org-capture-place-item () "Place the template as a new plain list item." @@ -1075,21 +1150,18 @@ may have been stored before." (t (setq beg (1+ (point-at-eol)) end (save-excursion (outline-next-heading) (point))))) + (setq ind nil) (if (org-capture-get :prepend) (progn (goto-char beg) - (if (org-list-search-forward (org-item-beginning-re) end t) - (progn - (goto-char (match-beginning 0)) - (setq ind (org-get-indentation))) - (goto-char end) - (setq ind 0))) + (when (org-list-search-forward (org-item-beginning-re) end t) + (goto-char (match-beginning 0)) + (setq ind (org-get-indentation)))) (goto-char end) - (if (org-list-search-backward (org-item-beginning-re) beg t) - (progn - (setq ind (org-get-indentation)) - (org-end-of-item)) - (setq ind 0)))) + (when (org-list-search-backward (org-item-beginning-re) beg t) + (setq ind (org-get-indentation)) + (org-end-of-item))) + (unless ind (goto-char end))) ;; Remove common indentation (setq txt (org-remove-indentation txt)) ;; Make sure this is indeed an item @@ -1097,23 +1169,28 @@ may have been stored before." (setq txt (concat "- " (mapconcat 'identity (split-string txt "\n") "\n ")))) + ;; Prepare surrounding empty lines. + (unless (bolp) (insert "\n")) + (org-capture-empty-lines-before) + (setq beg (point)) + (unless (eolp) (save-excursion (insert "\n"))) + (unless ind + (org-indent-line) + (setq ind (org-get-indentation)) + (delete-region beg (point))) ;; Set the correct indentation, depending on context (setq ind (make-string ind ?\ )) (setq txt (concat ind (mapconcat 'identity (split-string txt "\n") (concat "\n" ind)) "\n")) - ;; Insert, with surrounding empty lines - (org-capture-empty-lines-before) - (setq beg (point)) + ;; Insert item. (insert txt) - (or (bolp) (insert "\n")) - (org-capture-empty-lines-after 1) + (org-capture-empty-lines-after) (org-capture-position-for-last-stored beg) - (forward-char 1) (setq end (point)) - (org-capture-mark-kill-region beg (1- end)) - (org-capture-narrow beg (1- end)) + (org-capture-mark-kill-region beg end) + (org-capture-narrow beg end) (if (or (re-search-backward "%\\?" beg t) (re-search-forward "%\\?" end t)) (replace-match "")))) @@ -1124,7 +1201,7 @@ may have been stored before." (let* ((txt (org-capture-get :template)) (target-entry-p (org-capture-get :target-entry-p)) (table-line-pos (org-capture-get :table-line-pos)) - ind beg end) + beg end) (cond ((org-capture-get :exact-position) (goto-char (org-capture-get :exact-position))) @@ -1149,21 +1226,24 @@ may have been stored before." ;; Check if the template is good (if (not (string-match org-table-dataline-regexp txt)) (setq txt "| %?Bad template |\n")) + (if (functionp table-line-pos) + (setq table-line-pos (funcall table-line-pos)) + (setq table-line-pos (eval table-line-pos))) (cond ((and table-line-pos (string-match "\\(I+\\)\\([-+][0-9]\\)" table-line-pos)) - ;; we have a complex line specification (goto-char (point-min)) - (let ((nh (- (match-end 1) (match-beginning 1))) - (delta (string-to-number (match-string 2 table-line-pos))) - ll) + ;; we have a complex line specification + (let ((ll (ignore-errors + (save-match-data (org-table-analyze)) + (aref org-table-hlines + (- (match-end 1) (match-beginning 1))))) + (delta (string-to-number (match-string 2 table-line-pos)))) ;; The user wants a special position in the table - (org-table-get-specials) - (setq ll (ignore-errors (aref org-table-hlines nh))) - (unless ll (error "Invalid table line specification \"%s\"" - table-line-pos)) - (setq ll (+ ll delta (if (< delta 0) 0 -1))) - (org-goto-line ll) + (unless ll + (error "Invalid table line specification \"%s\"" table-line-pos)) + (goto-char org-table-current-begin-pos) + (forward-line (+ ll delta (if (< delta 0) 0 -1))) (org-table-insert-row 'below) (beginning-of-line 1) (delete-region (point) (1+ (point-at-eol))) @@ -1216,7 +1296,7 @@ Of course, if exact position has been required, just put it there." ;; we should place the text into this entry (if (org-capture-get :prepend) ;; Skip meta data and drawers - (org-end-of-meta-data-and-drawers) + (org-end-of-meta-data t) ;; go to ent of the entry text, before the next headline (outline-next-heading))) (t @@ -1226,7 +1306,7 @@ Of course, if exact position has been required, just put it there." (org-capture-empty-lines-before) (setq beg (point)) (insert txt) - (org-capture-empty-lines-after 1) + (org-capture-empty-lines-after) (org-capture-position-for-last-stored beg) (setq end (point)) (org-capture-mark-kill-region beg (1- end)) @@ -1237,8 +1317,8 @@ Of course, if exact position has been required, just put it there." (defun org-capture-mark-kill-region (beg end) "Mark the region that will have to be killed when aborting capture." - (let ((m1 (move-marker (make-marker) beg)) - (m2 (move-marker (make-marker) end))) + (let ((m1 (copy-marker beg)) + (m2 (copy-marker end t))) (org-capture-put :begin-marker m1) (org-capture-put :end-marker m2))) @@ -1256,8 +1336,8 @@ Of course, if exact position has been required, just put it there." (org-table-current-dline)))) (t (error "This should not happen")))) -(defun org-capture-bookmark-last-stored-position () - "Bookmark the last-captured position." +(defun org-capture-store-last-position () + "Store the last-captured position." (let* ((where (org-capture-get :position-for-last-stored 'local)) (pos (cond ((markerp where) @@ -1270,16 +1350,11 @@ Of course, if exact position has been required, just put it there." (point-at-bol)) (point)))))) (with-current-buffer (buffer-base-buffer (current-buffer)) - (save-excursion - (save-restriction - (widen) - (goto-char pos) - (let ((bookmark-name (plist-get org-bookmark-names-plist - :last-capture))) - (when bookmark-name - (with-demoted-errors - (bookmark-set bookmark-name)))) - (move-marker org-capture-last-stored-marker (point))))))) + (org-with-point-at pos + (when org-capture-bookmark + (let ((bookmark (plist-get org-bookmark-names-plist :last-capture))) + (when bookmark (with-demoted-errors (bookmark-set bookmark))))) + (move-marker org-capture-last-stored-marker (point)))))) (defun org-capture-narrow (beg end) "Narrow, unless configuration says not to narrow." @@ -1315,7 +1390,7 @@ Point will remain at the first line after the inserted text." (let* ((template (org-capture-get :template)) (type (org-capture-get :type)) beg end pp) - (or (bolp) (newline)) + (unless (bolp) (insert "\n")) (setq beg (point)) (cond ((and (eq type 'entry) (derived-mode-p 'org-mode)) @@ -1337,13 +1412,16 @@ Point will remain at the first line after the inserted text." (org-capture-empty-lines-after) (goto-char beg) (org-list-repair) - (org-end-of-item) - (setq end (point))) - (t (insert template))) + (org-end-of-item)) + (t + (insert template) + (org-capture-empty-lines-after) + (skip-chars-forward " \t\n") + (unless (eobp) (beginning-of-line)))) (setq end (point)) (goto-char beg) - (if (re-search-forward "%\\?" end t) - (replace-match "")))) + (when (re-search-forward "%\\?" end t) + (replace-match "")))) (defun org-capture-set-plist (entry) "Initialize the property list from the template definition." @@ -1365,13 +1443,11 @@ Point will remain at the first line after the inserted text." "Go to the target location of a capture template. The user is queried for the template." (interactive) - (let* (org-select-template-temp-major-mode - (entry (org-capture-select-template template-key))) - (unless entry - (error "No capture template selected")) + (let ((entry (org-capture-select-template template-key))) + (unless entry (error "No capture template selected")) (org-capture-set-plist entry) (org-capture-set-target-location) - (org-pop-to-buffer-same-window (org-capture-get :buffer)) + (pop-to-buffer-same-window (org-capture-get :buffer)) (goto-char (org-capture-get :pos)))) (defun org-capture-get-indirect-buffer (&optional buffer prefix) @@ -1381,7 +1457,7 @@ Use PREFIX as a prefix for the name of the indirect buffer." (let ((n 1) (base (buffer-name buffer)) bname) (setq bname (concat prefix "-" base)) (while (buffer-live-p (get-buffer bname)) - (setq bname (concat prefix "-" (number-to-string (incf n)) "-" base))) + (setq bname (concat prefix "-" (number-to-string (cl-incf n)) "-" base))) (condition-case nil (make-indirect-buffer buffer bname 'clone) (error @@ -1396,6 +1472,7 @@ Use PREFIX as a prefix for the name of the indirect buffer." (defun org-mks (table title &optional prompt specials) "Select a member of an alist with multiple keys. + TABLE is the alist which should contain entries where the car is a string. There should be two types of entries. @@ -1403,7 +1480,7 @@ There should be two types of entries. This indicates that `a' is a prefix key for multi-letter selection, and that there are entries following with keys like \"ab\", \"ax\"... -2. Selectable members must have more than two elements, with the first +2. Select-able members must have more than two elements, with the first being the string of keys that lead to selecting it, and the second a short description string of the item. @@ -1414,84 +1491,72 @@ When you press a prefix key, the commands (and maybe further prefixes) under this key will be shown and offered for selection. TITLE will be placed over the selection in the temporary buffer, -PROMPT will be used when prompting for a key. SPECIAL is an alist with -also (\"key\" \"description\") entries. When one of these is selection, -only the bare key is returned." - (setq prompt (or prompt "Select: ")) - (let (tbl orig-table dkey ddesc des-keys allowed-keys - current prefix rtn re pressed buffer (inhibit-quit t)) - (save-window-excursion - (setq buffer (org-switch-to-buffer-other-window "*Org Select*")) - (setq orig-table table) - (catch 'exit - (while t - (erase-buffer) - (insert title "\n\n") - (setq tbl table - des-keys nil - allowed-keys nil - cursor-type nil) - (setq prefix (if current (concat current " ") "")) - (while tbl - (cond - ((and (= 2 (length (car tbl))) (= (length (caar tbl)) 1)) - ;; This is a description on this level - (setq dkey (caar tbl) ddesc (cadar tbl)) - (pop tbl) - (push dkey des-keys) - (push dkey allowed-keys) - (insert prefix "[" dkey "]" "..." " " ddesc "..." "\n") - ;; Skip keys which are below this prefix - (setq re (concat "\\`" (regexp-quote dkey))) - (let (case-fold-search) - (while (and tbl (string-match re (caar tbl))) (pop tbl)))) - ((= 2 (length (car tbl))) - ;; Not yet a usable description, skip it - ) - (t - ;; usable entry on this level - (insert prefix "[" (caar tbl) "]" " " (nth 1 (car tbl)) "\n") - (push (caar tbl) allowed-keys) - (pop tbl)))) - (when specials - (insert "-------------------------------------------------------------------------------\n") - (let ((sp specials)) - (while sp - (insert (format "[%s] %s\n" - (caar sp) (nth 1 (car sp)))) - (push (caar sp) allowed-keys) - (pop sp)))) - (push "\C-g" allowed-keys) - (goto-char (point-min)) - (if (not (pos-visible-in-window-p (point-max))) - (org-fit-window-to-buffer)) - (message prompt) - (setq pressed (char-to-string (read-char-exclusive))) - (while (not (member pressed allowed-keys)) - (message "Invalid key `%s'" pressed) (sit-for 1) - (message prompt) - (setq pressed (char-to-string (read-char-exclusive)))) - (when (equal pressed "\C-g") - (kill-buffer buffer) - (error "Abort")) - (when (and (not (assoc pressed table)) - (not (member pressed des-keys)) - (assoc pressed specials)) - (throw 'exit (setq rtn pressed))) - (unless (member pressed des-keys) - (throw 'exit (setq rtn (rassoc (cdr (assoc pressed table)) - orig-table)))) - (setq current (concat current pressed)) - (setq table (mapcar - (lambda (x) - (if (and (> (length (car x)) 1) - (equal (substring (car x) 0 1) pressed)) - (cons (substring (car x) 1) (cdr x)) - nil)) - table)) - (setq table (remove nil table))))) - (when buffer (kill-buffer buffer)) - rtn)) +PROMPT will be used when prompting for a key. SPECIAL is an +alist with (\"key\" \"description\") entries. When one of these +is selected, only the bare key is returned." + (save-window-excursion + (let ((inhibit-quit t) + (buffer (org-switch-to-buffer-other-window "*Org Select*")) + (prompt (or prompt "Select: ")) + current) + (unwind-protect + (catch 'exit + (while t + (erase-buffer) + (insert title "\n\n") + (let ((des-keys nil) + (allowed-keys '("\C-g")) + (cursor-type nil)) + ;; Populate allowed keys and descriptions keys + ;; available with CURRENT selector. + (let ((re (format "\\`%s\\(.\\)\\'" + (if current (regexp-quote current) ""))) + (prefix (if current (concat current " ") ""))) + (dolist (entry table) + (pcase entry + ;; Description. + (`(,(and key (pred (string-match re))) ,desc) + (let ((k (match-string 1 key))) + (push k des-keys) + (push k allowed-keys) + (insert prefix "[" k "]" "..." " " desc "..." "\n"))) + ;; Usable entry. + (`(,(and key (pred (string-match re))) ,desc . ,_) + (let ((k (match-string 1 key))) + (insert prefix "[" k "]" " " desc "\n") + (push k allowed-keys))) + (_ nil)))) + ;; Insert special entries, if any. + (when specials + (insert "----------------------------------------------------\ +---------------------------\n") + (pcase-dolist (`(,key ,description) specials) + (insert (format "[%s] %s\n" key description)) + (push key allowed-keys))) + ;; Display UI and let user select an entry or + ;; a sub-level prefix. + (goto-char (point-min)) + (unless (pos-visible-in-window-p (point-max)) + (org-fit-window-to-buffer)) + (message prompt) + (let ((pressed (char-to-string (read-char-exclusive)))) + (while (not (member pressed allowed-keys)) + (message "Invalid key `%s'" pressed) (sit-for 1) + (message prompt) + (setq pressed (char-to-string (read-char-exclusive)))) + (setq current (concat current pressed)) + (cond + ((equal pressed "\C-g") (user-error "Abort")) + ;; Selection is a prefix: open a new menu. + ((member pressed des-keys)) + ;; Selection matches an association: return it. + ((let ((entry (assoc current table))) + (and entry (throw 'exit entry)))) + ;; Selection matches a special entry: return the + ;; selection prefix. + ((assoc current specials) (throw 'exit current)) + (t (error "No entry available"))))))) + (when buffer (kill-buffer buffer)))))) ;;; The template code (defun org-capture-select-template (&optional keys) @@ -1499,7 +1564,8 @@ only the bare key is returned." Lisp programs can force the template by setting KEYS to a string." (let ((org-capture-templates (or (org-contextualize-keys - org-capture-templates org-capture-templates-contexts) + (org-capture-upgrade-templates org-capture-templates) + org-capture-templates-contexts) '(("t" "Task" entry (file+headline "" "Tasks") "* TODO %?\n %u\n %a"))))) (if keys @@ -1511,46 +1577,41 @@ Lisp programs can force the template by setting KEYS to a string." '(("C" "Customize org-capture-templates") ("q" "Abort")))))) +(defvar org-capture--clipboards nil + "List various clipboards values.") + (defun org-capture-fill-template (&optional template initial annotation) "Fill a template and return the filled template as a string. The template may still contain \"%?\" for cursor positioning." - (setq template (or template (org-capture-get :template))) - (when (stringp initial) - (setq initial (org-no-properties initial))) - (let* ((buffer (org-capture-get :buffer)) + (let* ((template (or template (org-capture-get :template))) + (buffer (org-capture-get :buffer)) (file (buffer-file-name (or (buffer-base-buffer buffer) buffer))) - (ct (org-capture-get :default-time)) - (dct (decode-time ct)) - (ct1 - (if (< (nth 2 dct) org-extend-today-until) - (encode-time 0 59 23 (1- (nth 3 dct)) (nth 4 dct) (nth 5 dct)) - ct)) - (plist-p (if org-store-link-plist t nil)) - (v-c (and (> (length kill-ring) 0) (current-kill 0))) + (time (let* ((c (or (org-capture-get :default-time) (current-time))) + (d (decode-time c))) + (if (< (nth 2 d) org-extend-today-until) + (encode-time 0 59 23 (1- (nth 3 d)) (nth 4 d) (nth 5 d)) + c))) + (v-t (format-time-string (org-time-stamp-format nil) time)) + (v-T (format-time-string (org-time-stamp-format t) time)) + (v-u (format-time-string (org-time-stamp-format nil t) time)) + (v-U (format-time-string (org-time-stamp-format t t) time)) + (v-c (and kill-ring (current-kill 0))) (v-x (or (org-get-x-clipboard 'PRIMARY) (org-get-x-clipboard 'CLIPBOARD) (org-get-x-clipboard 'SECONDARY))) - (v-t (format-time-string (car org-time-stamp-formats) ct1)) - (v-T (format-time-string (cdr org-time-stamp-formats) ct1)) - (v-u (concat "[" (substring v-t 1 -1) "]")) - (v-U (concat "[" (substring v-T 1 -1) "]")) - ;; `initial' and `annotation' might habe been passed. - ;; But if the property list has them, we prefer those values + ;; `initial' and `annotation' might have been passed. But if + ;; the property list has them, we prefer those values. (v-i (or (plist-get org-store-link-plist :initial) - initial + (and (stringp initial) (org-no-properties initial)) (org-capture-get :initial) "")) - (v-a (or (plist-get org-store-link-plist :annotation) - annotation - (org-capture-get :annotation) - "")) - ;; Is the link empty? Then we do not want it... - (v-a (if (equal v-a "[[]]") "" v-a)) - (clipboards (remove nil (list v-i - (org-get-x-clipboard 'PRIMARY) - (org-get-x-clipboard 'CLIPBOARD) - (org-get-x-clipboard 'SECONDARY) - v-c))) + (v-a + (let ((a (or (plist-get org-store-link-plist :annotation) + annotation + (org-capture-get :annotation) + ""))) + ;; Is the link empty? Then we do not want it... + (if (equal a "[[]]") "" a))) (l-re "\\[\\[\\(.*?\\)\\]\\(\\[.*?\\]\\)?\\]") (v-A (if (and v-a (string-match l-re v-a)) (replace-match "[[\\1][%^{Link description}]]" nil nil v-a) @@ -1560,201 +1621,272 @@ The template may still contain \"%?\" for cursor positioning." v-a)) (v-n user-full-name) (v-k (if (marker-buffer org-clock-marker) - (org-no-properties org-clock-heading))) + (org-no-properties org-clock-heading) + "")) (v-K (if (marker-buffer org-clock-marker) (org-make-link-string - (buffer-file-name (marker-buffer org-clock-marker)) - org-clock-heading))) + (format "%s::*%s" + (buffer-file-name (marker-buffer org-clock-marker)) + v-k) + v-k) + "")) (v-f (or (org-capture-get :original-file-nondirectory) "")) (v-F (or (org-capture-get :original-file) "")) - v-I - (org-startup-folded nil) - (org-inhibit-startup t) - org-time-was-given org-end-time-was-given x - prompt completions char time pos default histvar strings) - - (setq org-store-link-plist - (plist-put org-store-link-plist :annotation v-a) - org-store-link-plist - (plist-put org-store-link-plist :initial v-i)) - (setq initial v-i) - - (unless template (setq template "") (message "No template") (ding) - (sit-for 1)) + (org-capture--clipboards + (delq nil + (list v-i + (org-get-x-clipboard 'PRIMARY) + (org-get-x-clipboard 'CLIPBOARD) + (org-get-x-clipboard 'SECONDARY) + v-c)))) + + (setq org-store-link-plist (plist-put org-store-link-plist :annotation v-a)) + (setq org-store-link-plist (plist-put org-store-link-plist :initial v-i)) + + (unless template + (setq template "") + (message "no template") (ding) + (sit-for 1)) (save-window-excursion - (delete-other-windows) - (org-pop-to-buffer-same-window (get-buffer-create "*Capture*")) + (org-switch-to-buffer-other-window (get-buffer-create "*Capture*")) (erase-buffer) + (setq buffer-file-name nil) + (setq mark-active nil) (insert template) (goto-char (point-min)) - (org-capture-steal-local-variables buffer) - (setq buffer-file-name nil mark-active nil) - ;; %[] Insert contents of a file. - (goto-char (point-min)) - (while (re-search-forward "%\\[\\(.+\\)\\]" nil t) - (unless (org-capture-escaped-%) - (let ((start (match-beginning 0)) - (end (match-end 0)) - (filename (expand-file-name (match-string 1)))) - (goto-char start) - (delete-region start end) - (condition-case error - (insert-file-contents filename) - (error (insert (format "%%![Could not insert %s: %s]" - filename error))))))) - ;; %() embedded elisp - (org-capture-expand-embedded-elisp) + ;; %[] insert contents of a file. + (save-excursion + (while (re-search-forward "%\\[\\(.+\\)\\]" nil t) + (let ((filename (expand-file-name (match-string 1))) + (beg (copy-marker (match-beginning 0))) + (end (copy-marker (match-end 0)))) + (unless (org-capture-escaped-%) + (delete-region beg end) + (set-marker beg nil) + (set-marker end nil) + (condition-case error + (insert-file-contents filename) + (error + (insert (format "%%![couldn not insert %s: %s]" + filename + error)))))))) - ;; The current time - (goto-char (point-min)) - (while (re-search-forward "%<\\([^>\n]+\\)>" nil t) - (replace-match (format-time-string (match-string 1)) t t)) + ;; Mark %() embedded elisp for later evaluation. + (org-capture-expand-embedded-elisp 'mark) - ;; Simple %-escapes - (goto-char (point-min)) - (while (re-search-forward "%\\([tTuUaliAcxkKInfF]\\)" nil t) - (unless (org-capture-escaped-%) - (when (and initial (equal (match-string 0) "%i")) - (save-match-data - (let* ((lead (buffer-substring - (point-at-bol) (match-beginning 0)))) - (setq v-i (mapconcat 'identity - (org-split-string initial "\n") - (concat "\n" lead)))))) - (replace-match (or (eval (intern (concat "v-" (match-string 1)))) "") - t t))) - - ;; From the property list - (when plist-p - (goto-char (point-min)) - (while (re-search-forward "%\\(:[-a-zA-Z]+\\)" nil t) - (unless (org-capture-escaped-%) - (and (setq x (or (plist-get org-store-link-plist - (intern (match-string 1))) "")) - (replace-match x t t))))) - - ;; Turn on org-mode in temp buffer, set local variables - ;; This is to support completion in interactive prompts + ;; Expand non-interactive templates. + (let ((regexp "%\\(:[-a-za-z]+\\|<\\([^>\n]+\\)>\\|[aAcfFikKlntTuUx]\\)")) + (save-excursion + (while (re-search-forward regexp nil t) + ;; `org-capture-escaped-%' may modify buffer and cripple + ;; match-data. Use markers instead. Ditto for other + ;; templates. + (let ((pos (copy-marker (match-beginning 0))) + (end (copy-marker (match-end 0))) + (value (match-string 1)) + (time-string (match-string 2))) + (unless (org-capture-escaped-%) + (delete-region pos end) + (set-marker pos nil) + (set-marker end nil) + (let* ((inside-sexp? (org-capture-inside-embedded-elisp-p)) + (replacement + (pcase (string-to-char value) + (?< (format-time-string time-string time)) + (?: + (or (plist-get org-store-link-plist (intern value)) + "")) + (?i + (if inside-sexp? v-i + ;; Outside embedded Lisp, repeat leading + ;; characters before initial place holder + ;; every line. + (let ((lead (buffer-substring-no-properties + (line-beginning-position) (point)))) + (replace-regexp-in-string "\n\\(.\\)" + (concat lead "\\1") + v-i nil nil 1)))) + (?a v-a) + (?A v-A) + (?c v-c) + (?f v-f) + (?F v-F) + (?k v-k) + (?K v-K) + (?l v-l) + (?n v-n) + (?t v-t) + (?T v-T) + (?u v-u) + (?U v-U) + (?x v-x)))) + (insert + (if inside-sexp? + ;; Escape sensitive characters. + (replace-regexp-in-string "[\\\"]" "\\\\\\&" replacement) + replacement)))))))) + + ;; Expand %() embedded Elisp. Limit to Sexp originally marked. + (org-capture-expand-embedded-elisp) + + ;; Expand interactive templates. This is the last step so that + ;; template is mostly expanded when prompting happens. Turn on + ;; Org mode and set local variables. This is to support + ;; completion in interactive prompts. (let ((org-inhibit-startup t)) (org-mode)) - ;; Interactive template entries - (goto-char (point-min)) - (while (re-search-forward "%^\\({\\([^}]*\\)}\\)?\\([gGtTuUCLp]\\)?" nil t) - (unless (org-capture-escaped-%) - (setq char (if (match-end 3) (match-string-no-properties 3)) - prompt (if (match-end 2) (match-string-no-properties 2))) - (goto-char (match-beginning 0)) - (replace-match "") - (setq completions nil default nil) - (when prompt - (setq completions (org-split-string prompt "|") - prompt (pop completions) - default (car completions) - histvar (intern (concat - "org-capture-template-prompt-history::" - (or prompt ""))) - completions (mapcar 'list completions))) - (unless (boundp histvar) (set histvar nil)) - (cond - ((member char '("G" "g")) - (let* ((org-last-tags-completion-table - (org-global-tags-completion-table - (if (equal char "G") - (org-agenda-files) - (and file (list file))))) - (org-add-colon-after-tag-completion t) - (ins (org-icompleting-read - (if prompt (concat prompt ": ") "Tags: ") - 'org-tags-completion-function nil nil nil - 'org-tags-history))) - (setq ins (mapconcat 'identity - (org-split-string - ins (org-re "[^[:alnum:]_@#%]+")) - ":")) - (when (string-match "\\S-" ins) - (or (equal (char-before) ?:) (insert ":")) - (insert ins) - (or (equal (char-after) ?:) (insert ":")) - (and (org-at-heading-p) - (let ((org-ignore-region t)) - (org-set-tags nil 'align)))))) - ((equal char "C") - (cond ((= (length clipboards) 1) (insert (car clipboards))) - ((> (length clipboards) 1) - (insert (read-string "Clipboard/kill value: " - (car clipboards) '(clipboards . 1) - (car clipboards)))))) - ((equal char "L") - (cond ((= (length clipboards) 1) - (org-insert-link 0 (car clipboards))) - ((> (length clipboards) 1) - (org-insert-link 0 (read-string "Clipboard/kill value: " - (car clipboards) - '(clipboards . 1) - (car clipboards)))))) - ((equal char "p") - (org-set-property (org-no-properties prompt) nil)) - (char - ;; These are the date/time related ones - (setq org-time-was-given (equal (upcase char) char)) - (setq time (org-read-date (equal (upcase char) char) t nil - prompt)) - (if (equal (upcase char) char) (setq org-time-was-given t)) - (org-insert-time-stamp time org-time-was-given - (member char '("u" "U")) - nil nil (list org-end-time-was-given))) - (t - (let (org-completion-use-ido) - (push (org-completing-read-no-i - (concat (if prompt prompt "Enter string") - (if default (concat " [" default "]")) - ": ") - completions nil nil nil histvar default) - strings) - (insert (car strings))))))) - ;; Replace %n escapes with nth %^{...} string - (setq strings (nreverse strings)) - (goto-char (point-min)) - (while (re-search-forward "%\\\\\\([1-9][0-9]*\\)" nil t) - (unless (org-capture-escaped-%) - (replace-match - (nth (1- (string-to-number (match-string 1))) strings) - nil t))) + (org-clone-local-variables buffer "\\`org-") + (let (strings) ; Stores interactive answers. + (save-excursion + (let ((regexp "%\\^\\(?:{\\([^}]*\\)}\\)?\\([CgGLptTuU]\\)?")) + (while (re-search-forward regexp nil t) + (let* ((items (and (match-end 1) + (save-match-data + (split-string (match-string-no-properties 1) + "|")))) + (key (match-string 2)) + (beg (copy-marker (match-beginning 0))) + (end (copy-marker (match-end 0))) + (prompt (nth 0 items)) + (default (nth 1 items)) + (completions (nthcdr 2 items))) + (unless (org-capture-escaped-%) + (delete-region beg end) + (set-marker beg nil) + (set-marker end nil) + (pcase key + ((or "G" "g") + (let* ((org-last-tags-completion-table + (org-global-tags-completion-table + (cond ((equal key "G") (org-agenda-files)) + (file (list file)) + (t nil)))) + (org-add-colon-after-tag-completion t) + (ins (mapconcat + #'identity + (org-split-string + (completing-read + (if prompt (concat prompt ": ") "Tags: ") + 'org-tags-completion-function nil nil nil + 'org-tags-history) + "[^[:alnum:]_@#%]+") + ":"))) + (when (org-string-nw-p ins) + (unless (eq (char-before) ?:) (insert ":")) + (insert ins) + (unless (eq (char-after) ?:) (insert ":")) + (and (org-at-heading-p) + (let ((org-ignore-region t)) + (org-set-tags nil 'align)))))) + ((or "C" "L") + (let ((insert-fun (if (equal key "C") #'insert + (lambda (s) (org-insert-link 0 s))))) + (pcase org-capture--clipboards + (`nil nil) + (`(,value) (funcall insert-fun value)) + (`(,first-value . ,_) + (funcall insert-fun + (read-string "Clipboard/kill value: " + first-value + 'org-capture--clipboards + first-value))) + (_ (error "Invalid `org-capture--clipboards' value: %S" + org-capture--clipboards))))) + ("p" (org-set-property prompt nil)) + ((or "t" "T" "u" "U") + ;; These are the date/time related ones. + (let* ((upcase? (equal (upcase key) key)) + (org-end-time-was-given nil) + (time (org-read-date upcase? t nil prompt))) + (org-insert-time-stamp + time (or org-time-was-given upcase?) + (member key '("u" "U")) + nil nil (list org-end-time-was-given)))) + (`nil + ;; Load history list for current prompt. + (setq org-capture--prompt-history + (gethash prompt org-capture--prompt-history-table)) + (push (org-completing-read + (concat (or prompt "Enter string") + (and default (format " [%s]" default)) + ": ") + completions + nil nil nil 'org-capture--prompt-history default) + strings) + (insert (car strings)) + ;; Save updated history list for current prompt. + (puthash prompt org-capture--prompt-history + org-capture--prompt-history-table)) + (_ + (error "Unknown template placeholder: \"%%^%s\"" + key)))))))) + + ;; Replace %n escapes with nth %^{...} string. + (setq strings (nreverse strings)) + (save-excursion + (while (re-search-forward "%\\\\\\([1-9][0-9]*\\)" nil t) + (unless (org-capture-escaped-%) + (replace-match + (nth (1- (string-to-number (match-string 1))) strings) + nil t))))) + ;; Make sure there are no empty lines before the text, and that - ;; it ends with a newline character - (goto-char (point-min)) - (while (looking-at "[ \t]*\n") (replace-match "")) - (if (re-search-forward "[ \t\n]*\\'" nil t) (replace-match "\n")) - ;; Return the expanded template and kill the temporary buffer + ;; it ends with a newline character. + (skip-chars-forward " \t\n") + (delete-region (point-min) (line-beginning-position)) + (goto-char (point-max)) + (skip-chars-backward " \t\n") + (delete-region (point) (point-max)) + (insert "\n") + + ;; Return the expanded template and kill the capture buffer. (untabify (point-min) (point-max)) (set-buffer-modified-p nil) - (prog1 (buffer-string) (kill-buffer (current-buffer)))))) + (prog1 (buffer-substring-no-properties (point-min) (point-max)) + (kill-buffer (current-buffer)))))) (defun org-capture-escaped-% () - "Check if % was escaped - if yes, unescape it now." - (if (equal (char-before (match-beginning 0)) ?\\) - (progn - (delete-region (1- (match-beginning 0)) (match-beginning 0)) - t) - nil)) - -(defun org-capture-expand-embedded-elisp () - "Evaluate embedded elisp %(sexp) and replace with the result." - (goto-char (point-min)) - (while (re-search-forward "%(" nil t) - (unless (org-capture-escaped-%) - (goto-char (match-beginning 0)) - (let ((template-start (point))) - (forward-char 1) - (let* ((sexp (read (current-buffer))) - (result (org-eval - (org-capture--expand-keyword-in-embedded-elisp sexp)))) - (delete-region template-start (point)) - (when result - (if (stringp result) - (insert result) - (error "Capture template sexp `%s' must evaluate to string or nil" - sexp)))))))) + "Non-nil if % was escaped. +If yes, unescape it now. Assume match-data contains the +placeholder to check." + (save-excursion + (goto-char (match-beginning 0)) + (let ((n (abs (skip-chars-backward "\\\\")))) + (delete-char (/ (1+ n) 2)) + (= (% n 2) 1)))) + +(defun org-capture-expand-embedded-elisp (&optional mark) + "Evaluate embedded elisp %(sexp) and replace with the result. +When optional MARK argument is non-nil, mark Sexp with a text +property (`org-embedded-elisp') for later evaluation. Only +marked Sexp are evaluated when this argument is nil." + (save-excursion + (goto-char (point-min)) + (while (re-search-forward "%(" nil t) + (cond + ((get-text-property (match-beginning 0) 'org-embedded-elisp) + (goto-char (match-beginning 0)) + (let ((template-start (point))) + (forward-char 1) + (let* ((sexp (read (current-buffer))) + (result (org-eval + (org-capture--expand-keyword-in-embedded-elisp + sexp)))) + (delete-region template-start (point)) + (cond + ((not result) nil) + ((stringp result) (insert result)) + (t (error + "Capture template sexp `%s' must evaluate to string or nil" + sexp)))))) + ((not mark) nil) + ;; Only mark valid and non-escaped sexp. + ((org-capture-escaped-%) nil) + (t + (let ((end (with-syntax-table emacs-lisp-mode-syntax-table + (ignore-errors (scan-sexps (1- (point)) 1))))) + (when end + (put-text-property (- (point) 2) end 'org-embedded-elisp t)))))))) (defun org-capture--expand-keyword-in-embedded-elisp (attr) "Recursively replace capture link keywords in ATTR sexp. @@ -1771,20 +1903,10 @@ Such keywords are prefixed with \"%:\". See (t attr))) (defun org-capture-inside-embedded-elisp-p () - "Return non-nil if point is inside of embedded elisp %(sexp)." - (let (beg end) - (with-syntax-table emacs-lisp-mode-syntax-table - (save-excursion - ;; `looking-at' and `search-backward' below do not match the "%(" if - ;; point is in its middle - (when (equal (char-before) ?%) - (backward-char)) - (save-match-data - (when (or (looking-at "%(") (search-backward "%(" nil t)) - (setq beg (point)) - (setq end (progn (forward-char) (forward-sexp) (1- (point))))))) - (when (and beg end) - (and (<= (point) end) (>= (point) beg)))))) + "Non-nil if point is inside of embedded elisp %(sexp). +Assume sexps have been marked with +`org-capture-expand-embedded-elisp' beforehand." + (get-text-property (point) 'org-embedded-elisp)) ;;;###autoload (defun org-capture-import-remember-templates () @@ -1829,6 +1951,7 @@ Such keywords are prefixed with \"%:\". See org-remember-templates)))) + (provide 'org-capture) ;;; org-capture.el ends here diff --git a/lisp/org/org-clock.el b/lisp/org/org-clock.el index 0bba92550f8..0e7eb214958 100644 --- a/lisp/org/org-clock.el +++ b/lisp/org/org-clock.el @@ -1,4 +1,4 @@ -;;; org-clock.el --- The time clocking code for Org-mode +;;; org-clock.el --- The time clocking code for Org mode -*- lexical-binding: t; -*- ;; Copyright (C) 2004-2017 Free Software Foundation, Inc. @@ -19,45 +19,53 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Commentary: -;; This file contains the time clocking code for Org-mode +;; This file contains the time clocking code for Org mode ;;; Code: -(eval-when-compile - (require 'cl)) +(require 'cl-lib) (require 'org) (declare-function calendar-iso-to-absolute "cal-iso" (date)) (declare-function notifications-notify "notifications" (&rest params)) -(declare-function org-pop-to-buffer-same-window "org-compat" (&optional buffer-or-name norecord label)) -(declare-function org-refresh-properties "org" (dprop tprop)) -(defvar org-time-stamp-formats) -(defvar org-ts-what) +(declare-function org-element-property "org-element" (property element)) +(declare-function org-element-type "org-element" (element)) +(declare-function org-table-goto-line "org-table" (n)) + (defvar org-frame-title-format-backup frame-title-format) +(defvar org-time-stamp-formats) + (defgroup org-clock nil - "Options concerning clocking working time in Org-mode." + "Options concerning clocking working time in Org mode." :tag "Org Clock" :group 'org-progress) -(defcustom org-clock-into-drawer org-log-into-drawer - "Should clocking info be wrapped into a drawer? -When t, clocking info will always be inserted into a :LOGBOOK: drawer. -If necessary, the drawer will be created. -When nil, the drawer will not be created, but used when present. -When an integer and the number of clocking entries in an item -reaches or exceeds this number, a drawer will be created. -When a string, it names the drawer to be used. - -The default for this variable is the value of `org-log-into-drawer', -which see." +(defcustom org-clock-into-drawer t + "Non-nil when clocking info should be wrapped into a drawer. + +When non-nil, clocking info will be inserted into the same drawer +as log notes (see variable `org-log-into-drawer'), if it exists, +or \"LOGBOOK\" otherwise. If necessary, the drawer will be +created. + +When an integer, the drawer is created only when the number of +clocking entries in an item reaches or exceeds this value. + +When a string, it becomes the name of the drawer, ignoring the +log notes drawer altogether. + +Do not check directly this variable in a Lisp program. Call +function `org-clock-into-drawer' instead." :group 'org-todo :group 'org-clock + :version "26.1" + :package-version '(Org . "8.3") :type '(choice (const :tag "Always" t) (const :tag "Only when drawer exists" nil) @@ -66,26 +74,29 @@ which see." (string :tag "Into Drawer named..."))) (defun org-clock-into-drawer () - "Return the value of `org-clock-into-drawer', but let properties overrule. + "Value of `org-clock-into-drawer'. but let properties overrule. + If the current entry has or inherits a CLOCK_INTO_DRAWER -property, it will be used instead of the default value; otherwise -if the current entry has or inherits a LOG_INTO_DRAWER property, -it will be used instead of the default value. -The default is the value of the customizable variable `org-clock-into-drawer', -which see." - (let ((p (org-entry-get nil "CLOCK_INTO_DRAWER" 'inherit)) - (q (org-entry-get nil "LOG_INTO_DRAWER" 'inherit))) - (cond - ((or (not (or p q)) (equal p "nil") (equal q "nil")) org-clock-into-drawer) - ((or (equal p "t") (equal q "t")) "LOGBOOK") - ((not p) q) - (t p)))) +property, it will be used instead of the default value. + +Return value is either a string, an integer, or nil." + (let ((p (org-entry-get nil "CLOCK_INTO_DRAWER" 'inherit t))) + (cond ((equal p "nil") nil) + ((equal p "t") (or (org-log-into-drawer) "LOGBOOK")) + ((org-string-nw-p p) + (if (string-match-p "\\`[0-9]+\\'" p) (string-to-number p) p)) + ((org-string-nw-p org-clock-into-drawer)) + ((integerp org-clock-into-drawer) org-clock-into-drawer) + ((not org-clock-into-drawer) nil) + ((org-log-into-drawer)) + (t "LOGBOOK")))) (defcustom org-clock-out-when-done t "When non-nil, clock will be stopped when the clocked entry is marked DONE. +\\<org-mode-map>\ DONE here means any DONE-like state. A nil value means clock will keep running until stopped explicitly with -`C-c C-x C-o', or until the clock is started in a different item. +`\\[org-clock-out]', or until the clock is started in a different item. Instead of t, this can also be a list of TODO states that should trigger clocking out." :group 'org-clock @@ -223,9 +234,6 @@ file name Play this sound file, fall back to beep" (const :tag "Standard beep" t) (file :tag "Play sound file"))) -(define-obsolete-variable-alias 'org-clock-modeline-total - 'org-clock-mode-line-total "24.3") - (defcustom org-clock-mode-line-total 'auto "Default setting for the time included for the mode line clock. This can be overruled locally using the CLOCK_MODELINE_TOTAL property. @@ -244,7 +252,7 @@ auto Automatically, either `all', or `repeat' for repeating tasks" (const :tag "All task time" all) (const :tag "Automatically, `all' or since `repeat'" auto))) -(org-defvaralias 'org-task-overrun-text 'org-clock-task-overrun-text) +(defvaralias 'org-task-overrun-text 'org-clock-task-overrun-text) (defcustom org-clock-task-overrun-text nil "Extra mode line text to indicate that the clock is overrun. The can be nil to indicate that instead of adding text, the clock time @@ -268,14 +276,14 @@ string as argument." (function :tag "Function"))) (defgroup org-clocktable nil - "Options concerning the clock table in Org-mode." + "Options concerning the clock table in Org mode." :tag "Org Clock Table" :group 'org-clock) (defcustom org-clocktable-defaults (list :maxlevel 2 - :lang (or (org-bound-and-true-p org-export-default-language) "en") + :lang (or (bound-and-true-p org-export-default-language) "en") :scope 'file :block nil :wstart 1 @@ -312,7 +320,9 @@ For more information, see `org-clocktable-write-default'." '(("en" "File" "L" "Timestamp" "Headline" "Time" "ALL" "Total time" "File time" "Clock summary at") ("es" "Archivo" "N" "Fecha y hora" "Tarea" "Tiempo" "TODO" "Tiempo total" "Tiempo archivo" "Clock summary at") ("fr" "Fichier" "N" "Horodatage" "En-tête" "Durée" "TOUT" "Durée totale" "Durée fichier" "Horodatage sommaire à") - ("nl" "Bestand" "N" "Tijdstip" "Hoofding" "Duur" "ALLES" "Totale duur" "Bestandstijd" "Clock summary at")) + ("nl" "Bestand" "N" "Tijdstip" "Hoofding" "Duur" "ALLES" "Totale duur" "Bestandstijd" "Clock summary at") + ("de" "Datei" "E" "Zeitstempel" "Kopfzeile" "Dauer" "GESAMT" + "Gesamtdauer" "Dateizeit" "Erstellt am")) "Terms used in clocktable, translated to different languages." :group 'org-clocktable :version "24.1" @@ -371,7 +381,7 @@ play with them." :type 'string) (defcustom org-clock-clocked-in-display 'mode-line - "When clocked in for a task, org-mode can display the current + "When clocked in for a task, Org can display the current task and accumulated time in the mode line and/or frame title. Allowed values are: @@ -413,6 +423,26 @@ if you are using Debian." :package-version '(Org . "8.0") :type 'string) +(defcustom org-clock-goto-before-context 2 + "Number of lines of context to display before currently clocked-in entry. +This applies when using `org-clock-goto'." + :group 'org-clock + :type 'integer) + +(defcustom org-clock-display-default-range 'thisyear + "Default range when displaying clocks with `org-clock-display'." + :group 'org-clock + :type '(choice (const today) + (const yesterday) + (const thisweek) + (const lastweek) + (const thismonth) + (const lastmonth) + (const thisyear) + (const lastyear) + (const untilnow) + (const :tag "Select range interactively" interactive))) + (defvar org-clock-in-prepare-hook nil "Hook run when preparing the clock. This hook is run before anything happens to the task that @@ -430,6 +460,43 @@ to add an effort property.") (defvar org-clock-has-been-used nil "Has the clock been used during the current Emacs session?") +(defvar org-clock-stored-history nil + "Clock history, populated by `org-clock-load'") +(defvar org-clock-stored-resume-clock nil + "Clock to resume, saved by `org-clock-load'") + +(defconst org-clock--oldest-date + (let* ((dichotomy + (lambda (min max pred) + (if (funcall pred min) min + (cl-incf min) + (while (> (- max min) 1) + (let ((mean (+ (ash min -1) (ash max -1) (logand min max 1)))) + (if (funcall pred mean) (setq max mean) (setq min mean))))) + max)) + (high + (funcall dichotomy + most-negative-fixnum + 0 + (lambda (m) + ;; libc in macOS 10.6 hangs when decoding times + ;; around year -2**31. Limit `high' not to go + ;; any earlier than that. + (unless (and (eq system-type 'darwin) + (string-match-p + "10\\.6\\.[[:digit:]]" + (shell-command-to-string + "sw_vers -productVersion")) + (<= m -1034058203135)) + (ignore-errors (decode-time (list m 0))))))) + (low + (funcall dichotomy + most-negative-fixnum + 0 + (lambda (m) (ignore-errors (decode-time (list high m))))))) + (list high low)) + "Internal time for oldest date representable on the system.") + ;;; The clock for measuring work time. (defvar org-mode-line-string "") @@ -465,6 +532,16 @@ of a different task.") (define-key org-clock-mode-line-map [mode-line mouse-2] 'org-clock-goto) (define-key org-clock-mode-line-map [mode-line mouse-1] 'org-clock-menu) +(defun org-clock--translate (s language) + "Translate string S into using string LANGUAGE. +Assume S in the English term to translate. Return S as-is if it +cannot be translated." + (or (nth (pcase s + ("File" 1) ("L" 2) ("Timestamp" 3) ("Headline" 4) ("Time" 5) + ("ALL" 6) ("Total time" 7) ("File time" 8) ("Clock summary at" 9)) + (assoc-string language org-clock-clocktable-language-setup t)) + s)) + (defun org-clock-menu () (interactive) (popup-menu @@ -500,8 +577,17 @@ of a different task.") (org-check-and-save-marker org-clock-hd-marker beg end) (org-check-and-save-marker org-clock-default-task beg end) (org-check-and-save-marker org-clock-interrupted-task beg end) - (mapc (lambda (m) (org-check-and-save-marker m beg end)) - org-clock-history)) + (dolist (m org-clock-history) + (org-check-and-save-marker m beg end))) + +(defun org-clock-drawer-name () + "Return clock drawer's name for current entry, or nil." + (let ((drawer (org-clock-into-drawer))) + (cond ((integerp drawer) + (let ((log-drawer (org-log-into-drawer))) + (if (stringp log-drawer) log-drawer "LOGBOOK"))) + ((stringp drawer) drawer) + (t nil)))) (defun org-clocking-buffer () "Return the clocking buffer if we are currently clocking a task or nil." @@ -515,12 +601,13 @@ of a different task.") "Hook called in task selection just before prompting the user.") (defun org-clock-select-task (&optional prompt) - "Select a task that was recently associated with clocking." - (interactive) + "Select a task that was recently associated with clocking. +Return marker position of the selected task. Raise an error if +there is no recent clock to choose from." (let (och chl sel-list rpl (i 0) s) ;; Remove successive dups from the clock history to consider - (mapc (lambda (c) (if (not (equal c (car och))) (push c och))) - org-clock-history) + (dolist (c org-clock-history) + (unless (equal c (car och)) (push c och))) (setq och (reverse och) chl (length och)) (if (zerop chl) (user-error "No recent clock") @@ -541,17 +628,15 @@ of a different task.") (setq s (org-clock-insert-selection-line ?c org-clock-marker)) (push s sel-list)) (insert (org-add-props "Recent Tasks\n" nil 'face 'bold)) - (mapc - (lambda (m) - (when (marker-buffer m) - (setq i (1+ i) - s (org-clock-insert-selection-line - (if (< i 10) - (+ i ?0) - (+ i (- ?A 10))) m)) - (if (fboundp 'int-to-char) (setf (car s) (int-to-char (car s)))) - (push s sel-list))) - och) + (dolist (m och) + (when (marker-buffer m) + (setq i (1+ i) + s (org-clock-insert-selection-line + (if (< i 10) + (+ i ?0) + (+ i (- ?A 10))) m)) + (if (fboundp 'int-to-char) (setf (car s) (int-to-char (car s)))) + (push s sel-list))) (run-hooks 'org-clock-before-select-task-hook) (goto-char (point-min)) ;; Set min-height relatively to circumvent a possible but in @@ -559,6 +644,7 @@ of a different task.") (fit-window-to-buffer nil nil (if (< chl 10) chl (+ 5 chl))) (message (or prompt "Select task for clocking:")) (setq cursor-type nil rpl (read-char-exclusive)) + (kill-buffer) (cond ((eq rpl ?q) nil) ((eq rpl ?x) nil) @@ -570,25 +656,22 @@ of a different task.") And return a cons cell with the selection character integer and the marker pointing to it." (when (marker-buffer marker) - (let (file cat task heading prefix) + (let (cat task heading prefix) (with-current-buffer (org-base-buffer (marker-buffer marker)) - (save-excursion - (save-restriction - (widen) - (ignore-errors - (goto-char marker) - (setq file (buffer-file-name (marker-buffer marker)) - cat (org-get-category) - heading (org-get-heading 'notags) - prefix (save-excursion - (org-back-to-heading t) - (looking-at org-outline-regexp) - (match-string 0)) - task (substring - (org-fontify-like-in-org-mode - (concat prefix heading) - org-odd-levels-only) - (length prefix))))))) + (org-with-wide-buffer + (ignore-errors + (goto-char marker) + (setq cat (org-get-category) + heading (org-get-heading 'notags) + prefix (save-excursion + (org-back-to-heading t) + (looking-at org-outline-regexp) + (match-string 0)) + task (substring + (org-fontify-like-in-org-mode + (concat prefix heading) + org-odd-levels-only) + (length prefix)))))) (when (and cat task) (insert (format "[%c] %-12s %s\n" i cat task)) (cons i marker))))) @@ -605,22 +688,21 @@ If an effort estimate was defined for the current item, use If not, show simply the clocked time like 01:50." (let ((clocked-time (org-clock-get-clocked-time))) (if org-clock-effort - (let* ((effort-in-minutes - (org-duration-string-to-minutes org-clock-effort)) + (let* ((effort-in-minutes (org-duration-to-minutes org-clock-effort)) (work-done-str - (org-propertize - (org-minutes-to-clocksum-string clocked-time) + (propertize + (org-duration-from-minutes clocked-time) 'face (if (and org-clock-task-overrun (not org-clock-task-overrun-text)) 'org-mode-line-clock-overrun 'org-mode-line-clock))) - (effort-str (org-minutes-to-clocksum-string effort-in-minutes)) - (clockstr (org-propertize + (effort-str (org-duration-from-minutes effort-in-minutes)) + (clockstr (propertize (concat " [%s/" effort-str "] (" (replace-regexp-in-string "%" "%%" org-clock-heading) ")") 'face 'org-mode-line-clock))) (format clockstr work-done-str)) - (org-propertize (concat "[" (org-minutes-to-clocksum-string clocked-time) - (format " (%s)" org-clock-heading) "]") - 'face 'org-mode-line-clock)))) + (propertize (concat " [" (org-duration-from-minutes clocked-time) + "]" (format " (%s)" org-clock-heading)) + 'face 'org-mode-line-clock)))) (defun org-clock-get-last-clock-out-time () "Get the last clock-out time for the current subtree." @@ -635,20 +717,21 @@ If not, show simply the clocked time like 01:50." (org-clock-notify-once-if-expired) (setq org-clock-task-overrun nil)) (setq org-mode-line-string - (org-propertize + (propertize (let ((clock-string (org-clock-get-clock-string)) - (help-text "Org-mode clock is running.\nmouse-1 shows a menu\nmouse-2 will jump to task")) + (help-text "Org mode clock is running.\nmouse-1 shows a \ +menu\nmouse-2 will jump to task")) (if (and (> org-clock-string-limit 0) (> (length clock-string) org-clock-string-limit)) - (org-propertize + (propertize (substring clock-string 0 org-clock-string-limit) 'help-echo (concat help-text ": " org-clock-heading)) - (org-propertize clock-string 'help-echo help-text))) + (propertize clock-string 'help-echo help-text))) 'local-map org-clock-mode-line-map - 'mouse-face (if (featurep 'xemacs) 'highlight 'mode-line-highlight))) + 'mouse-face 'mode-line-highlight)) (if (and org-clock-task-overrun org-clock-task-overrun-text) (setq org-mode-line-string - (concat (org-propertize + (concat (propertize org-clock-task-overrun-text 'face 'org-mode-line-clock-overrun) org-mode-line-string))) (force-mode-line-update)) @@ -687,15 +770,15 @@ clocked item, and the value displayed in the mode line." ;; A string. See if it is a delta (setq sign (string-to-char value)) (if (member sign '(?- ?+)) - (setq current (org-duration-string-to-minutes current) + (setq current (org-duration-to-minutes current) value (substring value 1)) (setq current 0)) - (setq value (org-duration-string-to-minutes value)) + (setq value (org-duration-to-minutes value)) (if (equal ?- sign) (setq value (- current value)) (if (equal ?+ sign) (setq value (+ current value))))) (setq value (max 0 value) - org-clock-effort (org-minutes-to-clocksum-string value)) + org-clock-effort (org-duration-from-minutes value)) (org-entry-put org-clock-marker "Effort" org-clock-effort) (org-clock-update-mode-line) (message "Effort is now %s" org-clock-effort)) @@ -708,7 +791,7 @@ clocked item, and the value displayed in the mode line." "Show notification if we spent more time than we estimated before. Notification is shown only once." (when (org-clocking-p) - (let ((effort-in-minutes (org-duration-string-to-minutes org-clock-effort)) + (let ((effort-in-minutes (org-duration-to-minutes org-clock-effort)) (clocked-time (org-clock-get-clocked-time))) (if (setq org-clock-task-overrun (if (or (null effort-in-minutes) (zerop effort-in-minutes)) @@ -739,7 +822,7 @@ use libnotify if available, or fall back on a message." org-show-notification-handler notification)) ((fboundp 'notifications-notify) (notifications-notify - :title "Org-mode message" + :title "Org mode message" :body notification ;; FIXME how to link to the Org icon? ;; :app-icon "~/.emacs.d/icons/mail.png" @@ -776,11 +859,12 @@ If CLOCK-SOUND is non-nil, it overrides `org-clock-sound'." "Search through the given file and find all open clocks." (let ((buf (or (get-file-buffer file) (find-file-noselect file))) + (org-clock-re (concat org-clock-string " \\(\\[.*?\\]\\)$")) clocks) (with-current-buffer buf (save-excursion (goto-char (point-min)) - (while (re-search-forward "CLOCK: \\(\\[.*?\\]\\)$" nil t) + (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)))) clocks)) @@ -793,12 +877,10 @@ If CLOCK-SOUND is non-nil, it overrides `org-clock-sound'." (defmacro org-with-clock-position (clock &rest forms) "Evaluate FORMS with CLOCK as the current active clock." `(with-current-buffer (marker-buffer (car ,clock)) - (save-excursion - (save-restriction - (widen) - (goto-char (car ,clock)) - (beginning-of-line) - ,@forms)))) + (org-with-wide-buffer + (goto-char (car ,clock)) + (beginning-of-line) + ,@forms))) (def-edebug-spec org-with-clock-position (form body)) (put 'org-with-clock-position 'lisp-indent-function 1) @@ -812,7 +894,7 @@ This macro also protects the current active clock from being altered." (org-clock-effort) (org-clock-marker (car ,clock)) (org-clock-hd-marker (save-excursion - (outline-back-to-heading t) + (org-back-to-heading t) (point-marker)))) ,@forms))) (def-edebug-spec org-with-clock (form body)) @@ -885,7 +967,7 @@ If necessary, clock-out of the currently active clock." (defun org-clock-jump-to-current-clock (&optional effective-clock) (interactive) - (let ((org-clock-into-drawer (org-clock-into-drawer)) + (let ((drawer (org-clock-into-drawer)) (clock (or effective-clock (cons org-clock-marker org-clock-start-time)))) (unless (marker-buffer (car clock)) @@ -893,26 +975,21 @@ If necessary, clock-out of the currently active clock." (org-with-clock clock (org-clock-goto)) (with-current-buffer (marker-buffer (car clock)) (goto-char (car clock)) - (if org-clock-into-drawer - (let ((logbook - (if (stringp org-clock-into-drawer) - (concat ":" org-clock-into-drawer ":") - ":LOGBOOK:"))) - (ignore-errors - (outline-flag-region - (save-excursion - (outline-back-to-heading t) - (search-forward logbook) - (goto-char (match-beginning 0))) - (save-excursion - (outline-back-to-heading t) - (search-forward logbook) - (search-forward ":END:") - (goto-char (match-end 0))) - nil))))))) + (when drawer + (org-with-wide-buffer + (let ((drawer-re (format "^[ \t]*:%s:[ \t]*$" + (regexp-quote (if (stringp drawer) drawer "LOGBOOK")))) + (beg (save-excursion (org-back-to-heading t) (point)))) + (catch 'exit + (while (re-search-backward drawer-re beg t) + (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)) + (throw 'exit nil))))))))))) (defun org-clock-resolve (clock &optional prompt-fn last-valid fail-quietly) - "Resolve an open org-mode clock. + "Resolve an open Org clock. An open clock was found, with `dangling' possibly being non-nil. If this function was invoked with a prefix argument, non-dangling open clocks are ignored. The given clock requires some sort of @@ -930,7 +1007,7 @@ The format of clock is (CONS MARKER START-TIME), where MARKER identifies the buffer and position the clock is open at (and thus, the heading it's under), and START-TIME is when the clock was started." - (assert clock) + (cl-assert clock) (let* ((ch (save-window-excursion (save-excursion @@ -947,7 +1024,7 @@ 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. -g/G Indicate that you “got back” X minutes ago. This is quite +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. @@ -963,10 +1040,6 @@ For all these options, using uppercase makes your final state to be CLOCKED OUT.")))) (org-fit-window-to-buffer (get-buffer-window "*Org Clock*")) (let (char-pressed) - (when (featurep 'xemacs) - (message (concat (funcall prompt-fn clock) - " [jkKgGsScCiq]? ")) - (setq char-pressed (read-char-exclusive))) (while (or (null char-pressed) (and (not (memq char-pressed '(?k ?K ?g ?G ?s ?S ?C @@ -1028,7 +1101,7 @@ to be CLOCKED OUT.")))) ;;;###autoload (defun org-resolve-clocks (&optional only-dangling-p prompt-fn last-valid) - "Resolve all currently open org-mode clocks. + "Resolve all currently open Org clocks. If `only-dangling-p' is non-nil, only ask to resolve dangling \(i.e., not currently open and valid) clocks." (interactive "P") @@ -1091,7 +1164,7 @@ This routine returns a floating point number." (defvar org-clock-user-idle-seconds) (defun org-resolve-clocks-if-idle () - "Resolve all currently open org-mode clocks. + "Resolve all currently open Org clocks. This is performed after `org-clock-idle-time' minutes, to check if the user really wants to stay clocked in after being idle for so long." @@ -1106,13 +1179,12 @@ so long." (org-clock-resolve (cons org-clock-marker org-clock-start-time) - (function - (lambda (clock) - (format "Clocked in & idle for %.1f mins" - (/ (float-time - (time-subtract (current-time) - org-clock-user-idle-start)) - 60.0)))) + (lambda (_) + (format "Clocked in & idle for %.1f mins" + (/ (float-time + (time-subtract (current-time) + org-clock-user-idle-start)) + 60.0))) org-clock-user-idle-start))))) (defvar org-clock-current-task nil "Task currently clocked in.") @@ -1122,18 +1194,25 @@ so long." ;;;###autoload (defun org-clock-in (&optional select start-time) "Start the clock on the current item. + If necessary, clock-out of the currently active clock. -With a prefix argument SELECT (\\[universal-argument]), offer a list of recently clocked -tasks to clock into. When SELECT is \\[universal-argument] \\[universal-argument], clock into the current task -and mark it as the default task, a special task that will always be offered -in the clocking selection, associated with the letter `d'. -When SELECT is \\[universal-argument] \\[universal-argument] \\[universal-argument], \ -clock in by using the last clock-out -time as the start time \(see `org-clock-continuously' to -make this the default behavior.)" + +With a `\\[universal-argument]' prefix argument SELECT, offer a list of \ +recently clocked +tasks to clock into. + +When SELECT is `\\[universal-argument] \ \\[universal-argument]', \ +clock into the current task and mark it as +the default task, a special task that will always be offered in the +clocking selection, associated with the letter `d'. + +When SELECT is `\\[universal-argument] \\[universal-argument] \ +\\[universal-argument]', clock in by using the last clock-out +time as the start time. See `org-clock-continuously' to make this +the default behavior." (interactive "P") (setq org-clock-notification-was-shown nil) - (org-refresh-properties org-effort-property 'org-effort) + (org-refresh-effort-properties) (catch 'abort (let ((interrupting (and (not org-clock-resolving-clocks-due-to-idleness) (org-clocking-p))) @@ -1148,7 +1227,7 @@ make this the default behavior.)" (not org-clock-resolving-clocks)) (setq org-clock-leftover-time nil) (let ((org-clock-clocking-in t)) - (org-resolve-clocks))) ; check if any clocks are dangling + (org-resolve-clocks))) ; check if any clocks are dangling (when (equal select '(64)) ;; Set start-time to `org-clock-out-time' @@ -1201,116 +1280,116 @@ make this the default behavior.)" (set-buffer (org-base-buffer (marker-buffer selected-task))) (setq target-pos (marker-position selected-task)) (move-marker selected-task nil)) - (save-excursion - (save-restriction - (widen) - (goto-char target-pos) - (org-back-to-heading t) - (or interrupting (move-marker org-clock-interrupted-task nil)) - (run-hooks 'org-clock-in-prepare-hook) - (org-clock-history-push) - (setq org-clock-current-task (nth 4 (org-heading-components))) - (cond ((functionp org-clock-in-switch-to-state) - (looking-at org-complex-heading-regexp) - (let ((newstate (funcall org-clock-in-switch-to-state - (match-string 2)))) - (if newstate (org-todo newstate)))) - ((and org-clock-in-switch-to-state - (not (looking-at (concat org-outline-regexp "[ \t]*" - org-clock-in-switch-to-state - "\\>")))) - (org-todo org-clock-in-switch-to-state))) - (setq org-clock-heading - (cond ((and org-clock-heading-function - (functionp org-clock-heading-function)) - (funcall org-clock-heading-function)) - ((nth 4 (org-heading-components)) - (replace-regexp-in-string - "\\[\\[.*?\\]\\[\\(.*?\\)\\]\\]" "\\1" - (match-string-no-properties 4))) - (t "???"))) - (org-clock-find-position org-clock-in-resume) - (cond - ((and org-clock-in-resume - (looking-at - (concat "^[ \t]*" org-clock-string - " \\[\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}" - " *\\sw+.? +[012][0-9]:[0-5][0-9]\\)\\][ \t]*$"))) - (message "Matched %s" (match-string 1)) - (setq ts (concat "[" (match-string 1) "]")) - (goto-char (match-end 1)) - (setq org-clock-start-time - (apply 'encode-time - (org-parse-time-string (match-string 1)))) - (setq org-clock-effort (org-entry-get (point) org-effort-property)) - (setq org-clock-total-time (org-clock-sum-current-item - (org-clock-get-sum-start)))) - ((eq org-clock-in-resume 'auto-restart) - ;; called from org-clock-load during startup, - ;; do not interrupt, but warn! - (message "Cannot restart clock because task does not contain unfinished clock") - (ding) - (sit-for 2) - (throw 'abort nil)) - (t - (insert-before-markers "\n") - (backward-char 1) - (org-indent-line) - (when (and (save-excursion - (end-of-line 0) - (org-in-item-p))) - (beginning-of-line 1) - (org-indent-line-to (- (org-get-indentation) 2))) - (insert org-clock-string " ") - (setq org-clock-effort (org-entry-get (point) org-effort-property)) - (setq org-clock-total-time (org-clock-sum-current-item - (org-clock-get-sum-start))) - (setq org-clock-start-time - (or (and org-clock-continuously org-clock-out-time) - (and leftover - (y-or-n-p - (format - "You stopped another clock %d mins ago; start this one from then? " - (/ (- (float-time - (org-current-time org-clock-rounding-minutes t)) - (float-time leftover)) 60))) - leftover) - start-time - (org-current-time org-clock-rounding-minutes t))) - (setq ts (org-insert-time-stamp org-clock-start-time - 'with-hm 'inactive)))) - (move-marker org-clock-marker (point) (buffer-base-buffer)) - (move-marker org-clock-hd-marker - (save-excursion (org-back-to-heading t) (point)) - (buffer-base-buffer)) - (setq org-clock-has-been-used t) - ;; add to mode line - (when (or (eq org-clock-clocked-in-display 'mode-line) - (eq org-clock-clocked-in-display 'both)) - (or global-mode-string (setq global-mode-string '(""))) - (or (memq 'org-mode-line-string global-mode-string) - (setq global-mode-string - (append global-mode-string '(org-mode-line-string))))) - ;; add to frame title - (when (or (eq org-clock-clocked-in-display 'frame-title) - (eq org-clock-clocked-in-display 'both)) - (setq frame-title-format org-clock-frame-title-format)) - (org-clock-update-mode-line) - (when org-clock-mode-line-timer - (cancel-timer org-clock-mode-line-timer) - (setq org-clock-mode-line-timer nil)) - (when org-clock-clocked-in-display - (setq org-clock-mode-line-timer - (run-with-timer org-clock-update-period - org-clock-update-period - 'org-clock-update-mode-line))) - (when org-clock-idle-timer - (cancel-timer org-clock-idle-timer) - (setq org-clock-idle-timer nil)) - (setq org-clock-idle-timer - (run-with-timer 60 60 'org-resolve-clocks-if-idle)) - (message "Clock starts at %s - %s" ts org--msg-extra) - (run-hooks 'org-clock-in-hook))))))) + (org-with-wide-buffer + (goto-char target-pos) + (org-back-to-heading t) + (or interrupting (move-marker org-clock-interrupted-task nil)) + (run-hooks 'org-clock-in-prepare-hook) + (org-clock-history-push) + (setq org-clock-current-task (nth 4 (org-heading-components))) + (cond ((functionp org-clock-in-switch-to-state) + (let ((case-fold-search nil)) + (looking-at org-complex-heading-regexp)) + (let ((newstate (funcall org-clock-in-switch-to-state + (match-string 2)))) + (when newstate (org-todo newstate)))) + ((and org-clock-in-switch-to-state + (not (looking-at (concat org-outline-regexp "[ \t]*" + org-clock-in-switch-to-state + "\\>")))) + (org-todo org-clock-in-switch-to-state))) + (setq org-clock-heading + (cond ((and org-clock-heading-function + (functionp org-clock-heading-function)) + (funcall org-clock-heading-function)) + ((nth 4 (org-heading-components)) + (replace-regexp-in-string + "\\[\\[.*?\\]\\[\\(.*?\\)\\]\\]" "\\1" + (match-string-no-properties 4))) + (t "???"))) + (org-clock-find-position org-clock-in-resume) + (cond + ((and org-clock-in-resume + (looking-at + (concat "^[ \t]*" org-clock-string + " \\[\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}" + " *\\sw+.? +[012][0-9]:[0-5][0-9]\\)\\][ \t]*$"))) + (message "Matched %s" (match-string 1)) + (setq ts (concat "[" (match-string 1) "]")) + (goto-char (match-end 1)) + (setq org-clock-start-time + (apply 'encode-time + (org-parse-time-string (match-string 1)))) + (setq org-clock-effort (org-entry-get (point) org-effort-property)) + (setq org-clock-total-time (org-clock-sum-current-item + (org-clock-get-sum-start)))) + ((eq org-clock-in-resume 'auto-restart) + ;; called from org-clock-load during startup, + ;; do not interrupt, but warn! + (message "Cannot restart clock because task does not contain unfinished clock") + (ding) + (sit-for 2) + (throw 'abort nil)) + (t + (insert-before-markers "\n") + (backward-char 1) + (org-indent-line) + (when (and (save-excursion + (end-of-line 0) + (org-in-item-p))) + (beginning-of-line 1) + (indent-line-to (- (org-get-indentation) 2))) + (insert org-clock-string " ") + (setq org-clock-effort (org-entry-get (point) org-effort-property)) + (setq org-clock-total-time (org-clock-sum-current-item + (org-clock-get-sum-start))) + (setq org-clock-start-time + (or (and org-clock-continuously org-clock-out-time) + (and leftover + (y-or-n-p + (format + "You stopped another clock %d mins ago; start this one from then? " + (/ (- (float-time + (org-current-time org-clock-rounding-minutes t)) + (float-time leftover)) + 60))) + leftover) + start-time + (org-current-time org-clock-rounding-minutes t))) + (setq ts (org-insert-time-stamp org-clock-start-time + 'with-hm 'inactive)))) + (move-marker org-clock-marker (point) (buffer-base-buffer)) + (move-marker org-clock-hd-marker + (save-excursion (org-back-to-heading t) (point)) + (buffer-base-buffer)) + (setq org-clock-has-been-used t) + ;; add to mode line + (when (or (eq org-clock-clocked-in-display 'mode-line) + (eq org-clock-clocked-in-display 'both)) + (or global-mode-string (setq global-mode-string '(""))) + (or (memq 'org-mode-line-string global-mode-string) + (setq global-mode-string + (append global-mode-string '(org-mode-line-string))))) + ;; add to frame title + (when (or (eq org-clock-clocked-in-display 'frame-title) + (eq org-clock-clocked-in-display 'both)) + (setq frame-title-format org-clock-frame-title-format)) + (org-clock-update-mode-line) + (when org-clock-mode-line-timer + (cancel-timer org-clock-mode-line-timer) + (setq org-clock-mode-line-timer nil)) + (when org-clock-clocked-in-display + (setq org-clock-mode-line-timer + (run-with-timer org-clock-update-period + org-clock-update-period + 'org-clock-update-mode-line))) + (when org-clock-idle-timer + (cancel-timer org-clock-idle-timer) + (setq org-clock-idle-timer nil)) + (setq org-clock-idle-timer + (run-with-timer 60 60 'org-resolve-clocks-if-idle)) + (message "Clock starts at %s - %s" ts org--msg-extra) + (run-hooks 'org-clock-in-hook)))))) ;;;###autoload (defun org-clock-in-last (&optional arg) @@ -1324,8 +1403,7 @@ With three universal prefix arguments, interactively prompt for a todo state to switch to, overriding the existing value `org-clock-in-switch-to-state'." (interactive "P") - (if (equal arg '(4)) - (org-clock-in (org-clock-select-task)) + (if (equal arg '(4)) (org-clock-in arg) (let ((start-time (if (or org-clock-continuously (equal arg '(16))) (or org-clock-out-time (org-current-time org-clock-rounding-minutes t)) @@ -1357,11 +1435,13 @@ for a todo state to switch to, overriding the existing value (defun org-clock-get-sum-start () "Return the time from which clock times should be counted. -This is for the currently running clock as it is displayed -in the mode line. This function looks at the properties -LAST_REPEAT and in particular CLOCK_MODELINE_TOTAL and the -corresponding variable `org-clock-mode-line-total' and then -decides which time to use." + +This is for the currently running clock as it is displayed in the +mode line. This function looks at the properties LAST_REPEAT and +in particular CLOCK_MODELINE_TOTAL and the corresponding variable +`org-clock-mode-line-total' and then decides which time to use. + +The time is always returned as UTC." (let ((cmt (or (org-entry-get nil "CLOCK_MODELINE_TOTAL") (symbol-name org-clock-mode-line-total))) (lr (org-entry-get nil "LAST_REPEAT"))) @@ -1371,11 +1451,13 @@ decides which time to use." (current-time)) ((equal cmt "today") (setq org--msg-extra "showing today's task time.") - (let* ((dt (decode-time))) - (setq dt (append (list 0 0 0) (nthcdr 3 dt))) - (if org-extend-today-until - (setf (nth 2 dt) org-extend-today-until)) - (apply 'encode-time dt))) + (let* ((dt (org-decode-time nil t)) + (hour (nth 2 dt)) + (day (nth 3 dt))) + (if (< hour org-extend-today-until) (setf (nth 3 dt) (1- day))) + (setf (nth 2 dt) org-extend-today-until) + (setq dt (append (list 0 0) (nthcdr 2 dt) '(t))) + (apply #'encode-time dt))) ((or (equal cmt "all") (and (or (not cmt) (equal cmt "auto")) (not lr))) @@ -1385,9 +1467,7 @@ decides which time to use." (and (or (not cmt) (equal cmt "auto")) lr)) (setq org--msg-extra "showing task time since last repeat.") - (if (not lr) - nil - (org-time-string-to-time lr))) + (and lr (org-time-string-to-time lr))) (t nil)))) (defun org-clock-find-position (find-unclosed) @@ -1396,87 +1476,93 @@ When FIND-UNCLOSED is non-nil, first check if there is an unclosed clock line and position cursor in that line." (org-back-to-heading t) (catch 'exit - (let* ((org-clock-into-drawer (org-clock-into-drawer)) - (beg (save-excursion - (beginning-of-line 2) - (or (bolp) (newline)) - (point))) - (end (progn (outline-next-heading) (point))) - (re (concat "^[ \t]*" org-clock-string)) - (cnt 0) - (drawer (if (stringp org-clock-into-drawer) - org-clock-into-drawer "LOGBOOK")) - first last ind-last) - (goto-char beg) - (when (and find-unclosed - (re-search-forward - (concat "^[ \t]*" org-clock-string - " \\[\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}" - " *\\sw+ +[012][0-9]:[0-5][0-9]\\)\\][ \t]*$") - end t)) - (beginning-of-line 1) - (throw 'exit t)) - (when (eobp) (newline) (setq end (max (point) end))) - (when (re-search-forward (concat "^[ \t]*:" drawer ":") end t) - ;; we seem to have a CLOCK drawer, so go there. - (beginning-of-line 2) - (or org-log-states-order-reversed - (and (re-search-forward org-property-end-re nil t) - (goto-char (match-beginning 0)))) - (throw 'exit t)) - ;; Lets count the CLOCK lines - (goto-char beg) - (while (re-search-forward re end t) - (setq first (or first (match-beginning 0)) - last (match-beginning 0) - cnt (1+ cnt))) - (when (and (integerp org-clock-into-drawer) - last - (>= (1+ cnt) org-clock-into-drawer)) - ;; Wrap current entries into a new drawer - (goto-char last) - (setq ind-last (org-get-indentation)) - (beginning-of-line 2) - (if (and (>= (org-get-indentation) ind-last) - (org-at-item-p)) - (when (and (>= (org-get-indentation) ind-last) - (org-at-item-p)) - (let ((struct (org-list-struct))) - (goto-char (org-list-get-bottom-point struct))))) - (insert ":END:\n") - (beginning-of-line 0) - (org-indent-line-to ind-last) - (goto-char first) - (insert ":" drawer ":\n") - (beginning-of-line 0) - (org-indent-line) - (org-flag-drawer t) - (beginning-of-line 2) - (or org-log-states-order-reversed - (and (re-search-forward org-property-end-re nil t) - (goto-char (match-beginning 0)))) - (throw 'exit nil)) - + (let* ((beg (line-beginning-position)) + (end (save-excursion (outline-next-heading) (point))) + (org-clock-into-drawer (org-clock-into-drawer)) + (drawer (org-clock-drawer-name))) + ;; Look for a running clock if FIND-UNCLOSED in non-nil. + (when find-unclosed + (let ((open-clock-re + (concat "^[ \t]*" + org-clock-string + " \\[\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}" + " *\\sw+ +[012][0-9]:[0-5][0-9]\\)\\][ \t]*$"))) + (while (re-search-forward open-clock-re end t) + (let ((element (org-element-at-point))) + (when (and (eq (org-element-type element) 'clock) + (eq (org-element-property :status element) 'running)) + (beginning-of-line) + (throw 'exit t)))))) + ;; Look for an existing clock drawer. + (when drawer + (goto-char beg) + (let ((drawer-re (concat "^[ \t]*:" (regexp-quote drawer) ":[ \t]*$"))) + (while (re-search-forward drawer-re end t) + (let ((element (org-element-at-point))) + (when (eq (org-element-type element) 'drawer) + (let ((cend (org-element-property :contents-end element))) + (if (and (not org-log-states-order-reversed) cend) + (goto-char cend) + (forward-line)) + (throw 'exit t))))))) (goto-char beg) - (while (and (looking-at (concat "[ \t]*" org-keyword-time-regexp)) - (not (equal (match-string 1) org-clock-string))) - ;; Planning info, skip to after it - (beginning-of-line 2) - (or (bolp) (newline))) - (when (or (eq org-clock-into-drawer t) - (stringp org-clock-into-drawer) - (and (integerp org-clock-into-drawer) - (< org-clock-into-drawer 2))) - (insert ":" drawer ":\n:END:\n") - (beginning-of-line -1) - (org-indent-line) - (org-flag-drawer t) - (beginning-of-line 2) - (org-indent-line) - (beginning-of-line) - (or org-log-states-order-reversed - (and (re-search-forward org-property-end-re nil t) - (goto-char (match-beginning 0)))))))) + (let ((clock-re (concat "^[ \t]*" org-clock-string)) + (count 0) + positions) + ;; Count the CLOCK lines and store their positions. + (save-excursion + (while (re-search-forward clock-re end t) + (let ((element (org-element-at-point))) + (when (eq (org-element-type element) 'clock) + (setq positions (cons (line-beginning-position) positions) + count (1+ count)))))) + (cond + ((null positions) + ;; Skip planning line and property drawer, if any. + (org-end-of-meta-data) + (unless (bolp) (insert "\n")) + ;; Create a new drawer if necessary. + (when (and org-clock-into-drawer + (or (not (wholenump org-clock-into-drawer)) + (< org-clock-into-drawer 2))) + (let ((beg (point))) + (insert ":" drawer ":\n:END:\n") + (org-indent-region beg (point)) + (goto-char beg) + (org-flag-drawer t) + (forward-line)))) + ;; When a clock drawer needs to be created because of the + ;; number of clock items or simply if it is missing, collect + ;; all clocks in the section and wrap them within the drawer. + ((if (wholenump org-clock-into-drawer) + (>= (1+ count) org-clock-into-drawer) + drawer) + ;; Skip planning line and property drawer, if any. + (org-end-of-meta-data) + (let ((beg (point))) + (insert + (mapconcat + (lambda (p) + (save-excursion + (goto-char p) + (org-trim (delete-and-extract-region + (save-excursion (skip-chars-backward " \r\t\n") + (line-beginning-position 2)) + (line-beginning-position 2))))) + positions "\n") + "\n:END:\n") + (let ((end (point-marker))) + (goto-char beg) + (save-excursion (insert ":" drawer ":\n")) + (org-flag-drawer t) + (org-indent-region (point) end) + (forward-line) + (unless org-log-states-order-reversed + (goto-char end) + (beginning-of-line -1)) + (set-marker end nil)))) + (org-log-states-order-reversed (goto-char (car (last positions)))) + (t (goto-char (car positions)))))))) ;;;###autoload (defun org-clock-out (&optional switch-to-state fail-quietly at-time) @@ -1504,7 +1590,7 @@ to, overriding the existing value of `org-clock-out-switch-to-state'." ts te s h m remove) (setq org-clock-out-time now) (save-excursion ; Do not replace this with `with-current-buffer'. - (org-no-warnings (set-buffer (org-clocking-buffer))) + (with-no-warnings (set-buffer (org-clocking-buffer))) (save-restriction (widen) (goto-char org-clock-marker) @@ -1517,24 +1603,28 @@ to, overriding the existing value of `org-clock-out-switch-to-state'." (delete-region (point) (point-at-eol)) (insert "--") (setq te (org-insert-time-stamp (or at-time now) 'with-hm 'inactive)) - (setq s (- (float-time (apply 'encode-time (org-parse-time-string te))) - (float-time (apply 'encode-time (org-parse-time-string ts)))) + (setq s (- (float-time + (apply #'encode-time (org-parse-time-string te))) + (float-time + (apply #'encode-time (org-parse-time-string ts)))) h (floor (/ s 3600)) s (- s (* 3600 h)) m (floor (/ s 60)) s (- s (* 60 s))) (insert " => " (format "%2d:%02d" h m)) - (when (setq remove (and org-clock-out-remove-zero-time-clocks - (= (+ h m) 0))) - (beginning-of-line 1) - (delete-region (point) (point-at-eol)) - (and (looking-at "\n") (> (point-max) (1+ (point))) - (delete-char 1))) (move-marker org-clock-marker nil) (move-marker org-clock-hd-marker nil) - (when org-log-note-clock-out - (org-add-log-setup 'clock-out nil nil nil nil - (concat "# Task: " (org-get-heading t) "\n\n"))) + ;; Possibly remove zero time clocks. However, do not add + ;; a note associated to the CLOCK line in this case. + (cond ((and org-clock-out-remove-zero-time-clocks + (= (+ h m) 0)) + (setq remove t) + (delete-region (line-beginning-position) + (line-beginning-position 2))) + (org-log-note-clock-out + (org-add-log-setup + 'clock-out nil nil nil + (concat "# Task: " (org-get-heading t) "\n\n")))) (when org-clock-mode-line-timer (cancel-timer org-clock-mode-line-timer) (setq org-clock-mode-line-timer nil)) @@ -1547,14 +1637,14 @@ to, overriding the existing value of `org-clock-out-switch-to-state'." (when org-clock-out-switch-to-state (save-excursion (org-back-to-heading t) - (let ((org-inhibit-logging t) - (org-clock-out-when-done nil)) + (let ((org-clock-out-when-done nil)) (cond ((functionp org-clock-out-switch-to-state) - (looking-at org-complex-heading-regexp) + (let ((case-fold-search nil)) + (looking-at org-complex-heading-regexp)) (let ((newstate (funcall org-clock-out-switch-to-state (match-string 2)))) - (if newstate (org-todo newstate)))) + (when newstate (org-todo newstate)))) ((and org-clock-out-switch-to-state (not (looking-at (concat org-outline-regexp "[ \t]*" org-clock-out-switch-to-state @@ -1562,36 +1652,27 @@ to, overriding the existing value of `org-clock-out-switch-to-state'." (org-todo org-clock-out-switch-to-state)))))) (force-mode-line-update) (message (concat "Clock stopped at %s after " - (org-minutes-to-clocksum-string (+ (* 60 h) m)) "%s") + (org-duration-from-minutes (+ (* 60 h) m)) "%s") te (if remove " => LINE REMOVED" "")) - (let ((h org-clock-out-hook)) - ;; If a closing note needs to be stored in the drawer - ;; where clocks are stored, let's temporarily disable - ;; `org-clock-remove-empty-clock-drawer' - (if (and (equal org-clock-into-drawer org-log-into-drawer) - (eq org-log-done 'note) - org-clock-out-when-done) - (setq h (delq 'org-clock-remove-empty-clock-drawer h))) - (mapc (lambda (f) (funcall f)) h)) + (run-hooks 'org-clock-out-hook) (unless (org-clocking-p) (setq org-clock-current-task nil))))))) (add-hook 'org-clock-out-hook 'org-clock-remove-empty-clock-drawer) -(defun org-clock-remove-empty-clock-drawer nil - "Remove empty clock drawer in the current subtree." - (let* ((olid (or (org-entry-get (point) "LOG_INTO_DRAWER") - org-log-into-drawer)) - (clock-drawer (if (eq t olid) "LOGBOOK" olid)) - (end (save-excursion (org-end-of-subtree t t)))) - (when clock-drawer - (save-excursion - (org-back-to-heading t) - (while (and (< (point) end) - (search-forward clock-drawer end t)) - (goto-char (match-beginning 0)) - (org-remove-empty-drawer-at clock-drawer (point)) - (forward-line 1)))))) +(defun org-clock-remove-empty-clock-drawer () + "Remove empty clock drawers in current subtree." + (save-excursion + (org-back-to-heading t) + (org-map-tree + (lambda () + (let ((drawer (org-clock-drawer-name)) + (case-fold-search t)) + (when drawer + (let ((re (format "^[ \t]*:%s:[ \t]*$" (regexp-quote drawer))) + (end (save-excursion (outline-next-heading)))) + (while (re-search-forward re end t) + (org-remove-empty-drawer-at (point)))))))))) (defun org-clock-timestamps-up (&optional n) "Increase CLOCK timestamps at cursor. @@ -1607,13 +1688,13 @@ Optional argument N tells to change by that many units." (defun org-clock-timestamps-change (updown &optional n) "Change CLOCK timestamps synchronously at cursor. -UPDOWN tells whether to change 'up or 'down. +UPDOWN tells whether to change `up' or `down'. Optional argument N tells to change by that many units." - (setq org-ts-what nil) - (when (org-at-timestamp-p t) - (let ((tschange (if (eq updown 'up) 'org-timestamp-up - 'org-timestamp-down)) - ts1 begts1 ts2 begts2 updatets1 tdiff) + (let ((tschange (if (eq updown 'up) 'org-timestamp-up + 'org-timestamp-down)) + (timestamp? (org-at-timestamp-p 'lax)) + ts1 begts1 ts2 begts2 updatets1 tdiff) + (when timestamp? (save-excursion (move-beginning-of-line 1) (re-search-forward org-ts-regexp3 nil t) @@ -1625,7 +1706,6 @@ Optional argument N tells to change by that many units." (if (not ts2) ;; fall back on org-timestamp-up if there is only one (funcall tschange n) - ;; setq this so that (boundp 'org-ts-what is non-nil) (funcall tschange n) (let ((ts (if updatets1 ts2 ts1)) (begts (if updatets1 begts1 begts2))) @@ -1637,12 +1717,13 @@ Optional argument N tells to change by that many units." (goto-char begts) (org-timestamp-change (round (/ (float-time tdiff) - (cond ((eq org-ts-what 'minute) 60) - ((eq org-ts-what 'hour) 3600) - ((eq org-ts-what 'day) (* 24 3600)) - ((eq org-ts-what 'month) (* 24 3600 31)) - ((eq org-ts-what 'year) (* 24 3600 365.2))))) - org-ts-what 'updown))))))) + (pcase timestamp? + (`minute 60) + (`hour 3600) + (`day (* 24 3600)) + (`month (* 24 3600 31)) + (`year (* 24 3600 365.2))))) + timestamp? 'updown))))))) ;;;###autoload (defun org-clock-cancel () @@ -1654,13 +1735,13 @@ Optional argument N tells to change by that many units." (setq frame-title-format org-frame-title-format-backup) (force-mode-line-update) (error "No active clock")) - (save-excursion ; Do not replace this with `with-current-buffer'. - (org-no-warnings (set-buffer (org-clocking-buffer))) + (save-excursion ; Do not replace this with `with-current-buffer'. + (with-no-warnings (set-buffer (org-clocking-buffer))) (goto-char org-clock-marker) - (if (org-looking-back (concat "^[ \t]*" org-clock-string ".*") - (line-beginning-position)) + (if (looking-back (concat "^[ \t]*" org-clock-string ".*") + (line-beginning-position)) (progn (delete-region (1- (point-at-bol)) (point-at-eol)) - (org-remove-empty-drawer-at "LOGBOOK" (point))) + (org-remove-empty-drawer-at (point))) (message "Clock gone, cancel the timer anyway") (sit-for 2))) (move-marker org-clock-marker nil) @@ -1672,12 +1753,6 @@ Optional argument N tells to change by that many units." (message "Clock canceled") (run-hooks 'org-clock-cancel-hook)) -(defcustom org-clock-goto-before-context 2 - "Number of lines of context to display before currently clocked-in entry. -This applies when using `org-clock-goto'." - :group 'org-clock - :type 'integer) - ;;;###autoload (defun org-clock-goto (&optional select) "Go to the currently clocked-in entry, or to the most recently clocked one. @@ -1695,7 +1770,7 @@ With prefix arg SELECT, offer recently clocked tasks for selection." (setq recent t) (car org-clock-history)) (t (error "No active or recent clock task"))))) - (org-pop-to-buffer-same-window (marker-buffer m)) + (pop-to-buffer-same-window (marker-buffer m)) (if (or (< m (point-min)) (> m (point-max))) (widen)) (goto-char m) (org-show-entry) @@ -1707,15 +1782,27 @@ With prefix arg SELECT, offer recently clocked tasks for selection." (message "No running clock, this is the most recently clocked task")) (run-hooks 'org-clock-goto-hook))) -(defvar org-clock-file-total-minutes nil +(defvar-local org-clock-file-total-minutes nil "Holds the file total time in minutes, after a call to `org-clock-sum'.") -(make-variable-buffer-local 'org-clock-file-total-minutes) (defun org-clock-sum-today (&optional headline-filter) "Sum the times for each subtree for today." - (interactive) (let ((range (org-clock-special-range 'today))) - (org-clock-sum (car range) (cadr range) nil :org-clock-minutes-today))) + (org-clock-sum (car range) (cadr range) + headline-filter :org-clock-minutes-today))) + +(defun org-clock-sum-custom (&optional headline-filter range propname) + "Sum the times for each subtree for today." + (let ((r (or (and (symbolp range) (org-clock-special-range range)) + (org-clock-special-range + (intern (completing-read + "Range: " + '("today" "yesterday" "thisweek" "lastweek" + "thismonth" "lastmonth" "thisyear" "lastyear" + "interactive") + nil t)))))) + (org-clock-sum (car r) (cadr r) + headline-filter (or propname :org-clock-minutes-custom)))) ;;;###autoload (defun org-clock-sum (&optional tstart tend headline-filter propname) @@ -1726,21 +1813,21 @@ HEADLINE-FILTER is a zero-arg function that, if specified, is called for each headline in the time range with point at the headline. Headlines for which HEADLINE-FILTER returns nil are excluded from the clock summation. PROPNAME lets you set a custom text property instead of :org-clock-minutes." - (interactive) (org-with-silent-modifications (let* ((re (concat "^\\(\\*+\\)[ \t]\\|^[ \t]*" org-clock-string "[ \t]*\\(?:\\(\\[.*?\\]\\)-+\\(\\[.*?\\]\\)\\|=>[ \t]+\\([0-9]+\\):\\([0-9]+\\)\\)")) (lmax 30) (ltimes (make-vector lmax 0)) - (t1 0) (level 0) - ts te dt + (tstart (cond ((stringp tstart) (org-time-string-to-seconds tstart)) + ((consp tstart) (float-time tstart)) + (t tstart))) + (tend (cond ((stringp tend) (org-time-string-to-seconds tend)) + ((consp tend) (float-time tend)) + (t tend))) + (t1 0) time) - (if (stringp tstart) (setq tstart (org-time-string-to-seconds tstart))) - (if (stringp tend) (setq tend (org-time-string-to-seconds tend))) - (if (consp tstart) (setq tstart (float-time tstart))) - (if (consp tend) (setq tend (float-time tend))) (remove-text-properties (point-min) (point-max) `(,(or propname :org-clock-minutes) t :org-clock-force-headline-inclusion t)) @@ -1749,32 +1836,33 @@ PROPNAME lets you set a custom text property instead of :org-clock-minutes." (while (re-search-backward re nil t) (cond ((match-end 2) - ;; Two time stamps - (setq ts (match-string 2) - te (match-string 3) - ts (float-time - (apply 'encode-time (org-parse-time-string ts))) - te (float-time - (apply 'encode-time (org-parse-time-string te))) - ts (if tstart (max ts tstart) ts) - te (if tend (min te tend) te) - dt (- te ts) - t1 (if (> dt 0) (+ t1 (floor (/ dt 60))) t1))) + ;; Two time stamps. + (let* ((ts (float-time + (apply #'encode-time + (save-match-data + (org-parse-time-string (match-string 2)))))) + (te (float-time + (apply #'encode-time + (org-parse-time-string (match-string 3))))) + (dt (- (if tend (min te tend) te) + (if tstart (max ts tstart) ts)))) + (when (> dt 0) (cl-incf t1 (floor (/ dt 60)))))) ((match-end 4) - ;; A naked time + ;; A naked time. (setq t1 (+ t1 (string-to-number (match-string 5)) (* 60 (string-to-number (match-string 4)))))) - (t ;; A headline - ;; Add the currently clocking item time to the total + (t ;A headline + ;; Add the currently clocking item time to the total. (when (and org-clock-report-include-clocking-task - (equal (org-clocking-buffer) (current-buffer)) - (equal (marker-position org-clock-hd-marker) (point)) + (eq (org-clocking-buffer) (current-buffer)) + (eq (marker-position org-clock-hd-marker) (point)) tstart tend (>= (float-time org-clock-start-time) tstart) (<= (float-time org-clock-start-time) tend)) (let ((time (floor (- (float-time) - (float-time org-clock-start-time)) 60))) + (float-time org-clock-start-time)) + 60))) (setq t1 (+ t1 time)))) (let* ((headline-forced (get-text-property (point) @@ -1784,27 +1872,27 @@ PROPNAME lets you set a custom text property instead of :org-clock-minutes." (save-excursion (save-match-data (funcall headline-filter)))))) (setq level (- (match-end 1) (match-beginning 1))) + (when (>= level lmax) + (setq ltimes (vconcat ltimes (make-vector lmax 0)) lmax (* 2 lmax))) (when (or (> t1 0) (> (aref ltimes level) 0)) (when (or headline-included headline-forced) (if headline-included - (loop for l from 0 to level do - (aset ltimes l (+ (aref ltimes l) t1)))) + (cl-loop for l from 0 to level do + (aset ltimes l (+ (aref ltimes l) t1)))) (setq time (aref ltimes level)) (goto-char (match-beginning 0)) (put-text-property (point) (point-at-eol) (or propname :org-clock-minutes) time) - (if headline-filter - (save-excursion - (save-match-data - (while - (> (funcall outline-level) 1) - (outline-up-heading 1 t) - (put-text-property - (point) (point-at-eol) - :org-clock-force-headline-inclusion t)))))) + (when headline-filter + (save-excursion + (save-match-data + (while (org-up-heading-safe) + (put-text-property + (point) (line-end-position) + :org-clock-force-headline-inclusion t)))))) (setq t1 0) - (loop for l from level to (1- lmax) do - (aset ltimes l 0))))))) + (cl-loop for l from level to (1- lmax) do + (aset ltimes l 0))))))) (setq org-clock-file-total-minutes (aref ltimes 0)))))) (defun org-clock-sum-current-item (&optional tstart) @@ -1816,74 +1904,99 @@ PROPNAME lets you set a custom text property instead of :org-clock-minutes." org-clock-file-total-minutes))) ;;;###autoload -(defun org-clock-display (&optional total-only) +(defun org-clock-display (&optional arg) "Show subtree times in the entire buffer. -If TOTAL-ONLY is non-nil, only show the total time for the entire file -in the echo area. -Use \\[org-clock-remove-overlays] to remove the subtree times." - (interactive) +By default, show the total time for the range defined in +`org-clock-display-default-range'. With `\\[universal-argument]' \ +prefix, show +the total time for today instead. + +With `\\[universal-argument] \\[universal-argument]' prefix, \ +use a custom range, entered at prompt. + +With `\\[universal-argument] \ \\[universal-argument] \ +\\[universal-argument]' prefix, display the total time in the +echo area. + +Use `\\[org-clock-remove-overlays]' to remove the subtree times." + (interactive "P") (org-clock-remove-overlays) - (let (time h m p) - (org-clock-sum) - (unless total-only + (let* ((todayp (equal arg '(4))) + (customp (member arg '((16) today yesterday + thisweek lastweek thismonth + lastmonth thisyear lastyear + untilnow interactive))) + (prop (cond ((not arg) :org-clock-minutes-default) + (todayp :org-clock-minutes-today) + (customp :org-clock-minutes-custom) + (t :org-clock-minutes))) + time h m p) + (cond ((not arg) (org-clock-sum-custom + nil org-clock-display-default-range prop)) + (todayp (org-clock-sum-today)) + (customp (org-clock-sum-custom nil arg)) + (t (org-clock-sum))) + (unless (eq arg '(64)) (save-excursion (goto-char (point-min)) (while (or (and (equal (setq p (point)) (point-min)) - (get-text-property p :org-clock-minutes)) + (get-text-property p prop)) (setq p (next-single-property-change - (point) :org-clock-minutes))) + (point) prop))) (goto-char p) - (when (setq time (get-text-property p :org-clock-minutes)) - (org-clock-put-overlay time (funcall outline-level)))) + (when (setq time (get-text-property p prop)) + (org-clock-put-overlay time))) (setq h (/ org-clock-file-total-minutes 60) m (- org-clock-file-total-minutes (* 60 h))) ;; Arrange to remove the overlays upon next change. (when org-remove-highlights-with-change - (org-add-hook 'before-change-functions 'org-clock-remove-overlays + (add-hook 'before-change-functions 'org-clock-remove-overlays nil 'local)))) - (message (concat "Total file time: " - (org-minutes-to-clocksum-string org-clock-file-total-minutes) - " (%d hours and %d minutes)") h m))) - -(defvar org-clock-overlays nil) -(make-variable-buffer-local 'org-clock-overlays) - -(defun org-clock-put-overlay (time &optional level) + (message (concat (format "Total file time%s: " + (cond (todayp " for today") + (customp " (custom)") + (t ""))) + (org-duration-from-minutes + org-clock-file-total-minutes) + " (%d hours and %d minutes)") + h m))) + +(defvar-local org-clock-overlays nil) + +(defun org-clock-put-overlay (time) "Put an overlays on the current line, displaying TIME. -If LEVEL is given, prefix time with a corresponding number of stars. This creates a new overlay and stores it in `org-clock-overlays', so that it will be easy to remove." - (let* ((l (if level (org-get-valid-level level 0) 0)) - ov tx) + (let (ov tx) (beginning-of-line) - (when (looking-at org-complex-heading-regexp) - (goto-char (match-beginning 4))) + (let ((case-fold-search nil)) + (when (looking-at org-complex-heading-regexp) + (goto-char (match-beginning 4)))) (setq ov (make-overlay (point) (point-at-eol)) - tx (concat (buffer-substring-no-properties (point) (match-end 4)) - (make-string - (max 0 (- (- 60 (current-column)) - (- (match-end 4) (match-beginning 4)) - (length (org-get-at-bol 'line-prefix)))) ?.) - (org-add-props (concat (make-string l ?*) " " - (org-minutes-to-clocksum-string time) - (make-string (- 16 l) ?\ )) - (list 'face 'org-clock-overlay)) + tx (concat (buffer-substring-no-properties (point) (match-end 4)) + (org-add-props + (make-string + (max 0 (- (- 60 (current-column)) + (- (match-end 4) (match-beginning 4)) + (length (org-get-at-bol 'line-prefix)))) + ?\·) + '(face shadow)) + (org-add-props + (format " %9s " (org-duration-from-minutes time)) + '(face org-clock-overlay)) "")) - (if (not (featurep 'xemacs)) - (overlay-put ov 'display tx) - (overlay-put ov 'invisible t) - (overlay-put ov 'end-glyph (make-glyph tx))) + (overlay-put ov 'display tx) (push ov org-clock-overlays))) ;;;###autoload -(defun org-clock-remove-overlays (&optional beg end noremove) +(defun org-clock-remove-overlays (&optional _beg _end noremove) "Remove the occur highlights from the buffer. -BEG and END are ignored. If NOREMOVE is nil, remove this function -from the `before-change-functions' in the current buffer." +If NOREMOVE is nil, remove this function from the +`before-change-functions' in the current buffer." (interactive) (unless org-inhibit-highlight-removal - (mapc 'delete-overlay org-clock-overlays) + (mapc #'delete-overlay org-clock-overlays) (setq org-clock-overlays nil) (unless noremove (remove-hook 'before-change-functions @@ -2020,127 +2133,159 @@ buffer and update it." (defun org-clock-special-range (key &optional time as-strings wstart mstart) "Return two times bordering a special time range. -Key is a symbol specifying the range and can be one of `today', `yesterday', -`thisweek', `lastweek', `thismonth', `lastmonth', `thisyear', `lastyear'. -By default, a week starts Monday 0:00 and ends Sunday 24:00. -The range is determined relative to TIME, which defaults to current time. -The return value is a cons cell with two internal times like the ones -returned by `current time' or `encode-time'. -If AS-STRINGS is non-nil, the returned times will be formatted strings. -If WSTART is non-nil, use this number to specify the starting day of a -week (monday is 1). -If MSTART is non-nil, use this number to specify the starting day of a -month (1 is the first day of the month). -If you can combine both, the month starting day will have priority." - (if (integerp key) (setq key (intern (number-to-string key)))) + +KEY is a symbol specifying the range and can be one of `today', +`yesterday', `thisweek', `lastweek', `thismonth', `lastmonth', +`thisyear', `lastyear' or `untilnow'. If set to `interactive', +user is prompted for range boundaries. It can be a string or an +integer. + +By default, a week starts Monday 0:00 and ends Sunday 24:00. The +range is determined relative to TIME, which defaults to current +time. + +The return value is a list containing two internal times, one for +the beginning of the range and one for its end, like the ones +returned by `current time' or `encode-time' and a string used to +display information. If AS-STRINGS is non-nil, the returned +times will be formatted strings. + +If WSTART is non-nil, use this number to specify the starting day +of a week (monday is 1). If MSTART is non-nil, use this number +to specify the starting day of a month (1 is the first day of the +month). If you can combine both, the month starting day will +have priority." (let* ((tm (decode-time time)) - (s 0) (m (nth 1 tm)) (h (nth 2 tm)) - (d (nth 3 tm)) (month (nth 4 tm)) (y (nth 5 tm)) + (m (nth 1 tm)) + (h (nth 2 tm)) + (d (nth 3 tm)) + (month (nth 4 tm)) + (y (nth 5 tm)) (dow (nth 6 tm)) - (ws (or wstart 1)) - (ms (or mstart 1)) - (skey (symbol-name key)) + (skey (format "%s" key)) (shift 0) - (q (cond ((>= (nth 4 tm) 10) 4) - ((>= (nth 4 tm) 7) 3) - ((>= (nth 4 tm) 4) 2) - ((>= (nth 4 tm) 1) 1))) - s1 m1 h1 d1 month1 y1 diff ts te fm txt w date - interval tmp shiftedy shiftedm shiftedq) + (q (cond ((>= month 10) 4) + ((>= month 7) 3) + ((>= month 4) 2) + (t 1))) + m1 h1 d1 month1 y1 shiftedy shiftedm shiftedq) (cond - ((string-match "^[0-9]+$" skey) - (setq y (string-to-number skey) m 1 d 1 key 'year)) - ((string-match "^\\([0-9]+\\)-\\([0-9]\\{1,2\\}\\)$" skey) + ((string-match "\\`[0-9]+\\'" skey) + (setq y (string-to-number skey) month 1 d 1 key 'year)) + ((string-match "\\`\\([0-9]+\\)-\\([0-9]\\{1,2\\}\\)\\'" skey) (setq y (string-to-number (match-string 1 skey)) month (string-to-number (match-string 2 skey)) - d 1 key 'month)) - ((string-match "^\\([0-9]+\\)-[wW]\\([0-9]\\{1,2\\}\\)$" skey) + d 1 + key 'month)) + ((string-match "\\`\\([0-9]+\\)-[wW]\\([0-9]\\{1,2\\}\\)\\'" skey) (require 'cal-iso) - (setq y (string-to-number (match-string 1 skey)) - w (string-to-number (match-string 2 skey))) - (setq date (calendar-gregorian-from-absolute - (calendar-absolute-from-iso (list w 1 y)))) - (setq d (nth 1 date) month (car date) y (nth 2 date) - dow 1 - key 'week)) - ((string-match "^\\([0-9]+\\)-[qQ]\\([1-4]\\)$" skey) + (let ((date (calendar-gregorian-from-absolute + (calendar-iso-to-absolute + (list (string-to-number (match-string 2 skey)) + 1 + (string-to-number (match-string 1 skey))))))) + (setq d (nth 1 date) + month (car date) + y (nth 2 date) + dow 1 + key 'week))) + ((string-match "\\`\\([0-9]+\\)-[qQ]\\([1-4]\\)\\'" skey) (require 'cal-iso) - (setq y (string-to-number (match-string 1 skey))) (setq q (string-to-number (match-string 2 skey))) - (setq date (calendar-gregorian-from-absolute - (calendar-absolute-from-iso (org-quarter-to-date q y)))) - (setq d (nth 1 date) month (car date) y (nth 2 date) - dow 1 - key 'quarter)) - ((string-match "^\\([0-9]+\\)-\\([0-9]\\{1,2\\}\\)-\\([0-9]\\{1,2\\}\\)$" skey) + (let ((date (calendar-gregorian-from-absolute + (calendar-iso-to-absolute + (org-quarter-to-date + q (string-to-number (match-string 1 skey))))))) + (setq d (nth 1 date) + month (car date) + y (nth 2 date) + dow 1 + key 'quarter))) + ((string-match + "\\`\\([0-9]+\\)-\\([0-9]\\{1,2\\}\\)-\\([0-9]\\{1,2\\}\\)\\'" + skey) (setq y (string-to-number (match-string 1 skey)) month (string-to-number (match-string 2 skey)) d (string-to-number (match-string 3 skey)) key 'day)) - ((string-match "\\([-+][0-9]+\\)$" skey) + ((string-match "\\([-+][0-9]+\\)\\'" skey) (setq shift (string-to-number (match-string 1 skey)) - key (intern (substring skey 0 (match-beginning 1)))) - (if (and (memq key '(quarter thisq)) (> shift 0)) - (error "Looking forward with quarters isn't implemented")))) - + key (intern (substring skey 0 (match-beginning 1)))) + (when (and (memq key '(quarter thisq)) (> shift 0)) + (error "Looking forward with quarters isn't implemented")))) (when (= shift 0) - (cond ((eq key 'yesterday) (setq key 'today shift -1)) - ((eq key 'lastweek) (setq key 'week shift -1)) - ((eq key 'lastmonth) (setq key 'month shift -1)) - ((eq key 'lastyear) (setq key 'year shift -1)) - ((eq key 'lastq) (setq key 'quarter shift -1)))) - (cond - ((memq key '(day today)) - (setq d (+ d shift) h 0 m 0 h1 24 m1 0)) - ((memq key '(week thisweek)) - (setq diff (+ (* -7 shift) (if (= dow 0) (- 7 ws) (- dow ws))) - m 0 h 0 d (- d diff) d1 (+ 7 d))) - ((memq key '(month thismonth)) - (setq d (or ms 1) h 0 m 0 d1 (or ms 1) - month (+ month shift) month1 (1+ month) h1 0 m1 0)) - ((memq key '(quarter thisq)) - ;; Compute if this shift remains in this year. If not, compute - ;; how many years and quarters we have to shift (via floor*) and - ;; compute the shifted years, months and quarters. - (cond - ((< (+ (- q 1) shift) 0) ; shift not in this year - (setq interval (* -1 (+ (- q 1) shift))) - ;; Set tmp to ((years to shift) (quarters to shift)). - (setq tmp (org-floor* interval 4)) - ;; Due to the use of floor, 0 quarters actually means 4. - (if (= 0 (nth 1 tmp)) - (setq shiftedy (- y (nth 0 tmp)) - shiftedm 1 - shiftedq 1) - (setq shiftedy (- y (+ 1 (nth 0 tmp))) - shiftedm (- 13 (* 3 (nth 1 tmp))) - shiftedq (- 5 (nth 1 tmp)))) - (setq d 1 h 0 m 0 d1 1 month shiftedm month1 (+ 3 shiftedm) h1 0 m1 0 y shiftedy)) - ((> (+ q shift) 0) ; shift is within this year - (setq shiftedq (+ q shift)) - (setq shiftedy y) - (setq d 1 h 0 m 0 d1 1 month (+ 1 (* 3 (- (+ q shift) 1))) month1 (+ 4 (* 3 (- (+ q shift) 1))) h1 0 m1 0)))) - ((memq key '(year thisyear)) - (setq m 0 h 0 d 1 month 1 y (+ y shift) y1 (1+ y))) - (t (error "No such time block %s" key))) - (setq ts (encode-time s m h d month y) - te (encode-time (or s1 s) (or m1 m) (or h1 h) - (or d1 d) (or month1 month) (or y1 y))) - (setq fm (cdr org-time-stamp-formats)) - (cond - ((memq key '(day today)) - (setq txt (format-time-string "%A, %B %d, %Y" ts))) - ((memq key '(week thisweek)) - (setq txt (format-time-string "week %G-W%V" ts))) - ((memq key '(month thismonth)) - (setq txt (format-time-string "%B %Y" ts))) - ((memq key '(year thisyear)) - (setq txt (format-time-string "the year %Y" ts))) - ((memq key '(quarter thisq)) - (setq txt (concat (org-count-quarter shiftedq) " quarter of " (number-to-string shiftedy))))) - (if as-strings - (list (format-time-string fm ts) (format-time-string fm te) txt) - (list ts te txt)))) + (pcase key + (`yesterday (setq key 'today shift -1)) + (`lastweek (setq key 'week shift -1)) + (`lastmonth (setq key 'month shift -1)) + (`lastyear (setq key 'year shift -1)) + (`lastq (setq key 'quarter shift -1)))) + ;; Prepare start and end times depending on KEY's type. + (pcase key + ((or `day `today) (setq m 0 h 0 h1 24 d (+ d shift))) + ((or `week `thisweek) + (let* ((ws (or wstart 1)) + (diff (+ (* -7 shift) (if (= dow 0) (- 7 ws) (- dow ws))))) + (setq m 0 h 0 d (- d diff) d1 (+ 7 d)))) + ((or `month `thismonth) + (setq h 0 m 0 d (or mstart 1) month (+ month shift) month1 (1+ month))) + ((or `quarter `thisq) + ;; Compute if this shift remains in this year. If not, compute + ;; how many years and quarters we have to shift (via floor*) and + ;; compute the shifted years, months and quarters. + (cond + ((< (+ (- q 1) shift) 0) ; Shift not in this year. + (let* ((interval (* -1 (+ (- q 1) shift))) + ;; Set tmp to ((years to shift) (quarters to shift)). + (tmp (cl-floor interval 4))) + ;; Due to the use of floor, 0 quarters actually means 4. + (if (= 0 (nth 1 tmp)) + (setq shiftedy (- y (nth 0 tmp)) + shiftedm 1 + shiftedq 1) + (setq shiftedy (- y (+ 1 (nth 0 tmp))) + shiftedm (- 13 (* 3 (nth 1 tmp))) + shiftedq (- 5 (nth 1 tmp))))) + (setq m 0 h 0 d 1 month shiftedm month1 (+ 3 shiftedm) y shiftedy)) + ((> (+ q shift) 0) ; Shift is within this year. + (setq shiftedq (+ q shift)) + (setq shiftedy y) + (let ((qshift (* 3 (1- (+ q shift))))) + (setq m 0 h 0 d 1 month (+ 1 qshift) month1 (+ 4 qshift)))))) + ((or `year `thisyear) + (setq m 0 h 0 d 1 month 1 y (+ y shift) y1 (1+ y))) + ((or `interactive `untilnow)) ; Special cases, ignore them. + (_ (user-error "No such time block %s" key))) + ;; Format start and end times according to AS-STRINGS. + (let* ((start (pcase key + (`interactive (org-read-date nil t nil "Range start? ")) + (`untilnow org-clock--oldest-date) + (_ (encode-time 0 m h d month y)))) + (end (pcase key + (`interactive (org-read-date nil t nil "Range end? ")) + (`untilnow (current-time)) + (_ (encode-time 0 + (or m1 m) + (or h1 h) + (or d1 d) + (or month1 month) + (or y1 y))))) + (text + (pcase key + ((or `day `today) (format-time-string "%A, %B %d, %Y" start)) + ((or `week `thisweek) (format-time-string "week %G-W%V" start)) + ((or `month `thismonth) (format-time-string "%B %Y" start)) + ((or `year `thisyear) (format-time-string "the year %Y" start)) + ((or `quarter `thisq) + (concat (org-count-quarter shiftedq) + " quarter of " (number-to-string shiftedy))) + (`interactive "(Range interactively set)") + (`untilnow "now")))) + (if (not as-strings) (list start end text) + (let ((f (cdr org-time-stamp-formats))) + (list (format-time-string f start) + (format-time-string f end) + text)))))) (defun org-count-quarter (n) (cond @@ -2196,7 +2341,7 @@ the currently selected interval size." ((and wp (string-match "w\\|W" wp) mw (> (length wp) 0)) (require 'cal-iso) (setq date (calendar-gregorian-from-absolute - (calendar-absolute-from-iso (list (+ mw n) 1 y)))) + (calendar-iso-to-absolute (list (+ mw n) 1 y)))) (setq ins (format-time-string "%G-W%V" (encode-time 0 0 0 (nth 1 date) (car date) (nth 2 date))))) @@ -2213,7 +2358,7 @@ the currently selected interval size." y (- y 1)) ()) (setq date (calendar-gregorian-from-absolute - (calendar-absolute-from-iso (org-quarter-to-date (+ mw n) y)))) + (calendar-iso-to-absolute (org-quarter-to-date (+ mw n) y)))) (setq ins (format-time-string (concat (number-to-string y) "-Q" (number-to-string (+ mw n))) (encode-time 0 0 0 (nth 1 date) (car date) (nth 2 date))))) @@ -2238,25 +2383,33 @@ the currently selected interval size." (setq params (org-combine-plists org-clocktable-defaults params)) (catch 'exit (let* ((scope (plist-get params :scope)) + (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)))) + ((pred functionp) (funcall scope)) + ((pred consp) scope) + (_ (or (buffer-file-name) (current-buffer))))) (block (plist-get params :block)) (ts (plist-get params :tstart)) (te (plist-get params :tend)) - (link (plist-get params :link)) - (maxlevel (or (plist-get params :maxlevel) 3)) (ws (plist-get params :wstart)) (ms (plist-get params :mstart)) (step (plist-get params :step)) - (timestamp (plist-get params :timestamp)) (formatter (or (plist-get params :formatter) org-clock-clocktable-formatter 'org-clocktable-write-default)) - cc range-text ipos pos one-file-with-archives - scope-is-list tbls level) + cc) ;; Check if we need to do steps (when block ;; Get the range text for the header (setq cc (org-clock-special-range block nil t ws ms) - ts (car cc) te (nth 1 cc) range-text (nth 2 cc))) + ts (car cc) + te (nth 1 cc))) (when step ;; Write many tables, in steps (unless (or block (and ts te)) @@ -2264,63 +2417,49 @@ the currently selected interval size." (org-clocktable-steps params) (throw 'exit nil)) - (setq ipos (point)) ; remember the insertion position - - ;; Get the right scope - (setq pos (point)) - (cond - ((and scope (listp scope) (symbolp (car scope))) - (setq scope (eval scope))) - ((eq scope 'agenda) - (setq scope (org-agenda-files t))) - ((eq scope 'agenda-with-archives) - (setq scope (org-agenda-files t)) - (setq scope (org-add-archive-files scope))) - ((eq scope 'file-with-archives) - (setq scope (org-add-archive-files (list (buffer-file-name))) - one-file-with-archives t))) - (setq scope-is-list (and scope (listp scope))) - (if scope-is-list - ;; we collect from several files - (let* ((files scope) - file) - (org-agenda-prepare-buffers files) - (while (setq file (pop files)) - (with-current-buffer (find-buffer-visiting file) - (save-excursion - (save-restriction - (push (org-clock-get-table-data file params) tbls)))))) - ;; Just from the current file - (save-restriction - ;; get the right range into the restriction - (org-agenda-prepare-buffers (list (buffer-file-name))) - (cond - ((not scope)) ; use the restriction as it is now - ((eq scope 'file) (widen)) - ((eq scope 'subtree) (org-narrow-to-subtree)) - ((eq scope 'tree) - (while (org-up-heading-safe)) - (org-narrow-to-subtree)) - ((and (symbolp scope) (string-match "^tree\\([0-9]+\\)$" - (symbol-name scope))) - (setq level (string-to-number (match-string 1 (symbol-name scope)))) - (catch 'exit - (while (org-up-heading-safe) - (looking-at org-outline-regexp) - (if (<= (org-reduced-level (funcall outline-level)) level) - (throw 'exit nil)))) - (org-narrow-to-subtree))) - ;; do the table, with no file name. - (push (org-clock-get-table-data nil params) tbls))) - - ;; OK, at this point we tbls as a list of tables, one per file - (setq tbls (nreverse tbls)) - - (setq params (plist-put params :multifile scope-is-list)) - (setq params (plist-put params :one-file-with-archives - one-file-with-archives)) - - (funcall formatter ipos tbls params)))) + (org-agenda-prepare-buffers (if (consp files) files (list files))) + + (let ((origin (point)) + (tables + (if (consp files) + (mapcar (lambda (file) + (with-current-buffer (find-buffer-visiting file) + (save-excursion + (save-restriction + (org-clock-get-table-data file params))))) + files) + ;; Get the right restriction for the scope. + (save-restriction + (cond + ((not scope)) ;use the restriction as it is now + ((eq scope 'file) (widen)) + ((eq scope 'subtree) (org-narrow-to-subtree)) + ((eq scope 'tree) + (while (org-up-heading-safe)) + (org-narrow-to-subtree)) + ((and (symbolp scope) + (string-match "\\`tree\\([0-9]+\\)\\'" + (symbol-name scope))) + (let ((level (string-to-number + (match-string 1 (symbol-name scope))))) + (catch 'exit + (while (org-up-heading-safe) + (looking-at org-outline-regexp) + (when (<= (org-reduced-level (funcall outline-level)) + level) + (throw 'exit nil)))) + (org-narrow-to-subtree)))) + (list (org-clock-get-table-data nil params))))) + (multifile + ;; Even though `file-with-archives' can consist of + ;; multiple files, we consider this is one extended file + ;; instead. + (and (consp files) (not (eq scope 'file-with-archives))))) + + (funcall formatter + origin + tables + (org-combine-plists params `(:multifile ,multifile))))))) (defun org-clocktable-write-default (ipos tables params) "Write out a clock table at position IPOS in the current buffer. @@ -2333,237 +2472,224 @@ from the dynamic block definition." ;; someone wants to write their own special formatter, this maybe ;; much easier because there can be a fixed format with a ;; well-defined number of columns... - (let* ((hlchars '((1 . "*") (2 . "/"))) - (lwords (assoc (or (plist-get params :lang) - (org-bound-and-true-p org-export-default-language) - "en") - org-clock-clocktable-language-setup)) + (let* ((lang (or (plist-get params :lang) "en")) (multifile (plist-get params :multifile)) (block (plist-get params :block)) - (ts (plist-get params :tstart)) - (te (plist-get params :tend)) - (header (plist-get params :header)) - (narrow (plist-get params :narrow)) - (ws (or (plist-get params :wstart) 1)) - (ms (or (plist-get params :mstart) 1)) + (sort (plist-get params :sort)) + (header (plist-get params :header)) (link (plist-get params :link)) (maxlevel (or (plist-get params :maxlevel) 3)) (emph (plist-get params :emphasize)) - (level-p (plist-get params :level)) - (org-time-clocksum-use-effort-durations - (plist-get params :effort-durations)) + (compact? (plist-get params :compact)) + (narrow (or (plist-get params :narrow) (and compact? '40!))) + (level? (and (not compact?) (plist-get params :level))) (timestamp (plist-get params :timestamp)) (properties (plist-get params :properties)) - (ntcol (max 1 (or (plist-get params :tcolumns) 100))) - (rm-file-column (plist-get params :one-file-with-archives)) - (indent (plist-get params :indent)) + (time-columns + (if (or compact? (< maxlevel 2)) 1 + ;; Deepest headline level is a hard limit for the number + ;; of time columns. + (let ((levels + (cl-mapcan + (lambda (table) + (pcase table + (`(,_ ,(and (pred wholenump) (pred (/= 0))) ,entries) + (mapcar #'car entries)))) + tables))) + (min maxlevel + (or (plist-get params :tcolumns) 100) + (if (null levels) 1 (apply #'max levels)))))) + (indent (or compact? (plist-get params :indent))) + (formula (plist-get params :formula)) (case-fold-search t) - range-text total-time tbl level hlc formula pcol - file-time entries entry headline - recalc content narrow-cut-p tcol) - - ;; Implement abbreviations - (when (plist-get params :compact) - (setq level nil indent t narrow (or narrow '40!) ntcol 1)) - - ;; Some consistency test for parameters - (unless (integerp ntcol) - (setq params (plist-put params :tcolumns (setq ntcol 100)))) + (total-time (apply #'+ (mapcar #'cadr tables))) + recalc narrow-cut-p) (when (and narrow (integerp narrow) link) - ;; We cannot have both integer narrow and link - (message - "Using hard narrowing in clocktable to allow for links") + ;; We cannot have both integer narrow and link. + (message "Using hard narrowing in clocktable to allow for links") (setq narrow (intern (format "%d!" narrow)))) - (when narrow - (cond - ((integerp narrow)) - ((and (symbolp narrow) - (string-match "\\`[0-9]+!\\'" (symbol-name narrow))) - (setq narrow-cut-p t - narrow (string-to-number (substring (symbol-name narrow) - 0 -1)))) - (t - (error "Invalid value %s of :narrow property in clock table" - narrow)))) - - (when block - ;; Get the range text for the header - (setq range-text (nth 2 (org-clock-special-range block nil t ws ms)))) - - ;; Compute the total time - (setq total-time (apply '+ (mapcar 'cadr tables))) + (pcase narrow + ((or `nil (pred integerp)) nil) ;nothing to do + ((and (pred symbolp) + (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))) - ;; Now we need to output this tsuff + ;; Now we need to output this table stuff. (goto-char ipos) - ;; Insert the text *before* the actual table + ;; Insert the text *before* the actual table. (insert-before-markers (or header - ;; Format the standard header - (concat - "#+CAPTION: " - (nth 9 lwords) " [" - (substring - (format-time-string (cdr org-time-stamp-formats)) - 1 -1) - "]" - (if block (concat ", for " range-text ".") "") - "\n"))) + ;; Format the standard header. + (format "#+CAPTION: %s %s%s\n" + (org-clock--translate "Clock summary at" lang) + (format-time-string (org-time-stamp-format t t)) + (if block + (let ((range-text + (nth 2 (org-clock-special-range + block nil t + (plist-get params :wstart) + (plist-get params :mstart))))) + (format ", for %s." range-text)) + "")))) ;; Insert the narrowing line (when (and narrow (integerp narrow) (not narrow-cut-p)) (insert-before-markers - "|" ; table line starter - (if multifile "|" "") ; file column, maybe - (if level-p "|" "") ; level column, maybe - (if timestamp "|" "") ; timestamp column, maybe - (if properties (make-string (length properties) ?|) "") ;properties columns, maybe - (format "<%d>| |\n" narrow))) ; headline and time columns + "|" ;table line starter + (if multifile "|" "") ;file column, maybe + (if level? "|" "") ;level column, maybe + (if timestamp "|" "") ;timestamp column, maybe + (if properties ;properties columns, maybe + (make-string (length properties) ?|) + "") + (format "<%d>| |\n" narrow))) ;headline and time columns ;; Insert the table header line (insert-before-markers - "|" ; table line starter - (if multifile (concat (nth 1 lwords) "|") "") ; file column, maybe - (if level-p (concat (nth 2 lwords) "|") "") ; level column, maybe - (if timestamp (concat (nth 3 lwords) "|") "") ; timestamp column, maybe - (if properties (concat (mapconcat 'identity properties "|") "|") "") ;properties columns, maybe - (concat (nth 4 lwords) "|" - (nth 5 lwords) "|\n")) ; headline and time columns + "|" ;table line starter + (if multifile ;file column, maybe + (concat (org-clock--translate "File" lang) "|") + "") + (if level? ;level column, maybe + (concat (org-clock--translate "L" lang) "|") + "") + (if timestamp ;timestamp column, maybe + (concat (org-clock--translate "Timestamp" lang) "|") + "") + (if properties ;properties columns, maybe + (concat (mapconcat #'identity properties "|") "|") + "") + (concat (org-clock--translate "Headline" lang)"|") + (concat (org-clock--translate "Time" lang) "|") + (make-string (max 0 (1- time-columns)) ?|) ;other time columns + (if (eq formula '%) "%|\n" "\n")) ;; Insert the total time in the table (insert-before-markers - "|-\n" ; a hline - "|" ; table line starter - (if multifile (concat "| " (nth 6 lwords) " ") "") - ; file column, maybe - (if level-p "|" "") ; level column, maybe - (if timestamp "|" "") ; timestamp column, maybe - (if properties (make-string (length properties) ?|) "") ; properties columns, maybe - (concat (format org-clock-total-time-cell-format (nth 7 lwords)) "| ") ; instead of a headline + "|-\n" ;a hline + "|" ;table line starter + (if multifile (format "| %s " (org-clock--translate "ALL" lang)) "") + ;file column, maybe + (if level? "|" "") ;level column, maybe + (if timestamp "|" "") ;timestamp column, maybe + (make-string (length properties) ?|) ;properties columns, maybe + (concat (format org-clock-total-time-cell-format + (org-clock--translate "Total time" lang)) + "| ") (format org-clock-total-time-cell-format - (org-minutes-to-clocksum-string (or total-time 0))) ; the time - "|\n") ; close line - - ;; Now iterate over the tables and insert the data - ;; but only if any time has been collected + (org-duration-from-minutes (or total-time 0))) ;time + "|" + (make-string (max 0 (1- time-columns)) ?|) + (cond ((not (eq formula '%)) "") + ((or (not total-time) (= total-time 0)) "0.0|") + (t "100.0|")) + "\n") + + ;; Now iterate over the tables and insert the data but only if any + ;; time has been collected. (when (and total-time (> total-time 0)) - - (while (setq tbl (pop tables)) - ;; now tbl is the table resulting from one file. - (setq file-time (nth 1 tbl)) + (pcase-dolist (`(,file-name ,file-time ,entries) tables) (when (or (and file-time (> file-time 0)) (not (plist-get params :fileskip0))) - (insert-before-markers "|-\n") ; a hline because a new file starts - ;; First the file time, if we have multiple files + (insert-before-markers "|-\n") ;hline at new file + ;; First the file time, if we have multiple files. (when multifile - ;; Summarize the time collected from this file + ;; Summarize the time collected from this file. (insert-before-markers (format (concat "| %s %s | %s%s" - (format org-clock-file-time-cell-format (nth 8 lwords)) + (format org-clock-file-time-cell-format + (org-clock--translate "File time" lang)) " | *%s*|\n") - (file-name-nondirectory (car tbl)) - (if level-p "| " "") ; level column, maybe - (if timestamp "| " "") ; timestamp column, maybe - (if properties (make-string (length properties) ?|) "") ;properties columns, maybe - (org-minutes-to-clocksum-string (nth 1 tbl))))) ; the time + (file-name-nondirectory file-name) + (if level? "| " "") ;level column, maybe + (if timestamp "| " "") ;timestamp column, maybe + (if properties ;properties columns, maybe + (make-string (length properties) ?|) + "") + (org-duration-from-minutes file-time)))) ;time ;; Get the list of node entries and iterate over it - (setq entries (nth 2 tbl)) - (while (setq entry (pop entries)) - (setq level (car entry) - headline (nth 1 entry) - hlc (if emph (or (cdr (assoc level hlchars)) "") "")) - (when narrow-cut-p - (if (and (string-match (concat "\\`" org-bracket-link-regexp - "\\'") - headline) - (match-end 3)) - (setq headline - (format "[[%s][%s]]" - (match-string 1 headline) - (org-shorten-string (match-string 3 headline) - narrow))) - (setq headline (org-shorten-string headline narrow)))) - (insert-before-markers - "|" ; start the table line - (if multifile "|" "") ; free space for file name column? - (if level-p (format "%d|" (car entry)) "") ; level, maybe - (if timestamp (concat (nth 2 entry) "|") "") ; timestamp, maybe - (if properties - (concat - (mapconcat - (lambda (p) (or (cdr (assoc p (nth 4 entry))) "")) - properties "|") "|") "") ;properties columns, maybe - (if indent (org-clocktable-indent-string level) "") ; indentation - hlc headline hlc "|" ; headline - (make-string (min (1- ntcol) (or (- level 1))) ?|) - ; empty fields for higher levels - hlc (org-minutes-to-clocksum-string (nth 3 entry)) hlc ; time - "|\n" ; close line - ))))) - ;; When exporting subtrees or regions the region might be - ;; activated, so let's disable ̀delete-active-region' - (let ((delete-active-region nil)) (backward-delete-char 1)) - (if (setq formula (plist-get params :formula)) - (cond - ((eq formula '%) - ;; compute the column where the % numbers need to go - (setq pcol (+ 2 - (if multifile 1 0) - (if level-p 1 0) - (if timestamp 1 0) - (min maxlevel (or ntcol 100)))) - ;; compute the column where the total time is - (setq tcol (+ 2 - (if multifile 1 0) - (if level-p 1 0) - (if timestamp 1 0))) - (insert - (format - "\n#+TBLFM: $%d='(org-clock-time%% @%d$%d $%d..$%d);%%.1f" - pcol ; the column where the % numbers should go - (if (and narrow (not narrow-cut-p)) 3 2) ; row of the total time - tcol ; column of the total time - tcol (1- pcol) ; range of columns where times can be found - )) - (setq recalc t)) - ((stringp formula) - (insert "\n#+TBLFM: " formula) - (setq recalc t)) - (t (error "Invalid formula in clocktable"))) - ;; Should we rescue an old formula? - (when (stringp (setq content (plist-get params :content))) - (when (string-match "^\\([ \t]*#\\+tblfm:.*\\)" content) + (when (> maxlevel 0) + (pcase-dolist (`(,level ,headline ,ts ,time ,props) entries) + (when narrow-cut-p + (setq headline + (if (and (string-match + (format "\\`%s\\'" org-bracket-link-regexp) + headline) + (match-end 3)) + (format "[[%s][%s]]" + (match-string 1 headline) + (org-shorten-string (match-string 3 headline) + narrow)) + (org-shorten-string headline narrow)))) + (cl-flet ((format-field (f) (format (cond ((not emph) "%s |") + ((= level 1) "*%s* |") + ((= level 2) "/%s/ |") + (t "%s |")) + f))) + (insert-before-markers + "|" ;start the table line + (if multifile "|" "") ;free space for file name column? + (if level? (format "%d|" level) "") ;level, maybe + (if timestamp (concat ts "|") "") ;timestamp, maybe + (if properties ;properties columns, maybe + (concat (mapconcat (lambda (p) (or (cdr (assoc p props)) "")) + properties + "|") + "|") + "") + (if indent ;indentation + (org-clocktable-indent-string level) + "") + (format-field headline) + ;; Empty fields for higher levels. + (make-string (max 0 (1- (min time-columns level))) ?|) + (format-field (org-duration-from-minutes time)) + (make-string (max 0 (- time-columns level)) ?|) + (if (eq formula '%) + (format "%.1f |" (* 100 (/ time (float total-time)))) + "") + "\n"))))))) + (delete-char -1) + (cond + ;; Possibly rescue old formula? + ((or (not formula) (eq formula '%)) + (let ((contents (org-string-nw-p (plist-get params :content)))) + (when (and contents (string-match "^\\([ \t]*#\\+tblfm:.*\\)" contents)) (setq recalc t) - (insert "\n" (match-string 1 (plist-get params :content))) + (insert "\n" (match-string 1 contents)) (beginning-of-line 0)))) - ;; Back to beginning, align the table, recalculate if necessary + ;; Insert specified formula line. + ((stringp formula) + (insert "\n#+TBLFM: " formula) + (setq recalc t)) + (t + (user-error "Invalid :formula parameter in clocktable"))) + ;; Back to beginning, align the table, recalculate if necessary. (goto-char ipos) (skip-chars-forward "^|") (org-table-align) (when org-hide-emphasis-markers - ;; we need to align a second time + ;; We need to align a second time. (org-table-align)) - (when recalc - (if (eq formula '%) - (save-excursion - (if (and narrow (not narrow-cut-p)) (beginning-of-line 2)) - (org-table-goto-column pcol nil 'force) - (insert "%"))) - (org-table-recalculate 'all)) - (when rm-file-column - ;; The file column is actually not wanted - (forward-char 1) - (org-table-delete-column)) + (when sort + (save-excursion + (org-table-goto-line 3) + (org-table-goto-column (car sort)) + (org-table-sort-lines nil (cdr sort)))) + (when recalc (org-table-recalculate 'all)) total-time)) (defun org-clocktable-indent-string (level) + "Return indentation string according to LEVEL. +LEVEL is an integer. Indent by two spaces per level above 1." (if (= level 1) "" - (let ((str " ")) - (dotimes (k (1- level) str) - (setq str (concat "\\emsp" str)))))) + (concat "\\_" (make-string (* 2 (1- level)) ?\s)))) (defun org-clocktable-steps (params) "Step through the range to make a number of clock tables." @@ -2576,29 +2702,31 @@ from the dynamic block definition." (step (cdr (assoc step0 '((day . 86400) (week . 604800))))) (stepskip0 (plist-get p1 :stepskip0)) (block (plist-get p1 :block)) - cc range-text step-time tsb) + cc step-time tsb) (when block (setq cc (org-clock-special-range block nil t ws ms) - ts (car cc) te (nth 1 cc) range-text (nth 2 cc))) + ts (car cc) + te (nth 1 cc))) (cond ((numberp ts) - ;; If ts is a number, it's an absolute day number from org-agenda. - (destructuring-bind (month day year) (calendar-gregorian-from-absolute ts) + ;; If ts is a number, it's an absolute day number from + ;; org-agenda. + (pcase-let ((`(,month ,day ,year) (calendar-gregorian-from-absolute ts))) (setq ts (float-time (encode-time 0 0 0 day month year))))) (ts - (setq ts (float-time - (apply 'encode-time (org-parse-time-string ts)))))) + (setq ts (float-time (apply #'encode-time (org-parse-time-string ts)))))) (cond ((numberp te) ;; Likewise for te. - (destructuring-bind (month day year) (calendar-gregorian-from-absolute te) + (pcase-let ((`(,month ,day ,year) (calendar-gregorian-from-absolute te))) (setq te (float-time (encode-time 0 0 0 day month year))))) (te - (setq te (float-time - (apply 'encode-time (org-parse-time-string te)))))) + (setq te (float-time (apply #'encode-time (org-parse-time-string te)))))) (setq tsb (if (eq step0 'week) - (- ts (* 86400 (- (nth 6 (decode-time (seconds-to-time ts))) ws))) + (let ((dow (nth 6 (decode-time (seconds-to-time ts))))) + (if (< dow ws) ts + (- ts (* 86400 (- dow ws))))) ts)) (setq p1 (plist-put p1 :header "")) (setq p1 (plist-put p1 :step nil)) @@ -2608,9 +2736,14 @@ from the dynamic block definition." (setq p1 (plist-put p1 :tstart (format-time-string (org-time-stamp-format nil t) (seconds-to-time (max tsb ts))))) + (cl-incf tsb (let ((dow (nth 6 (decode-time (seconds-to-time tsb))))) + (if (or (eq step0 'day) + (= dow ws)) + step + (* 86400 (- ws dow))))) (setq p1 (plist-put p1 :tend (format-time-string (org-time-stamp-format nil t) - (seconds-to-time (min te (setq tsb (+ tsb step))))))) + (seconds-to-time (min te tsb))))) (insert "\n" (if (eq step0 'day) "Daily report: " "Weekly report starting on: ") (plist-get p1 :tstart) "\n") @@ -2635,19 +2768,22 @@ file time (in minutes) as 1st and 2nd elements. The third element of this list will be a list of headline entries. Each entry has the following structure: - (LEVEL HEADLINE TIMESTAMP TIME) - -LEVEL: The level of the headline, as an integer. This will be - the reduced leve, so 1,2,3,... even if only odd levels - are being used. -HEADLINE: The text of the headline. Depending on PARAMS, this may - already be formatted like a link. -TIMESTAMP: If PARAMS require it, this will be a time stamp found in the - entry, any of SCHEDULED, DEADLINE, NORMAL, or first inactive, - in this sequence. -TIME: The sum of all time spend in this tree, in minutes. This time - will of cause be restricted to the time block and tags match - specified in PARAMS." + (LEVEL HEADLINE TIMESTAMP TIME PROPERTIES) + +LEVEL: The level of the headline, as an integer. This will be + the reduced level, so 1,2,3,... even if only odd levels + are being used. +HEADLINE: The text of the headline. Depending on PARAMS, this may + already be formatted like a link. +TIMESTAMP: If PARAMS require it, this will be a time stamp found in the + entry, any of SCHEDULED, DEADLINE, NORMAL, or first inactive, + in this sequence. +TIME: The sum of all time spend in this tree, in minutes. This time + will of cause be restricted to the time block and tags match + specified in PARAMS. +PROPERTIES: The list properties specified in the `:properties' parameter + along with their value, as an alist following the pattern + (NAME . VALUE)." (let* ((maxlevel (or (plist-get params :maxlevel) 3)) (timestamp (plist-get params :timestamp)) (ts (plist-get params :tstart)) @@ -2659,14 +2795,14 @@ TIME: The sum of all time spend in this tree, in minutes. This time (tags (plist-get params :tags)) (properties (plist-get params :properties)) (inherit-property-p (plist-get params :inherit-props)) - todo-only - (matcher (if tags (cdr (org-make-tags-matcher tags)))) - cc range-text st p time level hdl props tsp tbl) + (matcher (and tags (cdr (org-make-tags-matcher tags)))) + cc st p tbl) (setq org-clock-file-total-minutes nil) (when block (setq cc (org-clock-special-range block nil t ws ms) - ts (car cc) te (nth 1 cc) range-text (nth 2 cc))) + ts (car cc) + te (nth 1 cc))) (when (integerp ts) (setq ts (calendar-gregorian-from-absolute ts))) (when (integerp te) (setq te (calendar-gregorian-from-absolute te))) (when (and ts (listp ts)) @@ -2678,12 +2814,12 @@ TIME: The sum of all time spend in this tree, in minutes. This time (if te (setq te (org-matcher-time te))) (save-excursion (org-clock-sum ts te - (unless (null matcher) - (lambda () - (let* ((tags-list (org-get-tags-at)) - (org-scanner-tags tags-list) - (org-trust-scanner-tags t)) - (eval matcher))))) + (when matcher + `(lambda () + (let* ((tags-list (org-get-tags-at)) + (org-scanner-tags tags-list) + (org-trust-scanner-tags t)) + (funcall ,matcher nil tags-list nil))))) (goto-char (point-min)) (setq st t) (while (or (and (bobp) (prog1 st (setq st nil)) @@ -2692,66 +2828,42 @@ TIME: The sum of all time spend in this tree, in minutes. This time (setq p (next-single-property-change (point) :org-clock-minutes))) (goto-char p) - (when (setq time (get-text-property p :org-clock-minutes)) - (save-excursion - (beginning-of-line 1) - (when (and (looking-at (org-re "\\(\\*+\\)[ \t]+\\(.*?\\)\\([ \t]+:[[:alnum:]_@#%:]+:\\)?[ \t]*$")) - (setq level (org-reduced-level - (- (match-end 1) (match-beginning 1)))) - (<= level maxlevel)) - (setq hdl (if (not link) - (match-string 2) - (org-make-link-string - (format "file:%s::%s" - (buffer-file-name) - (save-match-data - (match-string 2))) - (org-make-org-heading-search-string - (replace-regexp-in-string - org-bracket-link-regexp - (lambda (m) (or (match-string 3 m) - (match-string 1 m))) - (match-string 2))))) - tsp (when timestamp - (setq props (org-entry-properties (point))) - (or (cdr (assoc "SCHEDULED" props)) - (cdr (assoc "DEADLINE" props)) - (cdr (assoc "TIMESTAMP" props)) - (cdr (assoc "TIMESTAMP_IA" props)))) - props (when properties - (remove nil - (mapcar - (lambda (p) - (when (org-entry-get (point) p inherit-property-p) - (cons p (org-entry-get (point) p inherit-property-p)))) - properties)))) - (when (> time 0) (push (list level hdl tsp time props) tbl)))))) - (setq tbl (nreverse tbl)) - (list file org-clock-file-total-minutes tbl)))) - -(defun org-clock-time% (total &rest strings) - "Compute a time fraction in percent. -TOTAL s a time string like 10:21 specifying the total times. -STRINGS is a list of strings that should be checked for a time. -The first string that does have a time will be used. -This function is made for clock tables." - (let ((re "\\([0-9]+\\):\\([0-9]+\\)") - tot s) - (save-match-data - (catch 'exit - (if (not (string-match re total)) - (throw 'exit 0.) - (setq tot (+ (string-to-number (match-string 2 total)) - (* 60 (string-to-number (match-string 1 total))))) - (if (= tot 0.) (throw 'exit 0.))) - (while (setq s (pop strings)) - (if (string-match "\\([0-9]+\\):\\([0-9]+\\)" s) - (throw 'exit - (/ (* 100.0 (+ (string-to-number (match-string 2 s)) - (* 60 (string-to-number - (match-string 1 s))))) - tot)))) - 0)))) + (let ((time (get-text-property p :org-clock-minutes))) + (when (and time (> time 0) (org-at-heading-p)) + (let ((level (org-reduced-level (org-current-level)))) + (when (<= level maxlevel) + (let* ((headline (org-get-heading t t t t)) + (hdl + (if (not link) headline + (let ((search + (org-make-org-heading-search-string headline))) + (org-make-link-string + (if (not (buffer-file-name)) search + (format "file:%s::%s" (buffer-file-name) search)) + ;; Prune statistics cookies. Replace + ;; links with their description, or + ;; a plain link if there is none. + (org-trim + (org-link-display-format + (replace-regexp-in-string + "\\[[0-9]+%\\]\\|\\[[0-9]+/[0-9]+\\]" "" + headline))))))) + (tsp + (and timestamp + (cl-some (lambda (p) (org-entry-get (point) p)) + '("SCHEDULED" "DEADLINE" "TIMESTAMP" + "TIMESTAMP_IA")))) + (props + (and properties + (delq nil + (mapcar + (lambda (p) + (let ((v (org-entry-get + (point) p inherit-property-p))) + (and v (cons p v)))) + properties))))) + (push (list level hdl tsp time props) tbl))))))) + (list file org-clock-file-total-minutes (nreverse tbl))))) ;; Saving and loading the clock @@ -2789,9 +2901,9 @@ Otherwise, return nil." (setq ts (match-string 1) te (match-string 3)) (setq s (- (float-time - (apply 'encode-time (org-parse-time-string te))) + (apply #'encode-time (org-parse-time-string te))) (float-time - (apply 'encode-time (org-parse-time-string ts)))) + (apply #'encode-time (org-parse-time-string ts)))) neg (< s 0) s (abs s) h (floor (/ s 3600)) @@ -2809,86 +2921,67 @@ The details of what will be saved are regulated by the variable (or org-clock-loaded org-clock-has-been-used (not (file-exists-p org-clock-persist-file)))) - (let (b) - (with-current-buffer (find-file (expand-file-name org-clock-persist-file)) - (progn - (delete-region (point-min) (point-max)) - ;;Store clock - (insert (format ";; org-persist.el - %s at %s\n" - (system-name) (format-time-string - (cdr org-time-stamp-formats)))) - (if (and (memq org-clock-persist '(t clock)) - (setq b (org-clocking-buffer)) - (setq b (or (buffer-base-buffer b) b)) - (buffer-live-p b) - (buffer-file-name b) - (or (not org-clock-persist-query-save) - (y-or-n-p (concat "Save current clock (" - org-clock-heading ") ")))) - (insert "(setq resume-clock '(\"" - (buffer-file-name (org-clocking-buffer)) - "\" . " (int-to-string (marker-position org-clock-marker)) - "))\n")) - ;; Store clocked task history. Tasks are stored reversed to make - ;; reading simpler - (when (and (memq org-clock-persist '(t history)) - org-clock-history) - (insert - "(setq stored-clock-history '(" - (mapconcat - (lambda (m) - (when (and (setq b (marker-buffer m)) - (setq b (or (buffer-base-buffer b) b)) - (buffer-live-p b) - (buffer-file-name b)) - (concat "(\"" (buffer-file-name b) - "\" . " (int-to-string (marker-position m)) - ")"))) - (reverse org-clock-history) " ") "))\n")) - (save-buffer) - (kill-buffer (current-buffer))))))) + (with-temp-file org-clock-persist-file + (insert (format ";; %s - %s at %s\n" + (file-name-nondirectory org-clock-persist-file) + (system-name) + (format-time-string (org-time-stamp-format t)))) + ;; Store clock to be resumed. + (when (and (memq org-clock-persist '(t clock)) + (let ((b (org-base-buffer (org-clocking-buffer)))) + (and (buffer-live-p b) + (buffer-file-name b) + (or (not org-clock-persist-query-save) + (y-or-n-p (format "Save current clock (%s) " + org-clock-heading)))))) + (insert + (format "(setq org-clock-stored-resume-clock '(%S . %d))\n" + (buffer-file-name (org-base-buffer (org-clocking-buffer))) + (marker-position org-clock-marker)))) + ;; Store clocked task history. Tasks are stored reversed to + ;; make reading simpler. + (when (and (memq org-clock-persist '(t history)) + org-clock-history) + (insert + (format "(setq org-clock-stored-history '(%s))\n" + (mapconcat + (lambda (m) + (let ((b (org-base-buffer (marker-buffer m)))) + (when (and (buffer-live-p b) + (buffer-file-name b)) + (format "(%S . %d)" + (buffer-file-name b) + (marker-position m))))) + (reverse org-clock-history) + " "))))))) (defun org-clock-load () "Load clock-related data from disk, maybe resuming a stored clock." (when (and org-clock-persist (not org-clock-loaded)) - (let ((filename (expand-file-name org-clock-persist-file)) - (org-clock-in-resume 'auto-restart) - resume-clock stored-clock-history) - (if (not (file-readable-p filename)) - (message "Not restoring clock data; %s not found" - org-clock-persist-file) - (message "%s" "Restoring clock data") - (setq org-clock-loaded t) - (load-file filename) - ;; load history - (when stored-clock-history - (save-window-excursion - (mapc (lambda (task) - (if (file-exists-p (car task)) - (org-clock-history-push (cdr task) - (find-file (car task))))) - stored-clock-history))) - ;; resume clock - (when (and resume-clock org-clock-persist - (file-exists-p (car resume-clock)) - (or (not org-clock-persist-query-resume) - (y-or-n-p - (concat - "Resume clock (" - (with-current-buffer (find-file (car resume-clock)) - (save-excursion - (goto-char (cdr resume-clock)) - (org-back-to-heading t) - (and (looking-at org-complex-heading-regexp) - (match-string 4)))) - ") ")))) - (when (file-exists-p (car resume-clock)) - (with-current-buffer (find-file (car resume-clock)) - (goto-char (cdr resume-clock)) - (let ((org-clock-auto-clock-resolution nil)) - (org-clock-in) - (if (outline-invisible-p) - (org-show-context)))))))))) + (if (not (file-readable-p org-clock-persist-file)) + (message "Not restoring clock data; %S not found" org-clock-persist-file) + (message "Restoring clock data") + ;; Load history. + (load-file org-clock-persist-file) + (setq org-clock-loaded t) + (pcase-dolist (`(,(and file (pred file-exists-p)) . ,position) + org-clock-stored-history) + (org-clock-history-push position (find-file-noselect file))) + ;; Resume clock. + (pcase org-clock-stored-resume-clock + (`(,(and file (pred file-exists-p)) . ,position) + (with-current-buffer (find-file-noselect file) + (when (or (not org-clock-persist-query-resume) + (y-or-n-p (format "Resume clock (%s) " + (save-excursion + (goto-char position) + (org-get-heading t t))))) + (goto-char position) + (let ((org-clock-in-resume 'auto-restart) + (org-clock-auto-clock-resolution nil)) + (org-clock-in) + (when (org-invisible-p) (org-show-context)))))) + (_ nil))))) ;; Suggested bindings (org-defkey org-mode-map "\C-c\C-x\C-e" 'org-clock-modify-effort-estimate) @@ -2897,6 +2990,7 @@ The details of what will be saved are regulated by the variable ;; Local variables: ;; generated-autoload-file: "org-loaddefs.el" +;; coding: utf-8 ;; End: ;;; org-clock.el ends here diff --git a/lisp/org/org-colview.el b/lisp/org/org-colview.el index a2046af29ec..649ca52c4f8 100644 --- a/lisp/org/org-colview.el +++ b/lisp/org/org-colview.el @@ -1,4 +1,4 @@ -;;; org-colview.el --- Column View in Org-mode +;;; org-colview.el --- Column View in Org -*- lexical-binding: t; -*- ;; Copyright (C) 2004-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Commentary: @@ -28,42 +28,117 @@ ;;; Code: -(eval-when-compile (require 'cl)) +(require 'cl-lib) (require 'org) (declare-function org-agenda-redo "org-agenda" (&optional all)) (declare-function org-agenda-do-context-action "org-agenda" ()) (declare-function org-clock-sum-today "org-clock" (&optional headline-filter)) - -(when (featurep 'xemacs) - (error "Do not load this file into XEmacs, use `org-colview-xemacs.el' from the contrib/ directory")) - +(declare-function org-element-extract-element "org-element" (element)) +(declare-function org-element-interpret-data "org-element" (data)) +(declare-function org-element-map "org-element" (data types fun &optional info first-match no-recursion with-affiliated)) +(declare-function org-element-parse-secondary-string "org-element" (string restriction &optional parent)) +(declare-function org-element-property "org-element" (property element)) +(declare-function org-element-restriction "org-element" (element)) +(declare-function org-element-type "org-element" (element)) + +(defvar org-agenda-columns-add-appointments-to-effort-sum) +(defvar org-agenda-columns-compute-summary-properties) +(defvar org-agenda-columns-show-summaries) +(defvar org-agenda-view-columns-initially) +(defvar org-inlinetask-min-level) + + +;;; Configuration + +(defcustom org-columns-modify-value-for-display-function nil + "Function that modifies values for display in column view. +For example, it can be used to cut out a certain part from a time stamp. +The function must take 2 arguments: + +column-title The title of the column (*not* the property name) +value The value that should be modified. + +The function should return the value that should be displayed, +or nil if the normal value should be used." + :group 'org-properties + :type '(choice (const nil) (function))) + +(defcustom org-columns-summary-types nil + "Alist between operators and summarize functions. + +Each association follows the pattern (LABEL . SUMMARIZE) where + + LABEL is a string used in #+COLUMNS definition describing the + summary type. It can contain any character but \"}\". It is + case-sensitive. + + SUMMARIZE is a function called with two arguments. The first + argument is a non-empty list of values, as non-empty strings. + The second one is a format string or nil. It has to return + a string summarizing the list of values. + +Note that the return value can become one value for an higher +order summary, so the function is expected to handle its own +output. + +Types defined in this variable take precedence over those defined +in `org-columns-summary-types-default', which see." + :group 'org-properties + :version "26.1" + :package-version '(Org . "9.0") + :type '(alist :key-type (string :tag " Label") + :value-type (function :tag "Summarize"))) + + + ;;; Column View -(defvar org-columns-overlays nil +(defvar-local org-columns-overlays nil "Holds the list of current column overlays.") -(defvar org-columns-current-fmt nil +(defvar-local org-columns-current-fmt nil "Local variable, holds the currently active column format.") -(make-variable-buffer-local 'org-columns-current-fmt) -(defvar org-columns-current-fmt-compiled nil + +(defvar-local org-columns-current-fmt-compiled nil "Local variable, holds the currently active column format. This is the compiled version of the format.") -(make-variable-buffer-local 'org-columns-current-fmt-compiled) -(defvar org-columns-current-widths nil - "Loval variable, holds the currently widths of fields.") -(make-variable-buffer-local 'org-columns-current-widths) -(defvar org-columns-current-maxwidths nil - "Loval variable, holds the currently active maximum column widths.") -(make-variable-buffer-local 'org-columns-current-maxwidths) -(defvar org-columns-begin-marker (make-marker) + +(defvar-local org-columns-current-maxwidths nil + "Currently active maximum column widths, as a vector.") + +(defvar-local org-columns-begin-marker nil "Points to the position where last a column creation command was called.") -(defvar org-columns-top-level-marker (make-marker) + +(defvar-local org-columns-top-level-marker nil "Points to the position where current columns region starts.") +(defvar org-columns--time 0.0 + "Number of seconds since the epoch, as a floating point number.") + (defvar org-columns-map (make-sparse-keymap) "The keymap valid in column display.") +(defconst org-columns-summary-types-default + '(("+" . org-columns--summary-sum) + ("$" . org-columns--summary-currencies) + ("X" . org-columns--summary-checkbox) + ("X/" . org-columns--summary-checkbox-count) + ("X%" . org-columns--summary-checkbox-percent) + ("max" . org-columns--summary-max) + ("mean" . org-columns--summary-mean) + ("min" . org-columns--summary-min) + (":" . org-columns--summary-sum-times) + (":max" . org-columns--summary-max-time) + (":mean" . org-columns--summary-mean-time) + (":min" . org-columns--summary-min-time) + ("@max" . org-columns--summary-max-age) + ("@mean" . org-columns--summary-mean-age) + ("@min" . org-columns--summary-min-age) + ("est+" . org-columns--summary-estimate)) + "Map operators to summarize functions. +See `org-columns-summary-types' for details.") + (defun org-columns-content () "Switch to contents view while in columns view." (interactive) @@ -146,121 +221,181 @@ This is the compiled version of the format.") "--" ["Quit" org-columns-quit t])) -(defun org-columns-new-overlay (beg end &optional string face) +(defun org-columns--displayed-value (spec value) + "Return displayed value for specification SPEC in current entry. +SPEC is a column format specification as stored in +`org-columns-current-fmt-compiled'. VALUE is the real value to +display, as a string." + (or (and (functionp org-columns-modify-value-for-display-function) + (funcall org-columns-modify-value-for-display-function + (nth 1 spec) ;column name + value)) + (pcase spec + (`("ITEM" . ,_) + (concat (make-string (1- (org-current-level)) + (if org-hide-leading-stars ?\s ?*)) + "* " + (org-columns-compact-links value))) + (`(,_ ,_ ,_ ,_ nil) value) + ;; If PRINTF is set, assume we are displaying a number and + ;; obey to the format string. + (`(,_ ,_ ,_ ,_ ,printf) (format printf (string-to-number value))) + (_ (error "Invalid column specification format: %S" spec))))) + +(defun org-columns--collect-values (&optional compiled-fmt) + "Collect values for columns on the current line. + +Return a list of triplets (SPEC VALUE DISPLAYED) suitable for +`org-columns--display-here'. + +This function assumes `org-columns-current-fmt-compiled' is +initialized is set in the current buffer. However, it is +possible to override it with optional argument COMPILED-FMT." + (let ((summaries (get-text-property (point) 'org-summaries))) + (mapcar + (lambda (spec) + (pcase spec + (`(,p . ,_) + (let* ((v (or (cdr (assoc spec summaries)) + (org-entry-get (point) p 'selective t) + (and compiled-fmt ;assume `org-agenda-columns' + ;; Effort property is not defined. Try + ;; to use appointment duration. + org-agenda-columns-add-appointments-to-effort-sum + (string= p (upcase org-effort-property)) + (get-text-property (point) 'duration) + (propertize (org-duration-from-minutes + (get-text-property (point) 'duration)) + 'face 'org-warning)) + ""))) + (list spec v (org-columns--displayed-value spec v)))))) + (or compiled-fmt org-columns-current-fmt-compiled)))) + +(defun org-columns--set-widths (cache) + "Compute the maximum column widths from the format and CACHE. +This function sets `org-columns-current-maxwidths' as a vector of +integers greater than 0." + (setq org-columns-current-maxwidths + (apply #'vector + (mapcar + (lambda (spec) + (pcase spec + (`(,_ ,_ ,(and width (pred wholenump)) . ,_) width) + (`(,_ ,name . ,_) + ;; No width is specified in the columns format. + ;; Compute it by checking all possible values for + ;; PROPERTY. + (let ((width (length name))) + (dolist (entry cache width) + (let ((value (nth 2 (assoc spec (cdr entry))))) + (setq width (max (length value) width)))))))) + org-columns-current-fmt-compiled)))) + +(defun org-columns--new-overlay (beg end &optional string face) "Create a new column overlay and add it to the list." (let ((ov (make-overlay beg end))) (overlay-put ov 'face (or face 'secondary-selection)) - (remove-text-properties 0 (length string) '(face nil) string) (org-overlay-display ov string face) (push ov org-columns-overlays) ov)) -(defun org-columns-display-here (&optional props dateline) - "Overlay the current line with column display." - (interactive) - (let* ((fmt org-columns-current-fmt-compiled) - (beg (point-at-bol)) - (level-face (save-excursion - (beginning-of-line 1) - (and (looking-at "\\(\\**\\)\\(\\* \\)") - (org-get-level-face 2)))) - (ref-face (or level-face - (and (eq major-mode 'org-agenda-mode) - (get-text-property (point-at-bol) 'face)) - 'default)) - (color (list :foreground (face-attribute ref-face :foreground))) - (font (list :height (face-attribute 'default :height) - :family (face-attribute 'default :family))) - (face (list color font 'org-column ref-face)) - (face1 (list color font 'org-agenda-column-dateline ref-face)) - (cphr (get-text-property (point-at-bol) 'org-complex-heading-regexp)) - pom property ass width f fc string fm ov column val modval s2 title calc) - ;; Check if the entry is in another buffer. - (unless props - (if (eq major-mode 'org-agenda-mode) - (setq pom (or (org-get-at-bol 'org-hd-marker) - (org-get-at-bol 'org-marker)) - props (if pom (org-entry-properties pom) nil)) - (setq props (org-entry-properties nil)))) - ;; Walk the format - (while (setq column (pop fmt)) - (setq property (car column) - title (nth 1 column) - ass (if (equal property "ITEM") - (cons "ITEM" - ;; When in a buffer, get the whole line, - ;; we'll clean it later… - (if (derived-mode-p 'org-mode) - (save-match-data - (org-remove-tabs - (buffer-substring-no-properties - (point-at-bol) (point-at-eol)))) - ;; In agenda, just get the `txt' property - (or (org-get-at-bol 'txt) - (buffer-substring-no-properties - (point) (progn (end-of-line) (point)))))) - (assoc property props)) - width (or (cdr (assoc property org-columns-current-maxwidths)) - (nth 2 column) - (length property)) - f (format "%%-%d.%ds | " width width) - fm (nth 4 column) - fc (nth 5 column) - calc (nth 7 column) - val (or (cdr ass) "") - modval (cond ((and org-columns-modify-value-for-display-function - (functionp - org-columns-modify-value-for-display-function)) - (funcall org-columns-modify-value-for-display-function - title val)) - ((equal property "ITEM") - (org-columns-cleanup-item - val org-columns-current-fmt-compiled - (or org-complex-heading-regexp cphr))) - (fc (org-columns-number-to-string - (org-columns-string-to-number val fm) fm fc)) - ((and calc (functionp calc) - (not (string= val "")) - (not (get-text-property 0 'org-computed val))) - (org-columns-number-to-string - (funcall calc (org-columns-string-to-number - val fm)) fm)))) - (setq s2 (org-columns-add-ellipses (or modval val) width)) - (setq string (format f s2)) - ;; Create the overlay +(defun org-columns--summarize (operator) + "Return summary function associated to string OPERATOR." + (if (not operator) nil + (cdr (or (assoc operator org-columns-summary-types) + (assoc operator org-columns-summary-types-default) + (error "Unknown %S operator" operator))))) + +(defun org-columns--overlay-text (value fmt width property original) + "Return text " + (format fmt + (let ((v (org-columns-add-ellipses value width))) + (pcase property + ("PRIORITY" + (propertize v 'face (org-get-priority-face original))) + ("TAGS" + (if (not org-tags-special-faces-re) + (propertize v 'face 'org-tag) + (replace-regexp-in-string + org-tags-special-faces-re + (lambda (m) (propertize m 'face (org-get-tag-face m))) + v nil nil 1))) + ("TODO" (propertize v 'face (org-get-todo-face original))) + (_ v))))) + +(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'." + (save-excursion + (beginning-of-line) + (let* ((level-face (and (looking-at "\\(\\**\\)\\(\\* \\)") + (org-get-level-face 2))) + (ref-face (or level-face + (and (eq major-mode 'org-agenda-mode) + (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))) + (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 + ;; to be at least as many characters available on the line as + ;; columns to display. + (let ((columns (length org-columns-current-fmt-compiled)) + (chars (- (line-end-position) (line-beginning-position)))) + (when (> columns chars) + (save-excursion + (end-of-line) + (let ((inhibit-read-only t)) + (insert (make-string (- columns chars) ?\s)))))) + ;; Display columns. Create and install the overlay for the + ;; current column on the next character. + (let ((i 0) + (last (1- (length columns)))) + (dolist (column columns) + (pcase column + (`(,spec ,original ,value) + (let* ((property (car spec)) + (width (aref org-columns-current-maxwidths i)) + (fmt (format (if (= i last) "%%-%d.%ds |" + "%%-%d.%ds | ") + width width)) + (ov (org-columns--new-overlay + (point) (1+ (point)) + (org-columns--overlay-text + value fmt width property original) + (if dateline face1 face)))) + (overlay-put ov 'keymap org-columns-map) + (overlay-put ov 'org-columns-key property) + (overlay-put ov 'org-columns-value original) + (overlay-put ov 'org-columns-value-modified value) + (overlay-put ov 'org-columns-format fmt) + (overlay-put ov 'line-prefix "") + (overlay-put ov 'wrap-prefix "") + (forward-char)))) + (cl-incf i))) + ;; Make the rest of the line disappear. + (let ((ov (org-columns--new-overlay (point) (line-end-position)))) + (overlay-put ov 'invisible t) + (overlay-put ov 'keymap org-columns-map) + (overlay-put ov 'line-prefix "") + (overlay-put ov 'wrap-prefix "")) + (let ((ov (make-overlay (1- (line-end-position)) + (line-beginning-position 2)))) + (overlay-put ov 'keymap org-columns-map) + (push ov org-columns-overlays)) (org-with-silent-modifications - (setq ov (org-columns-new-overlay - beg (setq beg (1+ beg)) string (if dateline face1 face))) - (overlay-put ov 'keymap org-columns-map) - (overlay-put ov 'org-columns-key property) - (overlay-put ov 'org-columns-value (cdr ass)) - (overlay-put ov 'org-columns-value-modified modval) - (overlay-put ov 'org-columns-pom pom) - (overlay-put ov 'org-columns-format f) - (overlay-put ov 'line-prefix "") - (overlay-put ov 'wrap-prefix "")) - (if (or (not (char-after beg)) - (equal (char-after beg) ?\n)) - (let ((inhibit-read-only t)) - (save-excursion - (goto-char beg) - (org-unmodified (insert " ")))))) ;; FIXME: add props and remove later? - ;; Make the rest of the line disappear. - (org-unmodified - (setq ov (org-columns-new-overlay beg (point-at-eol))) - (overlay-put ov 'invisible t) - (overlay-put ov 'keymap org-columns-map) - (overlay-put ov 'intangible t) - (overlay-put ov 'line-prefix "") - (overlay-put ov 'wrap-prefix "") - (push ov org-columns-overlays) - (setq ov (make-overlay (1- (point-at-eol)) (1+ (point-at-eol)))) - (overlay-put ov 'keymap org-columns-map) - (push ov org-columns-overlays) - (let ((inhibit-read-only t)) - (put-text-property (max (point-min) (1- (point-at-bol))) - (min (point-max) (1+ (point-at-eol))) - 'read-only "Type `e' to edit property"))))) + (let ((inhibit-read-only t)) + (put-text-property + (line-end-position 0) + (line-beginning-position 2) + 'read-only + (substitute-command-keys + "Type \\<org-columns-map>`\\[org-columns-edit-value]' \ +to edit property"))))))) (defun org-columns-add-ellipses (string width) "Truncate STRING with WIDTH characters, with ellipses." @@ -285,34 +420,27 @@ for the duration of the command.") (defvar header-line-format) (defvar org-columns-previous-hscroll 0) -(defun org-columns-display-here-title () +(defun org-columns--display-here-title () "Overlay the newline before the current line with the table title." (interactive) - (let ((fmt org-columns-current-fmt-compiled) - string (title "") - property width f column str widths) - (while (setq column (pop fmt)) - (setq property (car column) - str (or (nth 1 column) property) - width (or (cdr (assoc property org-columns-current-maxwidths)) - (nth 2 column) - (length str)) - widths (push width widths) - f (format "%%-%d.%ds | " width width) - string (format f str) - title (concat title string))) - (setq title (concat - (org-add-props " " nil 'display '(space :align-to 0)) - ;;(org-add-props title nil 'face '(:weight bold :underline t :inherit default)))) - (org-add-props title nil 'face 'org-column-title))) - (org-set-local 'org-previous-header-line-format header-line-format) - (org-set-local 'org-columns-current-widths (nreverse widths)) - (setq org-columns-full-header-line-format title) + (let ((title "") + (i 0)) + (dolist (column org-columns-current-fmt-compiled) + (pcase column + (`(,property ,name . ,_) + (let* ((width (aref org-columns-current-maxwidths i)) + (fmt (format "%%-%d.%ds | " width width))) + (setq title (concat title (format fmt (or name property))))))) + (cl-incf i)) + (setq-local org-previous-header-line-format header-line-format) + (setq org-columns-full-header-line-format + (concat + (org-add-props " " nil 'display '(space :align-to 0)) + (org-add-props (substring title 0 -1) nil 'face 'org-column-title))) (setq org-columns-previous-hscroll -1) - ; (org-columns-hscoll-title) - (org-add-hook 'post-command-hook 'org-columns-hscoll-title nil 'local))) + (add-hook 'post-command-hook 'org-columns-hscroll-title nil 'local))) -(defun org-columns-hscoll-title () +(defun org-columns-hscroll-title () "Set the `header-line-format' so that it scrolls along with the table." (sit-for .0001) ; need to force a redisplay to update window-hscroll (when (not (= (window-hscroll) org-columns-previous-hscroll)) @@ -330,46 +458,23 @@ for the duration of the command.") (defun org-columns-remove-overlays () "Remove all currently active column overlays." (interactive) - (when (marker-buffer org-columns-begin-marker) - (with-current-buffer (marker-buffer org-columns-begin-marker) - (when (local-variable-p 'org-previous-header-line-format) - (setq header-line-format org-previous-header-line-format) - (kill-local-variable 'org-previous-header-line-format) - (remove-hook 'post-command-hook 'org-columns-hscoll-title 'local)) - (move-marker org-columns-begin-marker nil) - (move-marker org-columns-top-level-marker nil) - (org-with-silent-modifications - (mapc 'delete-overlay org-columns-overlays) - (setq org-columns-overlays nil) - (let ((inhibit-read-only t)) - (remove-text-properties (point-min) (point-max) '(read-only t)))) - (when org-columns-flyspell-was-active - (flyspell-mode 1)) - (when (local-variable-p 'org-colview-initial-truncate-line-value) - (setq truncate-lines org-colview-initial-truncate-line-value))))) - -(defun org-columns-cleanup-item (item fmt cphr) - "Remove from ITEM what is a column in the format FMT. -CPHR is the complex heading regexp to use for parsing ITEM." - (let (fixitem) - (if (not cphr) - item - (unless (string-match "^\\*+ " item) - (setq item (concat "* " item) fixitem t)) - (if (string-match cphr item) - (setq item - (concat - (org-add-props (match-string 1 item) nil - 'org-whitespace (* 2 (1- (org-reduced-level (- (match-end 1) (match-beginning 1)))))) - (and (match-end 2) (not (assoc "TODO" fmt)) (concat " " (match-string 2 item))) - (and (match-end 3) (not (assoc "PRIORITY" fmt)) (concat " " (match-string 3 item))) - " " (save-match-data (org-columns-compact-links (or (match-string 4 item) ""))) - (and (match-end 5) (not (assoc "TAGS" fmt)) (concat " " (match-string 5 item))))) - (add-text-properties - 0 (1+ (match-end 1)) - (list 'org-whitespace (* 2 (1- (org-reduced-level (- (match-end 1) (match-beginning 1)))))) - item)) - (if fixitem (replace-regexp-in-string "^\\*+ " "" item) item)))) + (when org-columns-overlays + (when (local-variable-p 'org-previous-header-line-format) + (setq header-line-format org-previous-header-line-format) + (kill-local-variable 'org-previous-header-line-format) + (remove-hook 'post-command-hook 'org-columns-hscroll-title 'local)) + (set-marker org-columns-begin-marker nil) + (when (markerp org-columns-top-level-marker) + (set-marker org-columns-top-level-marker nil)) + (org-with-silent-modifications + (mapc #'delete-overlay org-columns-overlays) + (setq org-columns-overlays nil) + (let ((inhibit-read-only t)) + (remove-text-properties (point-min) (point-max) '(read-only t)))) + (when org-columns-flyspell-was-active + (flyspell-mode 1)) + (when (local-variable-p 'org-colview-initial-truncate-line-value) + (setq truncate-lines org-colview-initial-truncate-line-value)))) (defun org-columns-compact-links (s) "Replace [[link][desc]] with [desc] or [link]." @@ -394,25 +499,26 @@ CPHR is the complex heading regexp to use for parsing ITEM." (org-columns-remove-overlays) (let ((inhibit-read-only t)) (remove-text-properties (point-min) (point-max) '(read-only t)))) - (when (eq major-mode 'org-agenda-mode) + (if (not (eq major-mode 'org-agenda-mode)) + (setq org-columns-current-fmt nil) (setq org-agenda-columns-active nil) (message "Modification not yet reflected in Agenda buffer, use `r' to refresh"))) (defun org-columns-check-computed () - "Check if this column value is computed. -If yes, throw an error indicating that changing it does not make sense." - (let ((val (get-char-property (point) 'org-columns-value))) - (when (and (stringp val) - (get-char-property 0 'org-computed val)) - (error "This value is computed from the entry's children")))) - -(defun org-columns-todo (&optional arg) + "Throw an error if current column value is computed." + (let ((spec (nth (current-column) org-columns-current-fmt-compiled))) + (and + (nth 3 spec) + (assoc spec (get-text-property (line-beginning-position) 'org-summaries)) + (error "This value is computed from the entry's children")))) + +(defun org-columns-todo (&optional _arg) "Change the TODO state during column view." (interactive "P") (org-columns-edit-value "TODO")) -(defun org-columns-set-tags-or-toggle (&optional arg) +(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-]\\]\\'" @@ -430,107 +536,76 @@ Where possible, use the standard interface for changing this line." (interactive) (org-columns-check-computed) (let* ((col (current-column)) + (bol (line-beginning-position)) + (eol (line-end-position)) + (pom (or (get-text-property bol 'org-hd-marker) (point))) (key (or key (get-char-property (point) 'org-columns-key))) - (value (get-char-property (point) 'org-columns-value)) - (bol (point-at-bol)) (eol (point-at-eol)) - (pom (or (get-text-property bol 'org-hd-marker) - (point))) ; keep despite of compiler waring - (line-overlays - (delq nil (mapcar (lambda (x) - (and (eq (overlay-buffer x) (current-buffer)) - (>= (overlay-start x) bol) - (<= (overlay-start x) eol) - x)) - org-columns-overlays))) - (org-columns-time (time-to-number-of-days (current-time))) - nval eval allowed) + (org-columns--time (float-time (current-time))) + (action + (pcase key + ("CLOCKSUM" + (error "This special column cannot be edited")) + ("ITEM" + (lambda () (org-with-point-at pom (org-edit-headline)))) + ("TODO" + (lambda () + (org-with-point-at pom (call-interactively #'org-todo)))) + ("PRIORITY" + (lambda () + (org-with-point-at pom + (call-interactively #'org-priority)))) + ("TAGS" + (lambda () + (org-with-point-at pom + (let ((org-fast-tag-selection-single-key + (if (eq org-fast-tag-selection-single-key 'expert) + t + org-fast-tag-selection-single-key))) + (call-interactively #'org-set-tags))))) + ("DEADLINE" + (lambda () + (org-with-point-at pom (call-interactively #'org-deadline)))) + ("SCHEDULED" + (lambda () + (org-with-point-at pom (call-interactively #'org-schedule)))) + ("BEAMER_ENV" + (lambda () + (org-with-point-at pom + (call-interactively #'org-beamer-select-environment)))) + (_ + (let* ((allowed (org-property-get-allowed-values pom key 'table)) + (value (get-char-property (point) 'org-columns-value)) + (nval (org-trim + (if (null allowed) (read-string "Edit: " value) + (completing-read + "Value: " allowed nil + (not (get-text-property + 0 'org-unrestricted (caar allowed)))))))) + (and (not (equal nval value)) + (lambda () (org-entry-put pom key nval)))))))) (cond - ((equal key "CLOCKSUM") - (error "This special column cannot be edited")) - ((equal key "ITEM") - (setq eval '(org-with-point-at pom - (org-edit-headline)))) - ((equal key "TODO") - (setq eval '(org-with-point-at - pom - (call-interactively 'org-todo)))) - ((equal key "PRIORITY") - (setq eval '(org-with-point-at pom - (call-interactively 'org-priority)))) - ((equal key "TAGS") - (setq eval '(org-with-point-at pom - (let ((org-fast-tag-selection-single-key - (if (eq org-fast-tag-selection-single-key 'expert) - t org-fast-tag-selection-single-key))) - (call-interactively 'org-set-tags))))) - ((equal key "DEADLINE") - (setq eval '(org-with-point-at pom - (call-interactively 'org-deadline)))) - ((equal key "SCHEDULED") - (setq eval '(org-with-point-at pom - (call-interactively 'org-schedule)))) - ((equal key "BEAMER_env") - (setq eval '(org-with-point-at pom - (call-interactively 'org-beamer-select-environment)))) + ((null action)) + ((eq major-mode 'org-agenda-mode) + (org-columns--call action) + ;; The following let preserves the current format, and makes + ;; sure that in only a single file things need to be updated. + (let* ((org-agenda-overriding-columns-format org-columns-current-fmt) + (buffer (marker-buffer pom)) + (org-agenda-contributing-files + (list (with-current-buffer buffer + (buffer-file-name (buffer-base-buffer)))))) + (org-agenda-columns))) (t - (setq allowed (org-property-get-allowed-values pom key 'table)) - (if allowed - (setq nval (org-icompleting-read - "Value: " allowed nil - (not (get-text-property 0 'org-unrestricted - (caar allowed))))) - (setq nval (read-string "Edit: " value))) - (setq nval (org-trim nval)) - (when (not (equal nval value)) - (setq eval '(org-entry-put pom key nval))))) - (when eval - - (cond - ((equal major-mode 'org-agenda-mode) - (org-columns-eval eval) - ;; The following let preserves the current format, and makes sure - ;; that in only a single file things need to be updated. - (let* ((org-agenda-overriding-columns-format org-columns-current-fmt) - (buffer (marker-buffer pom)) - (org-agenda-contributing-files - (list (with-current-buffer buffer - (buffer-file-name (buffer-base-buffer)))))) - (org-agenda-columns))) - (t - (let ((inhibit-read-only t)) - (org-with-silent-modifications - (remove-text-properties - (max (point-min) (1- bol)) eol '(read-only t))) - (unwind-protect - (progn - (setq org-columns-overlays - (org-delete-all line-overlays org-columns-overlays)) - (mapc 'delete-overlay line-overlays) - (org-columns-eval eval)) - (org-columns-display-here))) - (org-move-to-column col) - (if (and (derived-mode-p 'org-mode) - (nth 3 (assoc key org-columns-current-fmt-compiled))) - (org-columns-update key))))))) - -(defun org-edit-headline () ; FIXME: this is not columns specific. Make interactive????? Use from agenda???? - "Edit the current headline, the part without TODO keyword, TAGS." - (org-back-to-heading) - (when (looking-at org-todo-line-regexp) - (let ((pos (point)) - (pre (buffer-substring (match-beginning 0) (match-beginning 3))) - (txt (match-string 3)) - (post "") - txt2) - (if (string-match (org-re "[ \t]+:[[:alnum:]:_@#%]+:[ \t]*$") txt) - (setq post (match-string 0 txt) - txt (substring txt 0 (match-beginning 0)))) - (setq txt2 (read-string "Edit: " txt)) - (when (not (equal txt txt2)) - (goto-char pos) - (insert pre txt2 post) - (delete-region (point) (point-at-eol)) - (org-set-tags nil t))))) + (let ((inhibit-read-only t)) + (org-with-silent-modifications + (remove-text-properties (max (point-min) (1- bol)) eol '(read-only t))) + (org-columns--call action)) + ;; Some properties can modify headline (e.g., "TODO"), and + ;; possible shuffle overlays. Make sure they are still all at + ;; the right place on the current line. + (let ((org-columns-inhibit-recalculation)) (org-columns-redo)) + (org-columns-update key) + (org-move-to-column col))))) (defun org-columns-edit-allowed () "Edit the list of allowed values for the current property." @@ -538,30 +613,30 @@ Where possible, use the standard interface for changing this line." (let* ((pom (or (org-get-at-bol 'org-marker) (org-get-at-bol 'org-hd-marker) (point))) - (key (get-char-property (point) 'org-columns-key)) - (key1 (concat key "_ALL")) - (allowed (org-entry-get pom key1 t)) - nval) + (key (concat (or (get-char-property (point) 'org-columns-key) + (user-error "No column to edit at point")) + "_ALL")) + (allowed (org-entry-get pom key t)) + (new-value (read-string "Allowed: " allowed))) ;; FIXME: Cover editing TODO, TAGS etc in-buffer settings.???? ;; FIXME: Write back to #+PROPERTY setting if that is needed. - (setq nval (read-string "Allowed: " allowed)) (org-entry-put (cond ((marker-position org-entry-property-inherited-from) org-entry-property-inherited-from) ((marker-position org-columns-top-level-marker) org-columns-top-level-marker) (t pom)) - key1 nval))) - -(defun org-columns-eval (form) - (let (hidep) - (save-excursion - (beginning-of-line 1) - ;; `next-line' is needed here, because it skips invisible line. - (condition-case nil (org-no-warnings (next-line 1)) (error nil)) - (setq hidep (org-at-heading-p 1))) - (eval form) - (and hidep (hide-entry)))) + key new-value))) + +(defun org-columns--call (fun) + "Call function FUN while preserving heading visibility. +FUN is a function called with no argument." + (let ((hide-body (and (/= (line-end-position) (point-max)) + (save-excursion + (move-beginning-of-line 2) + (org-at-heading-p t))))) + (unwind-protect (funcall fun) + (when hide-body (outline-hide-entry))))) (defun org-columns-previous-allowed-value () "Switch to the previous allowed value for this column." @@ -574,72 +649,57 @@ When PREVIOUS is set, go to the previous value. When NTH is an integer, select that value." (interactive) (org-columns-check-computed) - (let* ((col (current-column)) + (let* ((column (current-column)) (key (get-char-property (point) 'org-columns-key)) (value (get-char-property (point) 'org-columns-value)) - (bol (point-at-bol)) (eol (point-at-eol)) - (pom (or (get-text-property bol 'org-hd-marker) - (point))) ; keep despite of compiler waring - (line-overlays - (delq nil (mapcar (lambda (x) - (and (eq (overlay-buffer x) (current-buffer)) - (>= (overlay-start x) bol) - (<= (overlay-start x) eol) - x)) - org-columns-overlays))) - (allowed (or (org-property-get-allowed-values pom key) - (and (memq - (nth 4 (assoc key org-columns-current-fmt-compiled)) - '(checkbox checkbox-n-of-m checkbox-percent)) - '("[ ]" "[X]")) - (org-colview-construct-allowed-dates value))) - nval) - (when (integerp nth) - (setq nth (1- nth)) - (if (= nth -1) (setq nth 9))) - (when (equal key "ITEM") - (error "Cannot edit item headline from here")) + (pom (or (get-text-property (line-beginning-position) 'org-hd-marker) + (point))) + (allowed + (let ((all + (or (org-property-get-allowed-values pom key) + (pcase (nth column org-columns-current-fmt-compiled) + (`(,_ ,_ ,_ ,(or "X" "X/" "X%") ,_) '("[ ]" "[X]"))) + (org-colview-construct-allowed-dates value)))) + (if previous (reverse all) all)))) + (when (equal key "ITEM") (error "Cannot edit item headline from here")) (unless (or allowed (member key '("SCHEDULED" "DEADLINE" "CLOCKSUM"))) (error "Allowed values for this property have not been defined")) - (if (member key '("SCHEDULED" "DEADLINE" "CLOCKSUM")) - (setq nval (if previous 'earlier 'later)) - (if previous (setq allowed (reverse allowed))) + (let* ((l (length allowed)) + (new + (cond + ((member key '("SCHEDULED" "DEADLINE" "CLOCKSUM")) + (if previous 'earlier 'later)) + ((integerp nth) + (when (> (abs nth) l) + (user-error "Only %d allowed values for property `%s'" l key)) + (nth (mod (1- nth) l) allowed)) + ((member value allowed) + (when (= l 1) (error "Only one allowed value for this property")) + (or (nth 1 (member value allowed)) (car allowed))) + (t (car allowed)))) + (action (lambda () (org-entry-put pom key new)))) (cond - (nth - (setq nval (nth nth allowed)) - (if (not nval) - (error "There are only %d allowed values for property `%s'" - (length allowed) key))) - ((member value allowed) - (setq nval (or (car (cdr (member value allowed))) - (car allowed))) - (if (equal nval value) - (error "Only one allowed value for this property"))) - (t (setq nval (car allowed))))) - (cond - ((equal major-mode 'org-agenda-mode) - (org-columns-eval '(org-entry-put pom key nval)) - ;; The following let preserves the current format, and makes sure - ;; that in only a single file things need to be updated. - (let* ((org-agenda-overriding-columns-format org-columns-current-fmt) - (buffer (marker-buffer pom)) - (org-agenda-contributing-files - (list (with-current-buffer buffer - (buffer-file-name (buffer-base-buffer)))))) - (org-agenda-columns))) - (t - (let ((inhibit-read-only t)) - (remove-text-properties (1- bol) eol '(read-only t)) - (unwind-protect - (progn - (setq org-columns-overlays - (org-delete-all line-overlays org-columns-overlays)) - (mapc 'delete-overlay line-overlays) - (org-columns-eval '(org-entry-put pom key nval))) - (org-columns-display-here))) - (org-move-to-column col) - (and (nth 3 (assoc key org-columns-current-fmt-compiled)) - (org-columns-update key)))))) + ((eq major-mode 'org-agenda-mode) + (org-columns--call action) + ;; The following let preserves the current format, and makes + ;; sure that in only a single file things need to be updated. + (let* ((org-agenda-overriding-columns-format org-columns-current-fmt) + (buffer (marker-buffer pom)) + (org-agenda-contributing-files + (list (with-current-buffer buffer + (buffer-file-name (buffer-base-buffer)))))) + (org-agenda-columns))) + (t + (let ((inhibit-read-only t)) + (remove-text-properties (line-end-position 0) (line-end-position) + '(read-only t)) + (org-columns--call action)) + ;; Some properties can modify headline (e.g., "TODO"), and + ;; possible shuffle overlays. Make sure they are still all at + ;; the right place on the current line. + (let ((org-columns-inhibit-recalculation)) (org-columns-redo)) + (org-columns-update key) + (org-move-to-column column)))))) (defun org-colview-construct-allowed-dates (s) "Construct a list of three dates around the date in S. @@ -662,13 +722,6 @@ around it." (mapcar (lambda (x) (format-time-string fmt (apply 'encode-time x))) (list time-before time time-after))))) -(defun org-verify-version (task) - (cond - ((eq task 'columns) - (if (or (featurep 'xemacs) - (< emacs-major-version 22)) - (error "Emacs 22 is required for the columns feature"))))) - (defun org-columns-open-link (&optional arg) (interactive "P") (let ((value (get-char-property (point) 'org-columns-value))) @@ -681,179 +734,169 @@ around it." fmt)) (defun org-columns-get-format (&optional fmt-string) + "Return columns format specifications. +When optional argument FMT-STRING is non-nil, use it as the +current specifications. This function also sets +`org-columns-current-fmt-compiled' and +`org-columns-current-fmt'." (interactive) - (let (fmt-as-property fmt) - (when (condition-case nil (org-back-to-heading) (error nil)) - (setq fmt-as-property (org-entry-get nil "COLUMNS" t))) - (setq fmt (or fmt-string fmt-as-property org-columns-default-format)) - (org-set-local 'org-columns-current-fmt fmt) - (org-columns-compile-format fmt) - fmt)) + (let ((format + (or fmt-string + (org-entry-get nil "COLUMNS" t) + (org-with-wide-buffer + (goto-char (point-min)) + (catch :found + (let ((case-fold-search t)) + (while (re-search-forward "^[ \t]*#\\+COLUMNS: .+$" nil t) + (let ((element (org-element-at-point))) + (when (eq (org-element-type element) 'keyword) + (throw :found (org-element-property :value element))))) + nil))) + org-columns-default-format))) + (setq org-columns-current-fmt format) + (org-columns-compile-format format) + format)) (defun org-columns-goto-top-level () - (when (condition-case nil (org-back-to-heading) (error nil)) - (org-entry-get nil "COLUMNS" t)) - (if (marker-position org-entry-property-inherited-from) - (move-marker org-columns-top-level-marker org-entry-property-inherited-from) - (move-marker org-columns-top-level-marker (point)))) + "Move to the beginning of the column view area. +Also sets `org-columns-top-level-marker' to the new position." + (unless (markerp org-columns-top-level-marker) + (setq org-columns-top-level-marker (make-marker))) + (goto-char + (move-marker + org-columns-top-level-marker + (cond ((org-before-first-heading-p) (point-min)) + ((org-entry-get nil "COLUMNS" t) org-entry-property-inherited-from) + (t (org-back-to-heading) (point)))))) ;;;###autoload -(defun org-columns (&optional columns-fmt-string) - "Turn on column view on an org-mode file. +(defun org-columns (&optional global columns-fmt-string) + "Turn on column view on an Org mode file. + +Column view applies to the whole buffer if point is before the +first headline. Otherwise, it applies to the first ancestor +setting \"COLUMNS\" property. If there is none, it defaults to +the current headline. With a `\\[universal-argument]' prefix \ +argument, turn on column +view for the whole buffer unconditionally. + When COLUMNS-FMT-STRING is non-nil, use it as the column format." - (interactive) - (org-verify-version 'columns) + (interactive "P") (org-columns-remove-overlays) - (move-marker org-columns-begin-marker (point)) - (let ((org-columns-time (time-to-number-of-days (current-time))) - beg end fmt cache maxwidths) - (org-columns-goto-top-level) - (setq fmt (org-columns-get-format columns-fmt-string)) + (when global (goto-char (point-min))) + (if (markerp org-columns-begin-marker) + (move-marker org-columns-begin-marker (point)) + (setq org-columns-begin-marker (point-marker))) + (org-columns-goto-top-level) + ;; Initialize `org-columns-current-fmt' and + ;; `org-columns-current-fmt-compiled'. + (let ((org-columns--time (float-time (current-time)))) + (org-columns-get-format columns-fmt-string) + (unless org-columns-inhibit-recalculation (org-columns-compute-all)) (save-excursion - (goto-char org-columns-top-level-marker) - (setq beg (point)) - (unless org-columns-inhibit-recalculation - (org-columns-compute-all)) - (setq end (or (condition-case nil (org-end-of-subtree t t) (error nil)) - (point-max))) - ;; Get and cache the properties - (goto-char beg) - (when (assoc "CLOCKSUM" org-columns-current-fmt-compiled) - (save-excursion - (save-restriction - (narrow-to-region beg end) - (org-clock-sum)))) - (when (assoc "CLOCKSUM_T" org-columns-current-fmt-compiled) - (save-excursion - (save-restriction - (narrow-to-region beg end) - (org-clock-sum-today)))) - (while (re-search-forward org-outline-regexp-bol end t) - (if (and org-columns-skip-archived-trees - (looking-at (concat ".*:" org-archive-tag ":"))) - (org-end-of-subtree t) - (push (cons (org-current-line) (org-entry-properties)) cache))) - (when cache - (setq maxwidths (org-columns-get-autowidth-alist fmt cache)) - (org-set-local 'org-columns-current-maxwidths maxwidths) - (org-columns-display-here-title) - (when (org-set-local 'org-columns-flyspell-was-active - (org-bound-and-true-p flyspell-mode)) - (flyspell-mode 0)) - (unless (local-variable-p 'org-colview-initial-truncate-line-value) - (org-set-local 'org-colview-initial-truncate-line-value - truncate-lines)) - (setq truncate-lines t) - (mapc (lambda (x) - (org-goto-line (car x)) - (org-columns-display-here (cdr x))) - cache))))) - -(eval-when-compile (defvar org-columns-time)) - -(defvar org-columns-compile-map - '(("none" none +) - (":" add_times +) - ("+" add_numbers +) - ("$" currency +) - ("X" checkbox +) - ("X/" checkbox-n-of-m +) - ("X%" checkbox-percent +) - ("max" max_numbers max) - ("min" min_numbers min) - ("mean" mean_numbers - (lambda (&rest x) (/ (apply '+ x) (float (length x))))) - (":max" max_times max) - (":min" min_times min) - (":mean" mean_times - (lambda (&rest x) (/ (apply '+ x) (float (length x))))) - ("@min" min_age min (lambda (x) (- org-columns-time x))) - ("@max" max_age max (lambda (x) (- org-columns-time x))) - ("@mean" mean_age - (lambda (&rest x) (/ (apply '+ x) (float (length x)))) - (lambda (x) (- org-columns-time x))) - ("est+" estimate org-estimate-combine)) - "Operator <-> format,function,calc map. -Used to compile/uncompile columns format and completing read in -interactive function `org-columns-new'. - -operator string used in #+COLUMNS definition describing the - summary type -format symbol describing summary type selected interactively in - `org-columns-new' and internally in - `org-columns-number-to-string' and - `org-columns-string-to-number' -function called with a list of values as argument to calculate - the summary value -calc function called on every element before summarizing. This is - optional and should only be specified if needed") - -(defun org-columns-new (&optional prop title width op fmt fun &rest rest) - "Insert a new column, to the left of the current column." + (save-restriction + (when (and (not global) (org-at-heading-p)) + (narrow-to-region (point) (org-end-of-subtree t t))) + (when (assoc "CLOCKSUM" org-columns-current-fmt-compiled) + (org-clock-sum)) + (when (assoc "CLOCKSUM_T" org-columns-current-fmt-compiled) + (org-clock-sum-today)) + (let ((cache + ;; Collect contents of columns ahead of time so as to + ;; compute their maximum width. + (org-map-entries + (lambda () (cons (point) (org-columns--collect-values))) + nil nil (and org-columns-skip-archived-trees 'archive)))) + (when cache + (org-columns--set-widths cache) + (org-columns--display-here-title) + (when (setq-local org-columns-flyspell-was-active + (bound-and-true-p flyspell-mode)) + (flyspell-mode 0)) + (unless (local-variable-p 'org-colview-initial-truncate-line-value) + (setq-local org-colview-initial-truncate-line-value + truncate-lines)) + (setq truncate-lines t) + (dolist (entry cache) + (goto-char (car entry)) + (org-columns--display-here (cdr entry))))))))) + +(defun org-columns-new (&optional spec &rest attributes) + "Insert a new column, to the left of the current column. +Interactively fill attributes for new column. When column format +specification SPEC is provided, edit it instead. + +When optional argument attributes can be a list of columns +specifications attributes to create the new column +non-interactively. See `org-columns-compile-format' for +details." (interactive) - (let ((editp (and prop (assoc prop org-columns-current-fmt-compiled))) - cell) - (setq prop (org-icompleting-read - "Property: " (mapcar 'list (org-buffer-property-keys t nil t)) - nil nil prop)) - (setq title (read-string (concat "Column title [" prop "]: ") (or title prop))) - (setq width (read-string "Column width: " (if width (number-to-string width)))) - (if (string-match "\\S-" width) - (setq width (string-to-number width)) - (setq width nil)) - (setq fmt (org-icompleting-read - "Summary [none]: " - (mapcar (lambda (x) (list (symbol-name (cadr x)))) - org-columns-compile-map) - nil t)) - (setq fmt (intern fmt) - fun (cdr (assoc fmt (mapcar 'cdr org-columns-compile-map)))) - (if (eq fmt 'none) (setq fmt nil)) - (if editp - (progn - (setcar editp prop) - (setcdr editp (list title width nil fmt nil fun))) - (setq cell (nthcdr (1- (current-column)) - org-columns-current-fmt-compiled)) - (setcdr cell (cons (list prop title width nil fmt nil - (car fun) (cadr fun)) - (cdr cell)))) + (let ((new (or attributes + (let ((prop + (completing-read + "Property: " + (mapcar #'list (org-buffer-property-keys t nil t)) + nil nil (nth 0 spec)))) + (list prop + (read-string (format "Column title [%s]: " prop) + (nth 1 spec)) + ;; Use `read-string' instead of `read-number' + ;; to allow empty width. + (let ((w (read-string + "Column width: " + (and (nth 2 spec) + (number-to-string (nth 2 spec)))))) + (and (org-string-nw-p w) (string-to-number w))) + (org-string-nw-p + (completing-read + "Summary: " + (delete-dups + (cons '("") ;Allow empty operator. + (mapcar (lambda (x) (list (car x))) + (append + org-columns-summary-types + org-columns-summary-types-default)))) + nil t (nth 3 spec))) + (org-string-nw-p + (read-string "Format: " (nth 4 spec)))))))) + (if spec + (progn (setcar spec (car new)) + (setcdr spec (cdr new))) + (push new (nthcdr (current-column) org-columns-current-fmt-compiled))) (org-columns-store-format) (org-columns-redo))) (defun org-columns-delete () "Delete the column at point from columns view." (interactive) - (let* ((n (current-column)) - (title (nth 1 (nth n org-columns-current-fmt-compiled)))) - (when (y-or-n-p - (format "Are you sure you want to remove column \"%s\"? " title)) + (let ((spec (nth (current-column) org-columns-current-fmt-compiled))) + (when (y-or-n-p (format "Are you sure you want to remove column %S? " + (nth 1 spec))) (setq org-columns-current-fmt-compiled - (delq (nth n org-columns-current-fmt-compiled) - org-columns-current-fmt-compiled)) + (delq spec org-columns-current-fmt-compiled)) (org-columns-store-format) - (org-columns-redo) - (if (>= (current-column) (length org-columns-current-fmt-compiled)) - (backward-char 1))))) + ;; This may leave a now wrong value in a node property. However + ;; updating it may prove counter-intuitive. See comments in + ;; `org-columns-move-right' for details. + (let ((org-columns-inhibit-recalculation t)) (org-columns-redo)) + (when (>= (current-column) (length org-columns-current-fmt-compiled)) + (backward-char))))) (defun org-columns-edit-attributes () "Edit the attributes of the current column." (interactive) - (let* ((n (current-column)) - (info (nth n org-columns-current-fmt-compiled))) - (apply 'org-columns-new info))) + (org-columns-new (nth (current-column) org-columns-current-fmt-compiled))) (defun org-columns-widen (arg) "Make the column wider by ARG characters." (interactive "p") (let* ((n (current-column)) (entry (nth n org-columns-current-fmt-compiled)) - (width (or (nth 2 entry) - (cdr (assoc (car entry) org-columns-current-maxwidths))))) + (width (aref org-columns-current-maxwidths n))) (setq width (max 1 (+ width arg))) (setcar (nthcdr 2 entry) width) (org-columns-store-format) - (org-columns-redo))) + (let ((org-columns-inhibit-recalculation t)) (org-columns-redo)))) (defun org-columns-narrow (arg) "Make the column narrower by ARG characters." @@ -872,7 +915,16 @@ calc function called on every element before summarizing. This is (setcar cell (car (cdr cell))) (setcdr cell (cons e (cdr (cdr cell)))) (org-columns-store-format) - (org-columns-redo) + ;; Do not compute again properties, since we're just moving + ;; columns around. It can put a property value a bit off when + ;; switching between an non-computed and a computed value for the + ;; same property, e.g. from "%A %A{+}" to "%A{+} %A". + ;; + ;; In this case, the value needs to be updated since the first + ;; column related to a property determines how its value is + ;; computed. However, (correctly) updating the value could be + ;; surprising, so we leave it as-is nonetheless. + (let ((org-columns-inhibit-recalculation t)) (org-columns-redo)) (forward-char 1))) (defun org-columns-move-left () @@ -886,358 +938,427 @@ calc function called on every element before summarizing. This is (backward-char 1))) (defun org-columns-store-format () - "Store the text version of the current columns format in appropriate place. -This is either in the COLUMNS property of the node starting the current column -display, or in the #+COLUMNS line of the current buffer." - (let (fmt (cnt 0)) - (setq fmt (org-columns-uncompile-format org-columns-current-fmt-compiled)) - (org-set-local 'org-columns-current-fmt fmt) - (if (marker-position org-columns-top-level-marker) - (save-excursion - (goto-char org-columns-top-level-marker) - (if (and (org-at-heading-p) - (org-entry-get nil "COLUMNS")) - (org-entry-put nil "COLUMNS" fmt) - (goto-char (point-min)) - ;; Overwrite all #+COLUMNS lines.... - (while (re-search-forward "^#\\+COLUMNS:.*" nil t) - (setq cnt (1+ cnt)) - (replace-match (concat "#+COLUMNS: " fmt) t t)) - (unless (> cnt 0) + "Store the text version of the current columns format. +The format is stored either in the COLUMNS property of the node +starting the current column display, or in a #+COLUMNS line of +the current buffer." + (let ((fmt (org-columns-uncompile-format org-columns-current-fmt-compiled))) + (setq-local org-columns-current-fmt fmt) + (when org-columns-overlays + (org-with-point-at org-columns-top-level-marker + (if (and (org-at-heading-p) (org-entry-get nil "COLUMNS")) + (org-entry-put nil "COLUMNS" fmt) + (goto-char (point-min)) + (let ((case-fold-search t)) + ;; Try to replace the first COLUMNS keyword available. + (catch :found + (while (re-search-forward "^[ \t]*#\\+COLUMNS:\\(.*\\)" nil t) + (let ((element (save-match-data (org-element-at-point)))) + (when (and (eq (org-element-type element) 'keyword) + (equal (org-element-property :key element) + "COLUMNS")) + (replace-match (concat " " fmt) t t nil 1) + (throw :found nil)))) + ;; No COLUMNS keyword in the buffer. Insert one at the + ;; beginning, right before the first heading, if any. (goto-char (point-min)) - (or (org-at-heading-p t) (outline-next-heading)) + (unless (org-at-heading-p t) (outline-next-heading)) (let ((inhibit-read-only t)) - (insert-before-markers "#+COLUMNS: " fmt "\n"))) - (org-set-local 'org-columns-default-format fmt)))))) - -(defun org-columns-get-autowidth-alist (s cache) - "Derive the maximum column widths from the format and the cache." - (let ((start 0) rtn) - (while (string-match (org-re "%\\([[:alpha:]][[:alnum:]_-]*\\)") s start) - (push (cons (match-string 1 s) 1) rtn) - (setq start (match-end 0))) - (mapc (lambda (x) - (setcdr x (apply 'max - (mapcar - (lambda (y) - (length (or (cdr (assoc (car x) (cdr y))) " "))) - cache)))) - rtn) - rtn)) - -(defun org-columns-compute-all () - "Compute all columns that have operators defined." - (org-with-silent-modifications - (remove-text-properties (point-min) (point-max) '(org-summaries t))) - (let ((columns org-columns-current-fmt-compiled) - (org-columns-time (time-to-number-of-days (current-time))) - col) - (while (setq col (pop columns)) - (when (nth 3 col) - (save-excursion - (org-columns-compute (car col))))))) + (insert-before-markers "#+COLUMNS: " fmt "\n")))) + (setq-local org-columns-default-format fmt)))))) (defun org-columns-update (property) "Recompute PROPERTY, and update the columns display for it." (org-columns-compute property) - (let (fmt val pos) - (save-excursion - (mapc (lambda (ov) - (when (equal (overlay-get ov 'org-columns-key) property) - (setq pos (overlay-start ov)) - (goto-char pos) - (when (setq val (cdr (assoc property - (get-text-property - (point-at-bol) 'org-summaries)))) - (setq fmt (overlay-get ov 'org-columns-format)) - (overlay-put ov 'org-columns-value val) - (overlay-put ov 'display (format fmt val))))) - org-columns-overlays)))) - -(defvar org-inlinetask-min-level - (if (featurep 'org-inlinetask) org-inlinetask-min-level 15)) - -;;;###autoload -(defun org-columns-compute (property) - "Sum the values of property PROPERTY hierarchically, for the entire buffer." - (interactive) - (let* ((re org-outline-regexp-bol) - (lmax 30) ; Does anyone use deeper levels??? - (lvals (make-vector lmax nil)) - (lflag (make-vector lmax nil)) - (level 0) - (ass (assoc property org-columns-current-fmt-compiled)) - (format (nth 4 ass)) - (printf (nth 5 ass)) - (fun (nth 6 ass)) - (calc (or (nth 7 ass) 'identity)) - (beg org-columns-top-level-marker) - (inminlevel org-inlinetask-min-level) - (last-level org-inlinetask-min-level) - val valflag flag end sumpos sum-alist sum str str1 useval) - (save-excursion - ;; Find the region to compute - (goto-char beg) - (setq end (condition-case nil (org-end-of-subtree t) (error (point-max)))) - (goto-char end) - ;; Walk the tree from the back and do the computations - (while (re-search-backward re beg t) - (setq sumpos (match-beginning 0) - last-level (if (not (or (zerop level) (eq level inminlevel))) - level last-level) - level (org-outline-level) - val (org-entry-get nil property) - valflag (and val (string-match "\\S-" val))) - (cond - ((< level last-level) - ;; put the sum of lower levels here as a property - (setq sum (+ (if (and (/= last-level inminlevel) - (aref lvals last-level)) - (apply fun (aref lvals last-level)) 0) - (if (aref lvals inminlevel) - (apply fun (aref lvals inminlevel)) 0)) - flag (or (aref lflag last-level) ; any valid entries from children? - (aref lflag inminlevel)) ; or inline tasks? - str (org-columns-number-to-string sum format printf) - str1 (org-add-props (copy-sequence str) nil 'org-computed t 'face 'bold) - useval (if flag str1 (if valflag val "")) - sum-alist (get-text-property sumpos 'org-summaries)) - (if (assoc property sum-alist) - (setcdr (assoc property sum-alist) useval) - (push (cons property useval) sum-alist) - (org-with-silent-modifications - (add-text-properties sumpos (1+ sumpos) - (list 'org-summaries sum-alist)))) - (when (and val (not (equal val (if flag str val)))) - (org-entry-put nil property (if flag str val))) - ;; add current to current level accumulator - (when (or flag valflag) - (push (if flag - sum - (funcall calc (org-columns-string-to-number - (if flag str val) format))) - (aref lvals level)) - (aset lflag level t)) - ;; clear accumulators for deeper levels - (loop for l from (1+ level) to (1- lmax) do - (aset lvals l nil) - (aset lflag l nil))) - ((>= level last-level) - ;; add what we have here to the accumulator for this level - (when valflag - (push (funcall calc (org-columns-string-to-number val format)) - (aref lvals level)) - (aset lflag level t))) - (t (error "This should not happen"))))))) + (org-with-wide-buffer + (let ((p (upcase property))) + (dolist (ov org-columns-overlays) + (let ((key (overlay-get ov 'org-columns-key))) + (when (and key (equal key p) (overlay-start ov)) + (goto-char (overlay-start ov)) + (let* ((spec (nth (current-column) org-columns-current-fmt-compiled)) + (value + (or (cdr (assoc spec + (get-text-property (line-beginning-position) + 'org-summaries))) + (org-entry-get (point) key)))) + (when value + (let ((displayed (org-columns--displayed-value spec value)) + (format (overlay-get ov 'org-columns-format)) + (width + (aref org-columns-current-maxwidths (current-column)))) + (overlay-put ov 'org-columns-value value) + (overlay-put ov 'org-columns-value-modified displayed) + (overlay-put ov + 'display + (org-columns--overlay-text + displayed format width property value))))))))))) (defun org-columns-redo () "Construct the column display again." (interactive) - (message "Recomputing columns...") - (let ((line (org-current-line)) - (col (current-column))) - (save-excursion - (if (marker-position org-columns-begin-marker) - (goto-char org-columns-begin-marker)) + (when org-columns-overlays + (message "Recomputing columns...") + (org-with-point-at org-columns-begin-marker (org-columns-remove-overlays) (if (derived-mode-p 'org-mode) - (call-interactively 'org-columns) + ;; Since we already know the columns format, provide it + ;; instead of computing again. + (call-interactively #'org-columns org-columns-current-fmt) (org-agenda-redo) - (call-interactively 'org-agenda-columns))) - (org-goto-line line) - (move-to-column col)) - (message "Recomputing columns...done")) - -(defun org-columns-not-in-agenda () - (if (eq major-mode 'org-agenda-mode) - (error "This command is only allowed in Org-mode buffers"))) - -(defun org-string-to-number (s) - "Convert string to number, and interpret hh:mm:ss." - (if (not (string-match ":" s)) - (string-to-number s) - (let ((l (nreverse (org-split-string s ":"))) (sum 0.0)) - (while l - (setq sum (+ (string-to-number (pop l)) (/ sum 60)))) - sum))) + (call-interactively #'org-agenda-columns))) + (message "Recomputing columns...done"))) -;;;###autoload -(defun org-columns-number-to-string (n fmt &optional printf) - "Convert a computed column number to a string value, according to FMT." - (cond - ((memq fmt '(estimate)) (org-estimate-print n printf)) - ((not (numberp n)) "") - ((memq fmt '(add_times max_times min_times mean_times)) - (org-hours-to-clocksum-string n)) - ((eq fmt 'checkbox) - (cond ((= n (floor n)) "[X]") - ((> n 1.) "[-]") - (t "[ ]"))) - ((memq fmt '(checkbox-n-of-m checkbox-percent)) - (let* ((n1 (floor n)) (n2 (floor (+ .5 (* 1000000 (- n n1)))))) - (org-nofm-to-completion n1 (+ n2 n1) (eq fmt 'checkbox-percent)))) - (printf (format printf n)) - ((eq fmt 'currency) - (format "%.2f" n)) - ((memq fmt '(min_age max_age mean_age)) - (org-format-time-period n)) - (t (number-to-string n)))) - -(defun org-nofm-to-completion (n m &optional percent) - (if (not percent) - (format "[%d/%d]" n m) - (format "[%d%%]" (round (* 100.0 n) m)))) - - -(defun org-columns-string-to-number (s fmt) - "Convert a column value to a number that can be used for column computing." - (if s - (cond - ((memq fmt '(min_age max_age mean_age)) - (cond ((string= s "") org-columns-time) - ((string-match - "\\([0-9]+\\)d \\([0-9]+\\)h \\([0-9]+\\)m \\([0-9]+\\)s" - s) - (+ (* 60 (+ (* 60 (+ (* 24 (string-to-number (match-string 1 s))) - (string-to-number (match-string 2 s)))) - (string-to-number (match-string 3 s)))) - (string-to-number (match-string 4 s)))) - (t (time-to-number-of-days (apply 'encode-time - (org-parse-time-string s t)))))) - ((string-match ":" s) - (let ((l (nreverse (org-split-string s ":"))) (sum 0.0)) - (while l - (setq sum (+ (string-to-number (pop l)) (/ sum 60)))) - sum)) - ((string-match (concat "\\([0-9.]+\\) *\\(" - (regexp-opt (mapcar 'car org-effort-durations)) - "\\)") s) - (setq s (concat "0:" (org-duration-string-to-minutes s t))) - (let ((l (nreverse (org-split-string s ":"))) (sum 0.0)) - (while l - (setq sum (+ (string-to-number (pop l)) (/ sum 60)))) - sum)) - ((memq fmt '(checkbox checkbox-n-of-m checkbox-percent)) - (if (equal s "[X]") 1. 0.000001)) - ((memq fmt '(estimate)) (org-string-to-estimate s)) - (t (string-to-number s))))) - -(defun org-columns-uncompile-format (cfmt) - "Turn the compiled columns format back into a string representation." - (let ((rtn "") e s prop title op op-match width fmt printf fun calc) - (while (setq e (pop cfmt)) - (setq prop (car e) - title (nth 1 e) - width (nth 2 e) - op (nth 3 e) - fmt (nth 4 e) - printf (nth 5 e) - fun (nth 6 e) - calc (nth 7 e)) - (when (setq op-match (rassoc (list fmt fun calc) org-columns-compile-map)) - (setq op (car op-match))) - (if (and op printf) (setq op (concat op ";" printf))) - (if (equal title prop) (setq title nil)) - (setq s (concat "%" (if width (number-to-string width)) - prop - (if title (concat "(" title ")")) - (if op (concat "{" op "}")))) - (setq rtn (concat rtn " " s))) - (org-trim rtn))) +(defun org-columns-uncompile-format (compiled) + "Turn the compiled columns format back into a string representation. +COMPILED is an alist, as returned by +`org-columns-compile-format', which see." + (mapconcat + (lambda (spec) + (pcase spec + (`(,prop ,title ,width ,op ,printf) + (concat "%" + (and width (number-to-string width)) + prop + (and title (not (equal prop title)) (format "(%s)" title)) + (cond ((not op) nil) + (printf (format "{%s;%s}" op printf)) + (t (format "{%s}" op))))))) + compiled " ")) (defun org-columns-compile-format (fmt) - "Turn a column format string into an alist of specifications. + "Turn a column format string FMT into an alist of specifications. + The alist has one entry for each column in the format. The elements of that list are: -property the property -title the title field for the columns -width the column width in characters, can be nil for automatic -operator the operator if any -format the output format for computed results, derived from operator -printf a printf format for computed values -fun the lisp function to compute summary values, derived from operator -calc function to get values from base elements" - (let ((start 0) width prop title op op-match f printf fun calc) - (setq org-columns-current-fmt-compiled nil) +property the property name, as an upper-case string +title the title field for the columns, as a string +width the column width in characters, can be nil for automatic width +operator the summary operator, as a string, or nil +printf a printf format for computed values, as a string, or nil + +This function updates `org-columns-current-fmt-compiled'." + (setq org-columns-current-fmt-compiled nil) + (let ((start 0)) (while (string-match - (org-re "%\\([0-9]+\\)?\\([[:alnum:]_-]+\\)\\(?:(\\([^)]+\\))\\)?\\(?:{\\([^}]+\\)}\\)?\\s-*") + "%\\([0-9]+\\)?\\([[:alnum:]_-]+\\)\\(?:(\\([^)]+\\))\\)?\ +\\(?:{\\([^}]+\\)}\\)?\\s-*" fmt start) - (setq start (match-end 0) - width (match-string 1 fmt) - prop (match-string 2 fmt) - title (or (match-string 3 fmt) prop) - op (match-string 4 fmt) - f nil - printf nil - fun '+ - calc nil) - (if width (setq width (string-to-number width))) - (when (and op (string-match ";" op)) - (setq printf (substring op (match-end 0)) - op (substring op 0 (match-beginning 0)))) - (when (setq op-match (assoc op org-columns-compile-map)) - (setq f (cadr op-match) - fun (caddr op-match) - calc (cadddr op-match))) - (push (list prop title width op f printf fun calc) - org-columns-current-fmt-compiled)) + (setq start (match-end 0)) + (let* ((width (and (match-end 1) (string-to-number (match-string 1 fmt)))) + (prop (match-string-no-properties 2 fmt)) + (title (or (match-string-no-properties 3 fmt) prop)) + (operator (match-string-no-properties 4 fmt))) + (push (if (not operator) (list (upcase prop) title width nil nil) + (let (printf) + (when (string-match ";" operator) + (setq printf (substring operator (match-end 0))) + (setq operator (substring operator 0 (match-beginning 0)))) + (list (upcase prop) title width operator printf))) + org-columns-current-fmt-compiled))) (setq org-columns-current-fmt-compiled (nreverse org-columns-current-fmt-compiled)))) + +;;;; Column View Summary + +(defun org-columns--age-to-minutes (s) + "Turn age string S into a number of minutes. +An age is either computed from a given time-stamp, or indicated +as a canonical duration, i.e., using units defined in +`org-duration-canonical-units'." + (cond + ((string-match-p org-ts-regexp s) + (/ (- org-columns--time + (float-time (apply #'encode-time (org-parse-time-string s)))) + 60)) + ((org-duration-p s) (org-duration-to-minutes s t)) ;skip user units + (t (user-error "Invalid age: %S" s)))) + +(defun org-columns--format-age (minutes) + "Format MINUTES float as an age string." + (org-duration-from-minutes minutes + '(("d" . nil) ("h" . nil) ("min" . nil)) + t)) ;ignore user's custom units + +(defun org-columns--summary-apply-times (fun times) + "Apply FUN to time values TIMES. +Return the result as a duration." + (org-duration-from-minutes + (apply fun + (mapcar (lambda (time) + ;; Unlike to `org-duration-to-minutes' standard + ;; behavior, we want to consider plain numbers as + ;; hours. As a consequence, we treat them + ;; differently. + (if (string-match-p "\\`[0-9]+\\(?:\\.[0-9]*\\)?\\'" time) + (* 60 (string-to-number time)) + (org-duration-to-minutes time))) + times)) + (org-duration-h:mm-only-p times))) + +(defun org-columns--compute-spec (spec &optional update) + "Update tree according to SPEC. +SPEC is a column format specification. When optional argument +UPDATE is non-nil, summarized values can replace existing ones in +properties drawers." + (let* ((lmax (if (bound-and-true-p org-inlinetask-min-level) + org-inlinetask-min-level + 29)) ;Hard-code deepest level. + (lvals (make-vector (1+ lmax) nil)) + (level 0) + (inminlevel lmax) + (last-level lmax) + (property (car spec)) + (printf (nth 4 spec)) + (summarize (org-columns--summarize (nth 3 spec)))) + (org-with-wide-buffer + ;; Find the region to compute. + (goto-char org-columns-top-level-marker) + (goto-char (condition-case nil (org-end-of-subtree t) (error (point-max)))) + ;; Walk the tree from the back and do the computations. + (while (re-search-backward + org-outline-regexp-bol org-columns-top-level-marker t) + (unless (or (= level 0) (eq level inminlevel)) + (setq last-level level)) + (setq level (org-reduced-level (org-outline-level))) + (let* ((pos (match-beginning 0)) + (value (org-entry-get nil property)) + (value-set (org-string-nw-p value))) + (cond + ((< level last-level) + ;; Collect values from lower levels and inline tasks here + ;; and summarize them using SUMMARIZE. Store them in text + ;; property `org-summaries', in alist whose key is SPEC. + (let* ((summary + (and summarize + (let ((values (append (and (/= last-level inminlevel) + (aref lvals last-level)) + (aref lvals inminlevel)))) + (and values (funcall summarize values printf)))))) + ;; Leaf values are not summaries: do not mark them. + (when summary + (let* ((summaries-alist (get-text-property pos 'org-summaries)) + (old (assoc spec summaries-alist))) + (if old (setcdr old summary) + (push (cons spec summary) summaries-alist) + (org-with-silent-modifications + (add-text-properties + pos (1+ pos) (list 'org-summaries summaries-alist))))) + ;; When PROPERTY exists in current node, even if empty, + ;; but its value doesn't match the one computed, use + ;; the latter instead. + ;; + ;; Ignore leading or trailing white spaces that might + ;; have been introduced in summary, since those are not + ;; significant in properties value. + (let ((new-value (org-trim summary))) + (when (and update value (not (equal value new-value))) + (org-entry-put (point) property new-value)))) + ;; Add current to current level accumulator. + (when (or summary value-set) + (push (or summary value) (aref lvals level))) + ;; Clear accumulators for deeper levels. + (cl-loop for l from (1+ level) to lmax do (aset lvals l nil)))) + (value-set (push value (aref lvals level))) + (t nil))))))) +;;;###autoload +(defun org-columns-compute (property) + "Summarize the values of PROPERTY hierarchically. +Also update existing values for PROPERTY according to the first +column specification." + (interactive) + (let ((main-flag t) + (upcase-prop (upcase property))) + (dolist (spec org-columns-current-fmt-compiled) + (pcase spec + (`(,(pred (equal upcase-prop)) . ,_) + (org-columns--compute-spec spec main-flag) + ;; Only the first summary can update the property value. + (when main-flag (setq main-flag nil))))))) + +(defun org-columns-compute-all () + "Compute all columns that have operators defined." + (org-with-silent-modifications + (remove-text-properties (point-min) (point-max) '(org-summaries t))) + (let ((org-columns--time (float-time (current-time))) + seen) + (dolist (spec org-columns-current-fmt-compiled) + (let ((property (car spec))) + ;; Property value is updated only the first time a given + ;; property is encountered. + (org-columns--compute-spec spec (not (member property seen))) + (push property seen))))) + +(defun org-columns--summary-sum (values printf) + "Compute the sum of VALUES. +When PRINTF is non-nil, use it to format the result." + (format (or printf "%s") (apply #'+ (mapcar #'string-to-number values)))) + +(defun org-columns--summary-currencies (values _) + "Compute the sum of VALUES, with two decimals." + (format "%.2f" (apply #'+ (mapcar #'string-to-number values)))) + +(defun org-columns--summary-checkbox (check-boxes _) + "Summarize CHECK-BOXES with a check-box." + (let ((done (cl-count "[X]" check-boxes :test #'equal)) + (all (length check-boxes))) + (cond ((= done all) "[X]") + ((> done 0) "[-]") + (t "[ ]")))) + +(defun org-columns--summary-checkbox-count (check-boxes _) + "Summarize CHECK-BOXES with a check-box cookie." + (format "[%d/%d]" + (cl-count-if (lambda (b) (or (equal b "[X]") + (string-match-p "\\[\\([1-9]\\)/\\1\\]" b))) + check-boxes) + (length check-boxes))) + +(defun org-columns--summary-checkbox-percent (check-boxes _) + "Summarize CHECK-BOXES with a check-box percent." + (format "[%d%%]" + (round (* 100.0 (cl-count-if (lambda (b) (member b '("[X]" "[100%]"))) + check-boxes)) + (length check-boxes)))) + +(defun org-columns--summary-min (values printf) + "Compute the minimum of VALUES. +When PRINTF is non-nil, use it to format the result." + (format (or printf "%s") + (apply #'min (mapcar #'string-to-number values)))) + +(defun org-columns--summary-max (values printf) + "Compute the maximum of VALUES. +When PRINTF is non-nil, use it to format the result." + (format (or printf "%s") + (apply #'max (mapcar #'string-to-number values)))) + +(defun org-columns--summary-mean (values printf) + "Compute the mean of VALUES. +When PRINTF is non-nil, use it to format the result." + (format (or printf "%s") + (/ (apply #'+ (mapcar #'string-to-number values)) + (float (length values))))) + +(defun org-columns--summary-sum-times (times _) + "Sum TIMES." + (org-columns--summary-apply-times #'+ times)) + +(defun org-columns--summary-min-time (times _) + "Compute the minimum time among TIMES." + (org-columns--summary-apply-times #'min times)) + +(defun org-columns--summary-max-time (times _) + "Compute the maximum time among TIMES." + (org-columns--summary-apply-times #'max times)) + +(defun org-columns--summary-mean-time (times _) + "Compute the mean time among TIMES." + (org-columns--summary-apply-times + (lambda (&rest values) (/ (apply #'+ values) (float (length values)))) + times)) + +(defun org-columns--summary-min-age (ages _) + "Compute the minimum time among AGES." + (org-columns--format-age + (apply #'min (mapcar #'org-columns--age-to-minutes ages)))) + +(defun org-columns--summary-max-age (ages _) + "Compute the maximum time among AGES." + (org-columns--format-age + (apply #'max (mapcar #'org-columns--age-to-minutes ages)))) + +(defun org-columns--summary-mean-age (ages _) + "Compute the minimum time among AGES." + (org-columns--format-age + (/ (apply #'+ (mapcar #'org-columns--age-to-minutes ages)) + (float (length ages))))) + +(defun org-columns--summary-estimate (estimates _) + "Combine a list of estimates, using mean and variance. +The mean and variance of the result will be the sum of the means +and variances (respectively) of the individual estimates." + (let ((mean 0) + (var 0)) + (dolist (e estimates) + (pcase (mapcar #'string-to-number (split-string e "-")) + (`(,low ,high) + (let ((m (/ (+ low high) 2.0))) + (cl-incf mean m) + (cl-incf var (- (/ (+ (* low low) (* high high)) 2.0) (* m m))))) + (`(,value) (cl-incf mean value)))) + (let ((sd (sqrt var))) + (format "%s-%s" + (format "%.0f" (- mean sd)) + (format "%.0f" (+ mean sd)))))) + + + ;;; Dynamic block for Column view -(defvar org-heading-regexp) ; defined in org.el -(defvar org-heading-keyword-regexp-format) ; defined in org.el -(defun org-columns-capture-view (&optional maxlevel skip-empty-rows) - "Get the column view of the current buffer or subtree. -The first optional argument MAXLEVEL sets the level limit. A -second optional argument SKIP-EMPTY-ROWS tells whether to skip +(defun org-columns--capture-view (maxlevel skip-empty format local) + "Get the column view of the current buffer. + +MAXLEVEL sets the level limit. SKIP-EMPTY tells whether to skip empty rows, an empty row being one where all the column view -specifiers except ITEM are empty. This function returns a list -containing the title row and all other rows. Each row is a list -of fields." - (save-excursion - (let* ((title (mapcar 'cadr org-columns-current-fmt-compiled)) - (re-comment (format org-heading-keyword-regexp-format - org-comment-string)) - (re-archive (concat ".*:" org-archive-tag ":")) - (n (length title)) row tbl) - (goto-char (point-min)) - (while (re-search-forward org-heading-regexp nil t) - (catch 'next - (when (and (or (null maxlevel) - (>= maxlevel - (if org-odd-levels-only - (/ (1+ (length (match-string 1))) 2) - (length (match-string 1))))) - (get-char-property (match-beginning 0) 'org-columns-key)) - (when (save-excursion - (goto-char (point-at-bol)) - (or (looking-at re-comment) - (looking-at re-archive))) - (org-end-of-subtree t) - (throw 'next t)) - (setq row nil) - (loop for i from 0 to (1- n) do - (push - (org-quote-vert - (or (get-char-property (+ (match-beginning 0) i) 'org-columns-value-modified) - (get-char-property (+ (match-beginning 0) i) 'org-columns-value) - "")) - row)) - (setq row (nreverse row)) - (unless (and skip-empty-rows - (eq 1 (length (delete "" (delete-dups (copy-sequence row)))))) - (push row tbl))))) - (append (list title 'hline) (nreverse tbl))))) +specifiers but ITEM are empty. FORMAT is a format string for +columns, or nil. When LOCAL is non-nil, only capture headings in +current subtree. + +This function returns a list containing the title row and all +other rows. Each row is a list of fields, as strings, or +`hline'." + (org-columns (not local) format) + (goto-char org-columns-top-level-marker) + (let ((columns (length org-columns-current-fmt-compiled)) + (has-item (assoc "ITEM" org-columns-current-fmt-compiled)) + table) + (org-map-entries + (lambda () + (when (get-char-property (point) 'org-columns-key) + (let (row) + (dotimes (i columns) + (let* ((col (+ (line-beginning-position) i)) + (p (get-char-property col 'org-columns-key))) + (push (org-quote-vert + (get-char-property col + (if (string= p "ITEM") + 'org-columns-value + 'org-columns-value-modified))) + row))) + (unless (and skip-empty + (let ((r (delete-dups (remove "" row)))) + (or (null r) (and has-item (= (length r) 1))))) + (push (cons (org-reduced-level (org-current-level)) (nreverse row)) + table))))) + (and maxlevel (format "LEVEL<=%d" maxlevel)) + (and local 'tree) + 'archive 'comment) + (org-columns-quit) + ;; Add column titles and a horizontal rule in front of the table. + (cons (mapcar #'cadr org-columns-current-fmt-compiled) + (cons 'hline (nreverse table))))) + +(defun org-columns--clean-item (item) + "Remove sensitive contents from string ITEM. +This includes objects that may not be duplicated within +a document, e.g., a target, or those forbidden in tables, e.g., +an inline src-block." + (let ((data (org-element-parse-secondary-string + item (org-element-restriction 'headline)))) + (org-element-map data + '(footnote-reference inline-babel-call inline-src-block target + radio-target statistics-cookie) + #'org-element-extract-element) + (org-no-properties (org-element-interpret-data data)))) ;;;###autoload (defun org-dblock-write:columnview (params) "Write the column view table. PARAMS is a property list of parameters: -:width enforce same column widths with <N> specifiers. :id the :ID: property of the entry where the columns view should be built. When the symbol `local', call locally. When `global' call column view with the cursor at the beginning @@ -1247,339 +1368,268 @@ PARAMS is a property list of parameters: using `org-id-find'. :hlines When t, insert a hline before each item. When a number, insert a hline before each level <= that number. +:indent When non-nil, indent each ITEM field according to its level. :vlines When t, make each column a colgroup to enforce vertical lines. :maxlevel When set to a number, don't capture headlines below this level. :skip-empty-rows When t, skip rows where all specifiers other than ITEM are empty. +:width apply widths specified in columns format using <N> specifiers. :format When non-nil, specify the column view format to use." - (let ((pos (point-marker)) - (hlines (plist-get params :hlines)) - (vlines (plist-get params :vlines)) - (maxlevel (plist-get params :maxlevel)) - (content-lines (org-split-string (plist-get params :content) "\n")) - (skip-empty-rows (plist-get params :skip-empty-rows)) - (columns-fmt (plist-get params :format)) - (case-fold-search t) - tbl id idpos nfields tmp recalc line - id-as-string view-file view-pos) - (when (setq id (plist-get params :id)) - (setq id-as-string (cond ((numberp id) (number-to-string id)) - ((symbolp id) (symbol-name id)) - ((stringp id) id) - (t ""))) - (cond ((not id) nil) - ((eq id 'global) (setq view-pos (point-min))) - ((eq id 'local)) - ((string-match "^file:\\(.*\\)" id-as-string) - (setq view-file (match-string 1 id-as-string) - view-pos 1) - (unless (file-exists-p view-file) - (error "No such file: \"%s\"" id-as-string))) - ((setq idpos (org-find-entry-with-id id)) - (setq view-pos idpos)) - ((setq idpos (org-id-find id)) - (setq view-file (car idpos)) - (setq view-pos (cdr idpos))) - (t (error "Cannot find entry with :ID: %s" id)))) - (with-current-buffer (if view-file - (get-file-buffer view-file) - (current-buffer)) - (save-excursion - (save-restriction - (widen) - (goto-char (or view-pos (point))) - (org-columns columns-fmt) - (setq tbl (org-columns-capture-view maxlevel skip-empty-rows)) - (setq nfields (length (car tbl))) - (org-columns-quit)))) - (goto-char pos) - (move-marker pos nil) - (when tbl - (when (plist-get params :hlines) - (setq tmp nil) - (while tbl - (if (eq (car tbl) 'hline) - (push (pop tbl) tmp) - (if (string-match "\\` *\\(\\*+\\)" (caar tbl)) - (if (and (not (eq (car tmp) 'hline)) - (or (eq hlines t) - (and (numberp hlines) - (<= (- (match-end 1) (match-beginning 1)) - hlines)))) - (push 'hline tmp))) - (push (pop tbl) tmp))) - (setq tbl (nreverse tmp))) - (when vlines - (setq tbl (mapcar (lambda (x) - (if (eq 'hline x) x (cons "" x))) - tbl)) - (setq tbl (append tbl (list (cons "/" (make-list nfields "<>")))))) - (when content-lines - (while (string-match "^#" (car content-lines)) - (insert (pop content-lines) "\n"))) - (setq pos (point)) - (insert (org-listtable-to-string tbl)) + (let ((table + (let ((id (plist-get params :id)) + view-file view-pos) + (pcase id + (`global nil) + ((or `local `nil) (setq view-pos (point))) + ((and (let id-string (format "%s" id)) + (guard (string-match "^file:\\(.*\\)" id-string))) + (setq view-file (match-string-no-properties 1 id-string)) + (unless (file-exists-p view-file) + (user-error "No such file: %S" id-string))) + ((and (let idpos (org-find-entry-with-id id)) (guard idpos)) + (setq view-pos idpos)) + ((let `(,filename . ,position) (org-id-find id)) + (setq view-file filename) + (setq view-pos position)) + (_ (user-error "Cannot find entry with :ID: %s" id))) + (with-current-buffer (if view-file (get-file-buffer view-file) + (current-buffer)) + (org-with-wide-buffer + (when view-pos (goto-char view-pos)) + (org-columns--capture-view (plist-get params :maxlevel) + (plist-get params :skip-empty-rows) + (plist-get params :format) + view-pos)))))) + (when table + ;; Prune level information from the table. Also normalize + ;; headings: remove stars, add indentation entities, if + ;; required, and possibly precede some of them with a horizontal + ;; rule. + (let ((item-index + (let ((p (assoc "ITEM" org-columns-current-fmt-compiled))) + (and p (cl-position p + org-columns-current-fmt-compiled + :test #'equal)))) + (hlines (plist-get params :hlines)) + (indent (plist-get params :indent)) + new-table) + ;; Copy header and first rule. + (push (pop table) new-table) + (push (pop table) new-table) + (dolist (row table (setq table (nreverse new-table))) + (let ((level (car row))) + (when (and (not (eq (car new-table) 'hline)) + (or (eq hlines t) + (and (numberp hlines) (<= level hlines)))) + (push 'hline new-table)) + (when item-index + (let ((item (org-columns--clean-item (nth item-index (cdr row))))) + (setf (nth item-index (cdr row)) + (if (and indent (> level 1)) + (concat "\\_" (make-string (* 2 (1- level)) ?\s) item) + item)))) + (push (cdr row) new-table)))) (when (plist-get params :width) - (insert "\n|" (mapconcat (lambda (x) (format "<%d>" (max 3 x))) - org-columns-current-widths "|"))) - (while (setq line (pop content-lines)) - (when (string-match "^#" line) - (insert "\n" line) - (when (string-match "^[ \t]*#\\+tblfm" line) - (setq recalc t)))) - (if recalc - (progn (goto-char pos) (org-table-recalculate 'all)) - (goto-char pos) + (setq table + (append table + (list + (mapcar (lambda (spec) + (let ((w (nth 2 spec))) + (if w (format "<%d>" (max 3 w)) ""))) + org-columns-current-fmt-compiled))))) + (when (plist-get params :vlines) + (setq table + (let ((size (length org-columns-current-fmt-compiled))) + (append (mapcar (lambda (x) (if (eq 'hline x) x (cons "" x))) + table) + (list (cons "/" (make-list size "<>"))))))) + (let ((content-lines (org-split-string (plist-get params :content) "\n")) + recalc) + ;; Insert affiliated keywords before the table. + (when content-lines + (while (string-match-p "\\`[ \t]*#\\+" (car content-lines)) + (insert (pop content-lines) "\n"))) + (save-excursion + ;; Insert table at point. + (insert + (mapconcat (lambda (row) + (if (eq row 'hline) "|-|" + (format "|%s|" (mapconcat #'identity row "|")))) + table + "\n")) + ;; Insert TBLFM lines following table. + (let ((case-fold-search t)) + (dolist (line content-lines) + (when (string-match-p "\\`[ \t]*#\\+TBLFM:" line) + (insert "\n" line) + (unless recalc (setq recalc t)))))) + (when recalc (org-table-recalculate 'all t)) (org-table-align))))) -(defun org-listtable-to-string (tbl) - "Convert a listtable TBL to a string that contains the Org-mode table. -The table still need to be aligned. The resulting string has no leading -and tailing newline characters." - (mapconcat - (lambda (x) - (cond - ((listp x) - (concat "|" (mapconcat 'identity x "|") "|")) - ((eq x 'hline) "|-|") - (t (error "Garbage in listtable: %s" x)))) - tbl "\n")) - ;;;###autoload -(defun org-insert-columns-dblock () +(defun org-columns-insert-dblock () "Create a dynamic block capturing a column view table." (interactive) - (let ((defaults '(:name "columnview" :hlines 1)) - (id (org-icompleting-read + (let ((id (completing-read "Capture columns (local, global, entry with :ID: property) [local]: " (append '(("global") ("local")) - (mapcar 'list (org-property-values "ID")))))) - (if (equal id "") (setq id 'local)) - (if (equal id "global") (setq id 'global)) - (setq defaults (append defaults (list :id id))) - (org-create-dblock defaults) - (org-update-dblock))) + (mapcar #'list (org-property-values "ID")))))) + (org-create-dblock + (list :name "columnview" + :hlines 1 + :id (cond ((string= id "global") 'global) + ((member id '("" "local")) 'local) + (id))))) + (org-update-dblock)) -;;; Column view in the agenda - -(defvar org-agenda-view-columns-initially nil - "When set, switch to columns view immediately after creating the agenda.") -(defvar org-agenda-columns-show-summaries) ; defined in org-agenda.el -(defvar org-agenda-columns-compute-summary-properties); defined in org-agenda.el -(defvar org-agenda-columns-add-appointments-to-effort-sum); as well + +;;; Column view in the agenda ;;;###autoload (defun org-agenda-columns () "Turn on or update column view in the agenda." (interactive) - (org-verify-version 'columns) (org-columns-remove-overlays) - (move-marker org-columns-begin-marker (point)) - (let ((org-columns-time (time-to-number-of-days (current-time))) - cache maxwidths m p a d fmt) - (cond - ((and (boundp 'org-agenda-overriding-columns-format) - org-agenda-overriding-columns-format) - (setq fmt org-agenda-overriding-columns-format)) - ((setq m (org-get-at-bol 'org-hd-marker)) - (setq fmt (or (org-entry-get m "COLUMNS" t) - (with-current-buffer (marker-buffer m) - org-columns-default-format)))) - ((and (boundp 'org-columns-current-fmt) - (local-variable-p 'org-columns-current-fmt) - org-columns-current-fmt) - (setq fmt org-columns-current-fmt)) - ((setq m (next-single-property-change (point-min) 'org-hd-marker)) - (setq m (get-text-property m 'org-hd-marker)) - (setq fmt (or (org-entry-get m "COLUMNS" t) - (with-current-buffer (marker-buffer m) - org-columns-default-format))))) - (setq fmt (or fmt org-columns-default-format)) - (org-set-local 'org-columns-current-fmt fmt) - (org-columns-compile-format fmt) + (if (markerp org-columns-begin-marker) + (move-marker org-columns-begin-marker (point)) + (setq org-columns-begin-marker (point-marker))) + (let* ((org-columns--time (float-time (current-time))) + (fmt + (cond + ((bound-and-true-p org-agenda-overriding-columns-format)) + ((let ((m (org-get-at-bol 'org-hd-marker))) + (and m + (or (org-entry-get m "COLUMNS" t) + (with-current-buffer (marker-buffer m) + org-columns-default-format))))) + ((and (local-variable-p 'org-columns-current-fmt) + org-columns-current-fmt)) + ((let ((m (next-single-property-change (point-min) 'org-hd-marker))) + (and m + (let ((m (get-text-property m 'org-hd-marker))) + (or (org-entry-get m "COLUMNS" t) + (with-current-buffer (marker-buffer m) + org-columns-default-format)))))) + (t org-columns-default-format))) + (compiled-fmt (org-columns-compile-format fmt))) + (setq org-columns-current-fmt fmt) (when org-agenda-columns-compute-summary-properties (org-agenda-colview-compute org-columns-current-fmt-compiled)) (save-excursion - ;; Get and cache the properties + ;; Collect properties for each headline in current view. (goto-char (point-min)) - (while (not (eobp)) - (when (setq m (or (org-get-at-bol 'org-hd-marker) - (org-get-at-bol 'org-marker))) - (setq p (org-entry-properties m)) - - (when (or (not (setq a (assoc org-effort-property p))) - (not (string-match "\\S-" (or (cdr a) "")))) - ;; OK, the property is not defined. Use appointment duration? - (when (and org-agenda-columns-add-appointments-to-effort-sum - (setq d (get-text-property (point) 'duration))) - (setq d (org-minutes-to-clocksum-string d)) - (put-text-property 0 (length d) 'face 'org-warning d) - (push (cons org-effort-property d) p))) - (push (cons (org-current-line) p) cache)) - (beginning-of-line 2)) - (when cache - (setq maxwidths (org-columns-get-autowidth-alist fmt cache)) - (org-set-local 'org-columns-current-maxwidths maxwidths) - (org-columns-display-here-title) - (when (org-set-local 'org-columns-flyspell-was-active - (org-bound-and-true-p flyspell-mode)) - (flyspell-mode 0)) - (mapc (lambda (x) - (org-goto-line (car x)) - (org-columns-display-here (cdr x))) - cache) - (when org-agenda-columns-show-summaries - (org-agenda-colview-summarize cache)))))) + (let (cache) + (while (not (eobp)) + (let ((m (org-get-at-bol 'org-hd-marker))) + (when m + (push (cons (line-beginning-position) + ;; `org-columns-current-fmt-compiled' is + ;; initialized but only set locally to the + ;; agenda buffer. Since current buffer is + ;; changing, we need to force the original + ;; compiled-fmt there. + (org-with-point-at m + (org-columns--collect-values compiled-fmt))) + cache))) + (forward-line)) + (when cache + (org-columns--set-widths cache) + (org-columns--display-here-title) + (when (setq-local org-columns-flyspell-was-active + (bound-and-true-p flyspell-mode)) + (flyspell-mode 0)) + (dolist (entry cache) + (goto-char (car entry)) + (org-columns--display-here (cdr entry))) + (when org-agenda-columns-show-summaries + (org-agenda-colview-summarize cache))))))) (defun org-agenda-colview-summarize (cache) "Summarize the summarizable columns in column view in the agenda. This will add overlays to the date lines, to show the summary for each day." - (let* ((fmt (mapcar (lambda (x) - (if (string-match "CLOCKSUM.*" (car x)) - (list (match-string 0 (car x)) - (nth 1 x) (nth 2 x) ":" 'add_times - nil '+ nil) - x)) - org-columns-current-fmt-compiled)) - line c c1 stype calc sumfunc props lsum entries prop v title) - (catch 'exit - (when (delq nil (mapcar 'cadr fmt)) - ;; OK, at least one summation column, it makes sense to try this - (goto-char (point-max)) + (let ((fmt (mapcar + (lambda (spec) + (pcase spec + (`(,property ,title ,width . ,_) + (if (member property '("CLOCKSUM" "CLOCKSUM_T")) + (list property title width ":" nil) + spec)))) + org-columns-current-fmt-compiled))) + ;; Ensure there's at least one summation column. + (when (cl-some (lambda (spec) (nth 3 spec)) fmt) + (goto-char (point-max)) + (catch :complete (while t (when (or (get-text-property (point) 'org-date-line) (eq (get-text-property (point) 'face) 'org-agenda-structure)) - ;; OK, this is a date line that should be used - (setq line (org-current-line)) - (setq entries nil c cache cache nil) - (while (setq c1 (pop c)) - (if (> (car c1) line) - (push c1 entries) - (push c1 cache))) - ;; now ENTRIES are the ones we want to use, CACHE is the rest - ;; Compute the summaries for the properties we want, - ;; set nil properties for the rest. - (when (setq entries (mapcar 'cdr entries)) - (setq props - (mapcar - (lambda (f) - (setq prop (car f) - title (nth 1 f) - stype (nth 4 f) - sumfunc (nth 6 f) - calc (or (nth 7 f) 'identity)) - (cond - ((equal prop "ITEM") - (cons prop (buffer-substring (point-at-bol) - (point-at-eol)))) - ((not stype) (cons prop "")) - (t ;; do the summary - (setq lsum nil) - (dolist (x entries) - (setq v (cdr (assoc prop x))) - (if v - (push - (funcall - (if (not (get-text-property 0 'org-computed v)) - calc - 'identity) - (org-columns-string-to-number - v stype)) - lsum))) - (setq lsum (remove nil lsum)) - (setq lsum - (cond ((> (length lsum) 1) - (org-columns-number-to-string - (apply sumfunc lsum) stype)) - ((eq (length lsum) 1) - (org-columns-number-to-string - (car lsum) stype)) - (t ""))) - (put-text-property 0 (length lsum) 'face 'bold lsum) - (unless (eq calc 'identity) - (put-text-property 0 (length lsum) 'org-computed t lsum)) - (cons prop lsum)))) - fmt)) - (org-columns-display-here props 'dateline) - (org-set-local 'org-agenda-columns-active t))) - (if (bobp) (throw 'exit t)) - (beginning-of-line 0)))))) + ;; OK, this is a date line that should be used. + (let (entries) + (let (rest) + (dolist (c cache) + (if (> (car c) (point)) + (push c entries) + (push c rest))) + (setq cache rest)) + ;; ENTRIES contains entries below the current one. + ;; CACHE is the rest. Compute the summaries for the + ;; properties we want, set nil properties for the rest. + (when (setq entries (mapcar #'cdr entries)) + (org-columns--display-here + (mapcar + (lambda (spec) + (pcase spec + (`("ITEM" . ,_) + ;; Replace ITEM with current date. Preserve + ;; properties for fontification. + (let ((date (buffer-substring + (line-beginning-position) + (line-end-position)))) + (list spec date date))) + (`(,_ ,_ ,_ nil ,_) (list spec "" "")) + (`(,_ ,_ ,_ ,operator ,printf) + (let* ((summarize (org-columns--summarize operator)) + (values + ;; Use real values for summary, not + ;; those prepared for display. + (delq nil + (mapcar + (lambda (e) (org-string-nw-p + (nth 1 (assoc spec e)))) + entries))) + (final (if values + (funcall summarize values printf) + ""))) + (unless (equal final "") + (put-text-property 0 (length final) + 'face 'bold final)) + (list spec final final))))) + fmt) + 'dateline) + (setq-local org-agenda-columns-active t)))) + (if (bobp) (throw :complete t) (forward-line -1))))))) (defun org-agenda-colview-compute (fmt) "Compute the relevant columns in the contributing source buffers." - (let ((files org-agenda-contributing-files) - (org-columns-begin-marker (make-marker)) - (org-columns-top-level-marker (make-marker)) - f fm a b) - (while (setq f (pop files)) - (setq b (find-buffer-visiting f)) + (dolist (file org-agenda-contributing-files) + (let ((b (find-buffer-visiting file))) (with-current-buffer (or (buffer-base-buffer b) b) - (save-excursion - (save-restriction - (widen) - (org-with-silent-modifications - (remove-text-properties (point-min) (point-max) '(org-summaries t))) - (goto-char (point-min)) - (org-columns-get-format-and-top-level) - (while (setq fm (pop fmt)) - (cond ((equal (car fm) "CLOCKSUM") - (org-clock-sum)) - ((equal (car fm) "CLOCKSUM_T") - (org-clock-sum-today)) - ((and (nth 4 fm) - (setq a (assoc (car fm) - org-columns-current-fmt-compiled)) - (equal (nth 4 a) (nth 4 fm))) - (org-columns-compute (car fm))))))))))) - -(defun org-format-time-period (interval) - "Convert time in fractional days to days/hours/minutes/seconds." - (if (numberp interval) - (let* ((days (floor interval)) - (frac-hours (* 24 (- interval days))) - (hours (floor frac-hours)) - (minutes (floor (* 60 (- frac-hours hours)))) - (seconds (floor (* 60 (- (* 60 (- frac-hours hours)) minutes))))) - (format "%dd %02dh %02dm %02ds" days hours minutes seconds)) - "")) - -(defun org-estimate-mean-and-var (v) - "Return the mean and variance of an estimate." - (let* ((low (float (car v))) - (high (float (cadr v))) - (mean (/ (+ low high) 2.0)) - (var (/ (+ (expt (- mean low) 2.0) (expt (- high mean) 2.0)) 2.0))) - (list mean var))) - -(defun org-estimate-combine (&rest el) - "Combine a list of estimates, using mean and variance. -The mean and variance of the result will be the sum of the means -and variances (respectively) of the individual estimates." - (let ((mean 0) - (var 0)) - (mapc (lambda (e) - (let ((stats (org-estimate-mean-and-var e))) - (setq mean (+ mean (car stats))) - (setq var (+ var (cadr stats))))) - el) - (let ((stdev (sqrt var))) - (list (- mean stdev) (+ mean stdev))))) - -(defun org-estimate-print (e &optional fmt) - "Prepare a string representation of an estimate. -This formats these numbers as two numbers with a \"-\" between them." - (if (null fmt) (set 'fmt "%.0f")) - (format "%s" (mapconcat (lambda (n) (format fmt n)) e "-"))) - -(defun org-string-to-estimate (s) - "Convert a string to an estimate. -The string should be two numbers joined with a \"-\"." - (if (string-match "\\(.*\\)-\\(.*\\)" s) - (list (string-to-number (match-string 1 s)) - (string-to-number(match-string 2 s))) - (list (string-to-number s) (string-to-number s)))) + (org-with-wide-buffer + (org-with-silent-modifications + (remove-text-properties (point-min) (point-max) '(org-summaries t))) + (goto-char (point-min)) + (org-columns-get-format-and-top-level) + (dolist (spec fmt) + (let ((prop (car spec))) + (cond + ((equal prop "CLOCKSUM") (org-clock-sum)) + ((equal prop "CLOCKSUM_T") (org-clock-sum-today)) + ((and (nth 3 spec) + (let ((a (assoc prop org-columns-current-fmt-compiled))) + (equal (nth 3 a) (nth 3 spec)))) + (org-columns-compute prop)))))))))) + (provide 'org-colview) diff --git a/lisp/org/org-compat.el b/lisp/org/org-compat.el index 42e2271c076..c963f06b559 100644 --- a/lisp/org/org-compat.el +++ b/lisp/org/org-compat.el @@ -1,4 +1,4 @@ -;;; org-compat.el --- Compatibility code for Org-mode +;;; org-compat.el --- Compatibility Code for Older Emacsen -*- lexical-binding: t; -*- ;; Copyright (C) 2004-2017 Free Software Foundation, Inc. @@ -19,198 +19,422 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Commentary: -;; This file contains code needed for compatibility with XEmacs and older +;; This file contains code needed for compatibility with older ;; versions of GNU Emacs. ;;; Code: -(eval-when-compile - (require 'cl)) - +(require 'cl-lib) (require 'org-macs) -;; The following constant is for backward compatibility. We do not use -;; it in org-mode, because the Byte compiler evaluates (featurep 'xemacs) -;; at compilation time and can therefore optimize code better. -(defconst org-xemacs-p (featurep 'xemacs)) -(defconst org-format-transports-properties-p - (let ((x "a")) - (add-text-properties 0 1 '(test t) x) - (get-text-property 0 'test (format "%s" x))) - "Does format transport text properties?") +(declare-function org-at-table.el-p "org" (&optional table-type)) +(declare-function org-element-at-point "org-element" ()) +(declare-function org-element-type "org-element" (element)) +(declare-function org-end-of-subtree "org" (&optional invisible-ok to-heading)) +(declare-function org-link-set-parameters "org" (type &rest rest)) +(declare-function org-table-end (&optional table-type)) +(declare-function outline-next-heading "outline" ()) +(declare-function table--at-cell-p "table" (position &optional object at-column)) + +(defvar org-table-any-border-regexp) +(defvar org-table-dataline-regexp) +(defvar org-table-tab-recognizes-table.el) +(defvar org-table1-hline-regexp) + +;;; Emacs < 25.1 compatibility + +(when (< emacs-major-version 25) + (defalias 'outline-hide-entry 'hide-entry) + (defalias 'outline-hide-sublevels 'hide-sublevels) + (defalias 'outline-hide-subtree 'hide-subtree) + (defalias 'outline-show-all 'show-all) + (defalias 'outline-show-branches 'show-branches) + (defalias 'outline-show-children 'show-children) + (defalias 'outline-show-entry 'show-entry) + (defalias 'outline-show-subtree 'show-subtree) + (defalias 'xref-find-definitions 'find-tag) + (defalias 'format-message 'format) + (defalias 'gui-get-selection 'x-get-selection)) + +(defun org-decode-time (&optional time zone) + "Backward-compatible function for `decode-time'." + (if (< emacs-major-version 25) + (decode-time time) + (decode-time time zone))) + +(unless (fboundp 'directory-name-p) + (defun directory-name-p (name) + "Return non-nil if NAME ends with a directory separator character." + (let ((len (length name)) + (lastc ?.)) + (if (> len 0) + (setq lastc (aref name (1- len)))) + (or (= lastc ?/) + (and (memq system-type '(windows-nt ms-dos)) + (= lastc ?\\)))))) + +(unless (fboundp 'directory-files-recursively) + (defun directory-files-recursively (dir regexp &optional include-directories) + "Return list of all files under DIR that have file names matching REGEXP. +This function works recursively. Files are returned in \"depth first\" +order, and files from each directory are sorted in alphabetical order. +Each file name appears in the returned list in its absolute form. +Optional argument INCLUDE-DIRECTORIES non-nil means also include in the +output directories whose names match REGEXP." + (let ((result nil) + (files nil) + ;; When DIR is "/", remote file names like "/method:" could + ;; also be offered. We shall suppress them. + (tramp-mode (and tramp-mode (file-remote-p (expand-file-name dir))))) + (dolist (file (sort (file-name-all-completions "" dir) + 'string<)) + (unless (member file '("./" "../")) + (if (directory-name-p file) + (let* ((leaf (substring file 0 (1- (length file)))) + (full-file (expand-file-name leaf dir))) + ;; Don't follow symlinks to other directories. + (unless (file-symlink-p full-file) + (setq result + (nconc result (directory-files-recursively + full-file regexp include-directories)))) + (when (and include-directories + (string-match regexp leaf)) + (setq result (nconc result (list full-file))))) + (when (string-match regexp file) + (push (expand-file-name file dir) files))))) + (nconc result (nreverse files))))) + + +;;; Obsolete aliases (remove them after the next major release). + +;;;; XEmacs compatibility, now removed. +(define-obsolete-function-alias 'org-activate-mark 'activate-mark) +(define-obsolete-function-alias 'org-add-hook 'add-hook "Org 9.0") +(define-obsolete-function-alias 'org-bound-and-true-p 'bound-and-true-p "Org 9.0") +(define-obsolete-function-alias 'org-decompose-region 'decompose-region "Org 9.0") +(define-obsolete-function-alias 'org-defvaralias 'defvaralias "Org 9.0") +(define-obsolete-function-alias 'org-detach-overlay 'delete-overlay "Org 9.0") +(define-obsolete-function-alias 'org-file-equal-p 'file-equal-p "Org 9.0") +(define-obsolete-function-alias 'org-float-time 'float-time "Org 9.0") +(define-obsolete-function-alias 'org-indent-line-to 'indent-line-to "Org 9.0") +(define-obsolete-function-alias 'org-indent-to-column 'indent-to-column "Org 9.0") +(define-obsolete-function-alias 'org-looking-at-p 'looking-at-p "Org 9.0") +(define-obsolete-function-alias 'org-looking-back 'looking-back "Org 9.0") +(define-obsolete-function-alias 'org-match-string-no-properties 'match-string-no-properties "Org 9.0") +(define-obsolete-function-alias 'org-propertize 'propertize "Org 9.0") +(define-obsolete-function-alias 'org-select-frame-set-input-focus 'select-frame-set-input-focus "Org 9.0") + +(defmacro org-re (s) + "Replace posix classes in regular expression S." + (declare (debug (form)) + (obsolete "you can safely remove it." "Org 9.0")) + s) + +;;;; Functions from cl-lib that Org used to have its own implementation of. +(define-obsolete-function-alias 'org-count 'cl-count "Org 9.0") +(define-obsolete-function-alias 'org-every 'cl-every "Org 9.0") +(define-obsolete-function-alias 'org-find-if 'cl-find-if "Org 9.0") +(define-obsolete-function-alias 'org-reduce 'cl-reduce "Org 9.0") +(define-obsolete-function-alias 'org-remove-if 'cl-remove-if "Org 9.0") +(define-obsolete-function-alias 'org-remove-if-not 'cl-remove-if-not "Org 9.0") +(define-obsolete-function-alias 'org-some 'cl-some "Org 9.0") +(define-obsolete-function-alias 'org-floor* 'cl-floor "Org 9.0") + +(defun org-sublist (list start end) + "Return a section of LIST, from START to END. +Counting starts at 1." + (cl-subseq list (1- start) end)) +(make-obsolete 'org-sublist + "use cl-subseq (note the 0-based counting)." + "Org 9.0") + + +;;;; Functions available since Emacs 24.3 +(define-obsolete-function-alias 'org-buffer-narrowed-p 'buffer-narrowed-p "Org 9.0") +(define-obsolete-function-alias 'org-called-interactively-p 'called-interactively-p "Org 9.0") +(define-obsolete-function-alias 'org-char-to-string 'char-to-string "Org 9.0") +(define-obsolete-function-alias 'org-delete-directory 'delete-directory "Org 9.0") +(define-obsolete-function-alias 'org-format-seconds 'format-seconds "Org 9.0") +(define-obsolete-function-alias 'org-link-escape-browser 'url-encode-url "Org 9.0") +(define-obsolete-function-alias 'org-no-warnings 'with-no-warnings "Org 9.0") +(define-obsolete-function-alias 'org-number-sequence 'number-sequence "Org 9.0") +(define-obsolete-function-alias 'org-pop-to-buffer-same-window 'pop-to-buffer-same-window "Org 9.0") +(define-obsolete-function-alias 'org-string-match-p 'string-match-p "Org 9.0") + +;;;; Functions and variables from previous releases now obsolete. +(define-obsolete-function-alias 'org-element-remove-indentation + 'org-remove-indentation "Org 9.0") +(define-obsolete-variable-alias 'org-latex-create-formula-image-program + 'org-preview-latex-default-process "Org 9.0") +(define-obsolete-variable-alias 'org-latex-preview-ltxpng-directory + 'org-preview-latex-image-directory "Org 9.0") +(define-obsolete-function-alias 'org-table-p 'org-at-table-p "Org 9.0") +(define-obsolete-function-alias 'org-on-heading-p 'org-at-heading-p "Org 9.0") +(define-obsolete-function-alias 'org-at-regexp-p 'org-in-regexp "Org 8.3") +(define-obsolete-function-alias 'org-image-file-name-regexp + 'image-file-name-regexp "Org 9.0") +(define-obsolete-function-alias 'org-completing-read-no-i + 'completing-read "Org 9.0") +(define-obsolete-function-alias 'org-icompleting-read + 'completing-read "Org 9.0") +(define-obsolete-function-alias 'org-iread-file-name 'read-file-name "Org 9.0") +(define-obsolete-function-alias 'org-days-to-time + 'org-time-stamp-to-now "Org 8.2") +(define-obsolete-variable-alias 'org-agenda-ignore-drawer-properties + 'org-agenda-ignore-properties "Org 9.0") +(define-obsolete-function-alias 'org-preview-latex-fragment + 'org-toggle-latex-fragment "Org 8.3") +(define-obsolete-function-alias 'org-export-get-genealogy + 'org-element-lineage "Org 9.0") +(define-obsolete-variable-alias 'org-latex-with-hyperref + 'org-latex-hyperref-template "Org 9.0") +(define-obsolete-variable-alias 'hfy-optimisations 'hfy-optimizations "Org 9.0") +(define-obsolete-variable-alias 'org-export-htmlized-org-css-url + 'org-org-htmlized-css-url "Org 8.2") +(define-obsolete-function-alias 'org-list-parse-list 'org-list-to-lisp "Org 9.0") +(define-obsolete-function-alias 'org-agenda-todayp + 'org-agenda-today-p "Org 9.0") +(define-obsolete-function-alias 'org-babel-examplize-region + 'org-babel-examplify-region "Org 9.0") +(define-obsolete-variable-alias 'org-babel-capitalize-example-region-markers + 'org-babel-uppercase-example-markers "Org 9.1") + +(define-obsolete-function-alias 'org-babel-trim 'org-trim "Org 9.0") +(define-obsolete-variable-alias 'org-html-style 'org-html-head "24.4") +(define-obsolete-function-alias 'org-insert-columns-dblock + 'org-columns-insert-dblock "Org 9.0") +(define-obsolete-variable-alias 'org-export-babel-evaluate + 'org-export-use-babel "Org 9.1") +(define-obsolete-function-alias 'org-activate-bracket-links + 'org-activate-links "Org 9.0") +(define-obsolete-function-alias 'org-activate-plain-links 'ignore "Org 9.0") +(define-obsolete-function-alias 'org-activate-angle-links 'ignore "Org 9.0") + +(defun org-in-fixed-width-region-p () + "Non-nil if point in a fixed-width region." + (save-match-data + (eq 'fixed-width (org-element-type (org-element-at-point))))) +(make-obsolete 'org-in-fixed-width-region-p + "use `org-element' library" + "Org 9.0") (defun org-compatible-face (inherits specs) "Make a compatible face specification. -If INHERITS is an existing face and if the Emacs version supports it, -just inherit the face. If INHERITS is set and the Emacs version does -not support it, copy the face specification from the inheritance face. -If INHERITS is not given and SPECS is, use SPECS to define the face. -XEmacs and Emacs 21 do not know about the `min-colors' attribute. -For them we convert a (min-colors 8) entry to a `tty' entry and move it -to the top of the list. The `min-colors' attribute will be removed from -any other entries, and any resulting duplicates will be removed entirely." - (when (and inherits (facep inherits) (not specs)) - (setq specs (or specs - (get inherits 'saved-face) - (get inherits 'face-defface-spec)))) - (cond - ((and inherits (facep inherits) - (not (featurep 'xemacs)) - (>= emacs-major-version 22) - ;; do not inherit outline faces before Emacs 23 - (or (>= emacs-major-version 23) - (not (string-match "\\`outline-[0-9]+" - (symbol-name inherits))))) - (list (list t :inherit inherits))) - ((or (featurep 'xemacs) (< emacs-major-version 22)) - ;; These do not understand the `min-colors' attribute. - (let (r e a) - (while (setq e (pop specs)) - (cond - ((memq (car e) '(t default)) (push e r)) - ((setq a (member '(min-colors 8) (car e))) - (nconc r (list (cons (cons '(type tty) (delq (car a) (car e))) - (cdr e))))) - ((setq a (assq 'min-colors (car e))) - (setq e (cons (delq a (car e)) (cdr e))) - (or (assoc (car e) r) (push e r))) - (t (or (assoc (car e) r) (push e r))))) - (nreverse r))) - (t specs))) -(put 'org-compatible-face 'lisp-indent-function 1) +If INHERITS is an existing face and if the Emacs version supports +it, just inherit the face. If INHERITS is not given and SPECS +is, use SPECS to define the face." + (declare (indent 1)) + (if (facep inherits) + (list (list t :inherit inherits)) + specs)) +(make-obsolete 'org-compatible-face "you can remove it." "Org 9.0") + +(defun org-add-link-type (type &optional follow export) + "Add a new TYPE link. +FOLLOW and EXPORT are two functions. + +FOLLOW should take the link path as the single argument and do whatever +is necessary to follow the link, for example find a file or display +a mail message. + +EXPORT should format the link path for export to one of the export formats. +It should be a function accepting three arguments: + + path the path of the link, the text after the prefix (like \"http:\") + desc the description of the link, if any + format the export format, a symbol like `html' or `latex' or `ascii'. + +The function may use the FORMAT information to return different values +depending on the format. The return value will be put literally into +the exported file. If the return value is nil, this means Org should +do what it normally does with links which do not have EXPORT defined. + +Org mode has a built-in default for exporting links. If you are happy with +this default, there is no need to define an export function for the link +type. For a simple example of an export function, see `org-bbdb.el'. + +If TYPE already exists, update it with the arguments. +See `org-link-parameters' for documentation on the other parameters." + (org-link-set-parameters type :follow follow :export export) + (message "Created %s link." type)) + +(make-obsolete 'org-add-link-type "use `org-link-set-parameters' instead." "Org 9.0") + +(defun org-table-recognize-table.el () + "If there is a table.el table nearby, recognize it and move into it." + (when (and org-table-tab-recognizes-table.el (org-at-table.el-p)) + (beginning-of-line) + (unless (or (looking-at org-table-dataline-regexp) + (not (looking-at org-table1-hline-regexp))) + (forward-line) + (when (looking-at org-table-any-border-regexp) + (forward-line -2))) + (if (re-search-forward "|" (org-table-end t) t) + (progn + (require 'table) + (if (table--at-cell-p (point)) t + (message "recognizing table.el table...") + (table-recognize-table) + (message "recognizing table.el table...done"))) + (error "This should not happen")))) + +;; Not used by Org core since commit 6d1e3082, Feb 2010. +(make-obsolete 'org-table-recognize-table.el + "please notify the org mailing list if you use this function." + "Org 9.0") + +(defun org-remove-angle-brackets (s) + (org-unbracket-string "<" ">" s)) +(make-obsolete 'org-remove-angle-brackets 'org-unbracket-string "Org 9.0") + +(defun org-remove-double-quotes (s) + (org-unbracket-string "\"" "\"" s)) +(make-obsolete 'org-remove-double-quotes 'org-unbracket-string "Org 9.0") + +(defcustom org-publish-sitemap-file-entry-format "%t" + "Format string for site-map file entry. +You could use brackets to delimit on what part the link will be. + +%t is the title. +%a is the author. +%d is the date formatted using `org-publish-sitemap-date-format'." + :group 'org-export-publish + :type 'string) +(make-obsolete-variable + 'org-publish-sitemap-file-entry-format + "set `:sitemap-format-entry' in `org-publish-project-alist' instead." + "Org 9.1") + +(defvar org-agenda-skip-regexp) +(defun org-agenda-skip-entry-when-regexp-matches () + "Check if the current entry contains match for `org-agenda-skip-regexp'. +If yes, it returns the end position of this entry, causing agenda commands +to skip the entry but continuing the search in the subtree. This is a +function that can be put into `org-agenda-skip-function' for the duration +of a command." + (declare (obsolete "use `org-agenda-skip-if' instead." "Org 9.1")) + (let ((end (save-excursion (org-end-of-subtree t))) + skip) + (save-excursion + (setq skip (re-search-forward org-agenda-skip-regexp end t))) + (and skip end))) + +(defun org-agenda-skip-subtree-when-regexp-matches () + "Check if the current subtree contains match for `org-agenda-skip-regexp'. +If yes, it returns the end position of this tree, causing agenda commands +to skip this subtree. This is a function that can be put into +`org-agenda-skip-function' for the duration of a command." + (declare (obsolete "use `org-agenda-skip-if' instead." "Org 9.1")) + (let ((end (save-excursion (org-end-of-subtree t))) + skip) + (save-excursion + (setq skip (re-search-forward org-agenda-skip-regexp end t))) + (and skip end))) + +(defun org-agenda-skip-entry-when-regexp-matches-in-subtree () + "Check if the current subtree contains match for `org-agenda-skip-regexp'. +If yes, it returns the end position of the current entry (NOT the tree), +causing agenda commands to skip the entry but continuing the search in +the subtree. This is a function that can be put into +`org-agenda-skip-function' for the duration of a command. An important +use of this function is for the stuck project list." + (declare (obsolete "use `org-agenda-skip-if' instead." "Org 9.1")) + (let ((end (save-excursion (org-end-of-subtree t))) + (entry-end (save-excursion (outline-next-heading) (1- (point)))) + skip) + (save-excursion + (setq skip (re-search-forward org-agenda-skip-regexp end t))) + (and skip entry-end))) + +(define-obsolete-function-alias 'org-minutes-to-clocksum-string + 'org-duration-from-minutes "Org 9.1") + +(define-obsolete-function-alias 'org-hh:mm-string-to-minutes + 'org-duration-to-minutes "Org 9.1") + +(define-obsolete-function-alias 'org-duration-string-to-minutes + 'org-duration-to-minutes "Org 9.1") + +(make-obsolete-variable 'org-time-clocksum-format + "set `org-duration-format' instead." "Org 9.1") + +(make-obsolete-variable 'org-time-clocksum-use-fractional + "set `org-duration-format' instead." "Org 9.1") + +(make-obsolete-variable 'org-time-clocksum-fractional-format + "set `org-duration-format' instead." "Org 9.1") + +(make-obsolete-variable 'org-time-clocksum-use-effort-durations + "set `org-duration-units' instead." "Org 9.1") + +(define-obsolete-function-alias 'org-babel-number-p + 'org-babel--string-to-number "Org 9.0") + +(define-obsolete-variable-alias 'org-usenet-links-prefer-google + 'org-gnus-prefer-web-links "Org 9.1") + +(define-obsolete-variable-alias 'org-texinfo-def-table-markup + 'org-texinfo-table-default-markup "Org 9.1") + +;;; The function was made obsolete by commit 65399674d5 of 2013-02-22. +;;; This make-obsolete call was added 2016-09-01. +(make-obsolete 'org-capture-import-remember-templates + "use the `org-capture-templates' variable instead." + "Org 9.0") + + +;;;; Obsolete link types + +(eval-after-load 'org + '(progn + (org-link-set-parameters "file+emacs") ;since Org 9.0 + (org-link-set-parameters "file+sys"))) ;since Org 9.0 + + + +;;; Miscellaneous functions (defun org-version-check (version feature level) (let* ((v1 (mapcar 'string-to-number (split-string version "[.]"))) - (v2 (mapcar 'string-to-number (split-string emacs-version "[.]"))) - (rmaj (or (nth 0 v1) 99)) - (rmin (or (nth 1 v1) 99)) - (rbld (or (nth 2 v1) 99)) - (maj (or (nth 0 v2) 0)) - (min (or (nth 1 v2) 0)) - (bld (or (nth 2 v2) 0))) + (v2 (mapcar 'string-to-number (split-string emacs-version "[.]"))) + (rmaj (or (nth 0 v1) 99)) + (rmin (or (nth 1 v1) 99)) + (rbld (or (nth 2 v1) 99)) + (maj (or (nth 0 v2) 0)) + (min (or (nth 1 v2) 0)) + (bld (or (nth 2 v2) 0))) (if (or (< maj rmaj) - (and (= maj rmaj) - (< min rmin)) - (and (= maj rmaj) - (= min rmin) - (< bld rbld))) - (if (eq level :predicate) - ;; just return if we have the version - nil - (let ((msg (format "Emacs %s or greater is recommended for %s" - version feature))) - (display-warning 'org msg level) - t)) + (and (= maj rmaj) + (< min rmin)) + (and (= maj rmaj) + (= min rmin) + (< bld rbld))) + (if (eq level :predicate) + ;; just return if we have the version + nil + (let ((msg (format "Emacs %s or greater is recommended for %s" + version feature))) + (display-warning 'org msg level) + t)) t))) - -;;;; Emacs/XEmacs compatibility - -(eval-and-compile - (defun org-defvaralias (new-alias base-variable &optional docstring) - "Compatibility function for defvaralias. -Don't do the aliasing when `defvaralias' is not bound." - (declare (indent 1)) - (when (fboundp 'defvaralias) - (defvaralias new-alias base-variable docstring))) - - (when (and (not (boundp 'user-emacs-directory)) - (boundp 'user-init-directory)) - (org-defvaralias 'user-emacs-directory 'user-init-directory))) - -(when (featurep 'xemacs) - (defadvice custom-handle-keyword - (around org-custom-handle-keyword - activate preactivate) - "Remove custom keywords not recognized to avoid producing an error." - (cond - ((eq (ad-get-arg 1) :package-version)) - (t ad-do-it))) - (defadvice define-obsolete-variable-alias - (around org-define-obsolete-variable-alias - (obsolete-name current-name &optional when docstring) - activate preactivate) - "Declare arguments defined in later versions of Emacs." - ad-do-it) - (defadvice define-obsolete-function-alias - (around org-define-obsolete-function-alias - (obsolete-name current-name &optional when docstring) - activate preactivate) - "Declare arguments defined in later versions of Emacs." - ad-do-it) - (defvar customize-package-emacs-version-alist nil) - (defvar temporary-file-directory (temp-directory))) - -;; Keys -(defconst org-xemacs-key-equivalents - '(([mouse-1] . [button1]) - ([mouse-2] . [button2]) - ([mouse-3] . [button3]) - ([C-mouse-4] . [(control mouse-4)]) - ([C-mouse-5] . [(control mouse-5)])) - "Translation alist for a couple of keys.") - -;; Overlay compatibility functions -(defun org-detach-overlay (ovl) - (if (featurep 'xemacs) (detach-extent ovl) (delete-overlay ovl))) -(defun org-overlay-display (ovl text &optional face evap) - "Make overlay OVL display TEXT with face FACE." - (if (featurep 'xemacs) - (let ((gl (make-glyph text))) - (and face (set-glyph-face gl face)) - (set-extent-property ovl 'invisible t) - (set-extent-property ovl 'end-glyph gl)) - (overlay-put ovl 'display text) - (if face (overlay-put ovl 'face face)) - (if evap (overlay-put ovl 'evaporate t)))) -(defun org-overlay-before-string (ovl text &optional face evap) - "Make overlay OVL display TEXT with face FACE." - (if (featurep 'xemacs) - (let ((gl (make-glyph text))) - (and face (set-glyph-face gl face)) - (set-extent-property ovl 'begin-glyph gl)) - (if face (org-add-props text nil 'face face)) - (overlay-put ovl 'before-string text) - (if evap (overlay-put ovl 'evaporate t)))) -(defun org-find-overlays (prop &optional pos delete) - "Find all overlays specifying PROP at POS or point. -If DELETE is non-nil, delete all those overlays." - (let ((overlays (overlays-at (or pos (point)))) - ov found) - (while (setq ov (pop overlays)) - (if (overlay-get ov prop) - (if delete (delete-overlay ov) (push ov found)))) - found)) - (defun org-get-x-clipboard (value) - "Get the value of the x or Windows clipboard, compatible with XEmacs, and GNU Emacs 21." - (cond ((eq window-system 'x) - (let ((x (org-get-x-clipboard-compat value))) - (if x (org-no-properties x)))) - ((and (eq window-system 'w32) (fboundp 'w32-get-clipboard-data)) - (w32-get-clipboard-data)))) - -(defsubst org-decompose-region (beg end) - "Decompose from BEG to END." - (if (featurep 'xemacs) - (let ((modified-p (buffer-modified-p)) - (buffer-read-only nil)) - (remove-text-properties beg end '(composition nil)) - (set-buffer-modified-p modified-p)) - (decompose-region beg end))) - -;; Miscellaneous functions - -(defun org-add-hook (hook function &optional append local) - "Add-hook, compatible with both Emacsen." - (if (and local (featurep 'xemacs)) - (add-local-hook hook function append) - (add-hook hook function append local))) + "Get the value of the X or Windows clipboard." + (cond ((and (eq window-system 'x) + (fboundp 'gui-get-selection)) ;Silence byte-compiler. + (org-no-properties + (ignore-errors + (or (gui-get-selection value 'UTF8_STRING) + (gui-get-selection value 'COMPOUND_TEXT) + (gui-get-selection value 'STRING) + (gui-get-selection value 'TEXT))))) + ((and (eq window-system 'w32) (fboundp 'w32-get-clipboard-data)) + (w32-get-clipboard-data)))) (defun org-add-props (string plist &rest props) "Add text properties to entire string, from beginning to end. @@ -222,153 +446,67 @@ that will be added to PLIST. Returns the string that was modified." (put 'org-add-props 'lisp-indent-function 2) (defun org-fit-window-to-buffer (&optional window max-height min-height - shrink-only) + shrink-only) "Fit WINDOW to the buffer, but only if it is not a side-by-side window. WINDOW defaults to the selected window. MAX-HEIGHT and MIN-HEIGHT are passed through to `fit-window-to-buffer'. If SHRINK-ONLY is set, call `shrink-window-if-larger-than-buffer' instead, the height limit is ignored in this case." (cond ((if (fboundp 'window-full-width-p) - (not (window-full-width-p window)) - ;; do nothing if another window would suffer - (> (frame-width) (window-width window)))) - ((and (fboundp 'fit-window-to-buffer) (not shrink-only)) - (fit-window-to-buffer window max-height min-height)) - ((fboundp 'shrink-window-if-larger-than-buffer) - (shrink-window-if-larger-than-buffer window))) + (not (window-full-width-p window)) + ;; do nothing if another window would suffer + (> (frame-width) (window-width window)))) + ((and (fboundp 'fit-window-to-buffer) (not shrink-only)) + (fit-window-to-buffer window max-height min-height)) + ((fboundp 'shrink-window-if-larger-than-buffer) + (shrink-window-if-larger-than-buffer window))) (or window (selected-window))) -(defun org-number-sequence (from &optional to inc) - "Call `number-sequence' or emulate it." - (if (fboundp 'number-sequence) - (number-sequence from to inc) - (if (or (not to) (= from to)) - (list from) - (or inc (setq inc 1)) - (when (zerop inc) (error "The increment can not be zero")) - (let (seq (n 0) (next from)) - (if (> inc 0) - (while (<= next to) - (setq seq (cons next seq) - n (1+ n) - next (+ from (* n inc)))) - (while (>= next to) - (setq seq (cons next seq) - n (1+ n) - next (+ from (* n inc))))) - (nreverse seq))))) - ;; `set-transient-map' is only in Emacs >= 24.4 (defalias 'org-set-transient-map (if (fboundp 'set-transient-map) 'set-transient-map 'set-temporary-overlay-map)) -;; Region compatibility +;;; Region compatibility (defvar org-ignore-region nil "Non-nil means temporarily disable the active region.") (defun org-region-active-p () - "Is `transient-mark-mode' on and the region active? -Works on both Emacs and XEmacs." - (if org-ignore-region - nil - (if (featurep 'xemacs) - (and zmacs-regions (region-active-p)) - (if (fboundp 'use-region-p) - (use-region-p) - (and transient-mark-mode mark-active))))) ; Emacs 22 and before + "Non-nil when the region active. +Unlike to `use-region-p', this function also checks +`org-ignore-region'." + (and (not org-ignore-region) (use-region-p))) (defun org-cursor-to-region-beginning () (when (and (org-region-active-p) - (> (point) (region-beginning))) + (> (point) (region-beginning))) (exchange-point-and-mark))) -;; Emacs 22 misses `activate-mark' -(if (fboundp 'activate-mark) - (defalias 'org-activate-mark 'activate-mark) - (defun org-activate-mark () - (when (mark t) - (setq mark-active t) - (when (and (boundp 'transient-mark-mode) - (not transient-mark-mode)) - (set (make-local-variable 'transient-mark-mode) 'lambda)) - (when (boundp 'zmacs-regions) - (setq zmacs-regions t))))) - -;; Invisibility compatibility +;;; Invisibility compatibility (defun org-remove-from-invisibility-spec (arg) "Remove elements from `buffer-invisibility-spec'." (if (fboundp 'remove-from-invisibility-spec) (remove-from-invisibility-spec arg) (if (consp buffer-invisibility-spec) - (setq buffer-invisibility-spec - (delete arg buffer-invisibility-spec))))) + (setq buffer-invisibility-spec + (delete arg buffer-invisibility-spec))))) (defun org-in-invisibility-spec-p (arg) "Is ARG a member of `buffer-invisibility-spec'?" (if (consp buffer-invisibility-spec) (member arg buffer-invisibility-spec))) -(defmacro org-xemacs-without-invisibility (&rest body) - "Turn off extents with invisibility while executing BODY." - `(let ((ext-inv (extent-list nil (point-at-bol) (point-at-eol) - 'all-extents-closed-open 'invisible)) - ext-inv-specs) - (dolist (ext ext-inv) - (when (extent-property ext 'invisible) - (add-to-list 'ext-inv-specs (list ext (extent-property - ext 'invisible))) - (set-extent-property ext 'invisible nil))) - ,@body - (dolist (ext-inv-spec ext-inv-specs) - (set-extent-property (car ext-inv-spec) 'invisible - (cadr ext-inv-spec))))) -(def-edebug-spec org-xemacs-without-invisibility (body)) - -(defun org-indent-to-column (column &optional minimum buffer) - "Work around a bug with extents with invisibility in XEmacs." - (if (featurep 'xemacs) - (org-xemacs-without-invisibility (indent-to-column column minimum buffer)) - (indent-to-column column minimum))) - -(defun org-indent-line-to (column) - "Work around a bug with extents with invisibility in XEmacs." - (if (featurep 'xemacs) - (org-xemacs-without-invisibility (indent-line-to column)) - (indent-line-to column))) - -(defun org-move-to-column (column &optional force buffer) +(defun org-move-to-column (column &optional force _buffer) "Move to column COLUMN. -Pass COLUMN and FORCE to `move-to-column'. -Pass BUFFER to the XEmacs version of `move-to-column'." +Pass COLUMN and FORCE to `move-to-column'." (let ((buffer-invisibility-spec - (remove '(org-filtered) buffer-invisibility-spec))) - (if (featurep 'xemacs) - (org-xemacs-without-invisibility - (move-to-column column force buffer)) - (move-to-column column force)))) - -(defun org-get-x-clipboard-compat (value) - "Get the clipboard value on XEmacs or Emacs 21." - (cond ((featurep 'xemacs) - (org-no-warnings (get-selection-no-error value))) - ((fboundp 'x-get-selection) - (condition-case nil - (or (x-get-selection value 'UTF8_STRING) - (x-get-selection value 'COMPOUND_TEXT) - (x-get-selection value 'STRING) - (x-get-selection value 'TEXT)) - (error nil))))) - -(defun org-propertize (string &rest properties) - (if (featurep 'xemacs) - (progn - (add-text-properties 0 (length string) properties string) - string) - (apply 'propertize string properties))) + (if (listp buffer-invisibility-spec) + (remove '(org-filtered) buffer-invisibility-spec) + buffer-invisibility-spec))) + (move-to-column column force))) (defmacro org-find-library-dir (library) `(file-name-directory (or (locate-library ,library) ""))) @@ -379,45 +517,28 @@ Pass BUFFER to the XEmacs version of `move-to-column'." (while (string-match "\n" s start) (setq start (match-end 0) n (1+ n))) (if (and (> (length s) 0) (= (aref s (1- (length s))) ?\n)) - (setq n (1- n))) + (setq n (1- n))) n)) (defun org-kill-new (string &rest args) (remove-text-properties 0 (length string) '(line-prefix t wrap-prefix t) - string) + string) (apply 'kill-new string args)) -(defun org-select-frame-set-input-focus (frame) - "Select FRAME, raise it, and set input focus, if possible." - (cond ((featurep 'xemacs) - (if (fboundp 'select-frame-set-input-focus) - (select-frame-set-input-focus frame) - (raise-frame frame) - (select-frame frame) - (focus-frame frame))) - ;; `select-frame-set-input-focus' defined in Emacs 21 will not - ;; set the input focus. - ((>= emacs-major-version 22) - (select-frame-set-input-focus frame)) - (t - (raise-frame frame) - (select-frame frame) - (cond ((memq window-system '(x ns mac)) - (x-focus-frame frame)) - ((eq window-system 'w32) - (w32-focus-frame frame))) - (when focus-follows-mouse - (set-mouse-position frame (1- (frame-width frame)) 0))))) - -(define-obsolete-function-alias 'org-float-time 'float-time "26.1") - -;; `user-error' is only available from 24.3 on -(unless (fboundp 'user-error) - (defalias 'user-error 'error)) - -;; ‘format-message’ is available only from 25 on -(unless (fboundp 'format-message) - (defalias 'format-message 'format)) +;; `font-lock-ensure' is only available from 24.4.50 on +(defalias 'org-font-lock-ensure + (if (fboundp 'font-lock-ensure) + #'font-lock-ensure + (lambda (&optional _beg _end) + (with-no-warnings (font-lock-fontify-buffer))))) + +;; `file-local-name' was added in Emacs 26.1. +(defalias 'org-babel-local-file-name + (if (fboundp 'file-local-name) + 'file-local-name + (lambda (file) + "Return the local name component of FILE." + (or (file-remote-p file 'localname) file)))) (defmacro org-no-popups (&rest body) "Suppress popup windows. @@ -425,131 +546,27 @@ Let-bind some variables to nil around BODY to achieve the desired effect, which variables to use depends on the Emacs version." (if (org-version-check "24.2.50" "" :predicate) `(let (pop-up-frames display-buffer-alist) - ,@body) + ,@body) `(let (pop-up-frames special-display-buffer-names special-display-regexps special-display-function) ,@body))) -(if (fboundp 'string-match-p) - (defalias 'org-string-match-p 'string-match-p) - (defun org-string-match-p (regexp string &optional start) - (save-match-data - (funcall 'string-match regexp string start)))) - -(if (fboundp 'looking-at-p) - (defalias 'org-looking-at-p 'looking-at-p) - (defun org-looking-at-p (&rest args) - (save-match-data - (apply 'looking-at args)))) - -;; XEmacs does not have `looking-back'. -(if (fboundp 'looking-back) - (defalias 'org-looking-back 'looking-back) - (defun org-looking-back (regexp &optional limit greedy) - "Return non-nil if text before point matches regular expression REGEXP. -Like `looking-at' except matches before point, and is slower. -LIMIT if non-nil speeds up the search by specifying a minimum -starting position, to avoid checking matches that would start -before LIMIT. - -If GREEDY is non-nil, extend the match backwards as far as -possible, stopping when a single additional previous character -cannot be part of a match for REGEXP. When the match is -extended, its starting position is allowed to occur before -LIMIT." - (let ((start (point)) - (pos - (save-excursion - (and (re-search-backward (concat "\\(?:" regexp "\\)\\=") limit t) - (point))))) - (if (and greedy pos) - (save-restriction - (narrow-to-region (point-min) start) - (while (and (> pos (point-min)) - (save-excursion - (goto-char pos) - (backward-char 1) - (looking-at (concat "\\(?:" regexp "\\)\\'")))) - (setq pos (1- pos))) - (save-excursion - (goto-char pos) - (looking-at (concat "\\(?:" regexp "\\)\\'"))))) - (not (null pos))))) - -(defalias 'org-font-lock-ensure - (if (fboundp 'font-lock-ensure) - #'font-lock-ensure - (lambda (&optional _beg _end) (font-lock-fontify-buffer)))) - -(defun org-floor* (x &optional y) - "Return a list of the floor of X and the fractional part of X. -With two arguments, return floor and remainder of their quotient." - (let ((q (floor x y))) - (list q (- x (if y (* y q) q))))) - -;; `pop-to-buffer-same-window' has been introduced in Emacs 24.1. -(defun org-pop-to-buffer-same-window - (&optional buffer-or-name norecord label) - "Pop to buffer specified by BUFFER-OR-NAME in the selected window." - (if (fboundp 'pop-to-buffer-same-window) - (funcall - 'pop-to-buffer-same-window buffer-or-name norecord) - (funcall 'switch-to-buffer buffer-or-name norecord))) - -;; RECURSIVE has been introduced with Emacs 23.2. -;; This is copying and adapted from `tramp-compat-delete-directory' -(defun org-delete-directory (directory &optional recursive) - "Compatibility function for `delete-directory'." - (if (null recursive) - (delete-directory directory) - (condition-case nil - (funcall 'delete-directory directory recursive) - ;; This Emacs version does not support the RECURSIVE flag. We - ;; use the implementation from Emacs 23.2. - (wrong-number-of-arguments - (setq directory (directory-file-name (expand-file-name directory))) - (if (not (file-symlink-p directory)) - (mapc (lambda (file) - (if (eq t (car (file-attributes file))) - (org-delete-directory file recursive) - (delete-file file))) - (directory-files - directory 'full "^\\([^.]\\|\\.\\([^.]\\|\\..\\)\\).*"))) - (delete-directory directory))))) - ;;;###autoload (defmacro org-check-version () "Try very hard to provide sensible version strings." (let* ((org-dir (org-find-library-dir "org")) - (org-version.el (concat org-dir "org-version.el")) - (org-fixup.el (concat org-dir "../mk/org-fixup.el"))) + (org-version.el (concat org-dir "org-version.el")) + (org-fixup.el (concat org-dir "../mk/org-fixup.el"))) (if (require 'org-version org-version.el 'noerror) - '(progn - (autoload 'org-release "org-version.el") - (autoload 'org-git-version "org-version.el")) + '(progn + (autoload 'org-release "org-version.el") + (autoload 'org-git-version "org-version.el")) (if (require 'org-fixup org-fixup.el 'noerror) - '(org-fixup) - ;; provide fallback definitions and complain - (warn "Could not define org version correctly. Check installation!") - '(progn - (defun org-release () "N/A") - (defun org-git-version () "N/A !!check installation!!")))))) - -(defun org-file-equal-p (f1 f2) - "Return t if files F1 and F2 are the same. -Implements `file-equal-p' for older emacsen and XEmacs." - (if (fboundp 'file-equal-p) - (file-equal-p f1 f2) - (let (f1-attr f2-attr) - (and (setq f1-attr (file-attributes (file-truename f1))) - (setq f2-attr (file-attributes (file-truename f2))) - (equal f1-attr f2-attr))))) - -;; `buffer-narrowed-p' is available for Emacs >=24.3 -(defun org-buffer-narrowed-p () - "Compatibility function for `buffer-narrowed-p'." - (if (fboundp 'buffer-narrowed-p) - (buffer-narrowed-p) - (/= (- (point-max) (point-min)) (buffer-size)))) + '(org-fixup) + ;; provide fallback definitions and complain + (warn "Could not define org version correctly. Check installation!") + '(progn + (defun org-release () "N/A") + (defun org-git-version () "N/A !!check installation!!")))))) (defmacro org-with-silent-modifications (&rest body) (if (fboundp 'with-silent-modifications) @@ -557,6 +574,27 @@ Implements `file-equal-p' for older emacsen and XEmacs." `(org-unmodified ,@body))) (def-edebug-spec org-with-silent-modifications (body)) +;; Functions for Emacs < 24.4 compatibility +(defun org-define-error (name message) + "Define NAME as a new error signal. +MESSAGE is a string that will be output to the echo area if such +an error is signaled without being caught by a `condition-case'. +Implements `define-error' for older emacsen." + (if (fboundp 'define-error) (define-error name message) + (put name 'error-conditions + (copy-sequence (cons name (get 'error 'error-conditions)))))) + +(unless (fboundp 'string-suffix-p) + ;; From Emacs subr.el. + (defun string-suffix-p (suffix string &optional ignore-case) + "Return non-nil if SUFFIX is a suffix of STRING. +If IGNORE-CASE is non-nil, the comparison is done without paying +attention to case differences." + (let ((start-pos (- (length string) (length suffix)))) + (and (>= start-pos 0) + (eq t (compare-strings suffix nil nil + string start-pos nil ignore-case)))))) + (provide 'org-compat) ;;; org-compat.el ends here diff --git a/lisp/org/org-crypt.el b/lisp/org/org-crypt.el index 36144e25309..48c3ff0a5f9 100644 --- a/lisp/org/org-crypt.el +++ b/lisp/org/org-crypt.el @@ -1,5 +1,4 @@ -;;; org-crypt.el --- Public key encryption for org-mode entries - +;;; org-crypt.el --- Public Key Encryption for Org Entries -*- lexical-binding: t; -*- ;; Copyright (C) 2007-2017 Free Software Foundation, Inc. ;; Emacs Lisp Archive Entry @@ -7,7 +6,7 @@ ;; Keywords: org-mode ;; Author: John Wiegley <johnw@gnu.org> ;; Maintainer: Peter Jones <pjones@pmade.com> -;; Description: Adds public key encryption to org-mode buffers +;; Description: Adds public key encryption to Org buffers ;; URL: http://www.newartisans.com/software/emacs.html ;; Compatibility: Emacs22 @@ -24,7 +23,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -104,10 +103,10 @@ t : Disable auto-save-mode for the current buffer nil : Leave auto-save-mode enabled. This may cause data to be written to disk unencrypted! -'ask : Ask user whether or not to disable auto-save-mode +`ask' : Ask user whether or not to disable auto-save-mode for the current buffer. -'encrypt : Leave auto-save-mode enabled for the current buffer, +`encrypt': Leave auto-save-mode enabled for the current buffer, but automatically re-encrypt all decrypted entries *before* auto-saving. NOTE: This only works for entries which have a tag @@ -142,7 +141,7 @@ See `org-crypt-disable-auto-save'." (message "org-decrypt: Decrypting entry with auto-save-mode enabled. This may cause leakage.")) ((eq org-crypt-disable-auto-save 'encrypt) (message "org-decrypt: Enabling re-encryption on auto-save.") - (org-add-hook 'auto-save-hook + (add-hook 'auto-save-hook (lambda () (message "org-crypt: Re-encrypting all decrypted entries due to auto-save.") (org-encrypt-entries)) @@ -164,96 +163,96 @@ See `org-crypt-disable-auto-save'." (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) - (set (make-local-variable 'epg-context) (epg-make-context nil t t)) + (setq-local epg-context (epg-make-context nil t t)) (epg-encrypt-string epg-context str (epg-list-keys epg-context crypt-key)))) (defun org-encrypt-entry () "Encrypt the content of the current headline." (interactive) (require 'epg) - (save-excursion - (org-back-to-heading t) - (set (make-local-variable 'epg-context) (epg-make-context nil t t)) - (let ((start-heading (point))) - (forward-line) - (when (not (looking-at "-----BEGIN PGP MESSAGE-----")) - (let ((folded (outline-invisible-p)) - (crypt-key (org-crypt-key-for-heading)) - (beg (point)) - end encrypted-text) - (goto-char start-heading) - (org-end-of-subtree t t) - (org-back-over-empty-lines) - (setq end (point) - encrypted-text - (org-encrypt-string (buffer-substring beg end) crypt-key)) - (delete-region beg end) - (insert encrypted-text) - (when folded - (goto-char start-heading) - (hide-subtree)) - nil))))) + (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))) + (goto-char start-heading) + (org-end-of-subtree t t) + (org-back-over-empty-lines) + (let ((contents (delete-and-extract-region beg (point)))) + (condition-case err + (insert (org-encrypt-string 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) + (outline-hide-subtree)) + nil))))) (defun org-decrypt-entry () "Decrypt the content of the current headline." (interactive) (require 'epg) (unless (org-before-first-heading-p) - (save-excursion - (org-back-to-heading t) - (let ((heading-point (point)) - (heading-was-invisible-p - (save-excursion - (outline-end-of-heading) - (outline-invisible-p)))) - (forward-line) - (when (looking-at "-----BEGIN PGP MESSAGE-----") - (org-crypt-check-auto-save) - (set (make-local-variable '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 allow 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)))))) + (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)))))) (defun org-encrypt-entries () "Encrypt all top-level entries in the current buffer." (interactive) - (let (todo-only) + (let ((org--matcher-tags-todo-only nil)) (org-scan-tags 'org-encrypt-entry (cdr (org-make-tags-matcher org-crypt-tag-matcher)) - todo-only))) + org--matcher-tags-todo-only))) (defun org-decrypt-entries () "Decrypt all entries in the current buffer." (interactive) - (let (todo-only) + (let ((org--matcher-tags-todo-only nil)) (org-scan-tags 'org-decrypt-entry (cdr (org-make-tags-matcher org-crypt-tag-matcher)) - todo-only))) + org--matcher-tags-todo-only))) (defun org-at-encrypted-entry-p () "Is the current entry encrypted?" @@ -267,7 +266,7 @@ See `org-crypt-disable-auto-save'." "Add a hook to automatically encrypt entries before a file is saved to disk." (add-hook 'org-mode-hook - (lambda () (org-add-hook 'before-save-hook 'org-encrypt-entries nil t)))) + (lambda () (add-hook 'before-save-hook 'org-encrypt-entries nil t)))) (add-hook 'org-reveal-start-hook 'org-decrypt-entry) diff --git a/lisp/org/org-ctags.el b/lisp/org/org-ctags.el index 1ecf6744821..fe6caf209d9 100644 --- a/lisp/org/org-ctags.el +++ b/lisp/org/org-ctags.el @@ -1,4 +1,4 @@ -;;; org-ctags.el - Integrate Emacs "tags" facility with org mode. +;;; org-ctags.el - Integrate Emacs "tags" Facility with Org -*- lexical-binding: t; -*- ;; ;; Copyright (C) 2007-2017 Free Software Foundation, Inc. @@ -20,26 +20,27 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; ;; Synopsis ;; ======== ;; -;; Allows org-mode to make use of the Emacs `etags' system. Defines tag -;; destinations in org-mode files as any text between <<double angled -;; brackets>>. This allows the tags-generation program `exuberant ctags' to -;; parse these files and create tag tables that record where these -;; destinations are found. Plain [[links]] in org mode files which do not have -;; <<matching destinations>> within the same file will then be interpreted as -;; links to these 'tagged' destinations, allowing seamless navigation between -;; multiple org-mode files. Topics can be created in any org mode file and -;; will always be found by plain links from other files. Other file types -;; recognized by ctags (source code files, latex files, etc) will also be -;; available as destinations for plain links, and similarly, org-mode links -;; will be available as tags from source files. Finally, the function -;; `org-ctags-find-tag-interactive' lets you choose any known tag, using -;; autocompletion, and quickly jump to it. +;; Allows Org mode to make use of the Emacs `etags' system. Defines +;; tag destinations in Org files as any text between <<double angled +;; brackets>>. This allows the tags-generation program `exuberant +;; ctags' to parse these files and create tag tables that record where +;; these destinations are found. Plain [[links]] in org mode files +;; which do not have <<matching destinations>> within the same file +;; will then be interpreted as links to these 'tagged' destinations, +;; allowing seamless navigation between multiple Org files. Topics +;; can be created in any org mode file and will always be found by +;; plain links from other files. Other file types recognized by ctags +;; (source code files, latex files, etc) will also be available as +;; destinations for plain links, and similarly, Org links will be +;; available as tags from source files. Finally, the function +;; `org-ctags-find-tag-interactive' lets you choose any known tag, +;; using autocompletion, and quickly jump to it. ;; ;; Installation ;; ============ @@ -110,8 +111,9 @@ ;; Keeping the TAGS file up to date ;; ================================ ;; -;; Tags mode has no way of knowing that you have created new tags by typing in -;; your org-mode buffer. New tags make it into the TAGS file in 3 ways: +;; Tags mode has no way of knowing that you have created new tags by +;; typing in your Org buffer. New tags make it into the TAGS file in +;; 3 ways: ;; ;; 1. You re-run (org-ctags-create-tags "directory") to rebuild the file. ;; 2. You put the function `org-ctags-ask-rebuild-tags-file-then-find-tag' in @@ -135,12 +137,8 @@ ;;; Code: -(eval-when-compile (require 'cl)) - (require 'org) -(declare-function org-pop-to-buffer-same-window "org-compat" (&optional buffer-or-name norecord label)) - (defgroup org-ctags nil "Options concerning use of ctags within org mode." :tag "Org-Ctags" @@ -151,7 +149,7 @@ (defvar org-ctags-tag-regexp "/<<([^>]+)>>/\\1/d,definition/" "Regexp expression used by ctags external program. -The regexp matches tag destinations in org-mode files. +The regexp matches tag destinations in Org files. Format is: /REGEXP/TAGNAME/FLAGS,TAGTYPE/ See the ctags documentation for more information.") @@ -210,8 +208,8 @@ The following patterns are replaced in the string: (defadvice visit-tags-table (after org-ctags-load-tag-list activate compile) (when (and org-ctags-enabled-p tags-file-name) - (set (make-local-variable 'org-ctags-tag-list) - (org-ctags-all-tags-in-current-tags-table)))) + (setq-local org-ctags-tag-list + (org-ctags-all-tags-in-current-tags-table)))) (defun org-ctags-enable () @@ -273,11 +271,6 @@ Return the list." (replace-regexp-in-string (regexp-quote search) replace string t t)) -(defun y-or-n-minibuffer (prompt) - (let ((use-dialog-box nil)) - (y-or-n-p prompt))) - - ;;; Internal functions ======================================================= @@ -285,29 +278,28 @@ Return the list." "Visit or create a file called `NAME.org', and insert a new topic. The new topic will be titled NAME (or TITLE if supplied)." (interactive "sFile name: ") - (let ((filename (substitute-in-file-name (expand-file-name name)))) - (condition-case v - (progn - (org-open-file name t) - (message "Opened file OK") - (goto-char (point-max)) - (insert (org-ctags-string-search-and-replace - "%t" (capitalize (or title name)) - org-ctags-new-topic-template)) - (message "Inserted new file text OK") - (org-mode-restart)) - (error (error "Error %S in org-ctags-open-file" v))))) + (condition-case v + (progn + (org-open-file name t) + (message "Opened file OK") + (goto-char (point-max)) + (insert (org-ctags-string-search-and-replace + "%t" (capitalize (or title name)) + org-ctags-new-topic-template)) + (message "Inserted new file text OK") + (org-mode-restart)) + (error (error "Error %S in org-ctags-open-file" v)))) ;;;; Misc interoperability with etags system ================================= -(defadvice find-tag (before org-ctags-set-org-mark-before-finding-tag - activate compile) +(defadvice xref-find-definitions + (before org-ctags-set-org-mark-before-finding-tag activate compile) "Before trying to find a tag, save our current position on org mark ring." (save-excursion - (if (and (derived-mode-p 'org-mode) org-ctags-enabled-p) - (org-mark-ring-push)))) + (when (and (derived-mode-p 'org-mode) org-ctags-enabled-p) + (org-mark-ring-push)))) @@ -359,7 +351,7 @@ visit the file and location where the tag is found." (old-pnt (point-marker)) (old-mark (copy-marker (mark-marker)))) (condition-case nil - (progn (find-tag name) + (progn (xref-find-definitions name) t) (error ;; only restore old location if find-tag raises error @@ -386,7 +378,7 @@ the new file." (cond ((get-buffer (concat name ".org")) ;; Buffer is already open - (org-pop-to-buffer-same-window (get-buffer (concat name ".org")))) + (pop-to-buffer-same-window (get-buffer (concat name ".org")))) ((file-exists-p filename) ;; File exists but is not open --> open it (message "Opening existing org file `%S'..." @@ -421,7 +413,6 @@ the heading a destination for the tag `NAME'." (insert (org-ctags-string-search-and-replace "%t" (capitalize name) org-ctags-new-topic-template)) (backward-char 4) - (org-update-radio-target-regexp) (end-of-line) (forward-line 2) (when narrowp @@ -464,10 +455,10 @@ Wrapper for org-ctags-rebuild-tags-file-then-find-tag." nil)) -(defun org-ctags-fail-silently (name) +(defun org-ctags-fail-silently (_name) "This function is intended to be used in ORG-OPEN-LINK-FUNCTIONS. -Put as the last function in the list if you want to prevent org's default -behavior of free text search." +Put as the last function in the list if you want to prevent Org's +default behavior of free text search." t) @@ -484,7 +475,7 @@ end up in one file, called TAGS, located in the directory. This function may take several seconds to finish if the directory or its subdirectories contain large numbers of taggable files." (interactive) - (assert (buffer-file-name)) + (cl-assert (buffer-file-name)) (let ((dir-name (or directory-name (file-name-directory (buffer-file-name)))) (exitcode nil)) @@ -499,8 +490,8 @@ its subdirectories contain large numbers of taggable files." (expand-file-name (concat dir-name "/*"))))) (cond ((eql 0 exitcode) - (set (make-local-variable 'org-ctags-tag-list) - (org-ctags-all-tags-in-current-tags-table))) + (setq-local org-ctags-tag-list + (org-ctags-all-tags-in-current-tags-table))) (t ;; This seems to behave differently on Linux, so just ignore ;; error codes for now @@ -528,7 +519,7 @@ a new topic." ((member tag org-ctags-tag-list) ;; Existing tag (push tag org-ctags-find-tag-history) - (find-tag tag)) + (xref-find-definitions tag)) (t ;; New tag (run-hook-with-args-until-success diff --git a/lisp/org/org-datetree.el b/lisp/org/org-datetree.el index 891e64f9095..6d1926bc15e 100644 --- a/lisp/org/org-datetree.el +++ b/lisp/org/org-datetree.el @@ -1,4 +1,4 @@ -;;; org-datetree.el --- Create date entries in a tree +;;; org-datetree.el --- Create date entries in a tree -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Commentary: @@ -34,12 +34,14 @@ (defvar org-datetree-base-level 1 "The level at which years should be placed in the date tree. -This is normally one, but if the buffer has an entry with a DATE_TREE -property (any value), the date tree will become a subtree under that entry, -so the base level will be properly adjusted.") +This is normally one, but if the buffer has an entry with a +DATE_TREE (or WEEK_TREE for ISO week entries) property (any +value), the date tree will become a subtree under that entry, so +the base level will be properly adjusted.") (defcustom org-datetree-add-timestamp nil - "When non-nil, add a time stamp when create a datetree entry." + "When non-nil, add a time stamp matching date of entry. +Added time stamp is active unless value is `inactive'." :group 'org-capture :version "24.3" :type '(choice @@ -48,115 +50,146 @@ so the base level will be properly adjusted.") (const :tag "Add an active time stamp" active))) ;;;###autoload -(defun org-datetree-find-date-create (date &optional keep-restriction) - "Find or create an entry for DATE. +(defun org-datetree-find-date-create (d &optional keep-restriction) + "Find or create an 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." - (let ((year (nth 2 date)) - (month (car date)) - (day (nth 1 date))) - (org-set-local 'org-datetree-base-level 1) - (or keep-restriction (widen)) +tree can be found. If it is the symbol `subtree-at-point', then the tree +will be built under the headline at point." + (setq-local org-datetree-base-level 1) + (save-restriction + (if (eq keep-restriction 'subtree-at-point) + (progn + (unless (org-at-heading-p) (error "Not at heading")) + (widen) + (org-narrow-to-subtree) + (setq-local org-datetree-base-level + (org-get-valid-level (org-current-level) 1))) + (unless keep-restriction (widen)) + ;; Support the old way of tree placement, using a property + (let ((prop (org-find-property "DATE_TREE"))) + (when prop + (goto-char prop) + (setq-local org-datetree-base-level + (org-get-valid-level (org-current-level) 1)) + (org-narrow-to-subtree)))) (goto-char (point-min)) - (save-restriction - (when (re-search-forward "^[ \t]*:DATE_TREE:[ \t]+\\S-" nil t) - (org-back-to-heading t) - (org-set-local 'org-datetree-base-level - (org-get-valid-level (funcall outline-level) 1)) - (org-narrow-to-subtree)) - (goto-char (point-min)) - (org-datetree-find-year-create year) - (org-datetree-find-month-create year month) - (org-datetree-find-day-create year month day) - (goto-char (prog1 (point) (widen)))))) - -(defun org-datetree-find-year-create (year) - "Find the YEAR datetree or create it." - (let ((re "^\\*+[ \t]+\\([12][0-9]\\{3\\}\\)\\(\\s-*?\\([ \t]:[[:alnum:]:_@#%]+:\\)?\\s-*$\\)") - match) - (goto-char (point-min)) - (while (and (setq match (re-search-forward re nil t)) - (goto-char (match-beginning 1)) - (< (string-to-number (match-string 1)) year))) - (cond - ((not match) - (goto-char (point-max)) - (or (bolp) (newline)) - (org-datetree-insert-line year)) - ((= (string-to-number (match-string 1)) year) - (goto-char (point-at-bol))) - (t - (beginning-of-line 1) - (org-datetree-insert-line year))))) + (let ((year (calendar-extract-year d)) + (month (calendar-extract-month d)) + (day (calendar-extract-day d))) + (org-datetree--find-create + "^\\*+[ \t]+\\([12][0-9]\\{3\\}\\)\\(\\s-*?\ +\\([ \t]:[[:alnum:]:_@#%%]+:\\)?\\s-*$\\)" + year) + (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)))) -(defun org-datetree-find-month-create (year month) - "Find the datetree for YEAR and MONTH or create it." - (org-narrow-to-subtree) - (let ((re (format "^\\*+[ \t]+%d-\\([01][0-9]\\) \\w+$" year)) - match) +;;;###autoload +(defun org-datetree-find-iso-week-create (d &optional keep-restriction) + "Find or create an ISO week entry for date D. +Compared to `org-datetree-find-date-create' this function creates +entries ordered by week instead of months. +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." + (setq-local org-datetree-base-level 1) + (save-restriction + (if (eq keep-restriction 'subtree-at-point) + (progn + (unless (org-at-heading-p) (error "Not at heading")) + (widen) + (org-narrow-to-subtree) + (setq-local org-datetree-base-level + (org-get-valid-level (org-current-level) 1))) + (unless keep-restriction (widen)) + ;; Support the old way of tree placement, using a property + (let ((prop (org-find-property "WEEK_TREE"))) + (when prop + (goto-char prop) + (setq-local org-datetree-base-level + (org-get-valid-level (org-current-level) 1)) + (org-narrow-to-subtree)))) (goto-char (point-min)) - (while (and (setq match (re-search-forward re nil t)) - (goto-char (match-beginning 1)) - (< (string-to-number (match-string 1)) month))) - (cond - ((not match) - (goto-char (point-max)) - (or (bolp) (newline)) - (org-datetree-insert-line year month)) - ((= (string-to-number (match-string 1)) month) - (goto-char (point-at-bol))) - (t - (beginning-of-line 1) - (org-datetree-insert-line year month))))) - -(defun org-datetree-find-day-create (year month day) - "Find the datetree for YEAR, MONTH and DAY or create it." - (org-narrow-to-subtree) - (let ((re (format "^\\*+[ \t]+%d-%02d-\\([0123][0-9]\\) \\w+$" year month)) + (require 'cal-iso) + (let* ((year (calendar-extract-year d)) + (month (calendar-extract-month d)) + (day (calendar-extract-day d)) + (time (encode-time 0 0 0 day month year)) + (iso-date (calendar-iso-from-absolute + (calendar-absolute-from-gregorian d))) + (weekyear (nth 2 iso-date)) + (week (nth 0 iso-date))) + ;; ISO 8601 week format is %G-W%V(-%u) + (org-datetree--find-create + "^\\*+[ \t]+\\([12][0-9]\\{3\\}\\)\\(\\s-*?\ +\\([ \t]:[[:alnum:]:_@#%%]+:\\)?\\s-*$\\)" + weekyear nil nil + (format-time-string "%G" time)) + (org-datetree--find-create + "^\\*+[ \t]+%d-W\\([0-5][0-9]\\)$" + weekyear week nil + (format-time-string "%G-W%V" time)) + ;; For the actual day we use the regular date instead of ISO week. + (org-datetree--find-create + "^\\*+[ \t]+%d-%02d-\\([0123][0-9]\\) \\w+$" + year month day)))) + +(defun org-datetree--find-create (regex year &optional month day insert) + "Find the datetree matched by REGEX for YEAR, MONTH, or DAY. +REGEX is passed to `format' with YEAR, MONTH, and DAY as +arguments. Match group 1 is compared against the specified date +component. If INSERT is non-nil and there is no match then it is +inserted into the buffer." + (when (or month day) + (org-narrow-to-subtree)) + (let ((re (format regex year month day)) match) (goto-char (point-min)) (while (and (setq match (re-search-forward re nil t)) (goto-char (match-beginning 1)) - (< (string-to-number (match-string 1)) day))) + (< (string-to-number (match-string 1)) (or day month year)))) (cond ((not match) (goto-char (point-max)) - (or (bolp) (newline)) - (org-datetree-insert-line year month day)) - ((= (string-to-number (match-string 1)) day) - (goto-char (point-at-bol))) + (unless (bolp) (insert "\n")) + (org-datetree-insert-line year month day insert)) + ((= (string-to-number (match-string 1)) (or day month year)) + (beginning-of-line)) (t - (beginning-of-line 1) - (org-datetree-insert-line year month day))))) - -(defun org-datetree-insert-line (year &optional month day) - (let ((pos (point)) ts-type) - (skip-chars-backward " \t\n") - (delete-region (point) pos) - (insert "\n" (make-string org-datetree-base-level ?*) " \n") - (backward-char 1) - (if month (org-do-demote)) - (if day (org-do-demote)) + (beginning-of-line) + (org-datetree-insert-line year month day insert))))) + +(defun org-datetree-insert-line (year &optional month day text) + (delete-region (save-excursion (skip-chars-backward " \t\n") (point)) (point)) + (insert "\n" (make-string org-datetree-base-level ?*) " \n") + (backward-char) + (when month (org-do-demote)) + (when day (org-do-demote)) + (if text + (insert text) (insert (format "%d" year)) (when month - (insert (format "-%02d" month)) - (if day - (insert (format "-%02d %s" - day (format-time-string - "%A" (encode-time 0 0 0 day month year)))) - (insert (format " %s" - (format-time-string - "%B" (encode-time 0 0 0 1 month year)))))) - (when (and day (setq ts-type org-datetree-add-timestamp)) + (insert + (if day + (format-time-string "-%m-%d %A" (encode-time 0 0 0 day month year)) + (format-time-string "-%m %B" (encode-time 0 0 0 1 month year)))))) + (when (and day org-datetree-add-timestamp) + (save-excursion (insert "\n") (org-indent-line) - (org-insert-time-stamp (encode-time 0 0 0 day month year) nil ts-type)) - (beginning-of-line 1))) - -(defun org-datetree-file-entry-under (txt date) - "Insert a node TXT into the date tree under DATE." - (org-datetree-find-date-create date) + (org-insert-time-stamp + (encode-time 0 0 0 day month year) + nil + (eq org-datetree-add-timestamp 'inactive)))) + (beginning-of-line)) + +(defun org-datetree-file-entry-under (txt d) + "Insert a node TXT into the date tree under date D." + (org-datetree-find-date-create d) (let ((level (org-get-valid-level (funcall outline-level) 1))) (org-end-of-subtree t t) (org-back-over-empty-lines) @@ -169,44 +202,42 @@ before running this command, even though the command tries to be smart." (interactive) (goto-char (point-min)) (let ((dre (concat "\\<" org-deadline-string "\\>[ \t]*\\'")) - (sre (concat "\\<" org-scheduled-string "\\>[ \t]*\\'")) - dct ts tmp date year month day pos hdl-pos) + (sre (concat "\\<" org-scheduled-string "\\>[ \t]*\\'"))) (while (re-search-forward org-ts-regexp nil t) (catch 'next - (setq ts (match-string 0)) - (setq tmp (buffer-substring - (max (point-at-bol) (- (match-beginning 0) - org-ds-keyword-length)) - (match-beginning 0))) - (if (or (string-match "-\\'" tmp) - (string-match dre tmp) - (string-match sre tmp)) + (let ((tmp (buffer-substring + (max (line-beginning-position) + (- (match-beginning 0) org-ds-keyword-length)) + (match-beginning 0)))) + (when (or (string-suffix-p "-" tmp) + (string-match dre tmp) + (string-match sre tmp)) (throw 'next nil)) - (setq dct (decode-time (org-time-string-to-time (match-string 0))) - date (list (nth 4 dct) (nth 3 dct) (nth 5 dct)) - year (nth 2 date) - month (car date) - day (nth 1 date) - pos (point)) - (org-back-to-heading t) - (setq hdl-pos (point)) - (unless (org-up-heading-safe) - ;; No parent, we are not in a date tree - (goto-char pos) - (throw 'next nil)) - (unless (looking-at "\\*+[ \t]+[0-9]+-[0-1][0-9]-[0-3][0-9]") - ;; Parent looks wrong, we are not in a date tree - (goto-char pos) - (throw 'next nil)) - (when (looking-at (format "\\*+[ \t]+%d-%02d-%02d" year month day)) - ;; At correct date already, do nothing - (progn (goto-char pos) (throw 'next nil))) - ;; OK, we need to refile this entry - (goto-char hdl-pos) - (org-cut-subtree) - (save-excursion - (save-restriction - (org-datetree-file-entry-under (current-kill 0) date))))))) + (let* ((dct (decode-time (org-time-string-to-time (match-string 0)))) + (date (list (nth 4 dct) (nth 3 dct) (nth 5 dct))) + (year (nth 2 date)) + (month (car date)) + (day (nth 1 date)) + (pos (point)) + (hdl-pos (progn (org-back-to-heading t) (point)))) + (unless (org-up-heading-safe) + ;; No parent, we are not in a date tree. + (goto-char pos) + (throw 'next nil)) + (unless (looking-at "\\*+[ \t]+[0-9]+-[0-1][0-9]-[0-3][0-9]") + ;; Parent looks wrong, we are not in a date tree. + (goto-char pos) + (throw 'next nil)) + (when (looking-at (format "\\*+[ \t]+%d-%02d-%02d" year month day)) + ;; At correct date already, do nothing. + (goto-char pos) + (throw 'next nil)) + ;; OK, we need to refile this entry. + (goto-char hdl-pos) + (org-cut-subtree) + (save-excursion + (save-restriction + (org-datetree-file-entry-under (current-kill 0) date))))))))) (provide 'org-datetree) diff --git a/lisp/org/org-docview.el b/lisp/org/org-docview.el index c5d01158c9c..3361b0e59ea 100644 --- a/lisp/org/org-docview.el +++ b/lisp/org/org-docview.el @@ -1,4 +1,4 @@ -;;; org-docview.el --- support for links to doc-view-mode buffers +;;; org-docview.el --- Support for links to doc-view-mode buffers -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. @@ -19,13 +19,13 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Commentary: ;; This file implements links to open files in doc-view-mode. -;; Org-mode loads this module by default - if this is not what you want, +;; Org mode loads this module by default - if this is not what you want, ;; configure the variable `org-modules'. ;; The links take the form @@ -49,13 +49,15 @@ (declare-function doc-view-goto-page "doc-view" (page)) (declare-function image-mode-window-get "image-mode" (prop &optional winprops)) -(org-add-link-type "docview" 'org-docview-open 'org-docview-export) -(add-hook 'org-store-link-functions 'org-docview-store-link) +(org-link-set-parameters "docview" + :follow #'org-docview-open + :export #'org-docview-export + :store #'org-docview-store-link) (defun org-docview-export (link description format) "Export a docview link from Org files." - (let* ((path (when (string-match "\\(.+\\)::.+" link) - (match-string 1 link))) + (let* ((path (if (string-match "\\(.+\\)::.+" link) (match-string 1 link) + link)) (desc (or description link))) (when (stringp path) (setq path (org-link-escape (expand-file-name path))) @@ -66,13 +68,14 @@ (t path))))) (defun org-docview-open (link) - (when (string-match "\\(.*\\)::\\([0-9]+\\)$" link) - (let* ((path (match-string 1 link)) - (page (string-to-number (match-string 2 link)))) - (org-open-file path 1) ;; let org-mode open the file (in-emacs = 1) - ;; to ensure org-link-frame-setup is respected - (doc-view-goto-page page) - ))) + (string-match "\\(.*?\\)\\(?:::\\([0-9]+\\)\\)?$" link) + (let ((path (match-string 1 link)) + (page (and (match-beginning 2) + (string-to-number (match-string 2 link))))) + ;; Let Org mode open the file (in-emacs = 1) to ensure + ;; org-link-frame-setup is respected. + (org-open-file path 1) + (when page (doc-view-goto-page page)))) (defun org-docview-store-link () "Store a link to a docview buffer." @@ -80,8 +83,7 @@ ;; This buffer is in doc-view-mode (let* ((path buffer-file-name) (page (image-mode-window-get 'page)) - (link (concat "docview:" path "::" (number-to-string page))) - (description "")) + (link (concat "docview:" path "::" (number-to-string page)))) (org-store-link-props :type "docview" :link link diff --git a/lisp/org/org-duration.el b/lisp/org/org-duration.el new file mode 100644 index 00000000000..096e973d340 --- /dev/null +++ b/lisp/org/org-duration.el @@ -0,0 +1,448 @@ +;;; org-duration.el --- Library handling durations -*- lexical-binding: t; -*- + +;; Copyright (C) 2017 Free Software Foundation, Inc. + +;; Author: Nicolas Goaziou <mail@nicolasgoaziou.fr> +;; Keywords: outlines, hypermedia, calendar, wp + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; This library provides tools to manipulate durations. A duration +;; can have multiple formats: +;; +;; - 3:12 +;; - 1:23:45 +;; - 1y 3d 3h 4min +;; - 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. +;; +;; The library provides functions allowing to convert a duration to, +;; and from, a number of minutes: `org-duration-to-minutes' and +;; `org-duration-from-minutes'. It also provides two lesser tools: +;; `org-duration-p', and `org-duration-h:mm-only-p'. +;; +;; Users can set the number of minutes per unit, or define new units, +;; in `org-duration-units'. The library also supports canonical +;; duration, i.e., a duration that doesn't depend on user's settings, +;; through optional arguments. + +;;; Code: + +(require 'cl-lib) +(require 'org-macs) +(declare-function org-trim "org-trim" (s &optional keep-lead)) + + +;;; Public variables + +(defconst org-duration-canonical-units + `(("min" . 1) + ("h" . 60) + ("d" . ,(* 60 24))) + "Canonical time duration units. +See `org-duration-units' for details.") + +(defcustom org-duration-units + `(("min" . 1) + ("h" . 60) + ("d" . ,(* 60 24)) + ("w" . ,(* 60 24 7)) + ("m" . ,(* 60 24 30)) + ("y" . ,(* 60 24 365.25))) + "Conversion factor to minutes for a duration. + +Each entry has the form (UNIT . MODIFIER). + +In a duration string, a number followed by UNIT is multiplied by +the specified number of MODIFIER to obtain a duration in minutes. + +For example, the following value + + \\=`((\"min\" . 1) + (\"h\" . 60) + (\"d\" . ,(* 60 8)) + (\"w\" . ,(* 60 8 5)) + (\"m\" . ,(* 60 8 5 4)) + (\"y\" . ,(* 60 8 5 4 10))) + +is meaningful if you work an average of 8 hours per day, 5 days +a week, 4 weeks a month and 10 months a year. + +When setting this variable outside the Customize interface, make +sure to call the following command: + + \\[org-duration-set-regexps]" + :group 'org-agenda + :version "26.1" + :package-version '(Org . "9.1") + :set (lambda (var val) (set-default var val) (org-duration-set-regexps)) + :initialize 'custom-initialize-changed + :type '(choice + (const :tag "H:MM" 'h:mm) + (const :tag "H:MM:SS" 'h:mm:ss) + (alist :key-type (string :tag "Unit") + :value-type (number :tag "Modifier")))) + +(defcustom org-duration-format '(("d" . nil) (special . h:mm)) + "Format definition for a duration. + +The value can be set to, respectively, the symbols `h:mm:ss' or +`h:mm', which means a duration is expressed as, respectively, +a \"H:MM:SS\" or \"H:MM\" string. + +Alternatively, the value can be a list of entries following the +pattern: + + (UNIT . REQUIRED?) + +UNIT is a unit string, as defined in `org-duration-units'. The +time duration is formatted using only the time components that +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: + + (special . h:mm) + (special . h:mm:ss) + + Units shorter than an hour are ignored. The hours and + minutes part of the duration is expressed unconditionally + with H:MM, or H:MM:SS, pattern. + + (special . PRECISION) + + A duration is expressed with a single unit, PRECISION being + the number of decimal places to show. The unit chosen is the + first one required or with a non-zero integer part. If there + is no such unit, the smallest one is used. + +For example, + + ((\"d\" . nil) (\"h\" . t) (\"min\" . t)) + +means a duration longer than a day is expressed in days, hours +and minutes, whereas a duration shorter than a day is always +expressed in hours and minutes, even when shorter than an hour. + +On the other hand, the value + + ((\"d\" . nil) (\"min\" . nil)) + +means a duration longer than a day is expressed in days and +minutes, whereas a duration shorter than a day is expressed +entirely in minutes, even when longer than an hour. + +The following format + + ((\"d\" . nil) (special . h:mm)) + +means that any duration longer than a day is expressed with both +a \"d\" unit and a \"H:MM\" part, whereas a duration shorter than +a day is expressed only as a \"H:MM\" string. + +Eventually, + + ((\"d\" . nil) (\"h\" . nil) (special . 2)) + +expresses a duration longer than a day as a decimal number, with +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) + (const :tag "Use H:MM:SS" h:mm:ss) + (repeat :tag "Use units" + (choice + (cons :tag "Use units" + (string :tag "Unit") + (choice (const :tag "Skip when zero" nil) + (const :tag "Always used" t))) + (cons :tag "Use a single decimal unit" + (const special) + (integer :tag "Number of decimals")) + (cons :tag "Use both units and H:MM" + (const special) + (const h:mm)) + (cons :tag "Use both units and H:MM:SS" + (const special) + (const h:mm:ss)))))) + + +;;; Internal variables and functions + +(defconst org-duration--h:mm-re + "\\`[ \t]*[0-9]+\\(?::[0-9]\\{2\\}\\)\\{1,2\\}[ \t]*\\'" + "Regexp matching a duration expressed with H:MM or H:MM:SS format. +See `org-duration--h:mm:ss-re' to only match the latter. Hours +can use any number of digits.") + +(defconst org-duration--h:mm:ss-re + "\\`[ \t]*[0-9]+\\(?::[0-9]\\{2\\}\\)\\{2\\}[ \t]*\\'" + "Regexp matching a duration expressed H:MM:SS format. +See `org-duration--h:mm-re' to also support H:MM format. Hours +can use any number of digits.") + +(defvar org-duration--unit-re nil + "Regexp matching a duration with an unit. +Allowed units are defined in `org-duration-units'. Match group +1 contains the bare number. Match group 2 contains the unit.") + +(defvar org-duration--full-re nil + "Regexp matching a duration expressed with units. +Allowed units are defined in `org-duration-units'.") + +(defvar org-duration--mixed-re nil + "Regexp matching a duration expressed with units and H:MM or H:MM:SS format. +Allowed units are defined in `org-duration-units'. Match group +1 contains units part. Match group 2 contains H:MM or H:MM:SS +part.") + +(defun org-duration--modifier (unit &optional canonical) + "Return modifier associated to string UNIT. +When optional argument CANONICAL is non-nil, refer to +`org-duration-canonical-units' instead of `org-duration-units'." + (or (cdr (assoc unit (if canonical + org-duration-canonical-units + org-duration-units))) + (error "Unknown unit: %S" unit))) + + +;;; Public functions + +;;;###autoload +(defun org-duration-set-regexps () + "Set duration related regexps." + (interactive) + (setq org-duration--unit-re + (concat "\\([0-9]+\\(?:\\.[0-9]*\\)?\\)[ \t]*" + ;; Since user-defined units in `org-duration-units' + ;; can differ from canonical units in + ;; `org-duration-canonical-units', include both in + ;; regexp. + (regexp-opt (mapcar #'car (append org-duration-canonical-units + org-duration-units)) + t))) + (setq org-duration--full-re + (format "\\`[ \t]*%s\\(?:[ \t]+%s\\)*[ \t]*\\'" + org-duration--unit-re + org-duration--unit-re)) + (setq org-duration--mixed-re + (format "\\`[ \t]*\\(?1:%s\\(?:[ \t]+%s\\)*\\)[ \t]+\ +\\(?2:[0-9]+\\(?::[0-9][0-9]\\)\\{1,2\\}\\)[ \t]*\\'" + org-duration--unit-re + org-duration--unit-re))) + +;;;###autoload +(defun org-duration-p (s) + "Non-nil when string S is a time duration." + (and (stringp s) + (or (string-match-p org-duration--full-re s) + (string-match-p org-duration--mixed-re s) + (string-match-p org-duration--h:mm-re s)))) + +;;;###autoload +(defun org-duration-to-minutes (duration &optional canonical) + "Return number of minutes of DURATION string. + +When optional argument CANONICAL is non-nil, ignore +`org-duration-units' and use standard time units value. + +A bare number is translated into minutes. The empty string is +translated into 0.0. + +Return value as a float. Raise an error if duration format is +not recognized." + (cond + ((equal duration "") 0.0) + ((numberp duration) (float duration)) + ((string-match-p org-duration--h:mm-re duration) + (pcase-let ((`(,hours ,minutes ,seconds) + (mapcar #'string-to-number (split-string duration ":")))) + (+ (/ (or seconds 0) 60.0) minutes (* 60 hours)))) + ((string-match-p org-duration--full-re duration) + (let ((minutes 0) + (s 0)) + (while (string-match org-duration--unit-re duration s) + (setq s (match-end 0)) + (let ((value (string-to-number (match-string 1 duration))) + (unit (match-string 2 duration))) + (cl-incf minutes (* value (org-duration--modifier unit canonical))))) + (float minutes))) + ((string-match org-duration--mixed-re duration) + (let ((units-part (match-string 1 duration)) + (hms-part (match-string 2 duration))) + (+ (org-duration-to-minutes units-part) + (org-duration-to-minutes hms-part)))) + ((string-match-p "\\`[0-9]+\\(\\.[0-9]*\\)?\\'" duration) + (float (string-to-number duration))) + (t (error "Invalid duration format: %S" duration)))) + +;;;###autoload +(defun org-duration-from-minutes (minutes &optional fmt canonical) + "Return duration string for a given number of MINUTES. + +Format duration according to `org-duration-format' or FMT, when +non-nil. + +When optional argument CANONICAL is non-nil, ignore +`org-duration-units' and use standard time units value. + +Raise an error if expected format is unknown." + (pcase (or fmt org-duration-format) + (`h:mm + (let ((minutes (floor minutes))) + (format "%d:%02d" (/ minutes 60) (mod minutes 60)))) + (`h:mm:ss + (let* ((whole-minutes (floor minutes)) + (seconds (floor (* 60 (- minutes whole-minutes))))) + (format "%s:%02d" + (org-duration-from-minutes whole-minutes 'h:mm) + seconds))) + ((pred atom) (error "Invalid duration format specification: %S" fmt)) + ;; Mixed format. Call recursively the function on both parts. + ((and duration-format + (let `(special . ,(and mode (or `h:mm:ss `h:mm))) + (assq 'special duration-format))) + (let* ((truncated-format + ;; Remove "special" mode from duration format in order to + ;; recurse properly. Also remove units smaller or equal + ;; to an hour since H:MM part takes care of it. + (cl-remove-if-not + (lambda (pair) + (pcase pair + (`(,(and unit (pred stringp)) . ,_) + (> (org-duration--modifier unit canonical) 60)) + (_ nil))) + duration-format)) + (min-modifier ;smallest modifier above hour + (and truncated-format + (apply #'min + (mapcar (lambda (p) + (org-duration--modifier (car p) canonical)) + truncated-format))))) + (if (or (null min-modifier) (< minutes min-modifier)) + ;; There is not unit above the hour or the smallest unit + ;; above the hour is too large for the number of minutes we + ;; need to represent. Use H:MM or H:MM:SS syntax. + (org-duration-from-minutes minutes mode canonical) + ;; 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))) + (concat + (org-duration-from-minutes units-part truncated-format canonical) + " " + (org-duration-from-minutes minutes-part mode)))))) + ;; Units format. + (duration-format + (let* ((fractional + (let ((digits (cdr (assq 'special duration-format)))) + (and digits + (or (wholenump digits) + (error "Unknown formatting directive: %S" digits)) + (format "%%.%df" digits)))) + (selected-units + (sort (cl-remove-if + ;; Ignore special format cells. + (lambda (pair) (pcase pair (`(special . ,_) t) (_ nil))) + duration-format) + (lambda (a b) + (> (org-duration--modifier (car a) canonical) + (org-duration--modifier (car b) canonical)))))) + (cond + ;; Fractional duration: use first unit that is either required + ;; or smaller than MINUTES. + (fractional + (let* ((unit (car + (or (cl-find-if + (lambda (pair) + (pcase pair + (`(,u . ,req?) + (or req? + (<= (org-duration--modifier u canonical) + minutes))))) + selected-units) + ;; Fall back to smallest unit. + (org-last selected-units)))) + (modifier (org-duration--modifier unit canonical))) + (concat (format fractional (/ (float minutes) modifier)) unit))) + ;; Otherwise build duration string according to available + ;; units. + ((org-string-nw-p + (org-trim + (mapconcat + (lambda (units) + (pcase-let* ((`(,unit . ,required?) units) + (modifier (org-duration--modifier unit canonical))) + (cond ((<= modifier minutes) + (let ((value (if (integerp modifier) + (/ (floor minutes) modifier) + (floor (/ minutes modifier))))) + (cl-decf minutes (* value modifier)) + (format " %d%s" value unit))) + (required? (concat " 0" unit)) + (t "")))) + selected-units + "")))) + ;; No unit can properly represent MINUTES. Use the smallest + ;; one anyway. + (t + (pcase-let ((`((,unit . ,_)) (last selected-units))) + (concat "0" unit)))))))) + +;;;###autoload +(defun org-duration-h:mm-only-p (times) + "Non-nil when every duration in TIMES has \"H:MM\" or \"H:MM:SS\" format. + +TIMES is a list of duration strings. + +Return nil if any duration is expressed with units, as defined in +`org-duration-units'. Otherwise, if any duration is expressed +with \"H:MM:SS\" format, return `h:mm:ss'. Otherwise, return +`h:mm'." + (let (hms-flag) + (catch :exit + (dolist (time times) + (cond ((string-match-p org-duration--full-re time) + (throw :exit nil)) + ((string-match-p org-duration--mixed-re time) + (throw :exit nil)) + (hms-flag nil) + ((string-match-p org-duration--h:mm:ss-re time) + (setq hms-flag 'h:mm:ss)))) + (or hms-flag 'h:mm)))) + + +;;; Initialization + +(org-duration-set-regexps) + +(provide 'org-duration) +;;; org-duration.el ends here diff --git a/lisp/org/org-element.el b/lisp/org/org-element.el index e9731c17836..c5f656e09ea 100644 --- a/lisp/org/org-element.el +++ b/lisp/org/org-element.el @@ -1,4 +1,4 @@ -;;; org-element.el --- Parser And Applications for Org syntax +;;; org-element.el --- Parser for Org Syntax -*- lexical-binding: t; -*- ;; Copyright (C) 2012-2017 Free Software Foundation, Inc. @@ -18,84 +18,25 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; -;; Org syntax can be divided into three categories: "Greater -;; elements", "Elements" and "Objects". +;; See <http://orgmode.org/worg/dev/org-syntax.html> for details about +;; Org syntax. ;; -;; Elements are related to the structure of the document. Indeed, all -;; elements are a cover for the document: each position within belongs -;; to at least one element. -;; -;; An element always starts and ends at the beginning of a line. With -;; a few exceptions (`clock', `headline', `inlinetask', `item', -;; `planning', `node-property', `quote-section' `section' and -;; `table-row' types), it can also accept a fixed set of keywords as -;; attributes. Those are called "affiliated keywords" to distinguish -;; them from other keywords, which are full-fledged elements. Almost -;; all affiliated keywords are referenced in -;; `org-element-affiliated-keywords'; the others are export attributes -;; and start with "ATTR_" prefix. -;; -;; Element containing other elements (and only elements) are called -;; greater elements. Concerned types are: `center-block', `drawer', -;; `dynamic-block', `footnote-definition', `headline', `inlinetask', -;; `item', `plain-list', `property-drawer', `quote-block', `section' -;; and `special-block'. -;; -;; Other element types are: `babel-call', `clock', `comment', -;; `comment-block', `diary-sexp', `example-block', `export-block', -;; `fixed-width', `horizontal-rule', `keyword', `latex-environment', -;; `node-property', `paragraph', `planning', `quote-section', -;; `src-block', `table', `table-row' and `verse-block'. Among them, -;; `paragraph' and `verse-block' types can contain Org objects and -;; plain text. -;; -;; Objects are related to document's contents. Some of them are -;; recursive. Associated types are of the following: `bold', `code', -;; `entity', `export-snippet', `footnote-reference', -;; `inline-babel-call', `inline-src-block', `italic', -;; `latex-fragment', `line-break', `link', `macro', `radio-target', -;; `statistics-cookie', `strike-through', `subscript', `superscript', -;; `table-cell', `target', `timestamp', `underline' and `verbatim'. -;; -;; Some elements also have special properties whose value can hold -;; objects themselves (e.g. an item tag or a headline name). Such -;; values are called "secondary strings". Any object belongs to -;; either an element or a secondary string. -;; -;; Notwithstanding affiliated keywords, each greater element, element -;; and object has a fixed set of properties attached to it. Among -;; them, four are shared by all types: `:begin' and `:end', which -;; refer to the beginning and ending buffer positions of the -;; considered element or object, `:post-blank', which holds the number -;; of blank lines, or white spaces, at its end and `:parent' which -;; refers to the element or object containing it. Greater elements, -;; elements and objects containing objects will also have -;; `:contents-begin' and `:contents-end' properties to delimit -;; contents. Eventually, greater elements and elements accepting -;; affiliated keywords will have a `:post-affiliated' property, -;; referring to the buffer position after all such keywords. -;; -;; At the lowest level, a `:parent' property is also attached to any -;; string, as a text property. -;; -;; Lisp-wise, an element or an object can be represented as a list. +;; Lisp-wise, a syntax object can be represented as a list. ;; It follows the pattern (TYPE PROPERTIES CONTENTS), where: -;; TYPE is a symbol describing the Org element or object. +;; TYPE is a symbol describing the object. ;; PROPERTIES is the property list attached to it. See docstring of -;; appropriate parsing function to get an exhaustive -;; list. -;; CONTENTS is a list of elements, objects or raw strings contained -;; in the current element or object, when applicable. +;; appropriate parsing function to get an exhaustive list. +;; CONTENTS is a list of syntax objects or raw strings contained +;; in the current object, when applicable. ;; -;; An Org buffer is a nested list of such elements and objects, whose -;; type is `org-data' and properties is nil. +;; For the whole document, TYPE is `org-data' and PROPERTIES is nil. ;; -;; The first part of this file defines Org syntax, while the second -;; one provide accessors and setters functions. +;; The first part of this file defines constants for the Org syntax, +;; while the second one provide accessors and setters functions. ;; ;; The next part implements a parser and an interpreter for each ;; element and object type in Org syntax. @@ -111,13 +52,15 @@ ;; ;; The library ends by furnishing `org-element-at-point' function, and ;; a way to give information about document structure around point -;; with `org-element-context'. +;; with `org-element-context'. A cache mechanism is also provided for +;; these functions. ;;; Code: -(eval-when-compile (require 'cl)) (require 'org) +(require 'avl-tree) +(require 'cl-lib) @@ -127,56 +70,116 @@ ;; along with the affiliated keywords recognized. Also set up ;; restrictions on recursive objects combinations. ;; -;; These variables really act as a control center for the parsing -;; process. - -(defconst org-element-paragraph-separate - (concat "^\\(?:" - ;; Headlines, inlinetasks. - org-outline-regexp "\\|" - ;; Footnote definitions. - "\\[\\(?:[0-9]+\\|fn:[-_[:word:]]+\\)\\]" "\\|" - ;; Diary sexps. - "%%(" "\\|" - "[ \t]*\\(?:" - ;; Empty lines. - "$" "\\|" - ;; Tables (any type). - "\\(?:|\\|\\+-[-+]\\)" "\\|" - ;; Blocks (any type), Babel calls and keywords. Note: this - ;; is only an indication and need some thorough check. - "#\\(?:[+ ]\\|$\\)" "\\|" - ;; Drawers (any type) and fixed-width areas. This is also - ;; only an indication. - ":" "\\|" - ;; Horizontal rules. - "-\\{5,\\}[ \t]*$" "\\|" - ;; LaTeX environments. - "\\\\begin{\\([A-Za-z0-9]+\\*?\\)}" "\\|" - ;; Planning and Clock lines. - (regexp-opt (list org-scheduled-string - org-deadline-string - org-closed-string - org-clock-string)) - "\\|" - ;; Lists. - (let ((term (case org-plain-list-ordered-item-terminator - (?\) ")") (?. "\\.") (otherwise "[.)]"))) - (alpha (and org-list-allow-alphabetical "\\|[A-Za-z]"))) - (concat "\\(?:[-+*]\\|\\(?:[0-9]+" alpha "\\)" term "\\)" - "\\(?:[ \t]\\|$\\)")) - "\\)\\)") +;; `org-element-update-syntax' builds proper syntax regexps according +;; to current setup. + +(defvar org-element-paragraph-separate nil "Regexp to separate paragraphs in an Org buffer. In the case of lines starting with \"#\" and \":\", this regexp is not sufficient to know if point is at a paragraph ending. See `org-element-paragraph-parser' for more information.") +(defvar org-element--object-regexp nil + "Regexp possibly matching the beginning of an object. +This regexp allows false positives. Dedicated parser (e.g., +`org-export-bold-parser') will take care of further filtering. +Radio links are not matched by this regexp, as they are treated +specially in `org-element--object-lex'.") + +(defun org-element--set-regexps () + "Build variable syntax regexps." + (setq org-element-paragraph-separate + (concat "^\\(?:" + ;; Headlines, inlinetasks. + org-outline-regexp "\\|" + ;; Footnote definitions. + "\\[fn:[-_[:word:]]+\\]" "\\|" + ;; Diary sexps. + "%%(" "\\|" + "[ \t]*\\(?:" + ;; Empty lines. + "$" "\\|" + ;; Tables (any type). + "|" "\\|" + "\\+\\(?:-+\\+\\)+[ \t]*$" "\\|" + ;; Comments, keyword-like or block-like constructs. + ;; Blocks and keywords with dual values need to be + ;; double-checked. + "#\\(?: \\|$\\|\\+\\(?:" + "BEGIN_\\S-+" "\\|" + "\\S-+\\(?:\\[.*\\]\\)?:[ \t]*\\)\\)" + "\\|" + ;; Drawers (any type) and fixed-width areas. Drawers + ;; need to be double-checked. + ":\\(?: \\|$\\|[-_[:word:]]+:[ \t]*$\\)" "\\|" + ;; Horizontal rules. + "-\\{5,\\}[ \t]*$" "\\|" + ;; LaTeX environments. + "\\\\begin{\\([A-Za-z0-9*]+\\)}" "\\|" + ;; Clock lines. + (regexp-quote org-clock-string) "\\|" + ;; Lists. + (let ((term (pcase org-plain-list-ordered-item-terminator + (?\) ")") (?. "\\.") (_ "[.)]"))) + (alpha (and org-list-allow-alphabetical "\\|[A-Za-z]"))) + (concat "\\(?:[-+*]\\|\\(?:[0-9]+" alpha "\\)" term "\\)" + "\\(?:[ \t]\\|$\\)")) + "\\)\\)") + org-element--object-regexp + (mapconcat #'identity + (let ((link-types (regexp-opt (org-link-types)))) + (list + ;; Sub/superscript. + "\\(?:[_^][-{(*+.,[:alnum:]]\\)" + ;; Bold, code, italic, strike-through, underline + ;; and verbatim. + (concat "[*~=+_/]" + (format "[^%s]" + (nth 2 org-emphasis-regexp-components))) + ;; Plain links. + (concat "\\<" link-types ":") + ;; Objects starting with "[": regular link, + ;; footnote reference, statistics cookie, + ;; timestamp (inactive). + (concat "\\[\\(?:" + "fn:" "\\|" + "\\[" "\\|" + "[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}" "\\|" + "[0-9]*\\(?:%\\|/[0-9]*\\)\\]" + "\\)") + ;; Objects starting with "@": export snippets. + "@@" + ;; Objects starting with "{": macro. + "{{{" + ;; Objects starting with "<" : timestamp + ;; (active, diary), target, radio target and + ;; angular links. + (concat "<\\(?:%%\\|<\\|[0-9]\\|" link-types "\\)") + ;; Objects starting with "$": latex fragment. + "\\$" + ;; Objects starting with "\": line break, + ;; entity, latex fragment. + "\\\\\\(?:[a-zA-Z[(]\\|\\\\[ \t]*$\\|_ +\\)" + ;; Objects starting with raw text: inline Babel + ;; source block, inline Babel call. + "\\(?:call\\|src\\)_")) + "\\|"))) + +(org-element--set-regexps) + +;;;###autoload +(defun org-element-update-syntax () + "Update parser internals." + (interactive) + (org-element--set-regexps) + (org-element-cache-reset 'all)) + (defconst org-element-all-elements '(babel-call center-block clock comment comment-block diary-sexp drawer dynamic-block example-block export-block fixed-width footnote-definition headline horizontal-rule inlinetask item keyword latex-environment node-property paragraph plain-list - planning property-drawer quote-block quote-section section + planning property-drawer quote-block section special-block src-block table table-row verse-block) "Complete list of element types.") @@ -186,23 +189,6 @@ is not sufficient to know if point is at a paragraph ending. See special-block table) "List of recursive element types aka Greater Elements.") -(defconst org-element-all-successors - '(link export-snippet footnote-reference inline-babel-call - inline-src-block latex-or-entity line-break macro plain-link - radio-target statistics-cookie sub/superscript table-cell target - text-markup timestamp) - "Complete list of successors.") - -(defconst org-element-object-successor-alist - '((subscript . sub/superscript) (superscript . sub/superscript) - (bold . text-markup) (code . text-markup) (italic . text-markup) - (strike-through . text-markup) (underline . text-markup) - (verbatim . text-markup) (entity . latex-or-entity) - (latex-fragment . latex-or-entity)) - "Alist of translations between object type and successor name. -Sharing the same successor comes handy when, for example, the -regexp matching one object can also match the other object.") - (defconst org-element-all-objects '(bold code entity export-snippet footnote-reference inline-babel-call inline-src-block italic line-break latex-fragment link macro @@ -211,26 +197,13 @@ regexp matching one object can also match the other object.") "Complete list of object types.") (defconst org-element-recursive-objects - '(bold italic link subscript radio-target strike-through superscript - table-cell underline) + '(bold footnote-reference italic link subscript radio-target strike-through + superscript table-cell underline) "List of recursive object types.") -(defvar org-element-block-name-alist - '(("CENTER" . org-element-center-block-parser) - ("COMMENT" . org-element-comment-block-parser) - ("EXAMPLE" . org-element-example-block-parser) - ("QUOTE" . org-element-quote-block-parser) - ("SRC" . org-element-src-block-parser) - ("VERSE" . org-element-verse-block-parser)) - "Alist between block names and the associated parsing function. -Names must be uppercase. Any block whose name has no association -is parsed with `org-element-special-block-parser'.") - -(defconst org-element-link-type-is-file - '("file" "file+emacs" "file+sys" "docview") - "List of link types equivalent to \"file\". -Only these types can accept search options and an explicit -application to open them.") +(defconst org-element-object-containers + (append org-element-recursive-objects '(paragraph table-row verse-block)) + "List of object or element types that can directly contain objects.") (defconst org-element-affiliated-keywords '("CAPTION" "DATA" "HEADER" "HEADERS" "LABEL" "NAME" "PLOT" "RESNAME" "RESULT" @@ -268,6 +241,13 @@ strings and objects. This list is checked after translations have been applied. See `org-element-keyword-translation-alist'.") +(defconst org-element--parsed-properties-alist + (mapcar (lambda (k) (cons k (intern (concat ":" (downcase k))))) + org-element-parsed-keywords) + "Alist of parsed keywords and associated properties. +This is generated from `org-element-parsed-keywords', which +see.") + (defconst org-element-dual-keywords '("CAPTION" "RESULTS") "List of affiliated keywords which can have a secondary value. @@ -280,13 +260,8 @@ associated to a hash value with the following: This list is checked after translations have been applied. See `org-element-keyword-translation-alist'.") -(defconst org-element-document-properties '("AUTHOR" "DATE" "TITLE") - "List of properties associated to the whole document. -Any keyword in this list will have its value parsed and stored as -a secondary string.") - (defconst org-element--affiliated-re - (format "[ \t]*#\\+\\(?:%s\\):\\(?: \\|$\\)" + (format "[ \t]*#\\+\\(?:%s\\):[ \t]*" (concat ;; Dual affiliated keywords. (format "\\(?1:%s\\)\\(?:\\[\\(.*\\)\\]\\)?" @@ -295,9 +270,8 @@ a secondary string.") ;; Regular affiliated keywords. (format "\\(?1:%s\\)" (regexp-opt - (org-remove-if - #'(lambda (keyword) - (member keyword org-element-dual-keywords)) + (cl-remove-if + (lambda (k) (member k org-element-dual-keywords)) org-element-affiliated-keywords))) "\\|" ;; Export attributes. @@ -311,8 +285,7 @@ match group 2. Don't modify it, set `org-element-affiliated-keywords' instead.") (defconst org-element-object-restrictions - (let* ((standard-set - (remq 'plain-link (remq 'table-cell org-element-all-successors))) + (let* ((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) @@ -320,30 +293,33 @@ Don't modify it, set `org-element-affiliated-keywords' instead.") (inlinetask ,@standard-set-no-line-break) (italic ,@standard-set) (item ,@standard-set-no-line-break) - (keyword ,@standard-set) - ;; Ignore all links excepted plain links in a link description. - ;; Also ignore radio-targets and line breaks. - (link export-snippet inline-babel-call inline-src-block latex-or-entity - macro plain-link statistics-cookie sub/superscript text-markup) + (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) (paragraph ,@standard-set) ;; Remove any variable object from radio target as it would ;; prevent it from being properly recognized. - (radio-target latex-or-entity sub/superscript text-markup) + (radio-target bold code entity italic latex-fragment strike-through + subscript superscript underline superscript) (strike-through ,@standard-set) (subscript ,@standard-set) (superscript ,@standard-set) ;; Ignore inline babel call and inline src block as formulas are ;; possible. Also ignore line breaks and statistics cookies. - (table-cell link export-snippet footnote-reference latex-or-entity macro - radio-target sub/superscript target text-markup timestamp) + (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-row table-cell) (underline ,@standard-set) (verse-block ,@standard-set))) "Alist of objects restrictions. -CAR is an element or object type containing objects and CDR is -a list of successors that will be called within an element or -object of such type. +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 @@ -354,12 +330,56 @@ This alist also applies to secondary string. For example, an still has an entry since one of its properties (`:title') does.") (defconst org-element-secondary-value-alist - '((headline . :title) - (inlinetask . :title) - (item . :tag) - (footnote-reference . :inline-definition)) - "Alist between element types and location of secondary value.") - + '((headline :title) + (inlinetask :title) + (item :tag)) + "Alist between element types and locations of secondary values.") + +(defconst org-element--pair-round-table + (let ((table (make-syntax-table))) + (modify-syntax-entry ?\( "()" table) + (modify-syntax-entry ?\) ")(" table) + (dolist (char '(?\{ ?\} ?\[ ?\] ?\< ?\>) table) + (modify-syntax-entry char " " table))) + "Table used internally to pair only round brackets. +Other brackets are treated as spaces.") + +(defconst org-element--pair-square-table + (let ((table (make-syntax-table))) + (modify-syntax-entry ?\[ "(]" table) + (modify-syntax-entry ?\] ")[" table) + (dolist (char '(?\{ ?\} ?\( ?\) ?\< ?\>) table) + (modify-syntax-entry char " " table))) + "Table used internally to pair only square brackets. +Other brackets are treated as spaces.") + +(defconst org-element--pair-curly-table + (let ((table (make-syntax-table))) + (modify-syntax-entry ?\{ "(}" table) + (modify-syntax-entry ?\} "){" table) + (dolist (char '(?\[ ?\] ?\( ?\) ?\< ?\>) table) + (modify-syntax-entry char " " table))) + "Table used internally to pair only curly brackets. +Other brackets are treated as spaces.") + +(defun org-element--parse-paired-brackets (char) + "Parse paired brackets at point. +CHAR is the opening bracket to consider, as a character. Return +contents between brackets, as a string, or nil. Also move point +past the brackets." + (when (eq char (char-after)) + (let ((syntax-table (pcase char + (?\{ org-element--pair-curly-table) + (?\[ org-element--pair-square-table) + (?\( org-element--pair-round-table) + (_ nil))) + (pos (point))) + (when syntax-table + (with-syntax-table syntax-table + (let ((end (ignore-errors (scan-lists pos 1 0)))) + (when end + (goto-char end) + (buffer-substring-no-properties (1+ pos) (1- end))))))))) ;;; Accessors and Setters @@ -368,10 +388,18 @@ still has an entry since one of its properties (`:title') does.") ;; `org-element-contents' and `org-element-restriction'. ;; ;; Setter functions allow modification of elements by side effect. -;; There is `org-element-put-property', `org-element-set-contents', -;; `org-element-set-element' and `org-element-adopt-element'. Note -;; that `org-element-set-element' and `org-element-adopt-elements' are -;; higher level functions since also update `:parent' property. +;; There is `org-element-put-property', `org-element-set-contents'. +;; These low-level functions are useful to build a parse tree. +;; +;; `org-element-adopt-elements', `org-element-set-element', +;; `org-element-extract-element' and `org-element-insert-before' are +;; high-level functions useful to modify a parse tree. +;; +;; `org-element-secondary-p' is a predicate used to know if a given +;; object belongs to a secondary string. `org-element-class' tells if +;; some parsed data is an element or an object, handling pseudo +;; elements and objects. `org-element-copy' returns an element or +;; object, stripping its parent property in the process. (defsubst org-element-type (element) "Return type of ELEMENT. @@ -411,29 +439,49 @@ Return modified element." element)) (defsubst org-element-set-contents (element &rest contents) - "Set ELEMENT contents to CONTENTS. -Return modified element." - (cond ((not element) (list contents)) + "Set ELEMENT's contents to CONTENTS. +Return ELEMENT." + (cond ((null element) contents) ((not (symbolp (car element))) contents) - ((cdr element) (setcdr (cdr element) contents)) + ((cdr element) (setcdr (cdr element) contents) element) (t (nconc element contents)))) -(defsubst org-element-set-element (old new) - "Replace element or object OLD with element or object NEW. -The function takes care of setting `:parent' property for NEW." - ;; Since OLD is going to be changed into NEW by side-effect, first - ;; make sure that every element or object within NEW has OLD as - ;; parent. - (mapc (lambda (blob) (org-element-put-property blob :parent old)) - (org-element-contents new)) - ;; Transfer contents. - (apply 'org-element-set-contents old (org-element-contents new)) - ;; Ensure NEW has same parent as OLD, then overwrite OLD properties - ;; with NEW's. - (org-element-put-property new :parent (org-element-property :parent old)) - (setcar (cdr old) (nth 1 new)) - ;; Transfer type. - (setcar old (car new))) +(defun org-element-secondary-p (object) + "Non-nil when OBJECT directly belongs to a secondary string. +Return value is the property name, as a keyword, or nil." + (let* ((parent (org-element-property :parent object)) + (properties (cdr (assq (org-element-type parent) + org-element-secondary-value-alist)))) + (catch 'exit + (dolist (p properties) + (and (memq object (org-element-property p parent)) + (throw 'exit p)))))) + +(defsubst org-element-class (datum &optional parent) + "Return class for ELEMENT, as a symbol. +Class is either `element' or `object'. Optional argument PARENT +is the element or object containing DATUM. It defaults to the +value of DATUM `:parent' property." + (let ((type (org-element-type datum)) + (parent (or parent (org-element-property :parent datum)))) + (cond + ;; Trivial cases. + ((memq type org-element-all-objects) 'object) + ((memq type org-element-all-elements) 'element) + ;; Special cases. + ((eq type 'org-data) 'element) + ((eq type 'plain-text) 'object) + ((not type) 'object) + ;; Pseudo object or elements. Make a guess about its class. + ;; Basically a pseudo object is contained within another object, + ;; a secondary string or a container element. + ((not parent) 'element) + (t + (let ((parent-type (org-element-type parent))) + (cond ((not parent-type) 'object) + ((memq parent-type org-element-object-containers) 'object) + ((org-element-secondary-p datum) 'object) + (t 'element))))))) (defsubst org-element-adopt-elements (parent &rest children) "Append elements to the contents of another element. @@ -443,18 +491,108 @@ objects, or a strings. The function takes care of setting `:parent' property for CHILD. Return parent element." - ;; Link every child to PARENT. If PARENT is nil, it is a secondary - ;; string: parent is the list itself. - (mapc (lambda (child) - (org-element-put-property child :parent (or parent children))) - children) - ;; Add CHILDREN at the end of PARENT contents. - (when parent - (apply 'org-element-set-contents - parent - (nconc (org-element-contents parent) children))) - ;; Return modified PARENT element. - (or parent children)) + (if (not children) parent + ;; Link every child to PARENT. If PARENT is nil, it is a secondary + ;; string: parent is the list itself. + (dolist (child children) + (org-element-put-property child :parent (or parent children))) + ;; Add CHILDREN at the end of PARENT contents. + (when parent + (apply #'org-element-set-contents + parent + (nconc (org-element-contents parent) children))) + ;; Return modified PARENT element. + (or parent children))) + +(defun org-element-extract-element (element) + "Extract ELEMENT from parse tree. +Remove element from the parse tree by side-effect, and return it +with its `:parent' property stripped out." + (let ((parent (org-element-property :parent element)) + (secondary (org-element-secondary-p element))) + (if secondary + (org-element-put-property + parent secondary + (delq element (org-element-property secondary parent))) + (apply #'org-element-set-contents + parent + (delq element (org-element-contents parent)))) + ;; Return ELEMENT with its :parent removed. + (org-element-put-property element :parent nil))) + +(defun org-element-insert-before (element location) + "Insert ELEMENT before LOCATION in parse tree. +LOCATION is an element, object or string within the parse tree. +Parse tree is modified by side effect." + (let* ((parent (org-element-property :parent location)) + (property (org-element-secondary-p location)) + (siblings (if property (org-element-property property parent) + (org-element-contents parent))) + ;; Special case: LOCATION is the first element of an + ;; independent secondary string (e.g. :title property). Add + ;; ELEMENT in-place. + (specialp (and (not property) + (eq siblings parent) + (eq (car parent) location)))) + ;; Install ELEMENT at the appropriate LOCATION within SIBLINGS. + (cond (specialp) + ((or (null siblings) (eq (car siblings) location)) + (push element siblings)) + ((null location) (nconc siblings (list element))) + (t + (let ((index (cl-position location siblings))) + (unless index (error "No location found to insert element")) + (push element (cdr (nthcdr (1- index) siblings)))))) + ;; Store SIBLINGS at appropriate place in parse tree. + (cond + (specialp (setcdr parent (copy-sequence parent)) (setcar parent element)) + (property (org-element-put-property parent property siblings)) + (t (apply #'org-element-set-contents parent siblings))) + ;; Set appropriate :parent property. + (org-element-put-property element :parent parent))) + +(defun org-element-set-element (old new) + "Replace element or object OLD with element or object NEW. +The function takes care of setting `:parent' property for NEW." + ;; Ensure OLD and NEW have the same parent. + (org-element-put-property new :parent (org-element-property :parent old)) + (if (or (memq (org-element-type old) '(plain-text nil)) + (memq (org-element-type new) '(plain-text nil))) + ;; We cannot replace OLD with NEW since one of them is not an + ;; object or element. We take the long path. + (progn (org-element-insert-before new old) + (org-element-extract-element old)) + ;; Since OLD is going to be changed into NEW by side-effect, first + ;; make sure that every element or object within NEW has OLD as + ;; parent. + (dolist (blob (org-element-contents new)) + (org-element-put-property blob :parent old)) + ;; Transfer contents. + (apply #'org-element-set-contents old (org-element-contents new)) + ;; Overwrite OLD's properties with NEW's. + (setcar (cdr old) (nth 1 new)) + ;; Transfer type. + (setcar old (car new)))) + +(defun org-element-create (type &optional props &rest children) + "Create a new element of type TYPE. +Optional argument PROPS, when non-nil, is a plist defining the +properties of the element. CHILDREN can be elements, objects or +strings." + (apply #'org-element-adopt-elements (list type props) children)) + +(defun org-element-copy (datum) + "Return a copy of DATUM. +DATUM is an element, object, string or nil. `:parent' property +is cleared and contents are removed in the process." + (when datum + (let ((type (org-element-type datum))) + (pcase type + (`org-data (list 'org-data nil)) + (`plain-text (substring-no-properties datum)) + (`nil (copy-sequence datum)) + (_ + (list type (plist-put (copy-sequence (nth 1 datum)) :parent nil))))))) @@ -467,7 +605,7 @@ Return parent element." ;; Most of them accepts no argument. Though, exceptions exist. Hence ;; every element containing a secondary string (see ;; `org-element-secondary-value-alist') will accept an optional -;; argument to toggle parsing of that secondary string. Moreover, +;; argument to toggle parsing of these secondary strings. Moreover, ;; `item' parser requires current list's structure as its first ;; element. ;; @@ -503,8 +641,8 @@ keyword and CDR is a plist of affiliated keywords along with their value. Return a list whose CAR is `center-block' and CDR is a plist -containing `:begin', `:end', `:hiddenp', `:contents-begin', -`:contents-end', `:post-blank' and `:post-affiliated' keywords. +containing `:begin', `:end', `:contents-begin', `:contents-end', +`:post-blank' and `:post-affiliated' keywords. Assume point is at the beginning of the block." (let ((case-fold-search t)) @@ -520,7 +658,6 @@ Assume point is at the beginning of the block." (and (< (point) block-end-line) (point)))) (contents-end (and contents-begin block-end-line)) - (hidden (org-invisible-p2)) (pos-before-blank (progn (goto-char block-end-line) (forward-line) (point))) @@ -531,15 +668,14 @@ Assume point is at the beginning of the block." (nconc (list :begin begin :end end - :hiddenp hidden :contents-begin contents-begin :contents-end contents-end :post-blank (count-lines pos-before-blank end) :post-affiliated post-affiliated) (cdr affiliated)))))))) -(defun org-element-center-block-interpreter (center-block contents) - "Interpret CENTER-BLOCK element as Org syntax. +(defun org-element-center-block-interpreter (_ contents) + "Interpret a center-block element as Org syntax. CONTENTS is the contents of the element." (format "#+BEGIN_CENTER\n%s#+END_CENTER" contents)) @@ -555,7 +691,7 @@ keyword and CDR is a plist of affiliated keywords along with their value. Return a list whose CAR is `drawer' and CDR is a plist containing -`:drawer-name', `:begin', `:end', `:hiddenp', `:contents-begin', +`:drawer-name', `:begin', `:end', `:contents-begin', `:contents-end', `:post-blank' and `:post-affiliated' keywords. Assume point is at beginning of drawer." @@ -566,7 +702,7 @@ Assume point is at beginning of drawer." (save-excursion (let* ((drawer-end-line (match-beginning 0)) (name (progn (looking-at org-drawer-regexp) - (org-match-string-no-properties 1))) + (match-string-no-properties 1))) (begin (car affiliated)) (post-affiliated (point)) ;; Empty drawers have no contents. @@ -574,7 +710,6 @@ Assume point is at beginning of drawer." (and (< (point) drawer-end-line) (point)))) (contents-end (and contents-begin drawer-end-line)) - (hidden (org-invisible-p2)) (pos-before-blank (progn (goto-char drawer-end-line) (forward-line) (point))) @@ -585,7 +720,6 @@ Assume point is at beginning of drawer." (list :begin begin :end end :drawer-name name - :hiddenp hidden :contents-begin contents-begin :contents-end contents-end :post-blank (count-lines pos-before-blank end) @@ -611,9 +745,9 @@ keyword and CDR is a plist of affiliated keywords along with their value. Return a list whose CAR is `dynamic-block' and CDR is a plist -containing `:block-name', `:begin', `:end', `:hiddenp', -`:contents-begin', `:contents-end', `:arguments', `:post-blank' -and `:post-affiliated' keywords. +containing `:block-name', `:begin', `:end', `:contents-begin', +`:contents-end', `:arguments', `:post-blank' and +`:post-affiliated' keywords. Assume point is at beginning of dynamic block." (let ((case-fold-search t)) @@ -624,8 +758,8 @@ Assume point is at beginning of dynamic block." (let ((block-end-line (match-beginning 0))) (save-excursion (let* ((name (progn (looking-at org-dblock-start-re) - (org-match-string-no-properties 1))) - (arguments (org-match-string-no-properties 3)) + (match-string-no-properties 1))) + (arguments (match-string-no-properties 3)) (begin (car affiliated)) (post-affiliated (point)) ;; Empty blocks have no contents. @@ -633,7 +767,6 @@ Assume point is at beginning of dynamic block." (and (< (point) block-end-line) (point)))) (contents-end (and contents-begin block-end-line)) - (hidden (org-invisible-p2)) (pos-before-blank (progn (goto-char block-end-line) (forward-line) (point))) @@ -645,7 +778,6 @@ Assume point is at beginning of dynamic block." :end end :block-name name :arguments arguments - :hiddenp hidden :contents-begin contents-begin :contents-end contents-end :post-blank (count-lines pos-before-blank end) @@ -658,12 +790,18 @@ CONTENTS is the contents of the element." (format "#+BEGIN: %s%s\n%s#+END:" (org-element-property :block-name dynamic-block) (let ((args (org-element-property :arguments dynamic-block))) - (and args (concat " " args))) + (if args (concat " " args) "")) contents)) ;;;; Footnote Definition +(defconst org-element--footnote-separator + (concat org-outline-regexp-bol "\\|" + org-footnote-definition-re "\\|" + "^\\([ \t]*\n\\)\\{2,\\}") + "Regexp used as a footnote definition separator.") + (defun org-element-footnote-definition-parser (limit affiliated) "Parse a footnote definition. @@ -679,59 +817,104 @@ a plist containing `:label', `:begin' `:end', `:contents-begin', Assume point is at the beginning of the footnote definition." (save-excursion (let* ((label (progn (looking-at org-footnote-definition-re) - (org-match-string-no-properties 1))) + (match-string-no-properties 1))) (begin (car affiliated)) (post-affiliated (point)) - (ending (save-excursion - (if (progn - (end-of-line) - (re-search-forward - (concat org-outline-regexp-bol "\\|" - org-footnote-definition-re "\\|" - "^\\([ \t]*\n\\)\\{2,\\}") limit 'move)) - (match-beginning 0) - (point)))) - (contents-begin (progn - (search-forward "]") - (skip-chars-forward " \r\t\n" ending) - (cond ((= (point) ending) nil) - ((= (line-beginning-position) begin) (point)) - (t (line-beginning-position))))) - (contents-end (and contents-begin ending)) - (end (progn (goto-char ending) - (skip-chars-forward " \r\t\n" limit) - (if (eobp) (point) (line-beginning-position))))) + (end + (save-excursion + (end-of-line) + (cond + ((not + (re-search-forward org-element--footnote-separator limit t)) + limit) + ((eq ?\[ (char-after (match-beginning 0))) + ;; At a new footnote definition, make sure we end + ;; before any affiliated keyword above. + (forward-line -1) + (while (and (> (point) post-affiliated) + (looking-at-p org-element--affiliated-re)) + (forward-line -1)) + (line-beginning-position 2)) + ((eq ?* (char-after (match-beginning 0))) (match-beginning 0)) + (t (skip-chars-forward " \r\t\n" limit) + (if (= limit (point)) limit (line-beginning-position)))))) + (contents-begin + (progn (search-forward "]") + (skip-chars-forward " \r\t\n" end) + (cond ((= (point) end) nil) + ((= (line-beginning-position) post-affiliated) (point)) + (t (line-beginning-position))))) + (contents-end + (progn (goto-char end) + (skip-chars-backward " \r\t\n") + (line-beginning-position 2)))) (list 'footnote-definition (nconc (list :label label :begin begin :end end :contents-begin contents-begin - :contents-end contents-end - :post-blank (count-lines ending end) + :contents-end (and contents-begin contents-end) + :post-blank (count-lines contents-end end) :post-affiliated post-affiliated) (cdr affiliated)))))) (defun org-element-footnote-definition-interpreter (footnote-definition contents) "Interpret FOOTNOTE-DEFINITION element as Org syntax. CONTENTS is the contents of the footnote-definition." - (concat (format "[%s]" (org-element-property :label footnote-definition)) + (concat (format "[fn:%s]" (org-element-property :label footnote-definition)) " " contents)) ;;;; Headline +(defun org-element--get-node-properties () + "Return node properties associated to headline at point. +Upcase property names. It avoids confusion between properties +obtained through property drawer and default properties from the +parser (e.g. `:end' and :END:). Return value is a plist." + (save-excursion + (forward-line) + (when (looking-at-p org-planning-line-re) (forward-line)) + (when (looking-at org-property-drawer-re) + (forward-line) + (let ((end (match-end 0)) properties) + (while (< (line-end-position) end) + (looking-at org-property-re) + (push (match-string-no-properties 3) properties) + (push (intern (concat ":" (upcase (match-string 2)))) properties) + (forward-line)) + properties)))) + +(defun org-element--get-time-properties () + "Return time properties associated to headline at point. +Return value is a plist." + (save-excursion + (when (progn (forward-line) (looking-at org-planning-line-re)) + (let ((end (line-end-position)) plist) + (while (re-search-forward org-keyword-time-not-clock-regexp end t) + (goto-char (match-end 1)) + (skip-chars-forward " \t") + (let ((keyword (match-string 1)) + (time (org-element-timestamp-parser))) + (cond ((equal keyword org-scheduled-string) + (setq plist (plist-put plist :scheduled time))) + ((equal keyword org-deadline-string) + (setq plist (plist-put plist :deadline time))) + (t (setq plist (plist-put plist :closed time)))))) + plist)))) + (defun org-element-headline-parser (limit &optional raw-secondary-p) "Parse a headline. Return a list whose CAR is `headline' and CDR is a plist -containing `:raw-value', `:title', `:alt-title', `:begin', -`:end', `:pre-blank', `:hiddenp', `:contents-begin', -`:contents-end', `:level', `:priority', `:tags', -`:todo-keyword',`:todo-type', `:scheduled', `:deadline', -`:closed', `:quotedp', `:archivedp', `:commentedp', -`:footnote-section-p' and `:post-blank' keywords. +containing `:raw-value', `:title', `:begin', `:end', +`:pre-blank', `:contents-begin' and `:contents-end', `:level', +`:priority', `:tags', `:todo-keyword',`:todo-type', `:scheduled', +`:deadline', `:closed', `:archivedp', `:commentedp' +`:footnote-section-p', `:post-blank' and `:post-affiliated' +keywords. The plist also contains any property set in the property drawer, with its name in upper cases and colons added at the @@ -744,80 +927,46 @@ parsed as a secondary string, but as a plain string instead. Assume point is at beginning of the headline." (save-excursion - (let* ((components (org-heading-components)) - (level (nth 1 components)) - (todo (nth 2 components)) + (let* ((begin (point)) + (level (prog1 (org-reduced-level (skip-chars-forward "*")) + (skip-chars-forward " \t"))) + (todo (and org-todo-regexp + (let (case-fold-search) (looking-at org-todo-regexp)) + (progn (goto-char (match-end 0)) + (skip-chars-forward " \t") + (match-string 0)))) (todo-type (and todo (if (member todo org-done-keywords) 'done 'todo))) - (tags (let ((raw-tags (nth 5 components))) - (and raw-tags (org-split-string raw-tags ":")))) - (raw-value (or (nth 4 components) "")) - (quotedp - (let ((case-fold-search nil)) - (string-match (format "^%s\\( \\|$\\)" org-quote-string) - raw-value))) + (priority (and (looking-at "\\[#.\\][ \t]*") + (progn (goto-char (match-end 0)) + (aref (match-string 0) 2)))) (commentedp - (let ((case-fold-search nil)) - (string-match (format "^%s\\( \\|$\\)" org-comment-string) - raw-value))) + (and (let (case-fold-search) (looking-at org-comment-string)) + (goto-char (match-end 0)))) + (title-start (point)) + (tags (when (re-search-forward + "[ \t]+\\(:[[:alnum:]_@#%:]+:\\)[ \t]*$" + (line-end-position) + 'move) + (goto-char (match-beginning 0)) + (org-split-string (match-string 1) ":"))) + (title-end (point)) + (raw-value (org-trim + (buffer-substring-no-properties title-start title-end))) (archivedp (member org-archive-tag tags)) (footnote-section-p (and org-footnote-section (string= org-footnote-section raw-value))) - ;; Upcase property names. It avoids confusion between - ;; properties obtained through property drawer and default - ;; properties from the parser (e.g. `:end' and :END:) - (standard-props - (let (plist) - (mapc - (lambda (p) - (setq plist - (plist-put plist - (intern (concat ":" (upcase (car p)))) - (cdr p)))) - (org-entry-properties nil 'standard)) - plist)) - (time-props - ;; Read time properties on the line below the headline. - (save-excursion - (when (progn (forward-line) - (looking-at org-planning-or-clock-line-re)) - (let ((end (line-end-position)) plist) - (while (re-search-forward - org-keyword-time-not-clock-regexp end t) - (goto-char (match-end 1)) - (skip-chars-forward " \t") - (let ((keyword (match-string 1)) - (time (org-element-timestamp-parser))) - (cond ((equal keyword org-scheduled-string) - (setq plist (plist-put plist :scheduled time))) - ((equal keyword org-deadline-string) - (setq plist (plist-put plist :deadline time))) - (t (setq plist (plist-put plist :closed time)))))) - plist)))) - (begin (point)) + (standard-props (org-element--get-node-properties)) + (time-props (org-element--get-time-properties)) (end (min (save-excursion (org-end-of-subtree t t)) limit)) - (pos-after-head (progn (forward-line) (point))) (contents-begin (save-excursion + (forward-line) (skip-chars-forward " \r\t\n" end) (and (/= (point) end) (line-beginning-position)))) - (hidden (org-invisible-p2)) (contents-end (and contents-begin (progn (goto-char end) (skip-chars-backward " \r\t\n") - (forward-line) - (point))))) - ;; Clean RAW-VALUE from any quote or comment string. - (when (or quotedp commentedp) - (let ((case-fold-search nil)) - (setq raw-value - (replace-regexp-in-string - (concat - (regexp-opt (list org-quote-string org-comment-string)) - "\\(?: \\|$\\)") - "" - raw-value)))) - ;; Clean TAGS from archive tag, if any. - (when archivedp (setq tags (delete org-archive-tag tags))) + (line-beginning-position 2))))) (let ((headline (list 'headline (nconc @@ -826,36 +975,37 @@ Assume point is at beginning of the headline." :end end :pre-blank (if (not contents-begin) 0 - (count-lines pos-after-head contents-begin)) - :hiddenp hidden + (1- (count-lines begin contents-begin))) :contents-begin contents-begin :contents-end contents-end :level level - :priority (nth 3 components) + :priority priority :tags tags :todo-keyword todo :todo-type todo-type - :post-blank (count-lines - (or contents-end pos-after-head) - end) + :post-blank + (if contents-end + (count-lines contents-end end) + (1- (count-lines begin end))) :footnote-section-p footnote-section-p :archivedp archivedp :commentedp commentedp - :quotedp quotedp) + :post-affiliated begin) time-props standard-props)))) - (let ((alt-title (org-element-property :ALT_TITLE headline))) - (when alt-title - (org-element-put-property - headline :alt-title - (if raw-secondary-p alt-title - (org-element-parse-secondary-string - alt-title (org-element-restriction 'headline) headline))))) (org-element-put-property headline :title (if raw-secondary-p raw-value - (org-element-parse-secondary-string - raw-value (org-element-restriction 'headline) headline))))))) + (org-element--parse-objects + (progn (goto-char title-start) + (skip-chars-forward " \t") + (point)) + (progn (goto-char title-end) + (skip-chars-backward " \t") + (point)) + nil + (org-element-restriction 'headline) + headline))))))) (defun org-element-headline-interpreter (headline contents) "Interpret HEADLINE element as Org syntax. @@ -865,22 +1015,17 @@ CONTENTS is the contents of the element." (priority (org-element-property :priority headline)) (title (org-element-interpret-data (org-element-property :title headline))) - (tags (let ((tag-list (if (org-element-property :archivedp headline) - (cons org-archive-tag - (org-element-property :tags headline)) - (org-element-property :tags headline)))) + (tags (let ((tag-list (org-element-property :tags headline))) (and tag-list (format ":%s:" (mapconcat #'identity tag-list ":"))))) (commentedp (org-element-property :commentedp headline)) - (quotedp (org-element-property :quotedp headline)) (pre-blank (or (org-element-property :pre-blank headline) 0)) (heading (concat (make-string (if org-odd-levels-only (1- (* level 2)) level) ?*) (and todo (concat " " todo)) - (and quotedp (concat " " org-quote-string)) (and commentedp (concat " " org-comment-string)) - (and priority (format " [#%s]" (char-to-string priority))) + (and priority (format " [#%c]" priority)) " " (if (and org-footnote-section (org-element-property :footnote-section-p headline)) @@ -912,10 +1057,11 @@ CONTENTS is the contents of the element." "Parse an inline task. Return a list whose CAR is `inlinetask' and CDR is a plist -containing `:title', `:begin', `:end', `:hiddenp', +containing `:title', `:begin', `:end', `:pre-blank', `:contents-begin' and `:contents-end', `:level', `:priority', `:raw-value', `:tags', `:todo-keyword', `:todo-type', -`:scheduled', `:deadline', `:closed' and `:post-blank' keywords. +`:scheduled', `:deadline', `:closed', `:post-blank' and +`:post-affiliated' keywords. The plist also contains any property set in the property drawer, with its name in upper cases and colons added at the @@ -928,59 +1074,45 @@ string instead. Assume point is at beginning of the inline task." (save-excursion (let* ((begin (point)) - (components (org-heading-components)) - (todo (nth 2 components)) + (level (prog1 (org-reduced-level (skip-chars-forward "*")) + (skip-chars-forward " \t"))) + (todo (and org-todo-regexp + (let (case-fold-search) (looking-at org-todo-regexp)) + (progn (goto-char (match-end 0)) + (skip-chars-forward " \t") + (match-string 0)))) (todo-type (and todo (if (member todo org-done-keywords) 'done 'todo))) - (tags (let ((raw-tags (nth 5 components))) - (and raw-tags (org-split-string raw-tags ":")))) - (raw-value (or (nth 4 components) "")) - ;; Upcase property names. It avoids confusion between - ;; properties obtained through property drawer and default - ;; properties from the parser (e.g. `:end' and :END:) - (standard-props - (let (plist) - (mapc - (lambda (p) - (setq plist - (plist-put plist - (intern (concat ":" (upcase (car p)))) - (cdr p)))) - (org-entry-properties nil 'standard)) - plist)) - (time-props - ;; Read time properties on the line below the inlinetask - ;; opening string. - (save-excursion - (when (progn (forward-line) - (looking-at org-planning-or-clock-line-re)) - (let ((end (line-end-position)) plist) - (while (re-search-forward - org-keyword-time-not-clock-regexp end t) - (goto-char (match-end 1)) - (skip-chars-forward " \t") - (let ((keyword (match-string 1)) - (time (org-element-timestamp-parser))) - (cond ((equal keyword org-scheduled-string) - (setq plist (plist-put plist :scheduled time))) - ((equal keyword org-deadline-string) - (setq plist (plist-put plist :deadline time))) - (t (setq plist (plist-put plist :closed time)))))) - plist)))) + (priority (and (looking-at "\\[#.\\][ \t]*") + (progn (goto-char (match-end 0)) + (aref (match-string 0) 2)))) + (title-start (point)) + (tags (when (re-search-forward + "[ \t]+\\(:[[:alnum:]_@#%:]+:\\)[ \t]*$" + (line-end-position) + 'move) + (goto-char (match-beginning 0)) + (org-split-string (match-string 1) ":"))) + (title-end (point)) + (raw-value (org-trim + (buffer-substring-no-properties title-start title-end))) (task-end (save-excursion (end-of-line) (and (re-search-forward org-outline-regexp-bol limit t) - (org-looking-at-p "END[ \t]*$") + (looking-at-p "[ \t]*END[ \t]*$") (line-beginning-position)))) - (contents-begin (progn (forward-line) - (and task-end (< (point) task-end) (point)))) - (hidden (and contents-begin (org-invisible-p2))) + (standard-props (and task-end (org-element--get-node-properties))) + (time-props (and task-end (org-element--get-time-properties))) + (contents-begin (and task-end + (< (point) task-end) + (progn + (forward-line) + (skip-chars-forward " \t\n") + (line-beginning-position)))) (contents-end (and contents-begin task-end)) - (before-blank (if (not task-end) (point) - (goto-char task-end) - (forward-line) - (point))) - (end (progn (skip-chars-forward " \r\t\n" limit) + (end (progn (when task-end (goto-char task-end)) + (forward-line) + (skip-chars-forward " \r\t\n" limit) (if (eobp) (point) (line-beginning-position)))) (inlinetask (list 'inlinetask @@ -988,22 +1120,31 @@ Assume point is at beginning of the inline task." (list :raw-value raw-value :begin begin :end end - :hiddenp hidden + :pre-blank + (if (not contents-begin) 0 + (1- (count-lines begin contents-begin))) :contents-begin contents-begin :contents-end contents-end - :level (nth 1 components) - :priority (nth 3 components) + :level level + :priority priority :tags tags :todo-keyword todo :todo-type todo-type - :post-blank (count-lines before-blank end)) + :post-blank (1- (count-lines (or task-end begin) end)) + :post-affiliated begin) time-props standard-props)))) (org-element-put-property inlinetask :title (if raw-secondary-p raw-value - (org-element-parse-secondary-string - raw-value + (org-element--parse-objects + (progn (goto-char title-start) + (skip-chars-forward " \t") + (point)) + (progn (goto-char title-end) + (skip-chars-backward " \t") + (point)) + nil (org-element-restriction 'inlinetask) inlinetask)))))) @@ -1020,8 +1161,7 @@ CONTENTS is the contents of inlinetask." (format ":%s:" (mapconcat 'identity tag-list ":"))))) (task (concat (make-string level ?*) (and todo (concat " " todo)) - (and priority - (format " [#%s]" (char-to-string priority))) + (and priority (format " [#%c]" priority)) (and title (concat " " title))))) (concat task ;; Align tags. @@ -1048,15 +1188,15 @@ CONTENTS is the contents of inlinetask." ;;;; Item -(defun org-element-item-parser (limit struct &optional raw-secondary-p) +(defun org-element-item-parser (_ struct &optional raw-secondary-p) "Parse an item. STRUCT is the structure of the plain list. Return a list whose CAR is `item' and CDR is a plist containing `:bullet', `:begin', `:end', `:contents-begin', `:contents-end', -`:checkbox', `:counter', `:tag', `:structure', `:hiddenp' and -`:post-blank' keywords. +`:checkbox', `:counter', `:tag', `:structure', `:post-blank' and +`:post-affiliated' keywords. When optional argument RAW-SECONDARY-P is non-nil, item's tag, if any, will not be parsed as a secondary string, but as a plain @@ -1067,12 +1207,12 @@ Assume point is at the beginning of the item." (beginning-of-line) (looking-at org-list-full-item-re) (let* ((begin (point)) - (bullet (org-match-string-no-properties 1)) - (checkbox (let ((box (org-match-string-no-properties 3))) + (bullet (match-string-no-properties 1)) + (checkbox (let ((box (match-string 3))) (cond ((equal "[ ]" box) 'off) ((equal "[X]" box) 'on) ((equal "[-]" box) 'trans)))) - (counter (let ((c (org-match-string-no-properties 2))) + (counter (let ((c (match-string 2))) (save-match-data (cond ((not c) nil) @@ -1081,9 +1221,8 @@ Assume point is at the beginning of the item." 64)) ((string-match "[0-9]+" c) (string-to-number (match-string 0 c))))))) - (end (save-excursion (goto-char (org-list-get-item-end begin struct)) - (unless (bolp) (forward-line)) - (point))) + (end (progn (goto-char (nth 6 (assq (point) struct))) + (if (bolp) (point) (line-beginning-position 2)))) (contents-begin (progn (goto-char ;; Ignore tags in un-ordered lists: they are just @@ -1092,40 +1231,37 @@ Assume point is at the beginning of the item." (save-match-data (string-match "[.)]" bullet))) (match-beginning 4) (match-end 0))) - (skip-chars-forward " \r\t\n" limit) - ;; If first line isn't empty, contents really start - ;; at the text after item's meta-data. - (if (= (point-at-bol) begin) (point) (point-at-bol)))) - (hidden (progn (forward-line) - (and (not (= (point) end)) (org-invisible-p2)))) - (contents-end (progn (goto-char end) - (skip-chars-backward " \r\t\n") - (forward-line) - (point))) + (skip-chars-forward " \r\t\n" end) + (cond ((= (point) end) nil) + ;; If first line isn't empty, contents really + ;; start at the text after item's meta-data. + ((= (line-beginning-position) begin) (point)) + (t (line-beginning-position))))) + (contents-end (and contents-begin + (progn (goto-char end) + (skip-chars-backward " \r\t\n") + (line-beginning-position 2)))) (item (list 'item (list :bullet bullet :begin begin :end end - ;; CONTENTS-BEGIN and CONTENTS-END may be - ;; mixed up in the case of an empty item - ;; separated from the next by a blank line. - ;; Thus ensure the former is always the - ;; smallest. - :contents-begin (min contents-begin contents-end) - :contents-end (max contents-begin contents-end) + :contents-begin contents-begin + :contents-end contents-end :checkbox checkbox :counter counter - :hiddenp hidden :structure struct - :post-blank (count-lines contents-end end))))) + :post-blank (count-lines (or contents-end begin) end) + :post-affiliated begin)))) (org-element-put-property item :tag - (let ((raw-tag (org-list-get-tag begin struct))) - (and raw-tag - (if raw-secondary-p raw-tag - (org-element-parse-secondary-string - raw-tag (org-element-restriction 'item) item)))))))) + (let ((raw (org-list-get-tag begin struct))) + (when raw + (if raw-secondary-p raw + (org-element--parse-objects + (match-beginning 4) (match-end 4) nil + (org-element-restriction 'item) + item)))))))) (defun org-element-item-interpreter (item contents) "Interpret ITEM element as Org syntax. @@ -1148,10 +1284,11 @@ CONTENTS is the contents of the element." (concat bullet (and counter (format "[@%d] " counter)) - (case checkbox - (on "[X] ") - (off "[ ] ") - (trans "[-] ")) + (pcase checkbox + (`on "[X] ") + (`off "[ ] ") + (`trans "[-] ") + (_ nil)) (and tag (format "%s :: " tag)) (when contents (let ((contents (replace-regexp-in-string @@ -1168,29 +1305,22 @@ CONTENTS is the contents of the element." (let ((case-fold-search t) (top-ind limit) (item-re (org-item-re)) - (drawers-re (concat ":\\(" - (mapconcat 'regexp-quote org-drawers "\\|") - "\\):[ \t]*$")) (inlinetask-re (and (featurep 'org-inlinetask) "^\\*+ ")) items struct) (save-excursion - (catch 'exit + (catch :exit (while t (cond ;; At limit: end all items. ((>= (point) limit) - (throw 'exit - (let ((end (progn (skip-chars-backward " \r\t\n") - (forward-line) - (point)))) - (dolist (item items (sort (nconc items struct) - 'car-less-than-car)) - (setcar (nthcdr 6 item) end))))) + (let ((end (progn (skip-chars-backward " \r\t\n") + (line-beginning-position 2)))) + (dolist (item items) (setcar (nthcdr 6 item) end))) + (throw :exit (sort (nconc items struct) #'car-less-than-car))) ;; At list end: end all items. ((looking-at org-list-end-re) - (throw 'exit (dolist (item items (sort (nconc items struct) - 'car-less-than-car)) - (setcar (nthcdr 6 item) (point))))) + (dolist (item items) (setcar (nthcdr 6 item) (point))) + (throw :exit (sort (nconc items struct) #'car-less-than-car))) ;; At a new item: end previous sibling. ((looking-at item-re) (let ((ind (save-excursion (skip-chars-forward " \t") @@ -1214,7 +1344,7 @@ CONTENTS is the contents of the element." ;; Ending position, unknown so far. nil))) items)) - (forward-line 1)) + (forward-line)) ;; Skip empty lines. ((looking-at "^[ \t]*$") (forward-line)) ;; Skip inline tasks and blank lines along the way. @@ -1222,28 +1352,29 @@ CONTENTS is the contents of the element." (forward-line) (let ((origin (point))) (when (re-search-forward inlinetask-re limit t) - (if (org-looking-at-p "END[ \t]*$") (forward-line) + (if (looking-at-p "END[ \t]*$") (forward-line) (goto-char origin))))) ;; At some text line. Check if it ends any previous item. (t - (let ((ind (progn (skip-chars-forward " \t") (current-column)))) - (when (<= ind top-ind) - (skip-chars-backward " \r\t\n") - (forward-line)) + (let ((ind (save-excursion + (skip-chars-forward " \t") + (current-column))) + (end (save-excursion + (skip-chars-backward " \r\t\n") + (line-beginning-position 2)))) (while (<= ind (nth 1 (car items))) (let ((item (pop items))) - (setcar (nthcdr 6 item) (line-beginning-position)) + (setcar (nthcdr 6 item) end) (push item struct) (unless items - (throw 'exit (sort struct 'car-less-than-car)))))) + (throw :exit (sort struct #'car-less-than-car)))))) ;; Skip blocks (any type) and drawers contents. (cond - ((and (looking-at "#\\+BEGIN\\(:\\|_\\S-+\\)") + ((and (looking-at "[ \t]*#\\+BEGIN\\(:\\|_\\S-+\\)") (re-search-forward - (format "^[ \t]*#\\+END%s[ \t]*$" - (org-match-string-no-properties 1)) + (format "^[ \t]*#\\+END%s[ \t]*$" (match-string 1)) limit t))) - ((and (looking-at drawers-re) + ((and (looking-at org-drawer-regexp) (re-search-forward "^[ \t]*:END:[ \t]*$" limit t)))) (forward-line)))))))) @@ -1264,15 +1395,20 @@ containing `:type', `:begin', `:end', `:contents-begin' and Assume point is at the beginning of the list." (save-excursion (let* ((struct (or structure (org-element--list-struct limit))) - (prevs (org-list-prevs-alist struct)) - (type (org-list-get-list-type (point) struct prevs)) + (type (cond ((looking-at-p "[ \t]*[A-Za-z0-9]") 'ordered) + ((nth 5 (assq (point) struct)) 'descriptive) + (t 'unordered))) (contents-begin (point)) (begin (car affiliated)) - (contents-end - (progn (goto-char (org-list-get-list-end (point) struct prevs)) - (unless (bolp) (forward-line)) - (point))) - (end (progn (skip-chars-forward " \r\t\n" limit) + (contents-end (let* ((item (assq contents-begin struct)) + (ind (nth 1 item)) + (pos (nth 6 item))) + (while (and (setq item (assq pos struct)) + (= (nth 1 item) ind)) + (setq pos (nth 6 item))) + pos)) + (end (progn (goto-char contents-end) + (skip-chars-forward " \r\t\n" limit) (if (= (point) limit) limit (line-beginning-position))))) ;; Return value. (list 'plain-list @@ -1287,8 +1423,8 @@ Assume point is at the beginning of the list." :post-affiliated contents-begin) (cdr affiliated)))))) -(defun org-element-plain-list-interpreter (plain-list contents) - "Interpret PLAIN-LIST element as Org syntax. +(defun org-element-plain-list-interpreter (_ contents) + "Interpret plain-list element as Org syntax. CONTENTS is the contents of the element." (with-temp-buffer (insert contents) @@ -1299,52 +1435,36 @@ CONTENTS is the contents of the element." ;;;; Property Drawer -(defun org-element-property-drawer-parser (limit affiliated) +(defun org-element-property-drawer-parser (limit) "Parse a property drawer. -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 `property-drawer' and CDR is a plist -containing `:begin', `:end', `:hiddenp', `:contents-begin', -`:contents-end', `:post-blank' and `:post-affiliated' keywords. +Return a list whose car is `property-drawer' and cdr is a plist +containing `:begin', `:end', `:contents-begin', `:contents-end', +`:post-blank' and `:post-affiliated' keywords. Assume point is at the beginning of the property drawer." - (let ((case-fold-search t)) - (if (not (save-excursion (re-search-forward "^[ \t]*:END:[ \t]*$" limit t))) - ;; Incomplete drawer: parse it as a paragraph. - (org-element-paragraph-parser limit affiliated) - (save-excursion - (let* ((drawer-end-line (match-beginning 0)) - (begin (car affiliated)) - (post-affiliated (point)) - (contents-begin - (progn - (forward-line) - (and (re-search-forward org-property-re drawer-end-line t) - (line-beginning-position)))) - (contents-end (and contents-begin drawer-end-line)) - (hidden (org-invisible-p2)) - (pos-before-blank (progn (goto-char drawer-end-line) - (forward-line) - (point))) - (end (progn (skip-chars-forward " \r\t\n" limit) - (if (eobp) (point) (line-beginning-position))))) - (list 'property-drawer - (nconc - (list :begin begin - :end end - :hiddenp hidden - :contents-begin contents-begin - :contents-end contents-end - :post-blank (count-lines pos-before-blank end) - :post-affiliated post-affiliated) - (cdr affiliated)))))))) - -(defun org-element-property-drawer-interpreter (property-drawer contents) - "Interpret PROPERTY-DRAWER element as Org syntax. + (save-excursion + (let ((case-fold-search t) + (begin (point)) + (contents-begin (line-beginning-position 2))) + (re-search-forward "^[ \t]*:END:[ \t]*$" limit t) + (let ((contents-end (and (> (match-beginning 0) contents-begin) + (match-beginning 0))) + (before-blank (progn (forward-line) (point))) + (end (progn (skip-chars-forward " \r\t\n" limit) + (if (eobp) (point) (line-beginning-position))))) + (list 'property-drawer + (list :begin begin + :end end + :contents-begin (and contents-end contents-begin) + :contents-end contents-end + :post-blank (count-lines before-blank end) + :post-affiliated begin)))))) + +(defun org-element-property-drawer-interpreter (_ contents) + "Interpret property-drawer element as Org syntax. CONTENTS is the properties within the drawer." (format ":PROPERTIES:\n%s:END:" contents)) @@ -1360,8 +1480,8 @@ keyword and CDR is a plist of affiliated keywords along with their value. Return a list whose CAR is `quote-block' and CDR is a plist -containing `:begin', `:end', `:hiddenp', `:contents-begin', -`:contents-end', `:post-blank' and `:post-affiliated' keywords. +containing `:begin', `:end', `:contents-begin', `:contents-end', +`:post-blank' and `:post-affiliated' keywords. Assume point is at the beginning of the block." (let ((case-fold-search t)) @@ -1378,7 +1498,6 @@ Assume point is at the beginning of the block." (and (< (point) block-end-line) (point)))) (contents-end (and contents-begin block-end-line)) - (hidden (org-invisible-p2)) (pos-before-blank (progn (goto-char block-end-line) (forward-line) (point))) @@ -1388,29 +1507,26 @@ Assume point is at the beginning of the block." (nconc (list :begin begin :end end - :hiddenp hidden :contents-begin contents-begin :contents-end contents-end :post-blank (count-lines pos-before-blank end) :post-affiliated post-affiliated) (cdr affiliated))))))))) -(defun org-element-quote-block-interpreter (quote-block contents) - "Interpret QUOTE-BLOCK element as Org syntax. +(defun org-element-quote-block-interpreter (_ contents) + "Interpret quote-block element as Org syntax. CONTENTS is the contents of the element." (format "#+BEGIN_QUOTE\n%s#+END_QUOTE" contents)) ;;;; Section -(defun org-element-section-parser (limit) +(defun org-element-section-parser (_) "Parse a section. -LIMIT bounds the search. - Return a list whose CAR is `section' and CDR is a plist -containing `:begin', `:end', `:contents-begin', `contents-end' -and `:post-blank' keywords." +containing `:begin', `:end', `:contents-begin', `contents-end', +`:post-blank' and `:post-affiliated' keywords." (save-excursion ;; Beginning of section is the beginning of the first non-blank ;; line after previous headline. @@ -1418,17 +1534,17 @@ and `:post-blank' keywords." (end (progn (org-with-limited-levels (outline-next-heading)) (point))) (pos-before-blank (progn (skip-chars-backward " \r\t\n") - (forward-line) - (point)))) + (line-beginning-position 2)))) (list 'section (list :begin begin :end end :contents-begin begin :contents-end pos-before-blank - :post-blank (count-lines pos-before-blank end)))))) + :post-blank (count-lines pos-before-blank end) + :post-affiliated begin))))) -(defun org-element-section-interpreter (section contents) - "Interpret SECTION element as Org syntax. +(defun org-element-section-interpreter (_ contents) + "Interpret section element as Org syntax. CONTENTS is the contents of the element." contents) @@ -1444,14 +1560,13 @@ keyword and CDR is a plist of affiliated keywords along with their value. Return a list whose CAR is `special-block' and CDR is a plist -containing `:type', `:begin', `:end', `:hiddenp', -`:contents-begin', `:contents-end', `:post-blank' and -`:post-affiliated' keywords. +containing `:type', `:begin', `:end', `:contents-begin', +`:contents-end', `:post-blank' and `:post-affiliated' keywords. Assume point is at the beginning of the block." (let* ((case-fold-search t) (type (progn (looking-at "[ \t]*#\\+BEGIN_\\(\\S-+\\)") - (upcase (match-string-no-properties 1))))) + (match-string-no-properties 1)))) (if (not (save-excursion (re-search-forward (format "^[ \t]*#\\+END_%s[ \t]*$" (regexp-quote type)) @@ -1467,7 +1582,6 @@ Assume point is at the beginning of the block." (and (< (point) block-end-line) (point)))) (contents-end (and contents-begin block-end-line)) - (hidden (org-invisible-p2)) (pos-before-blank (progn (goto-char block-end-line) (forward-line) (point))) @@ -1478,7 +1592,6 @@ Assume point is at the beginning of the block." (list :type type :begin begin :end end - :hiddenp hidden :contents-begin contents-begin :contents-end contents-end :post-blank (count-lines pos-before-blank end) @@ -1502,9 +1615,6 @@ CONTENTS is the contents of the element." ;; through the following steps: implement a parser and an interpreter, ;; tweak `org-element--current-element' so that it recognizes the new ;; type and add that new type to `org-element-all-elements'. -;; -;; As a special case, when the newly defined type is a block type, -;; `org-element-block-name-alist' has to be modified accordingly. ;;;; Babel Call @@ -1512,43 +1622,61 @@ CONTENTS is the contents of the element." (defun org-element-babel-call-parser (limit affiliated) "Parse a babel call. -LIMIT bounds the search. AFFILIATED is a list of which CAR is +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 +keyword and cdr is a plist of affiliated keywords along with their value. -Return a list whose CAR is `babel-call' and CDR is a plist -containing `:begin', `:end', `:info', `:post-blank' and +Return a list whose car is `babel-call' and cdr is a plist +containing `:call', `:inside-header', `:arguments', +`:end-header', `:begin', `:end', `:value', `:post-blank' and `:post-affiliated' as keywords." (save-excursion - (let ((case-fold-search t) - (info (progn (looking-at org-babel-block-lob-one-liner-regexp) - (org-babel-lob-get-info))) - (begin (car affiliated)) - (post-affiliated (point)) - (pos-before-blank (progn (forward-line) (point))) - (end (progn (skip-chars-forward " \r\t\n" limit) - (if (eobp) (point) (line-beginning-position))))) + (let* ((begin (car affiliated)) + (post-affiliated (point)) + (before-blank (line-beginning-position 2)) + (value (progn (search-forward ":" before-blank t) + (skip-chars-forward " \t") + (org-trim + (buffer-substring-no-properties + (point) (line-end-position))))) + (call + (or (org-string-nw-p + (buffer-substring-no-properties + (point) (progn (skip-chars-forward "^[]()" before-blank) + (point)))))) + (inside-header (org-element--parse-paired-brackets ?\[)) + (arguments (org-string-nw-p + (org-element--parse-paired-brackets ?\())) + (end-header + (org-string-nw-p + (org-trim + (buffer-substring-no-properties (point) (line-end-position))))) + (end (progn (forward-line) + (skip-chars-forward " \r\t\n" limit) + (if (eobp) (point) (line-beginning-position))))) (list 'babel-call (nconc - (list :begin begin + (list :call call + :inside-header inside-header + :arguments arguments + :end-header end-header + :begin begin :end end - :info info - :post-blank (count-lines pos-before-blank end) + :value value + :post-blank (count-lines before-blank end) :post-affiliated post-affiliated) (cdr affiliated)))))) -(defun org-element-babel-call-interpreter (babel-call contents) - "Interpret BABEL-CALL element as Org syntax. -CONTENTS is nil." - (let* ((babel-info (org-element-property :info babel-call)) - (main (car babel-info)) - (post-options (nth 1 babel-info))) - (concat "#+CALL: " - (if (not (string-match "\\[\\(\\[.*?\\]\\)\\]" main)) main - ;; Remove redundant square brackets. - (replace-match (match-string 1 main) nil nil main)) - (and post-options (format "[%s]" post-options))))) +(defun org-element-babel-call-interpreter (babel-call _) + "Interpret BABEL-CALL element as Org syntax." + (concat "#+CALL: " + (org-element-property :call babel-call) + (let ((h (org-element-property :inside-header babel-call))) + (and h (format "[%s]" h))) + (concat "(" (org-element-property :arguments babel-call) ")") + (let ((h (org-element-property :end-header babel-call))) + (and h (concat " " h))))) ;;;; Clock @@ -1559,8 +1687,8 @@ CONTENTS is nil." LIMIT bounds the search. Return a list whose CAR is `clock' and CDR is a plist containing -`:status', `:value', `:time', `:begin', `:end' and `:post-blank' -as keywords." +`:status', `:value', `:time', `:begin', `:end', `:post-blank' and +`:post-affiliated' as keywords." (save-excursion (let* ((case-fold-search nil) (begin (point)) @@ -1570,7 +1698,7 @@ as keywords." (duration (and (search-forward " => " (line-end-position) t) (progn (skip-chars-forward " \t") (looking-at "\\(\\S-+\\)[ \t]*$")) - (org-match-string-no-properties 1))) + (match-string-no-properties 1))) (status (if duration 'closed 'running)) (post-blank (let ((before-blank (progn (forward-line) (point)))) (skip-chars-forward " \r\t\n" limit) @@ -1584,11 +1712,11 @@ as keywords." :duration duration :begin begin :end end - :post-blank post-blank))))) + :post-blank post-blank + :post-affiliated begin))))) -(defun org-element-clock-interpreter (clock contents) - "Interpret CLOCK element as Org syntax. -CONTENTS is nil." +(defun org-element-clock-interpreter (clock _) + "Interpret CLOCK element as Org syntax." (concat org-clock-string " " (org-element-timestamp-interpreter (org-element-property :value clock) nil) @@ -1647,7 +1775,7 @@ Assume point is at comment beginning." :post-affiliated post-affiliated) (cdr affiliated)))))) -(defun org-element-comment-interpreter (comment contents) +(defun org-element-comment-interpreter (comment _) "Interpret COMMENT element as Org syntax. CONTENTS is nil." (replace-regexp-in-string "^" "# " (org-element-property :value comment))) @@ -1664,8 +1792,8 @@ keyword and CDR is a plist of affiliated keywords along with their value. Return a list whose CAR is `comment-block' and CDR is a plist -containing `:begin', `:end', `:hiddenp', `:value', `:post-blank' -and `:post-affiliated' keywords. +containing `:begin', `:end', `:value', `:post-blank' and +`:post-affiliated' keywords. Assume point is at comment block beginning." (let ((case-fold-search t)) @@ -1678,7 +1806,6 @@ Assume point is at comment block beginning." (let* ((begin (car affiliated)) (post-affiliated (point)) (contents-begin (progn (forward-line) (point))) - (hidden (org-invisible-p2)) (pos-before-blank (progn (goto-char contents-end) (forward-line) (point))) @@ -1691,16 +1818,16 @@ Assume point is at comment block beginning." (list :begin begin :end end :value value - :hiddenp hidden :post-blank (count-lines pos-before-blank end) :post-affiliated post-affiliated) (cdr affiliated))))))))) -(defun org-element-comment-block-interpreter (comment-block contents) - "Interpret COMMENT-BLOCK element as Org syntax. -CONTENTS is nil." +(defun org-element-comment-block-interpreter (comment-block _) + "Interpret COMMENT-BLOCK element as Org syntax." (format "#+BEGIN_COMMENT\n%s#+END_COMMENT" - (org-remove-indentation (org-element-property :value comment-block)))) + (org-element-normalize-string + (org-remove-indentation + (org-element-property :value comment-block))))) ;;;; Diary Sexp @@ -1720,7 +1847,7 @@ containing `:begin', `:end', `:value', `:post-blank' and (let ((begin (car affiliated)) (post-affiliated (point)) (value (progn (looking-at "\\(%%(.*\\)[ \t]*$") - (org-match-string-no-properties 1))) + (match-string-no-properties 1))) (pos-before-blank (progn (forward-line) (point))) (end (progn (skip-chars-forward " \r\t\n" limit) (if (eobp) (point) (line-beginning-position))))) @@ -1733,43 +1860,13 @@ containing `:begin', `:end', `:value', `:post-blank' and :post-affiliated post-affiliated) (cdr affiliated)))))) -(defun org-element-diary-sexp-interpreter (diary-sexp contents) - "Interpret DIARY-SEXP as Org syntax. -CONTENTS is nil." +(defun org-element-diary-sexp-interpreter (diary-sexp _) + "Interpret DIARY-SEXP as Org syntax." (org-element-property :value diary-sexp)) ;;;; Example Block -(defun org-element--remove-indentation (s &optional n) - "Remove maximum common indentation in string S and return it. -When optional argument N is a positive integer, remove exactly -that much characters from indentation, if possible, or return -S as-is otherwise. Unlike to `org-remove-indentation', this -function doesn't call `untabify' on S." - (catch 'exit - (with-temp-buffer - (insert s) - (goto-char (point-min)) - ;; Find maximum common indentation, if not specified. - (setq n (or n - (let ((min-ind (point-max))) - (save-excursion - (while (re-search-forward "^[ \t]*\\S-" nil t) - (let ((ind (1- (current-column)))) - (if (zerop ind) (throw 'exit s) - (setq min-ind (min min-ind ind)))))) - min-ind))) - (if (zerop n) s - ;; Remove exactly N indentation, but give up if not possible. - (while (not (eobp)) - (let ((ind (progn (skip-chars-forward " \t") (current-column)))) - (cond ((eolp) (delete-region (line-beginning-position) (point))) - ((< ind n) (throw 'exit s)) - (t (org-indent-line-to (- ind n)))) - (forward-line))) - (buffer-string))))) - (defun org-element-example-block-parser (limit affiliated) "Parse an example block. @@ -1780,9 +1877,8 @@ their value. Return a list whose CAR is `example-block' and CDR is a plist containing `:begin', `:end', `:number-lines', `:preserve-indent', -`:retain-labels', `:use-labels', `:label-fmt', `:hiddenp', -`:switches', `:value', `:post-blank' and `:post-affiliated' -keywords." +`:retain-labels', `:use-labels', `:label-fmt', `:switches', +`:value', `:post-blank' and `:post-affiliated' keywords." (let ((case-fold-search t)) (if (not (save-excursion (re-search-forward "^[ \t]*#\\+END_EXAMPLE[ \t]*$" limit t))) @@ -1793,15 +1889,22 @@ keywords." (let* ((switches (progn (looking-at "^[ \t]*#\\+BEGIN_EXAMPLE\\(?: +\\(.*\\)\\)?") - (org-match-string-no-properties 1))) - ;; Switches analysis + (match-string-no-properties 1))) + ;; Switches analysis. (number-lines - (cond ((not switches) nil) - ((string-match "-n\\>" switches) 'new) - ((string-match "+n\\>" switches) 'continued))) + (and switches + (string-match "\\([-+]\\)n\\(?: *\\([0-9]+\\)\\)?\\>" + switches) + (cons + (if (equal (match-string 1 switches) "-") + 'new + 'continued) + (if (not (match-end 2)) 0 + ;; Subtract 1 to give number of lines before + ;; first line. + (1- (string-to-number (match-string 2 switches))))))) (preserve-indent - (or org-src-preserve-indentation - (and switches (string-match "-i\\>" switches)))) + (and switches (string-match "-i\\>" switches))) ;; Should labels be retained in (or stripped from) example ;; blocks? (retain-labels @@ -1821,14 +1924,10 @@ keywords." ;; Standard block parsing. (begin (car affiliated)) (post-affiliated (point)) - (block-ind (progn (skip-chars-forward " \t") (current-column))) - (contents-begin (progn (forward-line) (point))) - (hidden (org-invisible-p2)) - (value (org-element--remove-indentation - (org-unescape-code-in-string - (buffer-substring-no-properties - contents-begin contents-end)) - (and preserve-indent block-ind))) + (contents-begin (line-beginning-position 2)) + (value (org-unescape-code-in-string + (buffer-substring-no-properties + contents-begin contents-end))) (pos-before-blank (progn (goto-char contents-end) (forward-line) (point))) @@ -1845,18 +1944,21 @@ keywords." :retain-labels retain-labels :use-labels use-labels :label-fmt label-fmt - :hiddenp hidden :post-blank (count-lines pos-before-blank end) :post-affiliated post-affiliated) (cdr affiliated))))))))) -(defun org-element-example-block-interpreter (example-block contents) - "Interpret EXAMPLE-BLOCK element as Org syntax. -CONTENTS is nil." - (let ((switches (org-element-property :switches example-block))) +(defun org-element-example-block-interpreter (example-block _) + "Interpret EXAMPLE-BLOCK element as Org syntax." + (let ((switches (org-element-property :switches example-block)) + (value (org-element-property :value example-block))) (concat "#+BEGIN_EXAMPLE" (and switches (concat " " switches)) "\n" - (org-escape-code-in-string - (org-element-property :value example-block)) + (org-element-normalize-string + (org-escape-code-in-string + (if (or org-src-preserve-indentation + (org-element-property :preserve-indent example-block)) + value + (org-remove-indentation value)))) "#+END_EXAMPLE"))) @@ -1871,49 +1973,48 @@ keyword and CDR is a plist of affiliated keywords along with their value. Return a list whose CAR is `export-block' and CDR is a plist -containing `:begin', `:end', `:type', `:hiddenp', `:value', -`:post-blank' and `:post-affiliated' keywords. +containing `:begin', `:end', `:type', `:value', `:post-blank' and +`:post-affiliated' keywords. Assume point is at export-block beginning." - (let* ((case-fold-search t) - (type (progn (looking-at "[ \t]*#\\+BEGIN_\\(\\S-+\\)") - (upcase (org-match-string-no-properties 1))))) + (let* ((case-fold-search t)) (if (not (save-excursion - (re-search-forward - (format "^[ \t]*#\\+END_%s[ \t]*$" type) limit t))) + (re-search-forward "^[ \t]*#\\+END_EXPORT[ \t]*$" limit t))) ;; Incomplete block: parse it as a paragraph. (org-element-paragraph-parser limit affiliated) - (let ((contents-end (match-beginning 0))) - (save-excursion - (let* ((begin (car affiliated)) - (post-affiliated (point)) - (contents-begin (progn (forward-line) (point))) - (hidden (org-invisible-p2)) - (pos-before-blank (progn (goto-char contents-end) - (forward-line) - (point))) - (end (progn (skip-chars-forward " \r\t\n" limit) - (if (eobp) (point) (line-beginning-position)))) - (value (buffer-substring-no-properties contents-begin - contents-end))) - (list 'export-block - (nconc - (list :begin begin - :end end - :type type - :value value - :hiddenp hidden - :post-blank (count-lines pos-before-blank end) - :post-affiliated post-affiliated) - (cdr affiliated))))))))) + (save-excursion + (let* ((contents-end (match-beginning 0)) + (backend + (progn + (looking-at + "[ \t]*#\\+BEGIN_EXPORT\\(?:[ \t]+\\(\\S-+\\)\\)?[ \t]*$") + (match-string-no-properties 1))) + (begin (car affiliated)) + (post-affiliated (point)) + (contents-begin (progn (forward-line) (point))) + (pos-before-blank (progn (goto-char contents-end) + (forward-line) + (point))) + (end (progn (skip-chars-forward " \r\t\n" limit) + (if (eobp) (point) (line-beginning-position)))) + (value (org-unescape-code-in-string + (buffer-substring-no-properties contents-begin + contents-end)))) + (list 'export-block + (nconc + (list :type (and backend (upcase backend)) + :begin begin + :end end + :value value + :post-blank (count-lines pos-before-blank end) + :post-affiliated post-affiliated) + (cdr affiliated)))))))) -(defun org-element-export-block-interpreter (export-block contents) - "Interpret EXPORT-BLOCK element as Org syntax. -CONTENTS is nil." - (let ((type (org-element-property :type export-block))) - (concat (format "#+BEGIN_%s\n" type) - (org-element-property :value export-block) - (format "#+END_%s" type)))) +(defun org-element-export-block-interpreter (export-block _) + "Interpret EXPORT-BLOCK element as Org syntax." + (format "#+BEGIN_EXPORT %s\n%s#+END_EXPORT" + (org-element-property :type export-block) + (org-element-property :value export-block))) ;;;; Fixed-width @@ -1958,9 +2059,8 @@ Assume point is at the beginning of the fixed-width area." :post-affiliated post-affiliated) (cdr affiliated)))))) -(defun org-element-fixed-width-interpreter (fixed-width contents) - "Interpret FIXED-WIDTH element as Org syntax. -CONTENTS is nil." +(defun org-element-fixed-width-interpreter (fixed-width _) + "Interpret FIXED-WIDTH element as Org syntax." (let ((value (org-element-property :value fixed-width))) (and value (replace-regexp-in-string @@ -1995,9 +2095,8 @@ keywords." :post-affiliated post-affiliated) (cdr affiliated)))))) -(defun org-element-horizontal-rule-interpreter (horizontal-rule contents) - "Interpret HORIZONTAL-RULE element as Org syntax. -CONTENTS is nil." +(defun org-element-horizontal-rule-interpreter (&rest _) + "Interpret HORIZONTAL-RULE element as Org syntax." "-----") @@ -2015,10 +2114,13 @@ Return a list whose CAR is `keyword' and CDR is a plist containing `:key', `:value', `:begin', `:end', `:post-blank' and `:post-affiliated' keywords." (save-excursion - (let ((begin (car affiliated)) + ;; An orphaned affiliated keyword is considered as a regular + ;; keyword. In this case AFFILIATED is nil, so we take care of + ;; this corner case. + (let ((begin (or (car affiliated) (point))) (post-affiliated (point)) (key (progn (looking-at "[ \t]*#\\+\\(\\S-+*\\):") - (upcase (org-match-string-no-properties 1)))) + (upcase (match-string-no-properties 1)))) (value (org-trim (buffer-substring-no-properties (match-end 0) (point-at-eol)))) (pos-before-blank (progn (forward-line) (point))) @@ -2034,9 +2136,8 @@ containing `:key', `:value', `:begin', `:end', `:post-blank' and :post-affiliated post-affiliated) (cdr affiliated)))))) -(defun org-element-keyword-interpreter (keyword contents) - "Interpret KEYWORD element as Org syntax. -CONTENTS is nil." +(defun org-element-keyword-interpreter (keyword _) + "Interpret KEYWORD element as Org syntax." (format "#+%s: %s" (org-element-property :key keyword) (org-element-property :value keyword))) @@ -2044,6 +2145,18 @@ CONTENTS is nil." ;;;; Latex Environment +(defconst org-element--latex-begin-environment + "^[ \t]*\\\\begin{\\([A-Za-z0-9*]+\\)}" + "Regexp matching the beginning of a LaTeX environment. +The environment is captured by the first group. + +See also `org-element--latex-end-environment'.") + +(defconst org-element--latex-end-environment + "\\\\end{%s}[ \t]*$" + "Format string matching the ending of a LaTeX environment. +See also `org-element--latex-begin-environment'.") + (defun org-element-latex-environment-parser (limit affiliated) "Parse a LaTeX environment. @@ -2060,8 +2173,8 @@ Assume point is at the beginning of the latex environment." (save-excursion (let ((case-fold-search t) (code-begin (point))) - (looking-at "[ \t]*\\\\begin{\\([A-Za-z0-9]+\\*?\\)}") - (if (not (re-search-forward (format "^[ \t]*\\\\end{%s}[ \t]*$" + (looking-at org-element--latex-begin-environment) + (if (not (re-search-forward (format org-element--latex-end-environment (regexp-quote (match-string 1))) limit t)) ;; Incomplete latex environment: parse it as a paragraph. @@ -2080,9 +2193,8 @@ Assume point is at the beginning of the latex environment." :post-affiliated code-begin) (cdr affiliated)))))))) -(defun org-element-latex-environment-interpreter (latex-environment contents) - "Interpret LATEX-ENVIRONMENT element as Org syntax. -CONTENTS is nil." +(defun org-element-latex-environment-interpreter (latex-environment _) + "Interpret LATEX-ENVIRONMENT element as Org syntax." (org-element-property :value latex-environment)) @@ -2094,12 +2206,13 @@ CONTENTS is nil." LIMIT bounds the search. Return a list whose CAR is `node-property' and CDR is a plist -containing `:key', `:value', `:begin', `:end' and `:post-blank' -keywords." +containing `:key', `:value', `:begin', `:end', `:post-blank' and +`:post-affiliated' keywords." (looking-at org-property-re) - (let ((begin (point)) - (key (org-match-string-no-properties 2)) - (value (org-match-string-no-properties 3)) + (let ((case-fold-search t) + (begin (point)) + (key (match-string-no-properties 2)) + (value (match-string-no-properties 3)) (end (save-excursion (end-of-line) (if (re-search-forward org-property-re limit t) @@ -2110,11 +2223,11 @@ keywords." :value value :begin begin :end end - :post-blank 0)))) + :post-blank 0 + :post-affiliated begin)))) -(defun org-element-node-property-interpreter (node-property contents) - "Interpret NODE-PROPERTY element as Org syntax. -CONTENTS is nil." +(defun org-element-node-property-interpreter (node-property _) + "Interpret NODE-PROPERTY element as Org syntax." (format org-property-format (format ":%s:" (org-element-property :key node-property)) (or (org-element-property :value node-property) ""))) @@ -2141,66 +2254,42 @@ Assume point is at the beginning of the paragraph." (before-blank (let ((case-fold-search t)) (end-of-line) - (if (not (re-search-forward - org-element-paragraph-separate limit 'm)) - limit - ;; A matching `org-element-paragraph-separate' is not - ;; necessarily the end of the paragraph. In - ;; particular, lines starting with # or : as a first - ;; non-space character are ambiguous. We have to - ;; check if they are valid Org syntax (e.g., not an - ;; incomplete keyword). - (beginning-of-line) - (while (not - (or - ;; There's no ambiguity for other symbols or - ;; empty lines: stop here. - (looking-at "[ \t]*\\(?:[^:#]\\|$\\)") - ;; Stop at valid fixed-width areas. - (looking-at "[ \t]*:\\(?: \\|$\\)") - ;; Stop at drawers. - (and (looking-at org-drawer-regexp) - (save-excursion - (re-search-forward - "^[ \t]*:END:[ \t]*$" limit t))) - ;; Stop at valid comments. - (looking-at "[ \t]*#\\(?: \\|$\\)") - ;; Stop at valid dynamic blocks. - (and (looking-at org-dblock-start-re) - (save-excursion - (re-search-forward - "^[ \t]*#\\+END:?[ \t]*$" limit t))) - ;; Stop at valid blocks. - (and (looking-at "[ \t]*#\\+BEGIN_\\(\\S-+\\)") - (save-excursion - (re-search-forward - (format "^[ \t]*#\\+END_%s[ \t]*$" - (regexp-quote - (org-match-string-no-properties 1))) - limit t))) - ;; Stop at valid latex environments. - (and (looking-at - "[ \t]*\\\\begin{\\([A-Za-z0-9]+\\*?\\)}") - (save-excursion - (re-search-forward - (format "^[ \t]*\\\\end{%s}[ \t]*$" - (regexp-quote - (org-match-string-no-properties 1))) - limit t))) - ;; Stop at valid keywords. - (looking-at "[ \t]*#\\+\\S-+:") - ;; Skip everything else. - (not - (progn - (end-of-line) - (re-search-forward org-element-paragraph-separate - limit 'm))))) - (beginning-of-line))) + ;; A matching `org-element-paragraph-separate' is not + ;; necessarily the end of the paragraph. In particular, + ;; drawers, blocks or LaTeX environments opening lines + ;; must be closed. Moreover keywords with a secondary + ;; value must belong to "dual keywords". + (while (not + (cond + ((not (and (re-search-forward + org-element-paragraph-separate limit 'move) + (progn (beginning-of-line) t)))) + ((looking-at org-drawer-regexp) + (save-excursion + (re-search-forward "^[ \t]*:END:[ \t]*$" limit t))) + ((looking-at "[ \t]*#\\+BEGIN_\\(\\S-+\\)") + (save-excursion + (re-search-forward + (format "^[ \t]*#\\+END_%s[ \t]*$" + (regexp-quote (match-string 1))) + limit t))) + ((looking-at org-element--latex-begin-environment) + (save-excursion + (re-search-forward + (format org-element--latex-end-environment + (regexp-quote (match-string 1))) + limit t))) + ((looking-at "[ \t]*#\\+\\(\\S-+\\)\\[.*\\]:") + (member-ignore-case (match-string 1) + org-element-dual-keywords)) + ;; Everything else is unambiguous. + (t))) + (end-of-line)) (if (= (point) limit) limit (goto-char (line-beginning-position))))) - (contents-end (progn (skip-chars-backward " \r\t\n" contents-begin) - (forward-line) - (point))) + (contents-end (save-excursion + (skip-chars-backward " \r\t\n" contents-begin) + (line-beginning-position 2))) (end (progn (skip-chars-forward " \r\t\n" limit) (if (eobp) (point) (line-beginning-position))))) (list 'paragraph @@ -2213,8 +2302,8 @@ Assume point is at the beginning of the paragraph." :post-affiliated contents-begin) (cdr affiliated)))))) -(defun org-element-paragraph-interpreter (paragraph contents) - "Interpret PARAGRAPH element as Org syntax. +(defun org-element-paragraph-interpreter (_ contents) + "Interpret paragraph element as Org syntax. CONTENTS is the contents of the element." contents) @@ -2227,8 +2316,8 @@ CONTENTS is the contents of the element." LIMIT bounds the search. Return a list whose CAR is `planning' and CDR is a plist -containing `:closed', `:deadline', `:scheduled', `:begin', `:end' -and `:post-blank' keywords." +containing `:closed', `:deadline', `:scheduled', `:begin', +`:end', `:post-blank' and `:post-affiliated' keywords." (save-excursion (let* ((case-fold-search nil) (begin (point)) @@ -2254,13 +2343,13 @@ and `:post-blank' keywords." :scheduled scheduled :begin begin :end end - :post-blank post-blank))))) + :post-blank post-blank + :post-affiliated begin))))) -(defun org-element-planning-interpreter (planning contents) - "Interpret PLANNING element as Org syntax. -CONTENTS is nil." +(defun org-element-planning-interpreter (planning _) + "Interpret PLANNING element as Org syntax." (mapconcat - 'identity + #'identity (delq nil (list (let ((deadline (org-element-property :deadline planning))) (when deadline @@ -2277,37 +2366,6 @@ CONTENTS is nil." " ")) -;;;; Quote Section - -(defun org-element-quote-section-parser (limit) - "Parse a quote section. - -LIMIT bounds the search. - -Return a list whose CAR is `quote-section' and CDR is a plist -containing `:begin', `:end', `:value' and `:post-blank' keywords. - -Assume point is at beginning of the section." - (save-excursion - (let* ((begin (point)) - (end (progn (org-with-limited-levels (outline-next-heading)) - (point))) - (pos-before-blank (progn (skip-chars-backward " \r\t\n") - (forward-line) - (point))) - (value (buffer-substring-no-properties begin pos-before-blank))) - (list 'quote-section - (list :begin begin - :end end - :value value - :post-blank (count-lines pos-before-blank end)))))) - -(defun org-element-quote-section-interpreter (quote-section contents) - "Interpret QUOTE-SECTION element as Org syntax. -CONTENTS is nil." - (org-element-property :value quote-section)) - - ;;;; Src Block (defun org-element-src-block-parser (limit affiliated) @@ -2320,9 +2378,9 @@ their value. Return a list whose CAR is `src-block' and CDR is a plist containing `:language', `:switches', `:parameters', `:begin', -`:end', `:hiddenp', `:number-lines', `:retain-labels', -`:use-labels', `:label-fmt', `:preserve-indent', `:value', -`:post-blank' and `:post-affiliated' keywords. +`:end', `:number-lines', `:retain-labels', `:use-labels', +`:label-fmt', `:preserve-indent', `:value', `:post-blank' and +`:post-affiliated' keywords. Assume point is at the beginning of the block." (let ((case-fold-search t)) @@ -2338,23 +2396,30 @@ Assume point is at the beginning of the block." (language (progn (looking-at - (concat "^[ \t]*#\\+BEGIN_SRC" - "\\(?: +\\(\\S-+\\)\\)?" - "\\(\\(?: +\\(?:-l \".*?\"\\|[-+][A-Za-z]\\)\\)+\\)?" - "\\(.*\\)[ \t]*$")) - (org-match-string-no-properties 1))) + "^[ \t]*#\\+BEGIN_SRC\ +\\(?: +\\(\\S-+\\)\\)?\ +\\(\\(?: +\\(?:-\\(?:l \".+\"\\|[ikr]\\)\\|[-+]n\\(?: *[0-9]+\\)?\\)\\)+\\)?\ +\\(.*\\)[ \t]*$") + (match-string-no-properties 1))) ;; Get switches. - (switches (org-match-string-no-properties 2)) + (switches (match-string-no-properties 2)) ;; Get parameters. - (parameters (org-match-string-no-properties 3)) - ;; Switches analysis + (parameters (match-string-no-properties 3)) + ;; Switches analysis. (number-lines - (cond ((not switches) nil) - ((string-match "-n\\>" switches) 'new) - ((string-match "+n\\>" switches) 'continued))) - (preserve-indent (or org-src-preserve-indentation - (and switches - (string-match "-i\\>" switches)))) + (and switches + (string-match "\\([-+]\\)n\\(?: *\\([0-9]+\\)\\)?\\>" + switches) + (cons + (if (equal (match-string 1 switches) "-") + 'new + 'continued) + (if (not (match-end 2)) 0 + ;; Subtract 1 to give number of lines before + ;; first line. + (1- (string-to-number (match-string 2 switches))))))) + (preserve-indent (and switches + (string-match "-i\\>" switches))) (label-fmt (and switches (string-match "-l +\"\\([^\"\n]+\\)\"" switches) @@ -2371,16 +2436,10 @@ Assume point is at the beginning of the block." (or (not switches) (and retain-labels (not (string-match "-k\\>" switches))))) - ;; Indentation. - (block-ind (progn (skip-chars-forward " \t") (current-column))) - ;; Get visibility status. - (hidden (progn (forward-line) (org-invisible-p2))) ;; Retrieve code. - (value (org-element--remove-indentation - (org-unescape-code-in-string - (buffer-substring-no-properties - (point) contents-end)) - (and preserve-indent block-ind))) + (value (org-unescape-code-in-string + (buffer-substring-no-properties + (line-beginning-position 2) contents-end))) (pos-before-blank (progn (goto-char contents-end) (forward-line) (point))) @@ -2401,32 +2460,33 @@ Assume point is at the beginning of the block." :retain-labels retain-labels :use-labels use-labels :label-fmt label-fmt - :hiddenp hidden :value value :post-blank (count-lines pos-before-blank end) :post-affiliated post-affiliated) (cdr affiliated))))))))) -(defun org-element-src-block-interpreter (src-block contents) - "Interpret SRC-BLOCK element as Org syntax. -CONTENTS is nil." +(defun org-element-src-block-interpreter (src-block _) + "Interpret SRC-BLOCK element as Org syntax." (let ((lang (org-element-property :language src-block)) (switches (org-element-property :switches src-block)) (params (org-element-property :parameters src-block)) - (value (let ((val (org-element-property :value src-block))) - (cond - ((org-element-property :preserve-indent src-block) val) - ((zerop org-edit-src-content-indentation) val) - (t - (let ((ind (make-string - org-edit-src-content-indentation 32))) - (replace-regexp-in-string - "\\(^\\)[ \t]*\\S-" ind val nil nil 1))))))) + (value + (let ((val (org-element-property :value src-block))) + (cond + ((or org-src-preserve-indentation + (org-element-property :preserve-indent src-block)) + val) + ((zerop org-edit-src-content-indentation) + (org-remove-indentation val)) + (t + (let ((ind (make-string org-edit-src-content-indentation ?\s))) + (replace-regexp-in-string + "^" ind (org-remove-indentation val)))))))) (concat (format "#+BEGIN_SRC%s\n" (concat (and lang (concat " " lang)) (and switches (concat " " switches)) (and params (concat " " params)))) - (org-escape-code-in-string value) + (org-element-normalize-string (org-escape-code-in-string value)) "#+END_SRC"))) @@ -2449,15 +2509,17 @@ Assume point is at the beginning of the table." (save-excursion (let* ((case-fold-search t) (table-begin (point)) - (type (if (org-at-table.el-p) 'table.el 'org)) + (type (if (looking-at "[ \t]*|") 'org 'table.el)) + (end-re (format "^[ \t]*\\($\\|[^| \t%s]\\)" + (if (eq type 'org) "" "+"))) (begin (car affiliated)) (table-end - (if (re-search-forward org-table-any-border-regexp limit 'm) + (if (re-search-forward end-re limit 'move) (goto-char (match-beginning 0)) (point))) (tblfm (let (acc) (while (looking-at "[ \t]*#\\+TBLFM: +\\(.*\\)[ \t]*$") - (push (org-match-string-no-properties 1) acc) + (push (match-string-no-properties 1) acc) (forward-line)) acc)) (pos-before-blank (point)) @@ -2496,41 +2558,38 @@ CONTENTS is a string, if table's type is `org', or nil." ;;;; Table Row -(defun org-element-table-row-parser (limit) +(defun org-element-table-row-parser (_) "Parse table row at point. -LIMIT bounds the search. - Return a list whose CAR is `table-row' and CDR is a plist containing `:begin', `:end', `:contents-begin', `:contents-end', -`:type' and `:post-blank' keywords." +`:type', `:post-blank' and `:post-affiliated' keywords." (save-excursion (let* ((type (if (looking-at "^[ \t]*|-") 'rule 'standard)) (begin (point)) ;; A table rule has no contents. In that case, ensure ;; CONTENTS-BEGIN matches CONTENTS-END. - (contents-begin (and (eq type 'standard) - (search-forward "|") - (point))) + (contents-begin (and (eq type 'standard) (search-forward "|"))) (contents-end (and (eq type 'standard) (progn (end-of-line) (skip-chars-backward " \t") (point)))) - (end (progn (forward-line) (point)))) + (end (line-beginning-position 2))) (list 'table-row (list :type type :begin begin :end end :contents-begin contents-begin :contents-end contents-end - :post-blank 0))))) + :post-blank 0 + :post-affiliated begin))))) (defun org-element-table-row-interpreter (table-row contents) "Interpret TABLE-ROW element as Org syntax. CONTENTS is the contents of the table row." (if (eq (org-element-property :type table-row) 'rule) "|-" - (concat "| " contents))) + (concat "|" contents))) ;;;; Verse Block @@ -2545,7 +2604,7 @@ their value. Return a list whose CAR is `verse-block' and CDR is a plist containing `:begin', `:end', `:contents-begin', `:contents-end', -`:hiddenp', `:post-blank' and `:post-affiliated' keywords. +`:post-blank' and `:post-affiliated' keywords. Assume point is at beginning of the block." (let ((case-fold-search t)) @@ -2557,8 +2616,7 @@ Assume point is at beginning of the block." (save-excursion (let* ((begin (car affiliated)) (post-affiliated (point)) - (hidden (progn (forward-line) (org-invisible-p2))) - (contents-begin (point)) + (contents-begin (progn (forward-line) (point))) (pos-before-blank (progn (goto-char contents-end) (forward-line) (point))) @@ -2570,13 +2628,12 @@ Assume point is at beginning of the block." :end end :contents-begin contents-begin :contents-end contents-end - :hiddenp hidden :post-blank (count-lines pos-before-blank end) :post-affiliated post-affiliated) (cdr affiliated))))))))) -(defun org-element-verse-block-interpreter (verse-block contents) - "Interpret VERSE-BLOCK element as Org syntax. +(defun org-element-verse-block-interpreter (_ contents) + "Interpret verse-block element as Org syntax. CONTENTS is verse block contents." (format "#+BEGIN_VERSE\n%s#+END_VERSE" contents)) @@ -2584,373 +2641,289 @@ CONTENTS is verse block contents." ;;; Objects ;; -;; Unlike to elements, interstices can be found between objects. -;; That's why, along with the parser, successor functions are provided -;; for each object. Some objects share the same successor (e.g., -;; `code' and `verbatim' objects). -;; -;; A successor must accept a single argument bounding the search. It -;; will return either a cons cell whose CAR is the object's type, as -;; a symbol, and CDR the position of its next occurrence, or nil. -;; -;; Successors follow the naming convention: -;; org-element-NAME-successor, where NAME is the name of the -;; successor, as defined in `org-element-all-successors'. +;; Unlike to elements, raw text can be found between objects. Hence, +;; `org-element--object-lex' is provided to find the next object in +;; buffer. ;; ;; Some object types (e.g., `italic') are recursive. Restrictions on ;; object types they can contain will be specified in ;; `org-element-object-restrictions'. ;; -;; Adding a new type of object is simple. Implement a successor, -;; a parser, and an interpreter for it, all following the naming -;; convention. Register type in `org-element-all-objects' and -;; successor in `org-element-all-successors'. Maybe tweak -;; restrictions about it, and that's it. - +;; Creating a new type of object requires to alter +;; `org-element--object-regexp' and `org-element--object-lex', add the +;; new type in `org-element-all-objects', and possibly add +;; restrictions in `org-element-object-restrictions'. ;;;; Bold (defun org-element-bold-parser () - "Parse bold object at point. + "Parse bold object at point, if any. -Return a list whose CAR is `bold' and CDR is a plist with -`:begin', `:end', `:contents-begin' and `:contents-end' and -`:post-blank' keywords. +When at a bold object, return a list whose car is `bold' and cdr +is a plist with `:begin', `:end', `:contents-begin' and +`:contents-end' and `:post-blank' keywords. Otherwise, return +nil. Assume point is at the first star marker." (save-excursion (unless (bolp) (backward-char 1)) - (looking-at org-emph-re) - (let ((begin (match-beginning 2)) - (contents-begin (match-beginning 4)) - (contents-end (match-end 4)) - (post-blank (progn (goto-char (match-end 2)) - (skip-chars-forward " \t"))) - (end (point))) - (list 'bold - (list :begin begin - :end end - :contents-begin contents-begin - :contents-end contents-end - :post-blank post-blank))))) - -(defun org-element-bold-interpreter (bold contents) - "Interpret BOLD object as Org syntax. + (when (looking-at org-emph-re) + (let ((begin (match-beginning 2)) + (contents-begin (match-beginning 4)) + (contents-end (match-end 4)) + (post-blank (progn (goto-char (match-end 2)) + (skip-chars-forward " \t"))) + (end (point))) + (list 'bold + (list :begin begin + :end end + :contents-begin contents-begin + :contents-end contents-end + :post-blank post-blank)))))) + +(defun org-element-bold-interpreter (_ contents) + "Interpret bold object as Org syntax. CONTENTS is the contents of the object." (format "*%s*" contents)) -(defun org-element-text-markup-successor () - "Search for the next text-markup object. - -Return value is a cons cell whose CAR is a symbol among `bold', -`italic', `underline', `strike-through', `code' and `verbatim' -and CDR is beginning position." - (save-excursion - (unless (bolp) (backward-char)) - (when (re-search-forward org-emph-re nil t) - (let ((marker (match-string 3))) - (cons (cond - ((equal marker "*") 'bold) - ((equal marker "/") 'italic) - ((equal marker "_") 'underline) - ((equal marker "+") 'strike-through) - ((equal marker "~") 'code) - ((equal marker "=") 'verbatim) - (t (error "Unknown marker at %d" (match-beginning 3)))) - (match-beginning 2)))))) - ;;;; Code (defun org-element-code-parser () - "Parse code object at point. + "Parse code object at point, if any. -Return a list whose CAR is `code' and CDR is a plist with -`:value', `:begin', `:end' and `:post-blank' keywords. +When at a code object, return a list whose car is `code' and cdr +is a plist with `:value', `:begin', `:end' and `:post-blank' +keywords. Otherwise, return nil. Assume point is at the first tilde marker." (save-excursion (unless (bolp) (backward-char 1)) - (looking-at org-emph-re) - (let ((begin (match-beginning 2)) - (value (org-match-string-no-properties 4)) - (post-blank (progn (goto-char (match-end 2)) - (skip-chars-forward " \t"))) - (end (point))) - (list 'code - (list :value value - :begin begin - :end end - :post-blank post-blank))))) - -(defun org-element-code-interpreter (code contents) - "Interpret CODE object as Org syntax. -CONTENTS is nil." + (when (looking-at org-verbatim-re) + (let ((begin (match-beginning 2)) + (value (match-string-no-properties 4)) + (post-blank (progn (goto-char (match-end 2)) + (skip-chars-forward " \t"))) + (end (point))) + (list 'code + (list :value value + :begin begin + :end end + :post-blank post-blank)))))) + +(defun org-element-code-interpreter (code _) + "Interpret CODE object as Org syntax." (format "~%s~" (org-element-property :value code))) ;;;; Entity (defun org-element-entity-parser () - "Parse entity at point. + "Parse entity at point, if any. -Return a list whose CAR is `entity' and CDR a plist with -`:begin', `:end', `:latex', `:latex-math-p', `:html', `:latin1', -`:utf-8', `:ascii', `:use-brackets-p' and `:post-blank' as -keywords. +When at an entity, return a list whose car is `entity' and cdr +a plist with `:begin', `:end', `:latex', `:latex-math-p', +`:html', `:latin1', `:utf-8', `:ascii', `:use-brackets-p' and +`:post-blank' as keywords. Otherwise, return nil. Assume point is at the beginning of the entity." - (save-excursion - (looking-at "\\\\\\(there4\\|sup[123]\\|frac[13][24]\\|[a-zA-Z]+\\)\\($\\|{}\\|[^[:alpha:]]\\)") - (let* ((value (org-entity-get (match-string 1))) - (begin (match-beginning 0)) - (bracketsp (string= (match-string 2) "{}")) - (post-blank (progn (goto-char (match-end 1)) - (when bracketsp (forward-char 2)) - (skip-chars-forward " \t"))) - (end (point))) - (list 'entity - (list :name (car value) - :latex (nth 1 value) - :latex-math-p (nth 2 value) - :html (nth 3 value) - :ascii (nth 4 value) - :latin1 (nth 5 value) - :utf-8 (nth 6 value) - :begin begin - :end end - :use-brackets-p bracketsp - :post-blank post-blank))))) - -(defun org-element-entity-interpreter (entity contents) - "Interpret ENTITY object as Org syntax. -CONTENTS is nil." + (catch 'no-object + (when (looking-at "\\\\\\(?:\\(?1:_ +\\)\\|\\(?1:there4\\|sup[123]\\|frac[13][24]\\|[a-zA-Z]+\\)\\(?2:$\\|{}\\|[^[:alpha:]]\\)\\)") + (save-excursion + (let* ((value (or (org-entity-get (match-string 1)) + (throw 'no-object nil))) + (begin (match-beginning 0)) + (bracketsp (string= (match-string 2) "{}")) + (post-blank (progn (goto-char (match-end 1)) + (when bracketsp (forward-char 2)) + (skip-chars-forward " \t"))) + (end (point))) + (list 'entity + (list :name (car value) + :latex (nth 1 value) + :latex-math-p (nth 2 value) + :html (nth 3 value) + :ascii (nth 4 value) + :latin1 (nth 5 value) + :utf-8 (nth 6 value) + :begin begin + :end end + :use-brackets-p bracketsp + :post-blank post-blank))))))) + +(defun org-element-entity-interpreter (entity _) + "Interpret ENTITY object as Org syntax." (concat "\\" (org-element-property :name entity) (when (org-element-property :use-brackets-p entity) "{}"))) -(defun org-element-latex-or-entity-successor () - "Search for the next latex-fragment or entity object. - -Return value is a cons cell whose CAR is `entity' or -`latex-fragment' and CDR is beginning position." - (save-excursion - (unless (bolp) (backward-char)) - (let ((matchers (cdr org-latex-regexps)) - ;; ENTITY-RE matches both LaTeX commands and Org entities. - (entity-re - "\\\\\\(there4\\|sup[123]\\|frac[13][24]\\|[a-zA-Z]+\\)\\($\\|{}\\|[^[:alpha:]]\\)")) - (when (re-search-forward - (concat (mapconcat #'cadr matchers "\\|") "\\|" entity-re) nil t) - (goto-char (match-beginning 0)) - (if (looking-at entity-re) - ;; Determine if it's a real entity or a LaTeX command. - (cons (if (org-entity-get (match-string 1)) 'entity 'latex-fragment) - (match-beginning 0)) - ;; No entity nor command: point is at a LaTeX fragment. - ;; Determine its type to get the correct beginning position. - (cons 'latex-fragment - (catch 'return - (dolist (e matchers) - (when (looking-at (nth 1 e)) - (throw 'return (match-beginning (nth 2 e))))) - (point)))))))) - ;;;; Export Snippet (defun org-element-export-snippet-parser () "Parse export snippet at point. -Return a list whose CAR is `export-snippet' and CDR a plist with -`:begin', `:end', `:back-end', `:value' and `:post-blank' as -keywords. +When at an export snippet, return a list whose car is +`export-snippet' and cdr a plist with `:begin', `:end', +`:back-end', `:value' and `:post-blank' as keywords. Otherwise, +return nil. Assume point is at the beginning of the snippet." (save-excursion - (re-search-forward "@@\\([-A-Za-z0-9]+\\):" nil t) - (let* ((begin (match-beginning 0)) - (back-end (org-match-string-no-properties 1)) - (value (buffer-substring-no-properties - (point) - (progn (re-search-forward "@@" nil t) (match-beginning 0)))) - (post-blank (skip-chars-forward " \t")) - (end (point))) - (list 'export-snippet - (list :back-end back-end - :value value - :begin begin - :end end - :post-blank post-blank))))) - -(defun org-element-export-snippet-interpreter (export-snippet contents) - "Interpret EXPORT-SNIPPET object as Org syntax. -CONTENTS is nil." + (let (contents-end) + (when (and (looking-at "@@\\([-A-Za-z0-9]+\\):") + (setq contents-end + (save-match-data (goto-char (match-end 0)) + (re-search-forward "@@" nil t) + (match-beginning 0)))) + (let* ((begin (match-beginning 0)) + (back-end (match-string-no-properties 1)) + (value (buffer-substring-no-properties + (match-end 0) contents-end)) + (post-blank (skip-chars-forward " \t")) + (end (point))) + (list 'export-snippet + (list :back-end back-end + :value value + :begin begin + :end end + :post-blank post-blank))))))) + +(defun org-element-export-snippet-interpreter (export-snippet _) + "Interpret EXPORT-SNIPPET object as Org syntax." (format "@@%s:%s@@" (org-element-property :back-end export-snippet) (org-element-property :value export-snippet))) -(defun org-element-export-snippet-successor () - "Search for the next export-snippet object. - -Return value is a cons cell whose CAR is `export-snippet' and CDR -its beginning position." - (save-excursion - (let (beg) - (when (and (re-search-forward "@@[-A-Za-z0-9]+:" nil t) - (setq beg (match-beginning 0)) - (search-forward "@@" nil t)) - (cons 'export-snippet beg))))) - ;;;; Footnote Reference (defun org-element-footnote-reference-parser () - "Parse footnote reference at point. - -Return a list whose CAR is `footnote-reference' and CDR a plist -with `:label', `:type', `:inline-definition', `:begin', `:end' -and `:post-blank' as keywords." - (save-excursion - (looking-at org-footnote-re) - (let* ((begin (point)) - (label (or (org-match-string-no-properties 2) - (org-match-string-no-properties 3) - (and (match-string 1) - (concat "fn:" (org-match-string-no-properties 1))))) - (type (if (or (not label) (match-string 1)) 'inline 'standard)) - (inner-begin (match-end 0)) - (inner-end - (let ((count 1)) - (forward-char) - (while (and (> count 0) (re-search-forward "[][]" nil t)) - (if (equal (match-string 0) "[") (incf count) (decf count))) - (1- (point)))) - (post-blank (progn (goto-char (1+ inner-end)) - (skip-chars-forward " \t"))) - (end (point)) - (footnote-reference + "Parse footnote reference at point, if any. + +When at a footnote reference, return a list whose car is +`footnote-reference' and cdr a plist with `:label', `:type', +`:begin', `:end', `:content-begin', `:contents-end' and +`:post-blank' as keywords. Otherwise, return nil." + (when (looking-at org-footnote-re) + (let ((closing (with-syntax-table org-element--pair-square-table + (ignore-errors (scan-lists (point) 1 0))))) + (when closing + (save-excursion + (let* ((begin (point)) + (label (match-string-no-properties 1)) + (inner-begin (match-end 0)) + (inner-end (1- closing)) + (type (if (match-end 2) 'inline 'standard)) + (post-blank (progn (goto-char closing) + (skip-chars-forward " \t"))) + (end (point))) (list 'footnote-reference (list :label label :type type :begin begin :end end - :post-blank post-blank)))) - (org-element-put-property - footnote-reference :inline-definition - (and (eq type 'inline) - (org-element-parse-secondary-string - (buffer-substring inner-begin inner-end) - (org-element-restriction 'footnote-reference) - footnote-reference)))))) + :contents-begin (and (eq type 'inline) inner-begin) + :contents-end (and (eq type 'inline) inner-end) + :post-blank post-blank)))))))) (defun org-element-footnote-reference-interpreter (footnote-reference contents) "Interpret FOOTNOTE-REFERENCE object as Org syntax. -CONTENTS is nil." - (let ((label (or (org-element-property :label footnote-reference) "fn:")) - (def - (let ((inline-def - (org-element-property :inline-definition footnote-reference))) - (if (not inline-def) "" - (concat ":" (org-element-interpret-data inline-def)))))) - (format "[%s]" (concat label def)))) - -(defun org-element-footnote-reference-successor () - "Search for the next footnote-reference object. - -Return value is a cons cell whose CAR is `footnote-reference' and -CDR is beginning position." - (save-excursion - (catch 'exit - (while (re-search-forward org-footnote-re nil t) - (save-excursion - (let ((beg (match-beginning 0)) - (count 1)) - (backward-char) - (while (re-search-forward "[][]" nil t) - (if (equal (match-string 0) "[") (incf count) (decf count)) - (when (zerop count) - (throw 'exit (cons 'footnote-reference beg)))))))))) +CONTENTS is its definition, when inline, or nil." + (format "[fn:%s%s]" + (or (org-element-property :label footnote-reference) "") + (if contents (concat ":" contents) ""))) ;;;; Inline Babel Call (defun org-element-inline-babel-call-parser () - "Parse inline babel call at point. + "Parse inline babel call at point, if any. -Return a list whose CAR is `inline-babel-call' and CDR a plist -with `:begin', `:end', `:info' and `:post-blank' as keywords. +When at an inline babel call, return a list whose car is +`inline-babel-call' and cdr a plist with `:call', +`:inside-header', `:arguments', `:end-header', `:begin', `:end', +`:value' and `:post-blank' as keywords. Otherwise, return nil. Assume point is at the beginning of the babel call." (save-excursion - (unless (bolp) (backward-char)) - (looking-at org-babel-inline-lob-one-liner-regexp) - (let ((info (save-match-data (org-babel-lob-get-info))) - (begin (match-end 1)) - (post-blank (progn (goto-char (match-end 0)) - (skip-chars-forward " \t"))) - (end (point))) - (list 'inline-babel-call - (list :begin begin - :end end - :info info - :post-blank post-blank))))) - -(defun org-element-inline-babel-call-interpreter (inline-babel-call contents) - "Interpret INLINE-BABEL-CALL object as Org syntax. -CONTENTS is nil." - (let* ((babel-info (org-element-property :info inline-babel-call)) - (main-source (car babel-info)) - (post-options (nth 1 babel-info))) - (concat "call_" - (if (string-match "\\[\\(\\[.*?\\]\\)\\]" main-source) - ;; Remove redundant square brackets. - (replace-match - (match-string 1 main-source) nil nil main-source) - main-source) - (and post-options (format "[%s]" post-options))))) - -(defun org-element-inline-babel-call-successor () - "Search for the next inline-babel-call object. - -Return value is a cons cell whose CAR is `inline-babel-call' and -CDR is beginning position." - (save-excursion - (when (re-search-forward org-babel-inline-lob-one-liner-regexp nil t) - (cons 'inline-babel-call (match-end 1))))) + (catch :no-object + (when (let ((case-fold-search nil)) + (looking-at "\\<call_\\([^ \t\n[(]+\\)[([]")) + (goto-char (match-end 1)) + (let* ((begin (match-beginning 0)) + (call (match-string-no-properties 1)) + (inside-header + (let ((p (org-element--parse-paired-brackets ?\[))) + (and (org-string-nw-p p) + (replace-regexp-in-string "\n[ \t]*" " " (org-trim p))))) + (arguments (org-string-nw-p + (or (org-element--parse-paired-brackets ?\() + ;; Parenthesis are mandatory. + (throw :no-object nil)))) + (end-header + (let ((p (org-element--parse-paired-brackets ?\[))) + (and (org-string-nw-p p) + (replace-regexp-in-string "\n[ \t]*" " " (org-trim p))))) + (value (buffer-substring-no-properties begin (point))) + (post-blank (skip-chars-forward " \t")) + (end (point))) + (list 'inline-babel-call + (list :call call + :inside-header inside-header + :arguments arguments + :end-header end-header + :begin begin + :end end + :value value + :post-blank post-blank))))))) + +(defun org-element-inline-babel-call-interpreter (inline-babel-call _) + "Interpret INLINE-BABEL-CALL object as Org syntax." + (concat "call_" + (org-element-property :call inline-babel-call) + (let ((h (org-element-property :inside-header inline-babel-call))) + (and h (format "[%s]" h))) + "(" (org-element-property :arguments inline-babel-call) ")" + (let ((h (org-element-property :end-header inline-babel-call))) + (and h (format "[%s]" h))))) ;;;; Inline Src Block (defun org-element-inline-src-block-parser () - "Parse inline source block at point. + "Parse inline source block at point, if any. -Return a list whose CAR is `inline-src-block' and CDR a plist -with `:begin', `:end', `:language', `:value', `:parameters' and -`:post-blank' as keywords. +When at an inline source block, return a list whose car is +`inline-src-block' and cdr a plist with `:begin', `:end', +`:language', `:value', `:parameters' and `:post-blank' as +keywords. Otherwise, return nil. Assume point is at the beginning of the inline src block." (save-excursion - (unless (bolp) (backward-char)) - (looking-at org-babel-inline-src-block-regexp) - (let ((begin (match-beginning 1)) - (language (org-match-string-no-properties 2)) - (parameters (org-match-string-no-properties 4)) - (value (org-match-string-no-properties 5)) - (post-blank (progn (goto-char (match-end 0)) - (skip-chars-forward " \t"))) - (end (point))) - (list 'inline-src-block - (list :language language - :value value - :parameters parameters - :begin begin - :end end - :post-blank post-blank))))) - -(defun org-element-inline-src-block-interpreter (inline-src-block contents) - "Interpret INLINE-SRC-BLOCK object as Org syntax. -CONTENTS is nil." + (catch :no-object + (when (let ((case-fold-search nil)) + (looking-at "\\<src_\\([^ \t\n[{]+\\)[{[]")) + (goto-char (match-end 1)) + (let ((begin (match-beginning 0)) + (language (match-string-no-properties 1)) + (parameters + (let ((p (org-element--parse-paired-brackets ?\[))) + (and (org-string-nw-p p) + (replace-regexp-in-string "\n[ \t]*" " " (org-trim p))))) + (value (or (org-element--parse-paired-brackets ?\{) + (throw :no-object nil))) + (post-blank (skip-chars-forward " \t"))) + (list 'inline-src-block + (list :language language + :value value + :parameters parameters + :begin begin + :end (point) + :post-blank post-blank))))))) + +(defun org-element-inline-src-block-interpreter (inline-src-block _) + "Interpret INLINE-SRC-BLOCK object as Org syntax." (let ((language (org-element-property :language inline-src-block)) (arguments (org-element-property :parameters inline-src-block)) (body (org-element-property :value inline-src-block))) @@ -2959,44 +2932,35 @@ CONTENTS is nil." (if arguments (format "[%s]" arguments) "") body))) -(defun org-element-inline-src-block-successor () - "Search for the next inline-babel-call element. - -Return value is a cons cell whose CAR is `inline-babel-call' and -CDR is beginning position." - (save-excursion - (unless (bolp) (backward-char)) - (when (re-search-forward org-babel-inline-src-block-regexp nil t) - (cons 'inline-src-block (match-beginning 1))))) - ;;;; Italic (defun org-element-italic-parser () - "Parse italic object at point. + "Parse italic object at point, if any. -Return a list whose CAR is `italic' and CDR is a plist with -`:begin', `:end', `:contents-begin' and `:contents-end' and -`:post-blank' keywords. +When at an italic object, return a list whose car is `italic' and +cdr is a plist with `:begin', `:end', `:contents-begin' and +`:contents-end' and `:post-blank' keywords. Otherwise, return +nil. Assume point is at the first slash marker." (save-excursion (unless (bolp) (backward-char 1)) - (looking-at org-emph-re) - (let ((begin (match-beginning 2)) - (contents-begin (match-beginning 4)) - (contents-end (match-end 4)) - (post-blank (progn (goto-char (match-end 2)) - (skip-chars-forward " \t"))) - (end (point))) - (list 'italic - (list :begin begin - :end end - :contents-begin contents-begin - :contents-end contents-end - :post-blank post-blank))))) - -(defun org-element-italic-interpreter (italic contents) - "Interpret ITALIC object as Org syntax. + (when (looking-at org-emph-re) + (let ((begin (match-beginning 2)) + (contents-begin (match-beginning 4)) + (contents-end (match-end 4)) + (post-blank (progn (goto-char (match-end 2)) + (skip-chars-forward " \t"))) + (end (point))) + (list 'italic + (list :begin begin + :end end + :contents-begin contents-begin + :contents-end contents-end + :post-blank post-blank)))))) + +(defun org-element-italic-interpreter (_ contents) + "Interpret italic object as Org syntax. CONTENTS is the contents of the object." (format "/%s/" contents)) @@ -3004,169 +2968,196 @@ CONTENTS is the contents of the object." ;;;; Latex Fragment (defun org-element-latex-fragment-parser () - "Parse LaTeX fragment at point. + "Parse LaTeX fragment at point, if any. -Return a list whose CAR is `latex-fragment' and CDR a plist with -`:value', `:begin', `:end', and `:post-blank' as keywords. +When at a LaTeX fragment, return a list whose car is +`latex-fragment' and cdr a plist with `:value', `:begin', `:end', +and `:post-blank' as keywords. Otherwise, return nil. Assume point is at the beginning of the LaTeX fragment." - (save-excursion - (let* ((begin (point)) - (substring-match - (catch 'exit - (dolist (e (cdr org-latex-regexps)) - (let ((latex-regexp (nth 1 e))) - (when (or (looking-at latex-regexp) - (and (not (bobp)) - (save-excursion - (backward-char) - (looking-at latex-regexp)))) - (throw 'exit (nth 2 e))))) - ;; None found: it's a macro. - (looking-at "\\\\[a-zA-Z]+\\*?\\(\\(\\[[^][\n{}]*\\]\\)\\|\\({[^{}\n]*}\\)\\)*") - 0)) - (value (org-match-string-no-properties substring-match)) - (post-blank (progn (goto-char (match-end substring-match)) - (skip-chars-forward " \t"))) - (end (point))) - (list 'latex-fragment - (list :value value - :begin begin - :end end - :post-blank post-blank))))) - -(defun org-element-latex-fragment-interpreter (latex-fragment contents) - "Interpret LATEX-FRAGMENT object as Org syntax. -CONTENTS is nil." + (catch 'no-object + (save-excursion + (let* ((begin (point)) + (after-fragment + (cond + ((not (eq ?$ (char-after))) + (pcase (char-after (1+ (point))) + (?\( (search-forward "\\)" nil t)) + (?\[ (search-forward "\\]" nil t)) + (_ + ;; Macro. + (and (looking-at "\\\\[a-zA-Z]+\\*?\\(\\(\\[[^][\n{}]*\\]\\)\ +\\|\\({[^{}\n]*}\\)\\)*") + (match-end 0))))) + ((eq ?$ (char-after (1+ (point)))) + (search-forward "$$" nil t 2)) + (t + (and (not (eq ?$ (char-before))) + (not (memq (char-after (1+ (point))) + '(?\s ?\t ?\n ?, ?. ?\;))) + (search-forward "$" nil t 2) + (not (memq (char-before (match-beginning 0)) + '(?\s ?\t ?\n ?, ?.))) + (looking-at-p + "\\(\\s.\\|\\s-\\|\\s(\\|\\s)\\|\\s\"\\|'\\|$\\)") + (point))))) + (post-blank + (if (not after-fragment) (throw 'no-object nil) + (goto-char after-fragment) + (skip-chars-forward " \t"))) + (end (point))) + (list 'latex-fragment + (list :value (buffer-substring-no-properties begin after-fragment) + :begin begin + :end end + :post-blank post-blank)))))) + +(defun org-element-latex-fragment-interpreter (latex-fragment _) + "Interpret LATEX-FRAGMENT object as Org syntax." (org-element-property :value latex-fragment)) ;;;; Line Break (defun org-element-line-break-parser () - "Parse line break at point. + "Parse line break at point, if any. -Return a list whose CAR is `line-break', and CDR a plist with -`:begin', `:end' and `:post-blank' keywords. +When at a line break, return a list whose car is `line-break', +and cdr a plist with `:begin', `:end' and `:post-blank' keywords. +Otherwise, return nil. Assume point is at the beginning of the line break." - (list 'line-break - (list :begin (point) - :end (progn (forward-line) (point)) - :post-blank 0))) + (when (and (looking-at-p "\\\\\\\\[ \t]*$") + (not (eq (char-before) ?\\))) + (list 'line-break + (list :begin (point) + :end (line-beginning-position 2) + :post-blank 0)))) -(defun org-element-line-break-interpreter (line-break contents) - "Interpret LINE-BREAK object as Org syntax. -CONTENTS is nil." +(defun org-element-line-break-interpreter (&rest _) + "Interpret LINE-BREAK object as Org syntax." "\\\\\n") -(defun org-element-line-break-successor () - "Search for the next line-break object. - -Return value is a cons cell whose CAR is `line-break' and CDR is -beginning position." - (save-excursion - (let ((beg (and (re-search-forward "[^\\\\]\\(\\\\\\\\\\)[ \t]*$" nil t) - (goto-char (match-beginning 1))))) - ;; A line break can only happen on a non-empty line. - (when (and beg (re-search-backward "\\S-" (point-at-bol) t)) - (cons 'line-break beg))))) - ;;;; Link (defun org-element-link-parser () - "Parse link at point. + "Parse link at point, if any. -Return a list whose CAR is `link' and CDR a plist with `:type', -`:path', `:raw-link', `:application', `:search-option', `:begin', -`:end', `:contents-begin', `:contents-end' and `:post-blank' as -keywords. +When at a link, return a list whose car is `link' and cdr a plist +with `:type', `:path', `:format', `:raw-link', `:application', +`:search-option', `:begin', `:end', `:contents-begin', +`:contents-end' and `:post-blank' as keywords. Otherwise, return +nil. Assume point is at the beginning of the link." - (save-excursion + (catch 'no-object (let ((begin (point)) - end contents-begin contents-end link-end post-blank path type - raw-link link search-option application) + end contents-begin contents-end link-end post-blank path type format + raw-link search-option application) (cond ;; Type 1: Text targeted from a radio target. - ((and org-target-link-regexp (looking-at org-target-link-regexp)) - (setq type "radio" - link-end (match-end 0) - path (org-match-string-no-properties 0) - contents-begin (match-beginning 0) - contents-end (match-end 0))) + ((and org-target-link-regexp + (save-excursion (or (bolp) (backward-char)) + (looking-at org-target-link-regexp))) + (setq type "radio") + (setq format 'plain) + (setq link-end (match-end 1)) + (setq path (match-string-no-properties 1)) + (setq contents-begin (match-beginning 1)) + (setq contents-end (match-end 1))) ;; Type 2: Standard link, i.e. [[http://orgmode.org][homepage]] ((looking-at org-bracket-link-regexp) - (setq contents-begin (match-beginning 3) - contents-end (match-end 3) - link-end (match-end 0) - ;; RAW-LINK is the original link. Expand any - ;; abbreviation in it. - raw-link (org-translate-link - (org-link-expand-abbrev - (org-match-string-no-properties 1)))) - ;; Determine TYPE of link and set PATH accordingly. + (setq format 'bracket) + (setq contents-begin (match-beginning 3)) + (setq contents-end (match-end 3)) + (setq link-end (match-end 0)) + ;; RAW-LINK is the original link. Expand any + ;; abbreviation in it. + ;; + ;; Also treat any newline character and associated + ;; indentation as a single space character. This is not + ;; compatible with RFC 3986, which requires to ignore + ;; them altogether. However, doing so would require + ;; users to encode spaces on the fly when writing links + ;; (e.g., insert [[shell:ls%20*.org]] instead of + ;; [[shell:ls *.org]], which defeats Org's focus on + ;; simplicity. + (setq raw-link (org-link-expand-abbrev + (replace-regexp-in-string + "[ \t]*\n[ \t]*" " " + (match-string-no-properties 1)))) + ;; Determine TYPE of link and set PATH accordingly. According + ;; to RFC 3986, remove whitespaces from URI in external links. + ;; In internal ones, treat indentation as a single space. (cond ;; File type. ((or (file-name-absolute-p raw-link) (string-match "\\`\\.\\.?/" raw-link)) - (setq type "file" path raw-link)) - ;; Explicit type (http, irc, bbdb...). See `org-link-types'. + (setq type "file") + (setq path raw-link)) + ;; Explicit type (http, irc, bbdb...). ((string-match org-link-types-re raw-link) - (setq type (match-string 1 raw-link) - ;; According to RFC 3986, extra whitespace should be - ;; ignored when a URI is extracted. - path (replace-regexp-in-string - "[ \t]*\n[ \t]*" "" (substring raw-link (match-end 0))))) - ;; Id type: PATH is the id. - ((string-match "\\`id:\\([-a-f0-9]+\\)" raw-link) - (setq type "id" path (match-string 1 raw-link))) + (setq type (match-string 1 raw-link)) + (setq path (substring raw-link (match-end 0)))) ;; Code-ref type: PATH is the name of the reference. - ((string-match "\\`(\\(.*\\))\\'" raw-link) - (setq type "coderef" path (match-string 1 raw-link))) + ((and (string-match-p "\\`(" raw-link) + (string-match-p ")\\'" raw-link)) + (setq type "coderef") + (setq path (substring raw-link 1 -1))) ;; Custom-id type: PATH is the name of the custom id. - ((= (aref raw-link 0) ?#) - (setq type "custom-id" path (substring raw-link 1))) + ((= (string-to-char raw-link) ?#) + (setq type "custom-id") + (setq path (substring raw-link 1))) ;; Fuzzy type: Internal link either matches a target, an ;; headline name or nothing. PATH is the target or ;; headline's name. - (t (setq type "fuzzy" path raw-link)))) + (t + (setq type "fuzzy") + (setq path raw-link)))) ;; Type 3: Plain link, e.g., http://orgmode.org ((looking-at org-plain-link-re) - (setq raw-link (org-match-string-no-properties 0) - type (org-match-string-no-properties 1) - link-end (match-end 0) - path (org-match-string-no-properties 2))) - ;; Type 4: Angular link, e.g., <http://orgmode.org> + (setq format 'plain) + (setq raw-link (match-string-no-properties 0)) + (setq type (match-string-no-properties 1)) + (setq link-end (match-end 0)) + (setq path (match-string-no-properties 2))) + ;; Type 4: Angular link, e.g., <http://orgmode.org>. Unlike to + ;; bracket links, follow RFC 3986 and remove any extra + ;; whitespace in URI. ((looking-at org-angle-link-re) - (setq raw-link (buffer-substring-no-properties - (match-beginning 1) (match-end 2)) - type (org-match-string-no-properties 1) - link-end (match-end 0) - path (org-match-string-no-properties 2)))) + (setq format 'angle) + (setq type (match-string-no-properties 1)) + (setq link-end (match-end 0)) + (setq raw-link + (buffer-substring-no-properties + (match-beginning 1) (match-end 2))) + (setq path (replace-regexp-in-string + "[ \t]*\n[ \t]*" "" (match-string-no-properties 2)))) + (t (throw 'no-object nil))) ;; In any case, deduce end point after trailing white space from ;; LINK-END variable. - (setq post-blank (progn (goto-char link-end) (skip-chars-forward " \t")) - end (point)) - ;; Special "file" type link processing. - (when (member type org-element-link-type-is-file) - ;; Extract opening application and search option. - (cond ((string-match "^file\\+\\(.*\\)$" type) - (setq application (match-string 1 type))) - ((not (string-match "^file" type)) - (setq application type))) + (save-excursion + (setq post-blank + (progn (goto-char link-end) (skip-chars-forward " \t"))) + (setq end (point))) + ;; 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") (when (string-match "::\\(.*\\)\\'" path) - (setq search-option (match-string 1 path) - path (replace-match "" nil nil path))) - ;; Normalize URI. - (when (and (not (org-string-match-p "\\`//" path)) - (file-name-absolute-p path)) - (setq path (concat "//" (expand-file-name path)))) - ;; Make sure TYPE always reports "file". - (setq type "file")) + (setq search-option (match-string 1 path)) + (setq path (replace-match "" nil nil path))) + (setq path (replace-regexp-in-string "\\`///*\\(.:\\)?/" "\\1/" path))) + ;; Translate link, if `org-link-translation-function' is set. + (let ((trans (and (functionp org-link-translation-function) + (funcall org-link-translation-function type path)))) + (when trans + (setq type (car trans)) + (setq path (cdr trans)))) (list 'link (list :type type :path path + :format format :raw-link (or raw-link path) :application application :search-option search-option @@ -3180,197 +3171,167 @@ Assume point is at the beginning of the link." "Interpret LINK object as Org syntax. CONTENTS is the contents of the object, or nil." (let ((type (org-element-property :type link)) - (raw-link (org-element-property :raw-link link))) - (if (string= type "radio") raw-link - (format "[[%s]%s]" - raw-link - (if contents (format "[%s]" contents) ""))))) - -(defun org-element-link-successor () - "Search for the next link object. - -Return value is a cons cell whose CAR is `link' and CDR is -beginning position." - (save-excursion - (let ((link-regexp - (if (not org-target-link-regexp) org-any-link-re - (concat org-any-link-re "\\|" org-target-link-regexp)))) - (when (re-search-forward link-regexp nil t) - (cons 'link (match-beginning 0)))))) - -(defun org-element-plain-link-successor () - "Search for the next plain link object. - -Return value is a cons cell whose CAR is `link' and CDR is -beginning position." - (and (save-excursion (re-search-forward org-plain-link-re nil t)) - (cons 'link (match-beginning 0)))) + (path (org-element-property :path link))) + (if (string= type "radio") path + (let ((fmt (pcase (org-element-property :format link) + ;; Links with contents and internal links have to + ;; use bracket syntax. Ignore `:format' in these + ;; cases. This is also the default syntax when the + ;; property is not defined, e.g., when the object + ;; was crafted by the user. + ((guard contents) + (format "[[%%s][%s]]" + ;; Since this is going to be used as + ;; a format string, escape percent signs + ;; in description. + (replace-regexp-in-string "%" "%%" contents))) + ((or `bracket + `nil + (guard (member type '("coderef" "custom-id" "fuzzy")))) + "[[%s]]") + ;; Otherwise, just obey to `:format'. + (`angle "<%s>") + (`plain "%s") + (f (error "Wrong `:format' value: %s" f))))) + (format fmt + (pcase type + ("coderef" (format "(%s)" path)) + ("custom-id" (concat "#" path)) + ("file" + (let ((app (org-element-property :application link)) + (opt (org-element-property :search-option link))) + (concat type (and app (concat "+" app)) ":" + path + (and opt (concat "::" opt))))) + ("fuzzy" path) + (_ (concat type ":" path)))))))) ;;;; Macro (defun org-element-macro-parser () - "Parse macro at point. + "Parse macro at point, if any. -Return a list whose CAR is `macro' and CDR a plist with `:key', -`:args', `:begin', `:end', `:value' and `:post-blank' as -keywords. +When at a macro, return a list whose car is `macro' and cdr +a plist with `:key', `:args', `:begin', `:end', `:value' and +`:post-blank' as keywords. Otherwise, return nil. Assume point is at the macro." (save-excursion - (looking-at "{{{\\([a-zA-Z][-a-zA-Z0-9_]*\\)\\(([ \t\n]*\\([^\000]*?\\))\\)?}}}") - (let ((begin (point)) - (key (downcase (org-match-string-no-properties 1))) - (value (org-match-string-no-properties 0)) - (post-blank (progn (goto-char (match-end 0)) - (skip-chars-forward " \t"))) - (end (point)) - (args (let ((args (org-match-string-no-properties 3))) - (when args - ;; Do not use `org-split-string' since empty - ;; strings are meaningful here. - (split-string - (replace-regexp-in-string - "\\(\\\\*\\)\\(,\\)" - (lambda (str) - (let ((len (length (match-string 1 str)))) - (concat (make-string (/ len 2) ?\\) - (if (zerop (mod len 2)) "\000" ",")))) - args nil t) - "\000"))))) - (list 'macro - (list :key key - :value value - :args args - :begin begin - :end end - :post-blank post-blank))))) - -(defun org-element-macro-interpreter (macro contents) - "Interpret MACRO object as Org syntax. -CONTENTS is nil." + (when (looking-at "{{{\\([a-zA-Z][-a-zA-Z0-9_]*\\)\\(([ \t\n]*\\([^\000]*?\\))\\)?}}}") + (let ((begin (point)) + (key (downcase (match-string-no-properties 1))) + (value (match-string-no-properties 0)) + (post-blank (progn (goto-char (match-end 0)) + (skip-chars-forward " \t"))) + (end (point)) + (args (let ((args (match-string-no-properties 3))) + (and args (org-macro-extract-arguments args))))) + (list 'macro + (list :key key + :value value + :args args + :begin begin + :end end + :post-blank post-blank)))))) + +(defun org-element-macro-interpreter (macro _) + "Interpret MACRO object as Org syntax." (org-element-property :value macro)) -(defun org-element-macro-successor () - "Search for the next macro object. - -Return value is cons cell whose CAR is `macro' and CDR is -beginning position." - (save-excursion - (when (re-search-forward - "{{{\\([a-zA-Z][-a-zA-Z0-9_]*\\)\\(([ \t\n]*\\([^\000]*?\\))\\)?}}}" - nil t) - (cons 'macro (match-beginning 0))))) - ;;;; Radio-target (defun org-element-radio-target-parser () - "Parse radio target at point. + "Parse radio target at point, if any. -Return a list whose CAR is `radio-target' and CDR a plist with -`:begin', `:end', `:contents-begin', `:contents-end', `:value' -and `:post-blank' as keywords. +When at a radio target, return a list whose car is `radio-target' +and cdr a plist with `:begin', `:end', `:contents-begin', +`:contents-end', `:value' and `:post-blank' as keywords. +Otherwise, return nil. Assume point is at the radio target." (save-excursion - (looking-at org-radio-target-regexp) - (let ((begin (point)) - (contents-begin (match-beginning 1)) - (contents-end (match-end 1)) - (value (org-match-string-no-properties 1)) - (post-blank (progn (goto-char (match-end 0)) - (skip-chars-forward " \t"))) - (end (point))) - (list 'radio-target - (list :begin begin - :end end - :contents-begin contents-begin - :contents-end contents-end - :post-blank post-blank - :value value))))) - -(defun org-element-radio-target-interpreter (target contents) - "Interpret TARGET object as Org syntax. + (when (looking-at org-radio-target-regexp) + (let ((begin (point)) + (contents-begin (match-beginning 1)) + (contents-end (match-end 1)) + (value (match-string-no-properties 1)) + (post-blank (progn (goto-char (match-end 0)) + (skip-chars-forward " \t"))) + (end (point))) + (list 'radio-target + (list :begin begin + :end end + :contents-begin contents-begin + :contents-end contents-end + :post-blank post-blank + :value value)))))) + +(defun org-element-radio-target-interpreter (_ contents) + "Interpret target object as Org syntax. CONTENTS is the contents of the object." (concat "<<<" contents ">>>")) -(defun org-element-radio-target-successor () - "Search for the next radio-target object. - -Return value is a cons cell whose CAR is `radio-target' and CDR -is beginning position." - (save-excursion - (when (re-search-forward org-radio-target-regexp nil t) - (cons 'radio-target (match-beginning 0))))) - ;;;; Statistics Cookie (defun org-element-statistics-cookie-parser () - "Parse statistics cookie at point. + "Parse statistics cookie at point, if any. -Return a list whose CAR is `statistics-cookie', and CDR a plist -with `:begin', `:end', `:value' and `:post-blank' keywords. +When at a statistics cookie, return a list whose car is +`statistics-cookie', and cdr a plist with `:begin', `:end', +`:value' and `:post-blank' keywords. Otherwise, return nil. Assume point is at the beginning of the statistics-cookie." (save-excursion - (looking-at "\\[[0-9]*\\(%\\|/[0-9]*\\)\\]") - (let* ((begin (point)) - (value (buffer-substring-no-properties - (match-beginning 0) (match-end 0))) - (post-blank (progn (goto-char (match-end 0)) - (skip-chars-forward " \t"))) - (end (point))) - (list 'statistics-cookie - (list :begin begin - :end end - :value value - :post-blank post-blank))))) - -(defun org-element-statistics-cookie-interpreter (statistics-cookie contents) - "Interpret STATISTICS-COOKIE object as Org syntax. -CONTENTS is nil." + (when (looking-at "\\[[0-9]*\\(%\\|/[0-9]*\\)\\]") + (let* ((begin (point)) + (value (buffer-substring-no-properties + (match-beginning 0) (match-end 0))) + (post-blank (progn (goto-char (match-end 0)) + (skip-chars-forward " \t"))) + (end (point))) + (list 'statistics-cookie + (list :begin begin + :end end + :value value + :post-blank post-blank)))))) + +(defun org-element-statistics-cookie-interpreter (statistics-cookie _) + "Interpret STATISTICS-COOKIE object as Org syntax." (org-element-property :value statistics-cookie)) -(defun org-element-statistics-cookie-successor () - "Search for the next statistics cookie object. - -Return value is a cons cell whose CAR is `statistics-cookie' and -CDR is beginning position." - (save-excursion - (when (re-search-forward "\\[[0-9]*\\(%\\|/[0-9]*\\)\\]" nil t) - (cons 'statistics-cookie (match-beginning 0))))) - ;;;; Strike-Through (defun org-element-strike-through-parser () - "Parse strike-through object at point. + "Parse strike-through object at point, if any. -Return a list whose CAR is `strike-through' and CDR is a plist -with `:begin', `:end', `:contents-begin' and `:contents-end' and -`:post-blank' keywords. +When at a strike-through object, return a list whose car is +`strike-through' and cdr is a plist with `:begin', `:end', +`:contents-begin' and `:contents-end' and `:post-blank' keywords. +Otherwise, return nil. Assume point is at the first plus sign marker." (save-excursion (unless (bolp) (backward-char 1)) - (looking-at org-emph-re) - (let ((begin (match-beginning 2)) - (contents-begin (match-beginning 4)) - (contents-end (match-end 4)) - (post-blank (progn (goto-char (match-end 2)) - (skip-chars-forward " \t"))) - (end (point))) - (list 'strike-through - (list :begin begin - :end end - :contents-begin contents-begin - :contents-end contents-end - :post-blank post-blank))))) - -(defun org-element-strike-through-interpreter (strike-through contents) - "Interpret STRIKE-THROUGH object as Org syntax. + (when (looking-at org-emph-re) + (let ((begin (match-beginning 2)) + (contents-begin (match-beginning 4)) + (contents-end (match-end 4)) + (post-blank (progn (goto-char (match-end 2)) + (skip-chars-forward " \t"))) + (end (point))) + (list 'strike-through + (list :begin begin + :end end + :contents-begin contents-begin + :contents-end contents-end + :post-blank post-blank)))))) + +(defun org-element-strike-through-interpreter (_ contents) + "Interpret strike-through object as Org syntax. CONTENTS is the contents of the object." (format "+%s+" contents)) @@ -3378,32 +3339,32 @@ CONTENTS is the contents of the object." ;;;; Subscript (defun org-element-subscript-parser () - "Parse subscript at point. + "Parse subscript at point, if any. -Return a list whose CAR is `subscript' and CDR a plist with -`:begin', `:end', `:contents-begin', `:contents-end', -`:use-brackets-p' and `:post-blank' as keywords. +When at a subscript object, return a list whose car is +`subscript' and cdr a plist with `:begin', `:end', +`:contents-begin', `:contents-end', `:use-brackets-p' and +`:post-blank' as keywords. Otherwise, return nil. Assume point is at the underscore." (save-excursion (unless (bolp) (backward-char)) - (let ((bracketsp (if (looking-at org-match-substring-with-braces-regexp) - t - (not (looking-at org-match-substring-regexp)))) - (begin (match-beginning 2)) - (contents-begin (or (match-beginning 5) - (match-beginning 3))) - (contents-end (or (match-end 5) (match-end 3))) - (post-blank (progn (goto-char (match-end 0)) - (skip-chars-forward " \t"))) - (end (point))) - (list 'subscript - (list :begin begin - :end end - :use-brackets-p bracketsp - :contents-begin contents-begin - :contents-end contents-end - :post-blank post-blank))))) + (when (looking-at org-match-substring-regexp) + (let ((bracketsp (match-beginning 4)) + (begin (match-beginning 2)) + (contents-begin (or (match-beginning 4) + (match-beginning 3))) + (contents-end (or (match-end 4) (match-end 3))) + (post-blank (progn (goto-char (match-end 0)) + (skip-chars-forward " \t"))) + (end (point))) + (list 'subscript + (list :begin begin + :end end + :use-brackets-p bracketsp + :contents-begin contents-begin + :contents-end contents-end + :post-blank post-blank)))))) (defun org-element-subscript-interpreter (subscript contents) "Interpret SUBSCRIPT object as Org syntax. @@ -3412,46 +3373,36 @@ CONTENTS is the contents of the object." (if (org-element-property :use-brackets-p subscript) "_{%s}" "_%s") contents)) -(defun org-element-sub/superscript-successor () - "Search for the next sub/superscript object. - -Return value is a cons cell whose CAR is either `subscript' or -`superscript' and CDR is beginning position." - (save-excursion - (unless (bolp) (backward-char)) - (when (re-search-forward org-match-substring-regexp nil t) - (cons (if (string= (match-string 2) "_") 'subscript 'superscript) - (match-beginning 2))))) - ;;;; Superscript (defun org-element-superscript-parser () - "Parse superscript at point. + "Parse superscript at point, if any. -Return a list whose CAR is `superscript' and CDR a plist with -`:begin', `:end', `:contents-begin', `:contents-end', -`:use-brackets-p' and `:post-blank' as keywords. +When at a superscript object, return a list whose car is +`superscript' and cdr a plist with `:begin', `:end', +`:contents-begin', `:contents-end', `:use-brackets-p' and +`:post-blank' as keywords. Otherwise, return nil. Assume point is at the caret." (save-excursion (unless (bolp) (backward-char)) - (let ((bracketsp (if (looking-at org-match-substring-with-braces-regexp) t - (not (looking-at org-match-substring-regexp)))) - (begin (match-beginning 2)) - (contents-begin (or (match-beginning 5) - (match-beginning 3))) - (contents-end (or (match-end 5) (match-end 3))) - (post-blank (progn (goto-char (match-end 0)) - (skip-chars-forward " \t"))) - (end (point))) - (list 'superscript - (list :begin begin - :end end - :use-brackets-p bracketsp - :contents-begin contents-begin - :contents-end contents-end - :post-blank post-blank))))) + (when (looking-at org-match-substring-regexp) + (let ((bracketsp (match-beginning 4)) + (begin (match-beginning 2)) + (contents-begin (or (match-beginning 4) + (match-beginning 3))) + (contents-end (or (match-end 4) (match-end 3))) + (post-blank (progn (goto-char (match-end 0)) + (skip-chars-forward " \t"))) + (end (point))) + (list 'superscript + (list :begin begin + :end end + :use-brackets-p bracketsp + :contents-begin contents-begin + :contents-end contents-end + :post-blank post-blank)))))) (defun org-element-superscript-interpreter (superscript contents) "Interpret SUPERSCRIPT object as Org syntax. @@ -3465,8 +3416,7 @@ CONTENTS is the contents of the object." (defun org-element-table-cell-parser () "Parse table cell at point. - -Return a list whose CAR is `table-cell' and CDR is a plist +Return a list whose car is `table-cell' and cdr is a plist containing `:begin', `:end', `:contents-begin', `:contents-end' and `:post-blank' keywords." (looking-at "[ \t]*\\(.*?\\)[ \t]*\\(?:|\\|$\\)") @@ -3481,299 +3431,275 @@ and `:post-blank' keywords." :contents-end contents-end :post-blank 0)))) -(defun org-element-table-cell-interpreter (table-cell contents) - "Interpret TABLE-CELL element as Org syntax. +(defun org-element-table-cell-interpreter (_ contents) + "Interpret table-cell element as Org syntax. CONTENTS is the contents of the cell, or nil." (concat " " contents " |")) -(defun org-element-table-cell-successor () - "Search for the next table-cell object. - -Return value is a cons cell whose CAR is `table-cell' and CDR is -beginning position." - (when (looking-at "[ \t]*.*?[ \t]*\\(|\\|$\\)") (cons 'table-cell (point)))) - ;;;; Target (defun org-element-target-parser () - "Parse target at point. + "Parse target at point, if any. -Return a list whose CAR is `target' and CDR a plist with -`:begin', `:end', `:value' and `:post-blank' as keywords. +When at a target, return a list whose car is `target' and cdr +a plist with `:begin', `:end', `:value' and `:post-blank' as +keywords. Otherwise, return nil. Assume point is at the target." (save-excursion - (looking-at org-target-regexp) - (let ((begin (point)) - (value (org-match-string-no-properties 1)) - (post-blank (progn (goto-char (match-end 0)) - (skip-chars-forward " \t"))) - (end (point))) - (list 'target - (list :begin begin - :end end - :value value - :post-blank post-blank))))) - -(defun org-element-target-interpreter (target contents) - "Interpret TARGET object as Org syntax. -CONTENTS is nil." + (when (looking-at org-target-regexp) + (let ((begin (point)) + (value (match-string-no-properties 1)) + (post-blank (progn (goto-char (match-end 0)) + (skip-chars-forward " \t"))) + (end (point))) + (list 'target + (list :begin begin + :end end + :value value + :post-blank post-blank)))))) + +(defun org-element-target-interpreter (target _) + "Interpret TARGET object as Org syntax." (format "<<%s>>" (org-element-property :value target))) -(defun org-element-target-successor () - "Search for the next target object. - -Return value is a cons cell whose CAR is `target' and CDR is -beginning position." - (save-excursion - (when (re-search-forward org-target-regexp nil t) - (cons 'target (match-beginning 0))))) - ;;;; Timestamp +(defconst org-element--timestamp-regexp + (concat org-ts-regexp-both + "\\|" + "\\(?:<[0-9]+-[0-9]+-[0-9]+[^>\n]+?\\+[0-9]+[dwmy]>\\)" + "\\|" + "\\(?:<%%\\(?:([^>\n]+)\\)>\\)") + "Regexp matching any timestamp type object.") + (defun org-element-timestamp-parser () - "Parse time stamp at point. + "Parse time stamp at point, if any. -Return a list whose CAR is `timestamp', and CDR a plist with -`:type', `:raw-value', `:year-start', `:month-start', -`:day-start', `:hour-start', `:minute-start', `:year-end', -`:month-end', `:day-end', `:hour-end', `:minute-end', -`:repeater-type', `:repeater-value', `:repeater-unit', -`:warning-type', `:warning-value', `:warning-unit', `:begin', -`:end' and `:post-blank' keywords. +When at a time stamp, return a list whose car is `timestamp', and +cdr a plist with `:type', `:raw-value', `:year-start', +`:month-start', `:day-start', `:hour-start', `:minute-start', +`:year-end', `:month-end', `:day-end', `:hour-end', +`:minute-end', `:repeater-type', `:repeater-value', +`:repeater-unit', `:warning-type', `:warning-value', +`:warning-unit', `:begin', `:end' and `:post-blank' keywords. +Otherwise, return nil. Assume point is at the beginning of the timestamp." - (save-excursion - (let* ((begin (point)) - (activep (eq (char-after) ?<)) - (raw-value - (progn - (looking-at "\\([<[]\\(%%\\)?.*?\\)[]>]\\(?:--\\([<[].*?[]>]\\)\\)?") - (match-string-no-properties 0))) - (date-start (match-string-no-properties 1)) - (date-end (match-string 3)) - (diaryp (match-beginning 2)) - (post-blank (progn (goto-char (match-end 0)) - (skip-chars-forward " \t"))) - (end (point)) - (time-range - (and (not diaryp) - (string-match - "[012]?[0-9]:[0-5][0-9]\\(-\\([012]?[0-9]\\):\\([0-5][0-9]\\)\\)" - date-start) - (cons (string-to-number (match-string 2 date-start)) - (string-to-number (match-string 3 date-start))))) - (type (cond (diaryp 'diary) - ((and activep (or date-end time-range)) 'active-range) - (activep 'active) - ((or date-end time-range) 'inactive-range) - (t 'inactive))) - (repeater-props - (and (not diaryp) - (string-match "\\([.+]?\\+\\)\\([0-9]+\\)\\([hdwmy]\\)" - raw-value) - (list - :repeater-type - (let ((type (match-string 1 raw-value))) - (cond ((equal "++" type) 'catch-up) - ((equal ".+" type) 'restart) - (t 'cumulate))) - :repeater-value (string-to-number (match-string 2 raw-value)) - :repeater-unit - (case (string-to-char (match-string 3 raw-value)) - (?h 'hour) (?d 'day) (?w 'week) (?m 'month) (t 'year))))) - (warning-props - (and (not diaryp) - (string-match "\\(-\\)?-\\([0-9]+\\)\\([hdwmy]\\)" raw-value) - (list - :warning-type (if (match-string 1 raw-value) 'first 'all) - :warning-value (string-to-number (match-string 2 raw-value)) - :warning-unit - (case (string-to-char (match-string 3 raw-value)) - (?h 'hour) (?d 'day) (?w 'week) (?m 'month) (t 'year))))) - year-start month-start day-start hour-start minute-start year-end - month-end day-end hour-end minute-end) - ;; Parse date-start. - (unless diaryp - (let ((date (org-parse-time-string date-start t))) - (setq year-start (nth 5 date) - month-start (nth 4 date) - day-start (nth 3 date) - hour-start (nth 2 date) - minute-start (nth 1 date)))) - ;; Compute date-end. It can be provided directly in time-stamp, - ;; or extracted from time range. Otherwise, it defaults to the - ;; same values as date-start. - (unless diaryp - (let ((date (and date-end (org-parse-time-string date-end t)))) - (setq year-end (or (nth 5 date) year-start) - month-end (or (nth 4 date) month-start) - day-end (or (nth 3 date) day-start) - hour-end (or (nth 2 date) (car time-range) hour-start) - minute-end (or (nth 1 date) (cdr time-range) minute-start)))) - (list 'timestamp - (nconc (list :type type - :raw-value raw-value - :year-start year-start - :month-start month-start - :day-start day-start - :hour-start hour-start - :minute-start minute-start - :year-end year-end - :month-end month-end - :day-end day-end - :hour-end hour-end - :minute-end minute-end - :begin begin - :end end - :post-blank post-blank) - repeater-props - warning-props))))) - -(defun org-element-timestamp-interpreter (timestamp contents) - "Interpret TIMESTAMP object as Org syntax. -CONTENTS is nil." - ;; Use `:raw-value' if specified. - (or (org-element-property :raw-value timestamp) - ;; Otherwise, build timestamp string. - (let* ((repeat-string - (concat - (case (org-element-property :repeater-type timestamp) - (cumulate "+") (catch-up "++") (restart ".+")) - (let ((val (org-element-property :repeater-value timestamp))) - (and val (number-to-string val))) - (case (org-element-property :repeater-unit timestamp) - (hour "h") (day "d") (week "w") (month "m") (year "y")))) - (warning-string - (concat - (case (org-element-property :warning-type timestamp) - (first "--") - (all "-")) - (let ((val (org-element-property :warning-value timestamp))) - (and val (number-to-string val))) - (case (org-element-property :warning-unit timestamp) - (hour "h") (day "d") (week "w") (month "m") (year "y")))) - (build-ts-string - ;; Build an Org timestamp string from TIME. ACTIVEP is - ;; non-nil when time stamp is active. If WITH-TIME-P is - ;; non-nil, add a time part. HOUR-END and MINUTE-END - ;; specify a time range in the timestamp. REPEAT-STRING - ;; is the repeater string, if any. - (lambda (time activep &optional with-time-p hour-end minute-end) - (let ((ts (format-time-string - (funcall (if with-time-p 'cdr 'car) - org-time-stamp-formats) - time))) - (when (and hour-end minute-end) - (string-match "[012]?[0-9]:[0-5][0-9]" ts) - (setq ts - (replace-match - (format "\\&-%02d:%02d" hour-end minute-end) - nil nil ts))) - (unless activep (setq ts (format "[%s]" (substring ts 1 -1)))) - (dolist (s (list repeat-string warning-string)) - (when (org-string-nw-p s) - (setq ts (concat (substring ts 0 -1) - " " - s - (substring ts -1))))) - ;; Return value. - ts))) - (type (org-element-property :type timestamp))) - (case type - ((active inactive) - (let* ((minute-start (org-element-property :minute-start timestamp)) - (minute-end (org-element-property :minute-end timestamp)) - (hour-start (org-element-property :hour-start timestamp)) - (hour-end (org-element-property :hour-end timestamp)) - (time-range-p (and hour-start hour-end minute-start minute-end - (or (/= hour-start hour-end) - (/= minute-start minute-end))))) - (funcall - build-ts-string - (encode-time 0 - (or minute-start 0) - (or hour-start 0) - (org-element-property :day-start timestamp) - (org-element-property :month-start timestamp) - (org-element-property :year-start timestamp)) - (eq type 'active) - (and hour-start minute-start) - (and time-range-p hour-end) - (and time-range-p minute-end)))) - ((active-range inactive-range) - (let ((minute-start (org-element-property :minute-start timestamp)) - (minute-end (org-element-property :minute-end timestamp)) - (hour-start (org-element-property :hour-start timestamp)) - (hour-end (org-element-property :hour-end timestamp))) - (concat - (funcall - build-ts-string (encode-time - 0 - (or minute-start 0) - (or hour-start 0) - (org-element-property :day-start timestamp) - (org-element-property :month-start timestamp) - (org-element-property :year-start timestamp)) - (eq type 'active-range) - (and hour-start minute-start)) - "--" - (funcall build-ts-string - (encode-time 0 - (or minute-end 0) - (or hour-end 0) - (org-element-property :day-end timestamp) - (org-element-property :month-end timestamp) - (org-element-property :year-end timestamp)) - (eq type 'active-range) - (and hour-end minute-end))))))))) - -(defun org-element-timestamp-successor () - "Search for the next timestamp object. - -Return value is a cons cell whose CAR is `timestamp' and CDR is -beginning position." - (save-excursion - (when (re-search-forward - (concat org-ts-regexp-both - "\\|" - "\\(?:<[0-9]+-[0-9]+-[0-9]+[^>\n]+?\\+[0-9]+[dwmy]>\\)" - "\\|" - "\\(?:<%%\\(?:([^>\n]+)\\)>\\)") - nil t) - (cons 'timestamp (match-beginning 0))))) + (when (looking-at-p org-element--timestamp-regexp) + (save-excursion + (let* ((begin (point)) + (activep (eq (char-after) ?<)) + (raw-value + (progn + (looking-at "\\([<[]\\(%%\\)?.*?\\)[]>]\\(?:--\\([<[].*?[]>]\\)\\)?") + (match-string-no-properties 0))) + (date-start (match-string-no-properties 1)) + (date-end (match-string 3)) + (diaryp (match-beginning 2)) + (post-blank (progn (goto-char (match-end 0)) + (skip-chars-forward " \t"))) + (end (point)) + (time-range + (and (not diaryp) + (string-match + "[012]?[0-9]:[0-5][0-9]\\(-\\([012]?[0-9]\\):\\([0-5][0-9]\\)\\)" + date-start) + (cons (string-to-number (match-string 2 date-start)) + (string-to-number (match-string 3 date-start))))) + (type (cond (diaryp 'diary) + ((and activep (or date-end time-range)) 'active-range) + (activep 'active) + ((or date-end time-range) 'inactive-range) + (t 'inactive))) + (repeater-props + (and (not diaryp) + (string-match "\\([.+]?\\+\\)\\([0-9]+\\)\\([hdwmy]\\)" + raw-value) + (list + :repeater-type + (let ((type (match-string 1 raw-value))) + (cond ((equal "++" type) 'catch-up) + ((equal ".+" type) 'restart) + (t 'cumulate))) + :repeater-value (string-to-number (match-string 2 raw-value)) + :repeater-unit + (pcase (string-to-char (match-string 3 raw-value)) + (?h 'hour) (?d 'day) (?w 'week) (?m 'month) (_ 'year))))) + (warning-props + (and (not diaryp) + (string-match "\\(-\\)?-\\([0-9]+\\)\\([hdwmy]\\)" raw-value) + (list + :warning-type (if (match-string 1 raw-value) 'first 'all) + :warning-value (string-to-number (match-string 2 raw-value)) + :warning-unit + (pcase (string-to-char (match-string 3 raw-value)) + (?h 'hour) (?d 'day) (?w 'week) (?m 'month) (_ 'year))))) + year-start month-start day-start hour-start minute-start year-end + month-end day-end hour-end minute-end) + ;; Parse date-start. + (unless diaryp + (let ((date (org-parse-time-string date-start t))) + (setq year-start (nth 5 date) + month-start (nth 4 date) + day-start (nth 3 date) + hour-start (nth 2 date) + minute-start (nth 1 date)))) + ;; Compute date-end. It can be provided directly in time-stamp, + ;; or extracted from time range. Otherwise, it defaults to the + ;; same values as date-start. + (unless diaryp + (let ((date (and date-end (org-parse-time-string date-end t)))) + (setq year-end (or (nth 5 date) year-start) + month-end (or (nth 4 date) month-start) + day-end (or (nth 3 date) day-start) + hour-end (or (nth 2 date) (car time-range) hour-start) + minute-end (or (nth 1 date) (cdr time-range) minute-start)))) + (list 'timestamp + (nconc (list :type type + :raw-value raw-value + :year-start year-start + :month-start month-start + :day-start day-start + :hour-start hour-start + :minute-start minute-start + :year-end year-end + :month-end month-end + :day-end day-end + :hour-end hour-end + :minute-end minute-end + :begin begin + :end end + :post-blank post-blank) + repeater-props + warning-props)))))) + +(defun org-element-timestamp-interpreter (timestamp _) + "Interpret TIMESTAMP object as Org syntax." + (let* ((repeat-string + (concat + (pcase (org-element-property :repeater-type timestamp) + (`cumulate "+") (`catch-up "++") (`restart ".+")) + (let ((val (org-element-property :repeater-value timestamp))) + (and val (number-to-string val))) + (pcase (org-element-property :repeater-unit timestamp) + (`hour "h") (`day "d") (`week "w") (`month "m") (`year "y")))) + (warning-string + (concat + (pcase (org-element-property :warning-type timestamp) + (`first "--") (`all "-")) + (let ((val (org-element-property :warning-value timestamp))) + (and val (number-to-string val))) + (pcase (org-element-property :warning-unit timestamp) + (`hour "h") (`day "d") (`week "w") (`month "m") (`year "y")))) + (build-ts-string + ;; Build an Org timestamp string from TIME. ACTIVEP is + ;; non-nil when time stamp is active. If WITH-TIME-P is + ;; non-nil, add a time part. HOUR-END and MINUTE-END + ;; specify a time range in the timestamp. REPEAT-STRING is + ;; the repeater string, if any. + (lambda (time activep &optional with-time-p hour-end minute-end) + (let ((ts (format-time-string + (funcall (if with-time-p #'cdr #'car) + org-time-stamp-formats) + time))) + (when (and hour-end minute-end) + (string-match "[012]?[0-9]:[0-5][0-9]" ts) + (setq ts + (replace-match + (format "\\&-%02d:%02d" hour-end minute-end) + nil nil ts))) + (unless activep (setq ts (format "[%s]" (substring ts 1 -1)))) + (dolist (s (list repeat-string warning-string)) + (when (org-string-nw-p s) + (setq ts (concat (substring ts 0 -1) + " " + s + (substring ts -1))))) + ;; Return value. + ts))) + (type (org-element-property :type timestamp))) + (pcase type + ((or `active `inactive) + (let* ((minute-start (org-element-property :minute-start timestamp)) + (minute-end (org-element-property :minute-end timestamp)) + (hour-start (org-element-property :hour-start timestamp)) + (hour-end (org-element-property :hour-end timestamp)) + (time-range-p (and hour-start hour-end minute-start minute-end + (or (/= hour-start hour-end) + (/= minute-start minute-end))))) + (funcall + build-ts-string + (encode-time 0 + (or minute-start 0) + (or hour-start 0) + (org-element-property :day-start timestamp) + (org-element-property :month-start timestamp) + (org-element-property :year-start timestamp)) + (eq type 'active) + (and hour-start minute-start) + (and time-range-p hour-end) + (and time-range-p minute-end)))) + ((or `active-range `inactive-range) + (let ((minute-start (org-element-property :minute-start timestamp)) + (minute-end (org-element-property :minute-end timestamp)) + (hour-start (org-element-property :hour-start timestamp)) + (hour-end (org-element-property :hour-end timestamp))) + (concat + (funcall + build-ts-string (encode-time + 0 + (or minute-start 0) + (or hour-start 0) + (org-element-property :day-start timestamp) + (org-element-property :month-start timestamp) + (org-element-property :year-start timestamp)) + (eq type 'active-range) + (and hour-start minute-start)) + "--" + (funcall build-ts-string + (encode-time 0 + (or minute-end 0) + (or hour-end 0) + (org-element-property :day-end timestamp) + (org-element-property :month-end timestamp) + (org-element-property :year-end timestamp)) + (eq type 'active-range) + (and hour-end minute-end))))) + (_ (org-element-property :raw-value timestamp))))) ;;;; Underline (defun org-element-underline-parser () - "Parse underline object at point. + "Parse underline object at point, if any. -Return a list whose CAR is `underline' and CDR is a plist with -`:begin', `:end', `:contents-begin' and `:contents-end' and -`:post-blank' keywords. +When at an underline object, return a list whose car is +`underline' and cdr is a plist with `:begin', `:end', +`:contents-begin' and `:contents-end' and `:post-blank' keywords. +Otherwise, return nil. Assume point is at the first underscore marker." (save-excursion (unless (bolp) (backward-char 1)) - (looking-at org-emph-re) - (let ((begin (match-beginning 2)) - (contents-begin (match-beginning 4)) - (contents-end (match-end 4)) - (post-blank (progn (goto-char (match-end 2)) - (skip-chars-forward " \t"))) - (end (point))) - (list 'underline - (list :begin begin - :end end - :contents-begin contents-begin - :contents-end contents-end - :post-blank post-blank))))) - -(defun org-element-underline-interpreter (underline contents) - "Interpret UNDERLINE object as Org syntax. + (when (looking-at org-emph-re) + (let ((begin (match-beginning 2)) + (contents-begin (match-beginning 4)) + (contents-end (match-end 4)) + (post-blank (progn (goto-char (match-end 2)) + (skip-chars-forward " \t"))) + (end (point))) + (list 'underline + (list :begin begin + :end end + :contents-begin contents-begin + :contents-end contents-end + :post-blank post-blank)))))) + +(defun org-element-underline-interpreter (_ contents) + "Interpret underline object as Org syntax. CONTENTS is the contents of the object." (format "_%s_" contents)) @@ -3781,29 +3707,29 @@ CONTENTS is the contents of the object." ;;;; Verbatim (defun org-element-verbatim-parser () - "Parse verbatim object at point. + "Parse verbatim object at point, if any. -Return a list whose CAR is `verbatim' and CDR is a plist with -`:value', `:begin', `:end' and `:post-blank' keywords. +When at a verbatim object, return a list whose car is `verbatim' +and cdr is a plist with `:value', `:begin', `:end' and +`:post-blank' keywords. Otherwise, return nil. Assume point is at the first equal sign marker." (save-excursion (unless (bolp) (backward-char 1)) - (looking-at org-emph-re) - (let ((begin (match-beginning 2)) - (value (org-match-string-no-properties 4)) - (post-blank (progn (goto-char (match-end 2)) - (skip-chars-forward " \t"))) - (end (point))) - (list 'verbatim - (list :value value - :begin begin - :end end - :post-blank post-blank))))) - -(defun org-element-verbatim-interpreter (verbatim contents) - "Interpret VERBATIM object as Org syntax. -CONTENTS is nil." + (when (looking-at org-verbatim-re) + (let ((begin (match-beginning 2)) + (value (match-string-no-properties 4)) + (post-blank (progn (goto-char (match-end 2)) + (skip-chars-forward " \t"))) + (end (point))) + (list 'verbatim + (list :value value + :begin begin + :end end + :post-blank post-blank)))))) + +(defun org-element-verbatim-interpreter (verbatim _) + "Interpret VERBATIM object as Org syntax." (format "=%s=" (org-element-property :value verbatim))) @@ -3818,10 +3744,9 @@ CONTENTS is nil." ;; 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', `quote-section', `section' and `table-row'. +;; `node-property', `section' and `table-row'. -(defun org-element--current-element - (limit &optional granularity special structure) +(defun org-element--current-element (limit &optional granularity mode structure) "Parse the element starting at point. Return value is a list like (TYPE PROPS) where TYPE is the type @@ -3838,12 +3763,12 @@ recursion. Allowed values are `headline', `greater-element', nil), secondary values will not be parsed, since they only contain objects. -Optional argument SPECIAL, when non-nil, can be either -`first-section', `item', `node-property', `quote-section', -`section', and `table-row'. +Optional argument MODE, when non-nil, can be either +`first-section', `section', `planning', `item', `node-property' +and `table-row'. -If STRUCTURE isn't provided but SPECIAL is set to `item', it will -be computed. +If STRUCTURE isn't provided but MODE is set to `item', it will be +computed. This function assumes point is always at the beginning of the element it has to parse." @@ -3855,30 +3780,37 @@ element it has to parse." (raw-secondary-p (and granularity (not (eq granularity 'object))))) (cond ;; Item. - ((eq special 'item) + ((eq mode 'item) (org-element-item-parser limit structure raw-secondary-p)) ;; Table Row. - ((eq special 'table-row) (org-element-table-row-parser limit)) + ((eq mode 'table-row) (org-element-table-row-parser limit)) ;; Node Property. - ((eq special 'node-property) (org-element-node-property-parser limit)) + ((eq mode 'node-property) (org-element-node-property-parser limit)) ;; Headline. ((org-with-limited-levels (org-at-heading-p)) (org-element-headline-parser limit raw-secondary-p)) ;; Sections (must be checked after headline). - ((eq special 'section) (org-element-section-parser limit)) - ((eq special 'quote-section) (org-element-quote-section-parser limit)) - ((eq special 'first-section) + ((eq mode 'section) (org-element-section-parser limit)) + ((eq mode 'first-section) (org-element-section-parser (or (save-excursion (org-with-limited-levels (outline-next-heading))) 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)))) + (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 ;; a footnote definition: next item is always a paragraph. ((not (bolp)) (org-element-paragraph-parser limit (list (point)))) - ;; Planning and Clock. - ((looking-at org-planning-or-clock-line-re) - (if (equal (match-string 1) org-clock-string) - (org-element-clock-parser limit) - (org-element-planning-parser limit))) + ;; Clock. + ((looking-at org-clock-line-re) (org-element-clock-parser limit)) ;; Inlinetask. ((org-at-heading-p) (org-element-inlinetask-parser limit raw-secondary-p)) @@ -3891,13 +3823,11 @@ element it has to parse." (goto-char (car affiliated)) (org-element-keyword-parser limit nil)) ;; LaTeX Environment. - ((looking-at "[ \t]*\\\\begin{\\([A-Za-z0-9]+\\*?\\)}\\(\\[.*?\\]\\|{.*?}\\)*[ \t]*$") + ((looking-at org-element--latex-begin-environment) (org-element-latex-environment-parser limit affiliated)) ;; Drawer and Property Drawer. ((looking-at org-drawer-regexp) - (if (equal (match-string 1) "PROPERTIES") - (org-element-property-drawer-parser limit affiliated) - (org-element-drawer-parser limit affiliated))) + (org-element-drawer-parser limit affiliated)) ;; Fixed Width ((looking-at "[ \t]*:\\( \\|$\\)") (org-element-fixed-width-parser limit affiliated)) @@ -3905,27 +3835,35 @@ element it has to parse." ;; Keywords. ((looking-at "[ \t]*#") (goto-char (match-end 0)) - (cond ((looking-at "\\(?: \\|$\\)") - (beginning-of-line) - (org-element-comment-parser limit affiliated)) - ((looking-at "\\+BEGIN_\\(\\S-+\\)") - (beginning-of-line) - (let ((parser (assoc (upcase (match-string 1)) - org-element-block-name-alist))) - (if parser (funcall (cdr parser) limit affiliated) - (org-element-special-block-parser limit affiliated)))) - ((looking-at "\\+CALL:") - (beginning-of-line) - (org-element-babel-call-parser limit affiliated)) - ((looking-at "\\+BEGIN:? ") - (beginning-of-line) - (org-element-dynamic-block-parser limit affiliated)) - ((looking-at "\\+\\S-+:") - (beginning-of-line) - (org-element-keyword-parser limit affiliated)) - (t - (beginning-of-line) - (org-element-paragraph-parser limit affiliated)))) + (cond + ((looking-at "\\(?: \\|$\\)") + (beginning-of-line) + (org-element-comment-parser limit affiliated)) + ((looking-at "\\+BEGIN_\\(\\S-+\\)") + (beginning-of-line) + (funcall (pcase (upcase (match-string 1)) + ("CENTER" #'org-element-center-block-parser) + ("COMMENT" #'org-element-comment-block-parser) + ("EXAMPLE" #'org-element-example-block-parser) + ("EXPORT" #'org-element-export-block-parser) + ("QUOTE" #'org-element-quote-block-parser) + ("SRC" #'org-element-src-block-parser) + ("VERSE" #'org-element-verse-block-parser) + (_ #'org-element-special-block-parser)) + limit + affiliated)) + ((looking-at "\\+CALL:") + (beginning-of-line) + (org-element-babel-call-parser limit affiliated)) + ((looking-at "\\+BEGIN:? ") + (beginning-of-line) + (org-element-dynamic-block-parser limit affiliated)) + ((looking-at "\\+\\S-+:") + (beginning-of-line) + (org-element-keyword-parser limit affiliated)) + (t + (beginning-of-line) + (org-element-paragraph-parser limit affiliated)))) ;; Footnote Definition. ((looking-at org-footnote-definition-re) (org-element-footnote-definition-parser limit affiliated)) @@ -3936,7 +3874,8 @@ element it has to parse." ((looking-at "%%(") (org-element-diary-sexp-parser limit affiliated)) ;; Table. - ((org-at-table-p t) (org-element-table-parser limit affiliated)) + ((looking-at "[ \t]*\\(|\\|\\+\\(-+\\+\\)+[ \t]*$\\)") + (org-element-table-parser limit affiliated)) ;; List. ((looking-at (org-item-re)) (org-element-plain-list-parser @@ -3980,7 +3919,7 @@ position of point and CDR is nil." (save-match-data (org-trim (buffer-substring-no-properties - (match-end 0) (point-at-eol))))) + (match-end 0) (line-end-position))))) ;; PARSEDP is non-nil when keyword should have its ;; value parsed. (parsedp (member kwd org-element-parsed-keywords)) @@ -3989,14 +3928,20 @@ position of point and CDR is nil." (dualp (member kwd org-element-dual-keywords)) (dual-value (and dualp - (let ((sec (org-match-string-no-properties 2))) + (let ((sec (match-string-no-properties 2))) (if (or (not sec) (not parsedp)) sec - (org-element-parse-secondary-string sec restrict))))) + (save-match-data + (org-element--parse-objects + (match-beginning 2) (match-end 2) nil restrict)))))) ;; Attribute a property name to KWD. (kwd-sym (and kwd (intern (concat ":" (downcase kwd)))))) ;; Now set final shape for VALUE. (when parsedp - (setq value (org-element-parse-secondary-string value restrict))) + (setq value + (org-element--parse-objects + (match-end 0) + (progn (end-of-line) (skip-chars-backward " \t") (point)) + nil restrict))) (when dualp (setq value (and (or value dual-value) (cons value dual-value)))) (when (or (member kwd org-element-multiple-keywords) @@ -4037,7 +3982,7 @@ Optional argument GRANULARITY determines the depth of the recursion. It can be set to the following symbols: `headline' Only parse headlines. -`greater-element' Don't recurse into greater elements excepted +`greater-element' Don't recurse into greater elements except headlines and sections. Thus, elements parsed are the top-level ones. `element' Parse everything but objects and plain text. @@ -4046,7 +3991,7 @@ recursion. It can be set to the following symbols: When VISIBLE-ONLY is non-nil, don't parse contents of hidden elements. -An element or an objects is represented as a list with the +An element or object is represented as a list with the pattern (TYPE PROPERTIES CONTENTS), where : TYPE is a symbol describing the element or object. See @@ -4089,23 +4034,25 @@ looked after. Optional argument PARENT, when non-nil, is the element or object containing the secondary string. It is used to set correctly -`:parent' property within the string." - (let ((local-variables (buffer-local-variables))) - (with-temp-buffer - (dolist (v local-variables) - (ignore-errors - (if (symbolp v) (makunbound v) - (org-set-local (car v) (cdr v))))) - (insert string) - (restore-buffer-modified-p nil) - (let ((secondary (org-element--parse-objects - (point-min) (point-max) nil restriction))) - (when parent - (dolist (o secondary) (org-element-put-property o :parent parent))) - secondary)))) +`:parent' property within the string. + +If STRING is the empty string or nil, return nil." + (cond + ((not string) nil) + ((equal string "") nil) + (t (let ((local-variables (buffer-local-variables))) + (with-temp-buffer + (dolist (v local-variables) + (ignore-errors + (if (symbolp v) (makunbound v) + (set (make-local-variable (car v)) (cdr v))))) + (insert string) + (restore-buffer-modified-p nil) + (org-element--parse-objects + (point-min) (point-max) nil restriction parent)))))) (defun org-element-map - (data types fun &optional info first-match no-recursion with-affiliated) + (data types fun &optional info first-match no-recursion with-affiliated) "Map a function on selected elements or objects. DATA is a parse tree, an element, an object, a string, or a list @@ -4141,7 +4088,7 @@ Assuming TREE is a variable containing an Org buffer parse tree, the following example will return a flat list of all `src-block' and `example-block' elements in it: - (org-element-map tree \\='(example-block src-block) \\='identity) + (org-element-map tree \\='(example-block src-block) #\\='identity) The following snippet will find the first headline with a level of 1 and a \"phone\" tag, and will return its beginning position: @@ -4156,7 +4103,7 @@ of 1 and a \"phone\" tag, and will return its beginning position: The next example will return a flat list of all `plain-list' type elements in TREE that are not a sub-list themselves: - (org-element-map tree \\='plain-list \\='identity nil nil \\='plain-list) + (org-element-map tree \\='plain-list #\\='identity nil nil \\='plain-list) Eventually, this example will return a flat list of all `bold' type objects containing a `latex-snippet' type object, even @@ -4164,116 +4111,101 @@ looking into captions: (org-element-map tree \\='bold (lambda (b) - (and (org-element-map b \\='latex-snippet \\='identity nil t) b)) + (and (org-element-map b \\='latex-snippet #\\='identity nil t) b)) nil nil nil t)" ;; Ensure TYPES and NO-RECURSION are a list, even of one element. - (unless (listp types) (setq types (list types))) - (unless (listp no-recursion) (setq no-recursion (list no-recursion))) - ;; Recursion depth is determined by --CATEGORY. - (let* ((--category - (catch 'found - (let ((category 'greater-elements)) - (mapc (lambda (type) - (cond ((or (memq type org-element-all-objects) - (eq type 'plain-text)) - ;; If one object is found, the function - ;; has to recurse into every object. - (throw 'found 'objects)) - ((not (memq type org-element-greater-elements)) - ;; If one regular element is found, the - ;; function has to recurse, at least, - ;; into every element it encounters. - (and (not (eq category 'elements)) - (setq category 'elements))))) - types) - category))) - ;; Compute properties for affiliated keywords if necessary. - (--affiliated-alist - (and with-affiliated - (mapcar (lambda (kwd) - (cons kwd (intern (concat ":" (downcase kwd))))) - org-element-affiliated-keywords))) - --acc - --walk-tree - (--walk-tree - (function - (lambda (--data) - ;; Recursively walk DATA. INFO, if non-nil, is a plist - ;; holding contextual information. - (let ((--type (org-element-type --data))) - (cond - ((not --data)) - ;; Ignored element in an export context. - ((and info (memq --data (plist-get info :ignore-list)))) - ;; List of elements or objects. - ((not --type) (mapc --walk-tree --data)) - ;; Unconditionally enter parse trees. - ((eq --type 'org-data) - (mapc --walk-tree (org-element-contents --data))) - (t - ;; Check if TYPE is matching among TYPES. If so, - ;; apply FUN to --DATA and accumulate return value - ;; into --ACC (or exit if FIRST-MATCH is non-nil). - (when (memq --type types) - (let ((result (funcall fun --data))) - (cond ((not result)) - (first-match (throw '--map-first-match result)) - (t (push result --acc))))) - ;; If --DATA has a secondary string that can contain - ;; objects with their type among TYPES, look into it. - (when (and (eq --category 'objects) (not (stringp --data))) - (let ((sec-prop - (assq --type org-element-secondary-value-alist))) - (when sec-prop - (funcall --walk-tree - (org-element-property (cdr sec-prop) --data))))) - ;; If --DATA has any affiliated keywords and - ;; WITH-AFFILIATED is non-nil, look for objects in - ;; them. - (when (and with-affiliated - (eq --category 'objects) - (memq --type org-element-all-elements)) - (mapc (lambda (kwd-pair) - (let ((kwd (car kwd-pair)) - (value (org-element-property - (cdr kwd-pair) --data))) - ;; Pay attention to the type of value. - ;; Preserve order for multiple keywords. - (cond - ((not value)) - ((and (member kwd org-element-multiple-keywords) - (member kwd org-element-dual-keywords)) - (mapc (lambda (line) - (funcall --walk-tree (cdr line)) - (funcall --walk-tree (car line))) - (reverse value))) - ((member kwd org-element-multiple-keywords) - (mapc (lambda (line) (funcall --walk-tree line)) - (reverse value))) - ((member kwd org-element-dual-keywords) - (funcall --walk-tree (cdr value)) - (funcall --walk-tree (car value))) - (t (funcall --walk-tree value))))) - --affiliated-alist)) - ;; Determine if a recursion into --DATA is possible. - (cond - ;; --TYPE is explicitly removed from recursion. - ((memq --type no-recursion)) - ;; --DATA has no contents. - ((not (org-element-contents --data))) - ;; Looking for greater elements but --DATA is simply - ;; an element or an object. - ((and (eq --category 'greater-elements) - (not (memq --type org-element-greater-elements)))) - ;; Looking for elements but --DATA is an object. - ((and (eq --category 'elements) - (memq --type org-element-all-objects))) - ;; In any other case, map contents. - (t (mapc --walk-tree (org-element-contents --data))))))))))) - (catch '--map-first-match - (funcall --walk-tree data) - ;; Return value in a proper order. - (nreverse --acc)))) + (let* ((types (if (listp types) types (list types))) + (no-recursion (if (listp no-recursion) no-recursion + (list no-recursion))) + ;; Recursion depth is determined by --CATEGORY. + (--category + (catch :--found + (let ((category 'greater-elements) + (all-objects (cons 'plain-text org-element-all-objects))) + (dolist (type types category) + (cond ((memq type all-objects) + ;; If one object is found, the function has + ;; to recurse into every object. + (throw :--found 'objects)) + ((not (memq type org-element-greater-elements)) + ;; If one regular element is found, the + ;; function has to recurse, at least, into + ;; every element it encounters. + (and (not (eq category 'elements)) + (setq category 'elements)))))))) + --acc) + (letrec ((--walk-tree + (lambda (--data) + ;; Recursively walk DATA. INFO, if non-nil, is a plist + ;; holding contextual information. + (let ((--type (org-element-type --data))) + (cond + ((not --data)) + ;; Ignored element in an export context. + ((and info (memq --data (plist-get info :ignore-list)))) + ;; List of elements or objects. + ((not --type) (mapc --walk-tree --data)) + ;; Unconditionally enter parse trees. + ((eq --type 'org-data) + (mapc --walk-tree (org-element-contents --data))) + (t + ;; Check if TYPE is matching among TYPES. If so, + ;; apply FUN to --DATA and accumulate return value + ;; into --ACC (or exit if FIRST-MATCH is non-nil). + (when (memq --type types) + (let ((result (funcall fun --data))) + (cond ((not result)) + (first-match (throw :--map-first-match result)) + (t (push result --acc))))) + ;; If --DATA has a secondary string that can contain + ;; objects with their type among TYPES, look inside. + (when (and (eq --category 'objects) (not (stringp --data))) + (dolist (p (cdr (assq --type + org-element-secondary-value-alist))) + (funcall --walk-tree (org-element-property p --data)))) + ;; If --DATA has any parsed affiliated keywords and + ;; WITH-AFFILIATED is non-nil, look for objects in + ;; them. + (when (and with-affiliated + (eq --category 'objects) + (eq (org-element-class --data) 'element)) + (dolist (kwd-pair org-element--parsed-properties-alist) + (let ((kwd (car kwd-pair)) + (value (org-element-property (cdr kwd-pair) --data))) + ;; Pay attention to the type of parsed + ;; keyword. In particular, preserve order for + ;; multiple keywords. + (cond + ((not value)) + ((member kwd org-element-dual-keywords) + (if (member kwd org-element-multiple-keywords) + (dolist (line (reverse value)) + (funcall --walk-tree (cdr line)) + (funcall --walk-tree (car line))) + (funcall --walk-tree (cdr value)) + (funcall --walk-tree (car value)))) + ((member kwd org-element-multiple-keywords) + (mapc --walk-tree (reverse value))) + (t (funcall --walk-tree value)))))) + ;; Determine if a recursion into --DATA is possible. + (cond + ;; --TYPE is explicitly removed from recursion. + ((memq --type no-recursion)) + ;; --DATA has no contents. + ((not (org-element-contents --data))) + ;; Looking for greater elements but --DATA is + ;; simply an element or an object. + ((and (eq --category 'greater-elements) + (not (memq --type org-element-greater-elements)))) + ;; Looking for elements but --DATA is an object. + ((and (eq --category 'elements) + (eq (org-element-class --data) 'object))) + ;; In any other case, map contents. + (t (mapc --walk-tree (org-element-contents --data)))))))))) + (catch :--map-first-match + (funcall --walk-tree data) + ;; Return value in a proper order. + (nreverse --acc))))) (put 'org-element-map 'lisp-indent-function 2) ;; The following functions are internal parts of the parser. @@ -4282,24 +4214,38 @@ looking into captions: ;; level. ;; ;; The second one, `org-element--parse-objects' applies on all objects -;; of a paragraph or a secondary string. It uses -;; `org-element--get-next-object-candidates' to optimize the search of -;; the next object in the buffer. -;; -;; More precisely, that function looks for every allowed object type -;; first. Then, it discards failed searches, keeps further matches, -;; and searches again types matched behind point, for subsequent -;; calls. Thus, searching for a given type fails only once, and every -;; object is searched only once at top level (but sometimes more for -;; nested types). +;; of a paragraph or a secondary string. It calls +;; `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 + (pcase type + (`headline 'section) + (`inlinetask 'planning) + (`plain-list 'item) + (`property-drawer 'node-property) + (`section 'planning) + (`table 'table-row)) + (pcase type + (`item 'item) + (`node-property 'node-property) + (`planning 'property-drawer) + (`table-row 'table-row)))) (defun org-element--parse-elements - (beg end special structure granularity visible-only acc) + (beg end mode structure granularity visible-only acc) "Parse elements between BEG and END positions. -SPECIAL prioritize some elements over the others. It can be set -to `first-section', `quote-section', `section' `item' or -`table-row'. +MODE prioritizes some elements over the others. It can be set to +`first-section', `section', `planning', `item', `node-property' +or `table-row'. When value is `item', STRUCTURE will be used as the current list structure. @@ -4320,140 +4266,203 @@ Elements are accumulated into ACC." ;; 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))) - ;; Main loop start. - (while (< (point) end) - ;; Find current element's type and parse it accordingly to - ;; its category. - (let* ((element (org-element--current-element - end granularity special 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. - (case type - (headline - (if (org-element-property :quotedp element) 'quote-section - 'section)) - (plain-list 'item) - (property-drawer 'node-property) - (table 'table-row)) - (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)))) - (org-element-adopt-elements acc element))) - ;; Return result. - acc)) - -(defun org-element--parse-objects (beg end acc restriction) + (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)))) + ;; Return result. + (apply #'org-element-set-contents acc (nreverse elements))))) + +(defun org-element--object-lex (restriction) + "Return next object in current buffer or nil. +RESTRICTION is a list of object types, as symbols, that should be +looked after. This function assumes that the buffer is narrowed +to an appropriate container (e.g., a paragraph)." + (if (memq 'table-cell restriction) (org-element-table-cell-parser) + (let* ((start (point)) + (limit + ;; Object regexp sometimes needs to have a peek at + ;; a character ahead. Therefore, when there is a hard + ;; limit, make it one more than the true beginning of the + ;; radio target. + (save-excursion + (cond ((not org-target-link-regexp) nil) + ((not (memq 'link restriction)) nil) + ((progn + (unless (bolp) (forward-char -1)) + (not (re-search-forward org-target-link-regexp nil t))) + nil) + ;; Since we moved backward, we do not want to + ;; match again an hypothetical 1-character long + ;; radio link before us. Realizing that this can + ;; only happen if such a radio link starts at + ;; beginning of line, we prevent this here. + ((and (= start (1+ (line-beginning-position))) + (= start (match-end 1))) + (and (re-search-forward org-target-link-regexp nil t) + (1+ (match-beginning 1)))) + (t (1+ (match-beginning 1)))))) + found) + (save-excursion + (while (and (not found) + (re-search-forward org-element--object-regexp limit 'move)) + (goto-char (match-beginning 0)) + (let ((result (match-string 0))) + (setq found + (cond + ((string-prefix-p "call_" result t) + (and (memq 'inline-babel-call restriction) + (org-element-inline-babel-call-parser))) + ((string-prefix-p "src_" result t) + (and (memq 'inline-src-block restriction) + (org-element-inline-src-block-parser))) + (t + (pcase (char-after) + (?^ (and (memq 'superscript restriction) + (org-element-superscript-parser))) + (?_ (or (and (memq 'subscript restriction) + (org-element-subscript-parser)) + (and (memq 'underline restriction) + (org-element-underline-parser)))) + (?* (and (memq 'bold restriction) + (org-element-bold-parser))) + (?/ (and (memq 'italic restriction) + (org-element-italic-parser))) + (?~ (and (memq 'code restriction) + (org-element-code-parser))) + (?= (and (memq 'verbatim restriction) + (org-element-verbatim-parser))) + (?+ (and (memq 'strike-through restriction) + (org-element-strike-through-parser))) + (?@ (and (memq 'export-snippet restriction) + (org-element-export-snippet-parser))) + (?{ (and (memq 'macro restriction) + (org-element-macro-parser))) + (?$ (and (memq 'latex-fragment restriction) + (org-element-latex-fragment-parser))) + (?< + (if (eq (aref result 1) ?<) + (or (and (memq 'radio-target restriction) + (org-element-radio-target-parser)) + (and (memq 'target restriction) + (org-element-target-parser))) + (or (and (memq 'timestamp restriction) + (org-element-timestamp-parser)) + (and (memq 'link restriction) + (org-element-link-parser))))) + (?\\ + (if (eq (aref result 1) ?\\) + (and (memq 'line-break restriction) + (org-element-line-break-parser)) + (or (and (memq 'entity restriction) + (org-element-entity-parser)) + (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))))) + ;; This is probably a plain link. + (_ (and (memq 'link restriction) + (org-element-link-parser))))))) + (or (eobp) (forward-char)))) + (cond (found) + (limit (forward-char -1) + (org-element-link-parser)) ;radio link + (t nil)))))) + +(defun org-element--parse-objects (beg end acc restriction &optional parent) "Parse objects between BEG and END and return recursive structure. -Objects are accumulated in ACC. +Objects are accumulated in ACC. RESTRICTION is a list of object +successors which are allowed in the current object. -RESTRICTION is a list of object successors which are allowed in -the current object." - (let ((candidates 'initial)) - (save-excursion - (save-restriction - (narrow-to-region beg end) - (goto-char (point-min)) +ACC becomes the parent for all parsed objects. However, if ACC +is nil (i.e., a secondary string is being parsed) and optional +argument PARENT is non-nil, use it as the parent for all objects. +Eventually, if both ACC and PARENT are nil, the common parent is +the list of objects itself." + (save-excursion + (save-restriction + (narrow-to-region beg end) + (goto-char (point-min)) + (let (next-object contents) (while (and (not (eobp)) - (setq candidates - (org-element--get-next-object-candidates - restriction candidates))) - (let ((next-object - (let ((pos (apply 'min (mapcar 'cdr candidates)))) - (save-excursion - (goto-char pos) - (funcall (intern (format "org-element-%s-parser" - (car (rassq pos candidates))))))))) - ;; 1. Text before any object. Untabify it. - (let ((obj-beg (org-element-property :begin next-object))) - (unless (= (point) obj-beg) - (setq acc - (org-element-adopt-elements - acc - (replace-regexp-in-string - "\t" (make-string tab-width ? ) - (buffer-substring-no-properties (point) obj-beg)))))) - ;; 2. Object... - (let ((obj-end (org-element-property :end next-object)) - (cont-beg (org-element-property :contents-begin next-object))) - ;; Fill contents of NEXT-OBJECT by side-effect, if it has - ;; a recursive type. - (when (and cont-beg - (memq (car next-object) org-element-recursive-objects)) - (org-element--parse-objects - cont-beg (org-element-property :contents-end next-object) - next-object (org-element-restriction next-object))) - (setq acc (org-element-adopt-elements acc next-object)) - (goto-char obj-end)))) - ;; 3. Text after last object. Untabify it. + (setq next-object (org-element--object-lex restriction))) + ;; Text before any object. + (let ((obj-beg (org-element-property :begin next-object))) + (unless (= (point) obj-beg) + (let ((text (buffer-substring-no-properties (point) obj-beg))) + (push (if acc (org-element-put-property text :parent acc) text) + contents)))) + ;; Object... + (let ((obj-end (org-element-property :end next-object)) + (cont-beg (org-element-property :contents-begin next-object))) + (when acc (org-element-put-property next-object :parent acc)) + (push (if cont-beg + ;; Fill contents of NEXT-OBJECT if possible. + (org-element--parse-objects + cont-beg + (org-element-property :contents-end next-object) + next-object + (org-element-restriction next-object)) + next-object) + contents) + (goto-char obj-end))) + ;; Text after last object. (unless (eobp) - (setq acc - (org-element-adopt-elements - acc - (replace-regexp-in-string - "\t" (make-string tab-width ? ) - (buffer-substring-no-properties (point) end))))) - ;; Result. - acc)))) - -(defun org-element--get-next-object-candidates (restriction objects) - "Return an alist of candidates for the next object. - -RESTRICTION is a list of object types, as symbols. Only -candidates with such types are looked after. - -OBJECTS is the previous candidates alist. If it is set to -`initial', no search has been done before, and all symbols in -RESTRICTION should be looked after. - -Return value is an alist whose CAR is the object type and CDR its -beginning position." - (delq - nil - (if (eq objects 'initial) - ;; When searching for the first time, look for every successor - ;; allowed in RESTRICTION. - (mapcar - (lambda (res) - (funcall (intern (format "org-element-%s-successor" res)))) - restriction) - ;; Focus on objects returned during last search. Keep those - ;; still after point. Search again objects before it. - (mapcar - (lambda (obj) - (if (>= (cdr obj) (point)) obj - (let* ((type (car obj)) - (succ (or (cdr (assq type org-element-object-successor-alist)) - type))) - (and succ - (funcall (intern (format "org-element-%s-successor" succ))))))) - objects)))) + (let ((text (buffer-substring-no-properties (point) end))) + (push (if acc (org-element-put-property text :parent acc) text) + contents))) + ;; Result. Set appropriate parent. + (if acc (apply #'org-element-set-contents acc (nreverse contents)) + (let* ((contents (nreverse contents)) + (parent (or parent contents))) + (dolist (datum contents contents) + (org-element-put-property datum :parent parent)))))))) @@ -4468,71 +4477,74 @@ beginning position." ;; `org-element--interpret-affiliated-keywords'. ;;;###autoload -(defun org-element-interpret-data (data &optional parent) +(defun org-element-interpret-data (data) "Interpret DATA as Org syntax. - DATA is a parse tree, an element, an object or a secondary string -to interpret. - -Optional argument PARENT is used for recursive calls. It contains -the element or object containing data, or nil. - -Return Org syntax as a string." - (let* ((type (org-element-type data)) - (results - (cond - ;; Secondary string. - ((not type) - (mapconcat - (lambda (obj) (org-element-interpret-data obj parent)) - data "")) - ;; Full Org document. - ((eq type 'org-data) - (mapconcat - (lambda (obj) (org-element-interpret-data obj parent)) - (org-element-contents data) "")) - ;; Plain text: return it. - ((stringp data) data) - ;; Element/Object without contents. - ((not (org-element-contents data)) - (funcall (intern (format "org-element-%s-interpreter" type)) - data nil)) - ;; Element/Object with contents. - (t - (let* ((greaterp (memq type org-element-greater-elements)) - (objectp (and (not greaterp) - (memq type org-element-recursive-objects))) - (contents - (mapconcat - (lambda (obj) (org-element-interpret-data obj data)) - (org-element-contents - (if (or greaterp objectp) data - ;; Elements directly containing objects must - ;; have their indentation normalized first. - (org-element-normalize-contents +to interpret. Return Org syntax as a string." + (letrec ((fun + (lambda (data parent) + (let* ((type (org-element-type data)) + ;; Find interpreter for current object or + ;; element. If it doesn't exist (e.g. this is + ;; a pseudo object or element), return contents, + ;; if any. + (interpret + (let ((fun (intern + (format "org-element-%s-interpreter" type)))) + (if (fboundp fun) fun (lambda (_ contents) contents)))) + (results + (cond + ;; Secondary string. + ((not type) + (mapconcat (lambda (obj) (funcall fun obj parent)) + data + "")) + ;; Full Org document. + ((eq type 'org-data) + (mapconcat (lambda (obj) (funcall fun obj parent)) + (org-element-contents data) + "")) + ;; Plain text: return it. + ((stringp data) data) + ;; Element or object without contents. + ((not (org-element-contents data)) + (funcall interpret data nil)) + ;; Element or object with contents. + (t + (funcall + interpret data - ;; When normalizing first paragraph of an - ;; item or a footnote-definition, ignore - ;; first line's indentation. - (and (eq type 'paragraph) - (equal data (car (org-element-contents parent))) - (memq (org-element-type parent) - '(footnote-definition item)))))) - ""))) - (funcall (intern (format "org-element-%s-interpreter" type)) - data - (if greaterp (org-element-normalize-contents contents) - contents))))))) - (if (memq type '(org-data plain-text nil)) results - ;; Build white spaces. If no `:post-blank' property is - ;; specified, assume its value is 0. - (let ((post-blank (or (org-element-property :post-blank data) 0))) - (if (memq type org-element-all-objects) - (concat results (make-string post-blank 32)) - (concat - (org-element--interpret-affiliated-keywords data) - (org-element-normalize-string results) - (make-string post-blank 10))))))) + ;; Recursively interpret contents. + (mapconcat + (lambda (datum) (funcall fun datum data)) + (org-element-contents + (if (not (memq type '(paragraph verse-block))) + data + ;; Fix indentation of elements containing + ;; objects. We ignore `table-row' + ;; elements as they are one line long + ;; anyway. + (org-element-normalize-contents + data + ;; When normalizing first paragraph of + ;; an item or a footnote-definition, + ;; ignore first line's indentation. + (and (eq type 'paragraph) + (memq (org-element-type parent) + '(footnote-definition item)) + (eq data + (car (org-element-contents parent))))))) + "")))))) + (if (memq type '(org-data plain-text nil)) results + ;; Build white spaces. If no `:post-blank' property + ;; is specified, assume its value is 0. + (let ((blank (or (org-element-property :post-blank data) 0))) + (if (eq (org-element-class data parent) 'object) + (concat results (make-string blank ?\s)) + (concat (org-element--interpret-affiliated-keywords data) + (org-element-normalize-string results) + (make-string blank ?\n))))))))) + (funcall fun data nil))) (defun org-element--interpret-affiliated-keywords (element) "Return ELEMENT's affiliated keywords as Org syntax. @@ -4566,14 +4578,14 @@ If there is no affiliated keyword, return the empty string." ;; List all ELEMENT's properties matching an attribute line or an ;; affiliated keyword, but ignore translated keywords since they ;; cannot belong to the property list. - (loop for prop in (nth 1 element) by 'cddr - when (let ((keyword (upcase (substring (symbol-name prop) 1)))) - (or (string-match "^ATTR_" keyword) - (and - (member keyword org-element-affiliated-keywords) - (not (assoc keyword - org-element-keyword-translation-alist))))) - collect prop) + (cl-loop for prop in (nth 1 element) by 'cddr + when (let ((keyword (upcase (substring (symbol-name prop) 1)))) + (or (string-match "^ATTR_" keyword) + (and + (member keyword org-element-affiliated-keywords) + (not (assoc keyword + org-element-keyword-translation-alist))))) + collect prop) ""))) ;; Because interpretation of the parse tree must return the same @@ -4609,67 +4621,1065 @@ If optional argument IGNORE-FIRST is non-nil, ignore first line's indentation to compute maximal common indentation. Return the normalized element that is element with global -indentation removed from its contents. The function assumes that -indentation is not done with TAB characters." - (let* ((min-ind most-positive-fixnum) - find-min-ind ; For byte-compiler. - (find-min-ind - ;; Return minimal common indentation within BLOB. This is - ;; done by walking recursively BLOB and updating MIN-IND - ;; along the way. FIRST-FLAG is non-nil when the first - ;; string hasn't been seen yet. It is required as this - ;; string is the only one whose indentation doesn't happen - ;; after a newline character. - (lambda (blob first-flag) - (dolist (object (org-element-contents blob)) - (when (and first-flag (stringp object)) - (setq first-flag nil) - (string-match "\\` *" object) - (let ((len (match-end 0))) - ;; An indentation of zero means no string will be - ;; modified. Quit the process. - (if (zerop len) (throw 'zero (setq min-ind 0)) - (setq min-ind (min len min-ind))))) - (cond - ((stringp object) - (dolist (line (cdr (org-split-string object " *\n"))) - (unless (string= line "") - (setq min-ind (min (org-get-indentation line) min-ind))))) - ((memq (org-element-type object) org-element-recursive-objects) - (funcall find-min-ind object first-flag))))))) - ;; Find minimal indentation in ELEMENT. - (catch 'zero (funcall find-min-ind element (not ignore-first))) +indentation removed from its contents." + (letrec ((find-min-ind + ;; Return minimal common indentation within BLOB. This is + ;; done by walking recursively BLOB and updating MIN-IND + ;; along the way. FIRST-FLAG is non-nil when the next + ;; object is expected to be a string that doesn't start + ;; with a newline character. It happens for strings at + ;; the beginnings of the contents or right after a line + ;; break. + (lambda (blob first-flag min-ind) + (dolist (datum (org-element-contents blob) min-ind) + (when first-flag + (setq first-flag nil) + (cond + ;; Objects cannot start with spaces: in this + ;; case, indentation is 0. + ((not (stringp datum)) (throw :zero 0)) + ((not (string-match + "\\`\\([ \t]+\\)\\([^ \t\n]\\|\n\\|\\'\\)" datum)) + (throw :zero 0)) + ((equal (match-string 2 datum) "\n") + (put-text-property + (match-beginning 1) (match-end 1) 'org-ind 'empty datum)) + (t + (let ((i (string-width (match-string 1 datum)))) + (put-text-property + (match-beginning 1) (match-end 1) 'org-ind i datum) + (setq min-ind (min i min-ind)))))) + (cond + ((stringp datum) + (let ((s 0)) + (while (string-match + "\n\\([ \t]*\\)\\([^ \t\n]\\|\n\\|\\'\\)" datum s) + (setq s (match-end 1)) + (cond + ((equal (match-string 1 datum) "") + (unless (member (match-string 2 datum) '("" "\n")) + (throw :zero 0))) + ((equal (match-string 2 datum) "\n") + (put-text-property (match-beginning 1) (match-end 1) + 'org-ind 'empty datum)) + (t + (let ((i (string-width (match-string 1 datum)))) + (put-text-property (match-beginning 1) (match-end 1) + 'org-ind i datum) + (setq min-ind (min i min-ind)))))))) + ((eq (org-element-type datum) 'line-break) + (setq first-flag t)) + ((memq (org-element-type datum) org-element-recursive-objects) + (setq min-ind + (funcall find-min-ind datum first-flag min-ind))))))) + (min-ind + (catch :zero + (funcall find-min-ind + element (not ignore-first) most-positive-fixnum)))) (if (or (zerop min-ind) (= min-ind most-positive-fixnum)) element ;; Build ELEMENT back, replacing each string with the same ;; string minus common indentation. - (let* (build ; For byte compiler. - (build - (function - (lambda (blob first-flag) - ;; Return BLOB with all its strings indentation - ;; shortened from MIN-IND white spaces. FIRST-FLAG - ;; is non-nil when the first string hasn't been seen - ;; yet. - (setcdr (cdr blob) - (mapcar - #'(lambda (object) - (when (and first-flag (stringp object)) - (setq first-flag nil) - (setq object - (replace-regexp-in-string - (format "\\` \\{%d\\}" min-ind) - "" object))) - (cond - ((stringp object) - (replace-regexp-in-string - (format "\n \\{%d\\}" min-ind) "\n" object)) - ((memq (org-element-type object) - org-element-recursive-objects) - (funcall build object first-flag)) - (t object))) - (org-element-contents blob))) - blob)))) - (funcall build element (not ignore-first)))))) + (letrec ((build + (lambda (datum) + ;; Return DATUM with all its strings indentation + ;; shortened from MIN-IND white spaces. + (setcdr + (cdr datum) + (mapcar + (lambda (object) + (cond + ((stringp object) + (with-temp-buffer + (insert object) + (let ((s (point-min))) + (while (setq s (text-property-not-all + s (point-max) 'org-ind nil)) + (goto-char s) + (let ((i (get-text-property s 'org-ind))) + (delete-region s (progn + (skip-chars-forward " \t") + (point))) + (when (integerp i) (indent-to (- i min-ind)))))) + (buffer-string))) + ((memq (org-element-type object) + org-element-recursive-objects) + (funcall build object)) + (t object))) + (org-element-contents datum))) + datum))) + (funcall build element))))) + + + +;;; Cache +;; +;; Implement a caching mechanism for `org-element-at-point' and +;; `org-element-context', which see. +;; +;; A single public function is provided: `org-element-cache-reset'. +;; +;; Cache is enabled by default, but can be disabled 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 behaviour. +;; +;; Internally, parsed elements are stored in an AVL tree, +;; `org-element--cache'. This tree is updated lazily: whenever +;; a change happens to the buffer, a synchronization request is +;; registered in `org-element--cache-sync-requests' (see +;; `org-element--cache-submit-request'). During idle time, requests +;; are processed by `org-element--cache-sync'. Synchronization also +;; happens when an element is required from the cache. In this case, +;; the process stops as soon as the needed element is up-to-date. +;; +;; A synchronization request can only apply on a synchronized part of +;; the cache. Therefore, the cache is updated at least to the +;; location where the new request applies. Thus, requests are ordered +;; from left to right and all elements starting before the first +;; request are correct. This property is used by functions like +;; `org-element--cache-find' to retrieve elements in the part of the +;; cache that can be trusted. +;; +;; A request applies to every element, starting from its original +;; location (or key, see below). When a request is processed, it +;; moves forward and may collide the next one. In this case, both +;; requests are merged into a new one that starts from that element. +;; As a consequence, the whole synchronization complexity does not +;; depend on the number of pending requests, but on the number of +;; elements the very first request will be applied on. +;; +;; Elements cannot be accessed through their beginning position, which +;; may or may not be up-to-date. Instead, each element in the tree is +;; associated to a key, obtained with `org-element--cache-key'. This +;; mechanism is robust enough to preserve total order among elements +;; even when the tree is only partially synchronized. + + +(defvar org-element-use-cache nil + "Non-nil when Org parser should cache its results. + +WARNING: for the time being, using cache sometimes triggers +freezes. Therefore, it is disabled by default. Activate it if +you want to help debugging the issue.") + +(defvar org-element-cache-sync-idle-time 0.6 + "Length, in seconds, of idle time before syncing cache.") + +(defvar org-element-cache-sync-duration (seconds-to-time 0.04) + "Maximum duration, as a time value, for a cache synchronization. +If the synchronization is not over after this delay, the process +pauses and resumes after `org-element-cache-sync-break' +seconds.") + +(defvar org-element-cache-sync-break (seconds-to-time 0.3) + "Duration, as a time value, of the pause between synchronizations. +See `org-element-cache-sync-duration' for more information.") + + +;;;; Data Structure + +(defvar org-element--cache nil + "AVL tree used to cache elements. +Each node of the tree contains an element. Comparison is done +with `org-element--cache-compare'. This cache is used in +`org-element-at-point'.") + +(defvar org-element--cache-sync-requests nil + "List of pending synchronization requests. + +A request is a vector with the following pattern: + + \[NEXT BEG END OFFSET PARENT PHASE] + +Processing a synchronization request consists of three phases: + + 0. Delete modified elements, + 1. Fill missing area in cache, + 2. Shift positions and re-parent elements after the changes. + +During phase 0, NEXT is the key of the first element to be +removed, BEG and END is buffer position delimiting the +modifications. Elements starting between them (inclusive) are +removed. So are elements whose parent is removed. PARENT, when +non-nil, is the parent of the first element to be removed. + +During phase 1, NEXT is the key of the next known element in +cache and BEG its beginning position. Parse buffer between that +element and the one before it in order to determine the parent of +the next element. Set PARENT to the element containing NEXT. + +During phase 2, NEXT is the key of the next element to shift in +the parse tree. All elements starting from this one have their +properties relatives to buffer positions shifted by integer +OFFSET and, if they belong to element PARENT, are adopted by it. + +PHASE specifies the phase number, as an integer.") + +(defvar org-element--cache-sync-timer nil + "Timer used for cache synchronization.") + +(defvar org-element--cache-sync-keys nil + "Hash table used to store keys during synchronization. +See `org-element--cache-key' for more information.") + +(defsubst org-element--cache-key (element) + "Return a unique key for ELEMENT in cache tree. + +Keys are used to keep a total order among elements in the cache. +Comparison is done with `org-element--cache-key-less-p'. + +When no synchronization is taking place, a key is simply the +beginning position of the element, or that position plus one in +the case of an first item (respectively row) in +a list (respectively a table). + +During a synchronization, the key is the one the element had when +the cache was synchronized for the last time. Elements added to +cache during the synchronization get a new key generated with +`org-element--cache-generate-key'. + +Such keys are stored in `org-element--cache-sync-keys'. The hash +table is cleared once the synchronization is complete." + (or (gethash element org-element--cache-sync-keys) + (let* ((begin (org-element-property :begin element)) + ;; Increase beginning position of items (respectively + ;; table rows) by one, so the first item can get + ;; a different key from its parent list (respectively + ;; table). + (key (if (memq (org-element-type element) '(item table-row)) + (1+ begin) + begin))) + (if org-element--cache-sync-requests + (puthash element key org-element--cache-sync-keys) + key)))) + +(defun org-element--cache-generate-key (lower upper) + "Generate a key between LOWER and UPPER. + +LOWER and UPPER are integers or lists, possibly empty. + +If LOWER and UPPER are equals, return LOWER. Otherwise, return +a unique key, as an integer or a list of integers, according to +the following rules: + + - LOWER and UPPER are compared level-wise until values differ. + + - If, at a given level, LOWER and UPPER differ from more than + 2, the new key shares all the levels above with LOWER and + gets a new level. Its value is the mean between LOWER and + UPPER: + + (1 2) + (1 4) --> (1 3) + + - If LOWER has no value to compare with, it is assumed that its + value is `most-negative-fixnum'. E.g., + + (1 1) + (1 1 2) + + is equivalent to + + (1 1 m) + (1 1 2) + + where m is `most-negative-fixnum'. Likewise, if UPPER is + short of levels, the current value is `most-positive-fixnum'. + + - If they differ from only one, the new key inherits from + current LOWER level and fork it at the next level. E.g., + + (2 1) + (3 3) + + is equivalent to + + (2 1) + (2 M) + + where M is `most-positive-fixnum'. + + - If the key is only one level long, it is returned as an + integer: + + (1 2) + (3 2) --> 2 + +When they are not equals, the function assumes that LOWER is +lesser than UPPER, per `org-element--cache-key-less-p'." + (if (equal lower upper) lower + (let ((lower (if (integerp lower) (list lower) lower)) + (upper (if (integerp upper) (list upper) upper)) + skip-upper key) + (catch 'exit + (while t + (let ((min (or (car lower) most-negative-fixnum)) + (max (cond (skip-upper most-positive-fixnum) + ((car upper)) + (t most-positive-fixnum)))) + (if (< (1+ min) max) + (let ((mean (+ (ash min -1) (ash max -1) (logand min max 1)))) + (throw 'exit (if key (nreverse (cons mean key)) mean))) + (when (and (< min max) (not skip-upper)) + ;; When at a given level, LOWER and UPPER differ from + ;; 1, ignore UPPER altogether. Instead create a key + ;; between LOWER and the greatest key with the same + ;; prefix as LOWER so far. + (setq skip-upper t)) + (push min key) + (setq lower (cdr lower) upper (cdr upper))))))))) + +(defsubst org-element--cache-key-less-p (a b) + "Non-nil if key A is less than key B. +A and B are either integers or lists of integers, as returned by +`org-element--cache-key'." + (if (integerp a) (if (integerp b) (< a b) (<= a (car b))) + (if (integerp b) (< (car a) b) + (catch 'exit + (while (and a b) + (cond ((car-less-than-car a b) (throw 'exit t)) + ((car-less-than-car b a) (throw 'exit nil)) + (t (setq a (cdr a) b (cdr b))))) + ;; If A is empty, either keys are equal (B is also empty) and + ;; we return nil, or A is lesser than B (B is longer) and we + ;; return a non-nil value. + ;; + ;; If A is not empty, B is necessarily empty and A is greater + ;; than B (A is longer). Therefore, return nil. + (and (null a) b))))) + +(defun org-element--cache-compare (a b) + "Non-nil when element A is located before element B." + (org-element--cache-key-less-p (org-element--cache-key a) + (org-element--cache-key b))) + +(defsubst org-element--cache-root () + "Return root value in cache. +This function assumes `org-element--cache' is a valid AVL tree." + (avl-tree--node-left (avl-tree--dummyroot org-element--cache))) + + +;;;; Tools + +(defsubst org-element--cache-active-p () + "Non-nil when cache is active in current buffer." + (and org-element-use-cache + org-element--cache + (derived-mode-p 'org-mode))) + +(defun org-element--cache-find (pos &optional side) + "Find element in cache starting at POS or before. + +POS refers to a buffer position. + +When optional argument SIDE is non-nil, the function checks for +elements starting at or past POS instead. If SIDE is `both', the +function returns a cons cell where car is the first element +starting at or before POS and cdr the first element starting +after POS. + +The function can only find elements in the synchronized part of +the cache." + (let ((limit (and org-element--cache-sync-requests + (aref (car org-element--cache-sync-requests) 0))) + (node (org-element--cache-root)) + lower upper) + (while node + (let* ((element (avl-tree--node-data node)) + (begin (org-element-property :begin element))) + (cond + ((and limit + (not (org-element--cache-key-less-p + (org-element--cache-key element) limit))) + (setq node (avl-tree--node-left node))) + ((> begin pos) + (setq upper element + node (avl-tree--node-left node))) + ((< begin pos) + (setq lower element + node (avl-tree--node-right node))) + ;; We found an element in cache starting at POS. If `side' + ;; is `both' we also want the next one in order to generate + ;; a key in-between. + ;; + ;; If the element is the first row or item in a table or + ;; a plain list, we always return the table or the plain + ;; list. + ;; + ;; In any other case, we return the element found. + ((eq side 'both) + (setq lower element) + (setq node (avl-tree--node-right node))) + ((and (memq (org-element-type element) '(item table-row)) + (let ((parent (org-element-property :parent element))) + (and (= (org-element-property :begin element) + (org-element-property :contents-begin parent)) + (setq node nil + lower parent + upper parent))))) + (t + (setq node nil + lower element + upper element))))) + (pcase side + (`both (cons lower upper)) + (`nil lower) + (_ upper)))) + +(defun org-element--cache-put (element) + "Store ELEMENT in current buffer's cache, if allowed." + (when (org-element--cache-active-p) + (when org-element--cache-sync-requests + ;; During synchronization, first build an appropriate key for + ;; the new element so `avl-tree-enter' can insert it at the + ;; right spot in the cache. + (let ((keys (org-element--cache-find + (org-element-property :begin element) 'both))) + (puthash element + (org-element--cache-generate-key + (and (car keys) (org-element--cache-key (car keys))) + (cond ((cdr keys) (org-element--cache-key (cdr keys))) + (org-element--cache-sync-requests + (aref (car org-element--cache-sync-requests) 0)))) + org-element--cache-sync-keys))) + (avl-tree-enter org-element--cache element))) + +(defsubst org-element--cache-remove (element) + "Remove ELEMENT from cache. +Assume ELEMENT belongs to cache and that a cache is active." + (avl-tree-delete org-element--cache element)) + + +;;;; Synchronization + +(defsubst org-element--cache-set-timer (buffer) + "Set idle timer for cache synchronization in BUFFER." + (when org-element--cache-sync-timer + (cancel-timer org-element--cache-sync-timer)) + (setq org-element--cache-sync-timer + (run-with-idle-timer + (let ((idle (current-idle-time))) + (if idle (time-add idle org-element-cache-sync-break) + org-element-cache-sync-idle-time)) + nil + #'org-element--cache-sync + buffer))) + +(defsubst org-element--cache-interrupt-p (time-limit) + "Non-nil when synchronization process should be interrupted. +TIME-LIMIT is a time value or nil." + (and time-limit + (or (input-pending-p) + (time-less-p time-limit (current-time))))) + +(defsubst org-element--cache-shift-positions (element offset &optional props) + "Shift ELEMENT properties relative to buffer positions by OFFSET. + +Properties containing buffer positions are `:begin', `:end', +`:contents-begin', `:contents-end' and `:structure'. When +optional argument PROPS is a list of keywords, only shift +properties provided in that list. + +Properties are modified by side-effect." + (let ((properties (nth 1 element))) + ;; Shift `:structure' property for the first plain list only: it + ;; is the only one that really matters and it prevents from + ;; shifting it more than once. + (when (and (or (not props) (memq :structure props)) + (eq (org-element-type element) 'plain-list) + (not (eq (org-element-type (plist-get properties :parent)) + 'item))) + (dolist (item (plist-get properties :structure)) + (cl-incf (car item) offset) + (cl-incf (nth 6 item) offset))) + (dolist (key '(:begin :contents-begin :contents-end :end :post-affiliated)) + (let ((value (and (or (not props) (memq key props)) + (plist-get properties key)))) + (and value (plist-put properties key (+ offset value))))))) + +(defun org-element--cache-sync (buffer &optional threshold future-change) + "Synchronize cache with recent modification in BUFFER. + +When optional argument THRESHOLD is non-nil, do the +synchronization for all elements starting before or at threshold, +then exit. Otherwise, synchronize cache for as long as +`org-element-cache-sync-duration' or until Emacs leaves idle +state. + +FUTURE-CHANGE, when non-nil, is a buffer position where changes +not registered yet in the cache are going to happen. It is used +in `org-element--cache-submit-request', where cache is partially +updated before current modification are actually submitted." + (when (buffer-live-p buffer) + (with-current-buffer buffer + (let ((inhibit-quit t) request next) + (when org-element--cache-sync-timer + (cancel-timer org-element--cache-sync-timer)) + (catch 'interrupt + (while org-element--cache-sync-requests + (setq request (car org-element--cache-sync-requests) + next (nth 1 org-element--cache-sync-requests)) + (org-element--cache-process-request + request + (and next (aref next 0)) + threshold + (and (not threshold) + (time-add (current-time) + org-element-cache-sync-duration)) + future-change) + ;; Request processed. Merge current and next offsets and + ;; transfer ending position. + (when next + (cl-incf (aref next 3) (aref request 3)) + (aset next 2 (aref request 2))) + (setq org-element--cache-sync-requests + (cdr org-element--cache-sync-requests)))) + ;; If more requests are awaiting, set idle timer accordingly. + ;; Otherwise, reset keys. + (if org-element--cache-sync-requests + (org-element--cache-set-timer buffer) + (clrhash org-element--cache-sync-keys)))))) + +(defun org-element--cache-process-request + (request next threshold time-limit future-change) + "Process synchronization REQUEST for all entries before NEXT. + +REQUEST is a vector, built by `org-element--cache-submit-request'. + +NEXT is a cache key, as returned by `org-element--cache-key'. + +When non-nil, THRESHOLD is a buffer position. Synchronization +stops as soon as a shifted element begins after it. + +When non-nil, TIME-LIMIT is a time value. Synchronization stops +after this time or when Emacs exits idle state. + +When non-nil, FUTURE-CHANGE is a buffer position where changes +not registered yet in the cache are going to happen. See +`org-element--cache-submit-request' for more information. + +Throw `interrupt' if the process stops before completing the +request." + (catch 'quit + (when (= (aref request 5) 0) + ;; Phase 0. + ;; + ;; Delete all elements starting after BEG, but not after buffer + ;; position END or past element with key NEXT. Also delete + ;; elements contained within a previously removed element + ;; (stored in `last-container'). + ;; + ;; At each iteration, we start again at tree root since + ;; a deletion modifies structure of the balanced tree. + (catch 'end-phase + (while t + (when (org-element--cache-interrupt-p time-limit) + (throw 'interrupt nil)) + ;; Find first element in cache with key BEG or after it. + (let ((beg (aref request 0)) + (end (aref request 2)) + (node (org-element--cache-root)) + data data-key last-container) + (while node + (let* ((element (avl-tree--node-data node)) + (key (org-element--cache-key element))) + (cond + ((org-element--cache-key-less-p key beg) + (setq node (avl-tree--node-right node))) + ((org-element--cache-key-less-p beg key) + (setq data element + data-key key + node (avl-tree--node-left node))) + (t (setq data element + data-key key + node nil))))) + (if data + (let ((pos (org-element-property :begin data))) + (if (if (or (not next) + (org-element--cache-key-less-p data-key next)) + (<= pos end) + (and last-container + (let ((up data)) + (while (and up (not (eq up last-container))) + (setq up (org-element-property :parent up))) + up))) + (progn (when (and (not last-container) + (> (org-element-property :end data) + end)) + (setq last-container data)) + (org-element--cache-remove data)) + (aset request 0 data-key) + (aset request 1 pos) + (aset request 5 1) + (throw 'end-phase nil))) + ;; No element starting after modifications left in + ;; cache: further processing is futile. + (throw 'quit t)))))) + (when (= (aref request 5) 1) + ;; Phase 1. + ;; + ;; Phase 0 left a hole in the cache. Some elements after it + ;; could have parents within. For example, in the following + ;; buffer: + ;; + ;; - item + ;; + ;; + ;; Paragraph1 + ;; + ;; Paragraph2 + ;; + ;; if we remove a blank line between "item" and "Paragraph1", + ;; everything down to "Paragraph2" is removed from cache. But + ;; the paragraph now belongs to the list, and its `:parent' + ;; property no longer is accurate. + ;; + ;; Therefore we need to parse again elements in the hole, or at + ;; least in its last section, so that we can re-parent + ;; subsequent elements, during phase 2. + ;; + ;; Note that we only need to get the parent from the first + ;; element in cache after the hole. + ;; + ;; When next key is lesser or equal to the current one, delegate + ;; phase 1 processing to next request in order to preserve key + ;; order among requests. + (let ((key (aref request 0))) + (when (and next (not (org-element--cache-key-less-p key next))) + (let ((next-request (nth 1 org-element--cache-sync-requests))) + (aset next-request 0 key) + (aset next-request 1 (aref request 1)) + (aset next-request 5 1)) + (throw 'quit t))) + ;; Next element will start at its beginning position plus + ;; offset, since it hasn't been shifted yet. Therefore, LIMIT + ;; contains the real beginning position of the first element to + ;; shift and re-parent. + (let ((limit (+ (aref request 1) (aref request 3)))) + (cond ((and threshold (> limit threshold)) (throw 'interrupt nil)) + ((and future-change (>= limit future-change)) + ;; Changes are going to happen around this element and + ;; they will trigger another phase 1 request. Skip the + ;; current one. + (aset request 5 2)) + (t + (let ((parent (org-element--parse-to limit t time-limit))) + (aset request 4 parent) + (aset request 5 2)))))) + ;; Phase 2. + ;; + ;; Shift all elements starting from key START, but before NEXT, by + ;; OFFSET, and re-parent them when appropriate. + ;; + ;; Elements are modified by side-effect so the tree structure + ;; remains intact. + ;; + ;; Once THRESHOLD, if any, is reached, or once there is an input + ;; pending, exit. Before leaving, the current synchronization + ;; request is updated. + (let ((start (aref request 0)) + (offset (aref request 3)) + (parent (aref request 4)) + (node (org-element--cache-root)) + (stack (list nil)) + (leftp t) + exit-flag) + ;; No re-parenting nor shifting planned: request is over. + (when (and (not parent) (zerop offset)) (throw 'quit t)) + (while node + (let* ((data (avl-tree--node-data node)) + (key (org-element--cache-key data))) + (if (and leftp (avl-tree--node-left node) + (not (org-element--cache-key-less-p key start))) + (progn (push node stack) + (setq node (avl-tree--node-left node))) + (unless (org-element--cache-key-less-p key start) + ;; We reached NEXT. Request is complete. + (when (equal key next) (throw 'quit t)) + ;; Handle interruption request. Update current request. + (when (or exit-flag (org-element--cache-interrupt-p time-limit)) + (aset request 0 key) + (aset request 4 parent) + (throw 'interrupt nil)) + ;; Shift element. + (unless (zerop offset) + (org-element--cache-shift-positions data offset)) + (let ((begin (org-element-property :begin data))) + ;; Update PARENT and re-parent DATA, only when + ;; necessary. Propagate new structures for lists. + (while (and parent + (<= (org-element-property :end parent) begin)) + (setq parent (org-element-property :parent parent))) + (cond ((and (not parent) (zerop offset)) (throw 'quit nil)) + ((and parent + (let ((p (org-element-property :parent data))) + (or (not p) + (< (org-element-property :begin p) + (org-element-property :begin parent))))) + (org-element-put-property data :parent parent) + (let ((s (org-element-property :structure parent))) + (when (and s (org-element-property :structure data)) + (org-element-put-property data :structure s))))) + ;; Cache is up-to-date past THRESHOLD. Request + ;; interruption. + (when (and threshold (> begin threshold)) (setq exit-flag t)))) + (setq node (if (setq leftp (avl-tree--node-right node)) + (avl-tree--node-right node) + (pop stack)))))) + ;; We reached end of tree: synchronization complete. + t))) + +(defun org-element--parse-to (pos &optional syncp time-limit) + "Parse elements in current section, down to POS. + +Start parsing from the closest between the last known element in +cache or headline above. Return the smallest element containing +POS. + +When optional argument SYNCP is non-nil, return the parent of the +element containing POS instead. In that case, it is also +possible to provide TIME-LIMIT, which is a time value specifying +when the parsing should stop. The function throws `interrupt' if +the process stopped before finding the expected result." + (catch 'exit + (org-with-wide-buffer + (goto-char pos) + (let* ((cached (and (org-element--cache-active-p) + (org-element--cache-find pos nil))) + (begin (org-element-property :begin cached)) + element next mode) + (cond + ;; Nothing in cache before point: start parsing from first + ;; element following headline above, or first element in + ;; buffer. + ((not cached) + (when (org-with-limited-levels (outline-previous-heading)) + (setq mode 'planning) + (forward-line)) + (skip-chars-forward " \r\t\n") + (beginning-of-line)) + ;; Cache returned exact match: return it. + ((= pos begin) + (throw 'exit (if syncp (org-element-property :parent cached) cached))) + ;; There's a headline between cached value and POS: cached + ;; value is invalid. Start parsing from first element + ;; following the headline. + ((re-search-backward + (org-with-limited-levels org-outline-regexp-bol) begin t) + (forward-line) + (skip-chars-forward " \r\t\n") + (beginning-of-line) + (setq mode 'planning)) + ;; Check if CACHED or any of its ancestors contain point. + ;; + ;; If there is such an element, we inspect it in order to know + ;; if we return it or if we need to parse its contents. + ;; Otherwise, we just start parsing from current location, + ;; which is right after the top-most element containing + ;; CACHED. + ;; + ;; As a special case, if POS is at the end of the buffer, we + ;; want to return the innermost element ending there. + ;; + ;; Also, if we find an ancestor and discover that we need to + ;; parse its contents, make sure we don't start from + ;; `:contents-begin', as we would otherwise go past CACHED + ;; again. Instead, in that situation, we will resume parsing + ;; from NEXT, which is located after CACHED or its higher + ;; ancestor not containing point. + (t + (let ((up cached) + (pos (if (= (point-max) pos) (1- pos) pos))) + (goto-char (or (org-element-property :contents-begin cached) begin)) + (while (let ((end (org-element-property :end up))) + (and (<= end pos) + (goto-char end) + (setq up (org-element-property :parent up))))) + (cond ((not up)) + ((eobp) (setq element up)) + (t (setq element up next (point))))))) + ;; Parse successively each element until we reach POS. + (let ((end (or (org-element-property :end element) + (save-excursion + (org-with-limited-levels (outline-next-heading)) + (point)))) + (parent element)) + (while t + (when syncp + (cond ((= (point) pos) (throw 'exit parent)) + ((org-element--cache-interrupt-p time-limit) + (throw 'interrupt nil)))) + (unless element + (setq element (org-element--current-element + end 'element mode + (org-element-property :structure parent))) + (org-element-put-property element :parent parent) + (org-element--cache-put element)) + (let ((elem-end (org-element-property :end element)) + (type (org-element-type element))) + (cond + ;; Skip any element ending before point. Also skip + ;; element ending at point (unless it is also the end of + ;; buffer) since we're sure that another element begins + ;; after it. + ((and (<= elem-end pos) (/= (point-max) elem-end)) + (goto-char elem-end) + (setq mode (org-element--next-mode type nil))) + ;; A non-greater element contains point: return it. + ((not (memq type org-element-greater-elements)) + (throw 'exit element)) + ;; Otherwise, we have to decide if ELEMENT really + ;; contains POS. In that case we start parsing from + ;; contents' beginning. + ;; + ;; If POS is at contents' beginning but it is also at + ;; the beginning of the first item in a list or a table. + ;; In that case, we need to create an anchor for that + ;; list or table, so return it. + ;; + ;; Also, if POS is at the end of the buffer, no element + ;; can start after it, but more than one may end there. + ;; Arbitrarily, we choose to return the innermost of + ;; such elements. + ((let ((cbeg (org-element-property :contents-begin element)) + (cend (org-element-property :contents-end element))) + (when (or syncp + (and cbeg cend + (or (< cbeg pos) + (and (= cbeg pos) + (not (memq type '(plain-list table))))) + (or (> cend pos) + (and (= cend pos) (= (point-max) pos))))) + (goto-char (or next cbeg)) + (setq next nil + mode (org-element--next-mode type t) + parent element + end cend)))) + ;; Otherwise, return ELEMENT as it is the smallest + ;; element containing POS. + (t (throw 'exit element)))) + (setq element nil))))))) + + +;;;; Staging Buffer Changes + +(defconst org-element--cache-sensitive-re + (concat + org-outline-regexp-bol "\\|" + "\\\\end{[A-Za-z0-9*]+}[ \t]*$" "\\|" + "^[ \t]*\\(?:" + "#\\+\\(?:BEGIN[:_]\\|END\\(?:_\\|:?[ \t]*$\\)\\)" "\\|" + "\\\\begin{[A-Za-z0-9*]+}" "\\|" + ":\\(?:\\w\\|[-_]\\)+:[ \t]*$" + "\\)") + "Regexp matching a sensitive line, structure wise. +A sensitive line is a headline, inlinetask, block, drawer, or +latex-environment boundary. When such a line is modified, +structure changes in the document may propagate in the whole +section, possibly making cache invalid.") + +(defvar org-element--cache-change-warning nil + "Non-nil when a sensitive line is about to be changed. +It is a symbol among nil, t and `headline'.") + +(defun org-element--cache-before-change (beg end) + "Request extension of area going to be modified if needed. +BEG and END are the beginning and end of the range of changed +text. See `before-change-functions' for more information." + (when (org-element--cache-active-p) + (org-with-wide-buffer + (goto-char beg) + (beginning-of-line) + (let ((bottom (save-excursion (goto-char end) (line-end-position)))) + (setq org-element--cache-change-warning + (save-match-data + (if (and (org-with-limited-levels (org-at-heading-p)) + (= (line-end-position) bottom)) + 'headline + (let ((case-fold-search t)) + (re-search-forward + org-element--cache-sensitive-re bottom t))))))))) + +(defun org-element--cache-after-change (beg end pre) + "Update buffer modifications for current buffer. +BEG and END are the beginning and end of the range of changed +text, and the length in bytes of the pre-change text replaced by +that range. See `after-change-functions' for more information." + (when (org-element--cache-active-p) + (org-with-wide-buffer + (goto-char beg) + (beginning-of-line) + (save-match-data + (let ((top (point)) + (bottom (save-excursion (goto-char end) (line-end-position)))) + ;; Determine if modified area needs to be extended, according + ;; to both previous and current state. We make a special + ;; case for headline editing: if a headline is modified but + ;; not removed, do not extend. + (when (pcase org-element--cache-change-warning + (`t t) + (`headline + (not (and (org-with-limited-levels (org-at-heading-p)) + (= (line-end-position) bottom)))) + (_ + (let ((case-fold-search t)) + (re-search-forward + org-element--cache-sensitive-re bottom t)))) + ;; Effectively extend modified area. + (org-with-limited-levels + (setq top (progn (goto-char top) + (when (outline-previous-heading) (forward-line)) + (point))) + (setq bottom (progn (goto-char bottom) + (if (outline-next-heading) (1- (point)) + (point)))))) + ;; Store synchronization request. + (let ((offset (- end beg pre))) + (org-element--cache-submit-request top (- bottom offset) offset))))) + ;; Activate a timer to process the request during idle time. + (org-element--cache-set-timer (current-buffer)))) + +(defun org-element--cache-for-removal (beg end offset) + "Return first element to remove from cache. + +BEG and END are buffer positions delimiting buffer modifications. +OFFSET is the size of the changes. + +Returned element is usually the first element in cache containing +any position between BEG and END. As an exception, greater +elements around the changes that are robust to contents +modifications are preserved and updated according to the +changes." + (let* ((elements (org-element--cache-find (1- beg) 'both)) + (before (car elements)) + (after (cdr elements))) + (if (not before) after + (let ((up before) + (robust-flag t)) + (while up + (if (let ((type (org-element-type up))) + (and (or (memq type '(center-block dynamic-block quote-block + special-block)) + ;; Drawers named "PROPERTIES" are probably + ;; a properties drawer being edited. Force + ;; parsing to check if editing is over. + (and (eq type 'drawer) + (not (string= + (org-element-property :drawer-name up) + "PROPERTIES")))) + (let ((cbeg (org-element-property :contents-begin up))) + (and cbeg + (<= cbeg beg) + (> (org-element-property :contents-end up) end))))) + ;; UP is a robust greater element containing changes. + ;; We only need to extend its ending boundaries. + (org-element--cache-shift-positions + up offset '(:contents-end :end)) + (setq before up) + (when robust-flag (setq robust-flag nil))) + (setq up (org-element-property :parent up))) + ;; We're at top level element containing ELEMENT: if it's + ;; altered by buffer modifications, it is first element in + ;; cache to be removed. Otherwise, that first element is the + ;; following one. + ;; + ;; As a special case, do not remove BEFORE if it is a robust + ;; container for current changes. + (if (or (< (org-element-property :end before) beg) robust-flag) after + before))))) + +(defun org-element--cache-submit-request (beg end offset) + "Submit a new cache synchronization request for current buffer. +BEG and END are buffer positions delimiting the minimal area +where cache data should be removed. OFFSET is the size of the +change, as an integer." + (let ((next (car org-element--cache-sync-requests)) + delete-to delete-from) + (if (and next + (zerop (aref next 5)) + (> (setq delete-to (+ (aref next 2) (aref next 3))) end) + (<= (setq delete-from (aref next 1)) end)) + ;; Current changes can be merged with first sync request: we + ;; can save a partial cache synchronization. + (progn + (cl-incf (aref next 3) offset) + ;; If last change happened within area to be removed, extend + ;; boundaries of robust parents, if any. Otherwise, find + ;; first element to remove and update request accordingly. + (if (> beg delete-from) + (let ((up (aref next 4))) + (while up + (org-element--cache-shift-positions + up offset '(:contents-end :end)) + (setq up (org-element-property :parent up)))) + (let ((first (org-element--cache-for-removal beg delete-to offset))) + (when first + (aset next 0 (org-element--cache-key first)) + (aset next 1 (org-element-property :begin first)) + (aset next 4 (org-element-property :parent first)))))) + ;; Ensure cache is correct up to END. Also make sure that NEXT, + ;; if any, is no longer a 0-phase request, thus ensuring that + ;; phases are properly ordered. We need to provide OFFSET as + ;; optional parameter since current modifications are not known + ;; yet to the otherwise correct part of the cache (i.e, before + ;; the first request). + (when next (org-element--cache-sync (current-buffer) end beg)) + (let ((first (org-element--cache-for-removal beg end offset))) + (if first + (push (let ((beg (org-element-property :begin first)) + (key (org-element--cache-key first))) + (cond + ;; When changes happen before the first known + ;; element, re-parent and shift the rest of the + ;; cache. + ((> beg end) (vector key beg nil offset nil 1)) + ;; Otherwise, we find the first non robust + ;; element containing END. All elements between + ;; FIRST and this one are to be removed. + ((let ((first-end (org-element-property :end first))) + (and (> first-end end) + (vector key beg first-end offset first 0)))) + (t + (let* ((element (org-element--cache-find end)) + (end (org-element-property :end element)) + (up element)) + (while (and (setq up (org-element-property :parent up)) + (>= (org-element-property :begin up) beg)) + (setq end (org-element-property :end up) + element up)) + (vector key beg end offset element 0))))) + org-element--cache-sync-requests) + ;; No element to remove. No need to re-parent either. + ;; Simply shift additional elements, if any, by OFFSET. + (when org-element--cache-sync-requests + (cl-incf (aref (car org-element--cache-sync-requests) 3) + offset))))))) + + +;;;; Public Functions + +;;;###autoload +(defun org-element-cache-reset (&optional all) + "Reset cache in current buffer. +When optional argument ALL is non-nil, reset cache in all Org +buffers." + (interactive "P") + (dolist (buffer (if all (buffer-list) (list (current-buffer)))) + (with-current-buffer buffer + (when (and org-element-use-cache (derived-mode-p 'org-mode)) + (setq-local org-element--cache + (avl-tree-create #'org-element--cache-compare)) + (setq-local org-element--cache-sync-keys + (make-hash-table :weakness 'key :test #'eq)) + (setq-local org-element--cache-change-warning nil) + (setq-local org-element--cache-sync-requests nil) + (setq-local org-element--cache-sync-timer nil) + (add-hook 'before-change-functions + #'org-element--cache-before-change nil t) + (add-hook 'after-change-functions + #'org-element--cache-after-change nil t))))) + +;;;###autoload +(defun org-element-cache-refresh (pos) + "Refresh cache at position POS." + (when (org-element--cache-active-p) + (org-element--cache-sync (current-buffer) pos) + (org-element--cache-submit-request pos pos 0) + (org-element--cache-set-timer (current-buffer)))) @@ -4678,7 +5688,7 @@ indentation is not done with TAB characters." ;; The first move is to implement a way to obtain the smallest element ;; containing point. This is the job of `org-element-at-point'. It ;; basically jumps back to the beginning of section containing point -;; and moves, element after element, with +;; and proceed, one element after the other, with ;; `org-element--current-element' until the container is found. Note: ;; When using `org-element-at-point', secondary values are never ;; parsed since the function focuses on elements, not on objects. @@ -4689,8 +5699,9 @@ indentation is not done with TAB characters." ;; `org-element-nested-p' and `org-element-swap-A-B' may be used ;; internally by navigation and manipulation tools. + ;;;###autoload -(defun org-element-at-point (&optional keep-trail) +(defun org-element-at-point () "Determine closest element around point. Return value is a list like (TYPE PROPS) where TYPE is the type @@ -4701,118 +5712,36 @@ 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. -As a special case, if point is at the very beginning of a list or -sub-list, returned element will be that list instead of the first -item. In the same way, if point is at the beginning of the first -row of a table, returned element will be the table instead of the -first row. - -If optional argument KEEP-TRAIL is non-nil, the function returns -a list of elements leading to element at point. The list's CAR -is always the element at point. The following positions contain -element's siblings, then parents, siblings of parents, until the -first element of current section." +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 +instead of the item. Likewise, if point is at the beginning of +the first row of a table, returned element will be the table +instead of the first row. + +When point is at the end of the buffer, return the innermost +element ending there." (org-with-wide-buffer - ;; If at a headline, parse it. It is the sole element that - ;; doesn't require to know about context. Be sure to disallow - ;; secondary string parsing, though. - (if (org-with-limited-levels (org-at-heading-p)) - (progn - (beginning-of-line) - (if (not keep-trail) (org-element-headline-parser (point-max) t) - (list (org-element-headline-parser (point-max) t)))) - ;; Otherwise move at the beginning of the section containing - ;; point. - (catch 'exit - (let ((origin (point)) - (end (save-excursion - (org-with-limited-levels (outline-next-heading)) (point))) - element type special-flag trail struct prevs parent) - (org-with-limited-levels - (if (org-before-first-heading-p) - ;; In empty lines at buffer's beginning, return nil. - (progn (goto-char (point-min)) - (org-skip-whitespace) - (when (or (eobp) (> (line-beginning-position) origin)) - (throw 'exit nil))) - (org-back-to-heading) - (forward-line) - (org-skip-whitespace) - (when (or (eobp) (> (line-beginning-position) origin)) - ;; In blank lines just after the headline, point still - ;; belongs to the headline. - (throw 'exit - (progn (skip-chars-backward " \r\t\n") - (beginning-of-line) - (if (not keep-trail) - (org-element-headline-parser (point-max) t) - (list (org-element-headline-parser - (point-max) t)))))))) - (beginning-of-line) - ;; Parse successively each element, skipping those ending - ;; before original position. - (while t - (setq element - (org-element--current-element end 'element special-flag struct) - type (car element)) - (org-element-put-property element :parent parent) - (when keep-trail (push element trail)) - (cond - ;; 1. Skip any element ending before point. Also skip - ;; element ending at point when we're sure that another - ;; element has started. - ((let ((elem-end (org-element-property :end element))) - (when (or (< elem-end origin) - (and (= elem-end origin) (/= elem-end end))) - (goto-char elem-end)))) - ;; 2. An element containing point is always the element at - ;; point. - ((not (memq type org-element-greater-elements)) - (throw 'exit (if keep-trail trail element))) - ;; 3. At any other greater element type, if point is - ;; within contents, move into it. - (t - (let ((cbeg (org-element-property :contents-begin element)) - (cend (org-element-property :contents-end element))) - (if (or (not cbeg) (not cend) (> cbeg origin) (< cend origin) - ;; Create an anchor for tables and plain lists: - ;; when point is at the very beginning of these - ;; elements, ignoring affiliated keywords, - ;; target them instead of their contents. - (and (= cbeg origin) (memq type '(plain-list table))) - ;; When point is at contents end, do not move - ;; into elements with an explicit ending, but - ;; return that element instead. - (and (= cend origin) - (or (memq type - '(center-block - drawer dynamic-block inlinetask - property-drawer quote-block - special-block)) - ;; Corner case: if a list ends at the - ;; end of a buffer without a final new - ;; line, return last element in last - ;; item instead. - (and (memq type '(item plain-list)) - (progn (goto-char cend) - (or (bolp) (not (eobp)))))))) - (throw 'exit (if keep-trail trail element)) - (setq parent element) - (case type - (plain-list - (setq special-flag 'item - struct (org-element-property :structure element))) - (item (setq special-flag nil)) - (property-drawer - (setq special-flag 'node-property struct nil)) - (table (setq special-flag 'table-row struct nil)) - (otherwise (setq special-flag nil struct nil))) - (setq end cend) - (goto-char cbeg))))))))))) + (let ((origin (point))) + (end-of-line) + (skip-chars-backward " \r\t\n") + (cond + ;; Within blank lines at the beginning of buffer, return nil. + ((bobp) nil) + ;; Within blank lines right after a headline, return that + ;; headline. + ((org-with-limited-levels (org-at-heading-p)) + (beginning-of-line) + (org-element-headline-parser (point-max) t)) + ;; Otherwise parse until we find element containing ORIGIN. + (t + (when (org-element--cache-active-p) + (if (not org-element--cache) (org-element-cache-reset) + (org-element--cache-sync (current-buffer) origin))) + (org-element--parse-to origin)))))) ;;;###autoload (defun org-element-context (&optional element) - "Return closest element or object around point. + "Return smallest element or object around point. Return value is a list like (TYPE PROPS) where TYPE is the type of the element or object and PROPS a plist of properties @@ -4823,34 +5752,36 @@ Possible types are defined in `org-element-all-elements' and object type, but always include `:begin', `:end', `:parent' and `:post-blank'. +As a special case, if point is right after an object and not at +the beginning of any other object, return that object. + Optional argument ELEMENT, when non-nil, is the closest element containing point, as returned by `org-element-at-point'. Providing it allows for quicker computation." (catch 'objects-forbidden (org-with-wide-buffer - (let* ((origin (point)) - (element (or element (org-element-at-point))) - (type (org-element-type element)) - context) - ;; Check if point is inside an element containing objects or at - ;; a secondary string. In that case, narrow buffer to the - ;; containing area. Otherwise, return ELEMENT. + (let* ((pos (point)) + (element (or element (org-element-at-point))) + (type (org-element-type element)) + (post (org-element-property :post-affiliated element))) + ;; If point is inside an element containing objects or + ;; a secondary string, narrow buffer to the container and + ;; proceed with parsing. Otherwise, return ELEMENT. (cond ;; At a parsed affiliated keyword, check if we're inside main ;; or dual value. - ((let ((post (org-element-property :post-affiliated element))) - (and post (< origin post))) + ((and post (< pos post)) (beginning-of-line) (let ((case-fold-search t)) (looking-at org-element--affiliated-re)) (cond ((not (member-ignore-case (match-string 1) org-element-parsed-keywords)) (throw 'objects-forbidden element)) - ((< (match-end 0) origin) + ((< (match-end 0) pos) (narrow-to-region (match-end 0) (line-end-position))) ((and (match-beginning 2) - (>= origin (match-beginning 2)) - (< origin (match-end 2))) + (>= pos (match-beginning 2)) + (< pos (match-end 2))) (narrow-to-region (match-beginning 2) (match-end 2))) (t (throw 'objects-forbidden element))) ;; Also change type to retrieve correct restrictions. @@ -4858,88 +5789,108 @@ Providing it allows for quicker computation." ;; At an item, objects can only be located within tag, if any. ((eq type 'item) (let ((tag (org-element-property :tag element))) - (if (not tag) (throw 'objects-forbidden element) + (if (or (not tag) (/= (line-beginning-position) post)) + (throw 'objects-forbidden element) (beginning-of-line) (search-forward tag (line-end-position)) (goto-char (match-beginning 0)) - (if (and (>= origin (point)) (< origin (match-end 0))) + (if (and (>= pos (point)) (< pos (match-end 0))) (narrow-to-region (point) (match-end 0)) (throw 'objects-forbidden element))))) - ;; At an headline or inlinetask, objects are located within - ;; their title. + ;; At an headline or inlinetask, objects are in title. ((memq type '(headline inlinetask)) - (goto-char (org-element-property :begin element)) - (skip-chars-forward "*") - (if (and (> origin (point)) (< origin (line-end-position))) - (narrow-to-region (point) (line-end-position)) - (throw 'objects-forbidden element))) + (let ((case-fold-search nil)) + (goto-char (org-element-property :begin element)) + (looking-at org-complex-heading-regexp) + (let ((end (match-end 4))) + (if (not end) (throw 'objects-forbidden element) + (goto-char (match-beginning 4)) + (when (looking-at org-comment-string) + (goto-char (match-end 0))) + (if (>= (point) end) (throw 'objects-forbidden element) + (narrow-to-region (point) end)))))) ;; At a paragraph, a table-row or a verse block, objects are ;; located within their contents. ((memq type '(paragraph table-row verse-block)) (let ((cbeg (org-element-property :contents-begin element)) (cend (org-element-property :contents-end element))) ;; CBEG is nil for table rules. - (if (and cbeg cend (>= origin cbeg) (< origin cend)) + (if (and cbeg cend (>= pos cbeg) + (or (< pos cend) (and (= pos cend) (eobp)))) (narrow-to-region cbeg cend) (throw 'objects-forbidden element)))) - ;; At a parsed keyword, objects are located within value. - ((eq type 'keyword) - (if (not (member (org-element-property :key element) - org-element-document-properties)) - (throw 'objects-forbidden element) - (beginning-of-line) - (search-forward ":") - (if (and (>= origin (point)) (< origin (line-end-position))) - (narrow-to-region (point) (line-end-position)) - (throw 'objects-forbidden element)))) - ;; At a planning line, if point is at a timestamp, return it, - ;; otherwise, return element. - ((eq type 'planning) - (dolist (p '(:closed :deadline :scheduled)) - (let ((timestamp (org-element-property p element))) - (when (and timestamp - (<= (org-element-property :begin timestamp) origin) - (> (org-element-property :end timestamp) origin)) - (throw 'objects-forbidden timestamp)))) - (throw 'objects-forbidden element)) (t (throw 'objects-forbidden element))) (goto-char (point-min)) (let ((restriction (org-element-restriction type)) - (parent element) - (candidates 'initial)) - (catch 'exit - (while (setq candidates - (org-element--get-next-object-candidates - restriction candidates)) - (let ((closest-cand (rassq (apply 'min (mapcar 'cdr candidates)) - candidates))) - ;; If ORIGIN is before next object in element, there's - ;; no point in looking further. - (if (> (cdr closest-cand) origin) (throw 'exit parent) - (let* ((object - (progn (goto-char (cdr closest-cand)) - (funcall (intern (format "org-element-%s-parser" - (car closest-cand)))))) - (cbeg (org-element-property :contents-begin object)) - (cend (org-element-property :contents-end object)) - (obj-end (org-element-property :end object))) - (cond - ;; ORIGIN is after OBJECT, so skip it. - ((<= obj-end origin) (goto-char obj-end)) - ;; ORIGIN is within a non-recursive object or at - ;; an object boundaries: Return that object. - ((or (not cbeg) (< origin cbeg) (>= origin cend)) - (throw 'exit - (org-element-put-property object :parent parent))) - ;; Otherwise, move within current object and - ;; restrict search to the end of its contents. - (t (goto-char cbeg) - (narrow-to-region (point) cend) - (org-element-put-property object :parent parent) - (setq parent object - restriction (org-element-restriction object) - candidates 'initial))))))) - parent)))))) + (parent element) + last) + (catch 'exit + (while t + (let ((next (org-element--object-lex restriction))) + (when next (org-element-put-property next :parent parent)) + ;; Process NEXT, if any, in order to know if we need to + ;; skip it, return it or move into it. + (if (or (not next) (> (org-element-property :begin next) pos)) + (throw 'exit (or last parent)) + (let ((end (org-element-property :end next)) + (cbeg (org-element-property :contents-begin next)) + (cend (org-element-property :contents-end next))) + (cond + ;; Skip objects ending before point. Also skip + ;; objects ending at point unless it is also the + ;; end of buffer, since we want to return the + ;; innermost object. + ((and (<= end pos) (/= (point-max) end)) + (goto-char end) + ;; For convenience, when object ends at POS, + ;; without any space, store it in LAST, as we + ;; will return it if no object starts here. + (when (and (= end pos) + (not (memq (char-before) '(?\s ?\t)))) + (setq last next))) + ;; If POS is within a container object, move into + ;; that object. + ((and cbeg cend + (>= pos cbeg) + (or (< pos cend) + ;; At contents' end, if there is no + ;; space before point, also move into + ;; object, for consistency with + ;; convenience feature above. + (and (= pos cend) + (or (= (point-max) pos) + (not (memq (char-before pos) + '(?\s ?\t))))))) + (goto-char cbeg) + (narrow-to-region (point) cend) + (setq parent next) + (setq restriction (org-element-restriction next))) + ;; Otherwise, return NEXT. + (t (throw 'exit next))))))))))))) + +(defun org-element-lineage (blob &optional types with-self) + "List all ancestors of a given element or object. + +BLOB is an object or element. + +When optional argument TYPES is a list of symbols, return the +first element or object in the lineage whose type belongs to that +list. + +When optional argument WITH-SELF is non-nil, lineage includes +BLOB itself as the first element, and TYPES, if provided, also +apply to it. + +When BLOB is obtained through `org-element-context' or +`org-element-at-point', only ancestors from its section can be +found. There is no such limitation when BLOB belongs to a full +parse tree." + (let ((up (if with-self blob (org-element-property :parent blob))) + ancestors) + (while (and up (not (memq (org-element-type up) types))) + (unless types (push up ancestors)) + (setq up (org-element-property :parent up))) + (if types up (nreverse ancestors)))) (defun org-element-nested-p (elem-A elem-B) "Non-nil when elements ELEM-A and ELEM-B are nested." @@ -4982,39 +5933,44 @@ end of ELEM-A." (goto-char (org-element-property :end elem-B)) (skip-chars-backward " \r\t\n") (point-at-eol))) - ;; Store overlays responsible for visibility status. We - ;; also need to store their boundaries as they will be + ;; Store inner overlays responsible for visibility status. + ;; We also need to store their boundaries as they will be ;; removed from buffer. (overlays (cons - (mapcar (lambda (ov) (list ov (overlay-start ov) (overlay-end ov))) - (overlays-in beg-A end-A)) - (mapcar (lambda (ov) (list ov (overlay-start ov) (overlay-end ov))) - (overlays-in beg-B end-B)))) + (delq nil + (mapcar (lambda (o) + (and (>= (overlay-start o) beg-A) + (<= (overlay-end o) end-A) + (list o (overlay-start o) (overlay-end o)))) + (overlays-in beg-A end-A))) + (delq nil + (mapcar (lambda (o) + (and (>= (overlay-start o) beg-B) + (<= (overlay-end o) end-B) + (list o (overlay-start o) (overlay-end o)))) + (overlays-in beg-B end-B))))) ;; Get contents. (body-A (buffer-substring beg-A end-A)) (body-B (delete-and-extract-region beg-B end-B))) (goto-char beg-B) (when specialp (setq body-B (replace-regexp-in-string "\\`[ \t]*" "" body-B)) - (org-indent-to-column ind-B)) + (indent-to-column ind-B)) (insert body-A) ;; Restore ex ELEM-A overlays. (let ((offset (- beg-B beg-A))) - (mapc (lambda (ov) - (move-overlay - (car ov) (+ (nth 1 ov) offset) (+ (nth 2 ov) offset))) - (car overlays)) + (dolist (o (car overlays)) + (move-overlay (car o) (+ (nth 1 o) offset) (+ (nth 2 o) offset))) (goto-char beg-A) (delete-region beg-A end-A) (insert body-B) ;; Restore ex ELEM-B overlays. - (mapc (lambda (ov) - (move-overlay - (car ov) (- (nth 1 ov) offset) (- (nth 2 ov) offset))) - (cdr overlays))) + (dolist (o (cdr overlays)) + (move-overlay (car o) (- (nth 1 o) offset) (- (nth 2 o) offset)))) (goto-char (org-element-property :end elem-B))))) + (provide 'org-element) ;; Local variables: diff --git a/lisp/org/org-entities.el b/lisp/org/org-entities.el index 3ca2cceea7e..a138764fad1 100644 --- a/lisp/org/org-entities.el +++ b/lisp/org/org-entities.el @@ -1,4 +1,4 @@ -;;; org-entities.el --- Support for special entities in Org-mode +;;; org-entities.el --- Support for Special Entities -*- lexical-binding: t; -*- ;; Copyright (C) 2010-2017 Free Software Foundation, Inc. @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Commentary: @@ -30,38 +30,36 @@ (declare-function org-toggle-pretty-entities "org" ()) (declare-function org-table-align "org-table" ()) -(eval-when-compile - (require 'cl)) - (defgroup org-entities nil - "Options concerning entities in Org-mode." + "Options concerning entities in Org mode." :tag "Org Entities" :group 'org) -(defcustom org-entities-ascii-explanatory nil - "Non-nil means replace special entities in ASCII. -For example, this will replace \"\\nsup\" with \"[not a superset of]\" -in backends where the corresponding character is not available." - :group 'org-entities - :version "24.1" - :type 'boolean) +(defun org-entities--user-safe-p (v) + "Non-nil if V is a safe value for `org-entities-user'." + (pcase v + (`nil t) + (`(,(and (pred stringp) + (pred (string-match-p "\\`[a-zA-Z][a-zA-Z0-9]*\\'"))) + ,(pred stringp) ,(pred booleanp) ,(pred stringp) + ,(pred stringp) ,(pred stringp) ,(pred stringp)) + t) + (_ nil))) (defcustom org-entities-user nil - "User-defined entities used in Org-mode to produce special characters. + "User-defined entities used in Org to produce special characters. Each entry in this list is a list of strings. It associates the name of the entity that can be inserted into an Org file as \\name with the appropriate replacements for the different export backends. The order of the fields is the following -name As a string, without the leading backslash -LaTeX replacement In ready LaTeX, no further processing will take place -LaTeX mathp A Boolean, either t or nil. t if this entity needs - to be in math mode. +name As a string, without the leading backslash. +LaTeX replacement In ready LaTeX, no further processing will take place. +LaTeX mathp Either t or nil. When t this entity needs to be in + math mode. HTML replacement In ready HTML, no further processing will take place. Usually this will be an &...; entity. -ASCII replacement Plain ASCII, no extensions. Symbols that cannot be - represented will be left as they are, but see the. - variable `org-entities-ascii-explanatory'. +ASCII replacement Plain ASCII, no extensions. Latin1 replacement Use the special characters available in latin1. utf-8 replacement Use the special characters available in utf-8. @@ -77,439 +75,456 @@ packages to be loaded, add these packages to `org-latex-packages-alist'." (string :tag "HTML ") (string :tag "ASCII ") (string :tag "Latin1") - (string :tag "utf-8 ")))) + (string :tag "utf-8 "))) + :safe #'org-entities--user-safe-p) (defconst org-entities - '( - "* Letters" - "** Latin" - ("Agrave" "\\`{A}" nil "À" "A" "À" "À") - ("agrave" "\\`{a}" nil "à" "a" "à" "à") - ("Aacute" "\\'{A}" nil "Á" "A" "Á" "Á") - ("aacute" "\\'{a}" nil "á" "a" "á" "á") - ("Acirc" "\\^{A}" nil "Â" "A" "Â" "Â") - ("acirc" "\\^{a}" nil "â" "a" "â" "â") - ("Atilde" "\\~{A}" nil "Ã" "A" "Ã" "Ã") - ("atilde" "\\~{a}" nil "ã" "a" "ã" "ã") - ("Auml" "\\\"{A}" nil "Ä" "Ae" "Ä" "Ä") - ("auml" "\\\"{a}" nil "ä" "ae" "ä" "ä") - ("Aring" "\\AA{}" nil "Å" "A" "Å" "Å") - ("AA" "\\AA{}" nil "Å" "A" "Å" "Å") - ("aring" "\\aa{}" nil "å" "a" "å" "å") - ("AElig" "\\AE{}" nil "Æ" "AE" "Æ" "Æ") - ("aelig" "\\ae{}" nil "æ" "ae" "æ" "æ") - ("Ccedil" "\\c{C}" nil "Ç" "C" "Ç" "Ç") - ("ccedil" "\\c{c}" nil "ç" "c" "ç" "ç") - ("Egrave" "\\`{E}" nil "È" "E" "È" "È") - ("egrave" "\\`{e}" nil "è" "e" "è" "è") - ("Eacute" "\\'{E}" nil "É" "E" "É" "É") - ("eacute" "\\'{e}" nil "é" "e" "é" "é") - ("Ecirc" "\\^{E}" nil "Ê" "E" "Ê" "Ê") - ("ecirc" "\\^{e}" nil "ê" "e" "ê" "ê") - ("Euml" "\\\"{E}" nil "Ë" "E" "Ë" "Ë") - ("euml" "\\\"{e}" nil "ë" "e" "ë" "ë") - ("Igrave" "\\`{I}" nil "Ì" "I" "Ì" "Ì") - ("igrave" "\\`{i}" nil "ì" "i" "ì" "ì") - ("Iacute" "\\'{I}" nil "Í" "I" "Í" "Í") - ("iacute" "\\'{i}" nil "í" "i" "í" "í") - ("Icirc" "\\^{I}" nil "Î" "I" "Î" "Î") - ("icirc" "\\^{i}" nil "î" "i" "î" "î") - ("Iuml" "\\\"{I}" nil "Ï" "I" "Ï" "Ï") - ("iuml" "\\\"{i}" nil "ï" "i" "ï" "ï") - ("Ntilde" "\\~{N}" nil "Ñ" "N" "Ñ" "Ñ") - ("ntilde" "\\~{n}" nil "ñ" "n" "ñ" "ñ") - ("Ograve" "\\`{O}" nil "Ò" "O" "Ò" "Ò") - ("ograve" "\\`{o}" nil "ò" "o" "ò" "ò") - ("Oacute" "\\'{O}" nil "Ó" "O" "Ó" "Ó") - ("oacute" "\\'{o}" nil "ó" "o" "ó" "ó") - ("Ocirc" "\\^{O}" nil "Ô" "O" "Ô" "Ô") - ("ocirc" "\\^{o}" nil "ô" "o" "ô" "ô") - ("Otilde" "\\~{O}" nil "Õ" "O" "Õ" "Õ") - ("otilde" "\\~{o}" nil "õ" "o" "õ" "õ") - ("Ouml" "\\\"{O}" nil "Ö" "Oe" "Ö" "Ö") - ("ouml" "\\\"{o}" nil "ö" "oe" "ö" "ö") - ("Oslash" "\\O" nil "Ø" "O" "Ø" "Ø") - ("oslash" "\\o{}" nil "ø" "o" "ø" "ø") - ("OElig" "\\OE{}" nil "Œ" "OE" "OE" "Œ") - ("oelig" "\\oe{}" nil "œ" "oe" "oe" "œ") - ("Scaron" "\\v{S}" nil "Š" "S" "S" "Š") - ("scaron" "\\v{s}" nil "š" "s" "s" "š") - ("szlig" "\\ss{}" nil "ß" "ss" "ß" "ß") - ("Ugrave" "\\`{U}" nil "Ù" "U" "Ù" "Ù") - ("ugrave" "\\`{u}" nil "ù" "u" "ù" "ù") - ("Uacute" "\\'{U}" nil "Ú" "U" "Ú" "Ú") - ("uacute" "\\'{u}" nil "ú" "u" "ú" "ú") - ("Ucirc" "\\^{U}" nil "Û" "U" "Û" "Û") - ("ucirc" "\\^{u}" nil "û" "u" "û" "û") - ("Uuml" "\\\"{U}" nil "Ü" "Ue" "Ü" "Ü") - ("uuml" "\\\"{u}" nil "ü" "ue" "ü" "ü") - ("Yacute" "\\'{Y}" nil "Ý" "Y" "Ý" "Ý") - ("yacute" "\\'{y}" nil "ý" "y" "ý" "ý") - ("Yuml" "\\\"{Y}" nil "Ÿ" "Y" "Y" "Ÿ") - ("yuml" "\\\"{y}" nil "ÿ" "y" "ÿ" "ÿ") - - "** Latin (special face)" - ("fnof" "\\textit{f}" nil "ƒ" "f" "f" "ƒ") - ("real" "\\Re" t "ℜ" "R" "R" "ℜ") - ("image" "\\Im" t "ℑ" "I" "I" "ℑ") - ("weierp" "\\wp" t "℘" "P" "P" "℘") - ("ell" "\\ell" t "ℓ" "ell" "ell" "ℓ") - ("imath" "\\imath" t "ı" "[dotless i]" "dotless i" "ı") - ("jmath" "\\jmath" t "ȷ" "[dotless j]" "dotless j" "ȷ") - - "** Greek" - ("Alpha" "A" nil "Α" "Alpha" "Alpha" "Α") - ("alpha" "\\alpha" t "α" "alpha" "alpha" "α") - ("Beta" "B" nil "Β" "Beta" "Beta" "Β") - ("beta" "\\beta" t "β" "beta" "beta" "β") - ("Gamma" "\\Gamma" t "Γ" "Gamma" "Gamma" "Γ") - ("gamma" "\\gamma" t "γ" "gamma" "gamma" "γ") - ("Delta" "\\Delta" t "Δ" "Delta" "Gamma" "Δ") - ("delta" "\\delta" t "δ" "delta" "delta" "δ") - ("Epsilon" "E" nil "Ε" "Epsilon" "Epsilon" "Ε") - ("epsilon" "\\epsilon" t "ε" "epsilon" "epsilon" "ε") - ("varepsilon" "\\varepsilon" t "ε" "varepsilon" "varepsilon" "ε") - ("Zeta" "Z" nil "Ζ" "Zeta" "Zeta" "Ζ") - ("zeta" "\\zeta" t "ζ" "zeta" "zeta" "ζ") - ("Eta" "H" nil "Η" "Eta" "Eta" "Η") - ("eta" "\\eta" t "η" "eta" "eta" "η") - ("Theta" "\\Theta" t "Θ" "Theta" "Theta" "Θ") - ("theta" "\\theta" t "θ" "theta" "theta" "θ") - ("thetasym" "\\vartheta" t "ϑ" "theta" "theta" "ϑ") - ("vartheta" "\\vartheta" t "ϑ" "theta" "theta" "ϑ") - ("Iota" "I" nil "Ι" "Iota" "Iota" "Ι") - ("iota" "\\iota" t "ι" "iota" "iota" "ι") - ("Kappa" "K" nil "Κ" "Kappa" "Kappa" "Κ") - ("kappa" "\\kappa" t "κ" "kappa" "kappa" "κ") - ("Lambda" "\\Lambda" t "Λ" "Lambda" "Lambda" "Λ") - ("lambda" "\\lambda" t "λ" "lambda" "lambda" "λ") - ("Mu" "M" nil "Μ" "Mu" "Mu" "Μ") - ("mu" "\\mu" t "μ" "mu" "mu" "μ") - ("nu" "\\nu" t "ν" "nu" "nu" "ν") - ("Nu" "N" nil "Ν" "Nu" "Nu" "Ν") - ("Xi" "\\Xi" t "Ξ" "Xi" "Xi" "Ξ") - ("xi" "\\xi" t "ξ" "xi" "xi" "ξ") - ("Omicron" "O" nil "Ο" "Omicron" "Omicron" "Ο") - ("omicron" "\\textit{o}" nil "ο" "omicron" "omicron" "ο") - ("Pi" "\\Pi" t "Π" "Pi" "Pi" "Π") - ("pi" "\\pi" t "π" "pi" "pi" "π") - ("Rho" "P" nil "Ρ" "Rho" "Rho" "Ρ") - ("rho" "\\rho" t "ρ" "rho" "rho" "ρ") - ("Sigma" "\\Sigma" t "Σ" "Sigma" "Sigma" "Σ") - ("sigma" "\\sigma" t "σ" "sigma" "sigma" "σ") - ("sigmaf" "\\varsigma" t "ς" "sigmaf" "sigmaf" "ς") - ("varsigma" "\\varsigma" t "ς" "varsigma" "varsigma" "ς") - ("Tau" "T" nil "Τ" "Tau" "Tau" "Τ") - ("Upsilon" "\\Upsilon" t "Υ" "Upsilon" "Upsilon" "Υ") - ("upsih" "\\Upsilon" t "ϒ" "upsilon" "upsilon" "ϒ") - ("upsilon" "\\upsilon" t "υ" "upsilon" "upsilon" "υ") - ("Phi" "\\Phi" t "Φ" "Phi" "Phi" "Φ") - ("phi" "\\phi" t "φ" "phi" "phi" "φ") - ("varphi" "\\varphi" t "ϕ" "varphi" "varphi" "ɸ") - ("Chi" "X" nil "Χ" "Chi" "Chi" "Χ") - ("chi" "\\chi" t "χ" "chi" "chi" "χ") - ("acutex" "\\acute x" t "´x" "'x" "'x" "𝑥́") - ("Psi" "\\Psi" t "Ψ" "Psi" "Psi" "Ψ") - ("psi" "\\psi" t "ψ" "psi" "psi" "ψ") - ("tau" "\\tau" t "τ" "tau" "tau" "τ") - ("Omega" "\\Omega" t "Ω" "Omega" "Omega" "Ω") - ("omega" "\\omega" t "ω" "omega" "omega" "ω") - ("piv" "\\varpi" t "ϖ" "omega-pi" "omega-pi" "ϖ") - ("varpi" "\\varpi" t "ϖ" "omega-pi" "omega-pi" "ϖ") - ("partial" "\\partial" t "∂" "[partial differential]" "[partial differential]" "∂") - - "** Hebrew" - ("alefsym" "\\aleph" t "ℵ" "aleph" "aleph" "ℵ") - ("aleph" "\\aleph" t "ℵ" "aleph" "aleph" "ℵ") - ("gimel" "\\gimel" t "ℷ" "gimel" "gimel" "ℷ") - ("beth" "\\beth" t "ℶ" "beth" "beth" "ב") - ("dalet" "\\daleth" t "ℸ" "dalet" "dalet" "ד") - - "** Dead languages" - ("ETH" "\\DH{}" nil "Ð" "D" "Ð" "Ð") - ("eth" "\\dh{}" nil "ð" "dh" "ð" "ð") - ("THORN" "\\TH{}" nil "Þ" "TH" "Þ" "Þ") - ("thorn" "\\th{}" nil "þ" "th" "þ" "þ") - - "* Punctuation" - "** Dots and Marks" - ("dots" "\\dots{}" nil "…" "..." "..." "…") - ("cdots" "\\cdots{}" t "⋯" "..." "..." "⋯") - ("hellip" "\\dots{}" nil "…" "..." "..." "…") - ("middot" "\\textperiodcentered{}" nil "·" "." "·" "·") - ("iexcl" "!`" nil "¡" "!" "¡" "¡") - ("iquest" "?`" nil "¿" "?" "¿" "¿") - - "** Dash-like" - ("shy" "\\-" nil "­" "" "" "") - ("ndash" "--" nil "–" "-" "-" "–") - ("mdash" "---" nil "—" "--" "--" "—") - - "** Quotations" - ("quot" "\\textquotedbl{}" nil """ "\"" "\"" "\"") - ("acute" "\\textasciiacute{}" nil "´" "'" "´" "´") - ("ldquo" "\\textquotedblleft{}" nil "“" "\"" "\"" "“") - ("rdquo" "\\textquotedblright{}" nil "”" "\"" "\"" "”") - ("bdquo" "\\quotedblbase{}" nil "„" "\"" "\"" "„") - ("lsquo" "\\textquoteleft{}" nil "‘" "`" "`" "‘") - ("rsquo" "\\textquoteright{}" nil "’" "'" "'" "’") - ("sbquo" "\\quotesinglbase{}" nil "‚" "," "," "‚") - ("laquo" "\\guillemotleft{}" nil "«" "<<" "«" "«") - ("raquo" "\\guillemotright{}" nil "»" ">>" "»" "»") - ("lsaquo" "\\guilsinglleft{}" nil "‹" "<" "<" "‹") - ("rsaquo" "\\guilsinglright{}" nil "›" ">" ">" "›") - - "* Other" - "** Misc. (often used)" - ("circ" "\\^{}" nil "ˆ" "^" "^" "ˆ") - ("vert" "\\vert{}" t "|" "|" "|" "|") - ("brvbar" "\\textbrokenbar{}" nil "¦" "|" "¦" "¦") - ("S" "\\S" nil "§" "paragraph" "§" "§") - ("sect" "\\S" nil "§" "paragraph" "§" "§") - ("amp" "\\&" nil "&" "&" "&" "&") - ("lt" "\\textless{}" nil "<" "<" "<" "<") - ("gt" "\\textgreater{}" nil ">" ">" ">" ">") - ("tilde" "\\textasciitilde{}" nil "~" "~" "~" "~") - ("slash" "/" nil "/" "/" "/" "/") - ("plus" "+" nil "+" "+" "+" "+") - ("under" "\\_" nil "_" "_" "_" "_") - ("equal" "=" nil "=" "=" "=" "=") - ("asciicirc" "\\textasciicircum{}" nil "^" "^" "^" "^") - ("dagger" "\\textdagger{}" nil "†" "[dagger]" "[dagger]" "†") - ("dag" "\\dag{}" nil "†" "[dagger]" "[dagger]" "†") - ("Dagger" "\\textdaggerdbl{}" nil "‡" "[doubledagger]" "[doubledagger]" "‡") - ("ddag" "\\ddag{}" nil "‡" "[doubledagger]" "[doubledagger]" "‡") - - "** Whitespace" - ("nbsp" "~" nil " " " " " " " ") - ("ensp" "\\hspace*{.5em}" nil " " " " " " " ") - ("emsp" "\\hspace*{1em}" nil " " " " " " " ") - ("thinsp" "\\hspace*{.2em}" nil " " " " " " " ") - - "** Currency" - ("curren" "\\textcurrency{}" nil "¤" "curr." "¤" "¤") - ("cent" "\\textcent{}" nil "¢" "cent" "¢" "¢") - ("pound" "\\pounds{}" nil "£" "pound" "£" "£") - ("yen" "\\textyen{}" nil "¥" "yen" "¥" "¥") - ("euro" "\\texteuro{}" nil "€" "EUR" "EUR" "€") - ("EUR" "\\EUR{}" nil "€" "EUR" "EUR" "€") - ("EURdig" "\\EURdig{}" nil "€" "EUR" "EUR" "€") - ("EURhv" "\\EURhv{}" nil "€" "EUR" "EUR" "€") - ("EURcr" "\\EURcr{}" nil "€" "EUR" "EUR" "€") - ("EURtm" "\\EURtm{}" nil "€" "EUR" "EUR" "€") - - "** Property Marks" - ("copy" "\\textcopyright{}" nil "©" "(c)" "©" "©") - ("reg" "\\textregistered{}" nil "®" "(r)" "®" "®") - ("trade" "\\texttrademark{}" nil "™" "TM" "TM" "™") - - "** Science et al." - ("minus" "\\minus" t "−" "-" "-" "−") - ("pm" "\\textpm{}" nil "±" "+-" "±" "±") - ("plusmn" "\\textpm{}" nil "±" "+-" "±" "±") - ("times" "\\texttimes{}" nil "×" "*" "×" "×") - ("frasl" "/" nil "⁄" "/" "/" "⁄") - ("colon" "\\colon" t ":" ":" ":" ":") - ("div" "\\textdiv{}" nil "÷" "/" "÷" "÷") - ("frac12" "\\textonehalf{}" nil "½" "1/2" "½" "½") - ("frac14" "\\textonequarter{}" nil "¼" "1/4" "¼" "¼") - ("frac34" "\\textthreequarters{}" nil "¾" "3/4" "¾" "¾") - ("permil" "\\textperthousand{}" nil "‰" "per thousand" "per thousand" "‰") - ("sup1" "\\textonesuperior{}" nil "¹" "^1" "¹" "¹") - ("sup2" "\\texttwosuperior{}" nil "²" "^2" "²" "²") - ("sup3" "\\textthreesuperior{}" nil "³" "^3" "³" "³") - ("radic" "\\sqrt{\\,}" t "√" "[square root]" "[square root]" "√") - ("sum" "\\sum" t "∑" "[sum]" "[sum]" "∑") - ("prod" "\\prod" t "∏" "[product]" "[n-ary product]" "∏") - ("micro" "\\textmu{}" nil "µ" "micro" "µ" "µ") - ("macr" "\\textasciimacron{}" nil "¯" "[macron]" "¯" "¯") - ("deg" "\\textdegree{}" nil "°" "degree" "°" "°") - ("prime" "\\prime" t "′" "'" "'" "′") - ("Prime" "\\prime{}\\prime" t "″" "''" "''" "″") - ("infin" "\\propto" t "∞" "[infinity]" "[infinity]" "∞") - ("infty" "\\infty" t "∞" "[infinity]" "[infinity]" "∞") - ("prop" "\\propto" t "∝" "[proportional to]" "[proportional to]" "∝") - ("propto" "\\propto" t "∝" "[proportional to]" "[proportional to]" "∝") - ("not" "\\textlnot{}" nil "¬" "[angled dash]" "¬" "¬") - ("neg" "\\neg{}" t "¬" "[angled dash]" "¬" "¬") - ("land" "\\land" t "∧" "[logical and]" "[logical and]" "∧") - ("wedge" "\\wedge" t "∧" "[logical and]" "[logical and]" "∧") - ("lor" "\\lor" t "∨" "[logical or]" "[logical or]" "∨") - ("vee" "\\vee" t "∨" "[logical or]" "[logical or]" "∨") - ("cap" "\\cap" t "∩" "[intersection]" "[intersection]" "∩") - ("cup" "\\cup" t "∪" "[union]" "[union]" "∪") - ("int" "\\int" t "∫" "[integral]" "[integral]" "∫") - ("therefore" "\\therefore" t "∴" "[therefore]" "[therefore]" "∴") - ("there4" "\\therefore" t "∴" "[therefore]" "[therefore]" "∴") - ("because" "\\because" t "∵" "[because]" "[because]" "∵") - ("sim" "\\sim" t "∼" "~" "~" "∼") - ("cong" "\\cong" t "≅" "[approx. equal to]" "[approx. equal to]" "≅") - ("simeq" "\\simeq" t "≅" "[approx. equal to]" "[approx. equal to]" "≅") - ("asymp" "\\asymp" t "≈" "[almost equal to]" "[almost equal to]" "≈") - ("approx" "\\approx" t "≈" "[almost equal to]" "[almost equal to]" "≈") - ("ne" "\\ne" t "≠" "[not equal to]" "[not equal to]" "≠") - ("neq" "\\neq" t "≠" "[not equal to]" "[not equal to]" "≠") - ("equiv" "\\equiv" t "≡" "[identical to]" "[identical to]" "≡") - - ("triangleq" "\\triangleq" t "≜" "[defined to]" "[defined to]" "≜") - ("le" "\\le" t "≤" "<=" "<=" "≤") - ("leq" "\\le" t "≤" "<=" "<=" "≤") - ("ge" "\\ge" t "≥" ">=" ">=" "≥") - ("geq" "\\ge" t "≥" ">=" ">=" "≥") - ("lessgtr" "\\lessgtr" t "≶" "[less than or greater than]" "[less than or greater than]" "≶") - ("lesseqgtr" "\\lesseqgtr" t "⋚" "[less than or equal or greater than or equal]" "[less than or equal or greater than or equal]" "⋚") - ("ll" "\\ll" t "≪" "<<" "<<" "≪") - ("Ll" "\\lll" t "⋘" "<<<" "<<<" "⋘") - ("lll" "\\lll" t "⋘" "<<<" "<<<" "⋘") - ("gg" "\\gg" t "≫" ">>" ">>" "≫") - ("Gg" "\\ggg" t "⋙" ">>>" ">>>" "⋙") - ("ggg" "\\ggg" t "⋙" ">>>" ">>>" "⋙") - ("prec" "\\prec" t "≺" "[precedes]" "[precedes]" "≺") - ("preceq" "\\preceq" t "≼" "[precedes or equal]" "[precedes or equal]" "≼") - ("preccurlyeq" "\\preccurlyeq" t "≼" "[precedes or equal]" "[precedes or equal]" "≼") - ("succ" "\\succ" t "≻" "[succeeds]" "[succeeds]" "≻") - ("succeq" "\\succeq" t "≽" "[succeeds or equal]" "[succeeds or equal]" "≽") - ("succcurlyeq" "\\succcurlyeq" t "≽" "[succeeds or equal]" "[succeeds or equal]" "≽") - ("sub" "\\subset" t "⊂" "[subset of]" "[subset of]" "⊂") - ("subset" "\\subset" t "⊂" "[subset of]" "[subset of]" "⊂") - ("sup" "\\supset" t "⊃" "[superset of]" "[superset of]" "⊃") - ("supset" "\\supset" t "⊃" "[superset of]" "[superset of]" "⊃") - ("nsub" "\\not\\subset" t "⊄" "[not a subset of]" "[not a subset of" "⊄") - ("sube" "\\subseteq" t "⊆" "[subset of or equal to]" "[subset of or equal to]" "⊆") - ("nsup" "\\not\\supset" t "⊅" "[not a superset of]" "[not a superset of]" "⊅") - ("supe" "\\supseteq" t "⊇" "[superset of or equal to]" "[superset of or equal to]" "⊇") - ("setminus" "\\setminus" t "∖" "\" "\" "⧵") - ("forall" "\\forall" t "∀" "[for all]" "[for all]" "∀") - ("exist" "\\exists" t "∃" "[there exists]" "[there exists]" "∃") - ("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]" "∅") - ("emptyset" "\\emptyset" t "∅" "[empty set]" "[empty set]" "∅") - ("isin" "\\in" t "∈" "[element of]" "[element of]" "∈") - ("in" "\\in" t "∈" "[element of]" "[element of]" "∈") - ("notin" "\\notin" t "∉" "[not an element of]" "[not an element of]" "∉") - ("ni" "\\ni" t "∋" "[contains as member]" "[contains as member]" "∋") - ("nabla" "\\nabla" t "∇" "[nabla]" "[nabla]" "∇") - ("ang" "\\angle" t "∠" "[angle]" "[angle]" "∠") - ("angle" "\\angle" t "∠" "[angle]" "[angle]" "∠") - ("perp" "\\perp" t "⊥" "[up tack]" "[up tack]" "⊥") - ("sdot" "\\cdot" t "⋅" "[dot]" "[dot]" "⋅") - ("cdot" "\\cdot" t "⋅" "[dot]" "[dot]" "⋅") - ("lceil" "\\lceil" t "⌈" "[left ceiling]" "[left ceiling]" "⌈") - ("rceil" "\\rceil" t "⌉" "[right ceiling]" "[right ceiling]" "⌉") - ("lfloor" "\\lfloor" t "⌊" "[left floor]" "[left floor]" "⌊") - ("rfloor" "\\rfloor" t "⌋" "[right floor]" "[right floor]" "⌋") - ("lang" "\\langle" t "⟨" "<" "<" "⟨") - ("rang" "\\rangle" t "⟩" ">" ">" "⟩") - ("hbar" "\\hbar" t "ℏ" "hbar" "hbar" "ℏ") - ("mho" "\\mho" t "℧" "mho" "mho" "℧") - - "** Arrows" - ("larr" "\\leftarrow" t "←" "<-" "<-" "←") - ("leftarrow" "\\leftarrow" t "←" "<-" "<-" "←") - ("gets" "\\gets" t "←" "<-" "<-" "←") - ("lArr" "\\Leftarrow" t "⇐" "<=" "<=" "⇐") - ("Leftarrow" "\\Leftarrow" t "⇐" "<=" "<=" "⇐") - ("uarr" "\\uparrow" t "↑" "[uparrow]" "[uparrow]" "↑") - ("uparrow" "\\uparrow" t "↑" "[uparrow]" "[uparrow]" "↑") - ("uArr" "\\Uparrow" t "⇑" "[dbluparrow]" "[dbluparrow]" "⇑") - ("Uparrow" "\\Uparrow" t "⇑" "[dbluparrow]" "[dbluparrow]" "⇑") - ("rarr" "\\rightarrow" t "→" "->" "->" "→") - ("to" "\\to" t "→" "->" "->" "→") - ("rightarrow" "\\rightarrow" t "→" "->" "->" "→") - ("rArr" "\\Rightarrow" t "⇒" "=>" "=>" "⇒") - ("Rightarrow" "\\Rightarrow" t "⇒" "=>" "=>" "⇒") - ("darr" "\\downarrow" t "↓" "[downarrow]" "[downarrow]" "↓") - ("downarrow" "\\downarrow" t "↓" "[downarrow]" "[downarrow]" "↓") - ("dArr" "\\Downarrow" t "⇓" "[dbldownarrow]" "[dbldownarrow]" "⇓") - ("Downarrow" "\\Downarrow" t "⇓" "[dbldownarrow]" "[dbldownarrow]" "⇓") - ("harr" "\\leftrightarrow" t "↔" "<->" "<->" "↔") - ("leftrightarrow" "\\leftrightarrow" t "↔" "<->" "<->" "↔") - ("hArr" "\\Leftrightarrow" t "⇔" "<=>" "<=>" "⇔") - ("Leftrightarrow" "\\Leftrightarrow" t "⇔" "<=>" "<=>" "⇔") - ("crarr" "\\hookleftarrow" t "↵" "<-'" "<-'" "↵") - ("hookleftarrow" "\\hookleftarrow" t "↵" "<-'" "<-'" "↵") - - "** Function names" - ("arccos" "\\arccos" t "arccos" "arccos" "arccos" "arccos") - ("arcsin" "\\arcsin" t "arcsin" "arcsin" "arcsin" "arcsin") - ("arctan" "\\arctan" t "arctan" "arctan" "arctan" "arctan") - ("arg" "\\arg" t "arg" "arg" "arg" "arg") - ("cos" "\\cos" t "cos" "cos" "cos" "cos") - ("cosh" "\\cosh" t "cosh" "cosh" "cosh" "cosh") - ("cot" "\\cot" t "cot" "cot" "cot" "cot") - ("coth" "\\coth" t "coth" "coth" "coth" "coth") - ("csc" "\\csc" t "csc" "csc" "csc" "csc") - ("deg" "\\deg" t "°" "deg" "deg" "deg") - ("det" "\\det" t "det" "det" "det" "det") - ("dim" "\\dim" t "dim" "dim" "dim" "dim") - ("exp" "\\exp" t "exp" "exp" "exp" "exp") - ("gcd" "\\gcd" t "gcd" "gcd" "gcd" "gcd") - ("hom" "\\hom" t "hom" "hom" "hom" "hom") - ("inf" "\\inf" t "inf" "inf" "inf" "inf") - ("ker" "\\ker" t "ker" "ker" "ker" "ker") - ("lg" "\\lg" t "lg" "lg" "lg" "lg") - ("lim" "\\lim" t "lim" "lim" "lim" "lim") - ("liminf" "\\liminf" t "liminf" "liminf" "liminf" "liminf") - ("limsup" "\\limsup" t "limsup" "limsup" "limsup" "limsup") - ("ln" "\\ln" t "ln" "ln" "ln" "ln") - ("log" "\\log" t "log" "log" "log" "log") - ("max" "\\max" t "max" "max" "max" "max") - ("min" "\\min" t "min" "min" "min" "min") - ("Pr" "\\Pr" t "Pr" "Pr" "Pr" "Pr") - ("sec" "\\sec" t "sec" "sec" "sec" "sec") - ("sin" "\\sin" t "sin" "sin" "sin" "sin") - ("sinh" "\\sinh" t "sinh" "sinh" "sinh" "sinh") - ("sup" "\\sup" t "⊃" "sup" "sup" "sup") - ("tan" "\\tan" t "tan" "tan" "tan" "tan") - ("tanh" "\\tanh" t "tanh" "tanh" "tanh" "tanh") - - "** Signs & Symbols" - ("bull" "\\textbullet{}" nil "•" "*" "*" "•") - ("bullet" "\\textbullet{}" nil "•" "*" "*" "•") - ("star" "\\star" t "*" "*" "*" "⋆") - ("lowast" "\\ast" t "∗" "*" "*" "∗") - ("ast" "\\ast" t "∗" "*" "*" "*") - ("odot" "\\odot" t "o" "[circled dot]" "[circled dot]" "ʘ") - ("oplus" "\\oplus" t "⊕" "[circled plus]" "[circled plus]" "⊕") - ("otimes" "\\otimes" t "⊗" "[circled times]" "[circled times]" "⊗") - ("check" "\\checkmark" t "✓" "[checkmark]" "[checkmark]" "✓") - ("checkmark" "\\checkmark" t "✓" "[checkmark]" "[checkmark]" "✓") - - "** Miscellaneous (seldom used)" - ("para" "\\P{}" nil "¶" "[pilcrow]" "¶" "¶") - ("ordf" "\\textordfeminine{}" nil "ª" "_a_" "ª" "ª") - ("ordm" "\\textordmasculine{}" nil "º" "_o_" "º" "º") - ("cedil" "\\c{}" nil "¸" "[cedilla]" "¸" "¸") - ("oline" "\\overline{~}" t "‾" "[overline]" "¯" "‾") - ("uml" "\\textasciidieresis{}" nil "¨" "[diaeresis]" "¨" "¨") - ("zwnj" "\\/{}" nil "‌" "" "" "") - ("zwj" "" nil "‍" "" "" "") - ("lrm" "" nil "‎" "" "" "") - ("rlm" "" nil "‏" "" "" "") - - "** Smilies" - ("smile" "\\smile" t "⌣" ":-)" ":-)" "⌣") - ("frown" "\\frown" t "⌢" ":-(" ":-(" "⌢") - ("smiley" "\\smiley{}" nil "☺" ":-)" ":-)" "☺") - ("blacksmile" "\\blacksmiley{}" nil "☻" ":-)" ":-)" "☻") - ("sad" "\\frownie{}" nil "☹" ":-(" ":-(" "☹") - - "** Suits" - ("clubs" "\\clubsuit" t "♣" "[clubs]" "[clubs]" "♣") - ("clubsuit" "\\clubsuit" t "♣" "[clubs]" "[clubs]" "♣") - ("spades" "\\spadesuit" t "♠" "[spades]" "[spades]" "♠") - ("spadesuit" "\\spadesuit" t "♠" "[spades]" "[spades]" "♠") - ("hearts" "\\heartsuit" t "♥" "[hearts]" "[hearts]" "♥") - ("heartsuit" "\\heartsuit" t "♥" "[hearts]" "[hearts]" "♥") - ("diams" "\\diamondsuit" t "♦" "[diamonds]" "[diamonds]" "◆") - ("diamondsuit" "\\diamondsuit" t "♦" "[diamonds]" "[diamonds]" "◆") - ("diamond" "\\diamondsuit" t "⋄" "[diamond]" "[diamond]" "◆") - ("Diamond" "\\diamondsuit" t "⋄" "[diamond]" "[diamond]" "◆") - ("loz" "\\lozenge" t "◊" "[lozenge]" "[lozenge]" "⧫") - ) - "Default entities used in Org-mode to produce special characters. + (append + '("* Letters" + "** Latin" + ("Agrave" "\\`{A}" nil "À" "A" "À" "À") + ("agrave" "\\`{a}" nil "à" "a" "à" "à") + ("Aacute" "\\'{A}" nil "Á" "A" "Á" "Á") + ("aacute" "\\'{a}" nil "á" "a" "á" "á") + ("Acirc" "\\^{A}" nil "Â" "A" "Â" "Â") + ("acirc" "\\^{a}" nil "â" "a" "â" "â") + ("Amacr" "\\bar{A}" nil "Ā" "A" "Ã" "Ã") + ("amacr" "\\bar{a}" nil "ā" "a" "ã" "ã") + ("Atilde" "\\~{A}" nil "Ã" "A" "Ã" "Ã") + ("atilde" "\\~{a}" nil "ã" "a" "ã" "ã") + ("Auml" "\\\"{A}" nil "Ä" "Ae" "Ä" "Ä") + ("auml" "\\\"{a}" nil "ä" "ae" "ä" "ä") + ("Aring" "\\AA{}" nil "Å" "A" "Å" "Å") + ("AA" "\\AA{}" nil "Å" "A" "Å" "Å") + ("aring" "\\aa{}" nil "å" "a" "å" "å") + ("AElig" "\\AE{}" nil "Æ" "AE" "Æ" "Æ") + ("aelig" "\\ae{}" nil "æ" "ae" "æ" "æ") + ("Ccedil" "\\c{C}" nil "Ç" "C" "Ç" "Ç") + ("ccedil" "\\c{c}" nil "ç" "c" "ç" "ç") + ("Egrave" "\\`{E}" nil "È" "E" "È" "È") + ("egrave" "\\`{e}" nil "è" "e" "è" "è") + ("Eacute" "\\'{E}" nil "É" "E" "É" "É") + ("eacute" "\\'{e}" nil "é" "e" "é" "é") + ("Ecirc" "\\^{E}" nil "Ê" "E" "Ê" "Ê") + ("ecirc" "\\^{e}" nil "ê" "e" "ê" "ê") + ("Euml" "\\\"{E}" nil "Ë" "E" "Ë" "Ë") + ("euml" "\\\"{e}" nil "ë" "e" "ë" "ë") + ("Igrave" "\\`{I}" nil "Ì" "I" "Ì" "Ì") + ("igrave" "\\`{i}" nil "ì" "i" "ì" "ì") + ("Iacute" "\\'{I}" nil "Í" "I" "Í" "Í") + ("iacute" "\\'{i}" nil "í" "i" "í" "í") + ("Icirc" "\\^{I}" nil "Î" "I" "Î" "Î") + ("icirc" "\\^{i}" nil "î" "i" "î" "î") + ("Iuml" "\\\"{I}" nil "Ï" "I" "Ï" "Ï") + ("iuml" "\\\"{i}" nil "ï" "i" "ï" "ï") + ("Ntilde" "\\~{N}" nil "Ñ" "N" "Ñ" "Ñ") + ("ntilde" "\\~{n}" nil "ñ" "n" "ñ" "ñ") + ("Ograve" "\\`{O}" nil "Ò" "O" "Ò" "Ò") + ("ograve" "\\`{o}" nil "ò" "o" "ò" "ò") + ("Oacute" "\\'{O}" nil "Ó" "O" "Ó" "Ó") + ("oacute" "\\'{o}" nil "ó" "o" "ó" "ó") + ("Ocirc" "\\^{O}" nil "Ô" "O" "Ô" "Ô") + ("ocirc" "\\^{o}" nil "ô" "o" "ô" "ô") + ("Otilde" "\\~{O}" nil "Õ" "O" "Õ" "Õ") + ("otilde" "\\~{o}" nil "õ" "o" "õ" "õ") + ("Ouml" "\\\"{O}" nil "Ö" "Oe" "Ö" "Ö") + ("ouml" "\\\"{o}" nil "ö" "oe" "ö" "ö") + ("Oslash" "\\O" nil "Ø" "O" "Ø" "Ø") + ("oslash" "\\o{}" nil "ø" "o" "ø" "ø") + ("OElig" "\\OE{}" nil "Œ" "OE" "OE" "Œ") + ("oelig" "\\oe{}" nil "œ" "oe" "oe" "œ") + ("Scaron" "\\v{S}" nil "Š" "S" "S" "Š") + ("scaron" "\\v{s}" nil "š" "s" "s" "š") + ("szlig" "\\ss{}" nil "ß" "ss" "ß" "ß") + ("Ugrave" "\\`{U}" nil "Ù" "U" "Ù" "Ù") + ("ugrave" "\\`{u}" nil "ù" "u" "ù" "ù") + ("Uacute" "\\'{U}" nil "Ú" "U" "Ú" "Ú") + ("uacute" "\\'{u}" nil "ú" "u" "ú" "ú") + ("Ucirc" "\\^{U}" nil "Û" "U" "Û" "Û") + ("ucirc" "\\^{u}" nil "û" "u" "û" "û") + ("Uuml" "\\\"{U}" nil "Ü" "Ue" "Ü" "Ü") + ("uuml" "\\\"{u}" nil "ü" "ue" "ü" "ü") + ("Yacute" "\\'{Y}" nil "Ý" "Y" "Ý" "Ý") + ("yacute" "\\'{y}" nil "ý" "y" "ý" "ý") + ("Yuml" "\\\"{Y}" nil "Ÿ" "Y" "Y" "Ÿ") + ("yuml" "\\\"{y}" nil "ÿ" "y" "ÿ" "ÿ") + + "** Latin (special face)" + ("fnof" "\\textit{f}" nil "ƒ" "f" "f" "ƒ") + ("real" "\\Re" t "ℜ" "R" "R" "ℜ") + ("image" "\\Im" t "ℑ" "I" "I" "ℑ") + ("weierp" "\\wp" t "℘" "P" "P" "℘") + ("ell" "\\ell" t "ℓ" "ell" "ell" "ℓ") + ("imath" "\\imath" t "ı" "[dotless i]" "dotless i" "ı") + ("jmath" "\\jmath" t "ȷ" "[dotless j]" "dotless j" "ȷ") + + "** Greek" + ("Alpha" "A" nil "Α" "Alpha" "Alpha" "Α") + ("alpha" "\\alpha" t "α" "alpha" "alpha" "α") + ("Beta" "B" nil "Β" "Beta" "Beta" "Β") + ("beta" "\\beta" t "β" "beta" "beta" "β") + ("Gamma" "\\Gamma" t "Γ" "Gamma" "Gamma" "Γ") + ("gamma" "\\gamma" t "γ" "gamma" "gamma" "γ") + ("Delta" "\\Delta" t "Δ" "Delta" "Delta" "Δ") + ("delta" "\\delta" t "δ" "delta" "delta" "δ") + ("Epsilon" "E" nil "Ε" "Epsilon" "Epsilon" "Ε") + ("epsilon" "\\epsilon" t "ε" "epsilon" "epsilon" "ε") + ("varepsilon" "\\varepsilon" t "ε" "varepsilon" "varepsilon" "ε") + ("Zeta" "Z" nil "Ζ" "Zeta" "Zeta" "Ζ") + ("zeta" "\\zeta" t "ζ" "zeta" "zeta" "ζ") + ("Eta" "H" nil "Η" "Eta" "Eta" "Η") + ("eta" "\\eta" t "η" "eta" "eta" "η") + ("Theta" "\\Theta" t "Θ" "Theta" "Theta" "Θ") + ("theta" "\\theta" t "θ" "theta" "theta" "θ") + ("thetasym" "\\vartheta" t "ϑ" "theta" "theta" "ϑ") + ("vartheta" "\\vartheta" t "ϑ" "theta" "theta" "ϑ") + ("Iota" "I" nil "Ι" "Iota" "Iota" "Ι") + ("iota" "\\iota" t "ι" "iota" "iota" "ι") + ("Kappa" "K" nil "Κ" "Kappa" "Kappa" "Κ") + ("kappa" "\\kappa" t "κ" "kappa" "kappa" "κ") + ("Lambda" "\\Lambda" t "Λ" "Lambda" "Lambda" "Λ") + ("lambda" "\\lambda" t "λ" "lambda" "lambda" "λ") + ("Mu" "M" nil "Μ" "Mu" "Mu" "Μ") + ("mu" "\\mu" t "μ" "mu" "mu" "μ") + ("nu" "\\nu" t "ν" "nu" "nu" "ν") + ("Nu" "N" nil "Ν" "Nu" "Nu" "Ν") + ("Xi" "\\Xi" t "Ξ" "Xi" "Xi" "Ξ") + ("xi" "\\xi" t "ξ" "xi" "xi" "ξ") + ("Omicron" "O" nil "Ο" "Omicron" "Omicron" "Ο") + ("omicron" "\\textit{o}" nil "ο" "omicron" "omicron" "ο") + ("Pi" "\\Pi" t "Π" "Pi" "Pi" "Π") + ("pi" "\\pi" t "π" "pi" "pi" "π") + ("Rho" "P" nil "Ρ" "Rho" "Rho" "Ρ") + ("rho" "\\rho" t "ρ" "rho" "rho" "ρ") + ("Sigma" "\\Sigma" t "Σ" "Sigma" "Sigma" "Σ") + ("sigma" "\\sigma" t "σ" "sigma" "sigma" "σ") + ("sigmaf" "\\varsigma" t "ς" "sigmaf" "sigmaf" "ς") + ("varsigma" "\\varsigma" t "ς" "varsigma" "varsigma" "ς") + ("Tau" "T" nil "Τ" "Tau" "Tau" "Τ") + ("Upsilon" "\\Upsilon" t "Υ" "Upsilon" "Upsilon" "Υ") + ("upsih" "\\Upsilon" t "ϒ" "upsilon" "upsilon" "ϒ") + ("upsilon" "\\upsilon" t "υ" "upsilon" "upsilon" "υ") + ("Phi" "\\Phi" t "Φ" "Phi" "Phi" "Φ") + ("phi" "\\phi" t "φ" "phi" "phi" "ɸ") + ("varphi" "\\varphi" t "ϕ" "varphi" "varphi" "φ") + ("Chi" "X" nil "Χ" "Chi" "Chi" "Χ") + ("chi" "\\chi" t "χ" "chi" "chi" "χ") + ("acutex" "\\acute x" t "´x" "'x" "'x" "𝑥́") + ("Psi" "\\Psi" t "Ψ" "Psi" "Psi" "Ψ") + ("psi" "\\psi" t "ψ" "psi" "psi" "ψ") + ("tau" "\\tau" t "τ" "tau" "tau" "τ") + ("Omega" "\\Omega" t "Ω" "Omega" "Omega" "Ω") + ("omega" "\\omega" t "ω" "omega" "omega" "ω") + ("piv" "\\varpi" t "ϖ" "omega-pi" "omega-pi" "ϖ") + ("varpi" "\\varpi" t "ϖ" "omega-pi" "omega-pi" "ϖ") + ("partial" "\\partial" t "∂" "[partial differential]" "[partial differential]" "∂") + + "** Hebrew" + ("alefsym" "\\aleph" t "ℵ" "aleph" "aleph" "ℵ") + ("aleph" "\\aleph" t "ℵ" "aleph" "aleph" "ℵ") + ("gimel" "\\gimel" t "ℷ" "gimel" "gimel" "ℷ") + ("beth" "\\beth" t "ℶ" "beth" "beth" "ב") + ("dalet" "\\daleth" t "ℸ" "dalet" "dalet" "ד") + + "** Dead languages" + ("ETH" "\\DH{}" nil "Ð" "D" "Ð" "Ð") + ("eth" "\\dh{}" nil "ð" "dh" "ð" "ð") + ("THORN" "\\TH{}" nil "Þ" "TH" "Þ" "Þ") + ("thorn" "\\th{}" nil "þ" "th" "þ" "þ") + + "* Punctuation" + "** Dots and Marks" + ("dots" "\\dots{}" nil "…" "..." "..." "…") + ("cdots" "\\cdots{}" t "⋯" "..." "..." "⋯") + ("hellip" "\\dots{}" nil "…" "..." "..." "…") + ("middot" "\\textperiodcentered{}" nil "·" "." "·" "·") + ("iexcl" "!`" nil "¡" "!" "¡" "¡") + ("iquest" "?`" nil "¿" "?" "¿" "¿") + + "** Dash-like" + ("shy" "\\-" nil "­" "" "" "") + ("ndash" "--" nil "–" "-" "-" "–") + ("mdash" "---" nil "—" "--" "--" "—") + + "** Quotations" + ("quot" "\\textquotedbl{}" nil """ "\"" "\"" "\"") + ("acute" "\\textasciiacute{}" nil "´" "'" "´" "´") + ("ldquo" "\\textquotedblleft{}" nil "“" "\"" "\"" "“") + ("rdquo" "\\textquotedblright{}" nil "”" "\"" "\"" "”") + ("bdquo" "\\quotedblbase{}" nil "„" "\"" "\"" "„") + ("lsquo" "\\textquoteleft{}" nil "‘" "`" "`" "‘") + ("rsquo" "\\textquoteright{}" nil "’" "'" "'" "’") + ("sbquo" "\\quotesinglbase{}" nil "‚" "," "," "‚") + ("laquo" "\\guillemotleft{}" nil "«" "<<" "«" "«") + ("raquo" "\\guillemotright{}" nil "»" ">>" "»" "»") + ("lsaquo" "\\guilsinglleft{}" nil "‹" "<" "<" "‹") + ("rsaquo" "\\guilsinglright{}" nil "›" ">" ">" "›") + + "* Other" + "** Misc. (often used)" + ("circ" "\\^{}" nil "ˆ" "^" "^" "∘") + ("vert" "\\vert{}" t "|" "|" "|" "|") + ("vbar" "|" nil "|" "|" "|" "|") + ("brvbar" "\\textbrokenbar{}" nil "¦" "|" "¦" "¦") + ("S" "\\S" nil "§" "paragraph" "§" "§") + ("sect" "\\S" nil "§" "paragraph" "§" "§") + ("amp" "\\&" nil "&" "&" "&" "&") + ("lt" "\\textless{}" nil "<" "<" "<" "<") + ("gt" "\\textgreater{}" nil ">" ">" ">" ">") + ("tilde" "\\textasciitilde{}" nil "~" "~" "~" "~") + ("slash" "/" nil "/" "/" "/" "/") + ("plus" "+" nil "+" "+" "+" "+") + ("under" "\\_" nil "_" "_" "_" "_") + ("equal" "=" nil "=" "=" "=" "=") + ("asciicirc" "\\textasciicircum{}" nil "^" "^" "^" "^") + ("dagger" "\\textdagger{}" nil "†" "[dagger]" "[dagger]" "†") + ("dag" "\\dag{}" nil "†" "[dagger]" "[dagger]" "†") + ("Dagger" "\\textdaggerdbl{}" nil "‡" "[doubledagger]" "[doubledagger]" "‡") + ("ddag" "\\ddag{}" nil "‡" "[doubledagger]" "[doubledagger]" "‡") + + "** Whitespace" + ("nbsp" "~" nil " " " " "\x00A0" "\x00A0") + ("ensp" "\\hspace*{.5em}" nil " " " " " " " ") + ("emsp" "\\hspace*{1em}" nil " " " " " " " ") + ("thinsp" "\\hspace*{.2em}" nil " " " " " " " ") + + "** Currency" + ("curren" "\\textcurrency{}" nil "¤" "curr." "¤" "¤") + ("cent" "\\textcent{}" nil "¢" "cent" "¢" "¢") + ("pound" "\\pounds{}" nil "£" "pound" "£" "£") + ("yen" "\\textyen{}" nil "¥" "yen" "¥" "¥") + ("euro" "\\texteuro{}" nil "€" "EUR" "EUR" "€") + ("EUR" "\\texteuro{}" nil "€" "EUR" "EUR" "€") + ("dollar" "\\$" nil "$" "$" "$" "$") + ("USD" "\\$" nil "$" "$" "$" "$") + + "** Property Marks" + ("copy" "\\textcopyright{}" nil "©" "(c)" "©" "©") + ("reg" "\\textregistered{}" nil "®" "(r)" "®" "®") + ("trade" "\\texttrademark{}" nil "™" "TM" "TM" "™") + + "** Science et al." + ("minus" "\\minus" t "−" "-" "-" "−") + ("pm" "\\textpm{}" nil "±" "+-" "±" "±") + ("plusmn" "\\textpm{}" nil "±" "+-" "±" "±") + ("times" "\\texttimes{}" nil "×" "*" "×" "×") + ("frasl" "/" nil "⁄" "/" "/" "⁄") + ("colon" "\\colon" t ":" ":" ":" ":") + ("div" "\\textdiv{}" nil "÷" "/" "÷" "÷") + ("frac12" "\\textonehalf{}" nil "½" "1/2" "½" "½") + ("frac14" "\\textonequarter{}" nil "¼" "1/4" "¼" "¼") + ("frac34" "\\textthreequarters{}" nil "¾" "3/4" "¾" "¾") + ("permil" "\\textperthousand{}" nil "‰" "per thousand" "per thousand" "‰") + ("sup1" "\\textonesuperior{}" nil "¹" "^1" "¹" "¹") + ("sup2" "\\texttwosuperior{}" nil "²" "^2" "²" "²") + ("sup3" "\\textthreesuperior{}" nil "³" "^3" "³" "³") + ("radic" "\\sqrt{\\,}" t "√" "[square root]" "[square root]" "√") + ("sum" "\\sum" t "∑" "[sum]" "[sum]" "∑") + ("prod" "\\prod" t "∏" "[product]" "[n-ary product]" "∏") + ("micro" "\\textmu{}" nil "µ" "micro" "µ" "µ") + ("macr" "\\textasciimacron{}" nil "¯" "[macron]" "¯" "¯") + ("deg" "\\textdegree{}" nil "°" "degree" "°" "°") + ("prime" "\\prime" t "′" "'" "'" "′") + ("Prime" "\\prime{}\\prime" t "″" "''" "''" "″") + ("infin" "\\infty" t "∞" "[infinity]" "[infinity]" "∞") + ("infty" "\\infty" t "∞" "[infinity]" "[infinity]" "∞") + ("prop" "\\propto" t "∝" "[proportional to]" "[proportional to]" "∝") + ("propto" "\\propto" t "∝" "[proportional to]" "[proportional to]" "∝") + ("not" "\\textlnot{}" nil "¬" "[angled dash]" "¬" "¬") + ("neg" "\\neg{}" t "¬" "[angled dash]" "¬" "¬") + ("land" "\\land" t "∧" "[logical and]" "[logical and]" "∧") + ("wedge" "\\wedge" t "∧" "[logical and]" "[logical and]" "∧") + ("lor" "\\lor" t "∨" "[logical or]" "[logical or]" "∨") + ("vee" "\\vee" t "∨" "[logical or]" "[logical or]" "∨") + ("cap" "\\cap" t "∩" "[intersection]" "[intersection]" "∩") + ("cup" "\\cup" t "∪" "[union]" "[union]" "∪") + ("smile" "\\smile" t "⌣" "[cup product]" "[cup product]" "⌣") + ("frown" "\\frown" t "⌢" "[Cap product]" "[cap product]" "⌢") + ("int" "\\int" t "∫" "[integral]" "[integral]" "∫") + ("therefore" "\\therefore" t "∴" "[therefore]" "[therefore]" "∴") + ("there4" "\\therefore" t "∴" "[therefore]" "[therefore]" "∴") + ("because" "\\because" t "∵" "[because]" "[because]" "∵") + ("sim" "\\sim" t "∼" "~" "~" "∼") + ("cong" "\\cong" t "≅" "[approx. equal to]" "[approx. equal to]" "≅") + ("simeq" "\\simeq" t "≅" "[approx. equal to]" "[approx. equal to]" "≅") + ("asymp" "\\asymp" t "≈" "[almost equal to]" "[almost equal to]" "≈") + ("approx" "\\approx" t "≈" "[almost equal to]" "[almost equal to]" "≈") + ("ne" "\\ne" t "≠" "[not equal to]" "[not equal to]" "≠") + ("neq" "\\neq" t "≠" "[not equal to]" "[not equal to]" "≠") + ("equiv" "\\equiv" t "≡" "[identical to]" "[identical to]" "≡") + + ("triangleq" "\\triangleq" t "≜" "[defined to]" "[defined to]" "≜") + ("le" "\\le" t "≤" "<=" "<=" "≤") + ("leq" "\\le" t "≤" "<=" "<=" "≤") + ("ge" "\\ge" t "≥" ">=" ">=" "≥") + ("geq" "\\ge" t "≥" ">=" ">=" "≥") + ("lessgtr" "\\lessgtr" t "≶" "[less than or greater than]" "[less than or greater than]" "≶") + ("lesseqgtr" "\\lesseqgtr" t "⋚" "[less than or equal or greater than or equal]" "[less than or equal or greater than or equal]" "⋚") + ("ll" "\\ll" t "≪" "<<" "<<" "≪") + ("Ll" "\\lll" t "⋘" "<<<" "<<<" "⋘") + ("lll" "\\lll" t "⋘" "<<<" "<<<" "⋘") + ("gg" "\\gg" t "≫" ">>" ">>" "≫") + ("Gg" "\\ggg" t "⋙" ">>>" ">>>" "⋙") + ("ggg" "\\ggg" t "⋙" ">>>" ">>>" "⋙") + ("prec" "\\prec" t "≺" "[precedes]" "[precedes]" "≺") + ("preceq" "\\preceq" t "≼" "[precedes or equal]" "[precedes or equal]" "≼") + ("preccurlyeq" "\\preccurlyeq" t "≼" "[precedes or equal]" "[precedes or equal]" "≼") + ("succ" "\\succ" t "≻" "[succeeds]" "[succeeds]" "≻") + ("succeq" "\\succeq" t "≽" "[succeeds or equal]" "[succeeds or equal]" "≽") + ("succcurlyeq" "\\succcurlyeq" t "≽" "[succeeds or equal]" "[succeeds or equal]" "≽") + ("sub" "\\subset" t "⊂" "[subset of]" "[subset of]" "⊂") + ("subset" "\\subset" t "⊂" "[subset of]" "[subset of]" "⊂") + ("sup" "\\supset" t "⊃" "[superset of]" "[superset of]" "⊃") + ("supset" "\\supset" t "⊃" "[superset of]" "[superset of]" "⊃") + ("nsub" "\\not\\subset" t "⊄" "[not a subset of]" "[not a subset of" "⊄") + ("sube" "\\subseteq" t "⊆" "[subset of or equal to]" "[subset of or equal to]" "⊆") + ("nsup" "\\not\\supset" t "⊅" "[not a superset of]" "[not a superset of]" "⊅") + ("supe" "\\supseteq" t "⊇" "[superset of or equal to]" "[superset of or equal to]" "⊇") + ("setminus" "\\setminus" t "∖" "\" "\" "⧵") + ("forall" "\\forall" t "∀" "[for all]" "[for all]" "∀") + ("exist" "\\exists" t "∃" "[there exists]" "[there exists]" "∃") + ("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]" "∅") + ("emptyset" "\\emptyset" t "∅" "[empty set]" "[empty set]" "∅") + ("isin" "\\in" t "∈" "[element of]" "[element of]" "∈") + ("in" "\\in" t "∈" "[element of]" "[element of]" "∈") + ("notin" "\\notin" t "∉" "[not an element of]" "[not an element of]" "∉") + ("ni" "\\ni" t "∋" "[contains as member]" "[contains as member]" "∋") + ("nabla" "\\nabla" t "∇" "[nabla]" "[nabla]" "∇") + ("ang" "\\angle" t "∠" "[angle]" "[angle]" "∠") + ("angle" "\\angle" t "∠" "[angle]" "[angle]" "∠") + ("perp" "\\perp" t "⊥" "[up tack]" "[up tack]" "⊥") + ("parallel" "\\parallel" t "∥" "||" "||" "∥") + ("sdot" "\\cdot" t "⋅" "[dot]" "[dot]" "⋅") + ("cdot" "\\cdot" t "⋅" "[dot]" "[dot]" "⋅") + ("lceil" "\\lceil" t "⌈" "[left ceiling]" "[left ceiling]" "⌈") + ("rceil" "\\rceil" t "⌉" "[right ceiling]" "[right ceiling]" "⌉") + ("lfloor" "\\lfloor" t "⌊" "[left floor]" "[left floor]" "⌊") + ("rfloor" "\\rfloor" t "⌋" "[right floor]" "[right floor]" "⌋") + ("lang" "\\langle" t "⟨" "<" "<" "⟨") + ("rang" "\\rangle" t "⟩" ">" ">" "⟩") + ("langle" "\\langle" t "⟨" "<" "<" "⟨") + ("rangle" "\\rangle" t "⟩" ">" ">" "⟩") + ("hbar" "\\hbar" t "ℏ" "hbar" "hbar" "ℏ") + ("mho" "\\mho" t "℧" "mho" "mho" "℧") + + "** Arrows" + ("larr" "\\leftarrow" t "←" "<-" "<-" "←") + ("leftarrow" "\\leftarrow" t "←" "<-" "<-" "←") + ("gets" "\\gets" t "←" "<-" "<-" "←") + ("lArr" "\\Leftarrow" t "⇐" "<=" "<=" "⇐") + ("Leftarrow" "\\Leftarrow" t "⇐" "<=" "<=" "⇐") + ("uarr" "\\uparrow" t "↑" "[uparrow]" "[uparrow]" "↑") + ("uparrow" "\\uparrow" t "↑" "[uparrow]" "[uparrow]" "↑") + ("uArr" "\\Uparrow" t "⇑" "[dbluparrow]" "[dbluparrow]" "⇑") + ("Uparrow" "\\Uparrow" t "⇑" "[dbluparrow]" "[dbluparrow]" "⇑") + ("rarr" "\\rightarrow" t "→" "->" "->" "→") + ("to" "\\to" t "→" "->" "->" "→") + ("rightarrow" "\\rightarrow" t "→" "->" "->" "→") + ("rArr" "\\Rightarrow" t "⇒" "=>" "=>" "⇒") + ("Rightarrow" "\\Rightarrow" t "⇒" "=>" "=>" "⇒") + ("darr" "\\downarrow" t "↓" "[downarrow]" "[downarrow]" "↓") + ("downarrow" "\\downarrow" t "↓" "[downarrow]" "[downarrow]" "↓") + ("dArr" "\\Downarrow" t "⇓" "[dbldownarrow]" "[dbldownarrow]" "⇓") + ("Downarrow" "\\Downarrow" t "⇓" "[dbldownarrow]" "[dbldownarrow]" "⇓") + ("harr" "\\leftrightarrow" t "↔" "<->" "<->" "↔") + ("leftrightarrow" "\\leftrightarrow" t "↔" "<->" "<->" "↔") + ("hArr" "\\Leftrightarrow" t "⇔" "<=>" "<=>" "⇔") + ("Leftrightarrow" "\\Leftrightarrow" t "⇔" "<=>" "<=>" "⇔") + ("crarr" "\\hookleftarrow" t "↵" "<-'" "<-'" "↵") + ("hookleftarrow" "\\hookleftarrow" t "↵" "<-'" "<-'" "↵") + + "** Function names" + ("arccos" "\\arccos" t "arccos" "arccos" "arccos" "arccos") + ("arcsin" "\\arcsin" t "arcsin" "arcsin" "arcsin" "arcsin") + ("arctan" "\\arctan" t "arctan" "arctan" "arctan" "arctan") + ("arg" "\\arg" t "arg" "arg" "arg" "arg") + ("cos" "\\cos" t "cos" "cos" "cos" "cos") + ("cosh" "\\cosh" t "cosh" "cosh" "cosh" "cosh") + ("cot" "\\cot" t "cot" "cot" "cot" "cot") + ("coth" "\\coth" t "coth" "coth" "coth" "coth") + ("csc" "\\csc" t "csc" "csc" "csc" "csc") + ("deg" "\\deg" t "°" "deg" "deg" "deg") + ("det" "\\det" t "det" "det" "det" "det") + ("dim" "\\dim" t "dim" "dim" "dim" "dim") + ("exp" "\\exp" t "exp" "exp" "exp" "exp") + ("gcd" "\\gcd" t "gcd" "gcd" "gcd" "gcd") + ("hom" "\\hom" t "hom" "hom" "hom" "hom") + ("inf" "\\inf" t "inf" "inf" "inf" "inf") + ("ker" "\\ker" t "ker" "ker" "ker" "ker") + ("lg" "\\lg" t "lg" "lg" "lg" "lg") + ("lim" "\\lim" t "lim" "lim" "lim" "lim") + ("liminf" "\\liminf" t "liminf" "liminf" "liminf" "liminf") + ("limsup" "\\limsup" t "limsup" "limsup" "limsup" "limsup") + ("ln" "\\ln" t "ln" "ln" "ln" "ln") + ("log" "\\log" t "log" "log" "log" "log") + ("max" "\\max" t "max" "max" "max" "max") + ("min" "\\min" t "min" "min" "min" "min") + ("Pr" "\\Pr" t "Pr" "Pr" "Pr" "Pr") + ("sec" "\\sec" t "sec" "sec" "sec" "sec") + ("sin" "\\sin" t "sin" "sin" "sin" "sin") + ("sinh" "\\sinh" t "sinh" "sinh" "sinh" "sinh") + ("sup" "\\sup" t "⊃" "sup" "sup" "sup") + ("tan" "\\tan" t "tan" "tan" "tan" "tan") + ("tanh" "\\tanh" t "tanh" "tanh" "tanh" "tanh") + + "** Signs & Symbols" + ("bull" "\\textbullet{}" nil "•" "*" "*" "•") + ("bullet" "\\textbullet{}" nil "•" "*" "*" "•") + ("star" "\\star" t "*" "*" "*" "⋆") + ("lowast" "\\ast" t "∗" "*" "*" "∗") + ("ast" "\\ast" t "∗" "*" "*" "*") + ("odot" "\\odot" t "o" "[circled dot]" "[circled dot]" "ʘ") + ("oplus" "\\oplus" t "⊕" "[circled plus]" "[circled plus]" "⊕") + ("otimes" "\\otimes" t "⊗" "[circled times]" "[circled times]" "⊗") + ("check" "\\checkmark" t "✓" "[checkmark]" "[checkmark]" "✓") + ("checkmark" "\\checkmark" t "✓" "[checkmark]" "[checkmark]" "✓") + + "** Miscellaneous (seldom used)" + ("para" "\\P{}" nil "¶" "[pilcrow]" "¶" "¶") + ("ordf" "\\textordfeminine{}" nil "ª" "_a_" "ª" "ª") + ("ordm" "\\textordmasculine{}" nil "º" "_o_" "º" "º") + ("cedil" "\\c{}" nil "¸" "[cedilla]" "¸" "¸") + ("oline" "\\overline{~}" t "‾" "[overline]" "¯" "‾") + ("uml" "\\textasciidieresis{}" nil "¨" "[diaeresis]" "¨" "¨") + ("zwnj" "\\/{}" nil "‌" "" "" "") + ("zwj" "" nil "‍" "" "" "") + ("lrm" "" nil "‎" "" "" "") + ("rlm" "" nil "‏" "" "" "") + + "** Smilies" + ("smiley" "\\ddot\\smile" t "☺" ":-)" ":-)" "☺") + ("blacksmile" "\\ddot\\smile" t "☻" ":-)" ":-)" "☻") + ("sad" "\\ddot\\frown" t "☹" ":-(" ":-(" "☹") + ("frowny" "\\ddot\\frown" t "☹" ":-(" ":-(" "☹") + + "** Suits" + ("clubs" "\\clubsuit" t "♣" "[clubs]" "[clubs]" "♣") + ("clubsuit" "\\clubsuit" t "♣" "[clubs]" "[clubs]" "♣") + ("spades" "\\spadesuit" t "♠" "[spades]" "[spades]" "♠") + ("spadesuit" "\\spadesuit" t "♠" "[spades]" "[spades]" "♠") + ("hearts" "\\heartsuit" t "♥" "[hearts]" "[hearts]" "♥") + ("heartsuit" "\\heartsuit" t "♥" "[hearts]" "[hearts]" "♥") + ("diams" "\\diamondsuit" t "♦" "[diamonds]" "[diamonds]" "◆") + ("diamondsuit" "\\diamondsuit" t "♦" "[diamonds]" "[diamonds]" "◆") + ("diamond" "\\diamondsuit" t "⋄" "[diamond]" "[diamond]" "◆") + ("Diamond" "\\diamondsuit" t "⋄" "[diamond]" "[diamond]" "◆") + ("loz" "\\lozenge" t "◊" "[lozenge]" "[lozenge]" "⧫")) + ;; Add "\_ "-entity family for spaces. + (let (space-entities html-spaces (entity "_")) + (dolist (n (number-sequence 1 20) (nreverse space-entities)) + (let ((spaces (make-string n ?\s))) + (push (list (setq entity (concat entity " ")) + (format "\\hspace*{%sem}" (* n .5)) + nil + (setq html-spaces (concat " " html-spaces)) + spaces + spaces + (make-string n ?\x2002)) + space-entities))))) + "Default entities used in Org mode to produce special characters. For details see `org-entities-user'.") (defsubst org-entity-get (name) @@ -518,52 +533,27 @@ This first checks the user list, then the built-in list." (or (assoc name org-entities-user) (assoc name org-entities))) -(defun org-entity-get-representation (name kind) - "Get the correct representation of entity NAME for export type KIND. -Kind can be any of `latex', `html', `ascii', `latin1', or `utf8'." - (let* ((e (org-entity-get name)) - (n (cdr (assq kind '((latex . 1) (html . 3) (ascii . 4) - (latin1 . 5) (utf8 . 6))))) - (r (and e n (nth n e)))) - (if (and e r - (not org-entities-ascii-explanatory) - (memq kind '(ascii latin1 utf8)) - (= (string-to-char r) ?\[)) - (concat "\\" name) - r))) - -(defsubst org-entity-latex-math-p (name) - "Does entity NAME require math mode in LaTeX?" - (nth 2 (org-entity-get name))) - ;; Helpfunctions to create a table for orgmode.org/worg/org-symbols.org (defun org-entities-create-table () "Create an Org mode table with all entities." (interactive) - (let ((pos (point)) e latex mathp html latin utf8 name ascii) + (let ((pos (point))) (insert "|Name|LaTeX code|LaTeX|HTML code |HTML|ASCII|Latin1|UTF-8\n|-\n") - (mapc (lambda (e) (when (listp e) - (setq name (car e) - latex (nth 1 e) - mathp (nth 2 e) - html (nth 3 e) - ascii (nth 4 e) - latin (nth 5 e) - utf8 (nth 6 e)) - (if (equal ascii "|") (setq ascii "\\vert")) - (if (equal latin "|") (setq latin "\\vert")) - (if (equal utf8 "|") (setq utf8 "\\vert")) - (if (equal ascii "=>") (setq ascii "= >")) - (if (equal latin "=>") (setq latin "= >")) - (insert "|" name - "|" (format "=%s=" latex) - "|" (format (if mathp "$%s$" "$\\mbox{%s}$") - latex) - "|" (format "=%s=" html) "|" html - "|" ascii "|" latin "|" utf8 - "|\n"))) - org-entities) + (dolist (e org-entities) + (pcase e + (`(,name ,latex ,mathp ,html ,ascii ,latin ,utf8) + (if (equal ascii "|") (setq ascii "\\vert")) + (if (equal latin "|") (setq latin "\\vert")) + (if (equal utf8 "|") (setq utf8 "\\vert")) + (if (equal ascii "=>") (setq ascii "= >")) + (if (equal latin "=>") (setq latin "= >")) + (insert "|" name + "|" (format "=%s=" latex) + "|" (format (if mathp "$%s$" "$\\mbox{%s}$") latex) + "|" (format "=%s=" html) "|" html + "|" ascii "|" latin "|" utf8 + "|\n")))) (goto-char pos) (org-table-align))) @@ -572,31 +562,27 @@ Kind can be any of `latex', `html', `ascii', `latin1', or `utf8'." "Create a Help buffer with all available entities." (interactive) (with-output-to-temp-buffer "*Org Entity Help*" - (princ "Org-mode entities\n=================\n\n") + (princ "Org mode entities\n=================\n\n") (let ((ll (append '("* User-defined additions (variable org-entities-user)") org-entities-user org-entities)) - e latex mathp html latin utf8 name ascii (lastwasstring t) (head (concat "\n" " Symbol Org entity LaTeX code HTML code\n" " -----------------------------------------------------------\n"))) - (while ll - (setq e (pop ll)) - (if (stringp e) - (progn - (princ e) - (princ "\n") - (setq lastwasstring t)) - (if lastwasstring (princ head)) - (setq lastwasstring nil) - (setq name (car e) - latex (nth 1 e) - html (nth 3 e) - utf8 (nth 6 e)) - (princ (format " %-8s \\%-16s %-22s %-13s\n" - utf8 name latex html)))))) + (dolist (e ll) + (pcase e + (`(,name ,latex ,_ ,html ,_ ,_ ,utf8) + (when lastwasstring + (princ head) + (setq lastwasstring nil)) + (princ (format " %-8s \\%-16s %-22s %-13s\n" + utf8 name latex html))) + ((pred stringp) + (princ e) + (princ "\n") + (setq lastwasstring t)))))) (with-current-buffer "*Org Entity Help*" (org-mode) (when org-pretty-entities @@ -604,12 +590,6 @@ Kind can be any of `latex', `html', `ascii', `latin1', or `utf8'." (select-window (get-buffer-window "*Org Entity Help*"))) -(defun replace-amp () - "Postprocess HTML file to unescape the ampersand." - (interactive) - (while (re-search-forward "<td>&\\([^<;]+;\\)" nil t) - (replace-match (concat "<td>&" (match-string 1)) t t))) - (provide 'org-entities) ;; Local variables: diff --git a/lisp/org/org-eshell.el b/lisp/org/org-eshell.el index 9eddd3fcf4e..b0e9631e6f5 100644 --- a/lisp/org/org-eshell.el +++ b/lisp/org/org-eshell.el @@ -1,4 +1,4 @@ -;;; org-eshell.el - Support for links to working directories in eshell +;;; org-eshell.el - Support for Links to Working Directories in Eshell -*- lexical-binding: t; -*- ;; Copyright (C) 2011-2017 Free Software Foundation, Inc. @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -27,8 +27,9 @@ (require 'eshell) (require 'esh-mode) -(org-add-link-type "eshell" 'org-eshell-open) -(add-hook 'org-store-link-functions 'org-eshell-store-link) +(org-link-set-parameters "eshell" + :follow #'org-eshell-open + :store #'org-eshell-store-link) (defun org-eshell-open (link) "Switch to am eshell buffer and execute a command line. @@ -43,7 +44,7 @@ (eshell-buffer-name (car buffer-and-command)) (command (cadr buffer-and-command))) (if (get-buffer eshell-buffer-name) - (org-pop-to-buffer-same-window eshell-buffer-name) + (pop-to-buffer-same-window eshell-buffer-name) (eshell)) (goto-char (point-max)) (eshell-kill-input) diff --git a/lisp/org/org-eww.el b/lisp/org/org-eww.el new file mode 100644 index 00000000000..372b543f512 --- /dev/null +++ b/lisp/org/org-eww.el @@ -0,0 +1,175 @@ +;;; org-eww.el --- Store url and kill from Eww mode for Org -*- lexical-binding: t -*- + +;; Copyright (C) 2014-2017 Free Software Foundation, Inc. + +;; Author: Marco Wahl <marcowahlsoft>a<gmailcom> +;; Keywords: link, eww +;; Homepage: http://orgmode.org + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. + + +;;; Commentary: + +;; When this module is active `org-store-link' (often on key C-c l) in +;; a eww buffer stores a link to the current url of the eww buffer. + +;; In an eww buffer function `org-eww-copy-for-org-mode' kills either +;; a region or the whole buffer if no region is set and transforms the +;; text on the fly so that it can be pasted into an Org buffer with +;; hot links. + +;; C-c C-x C-w (and also C-c C-x M-w) trigger +;; `org-eww-copy-for-org-mode'. + +;; Hint: A lot of code of this module comes from module org-w3m which +;; has been written by Andy Steward based on the idea of Richard +;; Riley. Thanks! + +;; Potential: Since the code for w3m and eww is so similar one could +;; try to refactor. + + +;;; Code: +(require 'org) +(require 'cl-lib) + +(defvar eww-current-title) +(defvar eww-current-url) +(defvar eww-data) +(defvar eww-mode-map) + +(declare-function eww-current-url "eww") + + +;; Store Org-link in eww-mode buffer +(org-link-set-parameters "eww" :follow #'eww :store #'org-eww-store-link) +(defun org-eww-store-link () + "Store a link to the url of a Eww buffer." + (when (eq major-mode 'eww-mode) + (org-store-link-props + :type "eww" + :link (if (< emacs-major-version 25) + eww-current-url + (eww-current-url)) + :url (url-view-url t) + :description (if (< emacs-major-version 25) + (or eww-current-title eww-current-url) + (or (plist-get eww-data :title) + (eww-current-url)))))) + + +;; Some auxiliary functions concerning links in eww buffers +(defun org-eww-goto-next-url-property-change () + "Move to the start of next link if exists. +Otherwise point is not moved. Return point." + (goto-char + (or (next-single-property-change (point) 'shr-url) + (point)))) + +(defun org-eww-has-further-url-property-change-p () + "Non-nil if there is a next url property change." + (save-excursion + (not (eq (point) (org-eww-goto-next-url-property-change))))) + +(defun org-eww-url-below-point () + "Return the url below point if there is an url; otherwise, return nil." + (get-text-property (point) 'shr-url)) + + +(defun org-eww-copy-for-org-mode () + "Copy current buffer content or active region with `org-mode' style links. +This will encode `link-title' and `link-location' with +`org-make-link-string', and insert the transformed test into the kill ring, +so that it can be yanked into an Org mode buffer with links working correctly. + +Further lines starting with a star get quoted with a comma to keep +the structure of the Org file." + (interactive) + (let* ((regionp (org-region-active-p)) + (transform-start (point-min)) + (transform-end (point-max)) + return-content + link-location link-title + temp-position out-bound) + (when regionp + (setq transform-start (region-beginning)) + (setq transform-end (region-end)) + ;; Deactivate mark if current mark is activate. + (when (fboundp 'deactivate-mark) (deactivate-mark))) + (message "Transforming links...") + (save-excursion + (goto-char transform-start) + (while (and (not out-bound) ; still inside region to copy + (org-eww-has-further-url-property-change-p)) ; there is a next link + ;; Store current point before jump next anchor. + (setq temp-position (point)) + ;; Move to next anchor when current point is not at anchor. + (or (org-eww-url-below-point) + (org-eww-goto-next-url-property-change)) + (cl-assert + (org-eww-url-below-point) t + "program logic error: point must have an url below but it hasn't") + (if (<= (point) transform-end) ; if point is inside transform bound + (progn + ;; Get content between two links. + (when (< temp-position (point)) + (setq return-content (concat return-content + (buffer-substring + temp-position (point))))) + ;; Get link location at current point. + (setq link-location (org-eww-url-below-point)) + ;; Get link title at current point. + (setq link-title + (buffer-substring + (point) + (org-eww-goto-next-url-property-change))) + ;; concat `org-mode' style url to `return-content'. + (setq return-content + (concat return-content + (if (stringp link-location) + ;; hint: link-location is different for form-elements. + (org-make-link-string link-location link-title) + link-title)))) + (goto-char temp-position) ; reset point before jump next anchor + (setq out-bound t) ; for break out `while' loop + )) + ;; Add the rest until end of the region to be copied. + (when (< (point) transform-end) + (setq return-content + (concat return-content + (buffer-substring (point) transform-end)))) + ;; Quote lines starting with *. + (org-kill-new (replace-regexp-in-string "^\\*" ",*" return-content)) + (message "Transforming links...done, use C-y to insert text into Org mode file")))) + + +;; Additional keys for eww-mode + +(defun org-eww-extend-eww-keymap () + (define-key eww-mode-map "\C-c\C-x\M-w" 'org-eww-copy-for-org-mode) + (define-key eww-mode-map "\C-c\C-x\C-w" 'org-eww-copy-for-org-mode)) + +(when (and (boundp 'eww-mode-map) + (keymapp eww-mode-map)) ; eww is already up. + (org-eww-extend-eww-keymap)) + +(add-hook 'eww-mode-hook #'org-eww-extend-eww-keymap) + + +(provide 'org-eww) + +;;; org-eww.el ends here diff --git a/lisp/org/org-faces.el b/lisp/org/org-faces.el index c340aca73a5..eab9f3e313f 100644 --- a/lisp/org/org-faces.el +++ b/lisp/org/org-faces.el @@ -1,4 +1,4 @@ -;;; org-faces.el --- Face definitions for Org-mode. +;;; org-faces.el --- Face definitions -*- lexical-binding: t; -*- ;; Copyright (C) 2004-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Commentary: @@ -28,32 +28,12 @@ ;;; Code: -(require 'org-macs) -(require 'org-compat) - -(defun org-copy-face (old-face new-face docstring &rest attributes) - (unless (facep new-face) - (if (fboundp 'set-face-attribute) - (progn - (make-face new-face) - (set-face-attribute new-face nil :inherit old-face) - (apply 'set-face-attribute new-face nil attributes) - (set-face-doc-string new-face docstring)) - (copy-face old-face new-face) - (if (fboundp 'set-face-doc-string) - (set-face-doc-string new-face docstring))))) -(put 'org-copy-face 'lisp-indent-function 2) - -(when (featurep 'xemacs) - (put 'mode-line 'face-alias 'modeline)) - (defgroup org-faces nil - "Faces in Org-mode." + "Faces in Org mode." :tag "Org Faces" :group 'org-appearance) -(defface org-default - (org-compatible-face 'default nil) +(defface org-default '((t :inherit default)) "Face used for default text." :group 'org-faces) @@ -65,99 +45,49 @@ The foreground color of this face should be equal to the background color of the frame." :group 'org-faces) -(defface org-level-1 ;; originally copied from font-lock-function-name-face - (org-compatible-face 'outline-1 - '((((class color) (min-colors 88) (background light)) (:foreground "Blue1")) - (((class color) (min-colors 88) (background dark)) (:foreground "LightSkyBlue")) - (((class color) (min-colors 16) (background light)) (:foreground "Blue")) - (((class color) (min-colors 16) (background dark)) (:foreground "LightSkyBlue")) - (((class color) (min-colors 8)) (:foreground "blue" :bold t)) - (t (:bold t)))) +(defface org-level-1 '((t :inherit outline-1)) "Face used for level 1 headlines." :group 'org-faces) -(defface org-level-2 ;; originally copied from font-lock-variable-name-face - (org-compatible-face 'outline-2 - '((((class color) (min-colors 16) (background light)) (:foreground "DarkGoldenrod")) - (((class color) (min-colors 16) (background dark)) (:foreground "LightGoldenrod")) - (((class color) (min-colors 8) (background light)) (:foreground "yellow")) - (((class color) (min-colors 8) (background dark)) (:foreground "yellow" :bold t)) - (t (:bold t)))) +(defface org-level-2 '((t :inherit outline-2)) "Face used for level 2 headlines." :group 'org-faces) -(defface org-level-3 ;; originally copied from font-lock-keyword-face - (org-compatible-face 'outline-3 - '((((class color) (min-colors 88) (background light)) (:foreground "Purple")) - (((class color) (min-colors 88) (background dark)) (:foreground "Cyan1")) - (((class color) (min-colors 16) (background light)) (:foreground "Purple")) - (((class color) (min-colors 16) (background dark)) (:foreground "Cyan")) - (((class color) (min-colors 8) (background light)) (:foreground "purple" :bold t)) - (((class color) (min-colors 8) (background dark)) (:foreground "cyan" :bold t)) - (t (:bold t)))) +(defface org-level-3 '((t :inherit outline-3)) "Face used for level 3 headlines." :group 'org-faces) -(defface org-level-4 ;; originally copied from font-lock-comment-face - (org-compatible-face 'outline-4 - '((((class color) (min-colors 88) (background light)) (:foreground "Firebrick")) - (((class color) (min-colors 88) (background dark)) (:foreground "chocolate1")) - (((class color) (min-colors 16) (background light)) (:foreground "red")) - (((class color) (min-colors 16) (background dark)) (:foreground "red1")) - (((class color) (min-colors 8) (background light)) (:foreground "red" :bold t)) - (((class color) (min-colors 8) (background dark)) (:foreground "red" :bold t)) - (t (:bold t)))) +(defface org-level-4 '((t :inherit outline-4)) "Face used for level 4 headlines." :group 'org-faces) -(defface org-level-5 ;; originally copied from font-lock-type-face - (org-compatible-face 'outline-5 - '((((class color) (min-colors 16) (background light)) (:foreground "ForestGreen")) - (((class color) (min-colors 16) (background dark)) (:foreground "PaleGreen")) - (((class color) (min-colors 8)) (:foreground "green")))) +(defface org-level-5 '((t :inherit outline-5)) "Face used for level 5 headlines." :group 'org-faces) -(defface org-level-6 ;; originally copied from font-lock-constant-face - (org-compatible-face 'outline-6 - '((((class color) (min-colors 16) (background light)) (:foreground "CadetBlue")) - (((class color) (min-colors 16) (background dark)) (:foreground "Aquamarine")) - (((class color) (min-colors 8)) (:foreground "magenta")))) +(defface org-level-6 '((t :inherit outline-6)) "Face used for level 6 headlines." :group 'org-faces) -(defface org-level-7 ;; originally copied from font-lock-builtin-face - (org-compatible-face 'outline-7 - '((((class color) (min-colors 16) (background light)) (:foreground "Orchid")) - (((class color) (min-colors 16) (background dark)) (:foreground "LightSteelBlue")) - (((class color) (min-colors 8)) (:foreground "blue")))) +(defface org-level-7 '((t :inherit outline-7)) "Face used for level 7 headlines." :group 'org-faces) -(defface org-level-8 ;; originally copied from font-lock-string-face - (org-compatible-face 'outline-8 - '((((class color) (min-colors 16) (background light)) (:foreground "RosyBrown")) - (((class color) (min-colors 16) (background dark)) (:foreground "LightSalmon")) - (((class color) (min-colors 8)) (:foreground "green")))) +(defface org-level-8 '((t :inherit outline-8)) "Face used for level 8 headlines." :group 'org-faces) -(defface org-special-keyword ;; originally copied from font-lock-string-face - (org-compatible-face 'font-lock-keyword-face - '((((class color) (min-colors 16) (background light)) (:foreground "RosyBrown")) - (((class color) (min-colors 16) (background dark)) (:foreground "LightSalmon")) - (t (:italic t)))) +(defface org-special-keyword '((t :inherit font-lock-keyword-face)) "Face used for special keywords." :group 'org-faces) -(defface org-drawer ;; originally copied from font-lock-function-name-face - (org-compatible-face nil - '((((class color) (min-colors 88) (background light)) (:foreground "Blue1")) - (((class color) (min-colors 88) (background dark)) (:foreground "LightSkyBlue")) - (((class color) (min-colors 16) (background light)) (:foreground "Blue")) - (((class color) (min-colors 16) (background dark)) (:foreground "LightSkyBlue")) - (((class color) (min-colors 8)) (:foreground "blue" :bold t)) - (t (:bold t)))) +(defface org-drawer ;Copied from `font-lock-function-name-face' + '((((class color) (min-colors 88) (background light)) (:foreground "Blue1")) + (((class color) (min-colors 88) (background dark)) (:foreground "LightSkyBlue")) + (((class color) (min-colors 16) (background light)) (:foreground "Blue")) + (((class color) (min-colors 16) (background dark)) (:foreground "LightSkyBlue")) + (((class color) (min-colors 8)) (:foreground "blue" :bold t)) + (t (:bold t))) "Face used for drawers." :group 'org-faces) @@ -166,18 +96,17 @@ color of the frame." :group 'org-faces) (defface org-column - (org-compatible-face nil - '((((class color) (min-colors 16) (background light)) - (:background "grey90" :weight normal :slant normal :strike-through nil - :underline nil)) - (((class color) (min-colors 16) (background dark)) - (:background "grey30" :weight normal :slant normal :strike-through nil - :underline nil)) - (((class color) (min-colors 8)) - (:background "cyan" :foreground "black" - :weight normal :slant normal :strike-through nil - :underline nil)) - (t (:inverse-video t)))) + '((((class color) (min-colors 16) (background light)) + (:background "grey90" :weight normal :slant normal :strike-through nil + :underline nil)) + (((class color) (min-colors 16) (background dark)) + (:background "grey30" :weight normal :slant normal :strike-through nil + :underline nil)) + (((class color) (min-colors 8)) + (:background "cyan" :foreground "black" + :weight normal :slant normal :strike-through nil + :underline nil)) + (t (:inverse-video t))) "Face for column display of entry properties. This is actually only part of the face definition for the text in column view. The following faces apply, with this priority. @@ -198,59 +127,33 @@ character (this might for example be the a TODO keyword) might still shine through in some properties. So when your column view looks funny, with \"random\" colors, weight, strike-through, try to explicitly set the properties in the `org-column' face. For example, set -:underline to nil, or the :slant to `normal'. - -Under XEmacs, the rules are simpler, because the XEmacs version of -column view defines special faces for each outline level. See the file -`org-colview-xemacs.el' in Org's contrib/ directory for details." +:underline to nil, or the :slant to `normal'." :group 'org-faces) (defface org-column-title - (org-compatible-face nil - '((((class color) (min-colors 16) (background light)) - (:background "grey90" :underline t :weight bold)) - (((class color) (min-colors 16) (background dark)) - (:background "grey30" :underline t :weight bold)) - (((class color) (min-colors 8)) - (:background "cyan" :foreground "black" :underline t :weight bold)) - (t (:inverse-video t)))) + '((((class color) (min-colors 16) (background light)) + (:background "grey90" :underline t :weight bold)) + (((class color) (min-colors 16) (background dark)) + (:background "grey30" :underline t :weight bold)) + (((class color) (min-colors 8)) + (:background "cyan" :foreground "black" :underline t :weight bold)) + (t (:inverse-video t))) "Face for column display of entry properties." :group 'org-faces) -(defface org-agenda-column-dateline - (org-compatible-face 'org-column - '((t nil))) +(defface org-agenda-column-dateline '((t :inherit org-column)) "Face used in agenda column view for datelines with summaries." :group 'org-faces) -(defface org-warning - (org-compatible-face 'font-lock-warning-face - '((((class color) (min-colors 16) (background light)) (:foreground "Red1" :bold t)) - (((class color) (min-colors 16) (background dark)) (:foreground "Pink" :bold t)) - (((class color) (min-colors 8) (background light)) (:foreground "red" :bold t)) - (((class color) (min-colors 8) (background dark)) (:foreground "red" :bold t)) - (t (:bold t)))) +(defface org-warning '((t :inherit font-lock-warning-face)) "Face for deadlines and TODO keywords." :group 'org-faces) -(defface org-archived ; similar to shadow - (org-compatible-face 'shadow - '((((class color grayscale) (min-colors 88) (background light)) - (:foreground "grey50")) - (((class color grayscale) (min-colors 88) (background dark)) - (:foreground "grey70")) - (((class color) (min-colors 8) (background light)) - (:foreground "green")) - (((class color) (min-colors 8) (background dark)) - (:foreground "yellow")))) +(defface org-archived '((t :inherit shadow)) "Face for headline with the ARCHIVE tag." :group 'org-faces) -(defface org-link - (org-compatible-face 'link - '((((class color) (background light)) (:foreground "Purple" :underline t)) - (((class color) (background dark)) (:foreground "Cyan" :underline t)) - (t (:underline t)))) +(defface org-link '((t :inherit link)) "Face for links." :group 'org-faces) @@ -283,12 +186,11 @@ column view defines special faces for each outline level. See the file :group 'org-faces) (defface org-date-selected - (org-compatible-face nil - '((((class color) (min-colors 16) (background light)) (:foreground "Red1" :inverse-video t)) - (((class color) (min-colors 16) (background dark)) (:foreground "Pink" :inverse-video t)) - (((class color) (min-colors 8) (background light)) (:foreground "red" :inverse-video t)) - (((class color) (min-colors 8) (background dark)) (:foreground "red" :inverse-video t)) - (t (:inverse-video t)))) + '((((class color) (min-colors 16) (background light)) (:foreground "Red1" :inverse-video t)) + (((class color) (min-colors 16) (background dark)) (:foreground "Pink" :inverse-video t)) + (((class color) (min-colors 8) (background light)) (:foreground "red" :inverse-video t)) + (((class color) (min-colors 8) (background dark)) (:foreground "red" :inverse-video t)) + (t (:inverse-video t))) "Face for highlighting the calendar day when using `org-read-date'. Using a bold face here might cause discrepancies while displaying the calendar." @@ -301,43 +203,38 @@ calendar." "Face for diary-like sexp date specifications." :group 'org-faces) -(defface org-tag - '((t (:bold t))) +(defface org-tag '((t (:bold t))) "Default face for tags. Note that the variable `org-tag-faces' can be used to overrule this face for specific tags." :group 'org-faces) -(defface org-list-dt - '((t (:bold t))) +(defface org-list-dt '((t (:bold t))) "Default face for definition terms in lists." :group 'org-faces) -(defface org-todo ; font-lock-warning-face - (org-compatible-face nil - '((((class color) (min-colors 16) (background light)) (:foreground "Red1" :bold t)) - (((class color) (min-colors 16) (background dark)) (:foreground "Pink" :bold t)) - (((class color) (min-colors 8) (background light)) (:foreground "red" :bold t)) - (((class color) (min-colors 8) (background dark)) (:foreground "red" :bold t)) - (t (:inverse-video t :bold t)))) +(defface org-todo ;Copied from `font-lock-warning-face' + '((((class color) (min-colors 16) (background light)) (:foreground "Red1" :bold t)) + (((class color) (min-colors 16) (background dark)) (:foreground "Pink" :bold t)) + (((class color) (min-colors 8) (background light)) (:foreground "red" :bold t)) + (((class color) (min-colors 8) (background dark)) (:foreground "red" :bold t)) + (t (:inverse-video t :bold t))) "Face for TODO keywords." :group 'org-faces) -(defface org-done ;; originally copied from font-lock-type-face - (org-compatible-face nil - '((((class color) (min-colors 16) (background light)) (:foreground "ForestGreen" :bold t)) - (((class color) (min-colors 16) (background dark)) (:foreground "PaleGreen" :bold t)) - (((class color) (min-colors 8)) (:foreground "green")) - (t (:bold t)))) +(defface org-done ;Copied from `font-lock-type-face' + '((((class color) (min-colors 16) (background light)) (:foreground "ForestGreen" :bold t)) + (((class color) (min-colors 16) (background dark)) (:foreground "PaleGreen" :bold t)) + (((class color) (min-colors 8)) (:foreground "green")) + (t (:bold t))) "Face used for todo keywords that indicate DONE items." :group 'org-faces) -(defface org-agenda-done ;; originally copied from font-lock-type-face - (org-compatible-face nil - '((((class color) (min-colors 16) (background light)) (:foreground "ForestGreen")) - (((class color) (min-colors 16) (background dark)) (:foreground "PaleGreen")) - (((class color) (min-colors 8)) (:foreground "green")) - (t (:bold nil)))) +(defface org-agenda-done ;Copied from `font-lock-type-face' + '((((class color) (min-colors 16) (background light)) (:foreground "ForestGreen")) + (((class color) (min-colors 16) (background dark)) (:foreground "PaleGreen")) + (((class color) (min-colors 8)) (:foreground "green")) + (t (:bold nil))) "Face used in agenda, to indicate lines switched to DONE. This face is used to de-emphasize items that where brightly colored in the agenda because they were things to do, or overdue. The DONE state itself @@ -346,11 +243,10 @@ is of course immediately visible, but for example a passed deadline is of the frame, for example." :group 'org-faces) -(defface org-headline-done ;; originally copied from font-lock-string-face - (org-compatible-face nil - '((((class color) (min-colors 16) (background light)) (:foreground "RosyBrown")) - (((class color) (min-colors 16) (background dark)) (:foreground "LightSalmon")) - (((class color) (min-colors 8) (background light)) (:bold nil)))) +(defface org-headline-done ;Copied from `font-lock-string-face' + '((((class color) (min-colors 16) (background light)) (:foreground "RosyBrown")) + (((class color) (min-colors 16) (background dark)) (:foreground "LightSalmon")) + (((class color) (min-colors 8) (background light)) (:bold nil))) "Face used to indicate that a headline is DONE. This face is only used if `org-fontify-done-headline' is set. If applies to the part of the headline after the DONE keyword." @@ -388,18 +284,14 @@ determines if it is a foreground or a background color." (string :tag "Color") (sexp :tag "Face"))))) -(defface org-priority ;; originally copied from font-lock-string-face - (org-compatible-face 'font-lock-keyword-face - '((((class color) (min-colors 16) (background light)) (:foreground "RosyBrown")) - (((class color) (min-colors 16) (background dark)) (:foreground "LightSalmon")) - (t (:italic t)))) +(defface org-priority '((t :inherit font-lock-keyword-face)) "Face used for priority cookies." :group 'org-faces) (defcustom org-priority-faces nil "Faces for specific Priorities. This is a list of cons cells, with priority character in the car -and faces in the cdr. The face can be a symbol, a color as +and faces in the cdr. The face can be a symbol, a color as a string, or a property list of attributes, like (:foreground \"blue\" :weight bold :underline t). If it is a color string, the variable `org-faces-easy-properties' @@ -421,18 +313,17 @@ determines if it is a foreground or a background color." (setq org-tags-special-faces-re (concat ":\\(" (mapconcat 'car value "\\|") "\\):")))) -(defface org-checkbox - (org-compatible-face 'bold - '((t (:bold t)))) +(defface org-checkbox '((t :inherit bold)) "Face for checkboxes." :group 'org-faces) +(defface org-checkbox-statistics-todo '((t (:inherit org-todo))) + "Face used for unfinished checkbox statistics." + :group 'org-faces) -(org-copy-face 'org-todo 'org-checkbox-statistics-todo - "Face used for unfinished checkbox statistics.") - -(org-copy-face 'org-done 'org-checkbox-statistics-done - "Face used for finished checkbox statistics.") +(defface org-checkbox-statistics-done '((t (:inherit org-done))) + "Face used for finished checkbox statistics." + :group 'org-faces) (defcustom org-tag-faces nil "Faces for specific tags. @@ -454,44 +345,32 @@ changes." (string :tag "Foreground color") (sexp :tag "Face"))))) -(defface org-table ;; originally copied from font-lock-function-name-face - (org-compatible-face nil - '((((class color) (min-colors 88) (background light)) (:foreground "Blue1")) - (((class color) (min-colors 88) (background dark)) (:foreground "LightSkyBlue")) - (((class color) (min-colors 16) (background light)) (:foreground "Blue")) - (((class color) (min-colors 16) (background dark)) (:foreground "LightSkyBlue")) - (((class color) (min-colors 8) (background light)) (:foreground "blue")) - (((class color) (min-colors 8) (background dark))))) +(defface org-table ;Copied from `font-lock-function-name-face' + '((((class color) (min-colors 88) (background light)) (:foreground "Blue1")) + (((class color) (min-colors 88) (background dark)) (:foreground "LightSkyBlue")) + (((class color) (min-colors 16) (background light)) (:foreground "Blue")) + (((class color) (min-colors 16) (background dark)) (:foreground "LightSkyBlue")) + (((class color) (min-colors 8) (background light)) (:foreground "blue")) + (((class color) (min-colors 8) (background dark)))) "Face used for tables." :group 'org-faces) (defface org-formula - (org-compatible-face nil - '((((class color) (min-colors 88) (background light)) (:foreground "Firebrick")) - (((class color) (min-colors 88) (background dark)) (:foreground "chocolate1")) - (((class color) (min-colors 8) (background light)) (:foreground "red")) - (((class color) (min-colors 8) (background dark)) (:foreground "red")) - (t (:bold t :italic t)))) + '((((class color) (min-colors 88) (background light)) (:foreground "Firebrick")) + (((class color) (min-colors 88) (background dark)) (:foreground "chocolate1")) + (((class color) (min-colors 8) (background light)) (:foreground "red")) + (((class color) (min-colors 8) (background dark)) (:foreground "red")) + (t (:bold t :italic t))) "Face for formulas." :group 'org-faces) -(defface org-code - (org-compatible-face 'shadow - '((((class color grayscale) (min-colors 88) (background light)) - (:foreground "grey50")) - (((class color grayscale) (min-colors 88) (background dark)) - (:foreground "grey70")) - (((class color) (min-colors 8) (background light)) - (:foreground "green")) - (((class color) (min-colors 8) (background dark)) - (:foreground "yellow")))) +(defface org-code '((t :inherit shadow)) "Face for fixed-width text like code snippets." :group 'org-faces :version "22.1") -(defface org-meta-line - (org-compatible-face 'font-lock-comment-face nil) - "Face for meta lines startin with \"#+\"." +(defface org-meta-line '((t :inherit font-lock-comment-face)) + "Face for meta lines starting with \"#+\"." :group 'org-faces :version "22.1") @@ -510,60 +389,37 @@ changes." follows a #+DATE:, #+AUTHOR: or #+EMAIL: keyword." :group 'org-faces) -(defface org-document-info-keyword - (org-compatible-face 'shadow - '((((class color grayscale) (min-colors 88) (background light)) - (:foreground "grey50")) - (((class color grayscale) (min-colors 88) (background dark)) - (:foreground "grey70")) - (((class color) (min-colors 8) (background light)) - (:foreground "green")) - (((class color) (min-colors 8) (background dark)) - (:foreground "yellow")))) +(defface org-document-info-keyword '((t :inherit shadow)) "Face for #+TITLE:, #+AUTHOR:, #+EMAIL: and #+DATE: keywords." :group 'org-faces) -(defface org-block - (org-compatible-face 'shadow - '((((class color grayscale) (min-colors 88) (background light)) - (:foreground "grey50")) - (((class color grayscale) (min-colors 88) (background dark)) - (:foreground "grey70")) - (((class color) (min-colors 8) (background light)) - (:foreground "green")) - (((class color) (min-colors 8) (background dark)) - (:foreground "yellow")))) - "Face text in #+begin ... #+end blocks." +(defface org-block '((t :inherit shadow)) + "Face text in #+begin ... #+end blocks. +For source-blocks `org-src-block-faces' takes precedence. +See also `org-fontify-quote-and-verse-blocks'." :group 'org-faces - :version "22.1") + :version "26.1") -(defface org-block-background '((t ())) - "Face used for the source block background.") - -(org-copy-face 'org-meta-line 'org-block-begin-line - "Face used for the line delimiting the begin of source blocks.") - -(org-copy-face 'org-meta-line 'org-block-end-line - "Face used for the line delimiting the end of source blocks.") - -(defface org-verbatim - (org-compatible-face 'shadow - '((((class color grayscale) (min-colors 88) (background light)) - (:foreground "grey50" :underline t)) - (((class color grayscale) (min-colors 88) (background dark)) - (:foreground "grey70" :underline t)) - (((class color) (min-colors 8) (background light)) - (:foreground "green" :underline t)) - (((class color) (min-colors 8) (background dark)) - (:foreground "yellow" :underline t)))) - "Face for fixed-with text like code snippets." +(defface org-block-begin-line '((t (:inherit org-meta-line))) + "Face used for the line delimiting the begin of source blocks." + :group 'org-faces) + +(defface org-block-end-line '((t (:inherit org-block-begin-line))) + "Face used for the line delimiting the end of source blocks." + :group 'org-faces) + +(defface org-verbatim '((t (:inherit shadow))) + "Face for fixed-with text like code snippets" :group 'org-faces :version "22.1") -(org-copy-face 'org-block 'org-quote - "Face for #+BEGIN_QUOTE ... #+END_QUOTE blocks.") -(org-copy-face 'org-block 'org-verse - "Face for #+BEGIN_VERSE ... #+END_VERSE blocks.") +(defface org-quote '((t (:inherit org-block))) + "Face for #+BEGIN_QUOTE ... #+END_QUOTE blocks." + :group 'org-faces) + +(defface org-verse '((t (:inherit org-block))) + "Face for #+BEGIN_VERSE ... #+END_VERSE blocks." + :group 'org-faces) (defcustom org-fontify-quote-and-verse-blocks nil "Non-nil means, add a special face to #+begin_quote and #+begin_verse block. @@ -573,64 +429,64 @@ content of these blocks will still be treated as Org syntax." :version "24.1" :type 'boolean) -(defface org-clock-overlay ;; copied from secondary-selection - (org-compatible-face nil - '((((class color) (min-colors 88) (background light)) - (:background "yellow1")) - (((class color) (min-colors 88) (background dark)) - (:background "SkyBlue4")) - (((class color) (min-colors 16) (background light)) - (:background "yellow")) - (((class color) (min-colors 16) (background dark)) - (:background "SkyBlue4")) - (((class color) (min-colors 8)) - (:background "cyan" :foreground "black")) - (t (:inverse-video t)))) +(defface org-clock-overlay ;Copied from `secondary-selection' + '((((class color) (min-colors 88) (background light)) + (:background "LightGray" :foreground "black")) + (((class color) (min-colors 88) (background dark)) + (:background "SkyBlue4" :foreground "white")) + (((class color) (min-colors 16) (background light)) + (:background "gray" :foreground "black")) + (((class color) (min-colors 16) (background dark)) + (:background "SkyBlue4" :foreground "white")) + (((class color) (min-colors 8)) + (:background "cyan" :foreground "black")) + (t (:inverse-video t))) "Basic face for displaying the secondary selection." :group 'org-faces) -(defface org-agenda-structure ;; originally copied from font-lock-function-name-face - (org-compatible-face nil - '((((class color) (min-colors 88) (background light)) (:foreground "Blue1")) - (((class color) (min-colors 88) (background dark)) (:foreground "LightSkyBlue")) - (((class color) (min-colors 16) (background light)) (:foreground "Blue")) - (((class color) (min-colors 16) (background dark)) (:foreground "LightSkyBlue")) - (((class color) (min-colors 8)) (:foreground "blue" :bold t)) - (t (:bold t)))) +(defface org-agenda-structure ;Copied from `font-lock-function-name-face' + '((((class color) (min-colors 88) (background light)) (:foreground "Blue1")) + (((class color) (min-colors 88) (background dark)) (:foreground "LightSkyBlue")) + (((class color) (min-colors 16) (background light)) (:foreground "Blue")) + (((class color) (min-colors 16) (background dark)) (:foreground "LightSkyBlue")) + (((class color) (min-colors 8)) (:foreground "blue" :bold t)) + (t (:bold t))) "Face used in agenda for captions and dates." :group 'org-faces) -(org-copy-face 'org-agenda-structure 'org-agenda-date - "Face used in agenda for normal days.") +(defface org-agenda-date '((t (:inherit org-agenda-structure))) + "Face used in agenda for normal days." + :group 'org-faces) -(org-copy-face 'org-agenda-date 'org-agenda-date-today +(defface org-agenda-date-today + '((t (:inherit org-agenda-date :weight bold :italic t))) "Face used in agenda for today." - :weight 'bold :italic 't) + :group 'org-faces) -(org-copy-face 'secondary-selection 'org-agenda-clocking - "Face marking the current clock item in the agenda.") +(defface org-agenda-clocking '((t (:inherit secondary-selection))) + "Face marking the current clock item in the agenda." + :group 'org-faces) -(org-copy-face 'org-agenda-date 'org-agenda-date-weekend +(defface org-agenda-date-weekend '((t (:inherit org-agenda-date :weight bold))) "Face used in agenda for weekend days. -See the variable `org-agenda-weekend-days' for a definition of which days -belong to the weekend." - :weight 'bold) + +See the variable `org-agenda-weekend-days' for a definition of +which days belong to the weekend." + :group 'org-faces) (defface org-scheduled - (org-compatible-face nil - '((((class color) (min-colors 88) (background light)) (:foreground "DarkGreen")) - (((class color) (min-colors 88) (background dark)) (:foreground "PaleGreen")) - (((class color) (min-colors 8)) (:foreground "green")) - (t (:bold t :italic t)))) + '((((class color) (min-colors 88) (background light)) (:foreground "DarkGreen")) + (((class color) (min-colors 88) (background dark)) (:foreground "PaleGreen")) + (((class color) (min-colors 8)) (:foreground "green")) + (t (:bold t :italic t))) "Face for items scheduled for a certain day." :group 'org-faces) (defface org-scheduled-today - (org-compatible-face nil - '((((class color) (min-colors 88) (background light)) (:foreground "DarkGreen")) - (((class color) (min-colors 88) (background dark)) (:foreground "PaleGreen")) - (((class color) (min-colors 8)) (:foreground "green")) - (t (:bold t :italic t)))) + '((((class color) (min-colors 88) (background light)) (:foreground "DarkGreen")) + (((class color) (min-colors 88) (background dark)) (:foreground "PaleGreen")) + (((class color) (min-colors 8)) (:foreground "green")) + (t (:bold t :italic t))) "Face for items scheduled for a certain day." :group 'org-faces) @@ -641,22 +497,20 @@ belong to the weekend." :group 'org-faces) (defface org-scheduled-previously - (org-compatible-face nil - '((((class color) (min-colors 88) (background light)) (:foreground "Firebrick")) - (((class color) (min-colors 88) (background dark)) (:foreground "chocolate1")) - (((class color) (min-colors 8) (background light)) (:foreground "red")) - (((class color) (min-colors 8) (background dark)) (:foreground "red" :bold t)) - (t (:bold t)))) + '((((class color) (min-colors 88) (background light)) (:foreground "Firebrick")) + (((class color) (min-colors 88) (background dark)) (:foreground "chocolate1")) + (((class color) (min-colors 8) (background light)) (:foreground "red")) + (((class color) (min-colors 8) (background dark)) (:foreground "red" :bold t)) + (t (:bold t))) "Face for items scheduled previously, and not yet done." :group 'org-faces) (defface org-upcoming-deadline - (org-compatible-face nil - '((((class color) (min-colors 88) (background light)) (:foreground "Firebrick")) - (((class color) (min-colors 88) (background dark)) (:foreground "chocolate1")) - (((class color) (min-colors 8) (background light)) (:foreground "red")) - (((class color) (min-colors 8) (background dark)) (:foreground "red" :bold t)) - (t (:bold t)))) + '((((class color) (min-colors 88) (background light)) (:foreground "Firebrick")) + (((class color) (min-colors 88) (background dark)) (:foreground "chocolate1")) + (((class color) (min-colors 8) (background light)) (:foreground "red")) + (((class color) (min-colors 8) (background dark)) (:foreground "red" :bold t)) + (t (:bold t))) "Face for items scheduled previously, and not yet done." :group 'org-faces) @@ -666,7 +520,7 @@ belong to the weekend." (0.0 . default)) "Faces for showing deadlines in the agenda. This is a list of cons cells. The cdr of each cell is a face to be used, -and it can also just be like (:foreground \"yellow\"). +and it can also just be like \\='(:foreground \"yellow\"). Each car is a fraction of the head-warning time that must have passed for this the face in the cdr to be used for display. The numbers must be given in descending order. The head-warning time is normally taken @@ -686,65 +540,61 @@ month and 365.24 days for a year)." (sexp :tag "Face")))) (defface org-agenda-restriction-lock - (org-compatible-face nil - '((((class color) (min-colors 88) (background light)) (:background "#eeeeee")) - (((class color) (min-colors 88) (background dark)) (:background "#1C1C1C")) - (((class color) (min-colors 16) (background light)) (:background "#eeeeee")) - (((class color) (min-colors 16) (background dark)) (:background "#1C1C1C")) - (((class color) (min-colors 8)) (:background "cyan" :foreground "black")) - (t (:inverse-video t)))) + '((((class color) (min-colors 88) (background light)) (:background "#eeeeee")) + (((class color) (min-colors 88) (background dark)) (:background "#1C1C1C")) + (((class color) (min-colors 16) (background light)) (:background "#eeeeee")) + (((class color) (min-colors 16) (background dark)) (:background "#1C1C1C")) + (((class color) (min-colors 8)) (:background "cyan" :foreground "black")) + (t (:inverse-video t))) "Face for showing the agenda restriction lock." :group 'org-faces) -(defface org-agenda-filter-tags - (org-compatible-face 'mode-line nil) +(defface org-agenda-filter-tags '((t :inherit mode-line)) "Face for tag(s) in the mode-line when filtering the agenda." :group 'org-faces) -(defface org-agenda-filter-regexp - (org-compatible-face 'mode-line nil) +(defface org-agenda-filter-regexp '((t :inherit mode-line)) "Face for regexp(s) in the mode-line when filtering the agenda." :group 'org-faces) -(defface org-agenda-filter-category - (org-compatible-face 'mode-line nil) - "Face for categories(s) in the mode-line when filtering the agenda." +(defface org-agenda-filter-category '((t :inherit mode-line)) + "Face for categories in the mode-line when filtering the agenda." :group 'org-faces) -(defface org-time-grid ;; originally copied from font-lock-variable-name-face - (org-compatible-face nil - '((((class color) (min-colors 16) (background light)) (:foreground "DarkGoldenrod")) - (((class color) (min-colors 16) (background dark)) (:foreground "LightGoldenrod")) - (((class color) (min-colors 8)) (:foreground "yellow" :weight light)))) +(defface org-agenda-filter-effort '((t :inherit mode-line)) + "Face for effort in the mode-line when filtering the agenda." + :group 'org-faces) + +(defface org-time-grid ;Copied from `font-lock-variable-name-face' + '((((class color) (min-colors 16) (background light)) (:foreground "DarkGoldenrod")) + (((class color) (min-colors 16) (background dark)) (:foreground "LightGoldenrod")) + (((class color) (min-colors 8)) (:foreground "yellow" :weight light))) "Face used for time grids." :group 'org-faces) -(org-copy-face 'org-time-grid 'org-agenda-current-time - "Face used to show the current time in the time grid.") +(defface org-agenda-current-time '((t (:inherit org-time-grid))) + "Face used to show the current time in the time grid." + :group 'org-faces) -(defface org-agenda-diary - (org-compatible-face 'default nil) +(defface org-agenda-diary '((t :inherit default)) "Face used for agenda entries that come from the Emacs diary." :group 'org-faces) -(defface org-agenda-calendar-event - (org-compatible-face 'default nil) +(defface org-agenda-calendar-event '((t :inherit default)) "Face used to show events and appointments in the agenda." :group 'org-faces) -(defface org-agenda-calendar-sexp - (org-compatible-face 'default nil) +(defface org-agenda-calendar-sexp '((t :inherit default)) "Face used to show events computed from a S-expression." :group 'org-faces) (defconst org-level-faces '(org-level-1 org-level-2 org-level-3 org-level-4 - org-level-5 org-level-6 org-level-7 org-level-8 - )) + org-level-5 org-level-6 org-level-7 org-level-8)) (defcustom org-n-level-faces (length org-level-faces) "The number of different faces to be used for headlines. -Org-mode defines 8 different headline faces, so this can be at most 8. +Org mode defines 8 different headline faces, so this can be at most 8. If it is less than 8, the level-1 face gets re-used for level N+1 etc." :type 'integer :group 'org-faces) @@ -777,25 +627,26 @@ level org-n-level-faces" :version "24.4" :package-version '(Org . "8.0")) -(defface org-macro - (org-compatible-face 'org-latex-and-related nil) +(defface org-macro '((t :inherit org-latex-and-related)) "Face for macros." :group 'org-faces :version "24.4" :package-version '(Org . "8.0")) -(defface org-tag-group - (org-compatible-face 'org-tag nil) +(defface org-tag-group '((t :inherit org-tag)) "Face for group tags." :group 'org-faces :version "24.4" :package-version '(Org . "8.0")) -(org-copy-face 'mode-line 'org-mode-line-clock - "Face used for clock display in mode line.") -(org-copy-face 'mode-line 'org-mode-line-clock-overrun +(defface org-mode-line-clock '((t (:inherit mode-line))) + "Face used for clock display in mode line." + :group 'org-faces) + +(defface org-mode-line-clock-overrun + '((t (:inherit mode-line :background "red"))) "Face used for clock display for overrun tasks in mode line." - :background "red") + :group 'org-faces) (provide 'org-faces) diff --git a/lisp/org/org-feed.el b/lisp/org/org-feed.el index cfb4b4f7e33..cd2e95f7ad2 100644 --- a/lisp/org/org-feed.el +++ b/lisp/org/org-feed.el @@ -1,4 +1,4 @@ -;;; org-feed.el --- Add RSS feed items to Org files +;;; org-feed.el --- Add RSS feed items to Org files -*- lexical-binding: t; -*- ;; ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. ;; @@ -19,16 +19,16 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Commentary: ;; -;; This module allows entries to be created and changed in an Org-mode -;; file triggered by items in an RSS feed. The basic functionality is -;; geared toward simply adding new items found in a feed as outline nodes -;; to an Org file. Using hooks, arbitrary actions can be triggered for -;; new or changed items. +;; This module allows entries to be created and changed in an Org mode +;; file triggered by items in an RSS feed. The basic functionality +;; is geared toward simply adding new items found in a feed as +;; outline nodes to an Org file. Using hooks, arbitrary actions can +;; be triggered for new or changed items. ;; ;; Selecting feeds and target locations ;; ------------------------------------ @@ -77,10 +77,8 @@ ;; org-feed.el needs to keep track of which feed items have been handled ;; before, so that they will not be handled again. For this, org-feed.el ;; stores information in a special drawer, FEEDSTATUS, under the heading -;; that received the input of the feed. You should add FEEDSTATUS -;; to your list of drawers in the files that receive feed input: +;; that received the input of the feed. ;; -;; #+DRAWERS: PROPERTIES CLOCK LOGBOOK RESULTS FEEDSTATUS ;; ;; Acknowledgments ;; --------------- @@ -102,8 +100,8 @@ (declare-function xml-substitute-special "xml" (string)) (declare-function org-capture-escaped-% "org-capture" ()) +(declare-function org-capture-expand-embedded-elisp "org-capture" (&optional mark)) (declare-function org-capture-inside-embedded-elisp-p "org-capture" ()) -(declare-function org-capture-expand-embedded-elisp "org-capture" ()) (defgroup org-feed nil "Options concerning RSS feeds as inputs for Org files." @@ -117,7 +115,9 @@ to create inbox items in Org. Each entry is a list with the following items: name a custom name for this feed URL the Feed URL -file the target Org file where entries should be listed +file the target Org file where entries should be listed, when + nil the target becomes the current buffer (may be an + indirect buffer) each time the feed update is invoked headline the headline under which entries should be listed Additional arguments can be given using keyword-value pairs. Many of these @@ -216,10 +216,7 @@ Here are the keyword-value pair allows in `org-feed-alist'. (defcustom org-feed-drawer "FEEDSTATUS" "The name of the drawer for feed status information. Each feed may also specify its own drawer name using the `:drawer' -parameter in `org-feed-alist'. -Note that in order to make these drawers behave like drawers, they must -be added to the variable `org-drawers' or configured with a #+DRAWERS -line." +parameter in `org-feed-alist'." :group 'org-feed :type '(string :tag "Drawer Name")) @@ -300,7 +297,8 @@ it can be a list structured like an entry in `org-feed-alist'." (catch 'exit (let ((name (car feed)) (url (nth 1 feed)) - (file (nth 2 feed)) + (file (or (nth 2 feed) (buffer-file-name (or (buffer-base-buffer) + (current-buffer))))) (headline (nth 3 feed)) (filter (nth 1 (memq :filter feed))) (formatter (nth 1 (memq :formatter feed))) @@ -315,7 +313,7 @@ it can be a list structured like an entry in `org-feed-alist'." (parse-entry (or (nth 1 (memq :parse-entry feed)) 'org-feed-parse-rss-entry)) feed-buffer inbox-pos new-formatted - entries old-status status new changed guid-alist e guid olds) + entries old-status status new changed guid-alist guid olds) (setq feed-buffer (org-feed-get-feed url)) (unless (and feed-buffer (bufferp (get-buffer feed-buffer))) (error "Cannot get feed %s" name)) @@ -407,8 +405,8 @@ it can be a list structured like an entry in `org-feed-alist'." ;; Normalize the visibility of the inbox tree (goto-char inbox-pos) - (hide-subtree) - (show-children) + (outline-hide-subtree) + (org-show-children) (org-cycle-hide-drawers 'children) ;; Hooks and messages @@ -442,7 +440,7 @@ it can be a list structured like an entry in `org-feed-alist'." (if (stringp feed) (setq feed (assoc feed org-feed-alist))) (unless feed (error "No such feed in `org-feed-alist")) - (org-pop-to-buffer-same-window + (pop-to-buffer-same-window (org-feed-update feed 'retrieve-only)) (goto-char (point-min))) @@ -477,8 +475,7 @@ This will find DRAWER and extract the alist." "Write the feed STATUS to DRAWER in entry at POS." (save-excursion (goto-char pos) - (let ((end (save-excursion (org-end-of-subtree t t))) - guid) + (let ((end (save-excursion (org-end-of-subtree t t)))) (if (re-search-forward (concat "^[ \t]*:" drawer ":[ \t]*\n") end t) (progn @@ -514,66 +511,77 @@ ENTRY is a property list. This function adds a `:formatted-for-org' property and returns the full property list. If that property is already present, nothing changes." (require 'org-capture) - (if formatter - (funcall formatter entry) - (let (dlines time escape name tmp - v-h v-t v-T v-u v-U v-a) - (setq dlines (org-split-string (or (plist-get entry :description) "???") - "\n") - v-h (or (plist-get entry :title) (car dlines) "???") - time (or (if (plist-get entry :pubDate) - (org-read-date t t (plist-get entry :pubDate))) - (current-time)) - v-t (format-time-string (org-time-stamp-format nil nil) time) - v-T (format-time-string (org-time-stamp-format t nil) time) - v-u (format-time-string (org-time-stamp-format nil t) time) - v-U (format-time-string (org-time-stamp-format t t) time) - v-a (if (setq tmp (or (and (plist-get entry :guid-permalink) - (plist-get entry :guid)) - (plist-get entry :link))) - (concat "[[" tmp "]]\n") - "")) + (if formatter (funcall formatter entry) + (let* ((dlines + (org-split-string (or (plist-get entry :description) "???") + "\n")) + (time (or (if (plist-get entry :pubDate) + (org-read-date t t (plist-get entry :pubDate))) + (current-time))) + (v-h (or (plist-get entry :title) (car dlines) "???")) + (v-t (format-time-string (org-time-stamp-format nil nil) time)) + (v-T (format-time-string (org-time-stamp-format t nil) time)) + (v-u (format-time-string (org-time-stamp-format nil t) time)) + (v-U (format-time-string (org-time-stamp-format t t) time)) + (v-a (let ((tmp (or (and (plist-get entry :guid-permalink) + (plist-get entry :guid)) + (plist-get entry :link)))) + (if tmp (format "[[%s]]\n" tmp ) "")))) (with-temp-buffer - (insert template) - - ;; Simple %-escapes - ;; before embedded elisp to support simple %-escapes as - ;; arguments for embedded elisp - (goto-char (point-min)) - (while (re-search-forward "%\\([a-zA-Z]+\\)" nil t) - (unless (org-capture-escaped-%) - (setq name (match-string 1) - escape (org-capture-inside-embedded-elisp-p)) - (cond - ((member name '("h" "t" "T" "u" "U" "a")) - (setq tmp (symbol-value (intern (concat "v-" name))))) - ((setq tmp (plist-get entry (intern (concat ":" name)))) - (save-excursion - (save-match-data - (beginning-of-line 1) - (when (looking-at - (concat "^\\([ \t]*\\)%" name "[ \t]*$")) - (setq tmp (org-feed-make-indented-block - tmp (org-get-indentation)))))))) - (when tmp - ;; escape string delimiters `"' when inside %() embedded lisp - (when escape - (setq tmp (replace-regexp-in-string "\"" "\\\\\"" tmp))) - (replace-match tmp t t)))) - - ;; %() embedded elisp - (org-capture-expand-embedded-elisp) - - (decode-coding-string - (buffer-string) (detect-coding-region (point-min) (point-max) t)))))) + (insert template) + (goto-char (point-min)) + + ;; Mark %() embedded elisp for later evaluation. + (org-capture-expand-embedded-elisp 'mark) + + ;; Simple %-escapes. `org-capture-escaped-%' may modify + ;; buffer and cripple match-data. Use markers instead. + (while (re-search-forward "%\\([a-zA-Z]+\\)" nil t) + (let ((key (match-string 1)) + (beg (copy-marker (match-beginning 0))) + (end (copy-marker (match-end 0)))) + (unless (org-capture-escaped-%) + (delete-region beg end) + (set-marker beg nil) + (set-marker end nil) + (let ((replacement + (pcase key + ("h" v-h) + ("t" v-t) + ("T" v-T) + ("u" v-u) + ("U" v-U) + ("a" v-a) + (name + (let ((v (plist-get entry (intern (concat ":" name))))) + (save-excursion + (save-match-data + (beginning-of-line) + (if (looking-at + (concat "^\\([ \t]*\\)%" name "[ \t]*$")) + (org-feed-make-indented-block + v (org-get-indentation)) + v)))))))) + (when replacement + (insert + ;; Escape string delimiters within embedded lisp. + (if (org-capture-inside-embedded-elisp-p) + (replace-regexp-in-string "\"" "\\\\\"" replacement) + replacement))))))) + + ;; %() embedded elisp + (org-capture-expand-embedded-elisp) + + (decode-coding-string + (buffer-string) (detect-coding-region (point-min) (point-max) t)))))) (defun org-feed-make-indented-block (s n) "Add indentation of N spaces to a multiline string S." (if (not (string-match "\n" s)) s (mapconcat 'identity - (org-split-string s "\n") - (concat "\n" (make-string n ?\ ))))) + (org-split-string s "\n") + (concat "\n" (make-string n ?\ ))))) (defun org-feed-skip-http-headers (buffer) "Remove HTTP headers from BUFFER, and return it. @@ -605,6 +613,7 @@ Assumes headers are indeed present!" "Parse BUFFER for RSS feed entries. Returns a list of entries, with each entry a property list, containing the properties `:guid' and `:item-full-text'." + (require 'xml) (let ((case-fold-search t) entries beg end item guid entry) (with-current-buffer buffer @@ -615,8 +624,8 @@ containing the properties `:guid' and `:item-full-text'." end (and (re-search-forward "</item>" nil t) (match-beginning 0))) (setq item (buffer-substring beg end) - guid (if (string-match "<guid\\>.*?>\\(.*?\\)</guid>" item) - (org-match-string-no-properties 1 item))) + guid (if (string-match "<guid\\>.*?>\\([^\000]*?\\)</guid>" item) + (xml-substitute-special (match-string-no-properties 1 item)))) (setq entry (list :guid guid :item-full-text item)) (push entry entries) (widen) diff --git a/lisp/org/org-footnote.el b/lisp/org/org-footnote.el index 553f1240425..e039ab78509 100644 --- a/lisp/org/org-footnote.el +++ b/lisp/org/org-footnote.el @@ -1,4 +1,4 @@ -;;; org-footnote.el --- Footnote support in Org and elsewhere +;;; org-footnote.el --- Footnote support in Org -*- lexical-binding: t; -*- ;; ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. ;; @@ -19,77 +19,73 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Commentary: -;; This file contains the code dealing with footnotes in Org-mode. -;; The code can also be used in arbitrary text modes to provide -;; footnotes. Compared to Steven L Baur's footnote.el it provides -;; better support for resuming editing. It is less configurable than -;; Steve's code, though. +;; This file contains the code dealing with footnotes in Org mode. ;;; Code: -(eval-when-compile - (require 'cl)) +;;;; Declarations + +(require 'cl-lib) (require 'org-macs) (require 'org-compat) -(declare-function message-point-in-header-p "message" ()) +(declare-function org-at-comment-p "org" ()) +(declare-function org-at-heading-p "org" (&optional ignored)) (declare-function org-back-over-empty-lines "org" ()) -(declare-function org-back-to-heading "org" (&optional invisible-ok)) -(declare-function org-combine-plists "org" (&rest plists)) +(declare-function org-edit-footnote-reference "org-src" ()) +(declare-function org-element-at-point "org-element" ()) +(declare-function org-element-class "org-element" (datum &optional parent)) +(declare-function org-element-context "org-element" (&optional element)) +(declare-function org-element-lineage "org-element" (blob &optional types with-self)) +(declare-function org-element-property "org-element" (property element)) +(declare-function org-element-type "org-element" (element)) (declare-function org-end-of-subtree "org" (&optional invisible-ok to-heading)) (declare-function org-fill-paragraph "org" (&optional justify)) -(declare-function org-icompleting-read "org" (&rest args)) -(declare-function org-id-uuid "org-id" ()) (declare-function org-in-block-p "org" (names)) -(declare-function org-in-commented-line "org" ()) -(declare-function org-in-indented-comment-line "org" ()) (declare-function org-in-regexp "org" (re &optional nlines visually)) (declare-function org-in-verbatim-emphasis "org" ()) (declare-function org-inside-LaTeX-fragment-p "org" ()) (declare-function org-inside-latex-macro-p "org" ()) (declare-function org-mark-ring-push "org" (&optional pos buffer)) (declare-function org-show-context "org" (&optional key)) -(declare-function org-trim "org" (s)) -(declare-function org-skip-whitespace "org" ()) +(declare-function org-trim "org" (s &optional keep-lead)) (declare-function outline-next-heading "outline") -(declare-function org-skip-whitespace "org" ()) -(defvar org-outline-regexp-bol) ; defined in org.el -(defvar org-odd-levels-only) ; defined in org.el +(defvar electric-indent-mode) +(defvar org-blank-before-new-entry) ; defined in org.el (defvar org-bracket-link-regexp) ; defined in org.el -(defvar message-cite-prefix-regexp) ; defined in message.el -(defvar message-signature-separator) ; defined in message.el +(defvar org-complex-heading-regexp) ; defined in org.el +(defvar org-odd-levels-only) ; defined in org.el +(defvar org-outline-regexp) ; defined in org.el +(defvar org-outline-regexp-bol) ; defined in org.el + + +;;;; Constants (defconst org-footnote-re - ;; Only [1]-like footnotes are closed in this regexp, as footnotes - ;; from other types might contain square brackets (i.e. links) in - ;; their definition. - ;; - ;; `org-re' is used for regexp compatibility with XEmacs. - (concat "\\[\\(?:" - ;; Match inline footnotes. - (org-re "fn:\\([-_[:word:]]+\\)?:\\|") - ;; Match other footnotes. - "\\(?:\\([0-9]+\\)\\]\\)\\|" - (org-re "\\(fn:[-_[:word:]]+\\)") - "\\)") - "Regular expression for matching footnotes.") - -(defconst org-footnote-definition-re - (org-re "^\\[\\([0-9]+\\|fn:[-_[:word:]]+\\)\\]") - "Regular expression matching the definition of a footnote.") - -(defconst org-footnote-forbidden-blocks - '("ascii" "beamer" "comment" "example" "html" "latex" "odt" "src") + "\\[fn:\\(?:\\(?1:[-_[:word:]]+\\)?\\(:\\)\\|\\(?1:[-_[:word:]]+\\)\\]\\)" + "Regular expression for matching footnotes. +Match group 1 contains footnote's label. It is nil for anonymous +footnotes. Match group 2 is non-nil only when footnote is +inline, i.e., it contains its own definition.") + +(defconst org-footnote-definition-re "^\\[fn:\\([-_[:word:]]+\\)\\]" + "Regular expression matching the definition of a footnote. +Match group 1 contains definition's label.") + +(defconst org-footnote-forbidden-blocks '("comment" "example" "export" "src") "Names of blocks where footnotes are not allowed.") + +;;;; Customization + (defgroup org-footnote nil - "Footnotes in Org-mode." + "Footnotes in Org mode." :tag "Org Footnote" :group 'org) @@ -106,25 +102,21 @@ the notes. However, by hand you may place definitions *anywhere*. If this is a string, during export, all subtrees starting with -this heading will be ignored." - :group 'org-footnote - :type '(choice - (string :tag "Collect footnotes under heading") - (const :tag "Define footnotes locally" nil))) +this heading will be ignored. -(defcustom org-footnote-tag-for-non-org-mode-files "Footnotes:" - "Tag marking the beginning of footnote section. -The Org footnote engine can be used in arbitrary text files as well -as in Org-mode. Outside Org mode, new footnotes are always placed at -the end of the file. When you normalize the notes, any line containing -only this tag will be removed, a new one will be inserted at the end -of the file, followed by the collected and normalized footnotes. +If you don't use the customize interface to change this variable, +you will need to run the following command after the change: -If you don't want any tag in such buffers, set this variable to nil." + `\\[universal-argument] \\[org-element-cache-reset]'" :group 'org-footnote + :initialize 'custom-initialize-default + :set (lambda (var val) + (set var val) + (when (fboundp 'org-element-cache-reset) + (org-element-cache-reset 'all))) :type '(choice - (string :tag "Collect footnotes under tag") - (const :tag "Don't use a tag" nil))) + (string :tag "Collect footnotes under heading") + (const :tag "Define footnotes locally" nil))) (defcustom org-footnote-define-inline nil "Non-nil means define footnotes inline, at reference location. @@ -143,15 +135,13 @@ t Create unique labels of the form [fn:1], [fn:2], etc. confirm Like t, but let the user edit the created value. The label can be removed from the minibuffer to create an anonymous footnote. -random Automatically generate a unique, random label. -plain Automatically create plain number labels like [1]." +random Automatically generate a unique, random label." :group 'org-footnote :type '(choice (const :tag "Prompt for label" nil) (const :tag "Create automatic [fn:N]" t) (const :tag "Offer automatic [fn:N] for editing" confirm) - (const :tag "Create a random label" random) - (const :tag "Create automatic [N]" plain))) + (const :tag "Create a random label" random))) (defcustom org-footnote-auto-adjust nil "Non-nil means automatically adjust footnotes after insert/delete. @@ -179,23 +169,19 @@ extracted will be filled again." :group 'org-footnote :type 'boolean) + +;;;; Predicates + (defun org-footnote-in-valid-context-p () "Is point in a context where footnotes are allowed?" (save-match-data - (not (or (org-in-commented-line) - (org-in-indented-comment-line) + (not (or (org-at-comment-p) (org-inside-LaTeX-fragment-p) ;; Avoid literal example. (org-in-verbatim-emphasis) (save-excursion (beginning-of-line) (looking-at "[ \t]*:[ \t]+")) - ;; Avoid cited text and headers in message-mode. - (and (derived-mode-p 'message-mode) - (or (save-excursion - (beginning-of-line) - (looking-at message-cite-prefix-regexp)) - (message-point-in-header-p))) ;; Avoid forbidden blocks. (org-in-block-p org-footnote-forbidden-blocks))))) @@ -208,13 +194,9 @@ positions, and the definition, when inlined." (or (looking-at org-footnote-re) (org-in-regexp org-footnote-re) (save-excursion (re-search-backward org-footnote-re nil t))) - (/= (match-beginning 0) (point-at-bol))) + (/= (match-beginning 0) (line-beginning-position))) (let* ((beg (match-beginning 0)) - (label (or (org-match-string-no-properties 2) - (org-match-string-no-properties 3) - ;; Anonymous footnotes don't have labels - (and (match-string 1) - (concat "fn:" (org-match-string-no-properties 1))))) + (label (match-string-no-properties 1)) ;; Inline footnotes don't end at (match-end 0) as ;; `org-footnote-re' stops just after the second colon. ;; Find the real ending with `scan-sexps', so Org doesn't @@ -222,7 +204,8 @@ positions, and the definition, when inlined." (end (ignore-errors (scan-sexps beg 1)))) ;; Point is really at a reference if it's located before true ;; ending of the footnote. - (when (and end (< (point) end) + (when (and end + (< (point) end) ;; Verify match isn't a part of a link. (not (save-excursion (goto-char beg) @@ -234,16 +217,17 @@ positions, and the definition, when inlined." (not (org-inside-latex-macro-p))) (list label beg end ;; Definition: ensure this is an inline footnote first. - (and (or (not label) (match-string 1)) - (org-trim (buffer-substring-no-properties - (match-end 0) (1- end))))))))) + (and (match-end 2) + (org-trim + (buffer-substring-no-properties + (match-end 0) (1- end))))))))) (defun org-footnote-at-definition-p () "Is point within a footnote definition? This matches only pure definitions like [1] or [fn:name] at the beginning of a line. It does not match references like -[fn:name:definition], where the footnote text is included and +\[fn:name:definition], where the footnote text is included and defined locally. The return value will be nil if not at a footnote definition, and @@ -259,26 +243,224 @@ otherwise." (concat org-outline-regexp-bol "\\|^\\([ \t]*\n\\)\\{2,\\}") nil t)))) (when (re-search-backward org-footnote-definition-re lim t) - (let ((label (org-match-string-no-properties 1)) + (let ((label (match-string-no-properties 1)) (beg (match-beginning 0)) (beg-def (match-end 0)) - ;; In message-mode, do not search after signature. - (end (let ((bound (and (derived-mode-p 'message-mode) - (save-excursion - (goto-char (point-max)) - (re-search-backward - message-signature-separator nil t))))) - (if (progn - (end-of-line) - (re-search-forward - (concat org-outline-regexp-bol "\\|" - org-footnote-definition-re "\\|" - "^\\([ \t]*\n\\)\\{2,\\}") bound 'move)) - (match-beginning 0) - (point))))) + (end (if (progn + (end-of-line) + (re-search-forward + (concat org-outline-regexp-bol "\\|" + org-footnote-definition-re "\\|" + "^\\([ \t]*\n\\)\\{2,\\}") nil 'move)) + (match-beginning 0) + (point)))) (list label beg end (org-trim (buffer-substring-no-properties beg-def end))))))))) + +;;;; Internal functions + +(defun org-footnote--allow-reference-p () + "Non-nil when a footnote reference can be inserted at point." + ;; XXX: This is similar to `org-footnote-in-valid-context-p' but + ;; more accurate and usually faster, except in some corner cases. + ;; It may replace it after doing proper benchmarks as it would be + ;; used in fontification. + (unless (bolp) + (let* ((context (org-element-context)) + (type (org-element-type context))) + (cond + ;; No footnote reference in attributes. + ((let ((post (org-element-property :post-affiliated context))) + (and post (< (point) post))) + nil) + ;; Paragraphs and blank lines at top of document are fine. + ((memq type '(nil paragraph))) + ;; So are contents of verse blocks. + ((eq type 'verse-block) + (and (>= (point) (org-element-property :contents-begin context)) + (< (point) (org-element-property :contents-end context)))) + ;; In an headline or inlinetask, point must be either on the + ;; heading itself or on the blank lines below. + ((memq type '(headline inlinetask)) + (or (not (org-at-heading-p)) + (and (save-excursion + (beginning-of-line) + (and (let ((case-fold-search t)) + (not (looking-at-p "\\*+ END[ \t]*$"))) + (let ((case-fold-search nil)) + (looking-at org-complex-heading-regexp)))) + (match-beginning 4) + (>= (point) (match-beginning 4)) + (or (not (match-beginning 5)) + (< (point) (match-beginning 5)))))) + ;; White spaces after an object or blank lines after an element + ;; are OK. + ((>= (point) + (save-excursion (goto-char (org-element-property :end context)) + (skip-chars-backward " \r\t\n") + (if (eq (org-element-class context) 'object) (point) + (1+ (line-beginning-position 2)))))) + ;; Other elements are invalid. + ((eq (org-element-class context) 'element) nil) + ;; Just before object is fine. + ((= (point) (org-element-property :begin context))) + ;; Within recursive object too, but not in a link. + ((eq type 'link) nil) + ((let ((cbeg (org-element-property :contents-begin context)) + (cend (org-element-property :contents-end context))) + (and cbeg (>= (point) cbeg) (<= (point) cend)))))))) + +(defun org-footnote--clear-footnote-section () + "Remove all footnote sections in buffer and create a new one. +New section is created at the end of the buffer, before any file +local variable definition. Leave point within the new section." + (when org-footnote-section + (goto-char (point-min)) + (let ((regexp + (format "^\\*+ +%s[ \t]*$" + (regexp-quote org-footnote-section)))) + (while (re-search-forward regexp nil t) + (delete-region + (match-beginning 0) + (progn (org-end-of-subtree t t) + (if (not (eobp)) (point) + (org-footnote--goto-local-insertion-point) + (skip-chars-forward " \t\n") + (if (eobp) (point) (line-beginning-position))))))) + (goto-char (point-max)) + (org-footnote--goto-local-insertion-point) + (when (and (cdr (assq 'heading org-blank-before-new-entry)) + (zerop (save-excursion (org-back-over-empty-lines)))) + (insert "\n")) + (insert "* " org-footnote-section "\n"))) + +(defun org-footnote--set-label (label) + "Set label of footnote at point to string LABEL. +Assume point is at the beginning of the reference or definition +to rename." + (forward-char 4) + (cond ((eq (char-after) ?:) (insert label)) + ((looking-at "\\([-_[:word:]]+\\)") (replace-match label nil nil nil 1)) + (t nil))) + +(defun org-footnote--collect-references (&optional anonymous) + "Collect all labeled footnote references in current buffer. + +Return an alist where associations follow the pattern + + (LABEL MARKER TOP-LEVEL SIZE) + +with + + LABEL the label of the of the definition, + MARKER a marker pointing to its beginning, + TOP-LEVEL a boolean, nil when the footnote is contained within + another one, + SIZE the length of the inline definition, in characters, + or nil for non-inline references. + +When optional ANONYMOUS is non-nil, also collect anonymous +references. In such cases, LABEL is nil. + +References are sorted according to a deep-reading order." + (org-with-wide-buffer + (goto-char (point-min)) + (let ((regexp (if anonymous org-footnote-re "\\[fn:[-_[:word:]]+[]:]")) + references nested) + (save-excursion + (while (re-search-forward regexp nil t) + ;; Ignore definitions. + (unless (and (eq (char-before) ?\]) + (= (line-beginning-position) (match-beginning 0))) + ;; Ensure point is within the reference before parsing it. + (backward-char) + (let ((object (org-element-context))) + (when (eq (org-element-type object) 'footnote-reference) + (let* ((label (org-element-property :label object)) + (begin (org-element-property :begin object)) + (size + (and (eq (org-element-property :type object) 'inline) + (- (org-element-property :contents-end object) + (org-element-property :contents-begin object))))) + (let ((d (org-element-lineage object '(footnote-definition)))) + (push (list label (copy-marker begin) (not d) size) + references) + (when d + ;; Nested references are stored in alist NESTED. + ;; Associations there follow the pattern + ;; + ;; (DEFINITION-LABEL . REFERENCES) + (let* ((def-label (org-element-property :label d)) + (labels (assoc def-label nested))) + (if labels (push label (cdr labels)) + (push (list def-label label) nested))))))))))) + ;; Sort the list of references. Nested footnotes have priority + ;; over top-level ones. + (letrec ((ordered nil) + (add-reference + (lambda (ref allow-nested) + (when (or allow-nested (nth 2 ref)) + (push ref ordered) + (dolist (r (mapcar (lambda (l) (assoc l references)) + (reverse + (cdr (assoc (nth 0 ref) nested))))) + (funcall add-reference r t)))))) + (dolist (r (reverse references) (nreverse ordered)) + (funcall add-reference r nil)))))) + +(defun org-footnote--collect-definitions (&optional delete) + "Collect all footnote definitions in current buffer. + +Return an alist where associations follow the pattern + + (LABEL . DEFINITION) + +with LABEL and DEFINITION being, respectively, the label and the +definition of the footnote, as strings. + +When optional argument DELETE is non-nil, delete the definition +while collecting them." + (org-with-wide-buffer + (goto-char (point-min)) + (let (definitions seen) + (while (re-search-forward org-footnote-definition-re nil t) + (backward-char) + (let ((element (org-element-at-point))) + (let ((label (org-element-property :label element))) + (when (and (eq (org-element-type element) 'footnote-definition) + (not (member label seen))) + (push label seen) + (let* ((beg (progn + (goto-char (org-element-property :begin element)) + (skip-chars-backward " \r\t\n") + (if (bobp) (point) (line-beginning-position 2)))) + (end (progn + (goto-char (org-element-property :end element)) + (skip-chars-backward " \r\t\n") + (line-beginning-position 2))) + (def (org-trim (buffer-substring-no-properties beg end)))) + (push (cons label def) definitions) + (when delete (delete-region beg end))))))) + definitions))) + +(defun org-footnote--goto-local-insertion-point () + "Find insertion point for footnote, just before next outline heading. +Assume insertion point is within currently accessible part of the buffer." + (org-with-limited-levels (outline-next-heading)) + ;; Skip file local variables. See `modify-file-local-variable'. + (when (eobp) + (let ((case-fold-search t)) + (re-search-backward "^[ \t]*# +Local Variables:" + (max (- (point-max) 3000) (point-min)) + t))) + (skip-chars-backward " \t\n") + (forward-line) + (unless (bolp) (insert "\n"))) + + +;;;; Navigation + (defun org-footnote-get-next-reference (&optional label backward limit) "Return complete reference of the next footnote. @@ -289,7 +471,7 @@ the buffer position bounding the search. Return value is a list like those provided by `org-footnote-at-reference-p'. If no footnote is found, return nil." (save-excursion - (let* ((label-fmt (if label (format "\\[%s[]:]" label) org-footnote-re))) + (let* ((label-fmt (if label (format "\\[fn:%s[]:]" label) org-footnote-re))) (catch 'exit (while t (unless (funcall (if backward #'re-search-backward #'re-search-forward) @@ -313,59 +495,54 @@ If no footnote is found, return nil." (unless (re-search-forward org-footnote-re limit t) (goto-char origin) (throw 'exit nil)) - ;; Beware: with [1]-like footnotes point will be just after + ;; Beware: with non-inline footnotes point will be just after ;; the closing square bracket. (backward-char) (cond ((setq ref (org-footnote-at-reference-p)) (throw 'exit ref)) - ;; Definition: also grab the last square bracket, only - ;; matched in `org-footnote-re' for [1]-like footnotes. + ;; Definition: also grab the last square bracket, matched in + ;; `org-footnote-re' for non-inline footnotes. ((save-match-data (org-footnote-at-definition-p)) (let ((end (match-end 0))) (throw 'exit (list nil (match-beginning 0) - (if (eq (char-before end) 93) end (1+ end))))))))))) + (if (eq (char-before end) ?\]) end (1+ end))))))))))) -(defun org-footnote-get-definition (label) - "Return label, boundaries and definition of the footnote LABEL." - (let* ((label (regexp-quote (org-footnote-normalize-label label))) - (re (format "^\\[%s\\]\\|.\\[%s:" label label)) - pos) - (save-excursion - (save-restriction - (when (or (re-search-forward re nil t) - (and (goto-char (point-min)) - (re-search-forward re nil t)) - (and (progn (widen) t) - (goto-char (point-min)) - (re-search-forward re nil t))) - (let ((refp (org-footnote-at-reference-p))) - (cond - ((and (nth 3 refp) refp)) - ((org-footnote-at-definition-p))))))))) - -(defun org-footnote-goto-definition (label) +(defun org-footnote-goto-definition (label &optional location) "Move point to the definition of the footnote LABEL. -Return a non-nil value when a definition has been found." + +LOCATION, when non-nil specifies the buffer position of the +definition. + +Throw an error if there is no definition or if it cannot be +reached from current narrowed part of buffer. Return a non-nil +value if point was successfully moved." (interactive "sLabel: ") - (org-mark-ring-push) - (let ((def (org-footnote-get-definition label))) - (if (not def) - (error "Cannot find definition of footnote %s" label) - (goto-char (nth 1 def)) - (looking-at (format "\\[%s\\]\\|\\[%s:" label label)) - (goto-char (match-end 0)) - (org-show-context 'link-search) - (when (derived-mode-p 'org-mode) - (message "Edit definition and go back with `C-c &' or, if unique, with `C-c C-c'.")) - t))) + (let* ((label (org-footnote-normalize-label label)) + (def-start (or location (nth 1 (org-footnote-get-definition label))))) + (cond + ((not def-start) + (user-error "Cannot find definition of footnote %s" label)) + ((or (> def-start (point-max)) (< def-start (point-min))) + (user-error "Definition is outside narrowed part of buffer"))) + (org-mark-ring-push) + (goto-char def-start) + (looking-at (format "\\[fn:%s[]:] ?" (regexp-quote label))) + (goto-char (match-end 0)) + (org-show-context 'link-search) + (when (derived-mode-p 'org-mode) + (message "%s" (substitute-command-keys + "Edit definition and go back with \ +`\\[org-mark-ring-goto]' or, if unique, with `\\[org-ctrl-c-ctrl-c]'."))) + t)) (defun org-footnote-goto-previous-reference (label) "Find the first closest (to point) reference of footnote with label LABEL." (interactive "sLabel: ") (org-mark-ring-push) - (let* ((label (org-footnote-normalize-label label)) ref) + (let ((label (org-footnote-normalize-label label)) + ref) (save-excursion (setq ref (or (org-footnote-get-next-reference label t) (org-footnote-get-next-reference label) @@ -379,62 +556,74 @@ Return a non-nil value when a definition has been found." (goto-char (nth 1 ref)) (org-show-context 'link-search)))) + +;;;; Getters + (defun org-footnote-normalize-label (label) - "Return LABEL as an appropriate string." - (cond - ((numberp label) (number-to-string label)) - ((equal "" label) nil) - ((not (string-match "^[0-9]+$\\|^fn:" label)) - (concat "fn:" label)) - (t label))) - -(defun org-footnote-all-labels (&optional with-defs) - "Return list with all defined foot labels used in the buffer. - -If WITH-DEFS is non-nil, also associate the definition to each -label. The function will then return an alist whose key is label -and value definition." - (let* (rtn - (push-to-rtn - (function - ;; Depending on WITH-DEFS, store label or (label . def) of - ;; footnote reference/definition given as argument in RTN. - (lambda (el) - (let ((lbl (car el))) - (push (if with-defs (cons lbl (nth 3 el)) lbl) rtn)))))) - (save-excursion - (save-restriction - (widen) - ;; Find all labels found in definitions. - (goto-char (point-min)) - (let (def) - (while (re-search-forward org-footnote-definition-re nil t) - (when (setq def (org-footnote-at-definition-p)) - (funcall push-to-rtn def)))) - ;; Find all labels found in references. - (goto-char (point-min)) - (let (ref) - (while (setq ref (org-footnote-get-next-reference)) - (goto-char (nth 2 ref)) - (and (car ref) ; ignore anonymous footnotes - (not (funcall (if with-defs #'assoc #'member) (car ref) rtn)) - (funcall push-to-rtn ref)))))) - rtn)) + "Return LABEL without \"fn:\" prefix. +If LABEL is the empty string or constituted of white spaces only, +return nil instead." + (pcase (org-trim label) + ("" nil) + ((pred (string-prefix-p "fn:")) (substring label 3)) + (_ label))) + +(defun org-footnote-get-definition (label) + "Return label, boundaries and definition of the footnote LABEL." + (let* ((label (regexp-quote (org-footnote-normalize-label label))) + (re (format "^\\[fn:%s\\]\\|.\\[fn:%s:" label label))) + (org-with-wide-buffer + (goto-char (point-min)) + (catch 'found + (while (re-search-forward re nil t) + (let* ((datum (progn (backward-char) (org-element-context))) + (type (org-element-type datum))) + (when (memq type '(footnote-definition footnote-reference)) + (throw 'found + (list + label + (org-element-property :begin datum) + (org-element-property :end datum) + (let ((cbeg (org-element-property :contents-begin datum))) + (if (not cbeg) "" + (replace-regexp-in-string + "[ \t\n]*\\'" + "" + (buffer-substring-no-properties + cbeg + (org-element-property :contents-end datum)))))))))) + nil)))) + +(defun org-footnote-all-labels () + "List all defined footnote labels used throughout the buffer. +This function ignores narrowing, if any." + (org-with-wide-buffer + (goto-char (point-min)) + (let (all) + (while (re-search-forward org-footnote-re nil t) + (backward-char) + (let ((context (org-element-context))) + (when (memq (org-element-type context) + '(footnote-definition footnote-reference)) + (let ((label (org-element-property :label context))) + (when label (cl-pushnew label all :test #'equal)))))) + all))) (defun org-footnote-unique-label (&optional current) "Return a new unique footnote label. -The function returns the first \"fn:N\" or \"N\" label that is -currently not used. +The function returns the first numeric label currently unused. Optional argument CURRENT is the list of labels active in the buffer." - (unless current (setq current (org-footnote-all-labels))) - (let ((fmt (if (eq org-footnote-auto-label 'plain) "%d" "fn:%d")) - (cnt 1)) - (while (member (format fmt cnt) current) - (incf cnt)) - (format fmt cnt))) + (let ((current (or current (org-footnote-all-labels)))) + (let ((count 1)) + (while (member (number-to-string count) current) + (cl-incf count)) + (number-to-string count)))) + + +;;;; Adding, Deleting Footnotes (defun org-footnote-new () "Insert a new footnote. @@ -442,343 +631,66 @@ This command prompts for a label. If this is a label referencing an existing label, only insert the label. If the footnote label is empty or new, let the user edit the definition of the footnote." (interactive) - (unless (org-footnote-in-valid-context-p) - (error "Cannot insert a footnote here")) - (let* ((lbls (and (not (equal org-footnote-auto-label 'random)) - (org-footnote-all-labels))) - (propose (and (not (equal org-footnote-auto-label 'random)) - (org-footnote-unique-label lbls))) + (unless (org-footnote--allow-reference-p) + (user-error "Cannot insert a footnote here")) + (let* ((all (org-footnote-all-labels)) (label - (org-footnote-normalize-label - (cond - ((member org-footnote-auto-label '(t plain)) - propose) - ((equal org-footnote-auto-label 'random) - (require 'org-id) - (substring (org-id-uuid) 0 8)) - (t - (org-icompleting-read - "Label (leave empty for anonymous): " - (mapcar 'list lbls) nil nil - (if (eq org-footnote-auto-label 'confirm) propose nil))))))) - (cond - ((bolp) (error "Cannot create a footnote reference at left margin")) - ((not label) - (insert "[fn:: ]") - (backward-char 1)) - ((member label lbls) - (insert "[" label "]") - (message "New reference to existing note")) - (org-footnote-define-inline - (insert "[" label ": ]") - (backward-char 1) - (org-footnote-auto-adjust-maybe)) - (t - (insert "[" label "]") - (org-footnote-create-definition label) - (org-footnote-auto-adjust-maybe))))) - -(defvar org-blank-before-new-entry) ; silence byte-compiler + (if (eq org-footnote-auto-label 'random) + (format "%x" (random most-positive-fixnum)) + (org-footnote-normalize-label + (let ((propose (org-footnote-unique-label all))) + (if (eq org-footnote-auto-label t) propose + (completing-read + "Label (leave empty for anonymous): " + (mapcar #'list all) nil nil + (and (eq org-footnote-auto-label 'confirm) propose)))))))) + (cond ((not label) + (insert "[fn::]") + (backward-char 1)) + ((member label all) + (insert "[fn:" label "]") + (message "New reference to existing note")) + (org-footnote-define-inline + (insert "[fn:" label ":]") + (backward-char 1) + (org-footnote-auto-adjust-maybe)) + (t + (insert "[fn:" label "]") + (let ((p (org-footnote-create-definition label))) + ;; `org-footnote-goto-definition' needs to be called + ;; after `org-footnote-auto-adjust-maybe'. Otherwise + ;; both label and location of the definition are lost. + ;; On the contrary, it needs to be called before + ;; `org-edit-footnote-reference' so that the remote + ;; editing buffer can display the correct label. + (if (ignore-errors (org-footnote-goto-definition label p)) + (org-footnote-auto-adjust-maybe) + ;; Definition was created outside current scope: edit + ;; it remotely. + (org-footnote-auto-adjust-maybe) + (org-edit-footnote-reference))))))) + (defun org-footnote-create-definition (label) - "Start the definition of a footnote with label LABEL." - (interactive "sLabel: ") + "Start the definition of a footnote with label LABEL. +Return buffer position at the beginning of the definition. This +function doesn't move point." (let ((label (org-footnote-normalize-label label)) - electric-indent-mode) ;; Prevent wrong indentation - (cond - ;; In an Org file. - ((derived-mode-p 'org-mode) - ;; If `org-footnote-section' is defined, find it, or create it - ;; at the end of the buffer. - (when org-footnote-section - (goto-char (point-min)) - (let ((re (concat "^\\*+[ \t]+" org-footnote-section "[ \t]*$"))) - (unless (or (re-search-forward re nil t) - (and (progn (widen) t) - (re-search-forward re nil t))) - (goto-char (point-max)) - (skip-chars-backward " \t\r\n") - (unless (bolp) (newline)) - ;; Insert new section. Separate it from the previous one - ;; with a blank line, unless `org-blank-before-new-entry' - ;; explicitly says no. - (when (and (cdr (assq 'heading org-blank-before-new-entry)) - (zerop (save-excursion (org-back-over-empty-lines)))) - (insert "\n")) - (insert "* " org-footnote-section "\n")))) - ;; Move to the end of this entry (which may be - ;; `org-footnote-section' or the current one). - (org-footnote-goto-local-insertion-point) - (org-show-context 'link-search)) - (t - ;; In a non-Org file. Search for footnote tag, or create it if - ;; specified (at the end of buffer, or before signature if in - ;; Message mode). Set point after any definition already there. - (let ((tag (and org-footnote-tag-for-non-org-mode-files - (concat "^" (regexp-quote - org-footnote-tag-for-non-org-mode-files) - "[ \t]*$"))) - (max (if (and (derived-mode-p 'message-mode) - (goto-char (point-max)) - (re-search-backward - message-signature-separator nil t)) - (progn - ;; Ensure one blank line separates last - ;; footnote from signature. - (beginning-of-line) - (open-line 2) - (point-marker)) - (point-max-marker)))) - (set-marker-insertion-type max t) - (goto-char max) - ;; Check if the footnote tag is defined but missing. In this - ;; case, insert it, before any footnote or one blank line - ;; after any previous text. - (when (and tag (not (re-search-backward tag nil t))) - (skip-chars-backward " \t\r\n") - (while (re-search-backward org-footnote-definition-re nil t)) - (unless (bolp) (newline 2)) - (insert org-footnote-tag-for-non-org-mode-files "\n\n")) - ;; Remove superfluous white space and clear marker. - (goto-char max) - (skip-chars-backward " \t\r\n") - (delete-region (point) max) - (unless (bolp) (newline)) - (set-marker max nil)))) - ;; Insert footnote label. - (when (zerop (org-back-over-empty-lines)) (newline)) - (insert "[" label "] \n") - (backward-char) - ;; Only notify user about next possible action when in an Org - ;; buffer, as the bindings may have different meanings otherwise. - (when (derived-mode-p 'org-mode) - (message - "Edit definition and go back with `C-c &' or, if unique, with `C-c C-c'.")))) - -;;;###autoload -(defun org-footnote-action (&optional special) - "Do the right thing for footnotes. - -When at a footnote reference, jump to the definition. - -When at a definition, jump to the references if they exist, offer -to create them otherwise. - -When neither at definition or reference, create a new footnote, -interactively. - -With prefix arg SPECIAL, offer additional commands in a menu." - (interactive "P") - (let (tmp c) - (cond - (special - (message "Footnotes: [s]ort | [r]enumber fn:N | [S]=r+s |->[n]umeric | [d]elete") - (setq c (read-char-exclusive)) - (cond - ((eq c ?s) (org-footnote-normalize 'sort)) - ((eq c ?r) (org-footnote-renumber-fn:N)) - ((eq c ?S) - (org-footnote-renumber-fn:N) - (org-footnote-normalize 'sort)) - ((eq c ?n) (org-footnote-normalize)) - ((eq c ?d) (org-footnote-delete)) - (t (error "No such footnote command %c" c)))) - ((setq tmp (org-footnote-at-reference-p)) - (cond - ;; Anonymous footnote: move point at the beginning of its - ;; definition. - ((not (car tmp)) - (goto-char (nth 1 tmp)) - (forward-char 5)) - ;; A definition exists: move to it. - ((ignore-errors (org-footnote-goto-definition (car tmp)))) - ;; No definition exists: offer to create it. - ((yes-or-no-p (format "No definition for %s. Create one? " (car tmp))) - (org-footnote-create-definition (car tmp))))) - ((setq tmp (org-footnote-at-definition-p)) - (org-footnote-goto-previous-reference (car tmp))) - (t (org-footnote-new))))) - -;;;###autoload -(defun org-footnote-normalize (&optional sort-only) - "Collect the footnotes in various formats and normalize them. - -This finds the different sorts of footnotes allowed in Org, and -normalizes them to the usual [N] format. - -When SORT-ONLY is set, only sort the footnote definitions into the -referenced sequence." - ;; This is based on Paul's function, but rewritten. - ;; - ;; Re-create `org-with-limited-levels', but not limited to Org - ;; buffers. - (let* ((limit-level - (and (boundp 'org-inlinetask-min-level) - org-inlinetask-min-level - (1- org-inlinetask-min-level))) - (nstars (and limit-level - (if org-odd-levels-only (1- (* limit-level 2)) - limit-level))) - (org-outline-regexp - (concat "\\*" (if nstars (format "\\{1,%d\\} " nstars) "+ "))) - (count 0) - ins-point ref ref-table) - (save-excursion - ;; 1. Find every footnote reference, extract the definition, and - ;; collect that data in REF-TABLE. If SORT-ONLY is nil, also - ;; normalize references. - (goto-char (point-min)) - (while (setq ref (org-footnote-get-next-reference)) - (let* ((lbl (car ref)) - (pos (nth 1 ref)) - ;; When footnote isn't anonymous, check if it's label - ;; (REF) is already stored in REF-TABLE. In that case, - ;; extract number used to identify it (MARKER). If - ;; footnote is unknown, increment the global counter - ;; (COUNT) to create an unused identifier. - (a (and lbl (assoc lbl ref-table))) - (marker (or (nth 1 a) (incf count))) - ;; Is the reference inline or pointing to an inline - ;; footnote? - (inlinep (or (stringp (nth 3 ref)) (nth 3 a)))) - ;; Replace footnote reference with [MARKER]. Maybe fill - ;; paragraph once done. If SORT-ONLY is non-nil, only move - ;; to the end of reference found to avoid matching it twice. - (if sort-only (goto-char (nth 2 ref)) - (delete-region (nth 1 ref) (nth 2 ref)) - (goto-char (nth 1 ref)) - (insert (format "[%d]" marker)) - (and inlinep - org-footnote-fill-after-inline-note-extraction - (org-fill-paragraph))) - ;; Add label (REF), identifier (MARKER), definition (DEF) - ;; type (INLINEP) and position (POS) to REF-TABLE if data - ;; was unknown. - (unless a - (let ((def (or (nth 3 ref) ; Inline definition. - (nth 3 (org-footnote-get-definition lbl))))) - (push (list lbl marker def - ;; Reference beginning position is a marker - ;; to preserve it during further buffer - ;; modifications. - inlinep (copy-marker pos)) ref-table))))) - ;; 2. Find and remove the footnote section, if any. Also - ;; determine where footnotes shall be inserted (INS-POINT). - (cond - ((and org-footnote-section (derived-mode-p 'org-mode)) - (goto-char (point-min)) - (if (re-search-forward - (concat "^\\*[ \t]+" (regexp-quote org-footnote-section) - "[ \t]*$") nil t) - (delete-region (match-beginning 0) (org-end-of-subtree t t))) - ;; A new footnote section is inserted by default at the end of - ;; the buffer. - (goto-char (point-max)) - (skip-chars-backward " \r\t\n") - (forward-line) - (unless (bolp) (newline))) - ;; No footnote section set: Footnotes will be added at the end - ;; of the section containing their first reference. - ((derived-mode-p 'org-mode)) - (t - ;; Remove any left-over tag in the buffer, if one is set up. - (when org-footnote-tag-for-non-org-mode-files - (let ((tag (concat "^" (regexp-quote - org-footnote-tag-for-non-org-mode-files) - "[ \t]*$"))) - (goto-char (point-min)) - (while (re-search-forward tag nil t) - (replace-match "") - (delete-region (point) (progn (forward-line) (point)))))) - ;; In Message mode, ensure footnotes are inserted before the - ;; signature. - (if (and (derived-mode-p 'message-mode) - (goto-char (point-max)) - (re-search-backward message-signature-separator nil t)) - (beginning-of-line) - (goto-char (point-max))))) - (setq ins-point (point-marker)) - ;; 3. Clean-up REF-TABLE. - (setq ref-table - (delq nil - (mapcar - (lambda (x) - (cond - ;; When only sorting, ignore inline footnotes. - ;; Also clear position marker. - ((and sort-only (nth 3 x)) - (set-marker (nth 4 x) nil) nil) - ;; No definition available: provide one. - ((not (nth 2 x)) - (append - (list (car x) (nth 1 x) - (format "DEFINITION NOT FOUND: %s" (car x))) - (nthcdr 3 x))) - (t x))) - ref-table))) - (setq ref-table (nreverse ref-table)) - ;; 4. Remove left-over definitions in the buffer. - (mapc (lambda (x) - (unless (nth 3 x) (org-footnote-delete-definitions (car x)))) - ref-table) - ;; 5. Insert the footnotes again in the buffer, at the - ;; appropriate spot. - (goto-char ins-point) - (cond - ;; No footnote: exit. - ((not ref-table)) - ;; Cases when footnotes should be inserted in one place. - ((or (not (derived-mode-p 'org-mode)) org-footnote-section) - ;; Insert again the section title, if any. Ensure that title, - ;; or the subsequent footnotes, will be separated by a blank - ;; lines from the rest of the document. In an Org buffer, - ;; separate section with a blank line, unless explicitly - ;; stated in `org-blank-before-new-entry'. - (if (not (derived-mode-p 'org-mode)) - (progn (skip-chars-backward " \t\n\r") - (delete-region (point) ins-point) - (unless (bolp) (newline)) - (when org-footnote-tag-for-non-org-mode-files - (insert "\n" org-footnote-tag-for-non-org-mode-files "\n"))) - (when (and (cdr (assq 'heading org-blank-before-new-entry)) - (zerop (save-excursion (org-back-over-empty-lines)))) - (insert "\n")) - (insert "* " org-footnote-section "\n")) - (set-marker ins-point nil) - ;; Insert the footnotes, separated by a blank line. - (insert - (mapconcat - (lambda (x) - ;; Clean markers. - (set-marker (nth 4 x) nil) - (format "\n[%s] %s" (nth (if sort-only 0 1) x) (nth 2 x))) - ref-table "\n")) - (unless (eobp) (insert "\n\n"))) - ;; Each footnote definition has to be inserted at the end of - ;; the section where its first reference belongs. - (t - (mapc - (lambda (x) - (let ((pos (nth 4 x))) - (goto-char pos) - ;; Clean marker. - (set-marker pos nil)) - (org-footnote-goto-local-insertion-point) - (insert (format "\n[%s] %s\n" - (if sort-only (car x) (nth 1 x)) - (nth 2 x)))) - ref-table)))))) - -(defun org-footnote-goto-local-insertion-point () - "Find insertion point for footnote, just before next outline heading." - (org-with-limited-levels (outline-next-heading)) - (or (bolp) (newline)) - (beginning-of-line 0) - (while (and (not (bobp)) (= (char-after) ?#)) - (beginning-of-line 0)) - (if (let ((case-fold-search t)) (looking-at "[ \t]*#\\+tblfm:")) (beginning-of-line 2)) - (end-of-line 1) - (skip-chars-backward "\n\r\t ") - (forward-line)) + electric-indent-mode) ; Prevent wrong indentation. + (org-with-wide-buffer + (cond + ((not org-footnote-section) (org-footnote--goto-local-insertion-point)) + ((save-excursion + (goto-char (point-min)) + (re-search-forward + (concat "^\\*+[ \t]+" (regexp-quote org-footnote-section) "[ \t]*$") + nil t)) + (goto-char (match-end 0)) + (forward-line) + (unless (bolp) (insert "\n"))) + (t (org-footnote--clear-footnote-section))) + (when (zerop (org-back-over-empty-lines)) (insert "\n")) + (insert "[fn:" label "] \n") + (line-beginning-position 0)))) (defun org-footnote-delete-references (label) "Delete every reference to footnote LABEL. @@ -789,7 +701,7 @@ Return the number of footnotes removed." (while (setq ref (org-footnote-get-next-reference label)) (goto-char (nth 1 ref)) (delete-region (nth 1 ref) (nth 2 ref)) - (incf nref)) + (cl-incf nref)) nref))) (defun org-footnote-delete-definitions (label) @@ -797,17 +709,21 @@ Return the number of footnotes removed." Return the number of footnotes removed." (save-excursion (goto-char (point-min)) - (let ((def-re (concat "^\\[" (regexp-quote label) "\\]")) + (let ((def-re (format "^\\[fn:%s\\]" (regexp-quote label))) (ndef 0)) (while (re-search-forward def-re nil t) - (let ((full-def (org-footnote-at-definition-p))) - (when full-def - ;; Remove the footnote, and all blank lines before it. - (goto-char (nth 1 full-def)) - (skip-chars-backward " \r\t\n") - (unless (bolp) (forward-line)) - (delete-region (point) (nth 2 full-def)) - (incf ndef)))) + (pcase (org-footnote-at-definition-p) + (`(,_ ,start ,end ,_) + ;; Remove the footnote, and all blank lines before it. + (delete-region (progn + (goto-char start) + (skip-chars-backward " \r\t\n") + (if (bobp) (point) (line-beginning-position 2))) + (progn + (goto-char end) + (skip-chars-backward " \r\t\n") + (if (bobp) (point) (line-beginning-position 2)))) + (cl-incf ndef)))) ndef))) (defun org-footnote-delete (&optional label) @@ -843,24 +759,165 @@ If LABEL is non-nil, delete that footnote instead." (message "%d definition(s) of and %d reference(s) of footnote %s removed" ndef nref label)))) + +;;;; Sorting, Renumbering, Normalizing + (defun org-footnote-renumber-fn:N () - "Renumber the simple footnotes like fn:17 into a sequence in the document." + "Order numbered footnotes into a sequence in the document." (interactive) - (let (map (n 0)) - (org-with-wide-buffer - (goto-char (point-min)) - (while (re-search-forward "\\[fn:\\([0-9]+\\)[]:]" nil t) - (save-excursion - (goto-char (match-beginning 0)) - ;; Ensure match is a footnote reference or definition. - (when (save-match-data (if (bolp) - (org-footnote-at-definition-p) - (org-footnote-at-reference-p))) - (let ((new-val (or (cdr (assoc (match-string 1) map)) - (number-to-string (incf n))))) - (unless (assoc (match-string 1) map) - (push (cons (match-string 1) new-val) map)) - (replace-match new-val nil nil nil 1)))))))) + (let ((references (org-footnote--collect-references))) + (unwind-protect + (let* ((c 0) + (references (cl-remove-if-not + (lambda (r) (string-match-p "\\`[0-9]+\\'" (car r))) + references)) + (alist (mapcar (lambda (l) (cons l (number-to-string (cl-incf c)))) + (delete-dups (mapcar #'car references))))) + (org-with-wide-buffer + ;; Re-number references. + (dolist (ref references) + (goto-char (nth 1 ref)) + (org-footnote--set-label (cdr (assoc (nth 0 ref) alist)))) + ;; Re-number definitions. + (goto-char (point-min)) + (while (re-search-forward "^\\[fn:\\([0-9]+\\)\\]" nil t) + (replace-match (or (cdr (assoc (match-string 1) alist)) + ;; Un-referenced definitions get + ;; higher numbers. + (number-to-string (cl-incf c))) + nil nil nil 1)))) + (dolist (r references) (set-marker (nth 1 r) nil))))) + +(defun org-footnote-sort () + "Rearrange footnote definitions in the current buffer. +Sort footnote definitions so they match order of footnote +references. Also relocate definitions at the end of their +relative section or within a single footnote section, according +to `org-footnote-section'. Inline definitions are ignored." + (let ((references (org-footnote--collect-references))) + (unwind-protect + (let ((definitions (org-footnote--collect-definitions 'delete))) + (org-with-wide-buffer + (org-footnote--clear-footnote-section) + ;; Insert footnote definitions at the appropriate location, + ;; separated by a blank line. Each definition is inserted + ;; only once throughout the buffer. + (let (inserted) + (dolist (cell references) + (let ((label (car cell)) + (nested (not (nth 2 cell))) + (inline (nth 3 cell))) + (unless (or (member label inserted) inline) + (push label inserted) + (unless (or org-footnote-section nested) + ;; If `org-footnote-section' is non-nil, or + ;; reference is nested, point is already at the + ;; correct position. Otherwise, move at the + ;; appropriate location within the section + ;; containing the reference. + (goto-char (nth 1 cell)) + (org-footnote--goto-local-insertion-point)) + (insert "\n" + (or (cdr (assoc label definitions)) + (format "[fn:%s] DEFINITION NOT FOUND." label)) + "\n")))) + ;; Insert un-referenced footnote definitions at the end. + (let ((unreferenced + (cl-remove-if (lambda (d) (member (car d) inserted)) + definitions))) + (dolist (d unreferenced) (insert "\n" (cdr d) "\n")))))) + ;; Clear dangling markers in the buffer. + (dolist (r references) (set-marker (nth 1 r) nil))))) + +(defun org-footnote-normalize () + "Turn every footnote in buffer into a numbered one." + (interactive) + (let ((references (org-footnote--collect-references 'anonymous))) + (unwind-protect + (let ((n 0) + (translations nil) + (definitions nil)) + (org-with-wide-buffer + ;; Update label for reference. We need to do this before + ;; clearing definitions in order to rename nested footnotes + ;; before they are deleted. + (dolist (cell references) + (let* ((label (car cell)) + (anonymous (not label)) + (new + (cond + ;; In order to differentiate anonymous + ;; references from regular ones, set their + ;; labels to integers, not strings. + (anonymous (setcar cell (cl-incf n))) + ((cdr (assoc label translations))) + (t (let ((l (number-to-string (cl-incf n)))) + (push (cons label l) translations) + l))))) + (goto-char (nth 1 cell)) ; Move to reference's start. + (org-footnote--set-label + (if anonymous (number-to-string new) new)) + (let ((size (nth 3 cell))) + ;; Transform inline footnotes into regular references + ;; and retain their definition for later insertion as + ;; a regular footnote definition. + (when size + (let ((def (concat + (format "[fn:%s] " new) + (org-trim + (substring + (delete-and-extract-region + (point) (+ (point) size 1)) + 1))))) + (push (cons (if anonymous new label) def) definitions) + (when org-footnote-fill-after-inline-note-extraction + (org-fill-paragraph))))))) + ;; Collect definitions. Update labels according to ALIST. + (let ((definitions + (nconc definitions + (org-footnote--collect-definitions 'delete))) + (inserted)) + (org-footnote--clear-footnote-section) + (dolist (cell references) + (let* ((label (car cell)) + (anonymous (integerp label)) + (pos (nth 1 cell))) + ;; Move to appropriate location, if required. When + ;; there is a footnote section or reference is + ;; nested, point is already at the expected location. + (unless (or org-footnote-section (not (nth 2 cell))) + (goto-char pos) + (org-footnote--goto-local-insertion-point)) + ;; Insert new definition once label is updated. + (unless (member label inserted) + (push label inserted) + (let ((stored (cdr (assoc label definitions))) + ;; Anonymous footnotes' label is already + ;; up-to-date. + (new (if anonymous label + (cdr (assoc label translations))))) + (insert "\n" + (cond + ((not stored) + (format "[fn:%s] DEFINITION NOT FOUND." new)) + (anonymous stored) + (t + (replace-regexp-in-string + "\\`\\[fn:\\(.*?\\)\\]" new stored nil nil 1))) + "\n"))))) + ;; Insert un-referenced footnote definitions at the end. + (let ((unreferenced + (cl-remove-if (lambda (d) (member (car d) inserted)) + definitions))) + (dolist (d unreferenced) + (insert "\n" + (replace-regexp-in-string + org-footnote-definition-re + (format "[fn:%d]" (cl-incf n)) + (cdr d)) + "\n")))))) + ;; Clear dangling markers. + (dolist (r references) (set-marker (nth 1 r) nil))))) (defun org-footnote-auto-adjust-maybe () "Renumber and/or sort footnotes according to user settings." @@ -868,14 +925,77 @@ If LABEL is non-nil, delete that footnote instead." (org-footnote-renumber-fn:N)) (when (memq org-footnote-auto-adjust '(t sort)) (let ((label (car (org-footnote-at-definition-p)))) - (org-footnote-normalize 'sort) + (org-footnote-sort) (when label (goto-char (point-min)) - (and (re-search-forward (concat "^\\[" (regexp-quote label) "\\]") + (and (re-search-forward (format "^\\[fn:%s\\]" (regexp-quote label)) nil t) (progn (insert " ") (just-one-space))))))) + +;;;; End-user interface + +;;;###autoload +(defun org-footnote-action (&optional special) + "Do the right thing for footnotes. + +When at a footnote reference, jump to the definition. + +When at a definition, jump to the references if they exist, offer +to create them otherwise. + +When neither at definition or reference, create a new footnote, +interactively if possible. + +With prefix arg SPECIAL, or when no footnote can be created, +offer additional commands in a menu." + (interactive "P") + (let* ((context (and (not special) (org-element-context))) + (type (org-element-type context))) + (cond + ;; On white space after element, insert a new footnote. + ((and context + (> (point) + (save-excursion + (goto-char (org-element-property :end context)) + (skip-chars-backward " \t") + (point)))) + (org-footnote-new)) + ((eq type 'footnote-reference) + (let ((label (org-element-property :label context))) + (cond + ;; Anonymous footnote: move point at the beginning of its + ;; definition. + ((not label) + (goto-char (org-element-property :contents-begin context))) + ;; Check if a definition exists: then move to it. + ((let ((p (nth 1 (org-footnote-get-definition label)))) + (when p (org-footnote-goto-definition label p)))) + ;; No definition exists: offer to create it. + ((yes-or-no-p (format "No definition for %s. Create one? " label)) + (let ((p (org-footnote-create-definition label))) + (or (ignore-errors (org-footnote-goto-definition label p)) + ;; Since definition was created outside current scope, + ;; edit it remotely. + (org-edit-footnote-reference))))))) + ((eq type 'footnote-definition) + (org-footnote-goto-previous-reference + (org-element-property :label context))) + ((or special (not (org-footnote--allow-reference-p))) + (message "Footnotes: [s]ort | [r]enumber fn:N | [S]=r+s | [n]ormalize | \ +\[d]elete") + (pcase (read-char-exclusive) + (?s (org-footnote-sort)) + (?r (org-footnote-renumber-fn:N)) + (?S (org-footnote-renumber-fn:N) + (org-footnote-sort)) + (?n (org-footnote-normalize)) + (?d (org-footnote-delete)) + (char (error "No such footnote command %c" char)))) + (t (org-footnote-new))))) + + (provide 'org-footnote) ;; Local variables: diff --git a/lisp/org/org-gnus.el b/lisp/org/org-gnus.el index 1d287a740b5..26bb8899d3b 100644 --- a/lisp/org/org-gnus.el +++ b/lisp/org/org-gnus.el @@ -1,4 +1,4 @@ -;;; org-gnus.el --- Support for links to Gnus groups and messages from within Org-mode +;;; org-gnus.el --- Support for Links to Gnus Groups and Messages -*- lexical-binding: t; -*- ;; Copyright (C) 2004-2017 Free Software Foundation, Inc. @@ -20,50 +20,53 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Commentary: -;; This file implements links to Gnus groups and messages from within Org-mode. -;; Org-mode loads this module by default - if this is not what you want, +;; This file implements links to Gnus groups and messages from within Org. +;; Org mode loads this module by default - if this is not what you want, ;; configure the variable `org-modules'. ;;; Code: -(require 'org) +(require 'gnus-sum) (require 'gnus-util) -(eval-when-compile (require 'gnus-sum)) +(require 'nnheader) +(require 'nnir) +(require 'org) + + +;;; Declare external functions and variables -;; Declare external functions and variables +(declare-function gnus-activate-group "gnus-start" (group &optional scan dont-check method dont-sub-check)) +(declare-function gnus-find-method-for-group "gnus" (group &optional info)) +(declare-function gnus-group-group-name "gnus-group") +(declare-function gnus-group-jump-to-group "gnus-group" (group &optional prompt)) +(declare-function gnus-group-read-group "gnus-group" (&optional all no-article group select-articles)) (declare-function message-fetch-field "message" (header &optional not-all)) -(declare-function message-narrow-to-head-1 "message" nil) -;; The following line suppresses a compiler warning stemming from gnus-sum.el -(declare-function gnus-summary-last-subject "gnus-sum" nil) -;; Customization variables +(declare-function message-generate-headers "message" (headers)) +(declare-function message-narrow-to-headers "message") +(declare-function message-tokenize-header "message" (header &optional separator)) +(declare-function message-unquote-tokens "message" (elems)) +(declare-function nnvirtual-map-article "nnvirtual" (article)) -(org-defvaralias 'org-usenet-links-prefer-google 'org-gnus-prefer-web-links) +(defvar gnus-newsgroup-name) +(defvar gnus-summary-buffer) +(defvar gnus-other-frame-object) + + +;;; Customization variables (defcustom org-gnus-prefer-web-links nil "If non-nil, `org-store-link' creates web links to Google groups or Gmane. -When nil, Gnus will be used for such links. -Using a prefix arg to the command \\[org-store-link] (`org-store-link') +\\<org-mode-map>When nil, Gnus will be used for such links. +Using a prefix argument to the command `\\[org-store-link]' (`org-store-link') negates this setting for the duration of the command." :group 'org-link-store :type 'boolean) -(defcustom org-gnus-nnimap-query-article-no-from-file nil - "If non-nil, `org-gnus-follow-link' will try to translate -Message-Ids to article numbers by querying the .overview file. -Normally, this translation is done by querying the IMAP server, -which is usually very fast. Unfortunately, some (maybe badly -configured) IMAP servers don't support this operation quickly. -So if following a link to a Gnus article takes ages, try setting -this variable to t." - :group 'org-link-store - :version "24.1" - :type 'boolean) - (defcustom org-gnus-no-server nil "Should Gnus be started using `gnus-no-server'?" :group 'org-gnus @@ -71,29 +74,14 @@ this variable to t." :package-version '(Org . "8.0") :type 'boolean) -;; Install the link type -(org-add-link-type "gnus" 'org-gnus-open) -(add-hook 'org-store-link-functions 'org-gnus-store-link) - -;; Implementation + +;;; Install the link type -;; FIXME: nnimap-group-overview-filename was removed from Gnus in -;; September 2010. Perhaps remove this function? -(defun org-gnus-nnimap-cached-article-number (group server message-id) - "Return cached article number (uid) of message in GROUP on SERVER. -MESSAGE-ID is the message-id header field that identifies the -message. If the uid is not cached, return nil." - (with-temp-buffer - (let ((nov (nnimap-group-overview-filename group server))) - (when (file-exists-p nov) - (mm-insert-file-contents nov) - (set-buffer-modified-p nil) - (goto-char (point-min)) - (catch 'found - (while (search-forward message-id nil t) - (let ((hdr (split-string (thing-at-point 'line) "\t"))) - (if (string= (nth 4 hdr) message-id) - (throw 'found (nth 0 hdr)))))))))) +(org-link-set-parameters "gnus" + :follow #'org-gnus-open + :store #'org-gnus-store-link) + +;;; Implementation (defun org-gnus-group-link (group) "Create a link to the Gnus group GROUP. @@ -104,7 +92,7 @@ Otherwise create a link to the group inside Gnus. If `org-store-link' was called with a prefix arg the meaning of `org-gnus-prefer-web-links' is reversed." (let ((unprefixed-group (replace-regexp-in-string "^[^:]+:" "" group))) - (if (and (string-match "^nntp" group) ;; Only for nntp groups + (if (and (string-prefix-p "nntp" group) ;; Only for nntp groups (org-xor current-prefix-arg org-gnus-prefer-web-links)) (concat (if (string-match "gmane" unprefixed-group) @@ -136,91 +124,77 @@ If `org-store-link' was called with a prefix arg the meaning of (defun org-gnus-store-link () "Store a link to a Gnus folder or message." - (cond - ((eq major-mode 'gnus-group-mode) - (let* ((group (cond ((fboundp 'gnus-group-group-name) ; depending on Gnus - (gnus-group-group-name)) ; version - ((fboundp 'gnus-group-name) - (gnus-group-name)) - (t "???"))) - desc link) - (when group - (org-store-link-props :type "gnus" :group group) - (setq desc (org-gnus-group-link group) - link desc) - (org-add-link-props :link link :description desc) - link))) - - ((memq major-mode '(gnus-summary-mode gnus-article-mode)) - (let* ((group gnus-newsgroup-name) - (header (with-current-buffer gnus-summary-buffer - (gnus-summary-article-header))) - (from (mail-header-from header)) - (message-id (org-remove-angle-brackets (mail-header-id header))) - (date (org-trim (mail-header-date header))) - (date-ts (and date - (ignore-errors - (format-time-string - (org-time-stamp-format t) - (date-to-time date))))) - (date-ts-ia (and date - (ignore-errors - (format-time-string - (org-time-stamp-format t t) - (date-to-time date))))) - (subject (copy-sequence (mail-header-subject header))) - (to (cdr (assq 'To (mail-header-extra header)))) - newsgroups x-no-archive desc link) - ;; Remove text properties of subject string to avoid Emacs bug - ;; #3506 - (set-text-properties 0 (length subject) nil subject) - - ;; Fetching an article is an expensive operation; newsgroup and - ;; x-no-archive are only needed for web links. - (when (org-xor current-prefix-arg org-gnus-prefer-web-links) - ;; Make sure the original article buffer is up-to-date - (save-window-excursion (gnus-summary-select-article)) - (setq to (or to (gnus-fetch-original-field "To")) - newsgroups (gnus-fetch-original-field "Newsgroups") - x-no-archive (gnus-fetch-original-field "x-no-archive"))) - (org-store-link-props :type "gnus" :from from :subject subject - :message-id message-id :group group :to to) - (when date - (org-add-link-props :date date :date-timestamp date-ts - :date-timestamp-inactive date-ts-ia)) - (setq desc (org-email-link-description) - link (org-gnus-article-link - group newsgroups message-id x-no-archive)) - (org-add-link-props :link link :description desc) - link)) - ((eq major-mode 'message-mode) - (setq org-store-link-plist nil) ; reset - (save-excursion - (save-restriction - (message-narrow-to-headers) - (and (not (message-fetch-field "Message-ID")) - (message-generate-headers '(Message-ID))) - (goto-char (point-min)) - (re-search-forward "^Message-ID: *.*$" nil t) - (put-text-property (match-beginning 0) (match-end 0) 'message-deletable nil) - (let ((gcc (car (last - (message-unquote-tokens - (message-tokenize-header (mail-fetch-field "gcc" nil t) " ,"))))) - (id (org-remove-angle-brackets (mail-fetch-field "Message-ID"))) - (to (mail-fetch-field "To")) - (from (mail-fetch-field "From")) - (subject (mail-fetch-field "Subject")) - desc link - newsgroup xarchive) ; those are always nil for gcc - (and (not gcc) - (error "Can not create link: No Gcc header found")) - (org-store-link-props :type "gnus" :from from :subject subject - :message-id id :group gcc :to to) - (setq desc (org-email-link-description) - link (org-gnus-article-link - gcc newsgroup id xarchive)) - (org-add-link-props :link link :description desc) - link)))))) + (pcase major-mode + (`gnus-group-mode + (let ((group (gnus-group-group-name))) + (when group + (org-store-link-props :type "gnus" :group group) + (let ((description (org-gnus-group-link group))) + (org-add-link-props :link description :description description) + description)))) + ((or `gnus-summary-mode `gnus-article-mode) + (let* ((group + (pcase (gnus-find-method-for-group gnus-newsgroup-name) + (`(nnvirtual . ,_) + (save-excursion + (car (nnvirtual-map-article (gnus-summary-article-number))))) + (`(nnir . ,_) + (save-excursion + (nnir-article-group (gnus-summary-article-number)))) + (_ gnus-newsgroup-name))) + (header (with-current-buffer gnus-summary-buffer + (gnus-summary-article-header))) + (from (mail-header-from header)) + (message-id (org-unbracket-string "<" ">" (mail-header-id header))) + (date (org-trim (mail-header-date header))) + ;; Remove text properties of subject string to avoid Emacs + ;; bug #3506. + (subject (org-no-properties + (copy-sequence (mail-header-subject header)))) + (to (cdr (assq 'To (mail-header-extra header)))) + newsgroups x-no-archive) + ;; Fetching an article is an expensive operation; newsgroup and + ;; x-no-archive are only needed for web links. + (when (org-xor current-prefix-arg org-gnus-prefer-web-links) + ;; Make sure the original article buffer is up-to-date. + (save-window-excursion (gnus-summary-select-article)) + (setq to (or to (gnus-fetch-original-field "To"))) + (setq newsgroups (gnus-fetch-original-field "Newsgroups")) + (setq x-no-archive (gnus-fetch-original-field "x-no-archive"))) + (org-store-link-props :type "gnus" :from from :date date :subject subject + :message-id message-id :group group :to to) + (let ((link (org-gnus-article-link + group newsgroups message-id x-no-archive)) + (description (org-email-link-description))) + (org-add-link-props :link link :description description) + link))) + (`message-mode + (setq org-store-link-plist nil) ;reset + (save-excursion + (save-restriction + (message-narrow-to-headers) + (unless (message-fetch-field "Message-ID") + (message-generate-headers '(Message-ID))) + (goto-char (point-min)) + (re-search-forward "^Message-ID:" nil t) + (put-text-property (line-beginning-position) (line-end-position) + 'message-deletable nil) + (let ((gcc (org-last (message-unquote-tokens + (message-tokenize-header + (mail-fetch-field "gcc" nil t) " ,")))) + (id (org-unbracket-string "<" ">" + (mail-fetch-field "Message-ID"))) + (to (mail-fetch-field "To")) + (from (mail-fetch-field "From")) + (subject (mail-fetch-field "Subject")) + newsgroup xarchive) ;those are always nil for gcc + (unless gcc (error "Can not create link: No Gcc header found")) + (org-store-link-props :type "gnus" :from from :subject subject + :message-id id :group gcc :to to) + (let ((link (org-gnus-article-link gcc newsgroup id xarchive)) + (description (org-email-link-description))) + (org-add-link-props :link link :description description) + link))))))) (defun org-gnus-open-nntp (path) "Follow the nntp: link specified by PATH." @@ -234,66 +208,51 @@ If `org-store-link' was called with a prefix arg the meaning of (defun org-gnus-open (path) "Follow the Gnus message or folder link specified by PATH." - (let (group article) - (if (not (string-match "\\`\\([^#]+\\)\\(#\\(.*\\)\\)?" path)) - (error "Error in Gnus link")) - (setq group (match-string 1 path) - article (match-string 3 path)) - (when group - (setq group (org-no-properties group))) - (when article - (setq article (org-no-properties article))) + (unless (string-match "\\`\\([^#]+\\)\\(#\\(.*\\)\\)?" path) + (error "Error in Gnus link %S" path)) + (let ((group (match-string-no-properties 1 path)) + (article (match-string-no-properties 3 path))) (org-gnus-follow-link group article))) (defun org-gnus-follow-link (&optional group article) "Follow a Gnus link to GROUP and ARTICLE." (require 'gnus) (funcall (cdr (assq 'gnus org-link-frame-setup))) - (if gnus-other-frame-object (select-frame gnus-other-frame-object)) - (when group - (setq group (org-no-properties group))) - (when article - (setq article (org-no-properties article))) - (cond ((and group article) - (gnus-activate-group group) - (condition-case nil - (let* ((method (gnus-find-method-for-group group)) - (backend (car method)) - (server (cadr method))) - (cond - ((eq backend 'nndoc) - (if (gnus-group-read-group t nil group) + (when gnus-other-frame-object (select-frame gnus-other-frame-object)) + (let ((group (org-no-properties group)) + (article (org-no-properties article))) + (cond + ((and group article) + (gnus-activate-group group) + (condition-case nil + (let ((msg "Couldn't follow Gnus link. Summary couldn't be opened.")) + (pcase (gnus-find-method-for-group group) + (`(nndoc . ,_) + (if (gnus-group-read-group t nil group) + (gnus-summary-goto-article article nil t) + (message msg))) + (_ + (let ((articles 1) + group-opened) + (while (and (not group-opened) + ;; Stop on integer overflows. + (> articles 0)) + (setq group-opened (gnus-group-read-group articles t group)) + (setq articles (if (< articles 16) + (1+ articles) + (* articles 2)))) + (if group-opened (gnus-summary-goto-article article nil t) - (message "Couldn't follow gnus link. %s" - "The summary couldn't be opened."))) - (t - (let ((articles 1) - group-opened) - (when (and (eq backend 'nnimap) - org-gnus-nnimap-query-article-no-from-file) - (setq article - (or (org-gnus-nnimap-cached-article-number - (nth 1 (split-string group ":")) - server (concat "<" article ">")) article))) - (while (and (not group-opened) - ;; stop on integer overflows - (> articles 0)) - (setq group-opened (gnus-group-read-group - articles t group) - articles (if (< articles 16) - (1+ articles) - (* articles 2)))) - (if group-opened - (gnus-summary-goto-article article nil t) - (message "Couldn't follow gnus link. %s" - "The summary couldn't be opened.")))))) - (quit (message "Couldn't follow gnus link. %s" - "The linked group is empty.")))) - (group (gnus-group-jump-to-group group)))) + (message msg)))))) + (quit + (message "Couldn't follow Gnus link. The linked group is empty.")))) + (group (gnus-group-jump-to-group group))))) (defun org-gnus-no-new-news () "Like `\\[gnus]' but doesn't check for new news." - (if (not (gnus-alive-p)) (if org-gnus-no-server (gnus-no-server) (gnus)))) + (cond ((gnus-alive-p) nil) + (org-gnus-no-server (gnus-no-server)) + (t (gnus)))) (provide 'org-gnus) diff --git a/lisp/org/org-habit.el b/lisp/org/org-habit.el index bbbf845d148..89b75e6f680 100644 --- a/lisp/org/org-habit.el +++ b/lisp/org/org-habit.el @@ -1,4 +1,4 @@ -;;; org-habit.el --- The habit tracking code for Org-mode +;;; org-habit.el --- The habit tracking code for Org -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. @@ -19,23 +19,21 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Commentary: -;; This file contains the habit tracking code for Org-mode +;; This file contains the habit tracking code for Org mode ;;; Code: +(require 'cl-lib) (require 'org) (require 'org-agenda) -(eval-when-compile - (require 'cl)) - (defgroup org-habit nil - "Options concerning habit tracking in Org-mode." + "Options concerning habit tracking in Org mode." :tag "Org Habit" :group 'org-progress) @@ -165,16 +163,17 @@ Returns a list with the following elements: 2: Optional deadline (nil if not present) 3: If deadline, the repeater for the deadline, otherwise nil 4: A list of all the past dates this todo was mark closed + 5: Repeater type as a string This list represents a \"habit\" for the rest of this module." (save-excursion (if pom (goto-char pom)) - (assert (org-is-habit-p (point))) + (cl-assert (org-is-habit-p (point))) (let* ((scheduled (org-get-scheduled-time (point))) - (scheduled-repeat (org-get-repeat org-scheduled-string)) + (scheduled-repeat (org-get-repeat (org-entry-get (point) "SCHEDULED"))) (end (org-entry-end-position)) (habit-entry (org-no-properties (nth 4 (org-heading-components)))) - closed-dates deadline dr-days sr-days) + closed-dates deadline dr-days sr-days sr-type) (if scheduled (setq scheduled (time-to-days scheduled)) (error "Habit %s has no scheduled date" habit-entry)) @@ -182,7 +181,9 @@ This list represents a \"habit\" for the rest of this module." (error "Habit `%s' has no scheduled repeat period or has an incorrect one" habit-entry)) - (setq sr-days (org-habit-duration-to-days scheduled-repeat)) + (setq sr-days (org-habit-duration-to-days scheduled-repeat) + sr-type (progn (string-match "[\\.+]?\\+" scheduled-repeat) + (match-string-no-properties 0 scheduled-repeat))) (unless (> sr-days 0) (error "Habit %s scheduled repeat period is less than 1d" habit-entry)) (when (string-match "/\\([0-9]+[dwmy]\\)" scheduled-repeat) @@ -197,17 +198,33 @@ This list represents a \"habit\" for the rest of this module." (reversed org-log-states-order-reversed) (search (if reversed 're-search-forward 're-search-backward)) (limit (if reversed end (point))) - (count 0)) + (count 0) + (re (format + "^[ \t]*-[ \t]+\\(?:State \"%s\".*%s%s\\)" + (regexp-opt org-done-keywords) + org-ts-regexp-inactive + (let ((value (cdr (assq 'done org-log-note-headings)))) + (if (not value) "" + (concat "\\|" + (org-replace-escapes + (regexp-quote value) + `(("%d" . ,org-ts-regexp-inactive) + ("%D" . ,org-ts-regexp) + ("%s" . "\"\\S-+\"") + ("%S" . "\"\\S-+\"") + ("%t" . ,org-ts-regexp-inactive) + ("%T" . ,org-ts-regexp) + ("%u" . ".*?") + ("%U" . ".*?"))))))))) (unless reversed (goto-char end)) - (while (and (< count maxdays) - (funcall search (format "- State \"%s\".*\\[\\([^]]+\\)\\]" - (regexp-opt org-done-keywords)) - limit t)) + (while (and (< count maxdays) (funcall search re limit t)) (push (time-to-days - (org-time-string-to-time (match-string-no-properties 1))) + (org-time-string-to-time + (or (match-string-no-properties 1) + (match-string-no-properties 2)))) closed-dates) (setq count (1+ count)))) - (list scheduled sr-days deadline dr-days closed-dates)))) + (list scheduled sr-days deadline dr-days closed-dates sr-type)))) (defsubst org-habit-scheduled (habit) (nth 0 habit)) @@ -225,6 +242,8 @@ This list represents a \"habit\" for the rest of this module." (org-habit-scheduled-repeat habit))) (defsubst org-habit-done-dates (habit) (nth 4 habit)) +(defsubst org-habit-repeat-type (habit) + (nth 5 habit)) (defsubst org-habit-get-priority (habit &optional moment) "Determine the relative priority of a habit. @@ -265,7 +284,6 @@ Habits are assigned colors on the following basis: schedule's repeat period." (let* ((scheduled (or scheduled-days (org-habit-scheduled habit))) (s-repeat (org-habit-scheduled-repeat habit)) - (scheduled-end (+ scheduled (1- s-repeat))) (d-repeat (org-habit-deadline-repeat habit)) (deadline (if scheduled-days (+ scheduled-days (- d-repeat s-repeat)) @@ -289,13 +307,14 @@ Habits are assigned colors on the following basis: CURRENT gives the current time between STARTING and ENDING, for the purpose of drawing the graph. It need not be the actual current time." - (let* ((done-dates (sort (org-habit-done-dates habit) '<)) + (let* ((all-done-dates (sort (org-habit-done-dates habit) #'<)) + (done-dates all-done-dates) (scheduled (org-habit-scheduled habit)) (s-repeat (org-habit-scheduled-repeat habit)) (start (time-to-days starting)) (now (time-to-days current)) (end (time-to-days ending)) - (graph (make-string (1+ (- end start)) ?\ )) + (graph (make-string (1+ (- end start)) ?\s)) (index 0) last-done-date) (while (and done-dates (< (car done-dates) start)) @@ -304,18 +323,55 @@ current time." (while (< start end) (let* ((in-the-past-p (< start now)) (todayp (= start now)) - (donep (and done-dates - (= start (car done-dates)))) - (faces (if (and in-the-past-p - (not last-done-date) - (not (< scheduled now))) - '(org-habit-clear-face . org-habit-clear-future-face) - (org-habit-get-faces - habit start (and in-the-past-p - (if last-done-date - (+ last-done-date s-repeat) - scheduled)) - donep))) + (donep (and done-dates (= start (car done-dates)))) + (faces + (if (and in-the-past-p + (not last-done-date) + (not (< scheduled now))) + '(org-habit-clear-face . org-habit-clear-future-face) + (org-habit-get-faces + habit start + (and in-the-past-p + last-done-date + ;; Compute scheduled time for habit at the time + ;; START was current. + (let ((type (org-habit-repeat-type habit))) + (cond + ;; At the last done date, use current + ;; scheduling in all cases. + ((null done-dates) scheduled) + ((equal type ".+") (+ last-done-date s-repeat)) + ((equal type "+") + ;; Since LAST-DONE-DATE, each done mark + ;; shifted scheduled date by S-REPEAT. + (- scheduled (* (length done-dates) s-repeat))) + (t + ;; Compute the scheduled time after the + ;; first repeat. This is the closest time + ;; past FIRST-DONE which can reach SCHEDULED + ;; by a number of S-REPEAT hops. + ;; + ;; Then, play TODO state change history from + ;; the beginning in order to find current + ;; scheduled time. + (let* ((first-done (car all-done-dates)) + (s (let ((shift (mod (- scheduled first-done) + s-repeat))) + (+ (if (= shift 0) s-repeat shift) + first-done)))) + (if (= first-done last-done-date) s + (catch :exit + (dolist (done (cdr all-done-dates) s) + ;; Each repeat shifts S by any + ;; number of S-REPEAT hops it takes + ;; to get past DONE, with a minimum + ;; of one hop. + (cl-incf s (* (1+ (/ (max (- done s) 0) + s-repeat)) + s-repeat)) + (when (= done last-done-date) + (throw :exit s)))))))))) + donep))) markedp face) (if donep (let ((done-time (time-add @@ -348,7 +404,7 @@ current time." (defun org-habit-insert-consistency-graphs (&optional line) "Insert consistency graph for any habitual tasks." - (let ((inhibit-read-only t) l c + (let ((inhibit-read-only t) (buffer-invisibility-spec '(org-link)) (moment (time-subtract (current-time) (list 0 (* 3600 org-extend-today-until) 0)))) diff --git a/lisp/org/org-id.el b/lisp/org/org-id.el index 54fc733578d..09b873c49d4 100644 --- a/lisp/org/org-id.el +++ b/lisp/org/org-id.el @@ -1,4 +1,4 @@ -;;; org-id.el --- Global identifiers for Org-mode entries +;;; org-id.el --- Global identifiers for Org entries -*- lexical-binding: t; -*- ;; ;; Copyright (C) 2008-2017 Free Software Foundation, Inc. ;; @@ -19,12 +19,12 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Commentary: -;; This file implements globally unique identifiers for Org-mode entries. +;; This file implements globally unique identifiers for Org entries. ;; Identifiers are stored in the entry as an :ID: property. Functions ;; are provided that create and retrieve such identifiers, and that find ;; entries based on the identifier. @@ -73,20 +73,17 @@ (require 'org) (declare-function message-make-fqdn "message" ()) -(declare-function org-pop-to-buffer-same-window - "org-compat" (&optional buffer-or-name norecord label)) ;;; Customization (defgroup org-id nil - "Options concerning global entry identifiers in Org-mode." + "Options concerning global entry identifiers in Org mode." :tag "Org ID" :group 'org) -(define-obsolete-variable-alias - 'org-link-to-org-use-id 'org-id-link-to-org-use-id "24.3") (defcustom org-id-link-to-org-use-id nil "Non-nil means storing a link to an Org file will use entry IDs. +\\<org-mode-map>\ The variable can have the following values: @@ -101,7 +98,7 @@ create-if-interactive call `org-capture' that automatically and preemptively creates a link. If you do want to get an ID link in a capture template to an entry not having an ID, create it first by explicitly creating - a link to it, using `C-c C-l' first. + a link to it, using `\\[org-store-link]' first. create-if-interactive-and-no-custom-id Like create-if-interactive, but do not create an ID if there is @@ -203,7 +200,7 @@ This variable is only relevant when `org-id-track-globally' is set." When Org reparses files to remake the list of files and IDs it is tracking, it will normally scan the agenda files, the archives related to agenda files, any files that are listed as ID containing in the current register, and -any Org-mode files currently visited by Emacs. +any Org file currently visited by Emacs. You can list additional files here. This variable is only relevant when `org-id-track-globally' is set." :group 'org-id @@ -277,7 +274,7 @@ If necessary, the ID is created." (move-marker pom nil)))) ;;;###autoload -(defun org-id-get-with-outline-drilling (&optional targets) +(defun org-id-get-with-outline-drilling () "Use an outline-cycling interface to retrieve the ID of an entry. This only finds entries in the current buffer, using `org-get-location'. It returns the ID of the entry. If necessary, the ID is created." @@ -294,7 +291,7 @@ Move the cursor to that entry in that buffer." (let ((m (org-id-find id 'marker))) (unless m (error "Cannot find entry with ID \"%s\"" id)) - (org-pop-to-buffer-same-window (marker-buffer m)) + (pop-to-buffer-same-window (marker-buffer m)) (goto-char m) (move-marker m nil) (org-show-context))) @@ -447,8 +444,7 @@ and time is the usual three-integer representation of time." Store the relation between files and corresponding IDs. This will scan all agenda files, all associated archives, and all files currently mentioned in `org-id-locations'. -When FILES is given, scan these files instead. -When CHECK is given, prepare detailed information about duplicate IDs." +When FILES is given, scan these files instead." (interactive) (if (not org-id-track-globally) (error "Please turn on `org-id-track-globally' if you want to track IDs") @@ -466,7 +462,7 @@ When CHECK is given, prepare detailed information about duplicate IDs." (if (symbolp org-id-extra-files) (symbol-value org-id-extra-files) org-id-extra-files) - ;; Files associated with live org-mode buffers + ;; Files associated with live Org buffers (delq nil (mapcar (lambda (b) (with-current-buffer b @@ -494,7 +490,7 @@ When CHECK is given, prepare detailed information about duplicate IDs." (goto-char (point-min)) (while (re-search-forward "^[ \t]*:ID:[ \t]+\\(\\S-+\\)[ \t]*$" nil t) - (setq id (org-match-string-no-properties 1)) + (setq id (match-string-no-properties 1)) (if (member id found) (progn (message "Duplicate ID \"%s\", also in file %s" @@ -543,8 +539,7 @@ When CHECK is given, prepare detailed information about duplicate IDs." (with-temp-buffer (condition-case nil (progn - (insert-file-contents-literally org-id-locations-file) - (goto-char (point-min)) + (insert-file-contents org-id-locations-file) (setq org-id-locations (read (current-buffer)))) (error (message "Could not read org-id-values from %s. Setting it to nil." @@ -678,7 +673,7 @@ optional argument MARKERP, return the position as a new marker." (move-marker m nil) (org-show-context))) -(org-add-link-type "id" 'org-id-open) +(org-link-set-parameters "id" :follow #'org-id-open) (provide 'org-id) diff --git a/lisp/org/org-indent.el b/lisp/org/org-indent.el index baaff2ff7c8..b34586e09ec 100644 --- a/lisp/org/org-indent.el +++ b/lisp/org/org-indent.el @@ -1,4 +1,5 @@ -;;; org-indent.el --- Dynamic indentation for Org-mode +;;; org-indent.el --- Dynamic indentation for Org -*- lexical-binding: t; -*- + ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. ;; ;; Author: Carsten Dominik <carsten at orgmode dot org> @@ -18,7 +19,7 @@ ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; @@ -39,8 +40,7 @@ (require 'org-compat) (require 'org) -(eval-when-compile - (require 'cl)) +(require 'cl-lib) (declare-function org-inlinetask-get-task-level "org-inlinetask" ()) (declare-function org-inlinetask-in-task-p "org-inlinetask" ()) @@ -52,20 +52,6 @@ :tag "Org Indent" :group 'org) -(defconst org-indent-max 40 - "Maximum indentation in characters.") -(defconst org-indent-max-levels 20 - "Maximum added level through virtual indentation, in characters. - -It is computed by multiplying `org-indent-indentation-per-level' -minus one by actual level of the headline minus one.") - -(defvar org-indent-strings nil - "Vector with all indentation strings. -It will be set in `org-indent-initialize'.") -(defvar org-indent-stars nil - "Vector with all indentation star strings. -It will be set in `org-indent-initialize'.") (defvar org-indent-inlinetask-first-star (org-add-props "*" '(face org-warning)) "First star of inline tasks, with correct face.") (defvar org-indent-agent-timer nil @@ -82,7 +68,7 @@ Delay used when the buffer to initialize is current.") Delay used when the buffer to initialize isn't current.") (defvar org-indent-agent-resume-delay '(0 0 100000) "Minimal time for other idle processes before switching back to agent.") -(defvar org-indent-initial-marker nil +(defvar org-indent--initial-marker nil "Position of initialization before interrupt. This is used locally in each buffer being initialized.") (defvar org-hide-leading-stars-before-indent-mode nil @@ -92,15 +78,12 @@ This is used locally in each buffer being initialized.") It is modified by `org-indent-notify-modified-headline'.") -(defcustom org-indent-boundary-char ?\ ; comment to protect space char +(defcustom org-indent-boundary-char ?\s "The end of the virtual indentation strings, a single-character string. The default is just a space, but if you wish, you can use \"|\" or so. This can be useful on a terminal window - under a windowing system, -it may be prettier to customize the org-indent face." +it may be prettier to customize the `org-indent' face." :group 'org-indent - :set (lambda (var val) - (set var val) - (and org-indent-strings (org-indent-initialize))) :type 'character) (defcustom org-indent-mode-turns-off-org-adapt-indentation t @@ -121,29 +104,56 @@ turn on `org-hide-leading-stars'." :group 'org-indent :type 'integer) -(defface org-indent - (org-compatible-face nil nil) +(defface org-indent '((t (:inherit org-hide))) "Face for outline indentation. The default is to make it look like whitespace. But you may find it useful to make it ever so slightly different." :group 'org-faces) -(defun org-indent-initialize () - "Initialize the indentation strings." - (setq org-indent-strings (make-vector (1+ org-indent-max) nil)) - (setq org-indent-stars (make-vector (1+ org-indent-max) nil)) - (aset org-indent-strings 0 nil) - (aset org-indent-stars 0 nil) - (loop for i from 1 to org-indent-max do - (aset org-indent-strings i - (org-add-props - (concat (make-string (1- i) ?\ ) - (char-to-string org-indent-boundary-char)) - nil 'face 'org-indent))) - (loop for i from 1 to org-indent-max-levels do - (aset org-indent-stars i - (org-add-props (make-string i ?*) - nil 'face 'org-hide)))) +(defvar org-indent--text-line-prefixes nil + "Vector containing line prefixes strings for regular text.") + +(defvar org-indent--heading-line-prefixes nil + "Vector containing line prefix strings for headlines.") + +(defvar org-indent--inlinetask-line-prefixes nil + "Vector containing line prefix strings for inline tasks.") + +(defconst org-indent--deepest-level 50 + "Maximum theoretical headline depth.") + +(defun org-indent--compute-prefixes () + "Compute prefix strings for regular text and headlines." + (setq org-indent--heading-line-prefixes + (make-vector org-indent--deepest-level nil)) + (setq org-indent--inlinetask-line-prefixes + (make-vector org-indent--deepest-level nil)) + (setq org-indent--text-line-prefixes + (make-vector org-indent--deepest-level nil)) + (dotimes (n org-indent--deepest-level) + (let ((indentation (if (<= n 1) 0 + (* (1- org-indent-indentation-per-level) + (1- n))))) + ;; Headlines line prefixes. + (let ((heading-prefix (make-string indentation ?*))) + (aset org-indent--heading-line-prefixes + n + (org-add-props heading-prefix nil 'face 'org-indent)) + ;; Inline tasks line prefixes + (aset org-indent--inlinetask-line-prefixes + n + (cond ((<= n 1) "") + ((bound-and-true-p org-inlinetask-show-first-star) + (concat org-indent-inlinetask-first-star + (substring heading-prefix 1))) + (t (org-add-props heading-prefix nil 'face 'org-indent))))) + ;; Text line prefixes. + (aset org-indent--text-line-prefixes + n + (concat (org-add-props (make-string (+ n indentation) ?\s) + nil 'face 'org-indent) + (and (> n 0) + (char-to-string org-indent-boundary-char))))))) (defsubst org-indent-remove-properties (beg end) "Remove indentations between BEG and END." @@ -162,34 +172,25 @@ buffer, which can take a few seconds on large buffers, is done during idle time." nil " Ind" nil (cond - ((and org-indent-mode (featurep 'xemacs)) - (message "org-indent-mode does not work in XEmacs - refusing to turn it on") - (setq org-indent-mode nil)) - ((and org-indent-mode - (not (org-version-check "23.1.50" "Org Indent mode" :predicate))) - (message "org-indent-mode can crash Emacs 23.1 - refusing to turn it on!") - (ding) - (sit-for 1) - (setq org-indent-mode nil)) (org-indent-mode ;; mode was turned on. - (org-set-local 'indent-tabs-mode nil) - (or org-indent-strings (org-indent-initialize)) - (org-set-local 'org-indent-initial-marker (copy-marker 1)) + (setq-local indent-tabs-mode nil) + (setq-local org-indent--initial-marker (copy-marker 1)) (when org-indent-mode-turns-off-org-adapt-indentation - (org-set-local 'org-adapt-indentation nil)) + (setq-local org-adapt-indentation nil)) (when org-indent-mode-turns-on-hiding-stars - (org-set-local 'org-hide-leading-stars-before-indent-mode - org-hide-leading-stars) - (org-set-local 'org-hide-leading-stars t)) - (org-add-hook 'filter-buffer-substring-functions - (lambda (fun start end delete) - (org-indent-remove-properties-from-string - (funcall fun start end delete))) - nil t) - (org-add-hook 'after-change-functions 'org-indent-refresh-maybe nil 'local) - (org-add-hook 'before-change-functions - 'org-indent-notify-modified-headline nil 'local) + (setq-local org-hide-leading-stars-before-indent-mode + org-hide-leading-stars) + (setq-local org-hide-leading-stars t)) + (org-indent--compute-prefixes) + (add-hook 'filter-buffer-substring-functions + (lambda (fun start end delete) + (org-indent-remove-properties-from-string + (funcall fun start end delete))) + nil t) + (add-hook 'after-change-functions 'org-indent-refresh-maybe nil 'local) + (add-hook 'before-change-functions + 'org-indent-notify-modified-headline nil 'local) (and font-lock-mode (org-restart-font-lock)) (org-indent-remove-properties (point-min) (point-max)) ;; Submit current buffer to initialize agent. If it's the first @@ -205,11 +206,11 @@ during idle time." (kill-local-variable 'org-adapt-indentation) (setq org-indent-agentized-buffers (delq (current-buffer) org-indent-agentized-buffers)) - (when (markerp org-indent-initial-marker) - (set-marker org-indent-initial-marker nil)) + (when (markerp org-indent--initial-marker) + (set-marker org-indent--initial-marker nil)) (when (boundp 'org-hide-leading-stars-before-indent-mode) - (org-set-local 'org-hide-leading-stars - org-hide-leading-stars-before-indent-mode)) + (setq-local org-hide-leading-stars + org-hide-leading-stars-before-indent-mode)) (remove-hook 'filter-buffer-substring-functions (lambda (fun start end delete) (org-indent-remove-properties-from-string @@ -245,7 +246,7 @@ When no more buffer is being watched, the agent suppress itself." (when org-indent-agent-resume-timer (cancel-timer org-indent-agent-resume-timer)) (setq org-indent-agentized-buffers - (org-remove-if-not #'buffer-live-p org-indent-agentized-buffers)) + (cl-remove-if-not #'buffer-live-p org-indent-agentized-buffers)) (cond ;; Job done: kill agent. ((not org-indent-agentized-buffers) (cancel-timer org-indent-agent-timer)) @@ -269,46 +270,44 @@ a time value." (let ((interruptp ;; Always nil unless interrupted. (catch 'interrupt - (and org-indent-initial-marker - (marker-position org-indent-initial-marker) - (org-indent-add-properties org-indent-initial-marker + (and org-indent--initial-marker + (marker-position org-indent--initial-marker) + (equal (marker-buffer org-indent--initial-marker) + buffer) + (org-indent-add-properties org-indent--initial-marker (point-max) delay) nil)))) - (move-marker org-indent-initial-marker interruptp) + (move-marker org-indent--initial-marker interruptp) ;; Job is complete: un-agentize buffer. (unless interruptp (setq org-indent-agentized-buffers (delq buffer org-indent-agentized-buffers)))))))) -(defsubst org-indent-set-line-properties (l w h) +(defun org-indent-set-line-properties (level indentation &optional heading) "Set prefix properties on current line an move to next one. -Prefix properties `line-prefix' and `wrap-prefix' in current line -are set to, respectively, length L and W. - -If H is non-nil, `line-prefix' will be starred. If H is -`inline', the first star will have `org-warning' face. - -Assume point is at beginning of line." - (let ((line (cond - ((eq 'inline h) - (let ((stars (aref org-indent-stars - (min l org-indent-max-levels)))) - (and stars - (if (org-bound-and-true-p org-inlinetask-show-first-star) - (concat org-indent-inlinetask-first-star - (substring stars 1)) - stars)))) - (h (aref org-indent-stars - (min l org-indent-max-levels))) - (t (aref org-indent-strings - (min l org-indent-max))))) - (wrap (aref org-indent-strings (min w org-indent-max)))) +LEVEL is the current level of heading. INDENTATION is the +expected indentation when wrapping line. + +When optional argument HEADING is non-nil, assume line is at +a heading. Moreover, if is is `inlinetask', the first star will +have `org-warning' face." + (let* ((line (aref (pcase heading + (`nil org-indent--text-line-prefixes) + (`inlinetask org-indent--inlinetask-line-prefixes) + (_ org-indent--heading-line-prefixes)) + level)) + (wrap + (org-add-props + (concat line + (if heading (concat (make-string level ?*) " ") + (make-string indentation ?\s))) + nil 'face 'org-indent))) ;; Add properties down to the next line to indent empty lines. - (add-text-properties (point) (min (1+ (point-at-eol)) (point-max)) + (add-text-properties (line-beginning-position) (line-beginning-position 2) `(line-prefix ,line wrap-prefix ,wrap))) - (forward-line 1)) + (forward-line)) (defun org-indent-add-properties (beg end &optional delay) "Add indentation properties between BEG and END. @@ -322,26 +321,14 @@ stopped." (org-with-wide-buffer (goto-char beg) (beginning-of-line) - ;; 1. Initialize prefix at BEG. This is done by storing two - ;; variables: INLINE-PF and PF, representing respectively - ;; length of current `line-prefix' when line is inside an - ;; inline task or not. + ;; Initialize prefix at BEG, according to current entry's level. (let* ((case-fold-search t) (limited-re (org-get-limited-outline-regexp)) - (added-ind-per-lvl (abs (1- org-indent-indentation-per-level))) - (pf (save-excursion - (and (ignore-errors (let ((outline-regexp limited-re)) - (org-back-to-heading t))) - (+ (* org-indent-indentation-per-level - (- (match-end 0) (match-beginning 0) 2)) 2)))) - (pf-inline (and (featurep 'org-inlinetask) - (org-inlinetask-in-task-p) - (+ (* org-indent-indentation-per-level - (1- (org-inlinetask-get-task-level))) 2))) + (level (or (org-current-level) 0)) (time-limit (and delay (time-add (current-time) delay)))) - ;; 2. For each line, set `line-prefix' and `wrap-prefix' - ;; properties depending on the type of line (headline, - ;; inline task, item or other). + ;; For each line, set `line-prefix' and `wrap-prefix' + ;; properties depending on the type of line (headline, inline + ;; task, item or other). (org-with-silent-modifications (while (and (<= (point) end) (not (eobp))) (cond @@ -354,38 +341,23 @@ stopped." ((and delay (time-less-p time-limit (current-time))) (setq org-indent-agent-resume-timer (run-with-idle-timer - (time-add (current-idle-time) - org-indent-agent-resume-delay) + (time-add (current-idle-time) org-indent-agent-resume-delay) nil #'org-indent-initialize-agent)) (throw 'interrupt (point))) ;; Headline or inline task. ((looking-at org-outline-regexp) (let* ((nstars (- (match-end 0) (match-beginning 0) 1)) - (line (* added-ind-per-lvl (1- nstars))) - (wrap (+ line (1+ nstars)))) - (cond - ;; Headline: new value for PF. - ((looking-at limited-re) - (org-indent-set-line-properties line wrap t) - (setq pf wrap)) - ;; End of inline task: PF-INLINE is now nil. - ((looking-at "\\*+ end[ \t]*$") - (org-indent-set-line-properties line wrap 'inline) - (setq pf-inline nil)) - ;; Start of inline task. Determine if it contains - ;; text, or if it is only one line long. Set - ;; PF-INLINE accordingly. - (t (org-indent-set-line-properties line wrap 'inline) - (setq pf-inline (and (org-inlinetask-in-task-p) wrap)))))) + (type (or (looking-at-p limited-re) 'inlinetask))) + (org-indent-set-line-properties nstars 0 type) + ;; At an headline, define new value for LEVEL. + (unless (eq type 'inlinetask) (setq level nstars)))) ;; List item: `wrap-prefix' is set where body starts. ((org-at-item-p) - (let* ((line (or pf-inline pf 0)) - (wrap (+ (org-list-item-body-column (point)) line))) - (org-indent-set-line-properties line wrap nil))) - ;; Normal line: use PF-INLINE, PF or nil as prefixes. - (t (let* ((line (or pf-inline pf 0)) - (wrap (+ line (org-get-indentation)))) - (org-indent-set-line-properties line wrap nil)))))))))) + (org-indent-set-line-properties + level (org-list-item-body-column (point)))) + ;; Regular line. + (t + (org-indent-set-line-properties level (org-get-indentation)))))))))) (defun org-indent-notify-modified-headline (beg end) "Set `org-indent-modified-headline-flag' depending on context. @@ -398,13 +370,14 @@ Flag will be non-nil if command is going to modify or delete an headline." (when org-indent-mode (setq org-indent-modified-headline-flag - (save-excursion - (goto-char beg) - (save-match-data - (or (and (org-at-heading-p) (< beg (match-end 0))) - (re-search-forward org-outline-regexp-bol end t))))))) - -(defun org-indent-refresh-maybe (beg end dummy) + (org-with-wide-buffer + (goto-char beg) + (save-match-data + (or (and (org-at-heading-p) (< beg (match-end 0))) + (re-search-forward + (org-with-limited-levels org-outline-regexp-bol) end t))))))) + +(defun org-indent-refresh-maybe (beg end _) "Refresh indentation properties in an adequate portion of buffer. BEG and END are the positions of the beginning and end of the range of inserted text. DUMMY is an unused argument. @@ -414,19 +387,21 @@ This function is meant to be called by `after-change-functions'." (save-match-data ;; If a headline was modified or inserted, set properties until ;; next headline. - (if (or org-indent-modified-headline-flag - (save-excursion - (goto-char beg) - (beginning-of-line) - (re-search-forward org-outline-regexp-bol end t))) - (let ((end (save-excursion - (goto-char end) - (org-with-limited-levels (outline-next-heading)) - (point)))) - (setq org-indent-modified-headline-flag nil) - (org-indent-add-properties beg end)) - ;; Otherwise, only set properties on modified area. - (org-indent-add-properties beg end))))) + (org-with-wide-buffer + (if (or org-indent-modified-headline-flag + (save-excursion + (goto-char beg) + (beginning-of-line) + (re-search-forward + (org-with-limited-levels org-outline-regexp-bol) end t))) + (let ((end (save-excursion + (goto-char end) + (org-with-limited-levels (outline-next-heading)) + (point)))) + (setq org-indent-modified-headline-flag nil) + (org-indent-add-properties beg end)) + ;; Otherwise, only set properties on modified area. + (org-indent-add-properties beg end)))))) (provide 'org-indent) diff --git a/lisp/org/org-info.el b/lisp/org/org-info.el index c8f6f06de06..7f859f9040d 100644 --- a/lisp/org/org-info.el +++ b/lisp/org/org-info.el @@ -1,4 +1,4 @@ -;;; org-info.el --- Support for links to Info nodes from within Org-Mode +;;; org-info.el --- Support for Links to Info Nodes -*- lexical-binding: t; -*- ;; Copyright (C) 2004-2017 Free Software Foundation, Inc. @@ -19,13 +19,13 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Commentary: -;; This file implements links to Info nodes from within Org-mode. -;; Org-mode loads this module by default - if this is not what you want, +;; This file implements links to Info nodes from within Org mode. +;; Org mode loads this module by default - if this is not what you want, ;; configure the variable `org-modules'. ;;; Code: @@ -40,19 +40,20 @@ (defvar Info-current-node) ;; Install the link type -(org-add-link-type "info" 'org-info-open) -(add-hook 'org-store-link-functions 'org-info-store-link) +(org-link-set-parameters "info" + :follow #'org-info-open + :export #'org-info-export + :store #'org-info-store-link) ;; Implementation (defun org-info-store-link () "Store a link to an Info file and node." (when (eq major-mode 'Info-mode) - (let (link desc) - (setq link (concat "info:" - (file-name-nondirectory Info-current-file) - "#" Info-current-node)) - (setq desc (concat (file-name-nondirectory Info-current-file) - "#" Info-current-node)) + (let ((link (concat "info:" + (file-name-nondirectory Info-current-file) + "#" Info-current-node)) + (desc (concat (file-name-nondirectory Info-current-file) + "#" Info-current-node))) (org-store-link-props :type "info" :file Info-current-file :node Info-current-node :link link :desc desc) @@ -67,12 +68,80 @@ "Follow an Info file and node link specified by NAME." (if (or (string-match "\\(.*\\)[#:]:?\\(.*\\)" name) (string-match "\\(.*\\)" name)) - (progn + (let ((filename (match-string 1 name)) + (nodename-or-index (or (match-string 2 name) "Top"))) (require 'info) - (if (match-string 2 name) ; If there isn't a node, choose "Top" - (Info-find-node (match-string 1 name) (match-string 2 name)) - (Info-find-node (match-string 1 name) "Top"))) - (message "Could not open: %s" name))) + ;; If nodename-or-index is invalid node name, then look it up + ;; in the index. + (condition-case nil + (Info-find-node filename nodename-or-index) + (user-error (Info-find-node filename "Top") + (condition-case nil + (Info-index nodename-or-index) + (user-error "Could not find '%s' node or index entry" + nodename-or-index))))) + (user-error "Could not open: %s" name))) + +(defconst org-info-emacs-documents + '("ada-mode" "auth" "autotype" "bovine" "calc" "ccmode" "cl" "dbus" "dired-x" + "ebrowse" "ede" "ediff" "edt" "efaq-w32" "efaq" "eieio" "eintr" "elisp" + "emacs-gnutls" "emacs-mime" "emacs" "epa" "erc" "ert" "eshell" "eudc" "eww" + "flymake" "forms" "gnus" "htmlfontify" "idlwave" "ido" "info" "mairix-el" + "message" "mh-e" "newsticker" "nxml-mode" "octave-mode" "org" "pcl-cvs" + "pgg" "rcirc" "reftex" "remember" "sasl" "sc" "semantic" "ses" "sieve" + "smtpmail" "speedbar" "srecode" "todo-mode" "tramp" "url" "vip" "viper" + "widget" "wisent" "woman") + "List of emacs documents available. +Taken from <https://www.gnu.org/software/emacs/manual/html_mono/.>") + +(defconst org-info-other-documents + '(("libc" . "https://www.gnu.org/software/libc/manual/html_mono/libc.html") + ("make" . "https://www.gnu.org/software/make/manual/make.html")) + "Alist of documents generated from Texinfo source. +When converting info links to HTML, links to any one of these manuals are +converted to use these URL.") + +(defun org-info-map-html-url (filename) + "Return URL or HTML file associated to Info FILENAME. +If FILENAME refers to an official GNU document, return a URL pointing to +the official page for that document, e.g., use \"gnu.org\" for all Emacs +related documents. Otherwise, append \".html\" extension to FILENAME. +See `org-info-emacs-documents' and `org-info-other-documents' for details." + (cond ((member filename org-info-emacs-documents) + (format "https://www.gnu.org/software/emacs/manual/html_mono/%s.html" + filename)) + ((cdr (assoc filename org-info-other-documents))) + (t (concat filename ".html")))) + +(defun org-info--expand-node-name (node) + "Expand Info NODE to HTML cross reference." + ;; See (info "(texinfo) HTML Xref Node Name Expansion") for the + ;; expansion rule. + (let ((node (replace-regexp-in-string + "\\([ \t\n\r]+\\)\\|\\([^a-zA-Z0-9]\\)" + (lambda (m) + (if (match-end 1) "-" (format "_%04x" (string-to-char m)))) + (org-trim node)))) + (cond ((string= node "") "") + ((string-match-p "\\`[0-9]" node) (concat "g_t" node)) + (t node)))) + +(defun org-info-export (path desc format) + "Export an info link. +See `org-link-parameters' for details about PATH, DESC and FORMAT." + (let* ((parts (split-string path "[#:]:?")) + (manual (car parts)) + (node (or (nth 1 parts) "Top"))) + (pcase format + (`html + (format "<a href=\"%s#%s\">%s</a>" + (org-info-map-html-url manual) + (org-info--expand-node-name node) + (or desc path))) + (`texinfo + (let ((title (or desc ""))) + (format "@ref{%s,%s,,%s,}" node title manual))) + (_ nil)))) (provide 'org-info) diff --git a/lisp/org/org-inlinetask.el b/lisp/org/org-inlinetask.el index bf4ab205a4c..4a8e43db03b 100644 --- a/lisp/org/org-inlinetask.el +++ b/lisp/org/org-inlinetask.el @@ -1,4 +1,4 @@ -;;; org-inlinetask.el --- Tasks independent of outline hierarchy +;;; org-inlinetask.el --- Tasks Independent of Outline Hierarchy -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. ;; @@ -20,13 +20,13 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Commentary: ;; -;; This module implements inline tasks in Org-mode. Inline tasks are +;; This module implements inline tasks in Org mode. Inline tasks are ;; tasks that have all the properties of normal outline nodes, ;; including the ability to store meta data like scheduling dates, ;; TODO state, tags and properties. However, these nodes are treated @@ -108,14 +108,13 @@ When nil, the first star is not shown." (defvar org-odd-levels-only) (defvar org-keyword-time-regexp) -(defvar org-drawer-regexp) (defvar org-complex-heading-regexp) (defvar org-property-end-re) (defcustom org-inlinetask-default-state nil "Non-nil means make inline tasks have a TODO keyword initially. This should be the state `org-inlinetask-insert-task' should use by -default, or nil of no state should be assigned." +default, or nil if no state should be assigned." :group 'org-inlinetask :version "24.1" :type '(choice @@ -168,9 +167,9 @@ The number of levels is controlled by `org-inlinetask-min-level'." (stars-re (org-inlinetask-outline-regexp)) (task-beg-re (concat stars-re "\\(?:.*\\)")) (task-end-re (concat stars-re "END[ \t]*$"))) - (or (org-looking-at-p task-beg-re) + (or (looking-at-p task-beg-re) (and (re-search-forward "^\\*+[ \t]+" nil t) - (progn (beginning-of-line) (org-looking-at-p task-end-re))))))) + (progn (beginning-of-line) (looking-at-p task-end-re))))))) (defun org-inlinetask-goto-beginning () "Go to the beginning of the inline task at point." @@ -178,7 +177,7 @@ The number of levels is controlled by `org-inlinetask-min-level'." (let ((case-fold-search t) (inlinetask-re (org-inlinetask-outline-regexp))) (re-search-backward inlinetask-re nil t) - (when (org-looking-at-p (concat inlinetask-re "END[ \t]*$")) + (when (looking-at-p (concat inlinetask-re "END[ \t]*$")) (re-search-backward inlinetask-re nil t)))) (defun org-inlinetask-goto-end () @@ -190,17 +189,16 @@ Return point." (inlinetask-re (org-inlinetask-outline-regexp)) (task-end-re (concat inlinetask-re "END[ \t]*$"))) (cond - ((looking-at task-end-re) (forward-line)) + ((looking-at task-end-re)) ((looking-at inlinetask-re) (forward-line) (cond - ((looking-at task-end-re) (forward-line)) + ((looking-at task-end-re)) ((looking-at inlinetask-re)) ((org-inlinetask-in-task-p) - (re-search-forward inlinetask-re nil t) - (forward-line)))) - (t (re-search-forward inlinetask-re nil t) - (forward-line))) + (re-search-forward inlinetask-re nil t)))) + (t (re-search-forward inlinetask-re nil t))) + (end-of-line) (point)))) (defun org-inlinetask-get-task-level () @@ -273,8 +271,7 @@ If the task has an end part, also demote it." (defvar org-indent-indentation-per-level) ; defined in org-indent.el -(defface org-inlinetask - (org-compatible-face 'shadow '((t (:bold t)))) +(defface org-inlinetask '((t :inherit shadow)) "Face for inlinetask headlines." :group 'org-faces) @@ -288,7 +285,7 @@ If the task has an end part, also demote it." ",\\}\\)\\(\\*\\* .*\\)")) ;; Virtual indentation will add the warning face on the first ;; star. Thus, in that case, only hide it. - (start-face (if (and (org-bound-and-true-p org-indent-mode) + (start-face (if (and (bound-and-true-p org-indent-mode) (> org-indent-indentation-per-level 1)) 'org-hide 'org-warning))) @@ -315,19 +312,36 @@ If the task has an end part, also demote it." ;; Nothing to show/hide. ((= end start)) ;; Inlinetask was folded: expand it. - ((get-char-property (1+ start) 'invisible) + ((eq (get-char-property (1+ start) 'invisible) 'outline) (outline-flag-region start end nil) (org-cycle-hide-drawers 'children)) (t (outline-flag-region start end t))))) +(defun org-inlinetask-hide-tasks (state) + "Hide inline tasks in buffer when STATE is `contents' or `children'. +This function is meant to be used in `org-cycle-hook'." + (pcase state + (`contents + (let ((regexp (org-inlinetask-outline-regexp))) + (save-excursion + (goto-char (point-min)) + (while (re-search-forward regexp nil t) + (org-inlinetask-toggle-visibility) + (org-inlinetask-goto-end))))) + (`children + (save-excursion + (while (and (outline-next-heading) (org-inlinetask-at-task-p)) + (org-inlinetask-toggle-visibility) + (org-inlinetask-goto-end)))))) + (defun org-inlinetask-remove-END-maybe () "Remove an END line when present." (when (looking-at (format "\\([ \t]*\n\\)*\\*\\{%d,\\}[ \t]+END[ \t]*$" org-inlinetask-min-level)) (replace-match ""))) -(eval-after-load "org" - '(add-hook 'org-font-lock-hook 'org-inlinetask-fontify)) +(add-hook 'org-font-lock-hook 'org-inlinetask-fontify) +(add-hook 'org-cycle-hook 'org-inlinetask-hide-tasks) (provide 'org-inlinetask) diff --git a/lisp/org/org-irc.el b/lisp/org/org-irc.el index 1243587beb8..3617ae92422 100644 --- a/lisp/org/org-irc.el +++ b/lisp/org/org-irc.el @@ -1,4 +1,4 @@ -;;; org-irc.el --- Store links to IRC sessions +;;; org-irc.el --- Store Links to IRC Sessions -*- lexical-binding: t; -*- ;; ;; Copyright (C) 2008-2017 Free Software Foundation, Inc. ;; @@ -18,12 +18,12 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: -;; This file implements links to an IRC session from within Org-mode. -;; Org-mode loads this module by default - if this is not what you want, +;; This file implements links to an IRC session from within Org mode. +;; Org mode loads this module by default - if this is not what you want, ;; configure the variable `org-modules'. ;; ;; Please customize the variable `org-modules' to select @@ -50,20 +50,20 @@ (require 'org) -;; Declare the function form ERC that we use. +(declare-function erc-buffer-filter "erc" (predicate &optional proc)) +(declare-function erc-channel-p "erc" (channel)) +(declare-function erc-cmd-JOIN "erc" (channel &optional key)) (declare-function erc-current-logfile "erc-log" (&optional buffer)) -(declare-function erc-prompt "erc" ()) (declare-function erc-default-target "erc" ()) -(declare-function erc-channel-p "erc" (channel)) -(declare-function erc-buffer-filter "erc" (predicate &optional proc)) -(declare-function erc-server-buffer "erc" ()) (declare-function erc-get-server-nickname-list "erc" ()) -(declare-function erc-cmd-JOIN "erc" (channel &optional key)) -(declare-function org-pop-to-buffer-same-window - "org-compat" (&optional buffer-or-name norecord label)) +(declare-function erc-logging-enabled "erc-log" (&optional buffer)) +(declare-function erc-prompt "erc" ()) +(declare-function erc-save-buffer-in-logs "erc-log" (&optional buffer)) +(declare-function erc-server-buffer "erc" ()) (defvar org-irc-client 'erc "The IRC client to act on.") + (defvar org-irc-link-to-logs nil "Non-nil will store a link to the logs, nil will store an irc: style link.") @@ -73,9 +73,7 @@ ;; Generic functions/config (extend these for other clients) -(add-to-list 'org-store-link-functions 'org-irc-store-link) - -(org-add-link-type "irc" 'org-irc-visit nil) +(org-link-set-parameters "irc" :follow #'org-irc-visit :store #'org-irc-store-link) (defun org-irc-visit (link) "Parse LINK and dispatch to the correct function based on the client found." @@ -114,11 +112,9 @@ chars that the value AFTER with `...'" (cons "[ \t]*$" "") (cons (concat "^\\(.\\{" after "\\}\\).*") "\\1...")))) - (mapc (lambda (x) - (when (string-match (car x) string) - (setq string (replace-match (cdr x) nil nil string)))) - replace-map) - string)) + (dolist (x replace-map string) + (when (string-match (car x) string) + (setq string (replace-match (cdr x) nil nil string)))))) ;; ERC specific functions @@ -211,7 +207,8 @@ default." (require 'erc) (require 'erc-log) (let* ((server (car (car link))) - (port (or (string-to-number (cadr (pop link))) erc-default-port)) + (port (let ((p (cadr (pop link)))) + (if p (string-to-number p) erc-default-port))) (server-buffer) (buffer-list (erc-buffer-filter @@ -233,7 +230,7 @@ default." (throw 'found x)))))) (if chan-buf (progn - (org-pop-to-buffer-same-window chan-buf) + (pop-to-buffer-same-window chan-buf) ;; if we got a nick, and they're in the chan, ;; then start a chat with them (let ((nick (pop link))) @@ -244,9 +241,9 @@ default." (insert (concat nick ": "))) (error "%s not found in %s" nick chan-name))))) (progn - (org-pop-to-buffer-same-window server-buffer) + (pop-to-buffer-same-window server-buffer) (erc-cmd-JOIN chan-name)))) - (org-pop-to-buffer-same-window server-buffer))) + (pop-to-buffer-same-window server-buffer))) ;; no server match, make new connection (erc-select :server server :port port)))) diff --git a/lisp/org/org-lint.el b/lisp/org/org-lint.el new file mode 100644 index 00000000000..8372ae0fb85 --- /dev/null +++ b/lisp/org/org-lint.el @@ -0,0 +1,1242 @@ +;;; org-lint.el --- Linting for Org documents -*- lexical-binding: t; -*- + +;; Copyright (C) 2015-2017 Free Software Foundation, Inc. + +;; Author: Nicolas Goaziou <mail@nicolasgoaziou.fr> +;; Keywords: outlines, hypermedia, calendar, wp + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; This library implements linting for Org syntax. The sole public +;; function is `org-lint', which see. + +;; Internally, the library defines a new structure: +;; `org-lint-checker', with the following slots: + +;; - NAME: Unique check identifier, as a non-nil symbol that doesn't +;; start with an hyphen. +;; +;; The check is done calling the function `org-lint-NAME' with one +;; mandatory argument, the parse tree describing the current Org +;; buffer. Such function calls are wrapped within +;; a `save-excursion' and point is always at `point-min'. Its +;; return value has to be an alist (POSITION MESSAGE) when +;; POSITION refer to the buffer position of the error, as an +;; integer, and MESSAGE is a string describing the error. + +;; - DESCRIPTION: Summary about the check, as a string. + +;; - CATEGORIES: Categories relative to the check, as a list of +;; symbol. They are used for filtering when calling `org-lint'. +;; Checkers not explicitly associated to a category are collected +;; in the `default' one. + +;; - TRUST: The trust level one can have in the check. It is either +;; `low' or `high', depending on the heuristics implemented and +;; the nature of the check. This has an indicative value only and +;; is displayed along reports. + +;; All checks have to be listed in `org-lint--checkers'. + +;; Results are displayed in a special "*Org Lint*" buffer with +;; a dedicated major mode, derived from `tabulated-list-mode'. +;; +;; In addition to the usual key-bindings inherited from it, "C-j" and +;; "TAB" display problematic line reported under point whereas "RET" +;; jumps to it. Also, "h" hides all reports similar to the current +;; one. Additionally, "i" removes them from subsequent reports. + +;; Checks currently implemented are: + +;; - duplicate CUSTOM_ID properties +;; - duplicate NAME values +;; - duplicate targets +;; - duplicate footnote definitions +;; - orphaned affiliated keywords +;; - obsolete affiliated keywords +;; - missing language in src blocks +;; - missing back-end in export blocks +;; - invalid Babel call blocks +;; - NAME values with a colon +;; - deprecated export block syntax +;; - deprecated Babel header properties +;; - wrong header arguments in src blocks +;; - misuse of CATEGORY keyword +;; - "coderef" links with unknown destination +;; - "custom-id" links with unknown destination +;; - "fuzzy" links with unknown destination +;; - "id" links with unknown destination +;; - links to non-existent local files +;; - SETUPFILE keywords with non-existent file parameter +;; - INCLUDE keywords with wrong link parameter +;; - obsolete markup in INCLUDE keyword +;; - unknown items in OPTIONS keyword +;; - spurious macro arguments or invalid macro templates +;; - special properties in properties drawer +;; - obsolete syntax for PROPERTIES drawers +;; - Invalid EFFORT property value +;; - missing definition for footnote references +;; - missing reference for footnote definitions +;; - non-footnote definitions in footnote section +;; - probable invalid keywords +;; - invalid blocks +;; - misplaced planning info line +;; - incomplete drawers +;; - indented diary-sexps +;; - obsolete QUOTE section +;; - obsolete "file+application" link +;; - blank headlines with tags + + +;;; Code: + +(require 'cl-lib) +(require 'org-element) +(require 'org-macro) +(require 'ox) +(require 'ob) + + +;;; Checkers + +(cl-defstruct (org-lint-checker (:copier nil)) + (name 'missing-checker-name) + (description "") + (categories '(default)) + (trust 'high)) ; `low' or `high' + +(defun org-lint-missing-checker-name (_) + (error + "`A checker has no `:name' property. Please verify `org-lint--checkers'")) + +(defconst org-lint--checkers + (list + (make-org-lint-checker + :name 'duplicate-custom-id + :description "Report duplicates CUSTOM_ID properties" + :categories '(link)) + (make-org-lint-checker + :name 'duplicate-name + :description "Report duplicate NAME values" + :categories '(babel link)) + (make-org-lint-checker + :name 'duplicate-target + :description "Report duplicate targets" + :categories '(link)) + (make-org-lint-checker + :name 'duplicate-footnote-definition + :description "Report duplicate footnote definitions" + :categories '(footnote)) + (make-org-lint-checker + :name 'orphaned-affiliated-keywords + :description "Report orphaned affiliated keywords" + :trust 'low) + (make-org-lint-checker + :name 'obsolete-affiliated-keywords + :description "Report obsolete affiliated keywords" + :categories '(obsolete)) + (make-org-lint-checker + :name 'deprecated-export-blocks + :description "Report deprecated export block syntax" + :categories '(obsolete export) + :trust 'low) + (make-org-lint-checker + :name 'deprecated-header-syntax + :description "Report deprecated Babel header syntax" + :categories '(obsolete babel) + :trust 'low) + (make-org-lint-checker + :name 'missing-language-in-src-block + :description "Report missing language in src blocks" + :categories '(babel)) + (make-org-lint-checker + :name 'missing-backend-in-export-block + :description "Report missing back-end in export blocks" + :categories '(export)) + (make-org-lint-checker + :name 'invalid-babel-call-block + :description "Report invalid Babel call blocks" + :categories '(babel)) + (make-org-lint-checker + :name 'colon-in-name + :description "Report NAME values with a colon" + :categories '(babel)) + (make-org-lint-checker + :name 'wrong-header-argument + :description "Report wrong babel headers" + :categories '(babel)) + (make-org-lint-checker + :name 'wrong-header-value + :description "Report invalid value in babel headers" + :categories '(babel) + :trust 'low) + (make-org-lint-checker + :name 'deprecated-category-setup + :description "Report misuse of CATEGORY keyword" + :categories '(obsolete)) + (make-org-lint-checker + :name 'invalid-coderef-link + :description "Report \"coderef\" links with unknown destination" + :categories '(link)) + (make-org-lint-checker + :name 'invalid-custom-id-link + :description "Report \"custom-id\" links with unknown destination" + :categories '(link)) + (make-org-lint-checker + :name 'invalid-fuzzy-link + :description "Report \"fuzzy\" links with unknown destination" + :categories '(link)) + (make-org-lint-checker + :name 'invalid-id-link + :description "Report \"id\" links with unknown destination" + :categories '(link)) + (make-org-lint-checker + :name 'link-to-local-file + :description "Report links to non-existent local files" + :categories '(link) + :trust 'low) + (make-org-lint-checker + :name 'non-existent-setupfile-parameter + :description "Report SETUPFILE keywords with non-existent file parameter" + :trust 'low) + (make-org-lint-checker + :name 'wrong-include-link-parameter + :description "Report INCLUDE keywords with misleading link parameter" + :categories '(export) + :trust 'low) + (make-org-lint-checker + :name 'obsolete-include-markup + :description "Report obsolete markup in INCLUDE keyword" + :categories '(obsolete export) + :trust 'low) + (make-org-lint-checker + :name 'unknown-options-item + :description "Report unknown items in OPTIONS keyword" + :categories '(export) + :trust 'low) + (make-org-lint-checker + :name 'invalid-macro-argument-and-template + :description "Report spurious macro arguments or invalid macro templates" + :categories '(export) + :trust 'low) + (make-org-lint-checker + :name 'special-property-in-properties-drawer + :description "Report special properties in properties drawers" + :categories '(properties)) + (make-org-lint-checker + :name 'obsolete-properties-drawer + :description "Report obsolete syntax for properties drawers" + :categories '(obsolete properties)) + (make-org-lint-checker + :name 'invalid-effort-property + :description "Report invalid duration in EFFORT property" + :categories '(properties)) + (make-org-lint-checker + :name 'undefined-footnote-reference + :description "Report missing definition for footnote references" + :categories '(footnote)) + (make-org-lint-checker + :name 'unreferenced-footnote-definition + :description "Report missing reference for footnote definitions" + :categories '(footnote)) + (make-org-lint-checker + :name 'extraneous-element-in-footnote-section + :description "Report non-footnote definitions in footnote section" + :categories '(footnote)) + (make-org-lint-checker + :name 'invalid-keyword-syntax + :description "Report probable invalid keywords" + :trust 'low) + (make-org-lint-checker + :name 'invalid-block + :description "Report invalid blocks" + :trust 'low) + (make-org-lint-checker + :name 'misplaced-planning-info + :description "Report misplaced planning info line" + :trust 'low) + (make-org-lint-checker + :name 'incomplete-drawer + :description "Report probable incomplete drawers" + :trust 'low) + (make-org-lint-checker + :name 'indented-diary-sexp + :description "Report probable indented diary-sexps" + :trust 'low) + (make-org-lint-checker + :name 'quote-section + :description "Report obsolete QUOTE section" + :categories '(obsolete) + :trust 'low) + (make-org-lint-checker + :name 'file-application + :description "Report obsolete \"file+application\" link" + :categories '(link obsolete)) + (make-org-lint-checker + :name 'empty-headline-with-tags + :description "Report ambiguous empty headlines with tags" + :categories '(headline) + :trust 'low)) + "List of all available checkers.") + +(defun org-lint--collect-duplicates + (ast type extract-key extract-position build-message) + "Helper function to collect duplicates in parse tree AST. + +EXTRACT-KEY is a function extracting key. It is called with +a single argument: the element or object. Comparison is done +with `equal'. + +EXTRACT-POSITION is a function returning position for the report. +It is called with two arguments, the object or element, and the +key. + +BUILD-MESSAGE is a function creating the report message. It is +called with one argument, the key used for comparison." + (let* (keys + originals + reports + (make-report + (lambda (position value) + (push (list position (funcall build-message value)) reports)))) + (org-element-map ast type + (lambda (datum) + (let ((key (funcall extract-key datum))) + (cond + ((not key)) + ((assoc key keys) (cl-pushnew (assoc key keys) originals) + (funcall make-report (funcall extract-position datum key) key)) + (t (push (cons key (funcall extract-position datum key)) keys)))))) + (dolist (e originals reports) (funcall make-report (cdr e) (car e))))) + +(defun org-lint-duplicate-custom-id (ast) + (org-lint--collect-duplicates + ast + 'node-property + (lambda (property) + (and (eq (compare-strings "CUSTOM_ID" nil nil + (org-element-property :key property) nil nil + t) + t) + (org-element-property :value property))) + (lambda (property _) (org-element-property :begin property)) + (lambda (key) (format "Duplicate CUSTOM_ID property \"%s\"" key)))) + +(defun org-lint-duplicate-name (ast) + (org-lint--collect-duplicates + ast + org-element-all-elements + (lambda (datum) (org-element-property :name datum)) + (lambda (datum name) + (goto-char (org-element-property :begin datum)) + (re-search-forward + (format "^[ \t]*#\\+[A-Za-z]+: +%s *$" (regexp-quote name))) + (match-beginning 0)) + (lambda (key) (format "Duplicate NAME \"%s\"" key)))) + +(defun org-lint-duplicate-target (ast) + (org-lint--collect-duplicates + ast + 'target + (lambda (target) (split-string (org-element-property :value target))) + (lambda (target _) (org-element-property :begin target)) + (lambda (key) + (format "Duplicate target <<%s>>" (mapconcat #'identity key " "))))) + +(defun org-lint-duplicate-footnote-definition (ast) + (org-lint--collect-duplicates + ast + 'footnote-definition + (lambda (definition) (org-element-property :label definition)) + (lambda (definition _) (org-element-property :post-affiliated definition)) + (lambda (key) (format "Duplicate footnote definition \"%s\"" key)))) + +(defun org-lint-orphaned-affiliated-keywords (ast) + ;; Ignore orphan RESULTS keywords, which could be generated from + ;; a source block returning no value. + (let ((keywords (cl-set-difference org-element-affiliated-keywords + '("RESULT" "RESULTS") + :test #'equal))) + (org-element-map ast 'keyword + (lambda (k) + (let ((key (org-element-property :key k))) + (and (or (let ((case-fold-search t)) + (string-match-p "\\`ATTR_[-_A-Za-z0-9]+\\'" key)) + (member key keywords)) + (list (org-element-property :post-affiliated k) + (format "Orphaned affiliated keyword: \"%s\"" key)))))))) + +(defun org-lint-obsolete-affiliated-keywords (_) + (let ((regexp (format "^[ \t]*#\\+%s:" + (regexp-opt '("DATA" "LABEL" "RESNAME" "SOURCE" + "SRCNAME" "TBLNAME" "RESULT" "HEADERS") + t))) + reports) + (while (re-search-forward regexp nil t) + (let ((key (upcase (match-string-no-properties 1)))) + (when (< (point) + (org-element-property :post-affiliated (org-element-at-point))) + (push + (list (line-beginning-position) + (format + "Obsolete affiliated keyword: \"%s\". Use \"%s\" instead" + key + (pcase key + ("HEADERS" "HEADER") + ("RESULT" "RESULTS") + (_ "NAME")))) + reports)))) + reports)) + +(defun org-lint-deprecated-export-blocks (ast) + (let ((deprecated '("ASCII" "BEAMER" "HTML" "LATEX" "MAN" "MARKDOWN" "MD" + "ODT" "ORG" "TEXINFO"))) + (org-element-map ast 'special-block + (lambda (b) + (let ((type (org-element-property :type b))) + (when (member-ignore-case type deprecated) + (list + (org-element-property :post-affiliated b) + (format + "Deprecated syntax for export block. Use \"BEGIN_EXPORT %s\" \ +instead" + type)))))))) + +(defun org-lint-deprecated-header-syntax (ast) + (let* ((deprecated-babel-properties + (mapcar (lambda (arg) (symbol-name (car arg))) + org-babel-common-header-args-w-values)) + (deprecated-re + (format "\\`%s[ \t]" (regexp-opt deprecated-babel-properties t)))) + (org-element-map ast '(keyword node-property) + (lambda (datum) + (let ((key (org-element-property :key datum))) + (pcase (org-element-type datum) + (`keyword + (let ((value (org-element-property :value datum))) + (and (string= key "PROPERTY") + (string-match deprecated-re value) + (list (org-element-property :begin datum) + (format "Deprecated syntax for \"%s\". \ +Use header-args instead" + (match-string-no-properties 1 value)))))) + (`node-property + (and (member-ignore-case key deprecated-babel-properties) + (list + (org-element-property :begin datum) + (format "Deprecated syntax for \"%s\". \ +Use :header-args: instead" + key)))))))))) + +(defun org-lint-missing-language-in-src-block (ast) + (org-element-map ast 'src-block + (lambda (b) + (unless (org-element-property :language b) + (list (org-element-property :post-affiliated b) + "Missing language in source block"))))) + +(defun org-lint-missing-backend-in-export-block (ast) + (org-element-map ast 'export-block + (lambda (b) + (unless (org-element-property :type b) + (list (org-element-property :post-affiliated b) + "Missing back-end in export block"))))) + +(defun org-lint-invalid-babel-call-block (ast) + (org-element-map ast 'babel-call + (lambda (b) + (cond + ((not (org-element-property :call b)) + (list (org-element-property :post-affiliated b) + "Invalid syntax in babel call block")) + ((let ((h (org-element-property :end-header b))) + (and h (string-match-p "\\`\\[.*\\]\\'" h))) + (list + (org-element-property :post-affiliated b) + "Babel call's end header must not be wrapped within brackets")))))) + +(defun org-lint-deprecated-category-setup (ast) + (org-element-map ast 'keyword + (let (category-flag) + (lambda (k) + (cond + ((not (string= (org-element-property :key k) "CATEGORY")) nil) + (category-flag + (list (org-element-property :post-affiliated k) + "Spurious CATEGORY keyword. Set :CATEGORY: property instead")) + (t (setf category-flag t) nil)))))) + +(defun org-lint-invalid-coderef-link (ast) + (let ((info (list :parse-tree ast))) + (org-element-map ast 'link + (lambda (link) + (let ((ref (org-element-property :path link))) + (and (equal (org-element-property :type link) "coderef") + (not (ignore-errors (org-export-resolve-coderef ref info))) + (list (org-element-property :begin link) + (format "Unknown coderef \"%s\"" ref)))))))) + +(defun org-lint-invalid-custom-id-link (ast) + (let ((info (list :parse-tree ast))) + (org-element-map ast 'link + (lambda (link) + (and (equal (org-element-property :type link) "custom-id") + (not (ignore-errors (org-export-resolve-id-link link info))) + (list (org-element-property :begin link) + (format "Unknown custom ID \"%s\"" + (org-element-property :path link)))))))) + +(defun org-lint-invalid-fuzzy-link (ast) + (let ((info (list :parse-tree ast))) + (org-element-map ast 'link + (lambda (link) + (and (equal (org-element-property :type link) "fuzzy") + (not (ignore-errors (org-export-resolve-fuzzy-link link info))) + (list (org-element-property :begin link) + (format "Unknown fuzzy location \"%s\"" + (let ((path (org-element-property :path link))) + (if (string-prefix-p "*" path) + (substring path 1) + path))))))))) + +(defun org-lint-invalid-id-link (ast) + (org-element-map ast 'link + (lambda (link) + (let ((id (org-element-property :path link))) + (and (equal (org-element-property :type link) "id") + (not (org-id-find id)) + (list (org-element-property :begin link) + (format "Unknown ID \"%s\"" id))))))) + +(defun org-lint-special-property-in-properties-drawer (ast) + (org-element-map ast 'node-property + (lambda (p) + (let ((key (org-element-property :key p))) + (and (member-ignore-case key org-special-properties) + (list (org-element-property :begin p) + (format + "Special property \"%s\" found in a properties drawer" + key))))))) + +(defun org-lint-obsolete-properties-drawer (ast) + (org-element-map ast 'drawer + (lambda (d) + (when (equal (org-element-property :drawer-name d) "PROPERTIES") + (let ((section (org-element-lineage d '(section)))) + (unless (org-element-map section 'property-drawer #'identity nil t) + (list (org-element-property :post-affiliated d) + (if (save-excursion + (goto-char (org-element-property :post-affiliated d)) + (forward-line -1) + (or (org-at-heading-p) (org-at-planning-p))) + "Incorrect contents for PROPERTIES drawer" + "Incorrect location for PROPERTIES drawer")))))))) + +(defun org-lint-invalid-effort-property (ast) + (org-element-map ast 'node-property + (lambda (p) + (when (equal "EFFORT" (org-element-property :key p)) + (let ((value (org-element-property :value p))) + (and (org-string-nw-p value) + (not (org-duration-p value)) + (list (org-element-property :begin p) + (format "Invalid effort duration format: %S" value)))))))) + +(defun org-lint-link-to-local-file (ast) + (org-element-map ast 'link + (lambda (l) + (when (equal (org-element-property :type l) "file") + (let ((file (org-link-unescape (org-element-property :path l)))) + (and (not (file-remote-p file)) + (not (file-exists-p file)) + (list (org-element-property :begin l) + (format (if (org-element-lineage l '(link)) + "Link to non-existent image file \"%s\"\ + in link description" + "Link to non-existent local file \"%s\"") + file)))))))) + +(defun org-lint-non-existent-setupfile-parameter (ast) + (org-element-map ast 'keyword + (lambda (k) + (when (equal (org-element-property :key k) "SETUPFILE") + (let ((file (org-unbracket-string + "\"" "\"" + (org-element-property :value k)))) + (and (not (file-remote-p file)) + (not (file-exists-p file)) + (list (org-element-property :begin k) + (format "Non-existent setup file \"%s\"" file)))))))) + +(defun org-lint-wrong-include-link-parameter (ast) + (org-element-map ast 'keyword + (lambda (k) + (when (equal (org-element-property :key k) "INCLUDE") + (let* ((value (org-element-property :value k)) + (path + (and (string-match "^\\(\".+\"\\|\\S-+\\)[ \t]*" value) + (save-match-data + (org-unbracket-string "\"" "\"" (match-string 1 value)))))) + (if (not path) + (list (org-element-property :post-affiliated k) + "Missing location argument in INCLUDE keyword") + (let* ((file (org-string-nw-p + (if (string-match "::\\(.*\\)\\'" path) + (substring path 0 (match-beginning 0)) + path))) + (search (and (not (equal file path)) + (org-string-nw-p (match-string 1 path))))) + (if (and file + (not (file-remote-p file)) + (not (file-exists-p file))) + (list (org-element-property :post-affiliated k) + "Non-existent file argument in INCLUDE keyword") + (let* ((visiting (if file (find-buffer-visiting file) + (current-buffer))) + (buffer (or visiting (find-file-noselect file)))) + (unwind-protect + (with-current-buffer buffer + (when (and search + (not + (ignore-errors + (let ((org-link-search-inhibit-query t)) + (org-link-search search nil t))))) + (list (org-element-property :post-affiliated k) + (format + "Invalid search part \"%s\" in INCLUDE keyword" + search)))) + (unless visiting (kill-buffer buffer)))))))))))) + +(defun org-lint-obsolete-include-markup (ast) + (let ((regexp (format "\\`\\(?:\".+\"\\|\\S-+\\)[ \t]+%s" + (regexp-opt + '("ASCII" "BEAMER" "HTML" "LATEX" "MAN" "MARKDOWN" "MD" + "ODT" "ORG" "TEXINFO") + t)))) + (org-element-map ast 'keyword + (lambda (k) + (when (equal (org-element-property :key k) "INCLUDE") + (let ((case-fold-search t) + (value (org-element-property :value k))) + (when (string-match regexp value) + (let ((markup (match-string-no-properties 1 value))) + (list (org-element-property :post-affiliated k) + (format "Obsolete markup \"%s\" in INCLUDE keyword. \ +Use \"export %s\" instead" + markup + markup)))))))))) + +(defun org-lint-unknown-options-item (ast) + (let ((allowed (delq nil + (append + (mapcar (lambda (o) (nth 2 o)) org-export-options-alist) + (cl-mapcan + (lambda (b) + (mapcar (lambda (o) (nth 2 o)) + (org-export-backend-options b))) + org-export-registered-backends)))) + reports) + (org-element-map ast 'keyword + (lambda (k) + (when (string= (org-element-property :key k) "OPTIONS") + (let ((value (org-element-property :value k)) + (start 0)) + (while (string-match "\\(.+?\\):\\((.*?)\\|\\S-*\\)[ \t]*" + value + start) + (setf start (match-end 0)) + (let ((item (match-string 1 value))) + (unless (member item allowed) + (push (list (org-element-property :post-affiliated k) + (format "Unknown OPTIONS item \"%s\"" item)) + reports)))))))) + reports)) + +(defun org-lint-invalid-macro-argument-and-template (ast) + (let ((extract-placeholders + (lambda (template) + (let ((start 0) + args) + (while (string-match "\\$\\([1-9][0-9]*\\)" template start) + (setf start (match-end 0)) + (push (string-to-number (match-string 1 template)) args)) + (sort (org-uniquify args) #'<)))) + reports) + ;; Check arguments for macro templates. + (org-element-map ast 'keyword + (lambda (k) + (when (string= (org-element-property :key k) "MACRO") + (let* ((value (org-element-property :value k)) + (name (and (string-match "^\\S-+" value) + (match-string 0 value))) + (template (and name + (org-trim (substring value (match-end 0)))))) + (cond + ((not name) + (push (list (org-element-property :post-affiliated k) + "Missing name in MACRO keyword") + reports)) + ((not (org-string-nw-p template)) + (push (list (org-element-property :post-affiliated k) + "Missing template in macro \"%s\"" name) + reports)) + (t + (unless (let ((args (funcall extract-placeholders template))) + (equal (number-sequence 1 (or (org-last args) 0)) args)) + (push (list (org-element-property :post-affiliated k) + (format "Unused placeholders in macro \"%s\"" + name)) + reports)))))))) + ;; Check arguments for macros. + (org-macro-initialize-templates) + (let ((templates (append + (mapcar (lambda (m) (cons m "$1")) + '("author" "date" "email" "title" "results")) + org-macro-templates))) + (org-element-map ast 'macro + (lambda (macro) + (let* ((name (org-element-property :key macro)) + (template (cdr (assoc-string name templates t)))) + (if (not template) + (push (list (org-element-property :begin macro) + (format "Undefined macro \"%s\"" name)) + reports) + (let ((arg-numbers (funcall extract-placeholders template))) + (when arg-numbers + (let ((spurious-args + (nthcdr (apply #'max arg-numbers) + (org-element-property :args macro)))) + (when spurious-args + (push + (list (org-element-property :begin macro) + (format "Unused argument%s in macro \"%s\": %s" + (if (> (length spurious-args) 1) "s" "") + name + (mapconcat (lambda (a) (format "\"%s\"" a)) + spurious-args + ", "))) + reports)))))))))) + reports)) + +(defun org-lint-undefined-footnote-reference (ast) + (let ((definitions (org-element-map ast 'footnote-definition + (lambda (f) (org-element-property :label f))))) + (org-element-map ast 'footnote-reference + (lambda (f) + (let ((label (org-element-property :label f))) + (and (eq 'standard (org-element-property :type f)) + (not (member label definitions)) + (list (org-element-property :begin f) + (format "Missing definition for footnote [%s]" + label)))))))) + +(defun org-lint-unreferenced-footnote-definition (ast) + (let ((references (org-element-map ast 'footnote-reference + (lambda (f) (org-element-property :label f))))) + (org-element-map ast 'footnote-definition + (lambda (f) + (let ((label (org-element-property :label f))) + (and label + (not (member label references)) + (list (org-element-property :post-affiliated f) + (format "No reference for footnote definition [%s]" + label)))))))) + +(defun org-lint-colon-in-name (ast) + (org-element-map ast org-element-all-elements + (lambda (e) + (let ((name (org-element-property :name e))) + (and name + (string-match-p ":" name) + (list (progn + (goto-char (org-element-property :begin e)) + (re-search-forward + (format "^[ \t]*#\\+\\w+: +%s *$" (regexp-quote name))) + (match-beginning 0)) + (format + "Name \"%s\" contains a colon; Babel cannot use it as input" + name))))))) + +(defun org-lint-misplaced-planning-info (_) + (let ((case-fold-search t) + reports) + (while (re-search-forward org-planning-line-re nil t) + (unless (memq (org-element-type (org-element-at-point)) + '(comment-block example-block export-block planning + src-block verse-block)) + (push (list (line-beginning-position) "Misplaced planning info line") + reports))) + reports)) + +(defun org-lint-incomplete-drawer (_) + (let (reports) + (while (re-search-forward org-drawer-regexp nil t) + (let ((name (org-trim (match-string-no-properties 0))) + (element (org-element-at-point))) + (pcase (org-element-type element) + ((or `drawer `property-drawer) + (goto-char (org-element-property :end element)) + nil) + ((or `comment-block `example-block `export-block `src-block + `verse-block) + nil) + (_ + (push (list (line-beginning-position) + (format "Possible incomplete drawer \"%s\"" name)) + reports))))) + reports)) + +(defun org-lint-indented-diary-sexp (_) + (let (reports) + (while (re-search-forward "^[ \t]+%%(" nil t) + (unless (memq (org-element-type (org-element-at-point)) + '(comment-block diary-sexp example-block export-block + src-block verse-block)) + (push (list (line-beginning-position) "Possible indented diary-sexp") + reports))) + reports)) + +(defun org-lint-invalid-block (_) + (let ((case-fold-search t) + (regexp "^[ \t]*#\\+\\(BEGIN\\|END\\)\\(?::\\|_[^[:space:]]*\\)?[ \t]*") + reports) + (while (re-search-forward regexp nil t) + (let ((name (org-trim (buffer-substring-no-properties + (line-beginning-position) (line-end-position))))) + (cond + ((and (string-prefix-p "END" (match-string 1) t) + (not (eolp))) + (push (list (line-beginning-position) + (format "Invalid block closing line \"%s\"" name)) + reports)) + ((not (memq (org-element-type (org-element-at-point)) + '(center-block comment-block dynamic-block example-block + export-block quote-block special-block + src-block verse-block))) + (push (list (line-beginning-position) + (format "Possible incomplete block \"%s\"" + name)) + reports))))) + reports)) + +(defun org-lint-invalid-keyword-syntax (_) + (let ((regexp "^[ \t]*#\\+\\([^[:space:]:]*\\)\\(?: \\|$\\)") + (exception-re + (format "[ \t]*#\\+%s\\(\\[.*\\]\\)?:\\(?: \\|$\\)" + (regexp-opt org-element-dual-keywords))) + reports) + (while (re-search-forward regexp nil t) + (let ((name (match-string-no-properties 1))) + (unless (or (string-prefix-p "BEGIN" name t) + (string-prefix-p "END" name t) + (save-excursion + (beginning-of-line) + (let ((case-fold-search t)) (looking-at exception-re)))) + (push (list (match-beginning 0) + (format "Possible missing colon in keyword \"%s\"" name)) + reports)))) + reports)) + +(defun org-lint-extraneous-element-in-footnote-section (ast) + (org-element-map ast 'headline + (lambda (h) + (and (org-element-property :footnote-section-p h) + (org-element-map (org-element-contents h) + (cl-remove-if + (lambda (e) + (memq e '(comment comment-block footnote-definition + property-drawer section))) + org-element-all-elements) + (lambda (e) + (not (and (eq (org-element-type e) 'headline) + (org-element-property :commentedp e)))) + nil t '(footnote-definition property-drawer)) + (list (org-element-property :begin h) + "Extraneous elements in footnote section are not exported"))))) + +(defun org-lint-quote-section (ast) + (org-element-map ast '(headline inlinetask) + (lambda (h) + (let ((title (org-element-property :raw-value h))) + (and (or (string-prefix-p "QUOTE " title) + (string-prefix-p (concat org-comment-string " QUOTE ") title)) + (list (org-element-property :begin h) + "Deprecated QUOTE section")))))) + +(defun org-lint-file-application (ast) + (org-element-map ast 'link + (lambda (l) + (let ((app (org-element-property :application l))) + (and app + (list (org-element-property :begin l) + (format "Deprecated \"file+%s\" link type" app))))))) + +(defun org-lint-wrong-header-argument (ast) + (let* ((reports) + (verify + (lambda (datum language headers) + (let ((allowed + ;; If LANGUAGE is specified, restrict allowed + ;; headers to both LANGUAGE-specific and default + ;; ones. Otherwise, accept headers from any loaded + ;; language. + (append + org-babel-header-arg-names + (cl-mapcan + (lambda (l) + (let ((v (intern (format "org-babel-header-args:%s" l)))) + (and (boundp v) (mapcar #'car (symbol-value v))))) + (if language (list language) + (mapcar #'car org-babel-load-languages)))))) + (dolist (header headers) + (let ((h (symbol-name (car header))) + (p (or (org-element-property :post-affiliated datum) + (org-element-property :begin datum)))) + (cond + ((not (string-prefix-p ":" h)) + (push + (list p + (format "Missing colon in header argument \"%s\"" h)) + reports)) + ((assoc-string (substring h 1) allowed)) + (t (push (list p (format "Unknown header argument \"%s\"" h)) + reports))))))))) + (org-element-map ast '(babel-call inline-babel-call inline-src-block keyword + node-property src-block) + (lambda (datum) + (pcase (org-element-type datum) + ((or `babel-call `inline-babel-call) + (funcall verify + datum + nil + (cl-mapcan #'org-babel-parse-header-arguments + (list + (org-element-property :inside-header datum) + (org-element-property :end-header datum))))) + (`inline-src-block + (funcall verify + datum + (org-element-property :language datum) + (org-babel-parse-header-arguments + (org-element-property :parameters datum)))) + (`keyword + (when (string= (org-element-property :key datum) "PROPERTY") + (let ((value (org-element-property :value datum))) + (when (string-match "\\`header-args\\(?::\\(\\S-+\\)\\)?\\+? *" + value) + (funcall verify + datum + (match-string 1 value) + (org-babel-parse-header-arguments + (substring value (match-end 0)))))))) + (`node-property + (let ((key (org-element-property :key datum))) + (when (let ((case-fold-search t)) + (string-match "\\`HEADER-ARGS\\(?::\\(\\S-+\\)\\)?\\+?" + key)) + (funcall verify + datum + (match-string 1 key) + (org-babel-parse-header-arguments + (org-element-property :value datum)))))) + (`src-block + (funcall verify + datum + (org-element-property :language datum) + (cl-mapcan #'org-babel-parse-header-arguments + (cons (org-element-property :parameters datum) + (org-element-property :header datum)))))))) + reports)) + +(defun org-lint-wrong-header-value (ast) + (let (reports) + (org-element-map ast + '(babel-call inline-babel-call inline-src-block src-block) + (lambda (datum) + (let* ((type (org-element-type datum)) + (language (org-element-property :language datum)) + (allowed-header-values + (append (and language + (let ((v (intern (concat "org-babel-header-args:" + language)))) + (and (boundp v) (symbol-value v)))) + org-babel-common-header-args-w-values)) + (datum-header-values + (org-babel-parse-header-arguments + (org-trim + (pcase type + (`src-block + (mapconcat + #'identity + (cons (org-element-property :parameters datum) + (org-element-property :header datum)) + " ")) + (`inline-src-block + (or (org-element-property :parameters datum) "")) + (_ + (concat + (org-element-property :inside-header datum) + " " + (org-element-property :end-header datum)))))))) + (dolist (header datum-header-values) + (let ((allowed-values + (cdr (assoc-string (substring (symbol-name (car header)) 1) + allowed-header-values)))) + (unless (memq allowed-values '(:any nil)) + (let ((values (cdr header)) + groups-alist) + (dolist (v (if (stringp values) (split-string values) + (list values))) + (let ((valid-value nil)) + (catch 'exit + (dolist (group allowed-values) + (cond + ((not (funcall + (if (stringp v) #'assoc-string #'assoc) + v group)) + (when (memq :any group) + (setf valid-value t) + (push (cons group v) groups-alist))) + ((assq group groups-alist) + (push + (list + (or (org-element-property :post-affiliated datum) + (org-element-property :begin datum)) + (format + "Forbidden combination in header \"%s\": %s, %s" + (car header) + (cdr (assq group groups-alist)) + v)) + reports) + (throw 'exit nil)) + (t (push (cons group v) groups-alist) + (setf valid-value t)))) + (unless valid-value + (push + (list + (or (org-element-property :post-affiliated datum) + (org-element-property :begin datum)) + (format "Unknown value \"%s\" for header \"%s\"" + v + (car header))) + reports)))))))))))) + reports)) + +(defun org-lint-empty-headline-with-tags (ast) + (org-element-map ast '(headline inlinetask) + (lambda (h) + (let ((title (org-element-property :raw-value h))) + (and (string-match-p "\\`:[[:alnum:]_@#%:]+:\\'" title) + (list (org-element-property :begin h) + (format "Headline containing only tags is ambiguous: %S" + title))))))) + + +;;; Reports UI + +(defvar org-lint--report-mode-map + (let ((map (make-sparse-keymap))) + (set-keymap-parent map tabulated-list-mode-map) + (define-key map (kbd "RET") 'org-lint--jump-to-source) + (define-key map (kbd "TAB") 'org-lint--show-source) + (define-key map (kbd "C-j") 'org-lint--show-source) + (define-key map (kbd "h") 'org-lint--hide-checker) + (define-key map (kbd "i") 'org-lint--ignore-checker) + map) + "Local keymap for `org-lint--report-mode' buffers.") + +(define-derived-mode org-lint--report-mode tabulated-list-mode "OrgLint" + "Major mode used to display reports emitted during linting. +\\{org-lint--report-mode-map}" + (setf tabulated-list-format + `[("Line" 6 + (lambda (a b) + (< (string-to-number (aref (cadr a) 0)) + (string-to-number (aref (cadr b) 0)))) + :right-align t) + ("Trust" 5 t) + ("Warning" 0 t)]) + (tabulated-list-init-header)) + +(defun org-lint--generate-reports (buffer checkers) + "Generate linting report for BUFFER. + +CHECKERS is the list of checkers used. + +Return an alist (ID [LINE TRUST DESCRIPTION CHECKER]), suitable +for `tabulated-list-printer'." + (with-current-buffer buffer + (save-excursion + (goto-char (point-min)) + (let ((ast (org-element-parse-buffer)) + (id 0) + (last-line 1) + (last-pos 1)) + ;; Insert unique ID for each report. Replace buffer positions + ;; with line numbers. + (mapcar + (lambda (report) + (list + (cl-incf id) + (apply #'vector + (cons + (progn + (goto-char (car report)) + (beginning-of-line) + (prog1 (number-to-string + (cl-incf last-line + (count-lines last-pos (point)))) + (setf last-pos (point)))) + (cdr report))))) + ;; Insert trust level in generated reports. Also sort them + ;; by buffer position in order to optimize lines computation. + (sort (cl-mapcan + (lambda (c) + (let ((trust (symbol-name (org-lint-checker-trust c)))) + (mapcar + (lambda (report) + (list (car report) trust (nth 1 report) c)) + (save-excursion + (funcall + (intern (format "org-lint-%s" + (org-lint-checker-name c))) + ast))))) + checkers) + #'car-less-than-car)))))) + +(defvar-local org-lint--source-buffer nil + "Source buffer associated to current report buffer.") + +(defvar-local org-lint--local-checkers nil + "List of checkers used to build current report.") + +(defun org-lint--refresh-reports () + (setq tabulated-list-entries + (org-lint--generate-reports org-lint--source-buffer + org-lint--local-checkers)) + (tabulated-list-print)) + +(defun org-lint--current-line () + "Return current report line, as a number." + (string-to-number (aref (tabulated-list-get-entry) 0))) + +(defun org-lint--current-checker (&optional entry) + "Return current report checker. +When optional argument ENTRY is non-nil, use this entry instead +of current one." + (aref (if entry (nth 1 entry) (tabulated-list-get-entry)) 3)) + +(defun org-lint--display-reports (source checkers) + "Display linting reports for buffer SOURCE. +CHECKERS is the list of checkers used." + (let ((buffer (get-buffer-create "*Org Lint*"))) + (with-current-buffer buffer + (org-lint--report-mode) + (setf org-lint--source-buffer source) + (setf org-lint--local-checkers checkers) + (org-lint--refresh-reports) + (tabulated-list-print) + (add-hook 'tabulated-list-revert-hook #'org-lint--refresh-reports nil t)) + (pop-to-buffer buffer))) + +(defun org-lint--jump-to-source () + "Move to source line that generated the report at point." + (interactive) + (let ((l (org-lint--current-line))) + (switch-to-buffer-other-window org-lint--source-buffer) + (org-goto-line l) + (org-show-set-visibility 'local) + (recenter))) + +(defun org-lint--show-source () + "Show source line that generated the report at point." + (interactive) + (let ((buffer (current-buffer))) + (org-lint--jump-to-source) + (switch-to-buffer-other-window buffer))) + +(defun org-lint--hide-checker () + "Hide all reports from checker that generated the report at point." + (interactive) + (let ((c (org-lint--current-checker))) + (setf tabulated-list-entries + (cl-remove-if (lambda (e) (equal c (org-lint--current-checker e))) + tabulated-list-entries)) + (tabulated-list-print))) + +(defun org-lint--ignore-checker () + "Ignore all reports from checker that generated the report at point. +Checker will also be ignored in all subsequent reports." + (interactive) + (setf org-lint--local-checkers + (remove (org-lint--current-checker) org-lint--local-checkers)) + (org-lint--hide-checker)) + + +;;; Public function + +;;;###autoload +(defun org-lint (&optional arg) + "Check current Org buffer for syntax mistakes. + +By default, run all checkers. With a `\\[universal-argument]' prefix ARG, \ +select one +category of checkers only. With a `\\[universal-argument] \ +\\[universal-argument]' prefix, run one precise +checker by its name. + +ARG can also be a list of checker names, as symbols, to run." + (interactive "P") + (unless (derived-mode-p 'org-mode) (user-error "Not in an Org buffer")) + (when (called-interactively-p 'any) + (message "Org linting process starting...")) + (let ((checkers + (pcase arg + (`nil org-lint--checkers) + (`(4) + (let ((category + (completing-read + "Checker category: " + (mapcar #'org-lint-checker-categories org-lint--checkers) + nil t))) + (cl-remove-if-not + (lambda (c) + (assoc-string (org-lint-checker-categories c) category)) + org-lint--checkers))) + (`(16) + (list + (let ((name (completing-read + "Checker name: " + (mapcar #'org-lint-checker-name org-lint--checkers) + nil t))) + (catch 'exit + (dolist (c org-lint--checkers) + (when (string= (org-lint-checker-name c) name) + (throw 'exit c))))))) + ((pred consp) + (cl-remove-if-not (lambda (c) (memq (org-lint-checker-name c) arg)) + org-lint--checkers)) + (_ (user-error "Invalid argument `%S' for `org-lint'" arg))))) + (if (not (called-interactively-p 'any)) + (org-lint--generate-reports (current-buffer) checkers) + (org-lint--display-reports (current-buffer) checkers) + (message "Org linting process completed")))) + + +(provide 'org-lint) +;;; org-lint.el ends here diff --git a/lisp/org/org-list.el b/lisp/org/org-list.el index a24c496d726..5b292d0ca46 100644 --- a/lisp/org/org-list.el +++ b/lisp/org/org-list.el @@ -1,4 +1,4 @@ -;;; org-list.el --- Plain lists for Org-mode +;;; org-list.el --- Plain lists for Org -*- lexical-binding: t; -*- ;; ;; Copyright (C) 2004-2017 Free Software Foundation, Inc. ;; @@ -20,12 +20,12 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Commentary: -;; This file contains the code dealing with plain lists in Org-mode. +;; This file contains the code dealing with plain lists in Org mode. ;; The core concept behind lists is their structure. A structure is ;; a snapshot of the list, in the shape of a data tree (see @@ -76,8 +76,7 @@ ;;; Code: -(eval-when-compile - (require 'cl)) +(require 'cl-lib) (require 'org-macs) (require 'org-compat) @@ -88,59 +87,84 @@ (defvar org-closed-string) (defvar org-deadline-string) (defvar org-description-max-indent) -(defvar org-drawers) +(defvar org-done-keywords) +(defvar org-drawer-regexp) +(defvar org-element-all-objects) +(defvar org-inhibit-startup) (defvar org-odd-levels-only) +(defvar org-outline-regexp-bol) (defvar org-scheduled-string) +(defvar org-todo-line-regexp) (defvar org-ts-regexp) (defvar org-ts-regexp-both) -(declare-function outline-invisible-p "outline" (&optional pos)) -(declare-function outline-flag-region "outline" (from to flag)) -(declare-function outline-next-heading "outline" ()) -(declare-function outline-previous-heading "outline" ()) - -(declare-function org-at-heading-p "org" (&optional ignored)) -(declare-function org-before-first-heading-p "org" ()) +(declare-function org-at-heading-p "org" (&optional invisible-ok)) (declare-function org-back-to-heading "org" (&optional invisible-ok)) +(declare-function org-before-first-heading-p "org" ()) (declare-function org-combine-plists "org" (&rest plists)) -(declare-function org-count "org" (cl-item cl-seq)) (declare-function org-current-level "org" ()) +(declare-function org-element-at-point "org-element" ()) +(declare-function org-element-context "org-element" (&optional element)) +(declare-function org-element-interpret-data "org-element" (data)) +(declare-function + org-element-lineage "org-element" (blob &optional types with-self)) +(declare-function org-element-macro-interpreter "org-element" (macro ##)) +(declare-function + org-element-map "org-element" + (data types fun &optional info first-match no-recursion with-affiliated)) +(declare-function org-element-normalize-string "org-element" (s)) +(declare-function org-element-parse-buffer "org-element" + (&optional granularity visible-only)) +(declare-function org-element-property "org-element" (property element)) +(declare-function org-element-put-property "org-element" + (element property value)) +(declare-function org-element-set-element "org-element" (old new)) +(declare-function org-element-type "org-element" (element)) +(declare-function org-element-update-syntax "org-element" ()) (declare-function org-entry-get "org" (pom property &optional inherit literal-nil)) +(declare-function org-export-create-backend "ox" (&rest rest) t) +(declare-function org-export-data-with-backend "ox" (data backend info)) +(declare-function org-export-get-backend "ox" (name)) +(declare-function org-export-get-environment "ox" + (&optional backend subtreep ext-plist)) +(declare-function org-export-get-next-element "ox" + (blob info &optional n)) +(declare-function org-export-with-backend "ox" + (backend data &optional contents info)) (declare-function org-fix-tags-on-the-fly "org" ()) (declare-function org-get-indentation "org" (&optional line)) -(declare-function org-icompleting-read "org" (&rest args)) +(declare-function org-get-todo-state "org" ()) (declare-function org-in-block-p "org" (names)) (declare-function org-in-regexp "org" (re &optional nlines visually)) +(declare-function org-inlinetask-goto-beginning "org-inlinetask" ()) +(declare-function org-inlinetask-goto-end "org-inlinetask" ()) +(declare-function org-inlinetask-in-task-p "org-inlinetask" ()) +(declare-function org-inlinetask-outline-regexp "org-inlinetask" ()) (declare-function org-level-increment "org" ()) (declare-function org-narrow-to-subtree "org" ()) -(declare-function org-at-heading-p "org" (&optional invisible-ok)) -(declare-function org-previous-line-empty-p "org" (&optional next)) -(declare-function org-remove-if "org" (predicate seq)) +(declare-function org-outline-level "org" ()) +(declare-function org-previous-line-empty-p "org" ()) (declare-function org-reduced-level "org" (L)) +(declare-function org-remove-indentation "org" (code &optional n)) (declare-function org-show-subtree "org" ()) (declare-function org-sort-remove-invisible "org" (S)) (declare-function org-time-string-to-seconds "org" (s)) (declare-function org-timer-hms-to-secs "org-timer" (hms)) (declare-function org-timer-item "org-timer" (&optional arg)) -(declare-function org-trim "org" (s)) +(declare-function org-trim "org" (s &optional keep-lead)) (declare-function org-uniquify "org" (list)) - -(declare-function org-inlinetask-goto-beginning "org-inlinetask" ()) -(declare-function org-inlinetask-goto-end "org-inlinetask" ()) -(declare-function org-inlinetask-in-task-p "org-inlinetask" ()) -(declare-function org-inlinetask-outline-regexp "org-inlinetask" ()) - -(declare-function org-export-string-as "ox" - (string backend &optional body-only ext-plist)) - +(declare-function org-invisible-p "org" (&optional pos)) +(declare-function outline-flag-region "outline" (from to flag)) +(declare-function outline-next-heading "outline" ()) +(declare-function outline-previous-heading "outline" ()) ;;; Configuration variables (defgroup org-plain-lists nil - "Options concerning plain lists in Org-mode." + "Options concerning plain lists in Org mode." :tag "Org Plain lists" :group 'org-structure) @@ -211,14 +235,20 @@ into (defcustom org-plain-list-ordered-item-terminator t "The character that makes a line with leading number an ordered list item. -Valid values are ?. and ?\). To get both terminators, use t." +Valid values are ?. and ?\). To get both terminators, use t. + +This variable needs to be set before org.el is loaded. If you +need to make a change while Emacs is running, use the customize +interface or run the following code after updating it: + + `\\[org-element-update-syntax]'" :group 'org-plain-lists :type '(choice (const :tag "dot like in \"2.\"" ?.) (const :tag "paren like in \"2)\"" ?\)) - (const :tag "both" t))) + (const :tag "both" t)) + :set (lambda (var val) (set var val) + (when (featurep 'org-element) (org-element-update-syntax)))) -(define-obsolete-variable-alias 'org-alphabetical-lists - 'org-list-allow-alphabetical "24.4") ; Since 8.0 (defcustom org-list-allow-alphabetical nil "Non-nil means single character alphabetical bullets are allowed. @@ -230,13 +260,12 @@ This variable needs to be set before org.el is loaded. If you need to make a change while Emacs is running, use the customize interface or run the following code after updating it: - (when (featurep \\='org-element) (load \"org-element\" t t))" + `\\[org-element-update-syntax]'" :group 'org-plain-lists :version "24.1" :type 'boolean - :set (lambda (var val) - (when (featurep 'org-element) (load "org-element" t t)) - (set var val))) + :set (lambda (var val) (set var val) + (when (featurep 'org-element) (org-element-update-syntax)))) (defcustom org-list-two-spaces-after-bullet-regexp nil "A regular expression matching bullets that should have 2 spaces after them. @@ -250,23 +279,22 @@ spaces instead of one after the bullet in each item of the list." (const :tag "never" nil) (regexp))) -(define-obsolete-variable-alias 'org-empty-line-terminates-plain-lists - 'org-list-empty-line-terminates-plain-lists "24.4") ;; Since 8.0 -(defcustom org-list-empty-line-terminates-plain-lists nil - "Non-nil means an empty line ends all plain list levels. -Otherwise, two of them will be necessary." - :group 'org-plain-lists - :type 'boolean) - (defcustom org-list-automatic-rules '((checkbox . t) (indent . t)) "Non-nil means apply set of rules when acting on lists. +\\<org-mode-map> By default, automatic actions are taken when using - \\[org-meta-return], \\[org-metaright], \\[org-metaleft], - \\[org-shiftmetaright], \\[org-shiftmetaleft], - \\[org-ctrl-c-minus], \\[org-toggle-checkbox] or - \\[org-insert-todo-heading]. You can disable individually these - rules by setting them to nil. Valid rules are: + `\\[org-meta-return]', + `\\[org-metaright]', + `\\[org-metaleft]', + `\\[org-shiftmetaright]', + `\\[org-shiftmetaleft]', + `\\[org-ctrl-c-minus]', + `\\[org-toggle-checkbox]', + `\\[org-insert-todo-heading]'. + +You can disable individually these rules by setting them to nil. +Valid rules are: checkbox when non-nil, checkbox statistics is updated each time you either insert a new checkbox or toggle a checkbox. @@ -286,13 +314,15 @@ indent when non-nil, indenting or outdenting list top-item (defcustom org-list-use-circular-motion nil "Non-nil means commands implying motion in lists should be cyclic. - +\\<org-mode-map> In that case, the item following the last item is the first one, and the item preceding the first item is the last one. -This affects the behavior of \\[org-move-item-up], - \\[org-move-item-down], \\[org-next-item] and - \\[org-previous-item]." +This affects the behavior of + `\\[org-move-item-up]', + `\\[org-move-item-down]', + `\\[org-next-item]', + `\\[org-previous-item]'." :group 'org-plain-lists :version "24.1" :type 'boolean) @@ -304,8 +334,6 @@ This hook runs even if checkbox rule in implement alternative ways of collecting statistics information.") -(define-obsolete-variable-alias 'org-hierarchical-checkbox-statistics - 'org-checkbox-hierarchical-statistics "24.4") ;; Since 8.0 (defcustom org-checkbox-hierarchical-statistics t "Non-nil means checkbox statistics counts only the state of direct children. When nil, all boxes below the cookie are counted. @@ -314,8 +342,6 @@ with the word \"recursive\" in the value." :group 'org-plain-lists :type 'boolean) -(org-defvaralias 'org-description-max-indent - 'org-list-description-max-indent) ;; Since 8.0 (defcustom org-list-description-max-indent 20 "Maximum indentation for the second line of a description list. When the indentation would be larger than this, it will become @@ -358,8 +384,7 @@ list, obtained by prompting the user." (list (symbol :tag "Major mode") (string :tag "Format")))) -(defvar org-list-forbidden-blocks '("example" "verse" "src" "ascii" "beamer" - "html" "latex" "odt") +(defvar org-list-forbidden-blocks '("example" "verse" "src" "export") "Names of blocks where lists are not allowed. Names must be in lower case.") @@ -374,10 +399,8 @@ specifically, type `block' is determined by the variable ;;; Predicates and regexps -(defconst org-list-end-re (if org-list-empty-line-terminates-plain-lists "^[ \t]*\n" - "^[ \t]*\n[ \t]*\n") - "Regex corresponding to the end of a list. -It depends on `org-list-empty-line-terminates-plain-lists'.") +(defconst org-list-end-re "^[ \t]*\n[ \t]*\n" + "Regex matching the end of a plain list.") (defconst org-list-full-item-re (concat "^[ \t]*\\(\\(?:[-+*]\\|\\(?:[0-9]+\\|[A-Za-z]\\)[.)]\\)\\(?:[ \t]+\\|$\\)\\)" @@ -430,9 +453,6 @@ group 4: description tag") (let* ((case-fold-search t) (context (org-list-context)) (lim-up (car context)) - (drawers-re (concat "^[ \t]*:\\(" - (mapconcat #'regexp-quote org-drawers "\\|") - "\\):[ \t]*$")) (inlinetask-re (and (featurep 'org-inlinetask) (org-inlinetask-outline-regexp))) (item-re (org-item-re)) @@ -476,7 +496,7 @@ group 4: description tag") ((and (looking-at "^[ \t]*#\\+end_") (re-search-backward "^[ \t]*#\\+begin_" lim-up t))) ((and (looking-at "^[ \t]*:END:") - (re-search-backward drawers-re lim-up t)) + (re-search-backward org-drawer-regexp lim-up t)) (beginning-of-line)) ((and inlinetask-re (looking-at inlinetask-re)) (org-inlinetask-goto-beginning) @@ -547,11 +567,7 @@ Contexts `block' and `invalid' refer to `org-list-forbidden-blocks'." (lim-down (or (save-excursion (outline-next-heading)) (point-max)))) ;; Is point inside a drawer? (let ((end-re "^[ \t]*:END:") - ;; Can't use org-drawers-regexp as this function might - ;; be called in buffers not in Org mode. - (beg-re (concat "^[ \t]*:\\(" - (mapconcat #'regexp-quote org-drawers "\\|") - "\\):[ \t]*$"))) + (beg-re org-drawer-regexp)) (when (save-excursion (and (not (looking-at beg-re)) (not (looking-at end-re)) @@ -635,9 +651,6 @@ Assume point is at an item." (lim-down (nth 1 context)) (text-min-ind 10000) (item-re (org-item-re)) - (drawers-re (concat "^[ \t]*:\\(" - (mapconcat #'regexp-quote org-drawers "\\|") - "\\):[ \t]*$")) (inlinetask-re (and (featurep 'org-inlinetask) (org-inlinetask-outline-regexp))) (beg-cell (cons (point) (org-get-indentation))) @@ -654,7 +667,7 @@ Assume point is at an item." (match-string-no-properties 2) ; counter (match-string-no-properties 3) ; checkbox ;; Description tag. - (and (save-match-data (string-match "[-+*]" bullet)) + (and (string-match-p "[-+*]" bullet) (match-string-no-properties 4))))))) (end-before-blank (function @@ -700,7 +713,7 @@ Assume point is at an item." ((and (looking-at "^[ \t]*#\\+end_") (re-search-backward "^[ \t]*#\\+begin_" lim-up t))) ((and (looking-at "^[ \t]*:END:") - (re-search-backward drawers-re lim-up t)) + (re-search-backward org-drawer-regexp lim-up t)) (beginning-of-line)) ((and inlinetask-re (looking-at inlinetask-re)) (org-inlinetask-goto-beginning) @@ -766,7 +779,7 @@ Assume point is at an item." (cond ((and (looking-at "^[ \t]*#\\+begin_") (re-search-forward "^[ \t]*#\\+end_" lim-down t))) - ((and (looking-at drawers-re) + ((and (looking-at org-drawer-regexp) (re-search-forward "^[ \t]*:END:" lim-down t)))) (forward-line 1)))))) (setq struct (append itm-lst (cdr (nreverse itm-lst-2))) @@ -1021,7 +1034,7 @@ Possible types are `descriptive', `ordered' and `unordered'. The type is determined by the first item of the list." (let ((first (org-list-get-list-begin item struct prevs))) (cond - ((string-match "[[:alnum:]]" (org-list-get-bullet first struct)) 'ordered) + ((string-match-p "[[:alnum:]]" (org-list-get-bullet first struct)) 'ordered) ((org-list-get-tag first struct) 'descriptive) (t 'unordered)))) @@ -1043,7 +1056,7 @@ that value." (let ((seq 0) (pos item) counter) (while (and (not (setq counter (org-list-get-counter pos struct))) (setq pos (org-list-get-prev-item pos struct prevs))) - (incf seq)) + (cl-incf seq)) (if (not counter) (1+ seq) (cond ((string-match "[A-Za-z]" counter) @@ -1137,13 +1150,20 @@ This function modifies STRUCT." ;; Store overlays responsible for visibility status. We ;; also need to store their boundaries as they will be ;; removed from buffer. - (overlays (cons - (mapcar (lambda (ov) - (list ov (overlay-start ov) (overlay-end ov))) - (overlays-in beg-A end-A)) - (mapcar (lambda (ov) - (list ov (overlay-start ov) (overlay-end ov))) - (overlays-in beg-B end-B))))) + (overlays + (cons + (delq nil + (mapcar (lambda (o) + (and (>= (overlay-start o) beg-A) + (<= (overlay-end o) end-A) + (list o (overlay-start o) (overlay-end o)))) + (overlays-in beg-A end-A))) + (delq nil + (mapcar (lambda (o) + (and (>= (overlay-start o) beg-B) + (<= (overlay-end o) end-B) + (list o (overlay-start o) (overlay-end o)))) + (overlays-in beg-B end-B)))))) ;; 1. Move effectively items in buffer. (goto-char beg-A) (delete-region beg-A end-B-no-blank) @@ -1154,42 +1174,39 @@ This function modifies STRUCT." ;; as empty spaces are not moved there. In others words, ;; item BEG-A will end with whitespaces that were at the end ;; of BEG-B and the same applies to BEG-B. - (mapc (lambda (e) - (let ((pos (car e))) - (cond - ((< pos beg-A)) - ((memq pos sub-A) - (let ((end-e (nth 6 e))) - (setcar e (+ pos (- end-B-no-blank end-A-no-blank))) - (setcar (nthcdr 6 e) - (+ end-e (- end-B-no-blank end-A-no-blank))) - (when (= end-e end-A) (setcar (nthcdr 6 e) end-B)))) - ((memq pos sub-B) - (let ((end-e (nth 6 e))) - (setcar e (- (+ pos beg-A) beg-B)) - (setcar (nthcdr 6 e) (+ end-e (- beg-A beg-B))) - (when (= end-e end-B) - (setcar (nthcdr 6 e) - (+ beg-A size-B (- end-A end-A-no-blank)))))) - ((< pos beg-B) - (let ((end-e (nth 6 e))) - (setcar e (+ pos (- size-B size-A))) - (setcar (nthcdr 6 e) (+ end-e (- size-B size-A)))))))) - struct) - (setq struct (sort struct (lambda (e1 e2) (< (car e1) (car e2))))) + (dolist (e struct) + (let ((pos (car e))) + (cond + ((< pos beg-A)) + ((memq pos sub-A) + (let ((end-e (nth 6 e))) + (setcar e (+ pos (- end-B-no-blank end-A-no-blank))) + (setcar (nthcdr 6 e) + (+ end-e (- end-B-no-blank end-A-no-blank))) + (when (= end-e end-A) (setcar (nthcdr 6 e) end-B)))) + ((memq pos sub-B) + (let ((end-e (nth 6 e))) + (setcar e (- (+ pos beg-A) beg-B)) + (setcar (nthcdr 6 e) (+ end-e (- beg-A beg-B))) + (when (= end-e end-B) + (setcar (nthcdr 6 e) + (+ beg-A size-B (- end-A end-A-no-blank)))))) + ((< pos beg-B) + (let ((end-e (nth 6 e))) + (setcar e (+ pos (- size-B size-A))) + (setcar (nthcdr 6 e) (+ end-e (- size-B size-A)))))))) + (setq struct (sort struct #'car-less-than-car)) ;; Restore visibility status, by moving overlays to their new ;; position. - (mapc (lambda (ov) - (move-overlay - (car ov) - (+ (nth 1 ov) (- (+ beg-B (- size-B size-A)) beg-A)) - (+ (nth 2 ov) (- (+ beg-B (- size-B size-A)) beg-A)))) - (car overlays)) - (mapc (lambda (ov) - (move-overlay (car ov) - (+ (nth 1 ov) (- beg-A beg-B)) - (+ (nth 2 ov) (- beg-A beg-B)))) - (cdr overlays)) + (dolist (ov (car overlays)) + (move-overlay + (car ov) + (+ (nth 1 ov) (- (+ beg-B (- size-B size-A)) beg-A)) + (+ (nth 2 ov) (- (+ beg-B (- size-B size-A)) beg-A)))) + (dolist (ov (cdr overlays)) + (move-overlay (car ov) + (+ (nth 1 ov) (- beg-A beg-B)) + (+ (nth 2 ov) (- beg-A beg-B)))) ;; Return structure. struct))) @@ -1219,7 +1236,7 @@ some heuristics to guess the result." (point)))))))) (cond ;; Trivial cases where there should be none. - ((or org-list-empty-line-terminates-plain-lists (not insert-blank-p)) 0) + ((not insert-blank-p) 0) ;; When `org-blank-before-new-entry' says so, it is 1. ((eq insert-blank-p t) 1) ;; `plain-list-item' is 'auto. Count blank lines separating @@ -1272,12 +1289,16 @@ This function modifies STRUCT." (beforep (progn (looking-at org-list-full-item-re) - ;; Do not count tag in a non-descriptive list. - (<= pos (if (and (match-beginning 4) - (save-match-data - (string-match "[.)]" (match-string 1)))) - (match-beginning 4) - (match-end 0))))) + (<= pos + (cond + ((not (match-beginning 4)) (match-end 0)) + ;; Ignore tag in a non-descriptive list. + ((save-match-data (string-match "[.)]" (match-string 1))) + (match-beginning 4)) + (t (save-excursion + (goto-char (match-end 4)) + (skip-chars-forward " \t") + (point))))))) (split-line-p (org-get-alist-option org-M-RET-may-split-line 'item)) (blank-nb (org-list-separating-blank-lines-number pos struct prevs)) @@ -1317,7 +1338,7 @@ This function modifies STRUCT." (size-offset (- item-size (length text-cut)))) ;; 4. Insert effectively item into buffer. (goto-char item) - (org-indent-to-column ind) + (indent-to-column ind) (insert body item-sep) ;; 5. Add new item to STRUCT. (mapc (lambda (e) @@ -1459,7 +1480,7 @@ This function returns, destructively, the new list structure." (save-excursion (goto-char (org-list-get-last-item item struct prevs)) (point-at-eol))) - ((string-match "\\`[0-9]+\\'" dest) + ((string-match-p "\\`[0-9]+\\'" dest) (let* ((all (org-list-get-all-items item struct prevs)) (len (length all)) (index (mod (string-to-number dest) len))) @@ -1473,8 +1494,10 @@ This function returns, destructively, the new list structure." (point-at-eol))))) (t dest))) (org-M-RET-may-split-line nil) - ;; Store visibility. - (visibility (overlays-in item item-end))) + ;; Store inner overlays (to preserve visibility). + (overlays (cl-remove-if (lambda (o) (or (< (overlay-start o) item) + (> (overlay-end o) item))) + (overlays-in item item-end)))) (cond ((eq dest 'delete) (org-list-delete-item item struct)) ((eq dest 'kill) @@ -1509,13 +1532,12 @@ This function returns, destructively, the new list structure." new-end (+ end shift))))))) moved-items)) - (lambda (e1 e2) (< (car e1) (car e2)))))) - ;; 2. Restore visibility. - (mapc (lambda (ov) - (move-overlay ov - (+ (overlay-start ov) (- (point) item)) - (+ (overlay-end ov) (- (point) item)))) - visibility) + #'car-less-than-car))) + ;; 2. Restore inner overlays. + (dolist (o overlays) + (move-overlay o + (+ (overlay-start o) (- (point) item)) + (+ (overlay-end o) (- (point) item)))) ;; 3. Eventually delete extra copy of the item and clean marker. (prog1 (org-list-delete-item (marker-position item) struct) (move-marker item nil))) @@ -1632,7 +1654,7 @@ as returned by `org-list-prevs-alist'." (while item (let ((count (org-list-get-counter item struct))) ;; Virtually determine current bullet - (if (and count (string-match "[a-zA-Z]" count)) + (if (and count (string-match-p "[a-zA-Z]" count)) ;; Counters are not case-sensitive. (setq ascii (string-to-char (upcase count))) (setq ascii (1+ ascii))) @@ -1861,10 +1883,9 @@ Initial position of cursor is restored after the changes." (item-re (org-item-re)) (shift-body-ind (function - ;; Shift the indentation between END and BEG by DELTA. If - ;; MAX-IND is non-nil, ensure that no line will be indented - ;; more than that number. Start from the line before END. - (lambda (end beg delta max-ind) + ;; Shift the indentation between END and BEG by DELTA. + ;; Start from the line before END. + (lambda (end beg delta) (goto-char end) (skip-chars-backward " \r\t\n") (beginning-of-line) @@ -1876,10 +1897,8 @@ Initial position of cursor is restored after the changes." ((and inlinetask-re (looking-at inlinetask-re)) (org-inlinetask-goto-beginning)) ;; Shift only non-empty lines. - ((org-looking-at-p "^[ \t]*\\S-") - (let ((i (org-get-indentation))) - (org-indent-line-to - (if max-ind (min (+ i delta) max-ind) (+ i delta)))))) + ((looking-at-p "^[ \t]*\\S-") + (indent-line-to (+ (org-get-indentation) delta)))) (forward-line -1))))) (modify-item (function @@ -1934,37 +1953,53 @@ Initial position of cursor is restored after the changes." ;; belongs to: it is the last item (ITEM-UP), whose ;; ending is further than the position we're ;; interested in. - (let ((item-up (assoc-default end-pos acc-end '>))) + (let ((item-up (assoc-default end-pos acc-end #'>))) (push (cons end-pos item-up) end-list))) (push (cons end-pos pos) acc-end))) ;; 2. Slice the items into parts that should be shifted by the ;; same amount of indentation. Each slice follow the pattern - ;; (END BEG DELTA MAX-IND-OR-NIL). Slices are returned in - ;; reverse order. + ;; (END BEG DELTA). Slices are returned in reverse order. (setq all-ends (sort (append (mapcar #'car itm-shift) (org-uniquify (mapcar #'car end-list))) - '<)) + #'<) + acc-end (nreverse acc-end)) (while (cdr all-ends) (let* ((up (pop all-ends)) (down (car all-ends)) (itemp (assq up struct)) - (item (if itemp up (cdr (assq up end-list)))) - (ind (cdr (assq item itm-shift))) - ;; If we're not at an item, there's a child of the item - ;; point belongs to above. Make sure this slice isn't - ;; moved within that child by specifying a maximum - ;; indentation. - (max-ind (and (not itemp) - (+ (org-list-get-ind item struct) - (length (org-list-get-bullet item struct)) - org-list-indent-offset)))) - (push (list down up ind max-ind) sliced-struct))) + (delta + (if itemp (cdr (assq up itm-shift)) + ;; If we're not at an item, there's a child of the + ;; item point belongs to above. Make sure the less + ;; indented line in this slice has the same column + ;; as that child. + (let* ((child (cdr (assq up acc-end))) + (ind (org-list-get-ind child struct)) + (min-ind most-positive-fixnum)) + (save-excursion + (goto-char up) + (while (< (point) down) + ;; Ignore empty lines. Also ignore blocks and + ;; drawers contents. + (unless (looking-at-p "[ \t]*$") + (setq min-ind (min (org-get-indentation) min-ind)) + (cond + ((and (looking-at "#\\+BEGIN\\(:\\|_\\S-+\\)") + (re-search-forward + (format "^[ \t]*#\\+END%s[ \t]*$" + (match-string 1)) + down t))) + ((and (looking-at org-drawer-regexp) + (re-search-forward "^[ \t]*:END:[ \t]*$" + down t))))) + (forward-line))) + (- ind min-ind))))) + (push (list down up delta) sliced-struct))) ;; 3. Shift each slice in buffer, provided delta isn't 0, from ;; end to beginning. Take a special action when beginning is ;; at item bullet. (dolist (e sliced-struct) - (unless (and (zerop (nth 2 e)) (not (nth 3 e))) - (apply shift-body-ind e)) + (unless (zerop (nth 2 e)) (apply shift-body-ind e)) (let* ((beg (nth 1 e)) (cell (assq beg struct))) (unless (or (not cell) (equal cell (assq beg old-struct))) @@ -2060,16 +2095,27 @@ Possible values are: `folded', `children' or `subtree'. See (defun org-list-item-body-column (item) "Return column at which body of ITEM should start." - (let (bpos bcol tpos tcol) - (save-excursion - (goto-char item) - (looking-at "[ \t]*\\(\\S-+\\)\\(.*[ \t]+::\\)?\\([ \t]+\\|$\\)") - (setq bpos (match-beginning 1) tpos (match-end 0) - bcol (progn (goto-char bpos) (current-column)) - tcol (progn (goto-char tpos) (current-column))) - (when (> tcol (+ bcol org-description-max-indent)) - (setq tcol (+ bcol 5)))) - tcol)) + (save-excursion + (goto-char item) + (if (save-excursion + (end-of-line) + (re-search-backward + "[ \t]::\\([ \t]\\|$\\)" (line-beginning-position) t)) + ;; Descriptive list item. Body starts after item's tag, if + ;; possible. + (let ((start (1+ (- (match-beginning 1) (line-beginning-position)))) + (ind (org-get-indentation))) + (if (> start (+ ind org-list-description-max-indent)) + (+ ind 5) + start)) + ;; Regular item. Body starts after bullet. + (looking-at "[ \t]*\\(\\S-+\\)") + (+ (progn (goto-char (match-end 1)) (current-column)) + (if (and org-list-two-spaces-after-bullet-regexp + (string-match-p org-list-two-spaces-after-bullet-regexp + (match-string 1))) + 2 + 1))))) @@ -2204,13 +2250,14 @@ If CHECKBOX is non-nil, add a checkbox next to the bullet. Return t when things worked, nil when we are not in an item, or item is invisible." + (interactive "P") (let ((itemp (org-in-item-p)) (pos (point))) ;; If cursor isn't is a list or if list is invisible, return nil. (unless (or (not itemp) (save-excursion (goto-char itemp) - (outline-invisible-p))) + (org-invisible-p))) (if (save-excursion (goto-char itemp) (org-at-item-timer-p)) @@ -2325,9 +2372,6 @@ in subtree, ignoring drawers." block-item lim-up lim-down - (drawer-re (concat "^[ \t]*:\\(" - (mapconcat #'regexp-quote org-drawers "\\|") - "\\):[ \t]*$")) (keyword-re (concat "^[ \t]*\\<\\(" org-scheduled-string "\\|" org-deadline-string "\\|" org-closed-string @@ -2349,7 +2393,8 @@ in subtree, ignoring drawers." ;; time-stamps (scheduled, etc.). (let ((limit (save-excursion (outline-next-heading) (point)))) (forward-line 1) - (while (or (looking-at drawer-re) (looking-at keyword-re)) + (while (or (looking-at org-drawer-regexp) + (looking-at keyword-re)) (if (looking-at keyword-re) (forward-line 1) (re-search-forward "^[ \t]*:END:" limit nil))) @@ -2388,7 +2433,7 @@ in subtree, ignoring drawers." (parents (org-list-parents-alist struct)) (prevs (org-list-prevs-alist struct)) (bottom (copy-marker (org-list-get-bottom-point struct))) - (items-to-toggle (org-remove-if + (items-to-toggle (cl-remove-if (lambda (e) (or (< e lim-up) (> e lim-down))) (mapcar #'car struct)))) (mapc (lambda (e) (org-list-set-checkbox @@ -2439,130 +2484,129 @@ in subtree, ignoring drawers." (defun org-update-checkbox-count (&optional all) "Update the checkbox statistics in the current section. + This will find all statistic cookies like [57%] and [6/12] and update them with the current numbers. With optional prefix argument ALL, do this for the whole buffer." (interactive "P") - (save-excursion - (let ((cookie-re "\\(\\(\\[[0-9]*%\\]\\)\\|\\(\\[[0-9]*/[0-9]*\\]\\)\\)") - (box-re "^[ \t]*\\([-+*]\\|\\([0-9]+\\|[A-Za-z]\\)[.)]\\)[ \t]+\\(?:\\[@\\(?:start:\\)?\\([0-9]+\\|[A-Za-z]\\)\\][ \t]*\\)?\\(\\[[- X]\\]\\)") + (org-with-wide-buffer + (let* ((cookie-re "\\(\\(\\[[0-9]*%\\]\\)\\|\\(\\[[0-9]*/[0-9]*\\]\\)\\)") + (box-re "^[ \t]*\\([-+*]\\|\\([0-9]+\\|[A-Za-z]\\)[.)]\\)[ \t]+\ +\\(?:\\[@\\(?:start:\\)?\\([0-9]+\\|[A-Za-z]\\)\\][ \t]*\\)?\\(\\[[- X]\\]\\)") (recursivep (or (not org-checkbox-hierarchical-statistics) (string-match "\\<recursive\\>" (or (org-entry-get nil "COOKIE_DATA") "")))) - (bounds (if all - (cons (point-min) (point-max)) - (cons (or (ignore-errors (org-back-to-heading t) (point)) - (point-min)) - (save-excursion (outline-next-heading) (point))))) + (within-inlinetask (and (not all) + (featurep 'org-inlinetask) + (org-inlinetask-in-task-p))) + (end (cond (all (point-max)) + (within-inlinetask + (save-excursion (outline-next-heading) (point))) + (t (save-excursion + (org-with-limited-levels (outline-next-heading)) + (point))))) (count-boxes - (function - ;; Return number of checked boxes and boxes of all types - ;; in all structures in STRUCTS. If RECURSIVEP is - ;; non-nil, also count boxes in sub-lists. If ITEM is - ;; nil, count across the whole structure, else count only - ;; across subtree whose ancestor is ITEM. - (lambda (item structs recursivep) - (let ((c-on 0) (c-all 0)) - (mapc - (lambda (s) - (let* ((pre (org-list-prevs-alist s)) - (par (org-list-parents-alist s)) - (items - (cond - ((and recursivep item) (org-list-get-subtree item s)) - (recursivep (mapcar #'car s)) - (item (org-list-get-children item s par)) - (t (org-list-get-all-items - (org-list-get-top-point s) s pre)))) - (cookies (delq nil (mapcar - (lambda (e) - (org-list-get-checkbox e s)) - items)))) - (setq c-all (+ (length cookies) c-all) - c-on (+ (org-count "[X]" cookies) c-on)))) - structs) - (cons c-on c-all))))) - (backup-end 1) - cookies-list structs-bak) - (goto-char (car bounds)) - ;; 1. Build an alist for each cookie found within BOUNDS. The - ;; key will be position at beginning of cookie and values - ;; ending position, format of cookie, and a cell whose car is - ;; number of checked boxes to report, and cdr total number of - ;; boxes. - (while (re-search-forward cookie-re (cdr bounds) t) - (catch 'skip - (save-excursion - (push - (list - (match-beginning 1) ; cookie start - (match-end 1) ; cookie end - (match-string 2) ; percent? - (cond ; boxes count - ;; Cookie is at an heading, but specifically for todo, - ;; not for checkboxes: skip it. - ((and (org-at-heading-p) - (string-match "\\<todo\\>" - (downcase - (or (org-entry-get nil "COOKIE_DATA") "")))) - (throw 'skip nil)) - ;; Cookie is at an heading, but all lists before next - ;; heading already have been read. Use data collected - ;; in STRUCTS-BAK. This should only happen when - ;; heading has more than one cookie on it. - ((and (org-at-heading-p) - (<= (save-excursion (outline-next-heading) (point)) - backup-end)) - (funcall count-boxes nil structs-bak recursivep)) - ;; Cookie is at a fresh heading. Grab structure of - ;; every list containing a checkbox between point and - ;; next headline, and save them in STRUCTS-BAK. - ((org-at-heading-p) - (setq backup-end (save-excursion - (outline-next-heading) (point)) - structs-bak nil) - (while (org-list-search-forward box-re backup-end 'move) - (let* ((struct (org-list-struct)) - (bottom (org-list-get-bottom-point struct))) - (push struct structs-bak) - (goto-char bottom))) - (funcall count-boxes nil structs-bak recursivep)) - ;; Cookie is at an item, and we already have list - ;; structure stored in STRUCTS-BAK. - ((and (org-at-item-p) - (< (point-at-bol) backup-end) - ;; Only lists in no special context are stored. - (not (nth 2 (org-list-context)))) - (funcall count-boxes (point-at-bol) structs-bak recursivep)) - ;; Cookie is at an item, but we need to compute list - ;; structure. - ((org-at-item-p) - (let ((struct (org-list-struct))) - (setq backup-end (org-list-get-bottom-point struct) - structs-bak (list struct))) - (funcall count-boxes (point-at-bol) structs-bak recursivep)) - ;; Else, cookie found is at a wrong place. Skip it. - (t (throw 'skip nil)))) - cookies-list)))) - ;; 2. Apply alist to buffer, in reverse order so positions stay - ;; unchanged after cookie modifications. - (mapc (lambda (cookie) - (let* ((beg (car cookie)) - (end (nth 1 cookie)) - (percentp (nth 2 cookie)) - (checked (car (nth 3 cookie))) - (total (cdr (nth 3 cookie))) - (new (if percentp - (format "[%d%%]" (floor (* 100.0 checked) - (max 1 total))) - (format "[%d/%d]" checked total)))) - (goto-char beg) - (insert new) - (delete-region (point) (+ (point) (- end beg))) - (when org-auto-align-tags (org-fix-tags-on-the-fly)))) + (lambda (item structs recursivep) + ;; Return number of checked boxes and boxes of all types + ;; in all structures in STRUCTS. If RECURSIVEP is + ;; non-nil, also count boxes in sub-lists. If ITEM is + ;; nil, count across the whole structure, else count only + ;; across subtree whose ancestor is ITEM. + (let ((c-on 0) (c-all 0)) + (dolist (s structs (list c-on c-all)) + (let* ((pre (org-list-prevs-alist s)) + (par (org-list-parents-alist s)) + (items + (cond + ((and recursivep item) (org-list-get-subtree item s)) + (recursivep (mapcar #'car s)) + (item (org-list-get-children item s par)) + (t (org-list-get-all-items + (org-list-get-top-point s) s pre)))) + (cookies (delq nil (mapcar + (lambda (e) + (org-list-get-checkbox e s)) + items)))) + (cl-incf c-all (length cookies)) + (cl-incf c-on (cl-count "[X]" cookies :test #'equal))))))) + cookies-list cache) + ;; Move to start. + (cond (all (goto-char (point-min))) + (within-inlinetask (org-back-to-heading t)) + (t (org-with-limited-levels (outline-previous-heading)))) + ;; Build an alist for each cookie found. The key is the position + ;; at beginning of cookie and values ending position, format of + ;; cookie, number of checked boxes to report and total number of + ;; boxes. + (while (re-search-forward cookie-re end t) + (let ((context (save-excursion (backward-char) + (save-match-data (org-element-context))))) + (when (eq (org-element-type context) 'statistics-cookie) + (push + (append + (list (match-beginning 1) (match-end 1) (match-end 2)) + (let* ((container + (org-element-lineage + context + '(drawer center-block dynamic-block inlinetask item + quote-block special-block verse-block))) + (beg (if container + (org-element-property :contents-begin container) + (save-excursion + (org-with-limited-levels + (outline-previous-heading)) + (point))))) + (or (cdr (assq beg cache)) + (save-excursion + (goto-char beg) + (let ((end + (if container + (org-element-property :contents-end container) + (save-excursion + (org-with-limited-levels (outline-next-heading)) + (point)))) + structs) + (while (re-search-forward box-re end t) + (let ((element (org-element-at-point))) + (when (eq (org-element-type element) 'item) + (push (org-element-property :structure element) + structs) + ;; Skip whole list since we have its + ;; structure anyway. + (while (setq element (org-element-lineage + element '(plain-list))) + (goto-char + (min (org-element-property :end element) + end)))))) + ;; Cache count for cookies applying to the same + ;; area. Then return it. + (let ((count + (funcall count-boxes + (and (eq (org-element-type container) + 'item) + (org-element-property + :begin container)) + structs + recursivep))) + (push (cons beg count) cache) + count)))))) cookies-list)))) + ;; Apply alist to buffer. + (dolist (cookie cookies-list) + (let* ((beg (car cookie)) + (end (nth 1 cookie)) + (percent (nth 2 cookie)) + (checked (nth 3 cookie)) + (total (nth 4 cookie))) + (goto-char beg) + (insert + (if percent (format "[%d%%]" (floor (* 100.0 checked) + (max 1 total))) + (format "[%d/%d]" checked total))) + (delete-region (point) (+ (point) (- end beg))) + (when org-auto-align-tags (org-fix-tags-on-the-fly))))))) (defun org-get-checkbox-statistics-face () "Select the face for checkbox statistics. @@ -2664,7 +2708,7 @@ Return t if successful." ;; of the subtree mustn't have a child. (let ((last-item (caar (reverse - (org-remove-if + (cl-remove-if (lambda (e) (>= (car e) end)) struct))))) (org-list-has-child-p last-item struct)))) @@ -2781,7 +2825,7 @@ Return t at each successful move." ((and (= ind (car org-tab-ind-state)) (ignore-errors (org-list-indent-item-generic 1 t struct)))) (t (delete-region (point-at-bol) (point-at-eol)) - (org-indent-to-column (car org-tab-ind-state)) + (indent-to-column (car org-tab-ind-state)) (insert (cdr org-tab-ind-state) " ") ;; Break cycle (setq this-command 'identity))) @@ -2794,7 +2838,8 @@ Return t at each successful move." (t (user-error "Cannot move item")))) t)))) -(defun org-sort-list (&optional with-case sorting-type getkey-func compare-func) +(defun org-sort-list + (&optional with-case sorting-type getkey-func compare-func interactive?) "Sort list items. The cursor may be at any item of the list that should be sorted. Sublists are not sorted. Checkboxes, if any, are ignored. @@ -2820,13 +2865,15 @@ Capital letters will reverse the sort order. If the SORTING-TYPE is ?f or ?F, then GETKEY-FUNC specifies a function to be called with point at the beginning of the -record. It must return either a string or a number that should -serve as the sorting key for that record. It will then use -COMPARE-FUNC to compare entries. +record. It must return a value that is compatible with COMPARE-FUNC, +the function used to compare entries. Sorting is done against the visible part of the headlines, it -ignores hidden links." - (interactive "P") +ignores hidden links. + +A non-nil value for INTERACTIVE? is used to signal that this +function is being called interactively." + (interactive (list current-prefix-arg nil nil nil t)) (let* ((case-func (if with-case 'identity 'downcase)) (struct (org-list-struct)) (prevs (org-list-prevs-alist struct)) @@ -2838,23 +2885,31 @@ ignores hidden links." (message "Sort plain list: [a]lpha [n]umeric [t]ime [f]unc [x]checked A/N/T/F/X means reversed:") (read-char-exclusive)))) + (dcst (downcase sorting-type)) (getkey-func - (or getkey-func - (and (= (downcase sorting-type) ?f) - (intern (org-icompleting-read "Sort using function: " - obarray 'fboundp t nil nil)))))) + (and (= dcst ?f) + (or getkey-func + (and interactive? + (org-read-function "Function for extracting keys: ")) + (error "Missing key extractor")))) + (sort-func + (cond + ((= dcst ?a) #'string<) + ((= dcst ?f) + (or compare-func + (and interactive? + (org-read-function + (concat "Function for comparing keys " + "(empty for default `sort-subr' predicate): ") + 'allow-empty)))) + ((= dcst ?t) #'<) + ((= dcst ?x) #'string<)))) (message "Sorting items...") (save-restriction (narrow-to-region start end) (goto-char (point-min)) - (let* ((dcst (downcase sorting-type)) - (case-fold-search nil) + (let* ((case-fold-search nil) (now (current-time)) - (sort-func (cond - ((= dcst ?a) 'string<) - ((= dcst ?f) compare-func) - ((= dcst ?t) '<) - ((= dcst ?x) 'string<))) (next-record (lambda () (skip-chars-forward " \r\t\n") (or (eobp) (beginning-of-line)))) @@ -2908,128 +2963,249 @@ ignores hidden links." (run-hooks 'org-after-sorting-entries-or-items-hook) (message "Sorting items...done"))))) +(defun org-toggle-item (arg) + "Convert headings or normal lines to items, items to normal lines. +If there is no active region, only the current line is considered. + +If the first non blank line in the region is a headline, convert +all headlines to items, shifting text accordingly. + +If it is an item, convert all items to normal lines. + +If it is normal text, change region into a list of items. +With a prefix argument ARG, change the region in a single item." + (interactive "P") + (let ((shift-text + (lambda (ind end) + ;; Shift text in current section to IND, from point to END. + ;; The function leaves point to END line. + (let ((min-i 1000) (end (copy-marker end))) + ;; First determine the minimum indentation (MIN-I) of + ;; the text. + (save-excursion + (catch 'exit + (while (< (point) end) + (let ((i (org-get-indentation))) + (cond + ;; Skip blank lines and inline tasks. + ((looking-at "^[ \t]*$")) + ((looking-at org-outline-regexp-bol)) + ;; We can't find less than 0 indentation. + ((zerop i) (throw 'exit (setq min-i 0))) + ((< i min-i) (setq min-i i)))) + (forward-line)))) + ;; Then indent each line so that a line indented to + ;; MIN-I becomes indented to IND. Ignore blank lines + ;; and inline tasks in the process. + (let ((delta (- ind min-i))) + (while (< (point) end) + (unless (or (looking-at "^[ \t]*$") + (looking-at org-outline-regexp-bol)) + (indent-line-to (+ (org-get-indentation) delta))) + (forward-line)))))) + (skip-blanks + (lambda (pos) + ;; Return beginning of first non-blank line, starting from + ;; line at POS. + (save-excursion + (goto-char pos) + (skip-chars-forward " \r\t\n") + (point-at-bol)))) + beg end) + ;; Determine boundaries of changes. + (if (org-region-active-p) + (setq beg (funcall skip-blanks (region-beginning)) + end (copy-marker (region-end))) + (setq beg (funcall skip-blanks (point-at-bol)) + end (copy-marker (point-at-eol)))) + ;; Depending on the starting line, choose an action on the text + ;; between BEG and END. + (org-with-limited-levels + (save-excursion + (goto-char beg) + (cond + ;; Case 1. Start at an item: de-itemize. Note that it only + ;; happens when a region is active: `org-ctrl-c-minus' + ;; would call `org-cycle-list-bullet' otherwise. + ((org-at-item-p) + (while (< (point) end) + (when (org-at-item-p) + (skip-chars-forward " \t") + (delete-region (point) (match-end 0))) + (forward-line))) + ;; Case 2. Start at an heading: convert to items. + ((org-at-heading-p) + (let* ((bul (org-list-bullet-string "-")) + (bul-len (length bul)) + ;; Indentation of the first heading. It should be + ;; relative to the indentation of its parent, if any. + (start-ind (save-excursion + (cond + ((not org-adapt-indentation) 0) + ((not (outline-previous-heading)) 0) + (t (length (match-string 0)))))) + ;; Level of first heading. Further headings will be + ;; compared to it to determine hierarchy in the list. + (ref-level (org-reduced-level (org-outline-level)))) + (while (< (point) end) + (let* ((level (org-reduced-level (org-outline-level))) + (delta (max 0 (- level ref-level))) + (todo-state (org-get-todo-state))) + ;; If current headline is less indented than the first + ;; one, set it as reference, in order to preserve + ;; subtrees. + (when (< level ref-level) (setq ref-level level)) + ;; Remove stars and TODO keyword. + (let ((case-fold-search nil)) (looking-at org-todo-line-regexp)) + (delete-region (point) (or (match-beginning 3) + (line-end-position))) + (insert bul) + (indent-line-to (+ start-ind (* delta bul-len))) + ;; Turn TODO keyword into a check box. + (when todo-state + (let* ((struct (org-list-struct)) + (old (copy-tree struct))) + (org-list-set-checkbox + (line-beginning-position) + struct + (if (member todo-state org-done-keywords) + "[X]" + "[ ]")) + (org-list-write-struct struct + (org-list-parents-alist struct) + old))) + ;; Ensure all text down to END (or SECTION-END) belongs + ;; to the newly created item. + (let ((section-end (save-excursion + (or (outline-next-heading) (point))))) + (forward-line) + (funcall shift-text + (+ start-ind (* (1+ delta) bul-len)) + (min end section-end))))))) + ;; Case 3. Normal line with ARG: make the first line of region + ;; an item, and shift indentation of others lines to + ;; set them as item's body. + (arg (let* ((bul (org-list-bullet-string "-")) + (bul-len (length bul)) + (ref-ind (org-get-indentation))) + (skip-chars-forward " \t") + (insert bul) + (forward-line) + (while (< (point) end) + ;; Ensure that lines less indented than first one + ;; still get included in item body. + (funcall shift-text + (+ ref-ind bul-len) + (min end (save-excursion (or (outline-next-heading) + (point))))) + (forward-line)))) + ;; Case 4. Normal line without ARG: turn each non-item line + ;; into an item. + (t + (while (< (point) end) + (unless (or (org-at-heading-p) (org-at-item-p)) + (when (looking-at "\\([ \t]*\\)\\(\\S-\\)") + (replace-match + (concat "\\1" (org-list-bullet-string "-") "\\2")))) + (forward-line)))))))) ;;; Send and receive lists -(defun org-list-parse-list (&optional delete) +(defun org-list-to-lisp (&optional delete) "Parse the list at point and maybe DELETE it. Return a list whose car is a symbol of list type, among `ordered', `unordered' and `descriptive'. Then, each item is -a list whose car is counter, and cdr are strings and other -sub-lists. Inside strings, check-boxes are replaced by -\"[CBON]\", \"[CBOFF]\" and \"[CBTRANS]\". +a list of strings and other sub-lists. For example, the following list: -1. first item - + sub-item one - + [X] sub-item two - more text in first item -2. [@3] last item + 1. first item + + sub-item one + + [X] sub-item two + more text in first item + 2. [@3] last item -will be parsed as: +is parsed as (ordered - (nil \"first item\" - (unordered - (nil \"sub-item one\") - (nil \"[CBON] sub-item two\")) - \"more text in first item\") - (3 \"last item\")) - -Point is left at list end." - (defvar parse-item) ;FIXME: Or use `cl-labels' or `letrec'. - (let* ((struct (org-list-struct)) - (prevs (org-list-prevs-alist struct)) - (parents (org-list-parents-alist struct)) - (top (org-list-get-top-point struct)) - (bottom (org-list-get-bottom-point struct)) - out - (get-text - (function - ;; Return text between BEG and END, trimmed, with - ;; checkboxes replaced. - (lambda (beg end) - (let ((text (org-trim (buffer-substring beg end)))) - (if (string-match "\\`\\[\\([-X ]\\)\\]" text) - (replace-match - (let ((box (match-string 1 text))) - (cond - ((equal box " ") "CBOFF") - ((equal box "-") "CBTRANS") - (t "CBON"))) - t nil text 1) - text))))) - (parse-sublist - (function - ;; Return a list whose car is list type and cdr a list of - ;; items' body. - (lambda (e) - (cons (org-list-get-list-type (car e) struct prevs) - (mapcar parse-item e))))) - (parse-item - (function - ;; Return a list containing counter of item, if any, text - ;; and any sublist inside it. - (lambda (e) - (let ((start (save-excursion - (goto-char e) - (looking-at "[ \t]*\\S-+\\([ \t]+\\[@\\(start:\\)?\\([0-9]+\\|[a-zA-Z]\\)\\]\\)?[ \t]*") - (match-end 0))) - ;; Get counter number. For alphabetic counter, get - ;; its position in the alphabet. - (counter (let ((c (org-list-get-counter e struct))) - (cond - ((not c) nil) - ((string-match "[A-Za-z]" c) - (- (string-to-char (upcase (match-string 0 c))) - 64)) - ((string-match "[0-9]+" c) - (string-to-number (match-string 0 c)))))) - (childp (org-list-has-child-p e struct)) - (end (org-list-get-item-end e struct))) - ;; If item has a child, store text between bullet and - ;; next child, then recursively parse all sublists. At - ;; the end of each sublist, check for the presence of - ;; text belonging to the original item. - (if childp - (let* ((children (org-list-get-children e struct parents)) - (body (list (funcall get-text start childp)))) - (while children - (let* ((first (car children)) - (sub (org-list-get-all-items first struct prevs)) - (last-c (car (last sub))) - (last-end (org-list-get-item-end last-c struct))) - (push (funcall parse-sublist sub) body) - ;; Remove children from the list just parsed. - (setq children (cdr (member last-c children))) - ;; There is a chunk of text belonging to the - ;; item if last child doesn't end where next - ;; child starts or where item ends. - (unless (= (or (car children) end) last-end) - (push (funcall get-text - last-end (or (car children) end)) - body)))) - (cons counter (nreverse body))) - (list counter (funcall get-text start end)))))))) + (\"first item\" + (unordered + (\"sub-item one\") + (\"[X] sub-item two\")) + \"more text in first item\") + (\"[@3] last item\")) + +Point is left at list's end." + (letrec ((struct (org-list-struct)) + (prevs (org-list-prevs-alist struct)) + (parents (org-list-parents-alist struct)) + (top (org-list-get-top-point struct)) + (bottom (org-list-get-bottom-point struct)) + (trim + (lambda (text) + ;; Remove indentation and final newline from TEXT. + (org-remove-indentation + (if (string-match-p "\n\\'" text) + (substring text 0 -1) + text)))) + (parse-sublist + (lambda (e) + ;; Return a list whose car is list type and cdr a list + ;; of items' body. + (cons (org-list-get-list-type (car e) struct prevs) + (mapcar parse-item e)))) + (parse-item + (lambda (e) + ;; Return a list containing counter of item, if any, + ;; text and any sublist inside it. + (let* ((end (org-list-get-item-end e struct)) + (children (org-list-get-children e struct parents)) + (body + (save-excursion + (goto-char e) + (looking-at "[ \t]*\\S-+[ \t]*") + (list + (funcall + trim + (concat + (make-string (string-width (match-string 0)) ?\s) + (buffer-substring-no-properties + (match-end 0) (or (car children) end)))))))) + (while children + (let* ((child (car children)) + (sub (org-list-get-all-items child struct prevs)) + (last-in-sub (car (last sub)))) + (push (funcall parse-sublist sub) body) + ;; Remove whole sub-list from children. + (setq children (cdr (memq last-in-sub children))) + ;; There is a chunk of text belonging to the item + ;; if last child doesn't end where next child + ;; starts or where item ends. + (let ((sub-end (org-list-get-item-end last-in-sub struct)) + (next (or (car children) end))) + (when (/= sub-end next) + (push (funcall + trim + (buffer-substring-no-properties sub-end next)) + body))))) + (nreverse body))))) ;; Store output, take care of cursor position and deletion of ;; list, then return output. - (setq out (funcall parse-sublist (org-list-get-all-items top struct prevs))) - (goto-char top) - (when delete - (delete-region top bottom) - (when (and (not (looking-at "[ \t]*$")) (looking-at org-list-end-re)) - (replace-match ""))) - out)) + (prog1 (funcall parse-sublist (org-list-get-all-items top struct prevs)) + (goto-char top) + (when delete + (delete-region top bottom) + (when (and (not (looking-at "[ \t]*$")) (looking-at org-list-end-re)) + (replace-match "")))))) (defun org-list-make-subtree () "Convert the plain list at point into a subtree." (interactive) (if (not (ignore-errors (goto-char (org-in-item-p)))) (error "Not in a list") - (let ((list (save-excursion (org-list-parse-list t)))) + (let ((list (save-excursion (org-list-to-lisp t)))) (insert (org-list-to-subtree list))))) (defun org-list-insert-radio-list () @@ -3055,11 +3231,13 @@ for this list." (catch 'exit (unless (org-at-item-p) (error "Not at a list item")) (save-excursion - (re-search-backward "#\\+ORGLST" nil t) - (unless (looking-at "#\\+ORGLST:[ \t]+SEND[ \t]+\\(\\S-+\\)[ \t]+\\(\\S-+\\)") - (if maybe (throw 'exit nil) - (error "Don't know how to transform this list")))) - (let* ((name (match-string 1)) + (let ((case-fold-search t)) + (re-search-backward "^[ \t]*#\\+ORGLST:" nil t) + (unless (looking-at + "[ \t]*#\\+ORGLST:[ \t]+SEND[ \t]+\\(\\S-+\\)[ \t]+\\([^ \t\n]+\\)") + (if maybe (throw 'exit nil) + (error "Don't know how to transform this list"))))) + (let* ((name (regexp-quote (match-string 1))) (transform (intern (match-string 2))) (bottom-point (save-excursion @@ -3071,220 +3249,371 @@ for this list." (re-search-backward "#\\+ORGLST" nil t) (re-search-forward (org-item-beginning-re) bottom-point t) (match-beginning 0))) - (plain-list (buffer-substring-no-properties top-point bottom-point)) - beg) + (plain-list (save-excursion + (goto-char top-point) + (org-list-to-lisp)))) (unless (fboundp transform) (error "No such transformation function %s" transform)) (let ((txt (funcall transform plain-list))) - ;; Find the insertion place + ;; Find the insertion(s) place(s). (save-excursion (goto-char (point-min)) - (unless (re-search-forward - (concat "BEGIN RECEIVE ORGLST +" - name - "\\([ \t]\\|$\\)") - nil t) - (error "Don't know where to insert translated list")) - (goto-char (match-beginning 0)) - (beginning-of-line 2) - (setq beg (point)) - (unless (re-search-forward (concat "END RECEIVE ORGLST +" name) nil t) - (error "Cannot find end of insertion region")) - (delete-region beg (point-at-bol)) - (goto-char beg) - (insert txt "\n"))) - (message "List converted and installed at receiver location")))) - -(defsubst org-list-item-trim-br (item) - "Trim line breaks in a list ITEM." - (setq item (replace-regexp-in-string "\n +" " " item))) + (let ((receiver-count 0) + (begin-re (format "BEGIN +RECEIVE +ORGLST +%s\\([ \t]\\|$\\)" + name)) + (end-re (format "END +RECEIVE +ORGLST +%s\\([ \t]\\|$\\)" + name))) + (while (re-search-forward begin-re nil t) + (cl-incf receiver-count) + (let ((beg (line-beginning-position 2))) + (unless (re-search-forward end-re nil t) + (user-error "Cannot find end of receiver location at %d" beg)) + (beginning-of-line) + (delete-region beg (point)) + (insert txt "\n"))) + (cond + ((> receiver-count 1) + (message "List converted and installed at receiver locations")) + ((= receiver-count 1) + (message "List converted and installed at receiver location")) + (t (user-error "No valid receiver location found"))))))))) (defun org-list-to-generic (list params) - "Convert a LIST parsed through `org-list-parse-list' to other formats. -Valid parameters PARAMS are: - -:ustart String to start an unordered list -:uend String to end an unordered list - -:ostart String to start an ordered list -:oend String to end an ordered list - -:dstart String to start a descriptive list -:dend String to end a descriptive list -:dtstart String to start a descriptive term -:dtend String to end a descriptive term -:ddstart String to start a description -:ddend String to end a description - -:splice When set to t, return only list body lines, don't wrap - them into :[u/o]start and :[u/o]end. Default is nil. - -:istart String to start a list item. -:icount String to start an item with a counter. -:iend String to end a list item -:isep String to separate items -:lsep String to separate sublists -:csep String to separate text from a sub-list - -:cboff String to insert for an unchecked check-box -:cbon String to insert for a checked check-box -:cbtrans String to insert for a check-box in transitional state - -:nobr Non-nil means remove line breaks in lists items. - -Alternatively, each parameter can also be a form returning -a string. These sexp can use keywords `counter' and `depth', -representing respectively counter associated to the current -item, and depth of the current sub-list, starting at 0. -Obviously, `counter' is only available for parameters applying to -items." - (interactive) - (let* ((p params) - (splicep (plist-get p :splice)) - (ostart (plist-get p :ostart)) - (oend (plist-get p :oend)) - (ustart (plist-get p :ustart)) - (uend (plist-get p :uend)) - (dstart (plist-get p :dstart)) - (dend (plist-get p :dend)) - (dtstart (plist-get p :dtstart)) - (dtend (plist-get p :dtend)) - (ddstart (plist-get p :ddstart)) - (ddend (plist-get p :ddend)) - (istart (plist-get p :istart)) - (icount (plist-get p :icount)) - (iend (plist-get p :iend)) - (isep (plist-get p :isep)) - (lsep (plist-get p :lsep)) - (csep (plist-get p :csep)) - (cbon (plist-get p :cbon)) - (cboff (plist-get p :cboff)) - (cbtrans (plist-get p :cbtrans)) - (nobr (plist-get p :nobr)) - export-sublist ; for byte-compiler - (export-item - (function - ;; Export an item ITEM of type TYPE, at DEPTH. First - ;; string in item is treated in a special way as it can - ;; bring extra information that needs to be processed. - (lambda (item type depth) - (let* ((counter (pop item)) - (fmt (concat - (cond - ((eq type 'descriptive) - ;; Stick DTSTART to ISTART by - ;; left-trimming the latter. - (concat (let ((s (eval istart))) - (or (and (string-match "[ \t\n\r]+\\'" s) - (replace-match "" t t s)) - istart)) - "%s" (eval ddend))) - ((and counter (eq type 'ordered)) - (concat (eval icount) "%s")) - (t (concat (eval istart) "%s"))) - (eval iend))) - (first (car item))) - ;; Replace checkbox if any is found. - (cond - ((string-match "\\[CBON\\]" first) - (setq first (replace-match cbon t t first))) - ((string-match "\\[CBOFF\\]" first) - (setq first (replace-match cboff t t first))) - ((string-match "\\[CBTRANS\\]" first) - (setq first (replace-match cbtrans t t first)))) - ;; Replace line breaks if required - (when nobr (setq first (org-list-item-trim-br first))) - ;; Insert descriptive term if TYPE is `descriptive'. - (when (eq type 'descriptive) - (let* ((complete (string-match "^\\(.*\\)[ \t]+::" first)) - (term (if complete - (save-match-data - (org-trim (match-string 1 first))) - "???")) - (desc (if complete - (org-trim (substring first (match-end 0))) - first))) - (setq first (concat (eval dtstart) term (eval dtend) - (eval ddstart) desc)))) - (setcar item first) - (format fmt - (mapconcat (lambda (e) - (if (stringp e) e - (funcall export-sublist e (1+ depth)))) - item (or (eval csep) ""))))))) - (export-sublist - (function - ;; Export sublist SUB at DEPTH. - (lambda (sub depth) - (let* ((type (car sub)) - (items (cdr sub)) - (fmt (concat (cond - (splicep "%s") - ((eq type 'ordered) - (concat (eval ostart) "%s" (eval oend))) - ((eq type 'descriptive) - (concat (eval dstart) "%s" (eval dend))) - (t (concat (eval ustart) "%s" (eval uend)))) - (eval lsep)))) - (format fmt (mapconcat (lambda (e) - (funcall export-item e type depth)) - items (or (eval isep) "")))))))) - (concat (funcall export-sublist list 0) "\n"))) - -(defun org-list-to-latex (list &optional _params) + "Convert a LIST parsed through `org-list-to-lisp' to a custom format. + +LIST is a list as returned by `org-list-to-lisp', which see. +PARAMS is a property list of parameters used to tweak the output +format. + +Valid parameters are: + +:backend, :raw + + Export back-end used as a basis to transcode elements of the + list, when no specific parameter applies to it. It is also + used to translate its contents. You can prevent this by + setting :raw property to a non-nil value. + +:splice + + When non-nil, only export the contents of the top most plain + list, effectively ignoring its opening and closing lines. + +:ustart, :uend + + Strings to start and end an unordered list. They can also be + set to a function returning a string or nil, which will be + called with the depth of the list, counting from 1. + +:ostart, :oend + + Strings to start and end an ordered list. They can also be set + to a function returning a string or nil, which will be called + with the depth of the list, counting from 1. + +:dstart, :dend + + Strings to start and end a descriptive list. They can also be + set to a function returning a string or nil, which will be + called with the depth of the list, counting from 1. + +:dtstart, :dtend, :ddstart, :ddend + + Strings to start and end a descriptive term. + +:istart, :iend + + Strings to start or end a list item, and to start a list item + with a counter. They can also be set to a function returning + a string or nil, which will be called with two arguments: the + type of list and the depth of the item, counting from 1. + +:icount + + Strings to start a list item with a counter. It can also be + set to a function returning a string or nil, which will be + called with three arguments: the type of list, the depth of the + item, counting from 1, and the counter. Its value, when + non-nil, has precedence over `:istart'. + +:isep + + String used to separate items. It can also be set to + a function returning a string or nil, which will be called with + two arguments: the type of list and the depth of the item, + counting from 1. It always start on a new line. + +:ifmt + + Function to be applied to the contents of every item. It is + called with two arguments: the type of list and the contents. + +:cbon, :cboff, :cbtrans + + String to insert, respectively, an un-checked check-box, + a checked check-box and a check-box in transitional state." + (require 'ox) + (let* ((backend (plist-get params :backend)) + (custom-backend + (org-export-create-backend + :parent (or backend 'org) + :transcoders + `((plain-list . ,(org-list--to-generic-plain-list params)) + (item . ,(org-list--to-generic-item params)) + (macro . (lambda (m c i) (org-element-macro-interpreter m nil)))))) + data info) + ;; Write LIST back into Org syntax and parse it. + (with-temp-buffer + (let ((org-inhibit-startup t)) (org-mode)) + (letrec ((insert-list + (lambda (l) + (dolist (i (cdr l)) + (funcall insert-item i (car l))))) + (insert-item + (lambda (i type) + (let ((start (point))) + (insert (if (eq type 'ordered) "1. " "- ")) + (dolist (e i) + (if (consp e) (funcall insert-list e) + (insert e) + (insert "\n"))) + (beginning-of-line) + (save-excursion + (let ((ind (if (eq type 'ordered) 3 2))) + (while (> (point) start) + (unless (looking-at-p "[ \t]*$") + (indent-to ind)) + (forward-line -1)))))))) + (funcall insert-list list)) + (setf data + (org-element-map (org-element-parse-buffer) 'plain-list + #'identity nil t)) + (setf info (org-export-get-environment backend nil params))) + (when (and backend (symbolp backend) (not (org-export-get-backend backend))) + (user-error "Unknown :backend value")) + (unless backend (require 'ox-org)) + ;; When`:raw' property has a non-nil value, turn all objects back + ;; into Org syntax. + (when (and backend (plist-get params :raw)) + (org-element-map data org-element-all-objects + (lambda (object) + (org-element-set-element + object (org-element-interpret-data object))))) + ;; We use a low-level mechanism to export DATA so as to skip all + ;; usual pre-processing and post-processing, i.e., hooks, filters, + ;; Babel code evaluation, include keywords and macro expansion, + ;; and filters. + (let ((output (org-export-data-with-backend data custom-backend info))) + ;; Remove final newline. + (if (org-string-nw-p output) (substring-no-properties output 0 -1) "")))) + +(defun org-list--depth (element) + "Return the level of ELEMENT within current plain list. +ELEMENT is either an item or a plain list." + (cl-count-if (lambda (ancestor) (eq (org-element-type ancestor) 'plain-list)) + (org-element-lineage element nil t))) + +(defun org-list--trailing-newlines (string) + "Return the number of trailing newlines in STRING." + (with-temp-buffer + (insert string) + (skip-chars-backward " \t\n") + (count-lines (line-beginning-position 2) (point-max)))) + +(defun org-list--generic-eval (value &rest args) + "Evaluate VALUE according to its type. +VALUE is either nil, a string or a function. In the latter case, +it is called with arguments ARGS." + (cond ((null value) nil) + ((stringp value) value) + ((functionp value) (apply value args)) + (t (error "Wrong value: %s" value)))) + +(defun org-list--to-generic-plain-list (params) + "Return a transcoder for `plain-list' elements. +PARAMS is a plist used to tweak the behavior of the transcoder." + (let ((ustart (plist-get params :ustart)) + (uend (plist-get params :uend)) + (ostart (plist-get params :ostart)) + (oend (plist-get params :oend)) + (dstart (plist-get params :dstart)) + (dend (plist-get params :dend)) + (splice (plist-get params :splice)) + (backend (plist-get params :backend))) + (lambda (plain-list contents info) + (let* ((type (org-element-property :type plain-list)) + (depth (org-list--depth plain-list)) + (start (and (not splice) + (org-list--generic-eval + (pcase type + (`ordered ostart) + (`unordered ustart) + (_ dstart)) + depth))) + (end (and (not splice) + (org-list--generic-eval + (pcase type + (`ordered oend) + (`unordered uend) + (_ dend)) + depth)))) + ;; Make sure trailing newlines in END appear in the output by + ;; setting `:post-blank' property to their number. + (when end + (org-element-put-property + plain-list :post-blank (org-list--trailing-newlines end))) + ;; Build output. + (concat (and start (concat start "\n")) + (if (or start end splice (not backend)) + contents + (org-export-with-backend backend plain-list contents info)) + end))))) + +(defun org-list--to-generic-item (params) + "Return a transcoder for `item' elements. +PARAMS is a plist used to tweak the behavior of the transcoder." + (let ((backend (plist-get params :backend)) + (istart (plist-get params :istart)) + (iend (plist-get params :iend)) + (isep (plist-get params :isep)) + (icount (plist-get params :icount)) + (ifmt (plist-get params :ifmt)) + (cboff (plist-get params :cboff)) + (cbon (plist-get params :cbon)) + (cbtrans (plist-get params :cbtrans)) + (dtstart (plist-get params :dtstart)) + (dtend (plist-get params :dtend)) + (ddstart (plist-get params :ddstart)) + (ddend (plist-get params :ddend))) + (lambda (item contents info) + (let* ((type + (org-element-property :type (org-element-property :parent item))) + (tag (org-element-property :tag item)) + (depth (org-list--depth item)) + (separator (and (org-export-get-next-element item info) + (org-list--generic-eval isep type depth))) + (closing (pcase (org-list--generic-eval iend type depth) + ((or `nil "") "\n") + ((and (guard separator) s) + (if (equal (substring s -1) "\n") s (concat s "\n"))) + (s s)))) + ;; When a closing line or a separator is provided, make sure + ;; its trailing newlines are taken into account when building + ;; output. This is done by setting `:post-blank' property to + ;; the number of such lines in the last line to be added. + (let ((last-string (or separator closing))) + (when last-string + (org-element-put-property + item + :post-blank + (max (1- (org-list--trailing-newlines last-string)) 0)))) + ;; Build output. + (concat + (let ((c (org-element-property :counter item))) + (if (and c icount) (org-list--generic-eval icount type depth c) + (org-list--generic-eval istart type depth))) + (let ((body + (if (or istart iend icount ifmt cbon cboff cbtrans (not backend) + (and (eq type 'descriptive) + (or dtstart dtend ddstart ddend))) + (concat + (pcase (org-element-property :checkbox item) + (`on cbon) + (`off cboff) + (`trans cbtrans)) + (and tag + (concat dtstart + (if backend + (org-export-data-with-backend + tag backend info) + (org-element-interpret-data tag)) + dtend)) + (and tag ddstart) + (let ((contents + (if (= (length contents) 0) "" + (substring contents 0 -1)))) + (if ifmt (org-list--generic-eval ifmt type contents) + contents)) + (and tag ddend)) + (org-export-with-backend backend item contents info)))) + ;; Remove final newline. + (if (equal body "") "" + (substring (org-element-normalize-string body) 0 -1))) + closing + separator))))) + +(defun org-list-to-latex (list &optional params) "Convert LIST into a LaTeX list. -LIST is as string representing the list to transform, as Org -syntax. Return converted list as a string." +LIST is a parsed plain list, as returned by `org-list-to-lisp'. +PARAMS is a property list with overruling parameters for +`org-list-to-generic'. Return converted list as a string." (require 'ox-latex) - (org-export-string-as list 'latex t)) + (org-list-to-generic list (org-combine-plists '(:backend latex) params))) -(defun org-list-to-html (list) +(defun org-list-to-html (list &optional params) "Convert LIST into a HTML list. -LIST is as string representing the list to transform, as Org -syntax. Return converted list as a string." +LIST is a parsed plain list, as returned by `org-list-to-lisp'. +PARAMS is a property list with overruling parameters for +`org-list-to-generic'. Return converted list as a string." (require 'ox-html) - (org-export-string-as list 'html t)) + (org-list-to-generic list (org-combine-plists '(:backend html) params))) -(defun org-list-to-texinfo (list &optional _params) +(defun org-list-to-texinfo (list &optional params) "Convert LIST into a Texinfo list. -LIST is as string representing the list to transform, as Org -syntax. Return converted list as a string." +LIST is a parsed plain list, as returned by `org-list-to-lisp'. +PARAMS is a property list with overruling parameters for +`org-list-to-generic'. Return converted list as a string." (require 'ox-texinfo) - (org-export-string-as list 'texinfo t)) + (org-list-to-generic list (org-combine-plists '(:backend texinfo) params))) -(defun org-list-to-subtree (list &optional params) - "Convert LIST into an Org subtree. +(defun org-list-to-org (list &optional params) + "Convert LIST into an Org plain list. LIST is as returned by `org-list-parse-list'. PARAMS is a property list with overruling parameters for `org-list-to-generic'." - (defvar get-stars) (defvar org--blankp) - (let* ((rule (cdr (assq 'heading org-blank-before-new-entry))) + (let* ((make-item + (lambda (type _depth &optional c) + (concat (if (eq type 'ordered) "1. " "- ") + (and c (format "[@%d] " c))))) + (defaults + (list :istart make-item + :icount make-item + :ifmt (lambda (_type contents) + (replace-regexp-in-string "\n" "\n " contents)) + :dtend " :: " + :cbon "[X] " + :cboff "[ ] " + :cbtrans "[-] "))) + (org-list-to-generic list (org-combine-plists defaults params)))) + +(defun org-list-to-subtree (list &optional params) + "Convert LIST into an Org subtree. +LIST is as returned by `org-list-to-lisp'. PARAMS is a property +list with overruling parameters for `org-list-to-generic'." + (let* ((blank (pcase (cdr (assq 'heading org-blank-before-new-entry)) + (`t t) + (`auto (save-excursion + (org-with-limited-levels (outline-previous-heading)) + (org-previous-line-empty-p))))) (level (org-reduced-level (or (org-current-level) 0))) - (org--blankp (or (eq rule t) - (and (eq rule 'auto) - (save-excursion - (outline-previous-heading) - (org-previous-line-empty-p))))) - (get-stars ;FIXME: Can't rename without renaming it in org.el as well! - (function - ;; Return the string for the heading, depending on depth D - ;; of current sub-list. - (lambda (d) - (let ((oddeven-level (+ level d 1))) - (concat (make-string (if org-odd-levels-only - (1- (* 2 oddeven-level)) - oddeven-level) - ?*) - " ")))))) + (make-stars + (lambda (_type depth &optional _count) + ;; Return the string for the heading, depending on DEPTH + ;; of current sub-list. + (let ((oddeven-level (+ level depth))) + (concat (make-string (if org-odd-levels-only + (1- (* 2 oddeven-level)) + oddeven-level) + ?*) + " "))))) (org-list-to-generic list (org-combine-plists - '(:splice t - :dtstart " " :dtend " " - :istart (funcall get-stars depth) - :icount (funcall get-stars depth) - :isep (if org--blankp "\n\n" "\n") - :csep (if org--blankp "\n\n" "\n") - :cbon "DONE" :cboff "TODO" :cbtrans "TODO") + (list :splice t + :istart make-stars + :icount make-stars + :dtstart " " :dtend " " + :isep (if blank "\n\n" "\n") + :cbon "DONE " :cboff "TODO " :cbtrans "TODO ") params)))) (provide 'org-list) diff --git a/lisp/org/org-macro.el b/lisp/org/org-macro.el index f4919d1385e..1d2823ea0f9 100644 --- a/lisp/org/org-macro.el +++ b/lisp/org/org-macro.el @@ -1,4 +1,4 @@ -;;; org-macro.el --- Macro Replacement Code for Org Mode +;;; org-macro.el --- Macro Replacement Code for Org -*- lexical-binding: t; -*- ;; Copyright (C) 2013-2017 Free Software Foundation, Inc. @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -30,28 +30,43 @@ ;; `org-macro-initialize-templates', which recursively calls ;; `org-macro--collect-macros' in order to read setup files. +;; Argument in macros are separated with commas. Proper escaping rules +;; are implemented in `org-macro-escape-arguments' and arguments can +;; be extracted from a string with `org-macro-extract-arguments'. + ;; Along with macros defined through #+MACRO: keyword, default ;; templates include the following hard-coded macros: -;; {{{time(format-string)}}}, {{{property(node-property)}}}, -;; {{{input-file}}} and {{{modification-time(format-string)}}}. +;; {{{time(format-string)}}}, +;; {{{property(node-property)}}}, +;; {{{input-file}}}, +;; {{{modification-time(format-string)}}}, +;; {{{n(counter,action}}}. ;; Upon exporting, "ox.el" will also provide {{{author}}}, {{{date}}}, ;; {{{email}}} and {{{title}}} macros. ;;; Code: +(require 'cl-lib) (require 'org-macs) +(require 'org-compat) -(declare-function org-element-at-point "org-element" (&optional keep-trail)) +(declare-function org-element-at-point "org-element" ()) (declare-function org-element-context "org-element" (&optional element)) +(declare-function org-element-macro-parser "org-element" ()) (declare-function org-element-property "org-element" (property element)) (declare-function org-element-type "org-element" (element)) -(declare-function org-remove-double-quotes "org" (s)) +(declare-function org-file-contents "org" (file &optional noerror nocache)) +(declare-function org-file-url-p "org" (file)) +(declare-function org-in-commented-heading-p "org" (&optional no-inheritance)) (declare-function org-mode "org" ()) -(declare-function org-file-contents "org" (file &optional noerror)) +(declare-function org-trim "org" (s &optional keep-lead)) +(declare-function vc-backend "vc-hooks" (f)) +(declare-function vc-call "vc-hooks" (fun file &rest args) t) +(declare-function vc-exec-after "vc-dispatcher" (code)) ;;; Variables -(defvar org-macro-templates nil +(defvar-local org-macro-templates nil "Alist containing all macro templates in current buffer. Associations are in the shape of (NAME . TEMPLATE) where NAME stands for macro's name and template for its replacement value, @@ -59,48 +74,53 @@ both as strings. This is an internal variable. Do not set it directly, use instead: #+MACRO: name template") -(make-variable-buffer-local 'org-macro-templates) - ;;; Functions (defun org-macro--collect-macros () "Collect macro definitions in current buffer and setup files. Return an alist containing all macro templates found." - (let* (collect-macros ; For byte-compiler. - (collect-macros - (lambda (files templates) - ;; Return an alist of macro templates. FILES is a list of - ;; setup files names read so far, used to avoid circular - ;; dependencies. TEMPLATES is the alist collected so far. - (let ((case-fold-search t)) - (org-with-wide-buffer - (goto-char (point-min)) - (while (re-search-forward - "^[ \t]*#\\+\\(MACRO\\|SETUPFILE\\):" nil t) - (let ((element (org-element-at-point))) - (when (eq (org-element-type element) 'keyword) - (let ((val (org-element-property :value element))) - (if (equal (org-element-property :key element) "MACRO") - ;; Install macro in TEMPLATES. - (when (string-match - "^\\(.*?\\)\\(?:\\s-+\\(.*\\)\\)?\\s-*$" val) - (let* ((name (match-string 1 val)) - (template (or (match-string 2 val) "")) - (old-cell (assoc name templates))) - (if old-cell (setcdr old-cell template) - (push (cons name template) templates)))) - ;; Enter setup file. - (let ((file (expand-file-name - (org-remove-double-quotes val)))) - (unless (member file files) - (with-temp-buffer - (org-mode) - (insert (org-file-contents file 'noerror)) - (setq templates - (funcall collect-macros (cons file files) - templates))))))))))) - templates)))) + (letrec ((collect-macros + (lambda (files templates) + ;; Return an alist of macro templates. FILES is a list + ;; of setup files names read so far, used to avoid + ;; circular dependencies. TEMPLATES is the alist + ;; collected so far. + (let ((case-fold-search t)) + (org-with-wide-buffer + (goto-char (point-min)) + (while (re-search-forward + "^[ \t]*#\\+\\(MACRO\\|SETUPFILE\\):" nil t) + (let ((element (org-element-at-point))) + (when (eq (org-element-type element) 'keyword) + (let ((val (org-element-property :value element))) + (if (equal (org-element-property :key element) "MACRO") + ;; Install macro in TEMPLATES. + (when (string-match + "^\\(.*?\\)\\(?:\\s-+\\(.*\\)\\)?\\s-*$" val) + (let* ((name (match-string 1 val)) + (template (or (match-string 2 val) "")) + (old-cell (assoc name templates))) + (if old-cell (setcdr old-cell template) + (push (cons name template) templates)))) + ;; Enter setup file. + (let* ((uri (org-unbracket-string "\"" "\"" (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))) + (org-mode) + (insert (org-file-contents uri 'noerror)) + (setq templates + (funcall collect-macros (cons uri files) + templates))))))))))) + templates)))) (funcall collect-macros nil nil))) (defun org-macro-initialize-templates () @@ -116,18 +136,34 @@ function installs the following ones: \"property\", (let ((old-template (assoc (car cell) templates))) (if old-template (setcdr old-template (cdr cell)) (push cell templates)))))) - ;; Install hard-coded macros. - (mapc (lambda (cell) (funcall update-templates cell)) - (list (cons "property" "(eval (org-entry-get nil \"$1\" 'selective))") + ;; Install "property", "time" macros. + (mapc update-templates + (list (cons "property" + "(eval (save-excursion + (let ((l \"$2\")) + (when (org-string-nw-p l) + (condition-case _ + (let ((org-link-search-must-match-exact-headline t)) + (org-link-search l nil t)) + (error + (error \"Macro property failed: cannot find location %s\" + l))))) + (org-entry-get nil \"$1\" 'selective)))") (cons "time" "(eval (format-time-string \"$1\"))"))) + ;; Install "input-file", "modification-time" macros. (let ((visited-file (buffer-file-name (buffer-base-buffer)))) (when (and visited-file (file-exists-p visited-file)) - (mapc (lambda (cell) (funcall update-templates cell)) + (mapc update-templates (list (cons "input-file" (file-name-nondirectory visited-file)) (cons "modification-time" - (format "(eval (format-time-string \"$1\" '%s))" + (format "(eval (format-time-string \"$1\" (or (and (org-string-nw-p \"$2\") (org-macro--vc-modified-time %s)) '%s)))" + (prin1-to-string visited-file) (prin1-to-string (nth 5 (file-attributes visited-file))))))))) + ;; Initialize and install "n" macro. + (org-macro--counter-initialize) + (funcall update-templates + (cons "n" "(eval (org-macro--counter-increment \"$1\" \"$2\"))")) (setq org-macro-templates templates))) (defun org-macro-expand (macro templates) @@ -154,38 +190,165 @@ default value. Return nil if no template was found." ;; Return string. (format "%s" (or value "")))))) -(defun org-macro-replace-all (templates) +(defun org-macro-replace-all (templates &optional finalize keywords) "Replace all macros in current buffer by their expansion. + TEMPLATES is an alist of templates used for expansion. See -`org-macro-templates' for a buffer-local default value." - (save-excursion - (goto-char (point-min)) - (let (record) - (while (re-search-forward "{{{[-A-Za-z0-9_]" nil t) - (let ((object (org-element-context))) - (when (eq (org-element-type object) 'macro) - (let* ((value (org-macro-expand object templates)) - (begin (org-element-property :begin object)) - (signature (list begin - object - (org-element-property :args object)))) - ;; Avoid circular dependencies by checking if the same - ;; macro with the same arguments is expanded at the same - ;; position twice. - (if (member signature record) - (error "Circular macro expansion: %s" - (org-element-property :key object)) - (when value - (push signature record) - (delete-region - begin - ;; Preserve white spaces after the macro. - (progn (goto-char (org-element-property :end object)) - (skip-chars-backward " \t") - (point))) - ;; Leave point before replacement in case of recursive - ;; expansions. - (save-excursion (insert value))))))))))) +`org-macro-templates' for a buffer-local default value. + +If optional arg FINALIZE is non-nil, raise an error if a macro is +found in the buffer with no definition in TEMPLATES. + +Optional argument KEYWORDS, when non-nil is a list of keywords, +as strings, where macro expansion is allowed." + (org-with-wide-buffer + (goto-char (point-min)) + (let ((properties-regexp (format "\\`EXPORT_%s\\+?\\'" + (regexp-opt keywords))) + record) + (while (re-search-forward "{{{[-A-Za-z0-9_]" nil t) + (unless (save-match-data (org-in-commented-heading-p)) + (let* ((datum (save-match-data (org-element-context))) + (type (org-element-type datum)) + (macro + (cond + ((eq type 'macro) datum) + ;; In parsed keywords and associated node + ;; properties, force macro recognition. + ((or (and (eq type 'keyword) + (member (org-element-property :key datum) keywords)) + (and (eq type 'node-property) + (string-match-p properties-regexp + (org-element-property :key datum)))) + (save-excursion + (goto-char (match-beginning 0)) + (org-element-macro-parser)))))) + (when macro + (let* ((value (org-macro-expand macro templates)) + (begin (org-element-property :begin macro)) + (signature (list begin + macro + (org-element-property :args macro)))) + ;; Avoid circular dependencies by checking if the same + ;; macro with the same arguments is expanded at the + ;; same position twice. + (cond ((member signature record) + (error "Circular macro expansion: %s" + (org-element-property :key macro))) + (value + (push signature record) + (delete-region + begin + ;; Preserve white spaces after the macro. + (progn (goto-char (org-element-property :end macro)) + (skip-chars-backward " \t") + (point))) + ;; Leave point before replacement in case of + ;; recursive expansions. + (save-excursion (insert value))) + (finalize + (error "Undefined Org macro: %s; aborting" + (org-element-property :key macro)))))))))))) + +(defun org-macro-escape-arguments (&rest args) + "Build macro's arguments string from ARGS. +ARGS are strings. Return value is a string with arguments +properly escaped and separated with commas. This is the opposite +of `org-macro-extract-arguments'." + (let ((s "")) + (dolist (arg (reverse args) (substring s 1)) + (setq s + (concat + "," + (replace-regexp-in-string + "\\(\\\\*\\)," + (lambda (m) + (concat (make-string (1+ (* 2 (length (match-string 1 m)))) ?\\) + ",")) + ;; If a non-terminal argument ends on backslashes, make + ;; sure to also escape them as they will be followed by + ;; a comma. + (concat arg (and (not (equal s "")) + (string-match "\\\\+\\'" arg) + (match-string 0 arg))) + nil t) + s))))) + +(defun org-macro-extract-arguments (s) + "Extract macro arguments from string S. +S is a string containing comma separated values properly escaped. +Return a list of arguments, as strings. This is the opposite of +`org-macro-escape-arguments'." + ;; Do not use `org-split-string' since empty strings are + ;; meaningful here. + (split-string + (replace-regexp-in-string + "\\(\\\\*\\)," + (lambda (str) + (let ((len (length (match-string 1 str)))) + (concat (make-string (/ len 2) ?\\) + (if (zerop (mod len 2)) "\000" ",")))) + s nil t) + "\000")) + + +;;; Helper functions and variables for internal macros + +(defun org-macro--vc-modified-time (file) + (save-window-excursion + (when (vc-backend file) + (let ((buf (get-buffer-create " *org-vc*")) + (case-fold-search t) + date) + (unwind-protect + (progn + (vc-call print-log file buf nil nil 1) + (with-current-buffer buf + (vc-exec-after + (lambda () + (goto-char (point-min)) + (when (re-search-forward "Date:?[ \t]*" nil t) + (let ((time (parse-time-string + (buffer-substring + (point) (line-end-position))))) + (when (cl-some #'identity time) + (setq date (apply #'encode-time time)))))))) + (let ((proc (get-buffer-process buf))) + (while (and proc (accept-process-output proc .5 nil t))))) + (kill-buffer buf)) + date)))) + +(defvar org-macro--counter-table nil + "Hash table containing counter value per name.") + +(defun org-macro--counter-initialize () + "Initialize `org-macro--counter-table'." + (setq org-macro--counter-table (make-hash-table :test #'equal))) + +(defun org-macro--counter-increment (name &optional action) + "Increment counter NAME. +NAME is a string identifying the counter. + +When non-nil, optional argument ACTION is a string. + +If the string is \"-\", keep the NAME counter at its current +value, i.e. do not increment. + +If the string represents an integer, set the counter to this number. + +Any other non-empty string resets the counter to 1." + (let ((name-trimmed (org-trim name)) + (action-trimmed (when (org-string-nw-p action) + (org-trim action)))) + (puthash name-trimmed + (cond ((not (org-string-nw-p action-trimmed)) + (1+ (gethash name-trimmed org-macro--counter-table 0))) + ((string= "-" action-trimmed) + (gethash name-trimmed org-macro--counter-table 1)) + ((string-match-p "\\`[0-9]+\\'" action-trimmed) + (string-to-number action-trimmed)) + (t 1)) + org-macro--counter-table))) (provide 'org-macro) diff --git a/lisp/org/org-macs.el b/lisp/org/org-macs.el index 64e28cee04c..ff6d8c41d4b 100644 --- a/lisp/org/org-macs.el +++ b/lisp/org/org-macs.el @@ -1,4 +1,4 @@ -;;; org-macs.el --- Top-level definitions for Org-mode +;;; org-macs.el --- Top-level Definitions for Org -*- lexical-binding: t; -*- ;; Copyright (C) 2004-2017 Free Software Foundation, Inc. @@ -19,35 +19,18 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Commentary: ;; This file contains macro definitions, defsubst definitions, other -;; stuff needed for compilation and top-level forms in Org-mode, as well -;; lots of small functions that are not org-mode specific but simply -;; generally useful stuff. +;; stuff needed for compilation and top-level forms in Org mode, as +;; well lots of small functions that are not Org mode specific but +;; simply generally useful stuff. ;;; Code: -(eval-and-compile - (unless (fboundp 'declare-function) - (defmacro declare-function (fn file &optional _arglist _fileonly) - `(autoload ',fn ,file))) - - (if (>= emacs-major-version 23) - (defsubst org-char-to-string(c) - "Defsubst to decode UTF-8 character values in emacs 23 and beyond." - (char-to-string c)) - (defsubst org-char-to-string (c) - "Defsubst to decode UTF-8 character values in emacs 22." - (string (decode-char 'ucs c))))) - -(declare-function org-add-props "org-compat" (string plist &rest props)) -(declare-function org-string-match-p "org-compat" - (regexp string &optional start)) - (defmacro org-with-gensyms (symbols &rest body) (declare (debug (sexp body)) (indent 1)) `(let ,(mapcar (lambda (s) @@ -55,52 +38,101 @@ symbols) ,@body)) -(defmacro org-called-interactively-p (&optional kind) - (declare (debug (&optional ("quote" symbolp)))) ;Why not just t? - (if (featurep 'xemacs) - `(interactive-p) - (if (or (> emacs-major-version 23) - (and (>= emacs-major-version 23) - (>= emacs-minor-version 2))) - ;; defined with no argument in <=23.1 - `(with-no-warnings (called-interactively-p ,kind)) - `(interactive-p)))) - -(defmacro org-bound-and-true-p (var) - "Return the value of symbol VAR if it is bound, else nil." - (declare (debug (symbolp))) - `(and (boundp (quote ,var)) ,var)) - (defun org-string-nw-p (s) - "Is S a string with a non-white character?" + "Return S if S is a string containing a non-blank character. +Otherwise, return nil." (and (stringp s) - (org-string-match-p "\\S-" s) + (string-match-p "[^ \r\t\n]" s) s)) +(defun org-split-string (string &optional separators) + "Splits STRING into substrings at SEPARATORS. + +SEPARATORS is a regular expression. When nil, it defaults to +\"[ \f\t\n\r\v]+\". + +Unlike `split-string', matching SEPARATORS at the beginning and +end of string are ignored." + (let ((separators (or separators "[ \f\t\n\r\v]+"))) + (when (string-match (concat "\\`" separators) string) + (setq string (replace-match "" nil nil string))) + (when (string-match (concat separators "\\'") string) + (setq string (replace-match "" nil nil string))) + (split-string string separators))) + +(defun org-string-display (string) + "Return STRING as it is displayed in the current buffer. +This function takes into consideration `invisible' and `display' +text properties." + (let* ((build-from-parts + (lambda (s property filter) + ;; Build a new string out of string S. On every group of + ;; contiguous characters with the same PROPERTY value, + ;; call FILTER on the properties list at the beginning of + ;; the group. If it returns a string, replace the + ;; characters in the group with it. Otherwise, preserve + ;; those characters. + (let ((len (length s)) + (new "") + (i 0) + (cursor 0)) + (while (setq i (text-property-not-all i len property nil s)) + (let ((end (next-single-property-change i property s len)) + (value (funcall filter (text-properties-at i s)))) + (when value + (setq new (concat new (substring s cursor i) value)) + (setq cursor end)) + (setq i end))) + (concat new (substring s cursor))))) + (prune-invisible + (lambda (s) + (funcall build-from-parts s 'invisible + (lambda (props) + ;; If `invisible' property in PROPS means text + ;; is to be invisible, return the empty string. + ;; Otherwise return nil so that the part is + ;; skipped. + (and (or (eq t buffer-invisibility-spec) + (assoc-string (plist-get props 'invisible) + buffer-invisibility-spec)) + ""))))) + (replace-display + (lambda (s) + (funcall build-from-parts s 'display + (lambda (props) + ;; If there is any string specification in + ;; `display' property return it. Also attach + ;; other text properties on the part to that + ;; string (face...). + (let* ((display (plist-get props 'display)) + (value (if (stringp display) display + (cl-some #'stringp display)))) + (when value + (apply #'propertize + ;; Displayed string could contain + ;; invisible parts, but no nested + ;; display. + (funcall prune-invisible value) + 'display + (and (not (stringp display)) + (cl-remove-if #'stringp display)) + props)))))))) + ;; `display' property overrides `invisible' one. So we first + ;; replace characters with `display' property. Then we remove + ;; invisible characters. + (funcall prune-invisible (funcall replace-display string)))) + +(defun org-string-width (string) + "Return width of STRING when displayed in the current buffer. +Unlike `string-width', this function takes into consideration +`invisible' and `display' text properties." + (string-width (org-string-display string))) + (defun org-not-nil (v) "If V not nil, and also not the string \"nil\", then return V. Otherwise return nil." (and v (not (equal v "nil")) v)) -(defun org-substitute-posix-classes (re) - "Substitute posix classes in regular expression RE." - (let ((ss re)) - (save-match-data - (while (string-match "\\[:alnum:\\]" ss) - (setq ss (replace-match "a-zA-Z0-9" t t ss))) - (while (string-match "\\[:word:\\]" ss) - (setq ss (replace-match "a-zA-Z0-9" t t ss))) - (while (string-match "\\[:alpha:\\]" ss) - (setq ss (replace-match "a-zA-Z" t t ss))) - (while (string-match "\\[:punct:\\]" ss) - (setq ss (replace-match "\001-@[-`{-~" t t ss))) - ss))) - -(defmacro org-re (s) - "Replace posix classes in regular expression." - (declare (debug (form))) - (if (featurep 'xemacs) `(org-substitute-posix-classes ,s) s)) - (defmacro org-preserve-lc (&rest body) (declare (debug (body))) (org-with-gensyms (line col) @@ -136,19 +168,6 @@ Otherwise return nil." (partial-completion-mode 1)) ,@body)) -;; FIXME: Slated for removal. Current Org mode does not support Emacs < 22 -(defmacro org-maybe-intangible (props) - "Add (intangible t) to PROPS if Emacs version is earlier than Emacs 22. -In Emacs 21, invisible text is not avoided by the command loop, so the -intangible property is needed to make sure point skips this text. -In Emacs 22, this is not necessary. The intangible text property has -led to problems with flyspell. These problems are fixed in flyspell.el, -but we still avoid setting the property in Emacs 22 and later. -We use a macro so that the test can happen at compilation time." - (if (< emacs-major-version 22) - `(append '(intangible t) ,props) - props)) - (defmacro org-with-point-at (pom &rest body) "Move to buffer and point of point-or-marker POM for the duration of BODY." (declare (debug (form body)) (indent 1)) @@ -160,10 +179,6 @@ We use a macro so that the test can happen at compilation time." (goto-char (or ,mpom (point))) ,@body))))) -(defmacro org-no-warnings (&rest body) - (declare (debug (body))) - (cons (if (fboundp 'with-no-warnings) 'with-no-warnings 'progn) body)) - (defmacro org-with-remote-undo (buffer &rest body) "Execute BODY while recording undo information in two buffers." (declare (debug (form body)) (indent 1)) @@ -199,22 +214,12 @@ We use a macro so that the test can happen at compilation time." org-emphasis t) "Properties to remove when a string without properties is wanted.") -(defsubst org-match-string-no-properties (num &optional string) - (if (featurep 'xemacs) - (let ((s (match-string num string))) - (and s (remove-text-properties 0 (length s) org-rm-props s)) - s) - (match-string-no-properties num string))) - (defsubst org-no-properties (s &optional restricted) "Remove all text properties from string S. When RESTRICTED is non-nil, only remove the properties listed in `org-rm-props'." - (if (fboundp 'set-text-properties) - (set-text-properties 0 (length s) nil s) - (if restricted - (remove-text-properties 0 (length s) org-rm-props s) - (set-text-properties 0 (length s) nil s))) + (if restricted (remove-text-properties 0 (length s) org-rm-props s) + (set-text-properties 0 (length s) nil s)) s) (defsubst org-get-alist-option (option key) @@ -236,16 +241,6 @@ program is needed for, so that the error message can be more informative." (error "Can't find `%s'%s" cmd (if use (format " (%s)" use) ""))))) -(defsubst org-inhibit-invisibility () - "Modified `buffer-invisibility-spec' for Emacs 21. -Some ops with invisible text do not work correctly on Emacs 21. For these -we turn off invisibility temporarily. Use this in a `let' form." - (if (< emacs-major-version 22) nil buffer-invisibility-spec)) - -(defsubst org-set-local (var value) - "Make VAR local in current buffer and set it to VALUE." - (set (make-local-variable var) value)) - (defsubst org-last (list) "Return the last element of LIST." (car (last list))) @@ -282,11 +277,11 @@ we turn off invisibility temporarily. Use this in a `let' form." (<= (match-beginning n) pos) (>= (match-end n) pos))) -(defun org-match-line (re) - "Looking-at at the beginning of the current line." +(defun org-match-line (regexp) + "Match REGEXP at the beginning of the current line." (save-excursion - (goto-char (point-at-bol)) - (looking-at re))) + (beginning-of-line) + (looking-at regexp))) (defun org-plist-delete (plist property) "Delete PROPERTY from PLIST. @@ -298,13 +293,6 @@ This is in contrast to merely setting it to 0." (setq plist (cddr plist))) p)) -(defun org-replace-match-keep-properties (newtext &optional fixedcase - literal string) - "Like `replace-match', but add the text properties found original text." - (setq newtext (org-add-props newtext (text-properties-at - (match-beginning 0) string))) - (replace-match newtext fixedcase literal string)) - (defmacro org-save-outline-visibility (use-markers &rest body) "Save and restore outline visibility around BODY. If USE-MARKERS is non-nil, use markers for the positions. @@ -313,19 +301,15 @@ but it also means that the buffer should stay alive during the operation, because otherwise all these markers will point nowhere." (declare (debug (form body)) (indent 1)) - (org-with-gensyms (data rtn) - `(let ((,data (org-outline-overlay-data ,use-markers)) - ,rtn) + (org-with-gensyms (data) + `(let ((,data (org-outline-overlay-data ,use-markers))) (unwind-protect - (progn - (setq ,rtn (progn ,@body)) + (prog1 (progn ,@body) (org-set-outline-overlay-data ,data)) (when ,use-markers - (mapc (lambda (c) - (and (markerp (car c)) (move-marker (car c) nil)) - (and (markerp (cdr c)) (move-marker (cdr c) nil))) - ,data))) - ,rtn))) + (dolist (c ,data) + (when (markerp (car c)) (move-marker (car c) nil)) + (when (markerp (cdr c)) (move-marker (cdr c) nil)))))))) (defmacro org-with-wide-buffer (&rest body) "Execute body while temporarily widening the buffer." @@ -355,17 +339,16 @@ point nowhere." (defun org-get-limited-outline-regexp () "Return outline-regexp with limited number of levels. The number of levels is controlled by `org-inlinetask-min-level'" - (if (or (not (derived-mode-p 'org-mode)) (not (featurep 'org-inlinetask))) - org-outline-regexp - (let* ((limit-level (1- org-inlinetask-min-level)) - (nstars (if org-odd-levels-only (1- (* limit-level 2)) limit-level))) - (format "\\*\\{1,%d\\} " nstars)))) - -(defun org-format-seconds (string seconds) - "Compatibility function replacing format-seconds." - (if (fboundp 'format-seconds) - (format-seconds string seconds) - (format-time-string string (seconds-to-time seconds)))) + (cond ((not (derived-mode-p 'org-mode)) + outline-regexp) + ((not (featurep 'org-inlinetask)) + org-outline-regexp) + (t + (let* ((limit-level (1- org-inlinetask-min-level)) + (nstars (if org-odd-levels-only + (1- (* limit-level 2)) + limit-level))) + (format "\\*\\{1,%d\\} " nstars))))) (defmacro org-eval-in-environment (environment form) (declare (debug (form form)) (indent 1)) @@ -382,10 +365,64 @@ the value in cdr." ;;;###autoload (defmacro org-load-noerror-mustsuffix (file) - "Load FILE with optional arguments NOERROR and MUSTSUFFIX. Drop the MUSTSUFFIX argument for XEmacs, which doesn't recognize it." - (if (featurep 'xemacs) - `(load ,file 'noerror) - `(load ,file 'noerror nil nil 'mustsuffix))) + "Load FILE with optional arguments NOERROR and MUSTSUFFIX." + `(load ,file 'noerror nil nil 'mustsuffix)) + +(defun org-unbracket-string (pre post string) + "Remove PRE/POST from the beginning/end of STRING. +Both PRE and POST must be pre-/suffixes of STRING, or neither is +removed." + (if (and (string-prefix-p pre string) + (string-suffix-p post string)) + (substring string (length pre) (- (length post))) + string)) + +(defun org-read-function (prompt &optional allow-empty?) + "Prompt for a function. +If ALLOW-EMPTY? is non-nil, return nil rather than raising an +error when the user input is empty." + (let ((func (completing-read prompt obarray #'fboundp t))) + (cond ((not (string= func "")) + (intern func)) + (allow-empty? nil) + (t (user-error "Empty input is not valid"))))) + +(defconst org-unique-local-variables + '(org-element--cache + org-element--cache-objects + org-element--cache-sync-keys + org-element--cache-sync-requests + org-element--cache-sync-timer) + "List of local variables that cannot be transferred to another buffer.") + +(defun org-get-local-variables () + "Return a list of all local variables in an Org mode buffer." + (delq nil + (mapcar + (lambda (x) + (let* ((binding (if (symbolp x) (list x) (list (car x) (cdr x)))) + (name (car binding))) + (and (not (get name 'org-state)) + (not (memq name org-unique-local-variables)) + (string-match-p + "\\`\\(org-\\|orgtbl-\\|outline-\\|comment-\\|paragraph-\\|\ +auto-fill\\|normal-auto-fill\\|fill-paragraph\\|indent-\\)" + (symbol-name name)) + binding))) + (with-temp-buffer + (org-mode) + (buffer-local-variables))))) + +(defun org-clone-local-variables (from-buffer &optional regexp) + "Clone local variables from FROM-BUFFER. +Optional argument REGEXP selects variables to clone." + (dolist (pair (buffer-local-variables from-buffer)) + (pcase pair + (`(,name . ,value) ;ignore unbound variables + (when (and (not (memq name org-unique-local-variables)) + (or (null regexp) (string-match-p regexp (symbol-name name)))) + (ignore-errors (set (make-local-variable name) value))))))) + (provide 'org-macs) diff --git a/lisp/org/org-mhe.el b/lisp/org/org-mhe.el index d1067cd57e9..f06fea7777d 100644 --- a/lisp/org/org-mhe.el +++ b/lisp/org/org-mhe.el @@ -1,4 +1,4 @@ -;;; org-mhe.el --- Support for links to MH-E messages from within Org-mode +;;; org-mhe.el --- Support for Links to MH-E Messages -*- lexical-binding: t; -*- ;; Copyright (C) 2004-2017 Free Software Foundation, Inc. @@ -19,13 +19,13 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Commentary: -;; This file implements links to MH-E messages from within Org-mode. -;; Org-mode loads this module by default - if this is not what you want, +;; This file implements links to MH-E messages from within Org. +;; Org mode loads this module by default - if this is not what you want, ;; configure the variable `org-modules'. ;;; Code: @@ -74,34 +74,25 @@ supported by MH-E." (defvar mh-search-regexp-builder) ;; Install the link type -(org-add-link-type "mhe" 'org-mhe-open) -(add-hook 'org-store-link-functions 'org-mhe-store-link) +(org-link-set-parameters "mhe" :follow #'org-mhe-open :store #'org-mhe-store-link) ;; Implementation (defun org-mhe-store-link () "Store a link to an MH-E folder or message." - (when (or (equal major-mode 'mh-folder-mode) - (equal major-mode 'mh-show-mode)) + (when (or (eq major-mode 'mh-folder-mode) + (eq major-mode 'mh-show-mode)) (save-window-excursion (let* ((from (org-mhe-get-header "From:")) (to (org-mhe-get-header "To:")) (message-id (org-mhe-get-header "Message-Id:")) (subject (org-mhe-get-header "Subject:")) (date (org-mhe-get-header "Date:")) - (date-ts (and date (format-time-string - (org-time-stamp-format t) (date-to-time date)))) - (date-ts-ia (and date (format-time-string - (org-time-stamp-format t t) - (date-to-time date)))) link desc) - (org-store-link-props :type "mh" :from from :to to + (org-store-link-props :type "mh" :from from :to to :date date :subject subject :message-id message-id) - (when date - (org-add-link-props :date date :date-timestamp date-ts - :date-timestamp-inactive date-ts-ia)) (setq desc (org-email-link-description)) (setq link (concat "mhe:" (org-mhe-get-message-real-folder) "#" - (org-remove-angle-brackets message-id))) + (org-unbracket-string "<" ">" message-id))) (org-add-link-props :link link :description desc) link)))) @@ -120,7 +111,7 @@ supported by MH-E." So if you use sequences, it will now work." (save-excursion (let* ((folder - (if (equal major-mode 'mh-folder-mode) + (if (eq major-mode 'mh-folder-mode) mh-current-folder ;; Refer to the show buffer mh-show-folder-buffer)) @@ -132,7 +123,7 @@ So if you use sequences, it will now work." ;; mh-index-data is always nil in a show buffer. (if (and (boundp 'mh-index-folder) (string= mh-index-folder (substring folder 0 end-index))) - (if (equal major-mode 'mh-show-mode) + (if (eq major-mode 'mh-show-mode) (save-window-excursion (let (pop-up-frames) (when (buffer-live-p (get-buffer folder)) @@ -158,7 +149,7 @@ So if you use sequences, it will now work." "Return the name of the current message folder. Be careful if you use sequences." (save-excursion - (if (equal major-mode 'mh-folder-mode) + (if (eq major-mode 'mh-folder-mode) mh-current-folder ;; Refer to the show buffer mh-show-folder-buffer))) @@ -167,7 +158,7 @@ Be careful if you use sequences." "Return the number of the current message. Be careful if you use sequences." (save-excursion - (if (equal major-mode 'mh-folder-mode) + (if (eq major-mode 'mh-folder-mode) (mh-get-msg-num nil) ;; Refer to the show buffer (mh-show-buffer-message-number)))) @@ -182,12 +173,12 @@ you have a better idea of how to do this then please let us know." (header-field)) (with-current-buffer buffer (mh-display-msg num folder) - (if (equal major-mode 'mh-folder-mode) + (if (eq major-mode 'mh-folder-mode) (mh-header-display) (mh-show-header-display)) (set-buffer buffer) (setq header-field (mh-get-header-field header)) - (if (equal major-mode 'mh-folder-mode) + (if (eq major-mode 'mh-folder-mode) (mh-show) (mh-show-show)) (org-trim header-field)))) @@ -206,13 +197,13 @@ folders." (if (not article) (mh-visit-folder (mh-normalize-folder-name folder)) (mh-search-choose) - (if (equal mh-searcher 'pick) + (if (eq mh-searcher 'pick) (progn (setq article (org-add-angle-brackets article)) (mh-search folder (list "--message-id" article)) (when (and org-mhe-search-all-folders (not (org-mhe-get-message-real-folder))) - (kill-current-buffer) + (kill-buffer) (mh-search "+" (list "--message-id" article)))) (if mh-search-regexp-builder (mh-search "+" (funcall mh-search-regexp-builder @@ -220,7 +211,7 @@ folders." (mh-search "+" article))) (if (org-mhe-get-message-real-folder) (mh-show-msg 1) - (kill-current-buffer) + (kill-buffer) (error "Message not found")))) (provide 'org-mhe) diff --git a/lisp/org/org-mobile.el b/lisp/org/org-mobile.el index 34e6af10d81..a548930c0f9 100644 --- a/lisp/org/org-mobile.el +++ b/lisp/org/org-mobile.el @@ -1,4 +1,4 @@ -;;; org-mobile.el --- Code for asymmetric sync with a mobile device +;;; org-mobile.el --- Code for Asymmetric Sync With a Mobile Device -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2017 Free Software Foundation, Inc. ;; ;; Author: Carsten Dominik <carsten at orgmode dot org> @@ -18,27 +18,26 @@ ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Commentary: ;; -;; This file contains the code to interact with Richard Moreland's iPhone -;; application MobileOrg, as well as with the Android version by Matthew Jones. -;; This code is documented in Appendix B of the Org-mode manual. The code is -;; not specific for the iPhone and Android - any external -;; viewer/flagging/editing application that uses the same conventions could -;; be used. +;; This file contains the code to interact with Richard Moreland's +;; iPhone application MobileOrg, as well as with the Android version +;; by Matthew Jones. This code is documented in Appendix B of the Org +;; manual. The code is not specific for the iPhone and Android - any +;; external viewer/flagging/editing application that uses the same +;; conventions could be used. (require 'org) (require 'org-agenda) -;;; Code: +(require 'cl-lib) -(eval-when-compile (require 'cl)) +(defvar org-agenda-keep-restricted-file-list) -(declare-function org-pop-to-buffer-same-window - "org-compat" (&optional buffer-or-name norecord label)) +;;; Code: (defgroup org-mobile nil "Options concerning support for a viewer/editor on a mobile device." @@ -192,27 +191,6 @@ the editing types for which the mobile version should always dominate." (const heading) (const body)))) -(defcustom org-mobile-action-alist - '(("edit" . (org-mobile-edit data old new))) - "Alist with flags and actions for mobile sync. -When flagging an entry, MobileOrg will create entries that look like - - * F(action:data) [[id:entry-id][entry title]] - -This alist defines that the ACTION in the parentheses of F() should mean, -i.e. what action should be taken. The :data part in the parenthesis is -optional. If present, the string after the colon will be passed to the -action form as the `data' variable. -The car of each elements of the alist is an actions string. The cdr is -an Emacs Lisp form that will be evaluated with the cursor on the headline -of that entry. - -For now, it is not recommended to change this variable." - :group 'org-mobile - :type '(repeat - (cons (string :tag "Action flag") - (sexp :tag "Action form")))) - (defcustom org-mobile-checksum-binary (or (executable-find "shasum") (executable-find "sha1sum") (executable-find "md5sum") @@ -249,6 +227,23 @@ by the mobile device, this hook should be used to copy the emptied capture file `mobileorg.org' back to the WebDAV directory, for example using `rsync' or `scp'.") +(defconst org-mobile-action-alist '(("edit" . org-mobile-edit)) + "Alist with flags and actions for mobile sync. +When flagging an entry, MobileOrg will create entries that look like + + * F(action:data) [[id:entry-id][entry title]] + +This alist defines that the ACTION in the parentheses of F() +should mean, i.e. what action should be taken. The :data part in +the parenthesis is optional. If present, the string after the +colon will be passed to the action function as the first argument +variable. + +The car of each elements of the alist is an actions string. The +cdr is a function that is called with the cursor on the headline +of that entry. It should accept three arguments, the :data part, +the old and new values for the entry.") + (defvar org-mobile-last-flagged-files nil "List of files containing entries flagged in the latest pull.") @@ -313,40 +308,29 @@ Also exclude files matching `org-mobile-files-exclude-regexp'." This will create the index file, copy all agenda files there, and also create all custom agenda views, for upload to the mobile phone." (interactive) - (let ((a-buffer (get-buffer org-agenda-buffer-name))) - (let ((org-agenda-curbuf-name org-agenda-buffer-name) - (org-agenda-buffer-name "*SUMO*") - (org-agenda-tag-filter org-agenda-tag-filter) - (org-agenda-redo-command org-agenda-redo-command)) - (save-excursion - (save-restriction - (save-window-excursion - (run-hooks 'org-mobile-pre-push-hook) - (org-mobile-check-setup) - (org-mobile-prepare-file-lists) - (message "Creating agendas...") - (let ((inhibit-redisplay t) - (org-agenda-files (mapcar 'car org-mobile-files-alist))) - (org-mobile-create-sumo-agenda)) - (message "Creating agendas...done") - (org-save-all-org-buffers) ; to save any IDs created by this process - (message "Copying files...") - (org-mobile-copy-agenda-files) - (message "Writing index file...") - (org-mobile-create-index-file) - (message "Writing checksums...") - (org-mobile-write-checksums) - (run-hooks 'org-mobile-post-push-hook)))) - (setq org-agenda-buffer-name org-agenda-curbuf-name - org-agenda-this-buffer-name org-agenda-curbuf-name)) - (redraw-display) - (when (buffer-live-p a-buffer) - (if (not (get-buffer-window a-buffer)) - (kill-buffer a-buffer) - (let ((cw (selected-window))) - (select-window (get-buffer-window a-buffer)) - (org-agenda-redo) - (select-window cw))))) + (let ((org-agenda-buffer-name "*SUMO*") + (org-agenda-tag-filter org-agenda-tag-filter) + (org-agenda-redo-command org-agenda-redo-command)) + (save-excursion + (save-restriction + (save-window-excursion + (run-hooks 'org-mobile-pre-push-hook) + (org-mobile-check-setup) + (org-mobile-prepare-file-lists) + (message "Creating agendas...") + (let ((inhibit-redisplay t) + (org-agenda-files (mapcar 'car org-mobile-files-alist))) + (org-mobile-create-sumo-agenda)) + (message "Creating agendas...done") + (org-save-all-org-buffers) ; to save any IDs created by this process + (message "Copying files...") + (org-mobile-copy-agenda-files) + (message "Writing index file...") + (org-mobile-create-index-file) + (message "Writing checksums...") + (org-mobile-write-checksums) + (run-hooks 'org-mobile-post-push-hook))))) + (org-agenda-maybe-redo) (message "Files for mobile viewer staged")) (defvar org-mobile-before-process-capture-hook nil @@ -422,10 +406,10 @@ agenda view showing the flagged items." (let ((files-alist (sort (copy-sequence org-mobile-files-alist) (lambda (a b) (string< (cdr a) (cdr b))))) (def-todo (default-value 'org-todo-keywords)) - (def-tags (default-value 'org-tag-alist)) + (def-tags org-tag-alist) (target-file (expand-file-name org-mobile-index-file org-mobile-directory)) - file link-name todo-kwds done-kwds tags drawers entry kwds dwds twds) + todo-kwds done-kwds tags) (when (stringp (car def-todo)) (setq def-todo (list (cons 'sequence def-todo)))) (org-agenda-prepare-buffers (mapcar 'car files-alist)) @@ -433,52 +417,36 @@ agenda view showing the flagged items." (setq todo-kwds (org-delete-all done-kwds (org-uniquify org-todo-keywords-for-agenda))) - (setq drawers (org-uniquify org-drawers-for-agenda)) (setq tags (mapcar 'car (org-global-tags-completion-table (mapcar 'car files-alist)))) - (with-temp-file - (if org-mobile-use-encryption - org-mobile-encryption-tempfile - target-file) - (while (setq entry (pop def-todo)) - (insert "#+READONLY\n") - (setq kwds (mapcar (lambda (x) (if (string-match "(" x) - (substring x 0 (match-beginning 0)) - x)) - (cdr entry))) - (insert "#+TODO: " (mapconcat 'identity kwds " ") "\n") - (setq dwds (member "|" kwds) - twds (org-delete-all dwds kwds) - todo-kwds (org-delete-all twds todo-kwds) - done-kwds (org-delete-all dwds done-kwds))) + (with-temp-file (if org-mobile-use-encryption org-mobile-encryption-tempfile + target-file) + (insert "#+READONLY\n") + (dolist (entry def-todo) + (let ((kwds (mapcar (lambda (x) + (if (string-match "(" x) + (substring x 0 (match-beginning 0)) + x)) + (cdr entry)))) + (insert "#+TODO: " (mapconcat #'identity kwds " ") "\n") + (let* ((dwds (or (member "|" kwds) (last kwds))) + (twds (org-delete-all dwds kwds))) + (setq todo-kwds (org-delete-all twds todo-kwds)) + (setq done-kwds (org-delete-all dwds done-kwds))))) (when (or todo-kwds done-kwds) (insert "#+TODO: " (mapconcat 'identity todo-kwds " ") " | " (mapconcat 'identity done-kwds " ") "\n")) - (setq def-tags (mapcar - (lambda (x) - (cond ((null x) nil) - ((stringp x) x) - ((eq (car x) :startgroup) "{") - ((eq (car x) :endgroup) "}") - ((eq (car x) :grouptags) nil) - ((eq (car x) :newline) nil) - ((listp x) (car x)))) - def-tags)) - (setq def-tags (delq nil def-tags)) + (setq def-tags (split-string (org-tag-alist-to-string def-tags t))) (setq tags (org-delete-all def-tags tags)) (setq tags (sort tags (lambda (a b) (string< (downcase a) (downcase b))))) (setq tags (append def-tags tags nil)) (insert "#+TAGS: " (mapconcat 'identity tags " ") "\n") - (insert "#+DRAWERS: " (mapconcat 'identity drawers " ") "\n") (insert "#+ALLPRIORITIES: " org-mobile-allpriorities "\n") (when (file-exists-p (expand-file-name org-mobile-directory "agendas.org")) (insert "* [[file:agendas.org][Agenda Views]]\n")) - (while (setq entry (pop files-alist)) - (setq file (car entry) - link-name (cdr entry)) - (insert (format "* [[file:%s][%s]]\n" - link-name link-name))) + (pcase-dolist (`(,_ . ,link-name) files-alist) + (insert (format "* [[file:%s][%s]]\n" link-name link-name))) (push (cons org-mobile-index-file (md5 (buffer-string))) org-mobile-checksum-files)) (when org-mobile-use-encryption @@ -499,9 +467,10 @@ agenda view showing the flagged items." (make-directory target-dir 'parents)) (if org-mobile-use-encryption (org-mobile-encrypt-and-move file target-path) - (copy-file file target-path 'ok-if-exists)) + (copy-file file target-path 'ok-if-already-exists)) (setq check (shell-command-to-string - (concat org-mobile-checksum-binary " " + (concat (shell-quote-argument org-mobile-checksum-binary) + " " (shell-quote-argument (expand-file-name file))))) (when (string-match "[a-fA-F0-9]\\{30,40\\}" check) (push (cons link-name (match-string 0 check)) @@ -663,7 +632,7 @@ The table of checksums is written to the file mobile-checksums." m 10 " " 'planning) "\n") (when (setq id - (if (org-bound-and-true-p + (if (bound-and-true-p org-mobile-force-id-on-agenda-items) (org-id-get m 'create) (or (org-entry-get m "ID") @@ -679,7 +648,7 @@ The table of checksums is written to the file mobile-checksums." (org-with-point-at pom (concat "olp:" (org-mobile-escape-olp (file-name-nondirectory buffer-file-name)) - "/" + ":" (mapconcat 'org-mobile-escape-olp (org-get-outline-path) "/") @@ -718,13 +687,13 @@ encryption program does not understand them." (let ((encfile (concat infile "_enc"))) (org-mobile-encrypt-file infile encfile) (when outfile - (copy-file encfile outfile 'ok-if-exists) + (copy-file encfile outfile 'ok-if-already-exists) (delete-file encfile)))) (defun org-mobile-encrypt-file (infile outfile) "Encrypt INFILE to OUTFILE, using `org-mobile-encryption-password'." (shell-command - (format "openssl enc -aes-256-cbc -salt -pass %s -in %s -out %s" + (format "openssl enc -md md5 -aes-256-cbc -salt -pass %s -in %s -out %s" (shell-quote-argument (concat "pass:" (org-mobile-encryption-password))) (shell-quote-argument (expand-file-name infile)) @@ -733,7 +702,7 @@ encryption program does not understand them." (defun org-mobile-decrypt-file (infile outfile) "Decrypt INFILE to OUTFILE, using `org-mobile-encryption-password'." (shell-command - (format "openssl enc -d -aes-256-cbc -salt -pass %s -in %s -out %s" + (format "openssl enc -md md5 -d -aes-256-cbc -salt -pass %s -in %s -out %s" (shell-quote-argument (concat "pass:" (org-mobile-encryption-password))) (shell-quote-argument (expand-file-name infile)) @@ -823,14 +792,14 @@ If BEG and END are given, only do this in that region." (cnt-flag 0) (cnt-error 0) buf-list - id-pos org-mobile-error) + org-mobile-error) ;; Count the new captures (goto-char beg) (while (re-search-forward "^\\* \\(.*\\)" end t) (and (>= (- (match-end 1) (match-beginning 1)) 2) (not (equal (downcase (substring (match-string 1) 0 2)) "f(")) - (incf cnt-new))) + (cl-incf cnt-new))) ;; Find and apply the edits (goto-char beg) @@ -842,19 +811,21 @@ If BEG and END are given, only do this in that region." (id-pos (condition-case msg (org-mobile-locate-entry (match-string 4)) (error (nth 1 msg)))) - (bos (point-at-bol)) + (bos (line-beginning-position)) (eos (save-excursion (org-end-of-subtree t t))) (cmd (if (equal action "") - '(progn - (incf cnt-flag) - (org-toggle-tag "FLAGGED" 'on) - (and note - (org-entry-put nil "THEFLAGGINGNOTE" note))) - (incf cnt-edit) + (let ((note (buffer-substring-no-properties + (line-beginning-position 2) eos))) + (lambda (_data _old _new) + (cl-incf cnt-flag) + (org-toggle-tag "FLAGGED" 'on) + (org-entry-put + nil "THEFLAGGINGNOTE" + (replace-regexp-in-string "\n" "\\\\n" note)))) + (cl-incf cnt-edit) (cdr (assoc action org-mobile-action-alist)))) - (note (and (equal action "") - (buffer-substring (1+ (point-at-eol)) eos))) - (org-inhibit-logging 'note) ;; Do not take notes interactively + ;; Do not take notes interactively. + (org-inhibit-logging 'note) old new) (goto-char bos) @@ -867,11 +838,11 @@ If BEG and END are given, only do this in that region." (if (stringp id-pos) (insert id-pos " ") (insert "BAD REFERENCE ")) - (incf cnt-error) + (cl-incf cnt-error) (throw 'next t)) (unless cmd (insert "BAD FLAG ") - (incf cnt-error) + (cl-incf cnt-error) (throw 'next t)) (move-marker bos-marker (point)) (if (re-search-forward "^** Old value[ \t]*$" eos t) @@ -884,34 +855,28 @@ If BEG and END are given, only do this in that region." (progn (outline-next-heading) (if (eobp) (org-back-over-empty-lines)) (point))))) - (setq old (and old (if (string-match "\\S-" old) old nil))) - (setq new (and new (if (string-match "\\S-" new) new nil))) - (if (and note (> (length note) 0)) - ;; Make Note into a single line, to fit into a property - (setq note (mapconcat 'identity - (org-split-string (org-trim note) "\n") - "\\n"))) + (setq old (org-string-nw-p old)) + (setq new (org-string-nw-p new)) (unless (equal data "body") - (setq new (and new (org-trim new)) - old (and old (org-trim old)))) + (setq new (and new (org-trim new))) + (setq old (and old (org-trim old)))) (goto-char (+ 2 bos-marker)) ;; Remember this place so that we can return (move-marker marker (point)) (setq org-mobile-error nil) - (save-excursion - (condition-case msg - (org-with-point-at id-pos - (progn - (eval cmd) - (unless (member data (list "delete" "archive" "archive-sibling" "addheading")) - (if (member "FLAGGED" (org-get-tags)) - (add-to-list 'org-mobile-last-flagged-files - (buffer-file-name (current-buffer))))))) - (error (setq org-mobile-error msg)))) + (condition-case msg + (org-with-point-at id-pos + (funcall cmd data old new) + (unless (member data '("delete" "archive" "archive-sibling" + "addheading")) + (when (member "FLAGGED" (org-get-tags)) + (add-to-list 'org-mobile-last-flagged-files + (buffer-file-name))))) + (error (setq org-mobile-error msg))) (when org-mobile-error - (org-pop-to-buffer-same-window (marker-buffer marker)) + (pop-to-buffer-same-window (marker-buffer marker)) (goto-char marker) - (incf cnt-error) + (cl-incf cnt-error) (insert (if (stringp (nth 1 org-mobile-error)) (nth 1 org-mobile-error) "EXECUTION FAILED") @@ -924,8 +889,8 @@ If BEG and END are given, only do this in that region." (save-buffer) (move-marker marker nil) (move-marker end nil) - (message "%d new, %d edits, %d flags, %d errors" cnt-new - cnt-edit cnt-flag cnt-error) + (message "%d new, %d edits, %d flags, %d errors" + cnt-new cnt-edit cnt-flag cnt-error) (sit-for 1))) (defun org-mobile-timestamp-buffer (buf) @@ -1020,7 +985,7 @@ be returned that indicates what went wrong." ((equal new "DONEARCHIVE") (org-todo 'done) (org-archive-subtree-default)) - ((equal new current) t) ; nothing needs to be done + ((equal new current) t) ; nothing needs to be done ((or (equal current old) (eq org-mobile-force-mobile-change t) (memq 'todo org-mobile-force-mobile-change)) @@ -1042,33 +1007,35 @@ be returned that indicates what went wrong." (or old "") (or current ""))))) ((eq what 'priority) - (when (looking-at org-complex-heading-regexp) - (setq current (and (match-end 3) (substring (match-string 3) 2 3))) - (cond - ((equal current new) t) ; no action required - ((or (equal current old) - (eq org-mobile-force-mobile-change t) - (memq 'tags org-mobile-force-mobile-change)) - (org-priority (and new (string-to-char new)))) - (t (error "Priority was expected to be %s, but is %s" - old current))))) + (let ((case-fold-search nil)) + (when (looking-at org-complex-heading-regexp) + (let ((current (and (match-end 3) (substring (match-string 3) 2 3)))) + (cond + ((equal current new) t) ;no action required + ((or (equal current old) + (eq org-mobile-force-mobile-change t) + (memq 'tags org-mobile-force-mobile-change)) + (org-priority (and new (string-to-char new)))) + (t (error "Priority was expected to be %s, but is %s" + old current))))))) ((eq what 'heading) - (when (looking-at org-complex-heading-regexp) - (setq current (match-string 4)) - (cond - ((equal current new) t) ; no action required - ((or (equal current old) - (eq org-mobile-force-mobile-change t) - (memq 'heading org-mobile-force-mobile-change)) - (goto-char (match-beginning 4)) - (insert new) - (delete-region (point) (+ (point) (length current))) - (org-set-tags nil 'align)) - (t (error "Heading changed in MobileOrg and on the computer"))))) + (let ((case-fold-search nil)) + (when (looking-at org-complex-heading-regexp) + (let ((current (match-string 4))) + (cond + ((equal current new) t) ;no action required + ((or (equal current old) + (eq org-mobile-force-mobile-change t) + (memq 'heading org-mobile-force-mobile-change)) + (goto-char (match-beginning 4)) + (insert new) + (delete-region (point) (+ (point) (length current))) + (org-set-tags nil 'align)) + (t (error "Heading changed in MobileOrg and on the computer"))))))) ((eq what 'addheading) - (if (org-at-heading-p) ; if false we are in top-level of file + (if (org-at-heading-p) ; if false we are in top-level of file (progn ;; Workaround a `org-insert-heading-respect-content' bug ;; which prevents correct insertion when point is invisible @@ -1083,7 +1050,7 @@ be returned that indicates what went wrong." ((eq what 'refile) (org-copy-subtree) (org-with-point-at (org-mobile-locate-entry new) - (if (org-at-heading-p) ; if false we are in top-level of file + (if (org-at-heading-p) ; if false we are in top-level of file (progn (setq level (org-get-valid-level (funcall outline-level) 1)) (org-end-of-subtree t t) diff --git a/lisp/org/org-mouse.el b/lisp/org/org-mouse.el index 7eef5c6b8ba..7c982423228 100644 --- a/lisp/org/org-mouse.el +++ b/lisp/org/org-mouse.el @@ -1,4 +1,4 @@ -;;; org-mouse.el --- Better mouse support for org-mode +;;; org-mouse.el --- Better mouse support for Org -*- lexical-binding: t; -*- ;; Copyright (C) 2006-2017 Free Software Foundation, Inc. @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -26,8 +26,8 @@ ;; ;; http://orgmode.org ;; -;; Org-mouse implements the following features: -;; * following links with the left mouse button (in Emacs 22) +;; Org mouse implements the following features: +;; * following links with the left mouse button ;; * subtree expansion/collapse (org-cycle) with the left mouse button ;; * several context menus on the right mouse button: ;; + general text @@ -66,12 +66,12 @@ ;; History: ;; -;; Since version 5.10: Changes are listed in the general org-mode docs. +;; Since version 5.10: Changes are listed in the general Org docs. ;; -;; Version 5.09;; + Version number synchronization with Org-mode. +;; Version 5.09;; + Version number synchronization with Org mode. ;; ;; Version 0.25 -;; + made compatible with org-mode 4.70 (thanks to Carsten for the patch) +;; + made compatible with Org 4.70 (thanks to Carsten for the patch) ;; ;; Version 0.24 ;; + minor changes to the table menu @@ -81,7 +81,7 @@ ;; + context menu support for org-agenda-undo & org-sort-entries ;; ;; Version 0.22 -;; + handles undo support for the agenda buffer (requires org-mode >=4.58) +;; + handles undo support for the agenda buffer (requires Org >=4.58) ;; ;; Version 0.21 ;; + selected text activates its context menu @@ -105,7 +105,7 @@ ;; + added support for checkboxes ;; ;; Version 0.15 -;; + org-mode now works with the Agenda buffer as well +;; + Org now works with the Agenda buffer as well ;; ;; Version 0.14 ;; + added a menu option that converts plain list items to outline items @@ -125,7 +125,7 @@ ;; ;; Version 0.10 ;; + added a menu option to remove highlights -;; + compatible with org-mode 4.21 now +;; + compatible with Org 4.21 now ;; ;; Version 0.08: ;; + trees can be moved/promoted/demoted by dragging with the right @@ -136,8 +136,8 @@ ;;; Code: -(eval-when-compile (require 'cl)) (require 'org) +(require 'cl-lib) (defvar org-agenda-allow-remote-undo) (defvar org-agenda-undo-list) @@ -149,6 +149,8 @@ (declare-function org-agenda-earlier "org-agenda" (arg)) (declare-function org-agenda-later "org-agenda" (arg)) +(defvar org-mouse-main-buffer nil + "Active buffer for mouse operations.") (defvar org-mouse-plain-list-regexp "\\([ \t]*\\)\\([-+*]\\|[0-9]+[.)]\\) " "Regular expression that matches a plain list.") (defvar org-mouse-direct t @@ -191,15 +193,14 @@ Changing this variable requires a restart of Emacs to get activated." (interactive) (end-of-line) (skip-chars-backward "\t ") - (when (org-looking-back ":[A-Za-z]+:" (line-beginning-position)) + (when (looking-back ":[A-Za-z]+:" (line-beginning-position)) (skip-chars-backward ":A-Za-z") (skip-chars-backward "\t "))) -(defvar org-mouse-context-menu-function nil +(defvar-local org-mouse-context-menu-function nil "Function to create the context menu. The value of this variable is the function invoked by `org-mouse-context-menu' as the context menu.") -(make-variable-buffer-local 'org-mouse-context-menu-function) (defun org-mouse-show-context-menu (event prefix) "Invoke the context menu. @@ -215,13 +216,12 @@ this function is called. Otherwise, the current major mode menu is used." (when (not (org-mouse-mark-active)) (goto-char (posn-point (event-start event))) (when (not (eolp)) (save-excursion (run-hooks 'post-command-hook))) - (let ((redisplay-dont-pause t)) - (sit-for 0))) + (sit-for 0)) (if (functionp org-mouse-context-menu-function) (funcall org-mouse-context-menu-function event) (if (fboundp 'mouse-menu-major-mode-map) (popup-menu (mouse-menu-major-mode-map) event prefix) - (org-no-warnings ; don't warn about fallback, obsolete since 23.1 + (with-no-warnings ; don't warn about fallback, obsolete since 23.1 (mouse-major-mode-menu event prefix))))) (setq this-command 'mouse-save-then-kill) (mouse-save-then-kill event))) @@ -258,7 +258,7 @@ If the point is at the :beginning (`org-mouse-line-position') of the line, insert the new heading before the current line. Otherwise, insert it after the current heading." (interactive) - (case (org-mouse-line-position) + (cl-case (org-mouse-line-position) (:beginning (beginning-of-line) (org-insert-heading)) (t (org-mouse-next-heading) @@ -314,10 +314,10 @@ nor a function, elements of KEYWORDS are used directly." (just-one-space)) (defvar org-mouse-rest) -(defun org-mouse-replace-match-and-surround (newtext &optional fixedcase - literal string subexp) +(defun org-mouse-replace-match-and-surround + (_newtext &optional _fixedcase _literal _string subexp) "The same as `replace-match', but surrounds the replacement with spaces." - (apply 'replace-match org-mouse-rest) + (apply #'replace-match org-mouse-rest) (save-excursion (goto-char (match-beginning (or subexp 0))) (just-one-space) @@ -391,8 +391,8 @@ DEFAULT is returned if no priority is given in the headline." (defun org-mouse-delete-timestamp () "Deletes the current timestamp as well as the preceding keyword. SCHEDULED: or DEADLINE: or ANYTHINGLIKETHIS:" - (when (or (org-at-date-range-p) (org-at-timestamp-p)) - (replace-match "") ; delete the timestamp + (when (or (org-at-date-range-p) (org-at-timestamp-p 'lax)) + (replace-match "") ;delete the timestamp (skip-chars-backward " :A-Z") (when (looking-at " *[A-Z][A-Z]+:") (replace-match "")))) @@ -407,8 +407,8 @@ SCHEDULED: or DEADLINE: or ANYTHINGLIKETHIS:" (> (match-end 0) point)))))) (defun org-mouse-priority-list () - (loop for priority from ?A to org-lowest-priority - collect (char-to-string priority))) + (cl-loop for priority from ?A to org-lowest-priority + collect (char-to-string priority))) (defun org-mouse-todo-menu (state) "Create the menu with TODO keywords." @@ -460,33 +460,33 @@ SCHEDULED: or DEADLINE: or ANYTHINGLIKETHIS:" (insert " [ ] ")))) (defun org-mouse-agenda-type (type) - (case type - ('tags "Tags: ") - ('todo "TODO: ") - ('tags-tree "Tags tree: ") - ('todo-tree "TODO tree: ") - ('occur-tree "Occur tree: ") - (t "Agenda command ???"))) + (pcase type + (`tags "Tags: ") + (`todo "TODO: ") + (`tags-tree "Tags tree: ") + (`todo-tree "TODO tree: ") + (`occur-tree "Occur tree: ") + (_ "Agenda command ???"))) (defun org-mouse-list-options-menu (alloptions &optional function) (let ((options (save-match-data (split-string (match-string-no-properties 1))))) (print options) - (loop for name in alloptions - collect - (vector name - `(progn - (replace-match - (mapconcat 'identity - (sort (if (member ',name ',options) - (delete ',name ',options) - (cons ',name ',options)) - 'string-lessp) - " ") - nil nil nil 1) - (when (functionp ',function) (funcall ',function))) - :style 'toggle - :selected (and (member name options) t))))) + (cl-loop for name in alloptions + collect + (vector name + `(progn + (replace-match + (mapconcat 'identity + (sort (if (member ',name ',options) + (delete ',name ',options) + (cons ',name ',options)) + 'string-lessp) + " ") + nil nil nil 1) + (when (functionp ',function) (funcall ',function))) + :style 'toggle + :selected (and (member name options) t))))) (defun org-mouse-clip-text (text maxlength) (if (> (length text) maxlength) @@ -498,7 +498,7 @@ SCHEDULED: or DEADLINE: or ANYTHINGLIKETHIS:" `("Main Menu" ["Show Overview" org-mouse-show-overview t] ["Show Headlines" org-mouse-show-headlines t] - ["Show All" show-all t] + ["Show All" outline-show-all t] ["Remove Highlights" org-remove-occur-highlights :visible org-occur-highlights] "--" @@ -516,7 +516,6 @@ SCHEDULED: or DEADLINE: or ANYTHINGLIKETHIS:" ["Check Phrase ..." org-occur] "--" ["Display Agenda" org-agenda-list t] - ["Display Timeline" org-timeline t] ["Display TODO List" org-todo-list t] ("Display Tags" ,@(org-mouse-keyword-menu @@ -556,12 +555,12 @@ SCHEDULED: or DEADLINE: or ANYTHINGLIKETHIS:" (let ((contextdata (assq context contextlist))) (when contextdata (save-excursion - (goto-char (second contextdata)) - (re-search-forward ".*" (third contextdata)))))) + (goto-char (nth 1 contextdata)) + (re-search-forward ".*" (nth 2 contextdata)))))) (defun org-mouse-for-each-item (funct) - ;; Functions called by `org-apply-on-list' need an argument - (let ((wrap-fun (lambda (c) (funcall funct)))) + ;; Functions called by `org-apply-on-list' need an argument. + (let ((wrap-fun (lambda (_) (funcall funct)))) (when (ignore-errors (goto-char (org-in-item-p))) (save-excursion (org-apply-on-list wrap-fun nil))))) @@ -572,14 +571,14 @@ This means, between the beginning of line and the point." (skip-chars-backward " \t*") (bolp))) (defun org-mouse-insert-item (text) - (case (org-mouse-line-position) - (:beginning ; insert before + (cl-case (org-mouse-line-position) + (:beginning ; insert before (beginning-of-line) (looking-at "[ \t]*") (open-line 1) - (org-indent-to-column (- (match-end 0) (match-beginning 0))) + (indent-to-column (- (match-end 0) (match-beginning 0))) (insert "+ ")) - (:middle ; insert after + (:middle ; insert after (end-of-line) (newline t) (indent-relative) @@ -587,7 +586,7 @@ This means, between the beginning of line and the point." (:end ; insert text here (skip-chars-backward " \t") (kill-region (point) (point-at-eol)) - (unless (org-looking-back org-mouse-punctuation) + (unless (looking-back org-mouse-punctuation (line-beginning-position)) (insert (concat org-mouse-punctuation " "))))) (insert text) (beginning-of-line)) @@ -638,14 +637,15 @@ This means, between the beginning of line and the point." (progn (save-excursion (goto-char (region-beginning)) (insert "[[")) (save-excursion (goto-char (region-end)) (insert "]]")))] ["Insert Link Here" (org-mouse-yank-link ',event)])))) - ((save-excursion (beginning-of-line) (looking-at "#\\+STARTUP: \\(.*\\)")) + ((save-excursion (beginning-of-line) (looking-at "[ \t]*#\\+STARTUP: \\(.*\\)")) (popup-menu `(nil ,@(org-mouse-list-options-menu (mapcar 'car org-startup-options) 'org-mode-restart)))) ((or (eolp) (and (looking-at "\\( \\|\t\\)\\(+:[0-9a-zA-Z_:]+\\)?\\( \\|\t\\)+$") - (org-looking-back " \\|\t" (- (point) 2)))) + (looking-back " \\|\t" (- (point) 2) + (line-beginning-position)))) (org-mouse-popup-global-menu)) ((funcall get-context :checkbox) (popup-menu @@ -714,7 +714,7 @@ This means, between the beginning of line and the point." (org-tags-sparse-tree nil ,(match-string 1))] "--" ,@(org-mouse-tag-menu)))) - ((org-at-timestamp-p) + ((org-at-timestamp-p 'lax) (popup-menu '(nil ["Show Day" org-open-at-point t] @@ -737,13 +737,13 @@ This means, between the beginning of line and the point." ["- 1 Month" (org-timestamp-change -1 'month)]))) ((funcall get-context :table-special) (let ((mdata (match-data))) - (incf (car mdata) 2) + (cl-incf (car mdata) 2) (store-match-data mdata)) (message "match: %S" (match-string 0)) (popup-menu `(nil ,@(org-mouse-keyword-replace-menu '(" " "!" "^" "_" "$" "#" "*" "'") 0 (lambda (mark) - (case (string-to-char mark) + (cl-case (string-to-char mark) (? "( ) Nothing Special") (?! "(!) Column Names") (?^ "(^) Field Names Above") @@ -914,7 +914,7 @@ This means, between the beginning of line and the point." ((org-footnote-at-reference-p) nil) (t ad-do-it)))))) -(defun org-mouse-move-tree-start (event) +(defun org-mouse-move-tree-start (_event) (interactive "e") (message "Same line: promote/demote, (***):move before, (text): make a child")) @@ -993,7 +993,7 @@ This means, between the beginning of line and the point." (defvar org-mouse-cmd) ;dynamically scoped from `org-with-remote-undo'. (defun org-mouse-do-remotely (command) - ; (org-agenda-check-no-diary) + ;; (org-agenda-check-no-diary) (when (get-text-property (point) 'org-marker) (let* ((anticol (- (point-at-eol) (point))) (marker (get-text-property (point) 'org-marker)) @@ -1031,7 +1031,7 @@ This means, between the beginning of line and the point." (org-agenda-change-all-lines newhead hdmarker 'fixface)))) t)))) -(defun org-mouse-agenda-context-menu (&optional event) +(defun org-mouse-agenda-context-menu (&optional _event) (or (org-mouse-do-remotely 'org-mouse-context-menu) (popup-menu '("Agenda" @@ -1043,21 +1043,21 @@ This means, between the beginning of line and the point." org-agenda-undo-list)] ["Rebuild Buffer" org-agenda-redo t] ["New Diary Entry" - org-agenda-diary-entry (org-agenda-check-type nil 'agenda 'timeline) t] + org-agenda-diary-entry (org-agenda-check-type nil 'agenda) t] "--" ["Goto Today" org-agenda-goto-today - (org-agenda-check-type nil 'agenda 'timeline) t] + (org-agenda-check-type nil 'agenda) t] ["Display Calendar" org-agenda-goto-calendar - (org-agenda-check-type nil 'agenda 'timeline) t] + (org-agenda-check-type nil 'agenda) t] ("Calendar Commands" ["Phases of the Moon" org-agenda-phases-of-moon - (org-agenda-check-type nil 'agenda 'timeline)] + (org-agenda-check-type nil 'agenda)] ["Sunrise/Sunset" org-agenda-sunrise-sunset - (org-agenda-check-type nil 'agenda 'timeline)] + (org-agenda-check-type nil 'agenda)] ["Holidays" org-agenda-holidays - (org-agenda-check-type nil 'agenda 'timeline)] + (org-agenda-check-type nil 'agenda)] ["Convert" org-agenda-convert-date - (org-agenda-check-type nil 'agenda 'timeline)] + (org-agenda-check-type nil 'agenda)] "--" ["Create iCalendar file" org-icalendar-combine-agenda-files t]) "--" @@ -1070,7 +1070,7 @@ This means, between the beginning of line and the point." "--" ["Show Logbook entries" org-agenda-log-mode :style toggle :selected org-agenda-show-log - :active (org-agenda-check-type nil 'agenda 'timeline)] + :active (org-agenda-check-type nil 'agenda)] ["Include Diary" org-agenda-toggle-diary :style toggle :selected org-agenda-include-diary :active (org-agenda-check-type nil 'agenda)] @@ -1093,17 +1093,17 @@ This means, between the beginning of line and the point." ; (setq org-agenda-mode-hook nil) (defvar org-agenda-mode-map) (add-hook 'org-agenda-mode-hook - #'(lambda () - (setq org-mouse-context-menu-function 'org-mouse-agenda-context-menu) - (org-defkey org-agenda-mode-map [mouse-3] 'org-mouse-show-context-menu) - (org-defkey org-agenda-mode-map [down-mouse-3] 'org-mouse-move-tree-start) - (org-defkey org-agenda-mode-map [C-mouse-4] 'org-agenda-earlier) - (org-defkey org-agenda-mode-map [C-mouse-5] 'org-agenda-later) - (org-defkey org-agenda-mode-map [drag-mouse-3] - #'(lambda (event) (interactive "e") - (case (org-mouse-get-gesture event) - (:left (org-agenda-earlier 1)) - (:right (org-agenda-later 1))))))) + (lambda () + (setq org-mouse-context-menu-function 'org-mouse-agenda-context-menu) + (org-defkey org-agenda-mode-map [mouse-3] 'org-mouse-show-context-menu) + (org-defkey org-agenda-mode-map [down-mouse-3] 'org-mouse-move-tree-start) + (org-defkey org-agenda-mode-map [C-mouse-4] 'org-agenda-earlier) + (org-defkey org-agenda-mode-map [C-mouse-5] 'org-agenda-later) + (org-defkey org-agenda-mode-map [drag-mouse-3] + (lambda (event) (interactive "e") + (cl-case (org-mouse-get-gesture event) + (:left (org-agenda-earlier 1)) + (:right (org-agenda-later 1))))))) (provide 'org-mouse) diff --git a/lisp/org/org-pcomplete.el b/lisp/org/org-pcomplete.el index 034c20e3077..3c2561d1fa6 100644 --- a/lisp/org/org-pcomplete.el +++ b/lisp/org/org-pcomplete.el @@ -1,4 +1,4 @@ -;;; org-pcomplete.el --- In-buffer completion code +;;; org-pcomplete.el --- In-buffer Completion Code -*- lexical-binding: t; -*- ;; Copyright (C) 2004-2017 Free Software Foundation, Inc. ;; @@ -20,28 +20,24 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Code: ;;;; Require other packages -(eval-when-compile - (require 'cl)) - (require 'org-macs) (require 'org-compat) (require 'pcomplete) -(declare-function org-split-string "org" (string &optional separators)) -(declare-function org-make-org-heading-search-string "org" - (&optional string)) +(declare-function org-make-org-heading-search-string "org" (&optional string)) (declare-function org-get-buffer-tags "org" ()) (declare-function org-get-tags "org" ()) (declare-function org-buffer-property-keys "org" - (&optional include-specials include-defaults include-columns)) -(declare-function org-entry-properties "org" (&optional pom which specific)) + (&optional specials defaults columns ignore-malformed)) +(declare-function org-entry-properties "org" (&optional pom which)) +(declare-function org-tag-alist-to-string "org" (alist &optional skip-key)) ;;;; Customization variables @@ -52,12 +48,13 @@ (defvar org-drawer-regexp) (defvar org-property-re) +(defvar org-current-tag-alist) (defun org-thing-at-point () "Examine the thing at point and let the caller know what it is. The return value is a string naming the thing at point." (let ((beg1 (save-excursion - (skip-chars-backward (org-re "[:alnum:]-_@")) + (skip-chars-backward "[:alnum:]-_@") (point))) (beg (save-excursion (skip-chars-backward "a-zA-Z0-9-_:$") @@ -93,8 +90,10 @@ The return value is a string naming the thing at point." (skip-chars-backward "[ \t\n]") ;; org-drawer-regexp matches a whole line but while ;; looking-back, we just ignore trailing whitespaces - (or (org-looking-back (substring org-drawer-regexp 0 -1)) - (org-looking-back org-property-re)))) + (or (looking-back (substring org-drawer-regexp 0 -1) + (line-beginning-position)) + (looking-back org-property-re + (line-beginning-position))))) (cons "prop" nil)) ((and (equal (char-before beg1) ?:) (not (equal (char-after (point-at-bol)) ?*))) @@ -140,7 +139,6 @@ When completing for #+STARTUP, for example, this function returns pcomplete-default-completion-function)))) (defvar org-options-keywords) ; From org.el -(defvar org-element-block-name-alist) ; From org-element.el (defvar org-element-affiliated-keywords) ; From org-element.el (declare-function org-get-export-keywords "org" ()) (defun pcomplete/org-mode/file-option () @@ -153,16 +151,19 @@ When completing for #+STARTUP, for example, this function returns (mapcar (lambda (keyword) (concat keyword ": ")) org-element-affiliated-keywords) (let (block-names) - (dolist (block-info org-element-block-name-alist block-names) - (let ((name (car block-info))) - (push (format "END_%s" name) block-names) - (push (concat "BEGIN_" - name - ;; Since language is compulsory in - ;; source blocks, add a space. - (and (equal name "SRC") " ")) - block-names) - (push (format "ATTR_%s: " name) block-names)))) + (dolist (name + '("CENTER" "COMMENT" "EXAMPLE" "EXPORT" "QUOTE" "SRC" + "VERSE") + block-names) + (push (format "END_%s" name) block-names) + (push (concat "BEGIN_" + name + ;; Since language is compulsory in + ;; export blocks source blocks, add + ;; a space. + (and (member name '("EXPORT" "SRC")) " ")) + block-names) + (push (format "ATTR_%s: " name) block-names))) (mapcar (lambda (keyword) (concat keyword ": ")) (org-get-export-keywords)))) (substring pcomplete-stub 2))) @@ -233,20 +234,10 @@ When completing for #+STARTUP, for example, this function returns (setq opts (delete "showstars" opts))))) opts)))) -(defvar org-tag-alist) (defun pcomplete/org-mode/file-option/tags () "Complete arguments for the #+TAGS file option." (pcomplete-here - (list - (mapconcat (lambda (x) - (cond - ((eq :startgroup (car x)) "{") - ((eq :endgroup (car x)) "}") - ((eq :grouptags (car x)) ":") - ((eq :newline (car x)) "\\n") - ((cdr x) (format "%s(%c)" (car x) (cdr x))) - (t (car x)))) - org-tag-alist " ")))) + (list (org-tag-alist-to-string org-current-tag-alist)))) (defun pcomplete/org-mode/file-option/title () "Complete arguments for the #+TITLE file option." @@ -271,8 +262,8 @@ When completing for #+STARTUP, for example, this function returns "|:" "tags:" "tasks:" "<:" "todo:") ;; OPTION items from registered back-ends. (let (items) - (dolist (backend (org-bound-and-true-p - org-export--registered-backends)) + (dolist (backend (bound-and-true-p + org-export-registered-backends)) (dolist (option (org-export-backend-options backend)) (let ((item (nth 2 option))) (when item (push (concat item ":") items))))) @@ -283,7 +274,7 @@ When completing for #+STARTUP, for example, this function returns (while (pcomplete-here (pcomplete-uniqify-list (mapcar (lambda (item) (format "%s:" (car item))) - (org-bound-and-true-p org-html-infojs-opts-table)))))) + (bound-and-true-p org-html-infojs-opts-table)))))) (defun pcomplete/org-mode/file-option/bind () "Complete arguments for the #+BIND file option, which are variable names." @@ -324,26 +315,24 @@ This needs more work, to handle headings with lots of spaces in them." (save-excursion (goto-char (point-min)) (let (tbl) - (while (re-search-forward org-todo-line-regexp nil t) - (push (org-make-org-heading-search-string - (match-string-no-properties 3)) - tbl)) + (let ((case-fold-search nil)) + (while (re-search-forward org-todo-line-regexp nil t) + (push (org-make-org-heading-search-string + (match-string-no-properties 3)) + tbl))) (pcomplete-uniqify-list tbl))) (substring pcomplete-stub 1)))) -(defvar org-tag-alist) (defun pcomplete/org-mode/tag () "Complete a tag name. Omit tags already set." (while (pcomplete-here - (mapcar (lambda (x) - (concat x ":")) + (mapcar (lambda (x) (concat x ":")) (let ((lst (pcomplete-uniqify-list - (or (remove + (or (remq nil - (mapcar (lambda (x) - (and (stringp (car x)) (car x))) - org-tag-alist)) - (mapcar 'car (org-get-buffer-tags)))))) + (mapcar (lambda (x) (org-string-nw-p (car x))) + org-current-tag-alist)) + (mapcar #'car (org-get-buffer-tags)))))) (dolist (tag (org-get-tags)) (setq lst (delete tag lst))) lst)) @@ -357,31 +346,12 @@ This needs more work, to handle headings with lots of spaces in them." (concat x ": ")) (let ((lst (pcomplete-uniqify-list (copy-sequence - (org-buffer-property-keys nil t t))))) + (org-buffer-property-keys nil t t t))))) (dolist (prop (org-entry-properties)) (setq lst (delete (car prop) lst))) lst)) (substring pcomplete-stub 1))) -(defvar org-drawers) - -(defun pcomplete/org-mode/drawer () - "Complete a drawer name." - (let ((spc (save-excursion - (move-beginning-of-line 1) - (looking-at "^\\([ \t]*\\):") - (match-string 1))) - (cpllist (mapcar (lambda (x) (concat x ": ")) org-drawers))) - (pcomplete-here cpllist - (substring pcomplete-stub 1) - (unless (or (not (delq - nil - (mapcar (lambda(x) - (string-match (substring pcomplete-stub 1) x)) - cpllist))) - (looking-at "[ \t]*\n.*:END:")) - (save-excursion (insert "\n" spc ":END:")))))) - (defun pcomplete/org-mode/block-option/src () "Complete the arguments of a begin_src block. Complete a language in the first field, the header arguments and switches." diff --git a/lisp/org/org-plot.el b/lisp/org/org-plot.el index 5ccfbb1e662..a8028324bfd 100644 --- a/lisp/org/org-plot.el +++ b/lisp/org/org-plot.el @@ -1,4 +1,4 @@ -;;; org-plot.el --- Support for plotting from Org-mode +;;; org-plot.el --- Support for Plotting from Org -*- lexical-binding: t; -*- ;; Copyright (C) 2008-2017 Free Software Foundation, Inc. ;; @@ -19,20 +19,20 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; Borrows ideas and a couple of lines of code from org-exp.el. -;; Thanks to the org-mode mailing list for testing and implementation -;; and feature suggestions +;; Thanks to the Org mailing list for testing and implementation and +;; feature suggestions ;;; Code: + +(require 'cl-lib) (require 'org) (require 'org-table) -(eval-when-compile - (require 'cl)) (declare-function gnuplot-delchar-or-maybe-eof "ext:gnuplot" (arg)) (declare-function gnuplot-mode "ext:gnuplot" ()) @@ -49,41 +49,39 @@ (defun org-plot/add-options-to-plist (p options) "Parse an OPTIONS line and set values in the property list P. Returns the resulting property list." - (let (o) - (when options - (let ((op '(("type" . :plot-type) - ("script" . :script) - ("line" . :line) - ("set" . :set) - ("title" . :title) - ("ind" . :ind) - ("deps" . :deps) - ("with" . :with) - ("file" . :file) - ("labels" . :labels) - ("map" . :map) - ("timeind" . :timeind) - ("timefmt" . :timefmt))) - (multiples '("set" "line")) - (regexp ":\\([\"][^\"]+?[\"]\\|[(][^)]+?[)]\\|[^ \t\n\r;,.]*\\)") - (start 0) - o) - (while (setq o (pop op)) - (if (member (car o) multiples) ;; keys with multiple values - (while (string-match - (concat (regexp-quote (car o)) regexp) - options start) - (setq start (match-end 0)) - (setq p (plist-put p (cdr o) - (cons (car (read-from-string - (match-string 1 options))) - (plist-get p (cdr o))))) - p) - (if (string-match (concat (regexp-quote (car o)) regexp) - options) - (setq p (plist-put p (cdr o) - (car (read-from-string - (match-string 1 options))))))))))) + (when options + (let ((op '(("type" . :plot-type) + ("script" . :script) + ("line" . :line) + ("set" . :set) + ("title" . :title) + ("ind" . :ind) + ("deps" . :deps) + ("with" . :with) + ("file" . :file) + ("labels" . :labels) + ("map" . :map) + ("timeind" . :timeind) + ("timefmt" . :timefmt))) + (multiples '("set" "line")) + (regexp ":\\([\"][^\"]+?[\"]\\|[(][^)]+?[)]\\|[^ \t\n\r;,.]*\\)") + (start 0)) + (dolist (o op) + (if (member (car o) multiples) ;; keys with multiple values + (while (string-match + (concat (regexp-quote (car o)) regexp) + options start) + (setq start (match-end 0)) + (setq p (plist-put p (cdr o) + (cons (car (read-from-string + (match-string 1 options))) + (plist-get p (cdr o))))) + p) + (if (string-match (concat (regexp-quote (car o)) regexp) + options) + (setq p (plist-put p (cdr o) + (car (read-from-string + (match-string 1 options)))))))))) p) (defun org-plot/goto-nearest-table () @@ -119,10 +117,9 @@ will be added. Returns the resulting property list." Pass PARAMS through to `orgtbl-to-generic' when exporting TABLE." (with-temp-file data-file - (make-local-variable 'org-plot-timestamp-fmt) - (setq org-plot-timestamp-fmt (or - (plist-get params :timefmt) - "%Y-%m-%d-%H:%M:%S")) + (setq-local org-plot-timestamp-fmt (or + (plist-get params :timefmt) + "%Y-%m-%d-%H:%M:%S")) (insert (orgtbl-to-generic table (org-combine-plists @@ -140,7 +137,7 @@ and dependant variables." (deps (if (plist-member params :deps) (mapcar (lambda (val) (- val 1)) (plist-get params :deps)) (let (collector) - (dotimes (col (length (first table))) + (dotimes (col (length (nth 0 table))) (setf collector (cons col collector))) collector))) (counter 0) @@ -158,7 +155,7 @@ and dependant variables." table))) ;; write table to gnuplot grid datafile format (with-temp-file data-file - (let ((num-rows (length table)) (num-cols (length (first table))) + (let ((num-rows (length table)) (num-cols (length (nth 0 table))) (gnuplot-row (lambda (col row value) (setf col (+ 1 col)) (setf row (+ 1 row)) (format "%f %f %f\n%f %f %f\n" @@ -187,9 +184,7 @@ NUM-COLS controls the number of columns plotted in a 2-d plot. Optional argument PREFACE returns only option parameters in a manner suitable for prepending to a user-specified script." (let* ((type (plist-get params :plot-type)) - (with (if (equal type 'grid) - 'pm3d - (plist-get params :with))) + (with (if (eq type 'grid) 'pm3d (plist-get params :with))) (sets (plist-get params :set)) (lines (plist-get params :line)) (map (plist-get params :map)) @@ -204,68 +199,72 @@ manner suitable for prepending to a user-specified script." (x-labels (plist-get params :xlabels)) (y-labels (plist-get params :ylabels)) (plot-str "'%s' using %s%d%s with %s title '%s'") - (plot-cmd (case type - ('2d "plot") - ('3d "splot") - ('grid "splot"))) + (plot-cmd (pcase type + (`2d "plot") + (`3d "splot") + (`grid "splot"))) (script "reset") - ; ats = add-to-script - (ats (lambda (line) (setf script (format "%s\n%s" script line)))) + ;; ats = add-to-script + (ats (lambda (line) (setf script (concat script "\n" line)))) plot-lines) - (when file ;; output file + (when file ; output file (funcall ats (format "set term %s" (file-name-extension file))) (funcall ats (format "set output '%s'" file))) - (case type ;; type - ('2d ()) - ('3d (if map (funcall ats "set map"))) - ('grid (if map (funcall ats "set pm3d map") - (funcall ats "set pm3d")))) - (when title (funcall ats (format "set title '%s'" title))) ;; title - (when lines (mapc (lambda (el) (funcall ats el)) lines)) ;; line - (when sets ;; set - (mapc (lambda (el) (funcall ats (format "set %s" el))) sets)) - (when x-labels ;; x labels (xtics) + (pcase type ; type + (`2d ()) + (`3d (when map (funcall ats "set map"))) + (`grid (funcall ats (if map "set pm3d map" "set pm3d")))) + (when title (funcall ats (format "set title '%s'" title))) ; title + (mapc ats lines) ; line + (dolist (el sets) (funcall ats (format "set %s" el))) ; set + ;; Unless specified otherwise, values are TAB separated. + (unless (string-match-p "^set datafile separator" script) + (funcall ats "set datafile separator \"\\t\"")) + (when x-labels ; x labels (xtics) (funcall ats (format "set xtics (%s)" (mapconcat (lambda (pair) (format "\"%s\" %d" (cdr pair) (car pair))) x-labels ", ")))) - (when y-labels ;; y labels (ytics) + (when y-labels ; y labels (ytics) (funcall ats (format "set ytics (%s)" (mapconcat (lambda (pair) (format "\"%s\" %d" (cdr pair) (car pair))) y-labels ", ")))) - (when time-ind ;; timestamp index + (when time-ind ; timestamp index (funcall ats "set xdata time") (funcall ats (concat "set timefmt \"" - (or timefmt ;; timefmt passed to gnuplot + (or timefmt ; timefmt passed to gnuplot "%Y-%m-%d-%H:%M:%S") "\""))) (unless preface - (case type ;; plot command - ('2d (dotimes (col num-cols) - (unless (and (equal type '2d) - (or (and ind (equal (+ 1 col) ind)) - (and deps (not (member (+ 1 col) deps))))) + (pcase type ; plot command + (`2d (dotimes (col num-cols) + (unless (and (eq type '2d) + (or (and ind (equal (1+ col) ind)) + (and deps (not (member (1+ col) deps))))) (setf plot-lines (cons (format plot-str data-file (or (and ind (> ind 0) - (not text-ind) - (format "%d:" ind)) "") - (+ 1 col) + (not text-ind) + (format "%d:" ind)) "") + (1+ col) (if text-ind (format ":xticlabel(%d)" ind) "") with - (or (nth col col-labels) (format "%d" (+ 1 col)))) + (or (nth col col-labels) + (format "%d" (1+ col)))) plot-lines))))) - ('3d + (`3d (setq plot-lines (list (format "'%s' matrix with %s title ''" data-file with)))) - ('grid + (`grid (setq plot-lines (list (format "'%s' with %s title ''" data-file with))))) (funcall ats - (concat plot-cmd " " (mapconcat 'identity (reverse plot-lines) ",\\\n ")))) + (concat plot-cmd " " (mapconcat #'identity + (reverse plot-lines) + ",\\\n ")))) script)) ;;----------------------------------------------------------------------------- @@ -279,59 +278,59 @@ line directly before or after the table." (require 'gnuplot) (save-window-excursion (delete-other-windows) - (when (get-buffer "*gnuplot*") ;; reset *gnuplot* if it already running + (when (get-buffer "*gnuplot*") ; reset *gnuplot* if it already running (with-current-buffer "*gnuplot*" - (goto-char (point-max)) - (gnuplot-delchar-or-maybe-eof nil))) + (goto-char (point-max)))) (org-plot/goto-nearest-table) - ;; set default options - (mapc - (lambda (pair) - (unless (plist-member params (car pair)) - (setf params (plist-put params (car pair) (cdr pair))))) - org-plot/gnuplot-default-options) + ;; Set default options. + (dolist (pair org-plot/gnuplot-default-options) + (unless (plist-member params (car pair)) + (setf params (plist-put params (car pair) (cdr pair))))) ;; collect table and table information (let* ((data-file (make-temp-file "org-plot")) (table (org-table-to-lisp)) - (num-cols (length (if (eq (first table) 'hline) (second table) - (first table))))) - (while (equal 'hline (first table)) (setf table (cdr table))) - (when (equal (second table) 'hline) - (setf params (plist-put params :labels (first table))) ;; headers to labels - (setf table (delq 'hline (cdr table)))) ;; clean non-data from table - ;; collect options + (num-cols (length (if (eq (nth 0 table) 'hline) (nth 1 table) + (nth 0 table))))) + (run-with-idle-timer 0.1 nil #'delete-file data-file) + (while (eq 'hline (car table)) (setf table (cdr table))) + (when (eq (cadr table) 'hline) + (setf params + (plist-put params :labels (nth 0 table))) ; headers to labels + (setf table (delq 'hline (cdr table)))) ; clean non-data from table + ;; Collect options. (save-excursion (while (and (equal 0 (forward-line -1)) (looking-at "[[:space:]]*#\\+")) (setf params (org-plot/collect-options params)))) - ;; dump table to datafile (very different for grid) - (case (plist-get params :plot-type) - ('2d (org-plot/gnuplot-to-data table data-file params)) - ('3d (org-plot/gnuplot-to-data table data-file params)) - ('grid (let ((y-labels (org-plot/gnuplot-to-grid-data + ;; Dump table to datafile (very different for grid). + (pcase (plist-get params :plot-type) + (`2d (org-plot/gnuplot-to-data table data-file params)) + (`3d (org-plot/gnuplot-to-data table data-file params)) + (`grid (let ((y-labels (org-plot/gnuplot-to-grid-data table data-file params))) (when y-labels (plist-put params :ylabels y-labels))))) - ;; check for timestamp ind column - (let ((ind (- (plist-get params :ind) 1))) - (when (and (>= ind 0) (equal '2d (plist-get params :plot-type))) + ;; Check for timestamp ind column. + (let ((ind (1- (plist-get params :ind)))) + (when (and (>= ind 0) (eq '2d (plist-get params :plot-type))) (if (= (length (delq 0 (mapcar (lambda (el) - (if (string-match org-ts-regexp3 el) - 0 1)) - (mapcar (lambda (row) (nth ind row)) table)))) 0) + (if (string-match org-ts-regexp3 el) 0 1)) + (mapcar (lambda (row) (nth ind row)) table)))) + 0) (plist-put params :timeind t) - ;; check for text ind column + ;; Check for text ind column. (if (or (string= (plist-get params :with) "hist") (> (length (delq 0 (mapcar (lambda (el) (if (string-match org-table-number-regexp el) 0 1)) - (mapcar (lambda (row) (nth ind row)) table)))) 0)) + (mapcar (lambda (row) (nth ind row)) table)))) + 0)) (plist-put params :textind t))))) - ;; write script + ;; Write script. (with-temp-buffer - (if (plist-get params :script) ;; user script + (if (plist-get params :script) ; user script (progn (insert (org-plot/gnuplot-script data-file num-cols params t)) (insert "\n") @@ -339,14 +338,12 @@ line directly before or after the table." (goto-char (point-min)) (while (re-search-forward "$datafile" nil t) (replace-match data-file nil nil))) - (insert - (org-plot/gnuplot-script data-file num-cols params))) - ;; graph table + (insert (org-plot/gnuplot-script data-file num-cols params))) + ;; Graph table. (gnuplot-mode) (gnuplot-send-buffer-to-gnuplot)) - ;; cleanup - (bury-buffer (get-buffer "*gnuplot*")) - (run-with-idle-timer 0.1 nil (lambda () (delete-file data-file)))))) + ;; Cleanup. + (bury-buffer (get-buffer "*gnuplot*"))))) (provide 'org-plot) diff --git a/lisp/org/org-protocol.el b/lisp/org/org-protocol.el index 4bd83bea486..d92bfc6a158 100644 --- a/lisp/org/org-protocol.el +++ b/lisp/org/org-protocol.el @@ -1,4 +1,4 @@ -;;; org-protocol.el --- Intercept calls from emacsclient to trigger custom actions. +;;; org-protocol.el --- Intercept Calls from Emacsclient to Trigger Custom Actions -*- lexical-binding: t; -*- ;; ;; Copyright (C) 2008-2017 Free Software Foundation, Inc. ;; @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Commentary: @@ -49,7 +49,7 @@ ;; 4.) Try this from the command line (adjust the URL as needed): ;; ;; $ emacsclient \ -;; org-protocol://store-link://http:%2F%2Flocalhost%2Findex.html/The%20title +;; org-protocol://store-link?url=http:%2F%2Flocalhost%2Findex.html&title=The%20title ;; ;; 5.) Optionally add custom sub-protocols and handlers: ;; @@ -60,7 +60,7 @@ ;; ;; A "sub-protocol" will be found in URLs like this: ;; -;; org-protocol://sub-protocol://data +;; org-protocol://sub-protocol?key=val&key2=val2 ;; ;; If it works, you can now setup other applications for using this feature. ;; @@ -81,12 +81,12 @@ ;; * `org-protocol-open-source' uses the sub-protocol \"open-source\" and maps ;; URLs to local filenames defined in `org-protocol-project-alist'. ;; -;; * `org-protocol-store-link' stores an Org-link (if Org-mode is present) and +;; * `org-protocol-store-link' stores an Org link (if Org is present) and ;; pushes the browsers URL to the `kill-ring' for yanking. This handler is ;; triggered through the sub-protocol \"store-link\". ;; ;; * Call `org-protocol-capture' by using the sub-protocol \"capture\". If -;; Org-mode is loaded, Emacs will pop-up a capture buffer and fill the +;; Org is loaded, Emacs will pop-up a capture buffer and fill the ;; template with the data provided. I.e. the browser's URL is inserted as an ;; Org-link of which the page title will be the description part. If text ;; was select in the browser, that text will be the body of the entry. @@ -94,20 +94,20 @@ ;; You may use the same bookmark URL for all those standard handlers and just ;; adjust the sub-protocol used: ;; -;; location.href='org-protocol://sub-protocol://'+ -;; encodeURIComponent(location.href)+'/'+ -;; encodeURIComponent(document.title)+'/'+ +;; location.href='org-protocol://sub-protocol?url='+ +;; encodeURIComponent(location.href)+'&title='+ +;; encodeURIComponent(document.title)+'&body='+ ;; encodeURIComponent(window.getSelection()) ;; ;; The handler for the sub-protocol \"capture\" detects an optional template ;; char that, if present, triggers the use of a special template. ;; Example: ;; -;; location.href='org-protocol://sub-protocol://x/'+ ... +;; location.href='org-protocol://capture?template=x'+ ... ;; -;; use template ?x. +;; uses template ?x. ;; -;; Note, that using double slashes is optional from org-protocol.el's point of +;; Note that using double slashes is optional from org-protocol.el's point of ;; view because emacsclient squashes the slashes to one. ;; ;; @@ -116,25 +116,12 @@ ;;; Code: (require 'org) -(eval-when-compile - (require 'cl)) (declare-function org-publish-get-project-from-filename "ox-publish" (filename &optional up)) (declare-function server-edit "server" (&optional arg)) -(define-obsolete-function-alias - 'org-protocol-unhex-compound 'org-link-unescape-compound - "2011-02-17") - -(define-obsolete-function-alias - 'org-protocol-unhex-string 'org-link-unescape - "2011-02-17") - -(define-obsolete-function-alias - 'org-protocol-unhex-single-byte-sequence - 'org-link-unescape-single-byte-sequence - "2011-02-17") +(defvar org-capture-link-is-already-stored) (defgroup org-protocol nil "Intercept calls from emacsclient to trigger custom actions. @@ -207,7 +194,14 @@ Example: :working-suffix \".org\" :base-url \"http://localhost/org/\" :working-directory \"/home/user/org/\" - :rewrites ((\"org/?$\" . \"index.php\"))))) + :rewrites ((\"org/?$\" . \"index.php\"))) + (\"Hugo based blog\" + :base-url \"https://www.site.com/\" + :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 @@ -225,27 +219,36 @@ Each element of this list must be of the form: (module-name :protocol protocol :function func :kill-client nil) -protocol - protocol to detect in a filename without trailing colon and slashes. - See rfc1738 section 2.1 for more on this. - If you define a protocol \"my-protocol\", `org-protocol-check-filename-for-protocol' - will search filenames for \"org-protocol:/my-protocol:/\" - and trigger your action for every match. `org-protocol' is defined in - `org-protocol-the-protocol'. Double and triple slashes are compressed - to one by emacsclient. - -function - function that handles requests with protocol and takes exactly one - argument: the filename with all protocols stripped. If the function - returns nil, emacsclient and -server do nothing. Any non-nil return - value is considered a valid filename and thus passed to the server. - - `org-protocol.el provides some support for handling those filenames, - if you stay with the conventions used for the standard handlers in - `org-protocol-protocol-alist-default'. See `org-protocol-split-data'. +protocol - protocol to detect in a filename without trailing + colon and slashes. See rfc1738 section 2.1 for more + on this. If you define a protocol \"my-protocol\", + `org-protocol-check-filename-for-protocol' will search + filenames for \"org-protocol:/my-protocol\" and + trigger your action for every match. `org-protocol' + is defined in `org-protocol-the-protocol'. Double and + triple slashes are compressed to one by emacsclient. + +function - function that handles requests with protocol and takes + one argument. If a new-style link (key=val&key2=val2) + is given, the argument will be a property list with + the values from the link. If an old-style link is + given (val1/val2), the argument will be the filename + with all protocols stripped. + + If the function returns nil, emacsclient and -server + do nothing. Any non-nil return value is considered a + valid filename and thus passed to the server. + + `org-protocol.el' provides some support for handling + old-style filenames, if you follow the conventions + used for the standard handlers in + `org-protocol-protocol-alist-default'. See + `org-protocol-parse-parameters'. kill-client - If t, kill the client immediately, once the sub-protocol is detected. This is necessary for actions that can be interrupted by - `C-g' to avoid dangling emacsclients. Note, that all other command - line arguments but the this one will be discarded, greedy handlers + `C-g' to avoid dangling emacsclients. Note that all other command + line arguments but the this one will be discarded. Greedy handlers still receive the whole list of arguments though. Here is an example: @@ -269,7 +272,7 @@ string with two characters." (defcustom org-protocol-data-separator "/+\\|\\?" "The default data separator to use. - This should be a single regexp string." +This should be a single regexp string." :group 'org-protocol :version "24.4" :package-version '(Org . "8.0") @@ -278,21 +281,20 @@ string with two characters." ;;; Helper functions: (defun org-protocol-sanitize-uri (uri) - "emacsclient compresses double and triple slashes. -Slashes are sanitized to double slashes here." + "Sanitize slashes to double-slashes in URI. +Emacsclient compresses double and triple slashes." (when (string-match "^\\([a-z]+\\):/" uri) (let* ((splitparts (split-string uri "/+"))) (setq uri (concat (car splitparts) "//" (mapconcat 'identity (cdr splitparts) "/"))))) uri) (defun org-protocol-split-data (data &optional unhexify separator) - "Split what an org-protocol handler function gets as only argument. -DATA is that one argument. DATA is split at each occurrence of -SEPARATOR (regexp). If no SEPARATOR is specified or SEPARATOR is -nil, assume \"/+\". The results of that splitting are returned -as a list. If UNHEXIFY is non-nil, hex-decode each split part. -If UNHEXIFY is a function, use that function to decode each split -part." + "Split the DATA argument for an org-protocol handler function. +If UNHEXIFY is non-nil, hex-decode each split part. If UNHEXIFY +is a function, use that function to decode each split part. The +string is split at each occurrence of SEPARATOR (regexp). If no +SEPARATOR is specified or SEPARATOR is nil, assume \"/+\". The +results of that splitting are returned as a list." (let* ((sep (or separator "/+\\|\\?")) (split-parts (split-string data sep))) (if unhexify @@ -302,23 +304,25 @@ part." split-parts))) (defun org-protocol-flatten-greedy (param-list &optional strip-path replacement) - "Greedy handlers might receive a list like this from emacsclient: - ((\"/dir/org-protocol:/greedy:/~/path1\" (23 . 12)) (\"/dir/param\")) -where \"/dir/\" is the absolute path to emacsclients working directory. This + "Transform PARAM-LIST into a flat list for greedy handlers. + +Greedy handlers might receive a list like this from emacsclient: +\((\"/dir/org-protocol:/greedy:/~/path1\" (23 . 12)) (\"/dir/param\")) +where \"/dir/\" is the absolute path to emacsclient's working directory. This function transforms it into a flat list using `org-protocol-flatten' and transforms the elements of that list as follows: -If strip-path is non-nil, remove the \"/dir/\" prefix from all members of +If STRIP-PATH is non-nil, remove the \"/dir/\" prefix from all members of param-list. -If replacement is string, replace the \"/dir/\" prefix with it. +If REPLACEMENT is string, replace the \"/dir/\" prefix with it. The first parameter, the one that contains the protocols, is always changed. Everything up to the end of the protocols is stripped. Note, that this function will always behave as if `org-protocol-reverse-list-of-files' was set to t and the returned list will -reflect that. I.e. emacsclients first parameter will be the first one in the +reflect that. emacsclient's first parameter will be the first one in the returned list." (let* ((l (org-protocol-flatten (if org-protocol-reverse-list-of-files param-list @@ -345,50 +349,106 @@ returned list." ret) l))) -(defun org-protocol-flatten (l) - "Greedy handlers might receive a list like this from emacsclient: - ((\"/dir/org-protocol:/greedy:/~/path1\" (23 . 12)) (\"/dir/param\")) +(defun org-protocol-flatten (list) + "Transform LIST into a flat list. + +Greedy handlers might receive a list like this from emacsclient: +\((\"/dir/org-protocol:/greedy:/~/path1\" (23 . 12)) (\"/dir/param\")) where \"/dir/\" is the absolute path to emacsclients working directory. This function transforms it into a flat list." - (if (null l) () - (if (listp l) - (append (org-protocol-flatten (car l)) (org-protocol-flatten (cdr l))) - (list l)))) - + (if (null list) () + (if (listp list) + (append (org-protocol-flatten (car list)) (org-protocol-flatten (cdr list))) + (list list)))) + +(defun org-protocol-parse-parameters (info &optional new-style default-order) + "Return a property list of parameters from INFO. +If NEW-STYLE is non-nil, treat INFO as a query string (ex: +url=URL&title=TITLE). If old-style links are used (ex: +org-protocol://store-link/url/title), assign them to attributes +following DEFAULT-ORDER. + +If no DEFAULT-ORDER is specified, return the list of values. + +If INFO is already a property list, return it unchanged." + (if (listp info) + info + (if new-style + (let ((data (org-protocol-convert-query-to-plist info)) + result) + (while data + (setq result + (append + result + (list + (pop data) + (org-link-unescape (pop data)))))) + result) + (let ((data (org-protocol-split-data info t org-protocol-data-separator))) + (if default-order + (org-protocol-assign-parameters data default-order) + data))))) + +(defun org-protocol-assign-parameters (data default-order) + "Return a property list of parameters from DATA. +Key names are taken from DEFAULT-ORDER, which should be a list of +symbols. If DEFAULT-ORDER is shorter than the number of values +specified, the rest of the values are treated as :key value pairs." + (let (result) + (while default-order + (setq result + (append result + (list (pop default-order) + (pop data))))) + (while data + (setq result + (append result + (list (intern (concat ":" (pop data))) + (pop data))))) + result)) ;;; Standard protocol handlers: (defun org-protocol-store-link (fname) - "Process an org-protocol://store-link:// style url. + "Process an org-protocol://store-link style url. Additionally store a browser URL as an org link. Also pushes the link's URL to the `kill-ring'. +Parameters: url, title (optional), body (optional) + +Old-style links such as org-protocol://store-link://URL/TITLE are +also recognized. + The location for a browser's bookmark has to look like this: - javascript:location.href=\\='org-protocol://store-link://\\='+ \\ - encodeURIComponent(location.href) - encodeURIComponent(document.title)+\\='/\\='+ \\ + javascript:location.href = \\ + \\='org-protocol://store-link?url=\\=' + \\ + encodeURIComponent(location.href) + \\='&title=\\=' + \\ + encodeURIComponent(document.title); -Don't use `escape()'! Use `encodeURIComponent()' instead. The title of the page -could contain slashes and the location definitely will. +Don't use `escape()'! Use `encodeURIComponent()' instead. The +title of the page could contain slashes and the location +definitely will. The sub-protocol used to reach this function is set in -`org-protocol-protocol-alist'." - (let* ((splitparts (org-protocol-split-data fname t org-protocol-data-separator)) - (uri (org-protocol-sanitize-uri (car splitparts))) - (title (cadr splitparts)) - orglink) - (if (boundp 'org-stored-links) - (setq org-stored-links (cons (list uri title) org-stored-links))) +`org-protocol-protocol-alist'. + +FNAME should be a property list. If not, an old-style link of the +form URL/TITLE can also be used." + (let* ((splitparts (org-protocol-parse-parameters fname nil '(:url :title))) + (uri (org-protocol-sanitize-uri (plist-get splitparts :url))) + (title (plist-get splitparts :title))) + (when (boundp 'org-stored-links) + (push (list uri title) org-stored-links)) (kill-new uri) (message "`%s' to insert new org-link, `%s' to insert `%s'" - (substitute-command-keys"\\[org-insert-link]") - (substitute-command-keys"\\[yank]") + (substitute-command-keys "`\\[org-insert-link]'") + (substitute-command-keys "`\\[yank]'") uri)) nil) (defun org-protocol-capture (info) - "Process an org-protocol://capture:// style url. + "Process an org-protocol://capture style url with INFO. The sub-protocol used to reach this function is set in `org-protocol-protocol-alist'. @@ -396,16 +456,16 @@ The sub-protocol used to reach this function is set in This function detects an URL, title and optional text, separated by `/'. The location for a browser's bookmark looks like this: - javascript:location.href=\\='org-protocol://capture://\\='+ \\ - encodeURIComponent(location.href)+\\='/\\=' \\ - encodeURIComponent(document.title)+\\='/\\='+ \\ + javascript:location.href = \\='org-protocol://capture?url=\\='+ \\ + encodeURIComponent(location.href) + \\='&title=\\=' \\ + encodeURIComponent(document.title) + \\='&body=\\=' + \\ encodeURIComponent(window.getSelection()) By default, it uses the character `org-protocol-default-template-key', which should be associated with a template in `org-capture-templates'. -But you may prepend the encoded URL with a character and a slash like so: +You may specify the template with a template= query parameter, like this: - javascript:location.href=\\='org-protocol://capture://b/\\='+ ... + javascript:location.href = \\='org-protocol://capture?template=b\\='+ ... Now template ?b will be used." (if (and (boundp 'org-stored-links) @@ -414,7 +474,7 @@ Now template ?b will be used." nil) (defun org-protocol-convert-query-to-plist (query) - "Convert query string that is part of url to property list." + "Convert QUERY key=value pairs in the URL to a property list." (if query (apply 'append (mapcar (lambda (x) (let ((c (split-string x "="))) @@ -422,45 +482,54 @@ Now template ?b will be used." (split-string query "&"))))) (defun org-protocol-do-capture (info) - "Support `org-capture'." - (let* ((parts (org-protocol-split-data info t org-protocol-data-separator)) - (template (or (and (>= 2 (length (car parts))) (pop parts)) + "Perform the actual capture based on INFO." + (let* ((temp-parts (org-protocol-parse-parameters info)) + (parts + (cond + ((and (listp info) (symbolp (car info))) info) + ((= (length (car temp-parts)) 1) ;; First parameter is exactly one character long + (org-protocol-assign-parameters temp-parts '(:template :url :title :body))) + (t + (org-protocol-assign-parameters temp-parts '(:url :title :body))))) + (template (or (plist-get parts :template) org-protocol-default-template-key)) - (url (org-protocol-sanitize-uri (car parts))) - (type (if (string-match "^\\([a-z]+\\):" url) - (match-string 1 url))) - (title (or (cadr parts) "")) - (region (or (caddr parts) "")) - (orglink (org-make-link-string - url (if (string-match "[^[:space:]]" title) title url))) - (query (or (org-protocol-convert-query-to-plist (cadddr parts)) "")) + (url (and (plist-get parts :url) (org-protocol-sanitize-uri (plist-get parts :url)))) + (type (and url (if (string-match "^\\([a-z]+\\):" url) + (match-string 1 url)))) + (title (or (plist-get parts :title) "")) + (region (or (plist-get parts :body) "")) + (orglink (if url + (org-make-link-string + url (if (string-match "[^[:space:]]" title) title url)) + title)) (org-capture-link-is-already-stored t)) ;; avoid call to org-store-link (setq org-stored-links (cons (list url title) org-stored-links)) - (kill-new orglink) (org-store-link-props :type type :link url :description title :annotation orglink :initial region - :query query) + :query parts) (raise-frame) (funcall 'org-capture nil template))) (defun org-protocol-open-source (fname) - "Process an org-protocol://open-source:// style url. + "Process an org-protocol://open-source?url= style URL with FNAME. Change a filename by mapping URLs to local filenames as set in `org-protocol-project-alist'. The location for a browser's bookmark should look like this: - javascript:location.href=\\='org-protocol://open-source://\\='+ \\ + javascript:location.href = \\='org-protocol://open-source?url=\\=' + \\ encodeURIComponent(location.href)" ;; As we enter this function for a match on our protocol, the return value ;; defaults to nil. (let ((result nil) - (f (org-link-unescape fname))) + (f (org-protocol-sanitize-uri + (plist-get (org-protocol-parse-parameters fname nil '(:url)) + :url)))) (catch 'result (dolist (prolist org-protocol-project-alist) (let* ((base-url (plist-get (cdr prolist) :base-url)) @@ -490,13 +559,16 @@ The location for a browser's bookmark should look like this: (let ((rewrites (plist-get (cdr prolist) :rewrites))) (when rewrites (message "Rewrites found: %S" rewrites) - (mapc - (lambda (rewrite) - "Try to match a rewritten URL and map it to a real file." - ;; Compare redirects without suffix: - (if (string-match (car rewrite) f2) - (throw 'result (concat wdir (cdr rewrite))))) - rewrites)))) + (dolist (rewrite rewrites) + ;; Try to match a rewritten URL and map it to + ;; a real file. Compare redirects without + ;; suffix. + (when (string-match (car rewrite) f1) + (let ((replacement + (concat (directory-file-name + (replace-match "" nil nil f1 1)) + (cdr rewrite)))) + (throw 'result (concat wdir replacement)))))))) ;; -- end of redirects -- (if (file-readable-p the-file) @@ -509,44 +581,63 @@ The location for a browser's bookmark should look like this: ;;; Core functions: -(defun org-protocol-check-filename-for-protocol (fname restoffiles client) - "Detect if `org-protocol-the-protocol' and a known sub-protocol is used in fname. +(defun org-protocol-check-filename-for-protocol (fname restoffiles _client) + "Check if `org-protocol-the-protocol' and a valid protocol are used in FNAME. Sub-protocols are registered in `org-protocol-protocol-alist' and -`org-protocol-protocol-alist-default'. -This is, how the matching is done: +`org-protocol-protocol-alist-default'. This is how the matching is done: - (string-match \"protocol:/+sub-protocol:/+\" ...) + (string-match \"protocol:/+sub-protocol\\\\(://\\\\|\\\\?\\\\)\" ...) protocol and sub-protocol are regexp-quoted. -If a matching protocol is found, the protocol is stripped from fname and the -result is passed to the protocols function as the only parameter. If the -function returns nil, the filename is removed from the list of filenames -passed from emacsclient to the server. -If the function returns a non nil value, that value is passed to the server -as filename." +Old-style links such as \"protocol://sub-protocol://param1/param2\" are +also recognized. + +If a matching protocol is found, the protocol is stripped from +fname and the result is passed to the protocol function as the +first parameter. The second parameter will be non-nil if FNAME +uses key=val&key2=val2-type arguments, or nil if FNAME uses +val/val2-type arguments. If the function returns nil, the +filename is removed from the list of filenames passed from +emacsclient to the server. If the function returns a non-nil +value, that value is passed to the server as filename. + +If the handler function is greedy, RESTOFFILES will also be passed to it. + +CLIENT is ignored." (let ((sub-protocols (append org-protocol-protocol-alist org-protocol-protocol-alist-default))) (catch 'fname - (let ((the-protocol (concat (regexp-quote org-protocol-the-protocol) ":/+"))) + (let ((the-protocol (concat (regexp-quote org-protocol-the-protocol) + ":/+"))) (when (string-match the-protocol fname) (dolist (prolist sub-protocols) - (let ((proto (concat the-protocol - (regexp-quote (plist-get (cdr prolist) :protocol)) ":/+"))) + (let ((proto + (concat the-protocol + (regexp-quote (plist-get (cdr prolist) :protocol)) + "\\(:/+\\|\\?\\)"))) (when (string-match proto fname) (let* ((func (plist-get (cdr prolist) :function)) (greedy (plist-get (cdr prolist) :greedy)) (split (split-string fname proto)) - (result (if greedy restoffiles (cadr split)))) + (result (if greedy restoffiles (cadr split))) + (new-style (string= (match-string 1 fname) "?"))) (when (plist-get (cdr prolist) :kill-client) (message "Greedy org-protocol handler. Killing client.") (server-edit)) (when (fboundp func) (unless greedy - (throw 'fname (funcall func result))) - (funcall func result) + (throw 'fname + (if new-style + (funcall func (org-protocol-parse-parameters + result new-style)) + (warn "Please update your Org Protocol handler \ +to deal with new-style links.") + (funcall func result)))) + ;; Greedy protocol handlers are responsible for + ;; parsing their own filenames. + (funcall func result) (throw 'fname t)))))))) - ;; (message "fname: %s" fname) fname))) (defadvice server-visit-files (before org-protocol-detect-protocol-server activate) @@ -572,16 +663,18 @@ as filename." ;;; Org specific functions: (defun org-protocol-create-for-org () - "Create a org-protocol project for the current file's Org-mode project. + "Create a Org protocol project for the current file's project. The visited file needs to be part of a publishing project in `org-publish-project-alist' for this to work. The function delegates most of the work to `org-protocol-create'." (interactive) - (require 'org-publish) + (require 'ox-publish) (let ((all (or (org-publish-get-project-from-filename buffer-file-name)))) (if all (org-protocol-create (cdr all)) - (message "Not in an org-project. Did mean %s?" - (substitute-command-keys"\\[org-protocol-create]"))))) + (message "%s" + (substitute-command-keys + "Not in an Org project. \ +Did you mean `\\[org-protocol-create]'?"))))) (defun org-protocol-create (&optional project-plist) "Create a new org-protocol project interactively. @@ -600,19 +693,18 @@ the cdr of an element in `org-publish-project-alist', reuse (working-suffix (if (plist-get project-plist :base-extension) (concat "." (plist-get project-plist :base-extension)) ".org")) - (worglet-buffer nil) (insert-default-directory t) (minibuffer-allow-text-properties nil)) (setq base-url (read-string "Base URL of published content: " base-url nil base-url t)) - (if (not (string-match "\\/$" base-url)) - (setq base-url (concat base-url "/"))) + (or (string-suffix-p "/" base-url) + (setq base-url (concat base-url "/"))) (setq working-dir (expand-file-name (read-directory-name "Local working directory: " working-dir working-dir t))) - (if (not (string-match "\\/$" working-dir)) - (setq working-dir (concat working-dir "/"))) + (or (string-suffix-p "/" working-dir) + (setq working-dir (concat working-dir "/"))) (setq strip-suffix (read-string diff --git a/lisp/org/org-rmail.el b/lisp/org/org-rmail.el index 80bfce920c5..332c669a4fa 100644 --- a/lisp/org/org-rmail.el +++ b/lisp/org/org-rmail.el @@ -1,4 +1,4 @@ -;;; org-rmail.el --- Support for links to Rmail messages from within Org-mode +;;; org-rmail.el --- Support for Links to Rmail Messages -*- lexical-binding: t; -*- ;; Copyright (C) 2004-2017 Free Software Foundation, Inc. @@ -19,14 +19,14 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Commentary: -;; This file implements links to Rmail messages from within Org-mode. -;; Org-mode loads this module by default - if this is not what you want, -;; configure the variable `org-modules'. +;; This file implements links to Rmail messages from within Org mode. +;; Org mode loads this module by default - if this is not what you +;; want, configure the variable `org-modules'. ;;; Code: @@ -36,13 +36,14 @@ (declare-function rmail-show-message "rmail" (&optional n no-summary)) (declare-function rmail-what-message "rmail" (&optional pos)) (declare-function rmail-toggle-header "rmail" (&optional arg)) +(declare-function rmail "rmail" (&optional file-name-arg)) (declare-function rmail-widen "rmail" ()) (defvar rmail-current-message) ; From rmail.el (defvar rmail-header-style) ; From rmail.el +(defvar rmail-file-name) ; From rmail.el ;; Install the link type -(org-add-link-type "rmail" 'org-rmail-open) -(add-hook 'org-store-link-functions 'org-rmail-store-link) +(org-link-set-parameters "rmail" :follow #'org-rmail-open :store #'org-rmail-store-link) ;; Implementation (defun org-rmail-store-link () @@ -63,20 +64,11 @@ (to (mail-fetch-field "to")) (subject (mail-fetch-field "subject")) (date (mail-fetch-field "date")) - (date-ts (and date (format-time-string - (org-time-stamp-format t) - (date-to-time date)))) - (date-ts-ia (and date (format-time-string - (org-time-stamp-format t t) - (date-to-time date)))) desc link) (org-store-link-props - :type "rmail" :from from :to to + :type "rmail" :from from :to to :date date :subject subject :message-id message-id) - (when date - (org-add-link-props :date date :date-timestamp date-ts - :date-timestamp-inactive date-ts-ia)) - (setq message-id (org-remove-angle-brackets message-id)) + (setq message-id (org-unbracket-string "<" ">" message-id)) (setq desc (org-email-link-description)) (setq link (concat "rmail:" folder "#" message-id)) (org-add-link-props :link link :description desc) diff --git a/lisp/org/org-src.el b/lisp/org/org-src.el index 4eb8a531b85..4191d9aadcf 100644 --- a/lisp/org/org-src.el +++ b/lisp/org/org-src.el @@ -1,4 +1,4 @@ -;;; org-src.el --- Source code examples in Org +;;; org-src.el --- Source code examples in Org -*- lexical-binding: t; -*- ;; ;; Copyright (C) 2004-2017 Free Software Foundation, Inc. ;; @@ -21,48 +21,38 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Commentary: -;; This file contains the code dealing with source code examples in Org-mode. +;; This file contains the code dealing with source code examples in +;; Org mode. ;;; Code: +(require 'cl-lib) (require 'org-macs) (require 'org-compat) (require 'ob-keys) (require 'ob-comint) -(eval-when-compile - (require 'cl)) +(declare-function org-base-buffer "org" (buffer)) (declare-function org-do-remove-indentation "org" (&optional n)) -(declare-function org-at-table.el-p "org" ()) -(declare-function org-in-src-block-p "org" (&optional inside)) -(declare-function org-in-block-p "org" (names)) +(declare-function org-element-at-point "org-element" ()) +(declare-function org-element-class "org-element" (datum &optional parent)) +(declare-function org-element-context "org-element" (&optional element)) +(declare-function org-element-lineage "org-element" + (blob &optional types with-self)) +(declare-function org-element-property "org-element" (property element)) +(declare-function org-element-type "org-element" (element)) +(declare-function org-footnote-goto-definition "org-footnote" + (label &optional location)) (declare-function org-get-indentation "org" (&optional line)) (declare-function org-switch-to-buffer-other-window "org" (&rest args)) -(declare-function org-pop-to-buffer-same-window - "org-compat" (&optional buffer-or-name norecord label)) -(declare-function org-base-buffer "org" (buffer)) +(declare-function org-trim "org" (s &optional keep-lead)) -(defcustom org-edit-src-region-extra nil - "Additional regexps to identify regions for editing with `org-edit-src-code'. -For examples see the function `org-edit-src-find-region-and-lang'. -The regular expression identifying the begin marker should end with a newline, -and the regexp marking the end line should start with a newline, to make sure -there are kept outside the narrowed region." - :group 'org-edit-structure - :type '(repeat - (list - (regexp :tag "begin regexp") - (regexp :tag "end regexp") - (choice :tag "language" - (string :tag "specify") - (integer :tag "from match group") - (const :tag "from `lang' element") - (const :tag "from `style' element"))))) +(defvar org-inhibit-startup) (defcustom org-edit-src-turn-on-auto-save nil "Non-nil means turn `auto-save-mode' on when editing a source block. @@ -117,28 +107,29 @@ These are the regions where each line starts with a colon." (defcustom org-src-preserve-indentation nil "If non-nil preserve leading whitespace characters on export. +\\<org-mode-map> If non-nil leading whitespace characters in source code blocks are preserved on export, and when switching between the org -buffer and the language mode edit buffer. If this variable is nil -then, after editing with \\[org-edit-src-code], the -minimum (across-lines) number of leading whitespace characters -are removed from all lines, and the code block is uniformly -indented according to the value of `org-edit-src-content-indentation'." +buffer and the language mode edit buffer. + +When this variable is nil, after editing with `\\[org-edit-src-code]', +the minimum (across-lines) number of leading whitespace characters +are removed from all lines, and the code block is uniformly indented +according to the value of `org-edit-src-content-indentation'." :group 'org-edit-structure :type 'boolean) (defcustom org-edit-src-content-indentation 2 "Indentation for the content of a source code block. + This should be the number of spaces added to the indentation of the #+begin line in order to compute the indentation of the block content after -editing it with \\[org-edit-src-code]. Has no effect if -`org-src-preserve-indentation' is non-nil." +editing it with `\\[org-edit-src-code]'. + +It has no effect if `org-src-preserve-indentation' is non-nil." :group 'org-edit-structure :type 'integer) -(defvar org-src-strip-leading-and-trailing-blank-lines nil - "If non-nil, blank lines are removed when exiting the code edit buffer.") - (defcustom org-edit-src-persistent-message t "Non-nil means show persistent exit help message while editing src examples. The message is shown in the header-line, which will be created in the @@ -146,6 +137,17 @@ first line of the window showing the editing buffer." :group 'org-edit-structure :type 'boolean) +(defcustom org-src-ask-before-returning-to-edit-buffer t + "Non-nil means ask before switching to an existing edit buffer. +If nil, when `org-edit-src-code' is used on a block that already +has an active edit buffer, it will switch to that edit buffer +immediately; otherwise it will ask whether you want to return to +the existing edit buffer." + :group 'org-edit-structure + :version "24.4" + :package-version '(Org . "8.0") + :type 'boolean) + (defcustom org-src-window-setup 'reorganize-frame "How the source code edit buffer should be displayed. Possible values for this option are: @@ -167,10 +169,10 @@ other-frame Use `switch-to-buffer-other-frame' to display edit buffer. (defvar org-src-mode-hook nil "Hook run after Org switched a source code snippet to its Emacs mode. -This hook will run - -- when editing a source code snippet with `\\[org-src-mode-map]'. -- When formatting a source code snippet for export with htmlize. +\\<org-mode-map> +This hook will run: +- when editing a source code snippet with `\\[org-edit-special]' +- when formatting a source code snippet for export with htmlize. You may want to use this hook for example to turn off `outline-minor-mode' or similar things which you want to have when editing a source code file, @@ -180,7 +182,7 @@ but which mess up the display of a snippet in Org exported files.") '(("ocaml" . tuareg) ("elisp" . emacs-lisp) ("ditaa" . artist) ("asymptote" . asy) ("dot" . fundamental) ("sqlite" . sql) ("calc" . fundamental) ("C" . c) ("cpp" . c++) ("C++" . c++) - ("screen" . shell-script)) + ("screen" . shell-script) ("shell" . sh) ("bash" . sh)) "Alist mapping languages to their major mode. The key is the language name, the value is the string that should be inserted as the name of the major mode. For many languages this is @@ -194,451 +196,383 @@ For example, there is no ocaml-mode in Emacs, but the mode to use is (string "Language name") (symbol "Major mode")))) -;;; Editing source examples - -(defvar org-src-mode-map (make-sparse-keymap)) -(define-key org-src-mode-map "\C-c'" 'org-edit-src-exit) -(define-key org-src-mode-map "\C-c\C-k" 'org-edit-src-abort) -(define-key org-src-mode-map "\C-x\C-s" 'org-edit-src-save) +(defcustom org-src-block-faces nil + "Alist of faces to be used for source-block. +Each element is a cell of the format -(defvar org-edit-src-force-single-line nil) -(defvar org-edit-src-from-org-mode nil) -(defvar org-edit-src-allow-write-back-p t) -(defvar org-edit-src-picture nil) -(defvar org-edit-src-beg-marker nil) -(defvar org-edit-src-end-marker nil) -(defvar org-edit-src-overlay nil) -(defvar org-edit-src-block-indentation nil) -(defvar org-edit-src-saved-temp-window-config nil) + (\"language\" FACE) -(defcustom org-src-ask-before-returning-to-edit-buffer t - "If nil, when org-edit-src code is used on a block that already -has an active edit buffer, it will switch to that edit buffer -immediately; otherwise it will ask whether you want to return to -the existing edit buffer." - :group 'org-edit-structure - :version "24.4" - :package-version '(Org . "8.0") - :type 'boolean) +Where FACE is either a defined face or an anonymous face. -(defvar org-src-babel-info nil) - -(define-minor-mode org-src-mode - "Minor mode for language major mode buffers generated by org. -This minor mode is turned on in two situations: -- when editing a source code snippet with `\\[org-src-mode-map]'. -- When formatting a source code snippet for export with htmlize. -There is a mode hook, and keybindings for `org-edit-src-exit' and -`org-edit-src-save'") +For instance, the following value would color the background of +emacs-lisp source blocks and python source blocks in purple and +green, respectability. -(defvar org-edit-src-code-timer nil) -(defvar org-inhibit-startup) + \\='((\"emacs-lisp\" (:background \"#EEE2FF\")) + (\"python\" (:background \"#e5ffb8\")))" + :group 'org-edit-structure + :type '(repeat (list (string :tag "language") + (choice + (face :tag "Face") + (sexp :tag "Anonymous face")))) + :version "26.1" + :package-version '(Org . "9.0")) -(defun org-edit-src-code (&optional context code edit-buffer-name) - "Edit the source CODE block at point. -The code is copied to a separate buffer and the appropriate mode -is turned on. When done, exit with \\[org-edit-src-exit]. This will -remove the original code in the Org buffer, and replace it with the -edited version. An optional argument CONTEXT is used by \\[org-edit-src-save] -when calling this function. See `org-src-window-setup' to configure -the display of windows containing the Org buffer and the code buffer." - (interactive) - (if (not (or (org-in-block-p '("src" "example" "latex" "html")) - (org-at-table.el-p))) - (user-error "Not in a source code or example block") - (unless (eq context 'save) - (setq org-edit-src-saved-temp-window-config (current-window-configuration))) - (let* ((mark (and (org-region-active-p) (mark))) - (case-fold-search t) - (info - ;; If the src region consists in no lines, we insert a blank - ;; line. - (let* ((temp (org-edit-src-find-region-and-lang)) - (beg (nth 0 temp)) - (end (nth 1 temp))) - (if (>= end beg) temp - (goto-char beg) - (insert "\n") - (org-edit-src-find-region-and-lang)))) - (full-info (org-babel-get-src-block-info 'light)) - (org-mode-p (derived-mode-p 'org-mode)) ;; derived-mode-p is reflexive - (beg (make-marker)) - ;; Move marker with inserted text for case when src block is - ;; just one empty line, i.e. beg == end. - (end (copy-marker (make-marker) t)) - (allow-write-back-p (null code)) - block-nindent total-nindent ovl lang lang-f single buffer msg - begline markline markcol line col transmitted-variables) - (setq beg (move-marker beg (nth 0 info)) - end (move-marker end (nth 1 info)) - msg (if allow-write-back-p - "Edit, then exit with C-c ' (C-c and single quote) -- C-c C-k to abort" - "Exit with C-c ' (C-c and single quote) -- C-c C-k to abort") - code (or code (buffer-substring-no-properties beg end)) - lang (or (cdr (assoc (nth 2 info) org-src-lang-modes)) - (nth 2 info)) - lang (if (symbolp lang) (symbol-name lang) lang) - single (nth 3 info) - block-nindent (nth 5 info) - lang-f (intern (concat lang "-mode")) - begline (save-excursion (goto-char beg) (org-current-line)) - transmitted-variables - `((org-edit-src-content-indentation - ,org-edit-src-content-indentation) - (org-edit-src-force-single-line ,single) - (org-edit-src-from-org-mode ,org-mode-p) - (org-edit-src-allow-write-back-p ,allow-write-back-p) - (org-src-preserve-indentation ,org-src-preserve-indentation) - (org-src-babel-info ,(org-babel-get-src-block-info 'light)) - (org-coderef-label-format - ,(or (nth 4 info) org-coderef-label-format)) - (org-edit-src-beg-marker ,beg) - (org-edit-src-end-marker ,end) - (org-edit-src-block-indentation ,block-nindent))) - (if (and mark (>= mark beg) (<= mark (1+ end))) - (save-excursion (goto-char (min mark end)) - (setq markline (org-current-line) - markcol (current-column)))) - (if (equal lang-f 'table.el-mode) - (setq lang-f (lambda () - (text-mode) - (if (org-bound-and-true-p flyspell-mode) - (flyspell-mode -1)) - (table-recognize) - (org-set-local 'org-edit-src-content-indentation 0)))) - (unless (functionp lang-f) - (error "No such language mode: %s" lang-f)) - (save-excursion - (if (> (point) end) (goto-char end)) - (setq line (org-current-line) - col (current-column))) - (if (and (setq buffer (org-edit-src-find-buffer beg end)) - (or (eq context 'save) - (if org-src-ask-before-returning-to-edit-buffer - (y-or-n-p "Return to existing edit buffer ([n] will revert changes)? ") t))) - (org-src-switch-to-buffer buffer 'return) - (when buffer - (with-current-buffer buffer - (if (boundp 'org-edit-src-overlay) - (delete-overlay org-edit-src-overlay))) - (kill-buffer buffer)) - (setq buffer (generate-new-buffer - (or edit-buffer-name - (org-src-construct-edit-buffer-name (buffer-name) lang)))) - (setq ovl (make-overlay beg end)) - (overlay-put ovl 'edit-buffer buffer) - (overlay-put ovl 'help-echo "Click with mouse-1 to switch to buffer editing this segment") - (overlay-put ovl 'face 'secondary-selection) - (overlay-put ovl - 'keymap - (let ((map (make-sparse-keymap))) - (define-key map [mouse-1] 'org-edit-src-continue) - map)) - (overlay-put ovl :read-only "Leave me alone") - (setq transmitted-variables - (append transmitted-variables `((org-edit-src-overlay ,ovl)))) - (org-src-switch-to-buffer buffer 'edit) - (if (eq single 'macro-definition) - (setq code (replace-regexp-in-string "\\\\n" "\n" code t t))) - (insert code) - (remove-text-properties (point-min) (point-max) - '(display nil invisible nil intangible nil)) - (unless (cadr (assq 'org-src-preserve-indentation transmitted-variables)) - (setq total-nindent (or (org-do-remove-indentation) 0))) - (let ((org-inhibit-startup t)) - (condition-case e - (funcall lang-f) - (error - (message "Language mode `%s' fails with: %S" lang-f (nth 1 e))))) - (dolist (pair transmitted-variables) - (org-set-local (car pair) (cadr pair))) - ;; Remove protecting commas from visible part of buffer. - (org-unescape-code-in-region (point-min) (point-max)) - (when markline - (org-goto-line (1+ (- markline begline))) - (org-move-to-column - (if org-src-preserve-indentation markcol - (max 0 (- markcol total-nindent)))) - (push-mark (point) 'no-message t) - (setq deactivate-mark nil)) - (org-goto-line (1+ (- line begline))) - (org-move-to-column - (if org-src-preserve-indentation col (max 0 (- col total-nindent)))) - (org-src-mode) - (set-buffer-modified-p nil) - (setq buffer-file-name nil) - (when org-edit-src-turn-on-auto-save - (setq buffer-auto-save-file-name - (concat (make-temp-name "org-src-") - (format-time-string "-%Y-%d-%m") ".txt"))) - (and org-edit-src-persistent-message - (org-set-local 'header-line-format msg)) - (let ((edit-prep-func (intern (concat "org-babel-edit-prep:" lang)))) - (when (fboundp edit-prep-func) - (funcall edit-prep-func full-info))) - (or org-edit-src-code-timer - (zerop org-edit-src-auto-save-idle-delay) - (setq org-edit-src-code-timer - (run-with-idle-timer - org-edit-src-auto-save-idle-delay t - (lambda () - (cond - ((org-string-match-p "\\`\\*Org Src" (buffer-name)) - (when (buffer-modified-p) (org-edit-src-save))) - ((not (org-some (lambda (b) - (org-string-match-p "\\`\\*Org Src" - (buffer-name b))) - (buffer-list))) - (cancel-timer org-edit-src-code-timer) - (setq org-edit-src-code-timer nil)))))))) - t))) +(defcustom org-src-tab-acts-natively nil + "If non-nil, the effect of TAB in a code block is as if it were +issued in the language major mode buffer." + :type 'boolean + :version "24.1" + :group 'org-babel) -(defun org-edit-src-continue (e) - "Continue editing source blocks." ;; Fixme: be more accurate - (interactive "e") - (mouse-set-point e) - (let ((buf (get-char-property (point) 'edit-buffer))) - (if buf (org-src-switch-to-buffer buf 'continue) - (error "Something is wrong here")))) -(defun org-src-switch-to-buffer (buffer context) - (case org-src-window-setup - ('current-window - (org-pop-to-buffer-same-window buffer)) - ('other-window - (switch-to-buffer-other-window buffer)) - ('other-frame - (case context - ('exit - (let ((frame (selected-frame))) - (switch-to-buffer-other-frame buffer) - (delete-frame frame))) - ('save - (kill-buffer (current-buffer)) - (org-pop-to-buffer-same-window buffer)) - (t - (switch-to-buffer-other-frame buffer)))) - ('reorganize-frame - (if (eq context 'edit) (delete-other-windows)) - (org-switch-to-buffer-other-window buffer) - (if (eq context 'exit) (delete-other-windows))) - ('switch-invisibly - (set-buffer buffer)) - (t - (message "Invalid value %s for org-src-window-setup" - (symbol-name org-src-window-setup)) - (org-pop-to-buffer-same-window buffer)))) - -(defun org-src-construct-edit-buffer-name (org-buffer-name lang) + +;;; Internal functions and variables + +(defvar org-src--allow-write-back t) +(defvar org-src--auto-save-timer nil) +(defvar org-src--babel-info nil) +(defvar org-src--beg-marker nil) +(defvar org-src--block-indentation nil) +(defvar org-src--end-marker nil) +(defvar org-src--from-org-mode nil) +(defvar org-src--overlay nil) +(defvar org-src--preserve-indentation nil) +(defvar org-src--remote nil) +(defvar org-src--saved-temp-window-config nil) +(defvar org-src--source-type nil + "Type of element being edited, as a symbol.") +(defvar org-src--tab-width nil + "Contains `tab-width' value from Org source buffer. +However, if `indent-tabs-mode' is nil in that buffer, its value +is 0.") + +(defun org-src--construct-edit-buffer-name (org-buffer-name lang) "Construct the buffer name for a source editing buffer." (concat "*Org Src " org-buffer-name "[ " lang " ]*")) -(defun org-src-edit-buffer-p (&optional buffer) - "Test whether BUFFER (or the current buffer if BUFFER is nil) -is a source block editing buffer." - (let ((buffer (org-base-buffer (or buffer (current-buffer))))) - (and (buffer-name buffer) - (string-match "\\`*Org Src " (buffer-name buffer)) - (local-variable-p 'org-edit-src-beg-marker buffer) - (local-variable-p 'org-edit-src-end-marker buffer)))) - -(defun org-edit-src-find-buffer (beg end) - "Find a source editing buffer that is already editing the region BEG to END." +(defun org-src--edit-buffer (beg end) + "Return buffer editing area between BEG and END. +Return nil if there is no such buffer." (catch 'exit - (mapc - (lambda (b) - (with-current-buffer b - (if (and (string-match "\\`*Org Src " (buffer-name)) - (local-variable-p 'org-edit-src-beg-marker (current-buffer)) - (local-variable-p 'org-edit-src-end-marker (current-buffer)) - (equal beg org-edit-src-beg-marker) - (equal end org-edit-src-end-marker)) - (throw 'exit (current-buffer))))) - (buffer-list)) - nil)) + (dolist (b (buffer-list)) + (with-current-buffer b + (and (org-src-edit-buffer-p) + (= beg org-src--beg-marker) + (eq (marker-buffer beg) (marker-buffer org-src--beg-marker)) + (= end org-src--end-marker) + (eq (marker-buffer end) (marker-buffer org-src--end-marker)) + (throw 'exit b)))))) + +(defun org-src--source-buffer () + "Return source buffer edited by current buffer." + (unless (org-src-edit-buffer-p) (error "Not in a source buffer")) + (or (marker-buffer org-src--beg-marker) + (error "No source buffer available for current editing session"))) + +(defun org-src--get-lang-mode (lang) + "Return major mode that should be used for LANG. +LANG is a string, and the returned major mode is a symbol." + (intern + (concat + (let ((l (or (cdr (assoc lang org-src-lang-modes)) lang))) + (if (symbolp l) (symbol-name l) l)) + "-mode"))) -(defun org-edit-fixed-width-region () - "Edit the fixed-width ascii drawing at point. -This must be a region where each line starts with a colon followed by -a space character. -An new buffer is created and the fixed-width region is copied into it, -and the buffer is switched into `artist-mode' for editing. When done, -exit with \\[org-edit-src-exit]. The edited text will then replace -the fragment in the Org-mode buffer." - (interactive) - (let ((line (org-current-line)) - (col (current-column)) - (case-fold-search t) - (msg "Edit, then exit with C-c ' (C-c and single quote) -- C-c C-k to abort") - (org-mode-p (derived-mode-p 'org-mode)) - (beg (make-marker)) - (end (make-marker)) - block-nindent ovl beg1 end1 code begline buffer) - (beginning-of-line 1) - (if (looking-at "[ \t]*[^:\n \t]") - nil - (if (looking-at "[ \t]*\\(\n\\|\\'\\)") - (setq beg1 (point) end1 beg1) - (save-excursion - (if (re-search-backward "^[ \t]*[^: \t]" nil 'move) - (setq beg1 (point-at-bol 2)) - (setq beg1 (point)))) - (save-excursion - (if (re-search-forward "^[ \t]*[^: \t]" nil 'move) - (setq end1 (1- (match-beginning 0))) - (setq end1 (point)))) - (org-goto-line line)) - (setq beg (move-marker beg beg1) - end (move-marker end end1) - code (buffer-substring-no-properties beg end) - begline (save-excursion (goto-char beg) (org-current-line))) - (if (and (setq buffer (org-edit-src-find-buffer beg end)) - (y-or-n-p "Return to existing edit buffer ([n] will revert changes)? ")) - (org-pop-to-buffer-same-window buffer) - (when buffer - (with-current-buffer buffer - (if (boundp 'org-edit-src-overlay) - (delete-overlay org-edit-src-overlay))) - (kill-buffer buffer)) - (setq buffer (generate-new-buffer - (org-src-construct-edit-buffer-name - (buffer-name) "Fixed Width"))) - (setq ovl (make-overlay beg end)) - (overlay-put ovl 'face 'secondary-selection) - (overlay-put ovl 'edit-buffer buffer) - (overlay-put ovl 'help-echo "Click with mouse-1 to switch to buffer editing this segment") - (overlay-put ovl 'face 'secondary-selection) - (overlay-put ovl - 'keymap - (let ((map (make-sparse-keymap))) - (define-key map [mouse-1] 'org-edit-src-continue) - map)) - (overlay-put ovl :read-only "Leave me alone") - (org-pop-to-buffer-same-window buffer) - (insert code) +(defun org-src--coordinates (pos beg end) + "Return coordinates of POS relatively to BEG and END. +POS, BEG and END are buffer positions. Return value is either +a cons cell (LINE . COLUMN) or symbol `end'. See also +`org-src--goto-coordinates'." + (if (>= pos end) 'end + (org-with-wide-buffer + (goto-char (max beg pos)) + (cons (count-lines beg (line-beginning-position)) + ;; Column is relative to the end of line to avoid problems of + ;; comma escaping or colons appended in front of the line. + (- (current-column) + (progn (end-of-line) (current-column))))))) + +(defun org-src--goto-coordinates (coord beg end) + "Move to coordinates COORD relatively to BEG and END. +COORD are coordinates, as returned by `org-src--coordinates', +which see. BEG and END are buffer positions." + (goto-char + (if (eq coord 'end) (max (1- end) beg) + ;; If BEG happens to be located outside of the narrowed part of + ;; the buffer, widen it first. + (org-with-wide-buffer + (goto-char beg) + (forward-line (car coord)) + (end-of-line) + (org-move-to-column (max (+ (current-column) (cdr coord)) 0)) + (point))))) + +(defun org-src--contents-area (datum) + "Return contents boundaries of DATUM. +DATUM is an element or object. Return a list (BEG END CONTENTS) +where BEG and END are buffer positions and CONTENTS is a string." + (let ((type (org-element-type datum))) + (org-with-wide-buffer + (cond + ((eq type 'footnote-definition) + (let* ((beg (progn + (goto-char (org-element-property :post-affiliated datum)) + (search-forward "]"))) + (end (or (org-element-property :contents-end datum) beg))) + (list beg end (buffer-substring-no-properties beg end)))) + ((eq type 'inline-src-block) + (let ((beg (progn (goto-char (org-element-property :begin datum)) + (search-forward "{" (line-end-position) t))) + (end (progn (goto-char (org-element-property :end datum)) + (search-backward "}" (line-beginning-position) t)))) + (list beg end (buffer-substring-no-properties beg end)))) + ((org-element-property :contents-begin datum) + (let ((beg (org-element-property :contents-begin datum)) + (end (org-element-property :contents-end datum))) + (list beg end (buffer-substring-no-properties beg end)))) + ((memq type '(example-block export-block src-block)) + (list (progn (goto-char (org-element-property :post-affiliated datum)) + (line-beginning-position 2)) + (progn (goto-char (org-element-property :end datum)) + (skip-chars-backward " \r\t\n") + (line-beginning-position 1)) + (org-element-property :value datum))) + ((memq type '(fixed-width latex-environment table)) + (let ((beg (org-element-property :post-affiliated datum)) + (end (progn (goto-char (org-element-property :end datum)) + (skip-chars-backward " \r\t\n") + (line-beginning-position 2)))) + (list beg + end + (if (eq type 'fixed-width) (org-element-property :value datum) + (buffer-substring-no-properties beg end))))) + (t (error "Unsupported element or object: %s" type)))))) + +(defun org-src--make-source-overlay (beg end edit-buffer) + "Create overlay between BEG and END positions and return it. +EDIT-BUFFER is the buffer currently editing area between BEG and +END." + (let ((overlay (make-overlay beg end))) + (overlay-put overlay 'face 'secondary-selection) + (overlay-put overlay 'edit-buffer edit-buffer) + (overlay-put overlay 'help-echo + "Click with mouse-1 to switch to buffer editing this segment") + (overlay-put overlay 'face 'secondary-selection) + (overlay-put overlay 'keymap + (let ((map (make-sparse-keymap))) + (define-key map [mouse-1] 'org-edit-src-continue) + map)) + (let ((read-only + (list + (lambda (&rest _) + (user-error + "Cannot modify an area being edited in a dedicated buffer"))))) + (overlay-put overlay 'modification-hooks read-only) + (overlay-put overlay 'insert-in-front-hooks read-only) + (overlay-put overlay 'insert-behind-hooks read-only)) + overlay)) + +(defun org-src--remove-overlay () + "Remove overlay from current source buffer." + (when (overlayp org-src--overlay) (delete-overlay org-src--overlay))) + +(defun org-src--on-datum-p (datum) + "Non-nil when point is on DATUM. +DATUM is an element or an object. Consider blank lines or white +spaces after it as being outside." + (and (>= (point) (org-element-property :begin datum)) + (<= (point) + (org-with-wide-buffer + (goto-char (org-element-property :end datum)) + (skip-chars-backward " \r\t\n") + (if (eq (org-element-class datum) 'element) + (line-end-position) + (point)))))) + +(defun org-src--contents-for-write-back () + "Return buffer contents in a format appropriate for write back. +Assume point is in the corresponding edit buffer." + (let ((indentation-offset + (if org-src--preserve-indentation 0 + (+ (or org-src--block-indentation 0) + (if (memq org-src--source-type '(example-block src-block)) + org-edit-src-content-indentation + 0)))) + (use-tabs? (and (> org-src--tab-width 0) t)) + (source-tab-width org-src--tab-width) + (contents (org-with-wide-buffer (buffer-string))) + (write-back org-src--allow-write-back)) + (with-temp-buffer + ;; Reproduce indentation parameters from source buffer. + (setq-local indent-tabs-mode use-tabs?) + (when (> source-tab-width 0) (setq-local tab-width source-tab-width)) + ;; Apply WRITE-BACK function on edit buffer contents. + (insert (org-no-properties contents)) + (goto-char (point-min)) + (when (functionp write-back) (save-excursion (funcall write-back))) + ;; Add INDENTATION-OFFSET to every non-empty line in buffer, + ;; unless indentation is meant to be preserved. + (when (> indentation-offset 0) + (while (not (eobp)) + (skip-chars-forward " \t") + (unless (eolp) ;ignore blank lines + (let ((i (current-column))) + (delete-region (line-beginning-position) (point)) + (indent-to (+ i indentation-offset)))) + (forward-line))) + (buffer-string)))) + +(defun org-src--edit-element + (datum name &optional initialize write-back contents remote) + "Edit DATUM contents in a dedicated buffer NAME. + +INITIALIZE is a function to call upon creating the buffer. + +When WRITE-BACK is non-nil, assume contents will replace original +region. Moreover, if it is a function, apply it in the edit +buffer, from point min, before returning the contents. + +When CONTENTS is non-nil, display them in the edit buffer. +Otherwise, show DATUM contents as specified by +`org-src--contents-area'. + +When REMOTE is non-nil, do not try to preserve point or mark when +moving from the edit area to the source. + +Leave point in edit buffer." + (setq org-src--saved-temp-window-config (current-window-configuration)) + (let* ((area (org-src--contents-area datum)) + (beg (copy-marker (nth 0 area))) + (end (copy-marker (nth 1 area) t)) + (old-edit-buffer (org-src--edit-buffer beg end)) + (contents (or contents (nth 2 area)))) + (if (and old-edit-buffer + (or (not org-src-ask-before-returning-to-edit-buffer) + (y-or-n-p "Return to existing edit buffer ([n] will revert changes)? "))) + ;; Move to existing buffer. + (org-src-switch-to-buffer old-edit-buffer 'return) + ;; Discard old edit buffer. + (when old-edit-buffer + (with-current-buffer old-edit-buffer (org-src--remove-overlay)) + (kill-buffer old-edit-buffer)) + (let* ((org-mode-p (derived-mode-p 'org-mode)) + (source-tab-width (if indent-tabs-mode tab-width 0)) + (type (org-element-type datum)) + (ind (org-with-wide-buffer + (goto-char (org-element-property :begin datum)) + (org-get-indentation))) + (preserve-ind + (and (memq type '(example-block src-block)) + (or (org-element-property :preserve-indent datum) + org-src-preserve-indentation))) + ;; Store relative positions of mark (if any) and point + ;; within the edited area. + (point-coordinates (and (not remote) + (org-src--coordinates (point) beg end))) + (mark-coordinates (and (not remote) + (org-region-active-p) + (let ((m (mark))) + (and (>= m beg) (>= end m) + (org-src--coordinates m beg end))))) + ;; Generate a new edit buffer. + (buffer (generate-new-buffer name)) + ;; Add an overlay on top of source. + (overlay (org-src--make-source-overlay beg end buffer))) + ;; Switch to edit buffer. + (org-src-switch-to-buffer buffer 'edit) + ;; Insert contents. + (insert contents) (remove-text-properties (point-min) (point-max) '(display nil invisible nil intangible nil)) - (setq block-nindent (or (org-do-remove-indentation) 0)) - (cond - ((eq org-edit-fixed-width-region-mode 'artist-mode) - (fundamental-mode) - (artist-mode 1)) - (t (funcall org-edit-fixed-width-region-mode))) - (set (make-local-variable 'org-edit-src-force-single-line) nil) - (set (make-local-variable 'org-edit-src-from-org-mode) org-mode-p) - (set (make-local-variable 'org-edit-src-picture) t) - (goto-char (point-min)) - (while (re-search-forward "^[ \t]*: ?" nil t) - (replace-match "")) - (org-goto-line (1+ (- line begline))) - (org-move-to-column (max 0 (- col block-nindent 2))) - (org-set-local 'org-edit-src-beg-marker beg) - (org-set-local 'org-edit-src-end-marker end) - (org-set-local 'org-edit-src-overlay ovl) - (org-set-local 'org-edit-src-block-indentation block-nindent) - (org-set-local 'org-edit-src-content-indentation 0) - (org-set-local 'org-src-preserve-indentation nil) - (org-src-mode) + (unless preserve-ind (org-do-remove-indentation)) (set-buffer-modified-p nil) - (and org-edit-src-persistent-message - (org-set-local 'header-line-format msg))) - (message "%s" msg) - t))) + (setq buffer-file-name nil) + ;; Initialize buffer. + (when (functionp initialize) + (let ((org-inhibit-startup t)) + (condition-case e + (funcall initialize) + (error (message "Initialization fails with: %S" + (error-message-string e)))))) + ;; Transmit buffer-local variables for exit function. It must + ;; be done after initializing major mode, as this operation + ;; may reset them otherwise. + (setq-local org-src--tab-width source-tab-width) + (setq-local org-src--from-org-mode org-mode-p) + (setq-local org-src--beg-marker beg) + (setq-local org-src--end-marker end) + (setq-local org-src--remote remote) + (setq-local org-src--source-type type) + (setq-local org-src--block-indentation ind) + (setq-local org-src--preserve-indentation preserve-ind) + (setq-local org-src--overlay overlay) + (setq-local org-src--allow-write-back write-back) + ;; Start minor mode. + (org-src-mode) + ;; Move mark and point in edit buffer to the corresponding + ;; location. + (if remote + (progn + ;; Put point at first non read-only character after + ;; leading blank. + (goto-char + (or (text-property-any (point-min) (point-max) 'read-only nil) + (point-max))) + (skip-chars-forward " \r\t\n")) + ;; Set mark and point. + (when mark-coordinates + (org-src--goto-coordinates mark-coordinates (point-min) (point-max)) + (push-mark (point) 'no-message t) + (setq deactivate-mark nil)) + (org-src--goto-coordinates + point-coordinates (point-min) (point-max))))))) + + + +;;; Fontification of source blocks -(defun org-edit-src-find-region-and-lang () - "Find the region and language for a local edit. -Return a list with beginning and end of the region, a string representing -the language, a switch telling if the content should be in a single line." - (let ((re-list - (append - org-edit-src-region-extra - '( - ("<src\\>[^<]*>[ \t]*\n?" "\n?[ \t]*</src>" lang) - ("<literal\\>[^<]*>[ \t]*\n?" "\n?[ \t]*</literal>" style) - ("<example>[ \t]*\n?" "\n?[ \t]*</example>" "fundamental") - ("<lisp>[ \t]*\n?" "\n?[ \t]*</lisp>" "emacs-lisp") - ("<perl>[ \t]*\n?" "\n?[ \t]*</perl>" "perl") - ("<python>[ \t]*\n?" "\n?[ \t]*</python>" "python") - ("<ruby>[ \t]*\n?" "\n?[ \t]*</ruby>" "ruby") - ("^[ \t]*#\\+begin_src\\( \\([^ \t\n]+\\)\\)?.*\n" "\n[ \t]*#\\+end_src" 2) - ("^[ \t]*#\\+begin_example.*\n" "\n[ \t]*#\\+end_example" "fundamental") - ("^[ \t]*#\\+html:" "\n" "html" single-line) - ("^[ \t]*#\\+begin_html.*\n" "\n[ \t]*#\\+end_html" "html") - ("^[ \t]*#\\+latex:" "\n" "latex" single-line) - ("^[ \t]*#\\+begin_latex.*\n" "\n[ \t]*#\\+end_latex" "latex") - ("^[ \t]*#\\+ascii:" "\n" "fundamental" single-line) - ("^[ \t]*#\\+begin_ascii.*\n" "\n[ \t]*#\\+end_ascii" "fundamental") - ("^[ \t]*#\\+macro:[ \t]+\\S-+\\( \\|$\\)" - "\n" "fundamental" macro-definition) - ))) - (pos (point)) - re1 re2 single beg end lang lfmt match-re1 ind entry) - (catch 'exit - (while (setq entry (pop re-list)) - (setq re1 (car entry) re2 (nth 1 entry) lang (nth 2 entry) - single (nth 3 entry)) - (save-excursion - (if (or (looking-at re1) - (re-search-backward re1 nil t)) - (progn - (setq match-re1 (match-string 0)) - (setq beg (match-end 0) - lang (org-edit-src-get-lang lang) - lfmt (org-edit-src-get-label-format match-re1) - ind (org-edit-src-get-indentation (match-beginning 0))) - (if (and (re-search-forward re2 nil t) - (>= (match-end 0) pos)) - (throw 'exit (list beg (match-beginning 0) - lang single lfmt ind)))) - (if (or (looking-at re2) - (re-search-forward re2 nil t)) - (progn - (setq end (match-beginning 0)) - (if (and (re-search-backward re1 nil t) - (<= (match-beginning 0) pos)) - (progn - (setq lfmt (org-edit-src-get-label-format - (match-string 0)) - ind (org-edit-src-get-indentation - (match-beginning 0))) - (throw 'exit - (list (match-end 0) end - (org-edit-src-get-lang lang) - single lfmt ind))))))))) - (when (org-at-table.el-p) - (re-search-backward "^[\t]*[^ \t|\\+]" nil t) - (setq beg (1+ (point-at-eol))) - (goto-char beg) - (or (re-search-forward "^[\t]*[^ \t|\\+]" nil t) - (progn (goto-char (point-max)) (newline))) - (setq end (1- (point-at-bol))) - (throw 'exit (list beg end 'table.el nil nil 0)))))) - -(defun org-edit-src-get-lang (lang) - "Extract the src language." - (let ((m (match-string 0))) - (cond - ((stringp lang) lang) - ((integerp lang) (match-string lang)) - ((and (eq lang 'lang) - (string-match "\\<lang=\"\\([^ \t\n\"]+\\)\"" m)) - (match-string 1 m)) - ((and (eq lang 'style) - (string-match "\\<style=\"\\([^ \t\n\"]+\\)\"" m)) - (match-string 1 m)) - (t "fundamental")))) - -(defun org-edit-src-get-label-format (s) - "Extract the label format." - (save-match-data - (if (string-match "-l[ \t]+\\\\?\"\\([^\t\r\n\"]+\\)\\\\?\"" s) - (match-string 1 s)))) - -(defun org-edit-src-get-indentation (pos) - "Count leading whitespace characters on line." - (save-match-data - (goto-char pos) - (org-get-indentation))) +(defun org-src-font-lock-fontify-block (lang start end) + "Fontify code block. +This function is called by emacs automatic fontification, as long +as `org-src-fontify-natively' is non-nil." + (let ((lang-mode (org-src--get-lang-mode lang))) + (when (fboundp lang-mode) + (let ((string (buffer-substring-no-properties start end)) + (modified (buffer-modified-p)) + (org-buffer (current-buffer))) + (remove-text-properties start end '(face nil)) + (with-current-buffer + (get-buffer-create + (format " *org-src-fontification:%s*" lang-mode)) + (let ((inhibit-modification-hooks nil)) + (erase-buffer) + ;; Add string and a final space to ensure property change. + (insert string " ")) + (unless (eq major-mode lang-mode) (funcall lang-mode)) + (org-font-lock-ensure) + (let ((pos (point-min)) next) + (while (setq next (next-property-change pos)) + ;; Handle additional properties from font-lock, so as to + ;; preserve, e.g., composition. + (dolist (prop (cons 'face font-lock-extra-managed-props)) + (let ((new-prop (get-text-property pos prop))) + (put-text-property + (+ start (1- pos)) (1- (+ start next)) prop new-prop + org-buffer))) + (setq pos next)))) + ;; Add Org faces. + (let ((src-face (nth 1 (assoc-string lang org-src-block-faces t)))) + (when (or (facep src-face) (listp src-face)) + (font-lock-append-text-property start end 'face src-face)) + (font-lock-append-text-property start end 'face 'org-block)) + (add-text-properties + start end + '(font-lock-fontified t fontified t font-lock-multiline t)) + (set-buffer-modified-p modified))))) + + +;;; Escape contents (defun org-escape-code-in-region (beg end) "Escape lines between BEG and END. @@ -646,15 +580,16 @@ Escaping happens when a line starts with \"*\", \"#+\", \",*\" or \",#+\" by appending a comma to it." (interactive "r") (save-excursion - (goto-char beg) - (while (re-search-forward "^[ \t]*,?\\(\\*\\|#\\+\\)" end t) - (replace-match ",\\1" nil nil nil 1)))) + (goto-char end) + (while (re-search-backward "^[ \t]*\\(,*\\(?:\\*\\|#\\+\\)\\)" beg t) + (save-excursion (replace-match ",\\1" nil nil nil 1))))) (defun org-escape-code-in-string (s) "Escape lines in string S. Escaping happens when a line starts with \"*\", \"#+\", \",*\" or \",#+\" by appending a comma to it." - (replace-regexp-in-string "^[ \t]*,?\\(\\*\\|#\\+\\)" ",\\1" s nil nil 1)) + (replace-regexp-in-string "^[ \t]*\\(,*\\(?:\\*\\|#\\+\\)\\)" ",\\1" + s nil nil 1)) (defun org-unescape-code-in-region (beg end) "Un-escape lines between BEG and END. @@ -662,180 +597,93 @@ Un-escaping happens by removing the first comma on lines starting with \",*\", \",#+\", \",,*\" and \",,#+\"." (interactive "r") (save-excursion - (goto-char beg) - (while (re-search-forward "^[ \t]*,?\\(,\\)\\(?:\\*\\|#\\+\\)" end t) - (replace-match "" nil nil nil 1)))) + (goto-char end) + (while (re-search-backward "^[ \t]*,*\\(,\\)\\(?:\\*\\|#\\+\\)" beg t) + (save-excursion (replace-match "" nil nil nil 1))))) (defun org-unescape-code-in-string (s) "Un-escape lines in string S. Un-escaping happens by removing the first comma on lines starting with \",*\", \",#+\", \",,*\" and \",,#+\"." (replace-regexp-in-string - "^[ \t]*,?\\(,\\)\\(?:\\*\\|#\\+\\)" "" s nil nil 1)) + "^[ \t]*,*\\(,\\)\\(?:\\*\\|#\\+\\)" "" s nil nil 1)) -(defun org-edit-src-exit (&optional context) - "Exit special edit and protect problematic lines." - (interactive) - (unless (org-bound-and-true-p org-edit-src-from-org-mode) - (error "This is not a sub-editing buffer, something is wrong")) - (widen) - (let* ((fixed-width-p (string-match "Fixed Width" (buffer-name))) - (beg org-edit-src-beg-marker) - (end org-edit-src-end-marker) - (ovl org-edit-src-overlay) - (bufstr (buffer-string)) - (buffer (current-buffer)) - (single (org-bound-and-true-p org-edit-src-force-single-line)) - (macro (eq single 'macro-definition)) - (total-nindent (+ (or org-edit-src-block-indentation 0) - org-edit-src-content-indentation)) - (preserve-indentation org-src-preserve-indentation) - (allow-write-back-p (org-bound-and-true-p org-edit-src-allow-write-back-p)) - (delta 0) code line col indent) - (when allow-write-back-p - (unless preserve-indentation (untabify (point-min) (point-max))) - (if org-src-strip-leading-and-trailing-blank-lines - (save-excursion - (goto-char (point-min)) - (if (looking-at "[ \t\n]*\n") (replace-match "")) - (unless macro - (if (re-search-forward "\n[ \t\n]*\\'" nil t) (replace-match "")))))) - (setq line (if (org-bound-and-true-p org-edit-src-force-single-line) - 1 - (org-current-line)) - col (current-column)) - (when allow-write-back-p - (when single - (goto-char (point-min)) - (if (re-search-forward "\\s-+\\'" nil t) (replace-match "")) - (goto-char (point-min)) - (let ((cnt 0)) - (while (re-search-forward "\n" nil t) - (setq cnt (1+ cnt)) - (replace-match (if macro "\\n" " ") t t)) - (when (and macro (> cnt 0)) - (goto-char (point-max)) (insert "\\n"))) - (goto-char (point-min)) - (if (looking-at "\\s-*") (replace-match " "))) - (when (and (org-bound-and-true-p org-edit-src-from-org-mode) - (not fixed-width-p)) - (org-escape-code-in-region (point-min) (point-max)) - (setq delta (+ delta - (save-excursion - (org-goto-line line) - (if (looking-at "[ \t]*\\(,,\\)?\\(\\*\\|#+\\)") 1 - 0))))) - (when (org-bound-and-true-p org-edit-src-picture) - (setq preserve-indentation nil) - (untabify (point-min) (point-max)) - (goto-char (point-min)) - (while (re-search-forward "^" nil t) - (replace-match ": "))) - (unless (or single preserve-indentation (= total-nindent 0)) - (setq indent (make-string total-nindent ?\ )) - (goto-char (point-min)) - (while (re-search-forward "\\(^\\).+" nil t) - (replace-match indent nil nil nil 1))) - (if (org-bound-and-true-p org-edit-src-picture) - (setq total-nindent (+ total-nindent 2))) - (setq code (buffer-string)) - (when (eq context 'save) - (erase-buffer) - (insert bufstr)) - (set-buffer-modified-p nil)) - (org-src-switch-to-buffer (marker-buffer beg) (or context 'exit)) - (if (eq context 'save) (save-buffer) - (with-current-buffer buffer - (set-buffer-modified-p nil)) - (kill-buffer buffer)) - (goto-char beg) - (when allow-write-back-p - (undo-boundary) - (delete-region beg (max beg end)) - (unless (string-match "\\`[ \t]*\\'" code) - (insert code)) - ;; Make sure the overlay stays in place - (when (eq context 'save) (move-overlay ovl beg (point))) - (goto-char beg) - (if single (just-one-space))) - (if (memq t (mapcar (lambda (overlay) - (eq (overlay-get overlay 'invisible) - 'org-hide-block)) - (overlays-at (point)))) - ;; Block is hidden; put point at start of block - (beginning-of-line 0) - ;; Block is visible, put point where it was in the code buffer - (when allow-write-back-p - (org-goto-line (1- (+ (org-current-line) line))) - (org-move-to-column (if preserve-indentation col (+ col total-nindent delta))))) - (unless (eq context 'save) - (move-marker beg nil) - (move-marker end nil))) - (unless (eq context 'save) - (when org-edit-src-saved-temp-window-config - (set-window-configuration org-edit-src-saved-temp-window-config) - (setq org-edit-src-saved-temp-window-config nil)))) - -(defun org-edit-src-abort () - "Abort editing of the src code and return to the Org buffer." - (interactive) - (let (org-edit-src-allow-write-back-p) - (org-edit-src-exit 'exit))) - -(defmacro org-src-in-org-buffer (&rest body) - `(let ((p (point)) (m (mark)) (ul buffer-undo-list) msg) - (save-window-excursion - (org-edit-src-exit 'save) - ,@body - (setq msg (current-message)) - (if (eq org-src-window-setup 'other-frame) - (let ((org-src-window-setup 'current-window)) - (org-edit-src-code 'save)) - (org-edit-src-code 'save))) - (setq buffer-undo-list ul) - (push-mark m 'nomessage) - (goto-char (min p (point-max))) - (message (or msg "")))) -(def-edebug-spec org-src-in-org-buffer (body)) -(defun org-edit-src-save () - "Save parent buffer with current state source-code buffer." - (interactive) - (if (string-match "Fixed Width" (buffer-name)) - (user-error "%s" "Use C-c ' to save and exit, C-c C-k to abort editing") - (org-src-in-org-buffer (save-buffer)))) + +;;; Org src minor mode -(declare-function org-babel-tangle "ob-tangle" (&optional arg target-file lang)) +(defvar org-src-mode-map + (let ((map (make-sparse-keymap))) + (define-key map "\C-c'" 'org-edit-src-exit) + (define-key map "\C-c\C-k" 'org-edit-src-abort) + (define-key map "\C-x\C-s" 'org-edit-src-save) + map)) -(defun org-src-tangle (arg) - "Tangle the parent buffer." - (interactive) - (org-src-in-org-buffer (org-babel-tangle arg))) +(define-minor-mode org-src-mode + "Minor mode for language major mode buffers generated by Org. +\\<org-mode-map> +This minor mode is turned on in two situations: + - when editing a source code snippet with `\\[org-edit-special]' + - when formatting a source code snippet for export with htmlize. + +\\{org-src-mode-map} + +See also `org-src-mode-hook'." + nil " OrgSrc" nil + (when org-edit-src-persistent-message + (setq-local + header-line-format + (substitute-command-keys + (if org-src--allow-write-back + "Edit, then exit with `\\[org-edit-src-exit]' or abort with \ +`\\[org-edit-src-abort]'" + "Exit with `\\[org-edit-src-exit]' or abort with \ +`\\[org-edit-src-abort]'")))) + ;; Possibly activate various auto-save features (for the edit buffer + ;; or the source buffer). + (when org-edit-src-turn-on-auto-save + (setq buffer-auto-save-file-name + (concat (make-temp-name "org-src-") + (format-time-string "-%Y-%d-%m") + ".txt"))) + (unless (or org-src--auto-save-timer (zerop org-edit-src-auto-save-idle-delay)) + (setq org-src--auto-save-timer + (run-with-idle-timer + org-edit-src-auto-save-idle-delay t + (lambda () + (save-excursion + (let (edit-flag) + (dolist (b (buffer-list)) + (with-current-buffer b + (when (org-src-edit-buffer-p) + (unless edit-flag (setq edit-flag t)) + (when (buffer-modified-p) (org-edit-src-save))))) + (unless edit-flag + (cancel-timer org-src--auto-save-timer) + (setq org-src--auto-save-timer nil))))))))) (defun org-src-mode-configure-edit-buffer () - (when (org-bound-and-true-p org-edit-src-from-org-mode) - (org-add-hook 'kill-buffer-hook - #'(lambda () (delete-overlay org-edit-src-overlay)) nil 'local) - (if (org-bound-and-true-p org-edit-src-allow-write-back-p) + (when (bound-and-true-p org-src--from-org-mode) + (add-hook 'kill-buffer-hook #'org-src--remove-overlay nil 'local) + (if (bound-and-true-p org-src--allow-write-back) (progn (setq buffer-offer-save t) (setq buffer-file-name - (concat (buffer-file-name (marker-buffer org-edit-src-beg-marker)) + (concat (buffer-file-name (marker-buffer org-src--beg-marker)) "[" (buffer-name) "]")) - (if (featurep 'xemacs) - (progn - (make-variable-buffer-local 'write-contents-hooks) ; needed only for 21.4 - (setq write-contents-hooks '(org-edit-src-save))) - (setq write-contents-functions '(org-edit-src-save)))) + (setq-local write-contents-functions '(org-edit-src-save))) (setq buffer-read-only t)))) -(org-add-hook 'org-src-mode-hook 'org-src-mode-configure-edit-buffer) +(add-hook 'org-src-mode-hook #'org-src-mode-configure-edit-buffer) + +;;; Babel related functions + (defun org-src-associate-babel-session (info) "Associate edit buffer with comint session." (interactive) - (let ((session (cdr (assoc :session (nth 2 info))))) + (let ((session (cdr (assq :session (nth 2 info))))) (and session (not (string= session "none")) (org-babel-comint-buffer-livep session) (let ((f (intern (format "org-babel-%s-associate-session" @@ -843,18 +691,22 @@ with \",*\", \",#+\", \",,*\" and \",,#+\"." (and (fboundp f) (funcall f session)))))) (defun org-src-babel-configure-edit-buffer () - (when org-src-babel-info - (org-src-associate-babel-session org-src-babel-info))) + (when org-src--babel-info + (org-src-associate-babel-session org-src--babel-info))) + +(add-hook 'org-src-mode-hook #'org-src-babel-configure-edit-buffer) + + +;;; Public API -(org-add-hook 'org-src-mode-hook 'org-src-babel-configure-edit-buffer) (defmacro org-src-do-at-code-block (&rest body) - "Execute a command from an edit buffer in the Org-mode buffer." - `(let ((beg-marker org-edit-src-beg-marker)) - (if beg-marker - (with-current-buffer (marker-buffer beg-marker) - (goto-char (marker-position beg-marker)) - ,@body)))) -(def-edebug-spec org-src-do-at-code-block (body)) + "Execute BODY from an edit buffer in the Org mode buffer." + (declare (debug (body))) + `(let ((beg-marker org-src--beg-marker)) + (when beg-marker + (with-current-buffer (marker-buffer beg-marker) + (goto-char beg-marker) + ,@body)))) (defun org-src-do-key-sequence-at-code-block (&optional key) "Execute key sequence at code block in the source Org buffer. @@ -878,85 +730,403 @@ Org-babel commands." (if (equal key (kbd "C-g")) (keyboard-quit) (org-edit-src-save) (org-src-do-at-code-block - (call-interactively - (lookup-key org-babel-map key))))) + (call-interactively (lookup-key org-babel-map key))))) -(defcustom org-src-tab-acts-natively nil - "If non-nil, the effect of TAB in a code block is as if it were -issued in the language major mode buffer." - :type 'boolean - :version "24.1" - :group 'org-babel) +(defun org-src-edit-buffer-p (&optional buffer) + "Non-nil when current buffer is a source editing buffer. +If BUFFER is non-nil, test it instead." + (let ((buffer (org-base-buffer (or buffer (current-buffer))))) + (and (buffer-live-p buffer) + (local-variable-p 'org-src--beg-marker buffer) + (local-variable-p 'org-src--end-marker buffer)))) + +(defun org-src-switch-to-buffer (buffer context) + (pcase org-src-window-setup + (`current-window (pop-to-buffer-same-window buffer)) + (`other-window + (switch-to-buffer-other-window buffer)) + (`other-frame + (pcase context + (`exit + (let ((frame (selected-frame))) + (switch-to-buffer-other-frame buffer) + (delete-frame frame))) + (`save + (kill-buffer (current-buffer)) + (pop-to-buffer-same-window buffer)) + (_ (switch-to-buffer-other-frame buffer)))) + (`reorganize-frame + (when (eq context 'edit) (delete-other-windows)) + (org-switch-to-buffer-other-window buffer) + (when (eq context 'exit) (delete-other-windows))) + (`switch-invisibly (set-buffer buffer)) + (_ + (message "Invalid value %s for `org-src-window-setup'" + org-src-window-setup) + (pop-to-buffer-same-window buffer)))) + +(defun org-src-coderef-format (&optional element) + "Return format string for block at point. + +When optional argument ELEMENT is provided, use that block. +Otherwise, assume point is either at a source block, at an +example block. + +If point is in an edit buffer, retrieve format string associated +to the remote source block." + (cond + ((and element (org-element-property :label-fmt element))) + ((org-src-edit-buffer-p) (org-src-do-at-code-block (org-src-coderef-format))) + ((org-element-property :label-fmt (org-element-at-point))) + (t org-coderef-label-format))) + +(defun org-src-coderef-regexp (fmt &optional label) + "Return regexp matching a coderef format string FMT. + +When optional argument LABEL is non-nil, match coderef for that +label only. + +Match group 1 contains the full coderef string with surrounding +white spaces. Match group 2 contains the same string without any +surrounding space. Match group 3 contains the label. + +A coderef format regexp can only match at the end of a line." + (format "\\([ \t]*\\(%s\\)[ \t]*\\)$" + (replace-regexp-in-string + "%s" + (if label (regexp-quote label) "\\([-a-zA-Z0-9_][-a-zA-Z0-9_ ]*\\)") + (regexp-quote fmt) + nil t))) + +(defun org-edit-footnote-reference () + "Edit definition of footnote reference at point." + (interactive) + (let* ((context (org-element-context)) + (label (org-element-property :label context))) + (unless (and (eq (org-element-type context) 'footnote-reference) + (org-src--on-datum-p context)) + (user-error "Not on a footnote reference")) + (unless label (user-error "Cannot edit remotely anonymous footnotes")) + (let* ((definition (org-with-wide-buffer + (org-footnote-goto-definition label) + (backward-char) + (org-element-context))) + (inline? (eq 'footnote-reference (org-element-type definition))) + (contents + (org-with-wide-buffer + (buffer-substring-no-properties + (or (org-element-property :post-affiliated definition) + (org-element-property :begin definition)) + (cond + (inline? (1+ (org-element-property :contents-end definition))) + ((org-element-property :contents-end definition)) + (t (goto-char (org-element-property :post-affiliated definition)) + (line-end-position))))))) + (add-text-properties + 0 + (progn (string-match (if inline? "\\`\\[fn:.*?:" "\\`.*?\\]") contents) + (match-end 0)) + '(read-only "Cannot edit footnote label" front-sticky t rear-nonsticky t) + contents) + (when inline? + (let ((l (length contents))) + (add-text-properties + (1- l) l + '(read-only "Cannot edit past footnote reference" + front-sticky nil rear-nonsticky nil) + contents))) + (org-src--edit-element + definition + (format "*Edit footnote [%s]*" label) + (let ((source (current-buffer))) + (lambda () + (org-mode) + (org-clone-local-variables source))) + (lambda () + (if (not inline?) (delete-region (point) (search-forward "]")) + (delete-region (point) (search-forward ":" nil t 2)) + (delete-region (1- (point-max)) (point-max)) + (when (re-search-forward "\n[ \t]*\n" nil t) + (user-error "Inline definitions cannot contain blank lines")) + ;; If footnote reference belongs to a table, make sure to + ;; remove any newline characters in order to preserve + ;; table's structure. + (when (org-element-lineage definition '(table-cell)) + (while (search-forward "\n" nil t) (replace-match ""))))) + contents + 'remote)) + ;; Report success. + t)) + +(defun org-edit-table.el () + "Edit \"table.el\" table at point. +\\<org-src-mode-map> +A new buffer is created and the table is copied into it. Then +the table is recognized with `table-recognize'. When done +editing, exit with `\\[org-edit-src-exit]'. The edited text will \ +then replace +the area in the Org mode buffer. + +Throw an error when not at such a table." + (interactive) + (let ((element (org-element-at-point))) + (unless (and (eq (org-element-type element) 'table) + (eq (org-element-property :type element) 'table.el) + (org-src--on-datum-p element)) + (user-error "Not in a table.el table")) + (org-src--edit-element + element + (org-src--construct-edit-buffer-name (buffer-name) "Table") + #'text-mode t) + (when (bound-and-true-p flyspell-mode) (flyspell-mode -1)) + (table-recognize) + t)) + +(defun org-edit-latex-environment () + "Edit LaTeX environment at point. +\\<org-src-mode-map> +The LaTeX environment is copied into a new buffer. Major mode is +set to the one associated to \"latex\" in `org-src-lang-modes', +or to `latex-mode' if there is none. + +When done, exit with `\\[org-edit-src-exit]'. The edited text \ +will then replace +the LaTeX environment in the Org mode buffer." + (interactive) + (let ((element (org-element-at-point))) + (unless (and (eq (org-element-type element) 'latex-environment) + (org-src--on-datum-p element)) + (user-error "Not in a LaTeX environment")) + (org-src--edit-element + element + (org-src--construct-edit-buffer-name (buffer-name) "LaTeX environment") + (org-src--get-lang-mode "latex") + t) + t)) + +(defun org-edit-export-block () + "Edit export block at point. +\\<org-src-mode-map> +A new buffer is created and the block is copied into it, and the +buffer is switched into an appropriate major mode. See also +`org-src-lang-modes'. + +When done, exit with `\\[org-edit-src-exit]'. The edited text \ +will then replace +the area in the Org mode buffer. + +Throw an error when not at an export block." + (interactive) + (let ((element (org-element-at-point))) + (unless (and (eq (org-element-type element) 'export-block) + (org-src--on-datum-p element)) + (user-error "Not in an export block")) + (let* ((type (downcase (or (org-element-property :type element) + ;; Missing export-block type. Fallback + ;; to default mode. + "fundamental"))) + (mode (org-src--get-lang-mode type))) + (unless (functionp mode) (error "No such language mode: %s" mode)) + (org-src--edit-element + element + (org-src--construct-edit-buffer-name (buffer-name) type) + mode + (lambda () (org-escape-code-in-region (point-min) (point-max))))) + t)) + +(defun org-edit-src-code (&optional code edit-buffer-name) + "Edit the source or example block at point. +\\<org-src-mode-map> +The code is copied to a separate buffer and the appropriate mode +is turned on. When done, exit with `\\[org-edit-src-exit]'. This \ +will remove the +original code in the Org buffer, and replace it with the edited +version. See `org-src-window-setup' to configure the display of +windows containing the Org buffer and the code buffer. -(defun org-src-native-tab-command-maybe () - "Perform language-specific TAB action. -Alter code block according to what TAB does in the language major mode." - (and org-src-tab-acts-natively - (org-in-src-block-p) - (not (equal this-command 'org-shifttab)) - (let ((org-src-strip-leading-and-trailing-blank-lines nil)) - (org-babel-do-key-sequence-in-edit-buffer (kbd "TAB"))))) +When optional argument CODE is a string, edit it in a dedicated +buffer instead. -(add-hook 'org-tab-first-hook 'org-src-native-tab-command-maybe) +When optional argument EDIT-BUFFER-NAME is non-nil, use it as the +name of the sub-editing buffer." + (interactive) + (let* ((element (org-element-at-point)) + (type (org-element-type element))) + (unless (and (memq type '(example-block src-block)) + (org-src--on-datum-p element)) + (user-error "Not in a source or example block")) + (let* ((lang + (if (eq type 'src-block) (org-element-property :language element) + "example")) + (lang-f (and (eq type 'src-block) (org-src--get-lang-mode lang))) + (babel-info (and (eq type 'src-block) + (org-babel-get-src-block-info 'light))) + deactivate-mark) + (when (and (eq type 'src-block) (not (functionp lang-f))) + (error "No such language mode: %s" lang-f)) + (org-src--edit-element + element + (or edit-buffer-name + (org-src--construct-edit-buffer-name (buffer-name) lang)) + lang-f + (and (null code) + (lambda () (org-escape-code-in-region (point-min) (point-max)))) + (and code (org-unescape-code-in-string code))) + ;; Finalize buffer. + (setq-local org-coderef-label-format + (or (org-element-property :label-fmt element) + org-coderef-label-format)) + (when (eq type 'src-block) + (setq-local org-src--babel-info babel-info) + (let ((edit-prep-func (intern (concat "org-babel-edit-prep:" lang)))) + (when (fboundp edit-prep-func) + (funcall edit-prep-func babel-info)))) + t))) -(defun org-src-font-lock-fontify-block (lang start end) - "Fontify code block. -This function is called by emacs automatic fontification, as long -as `org-src-fontify-natively' is non-nil. For manual -fontification of code blocks see `org-src-fontify-block' and -`org-src-fontify-buffer'" - (let ((lang-mode (org-src-get-lang-mode lang))) - (if (fboundp lang-mode) - (let ((string (buffer-substring-no-properties start end)) - (modified (buffer-modified-p)) - (org-buffer (current-buffer)) pos next) - (remove-text-properties start end '(face nil)) - (with-current-buffer - (get-buffer-create - (concat " org-src-fontification:" (symbol-name lang-mode))) - ;; Make sure that modification hooks are not inhibited in - ;; the org-src-fontification buffer in case we're called - ;; from `jit-lock-function' (Bug#25132). - (let ((inhibit-modification-hooks nil)) - (delete-region (point-min) (point-max)) - (insert string " ")) ;; so there's a final property change - (unless (eq major-mode lang-mode) (funcall lang-mode)) - (org-font-lock-ensure) - (setq pos (point-min)) - (while (setq next (next-single-property-change pos 'face)) - (put-text-property - (+ start (1- pos)) (1- (+ start next)) 'face - (get-text-property pos 'face) org-buffer) - (setq pos next))) - (add-text-properties - start end - '(font-lock-fontified t fontified t font-lock-multiline t)) - (set-buffer-modified-p modified))))) +(defun org-edit-inline-src-code () + "Edit inline source code at point." + (interactive) + (let ((context (org-element-context))) + (unless (and (eq (org-element-type context) 'inline-src-block) + (org-src--on-datum-p context)) + (user-error "Not on inline source code")) + (let* ((lang (org-element-property :language context)) + (lang-f (org-src--get-lang-mode lang)) + (babel-info (org-babel-get-src-block-info 'light)) + deactivate-mark) + (unless (functionp lang-f) (error "No such language mode: %s" lang-f)) + (org-src--edit-element + context + (org-src--construct-edit-buffer-name (buffer-name) lang) + lang-f + (lambda () + ;; Inline src blocks are limited to one line. + (while (re-search-forward "\n[ \t]*" nil t) (replace-match " ")) + ;; Trim contents. + (goto-char (point-min)) + (skip-chars-forward " \t") + (delete-region (point-min) (point)) + (goto-char (point-max)) + (skip-chars-backward " \t") + (delete-region (point) (point-max)))) + ;; Finalize buffer. + (setq-local org-src--babel-info babel-info) + (setq-local org-src--preserve-indentation t) + (let ((edit-prep-func (intern (concat "org-babel-edit-prep:" lang)))) + (when (fboundp edit-prep-func) (funcall edit-prep-func babel-info))) + ;; Return success. + t))) -(defvar org-src-fontify-natively) +(defun org-edit-fixed-width-region () + "Edit the fixed-width ASCII drawing at point. +\\<org-src-mode-map> +This must be a region where each line starts with a colon +followed by a space or a newline character. + +A new buffer is created and the fixed-width region is copied into +it, and the buffer is switched into the major mode defined in +`org-edit-fixed-width-region-mode', which see. + +When done, exit with `\\[org-edit-src-exit]'. The edited text \ +will then replace +the area in the Org mode buffer." + (interactive) + (let ((element (org-element-at-point))) + (unless (and (eq (org-element-type element) 'fixed-width) + (org-src--on-datum-p element)) + (user-error "Not in a fixed-width area")) + (org-src--edit-element + element + (org-src--construct-edit-buffer-name (buffer-name) "Fixed Width") + org-edit-fixed-width-region-mode + (lambda () (while (not (eobp)) (insert ": ") (forward-line)))) + ;; Return success. + t)) -(defun org-src-fontify-block () - "Fontify code block at point." +(defun org-edit-src-abort () + "Abort editing of the src code and return to the Org buffer." (interactive) - (save-excursion - (let ((org-src-fontify-natively t) - (info (org-edit-src-find-region-and-lang))) - (font-lock-fontify-region (nth 0 info) (nth 1 info))))) + (let (org-src--allow-write-back) (org-edit-src-exit))) -(defun org-src-fontify-buffer () - "Fontify all code blocks in the current buffer." +(defun org-edit-src-continue (e) + "Unconditionally return to buffer editing area under point. +Throw an error if there is no such buffer." + (interactive "e") + (mouse-set-point e) + (let ((buf (get-char-property (point) 'edit-buffer))) + (if buf (org-src-switch-to-buffer buf 'continue) + (user-error "No sub-editing buffer for area at point")))) + +(defun org-edit-src-save () + "Save parent buffer with current state source-code buffer." (interactive) - (org-babel-map-src-blocks nil - (org-src-fontify-block))) + (unless (org-src-edit-buffer-p) (user-error "Not in a sub-editing buffer")) + (set-buffer-modified-p nil) + (let ((edited-code (org-src--contents-for-write-back)) + (beg org-src--beg-marker) + (end org-src--end-marker) + (overlay org-src--overlay)) + (with-current-buffer (org-src--source-buffer) + (undo-boundary) + (goto-char beg) + ;; Temporarily disable read-only features of OVERLAY in order to + ;; insert new contents. + (delete-overlay overlay) + (delete-region beg end) + (let ((expecting-bol (bolp))) + (insert edited-code) + (when (and expecting-bol (not (bolp))) (insert "\n"))) + (save-buffer) + (move-overlay overlay beg (point)))) + ;; `write-contents-functions' requires the function to return + ;; a non-nil value so that other functions are not called. + t) + +(defun org-edit-src-exit () + "Kill current sub-editing buffer and return to source buffer." + (interactive) + (unless (org-src-edit-buffer-p) (error "Not in a sub-editing buffer")) + (let* ((beg org-src--beg-marker) + (end org-src--end-marker) + (write-back org-src--allow-write-back) + (remote org-src--remote) + (coordinates (and (not remote) + (org-src--coordinates (point) 1 (point-max)))) + (code (and write-back (org-src--contents-for-write-back)))) + (set-buffer-modified-p nil) + ;; Switch to source buffer. Kill sub-editing buffer. + (let ((edit-buffer (current-buffer)) + (source-buffer (marker-buffer beg))) + (unless source-buffer (error "Source buffer disappeared. Aborting")) + (org-src-switch-to-buffer source-buffer 'exit) + (kill-buffer edit-buffer)) + ;; Insert modified code. Ensure it ends with a newline character. + (org-with-wide-buffer + (when (and write-back (not (equal (buffer-substring beg end) code))) + (undo-boundary) + (goto-char beg) + (delete-region beg end) + (let ((expecting-bol (bolp))) + (insert code) + (when (and expecting-bol (not (bolp))) (insert "\n"))))) + ;; If we are to return to source buffer, put point at an + ;; appropriate location. In particular, if block is hidden, move + ;; to the beginning of the block opening line. + (unless remote + (goto-char beg) + (cond + ;; Block is hidden; move at start of block. + ((cl-some (lambda (o) (eq (overlay-get o 'invisible) 'org-hide-block)) + (overlays-at (point))) + (beginning-of-line 0)) + (write-back (org-src--goto-coordinates coordinates beg end)))) + ;; Clean up left-over markers and restore window configuration. + (set-marker beg nil) + (set-marker end nil) + (when org-src--saved-temp-window-config + (set-window-configuration org-src--saved-temp-window-config) + (setq org-src--saved-temp-window-config nil)))) -(defun org-src-get-lang-mode (lang) - "Return major mode that should be used for LANG. -LANG is a string, and the returned major mode is a symbol." - (intern - (concat - (let ((l (or (cdr (assoc lang org-src-lang-modes)) lang))) - (if (symbolp l) (symbol-name l) l)) - "-mode"))) (provide 'org-src) diff --git a/lisp/org/org-table.el b/lisp/org/org-table.el index 0c813d03a17..6ebd6da9d0a 100644 --- a/lisp/org/org-table.el +++ b/lisp/org/org-table.el @@ -1,4 +1,4 @@ -;;; org-table.el --- The table editor for Org-mode +;;; org-table.el --- The Table Editor for Org -*- lexical-binding: t; -*- ;; Copyright (C) 2004-2017 Free Software Foundation, Inc. @@ -19,32 +19,59 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Commentary: -;; This file contains the table editor and spreadsheet for Org-mode. +;; This file contains the table editor and spreadsheet for Org mode. ;; Watch out: Here we are talking about two different kind of tables. -;; Most of the code is for the tables created with the Org-mode table editor. +;; Most of the code is for the tables created with the Org mode table editor. ;; Sometimes, we talk about tables created and edited with the table.el ;; Emacs package. We call the former org-type tables, and the latter ;; table.el-type tables. ;;; Code: -(eval-when-compile - (require 'cl)) +(require 'cl-lib) (require 'org) -(declare-function org-export-string-as "ox" - (string backend &optional body-only ext-plist)) -(declare-function aa2u "ext:ascii-art-to-unicode" ()) -(defvar orgtbl-mode) ; defined below -(defvar orgtbl-mode-menu) ; defined when orgtbl mode get initialized +(declare-function org-element-at-point "org-element" ()) +(declare-function org-element-contents "org-element" (element)) +(declare-function org-element-extract-element "org-element" (element)) +(declare-function org-element-interpret-data "org-element" (data)) +(declare-function org-element-lineage "org-element" + (blob &optional types with-self)) +(declare-function org-element-map "org-element" + (data types fun + &optional info first-match no-recursion with-affiliated)) +(declare-function org-element-parse-buffer "org-element" + (&optional granularity visible-only)) +(declare-function org-element-property "org-element" (property element)) +(declare-function org-element-type "org-element" (element)) + +(declare-function org-export-create-backend "ox" (&rest rest) t) +(declare-function org-export-data-with-backend "ox" (data backend info)) +(declare-function org-export-filter-apply-functions "ox" + (filters value info)) +(declare-function org-export-first-sibling-p "ox" (blob info)) +(declare-function org-export-get-backend "ox" (name)) +(declare-function org-export-get-environment "ox" + (&optional backend subtreep ext-plist)) +(declare-function org-export-install-filters "ox" (info)) +(declare-function org-export-table-has-special-column-p "ox" (table)) +(declare-function org-export-table-row-is-special-p "ox" (table-row info)) + +(declare-function calc-eval "calc" (str &optional separator &rest args)) + (defvar constants-unit-system) +(defvar org-element-use-cache) +(defvar org-export-filters-alist) (defvar org-table-follow-field-mode) +(defvar orgtbl-mode) ; defined below +(defvar orgtbl-mode-menu) ; defined when orgtbl mode get initialized +(defvar sort-fold-case) (defvar orgtbl-after-send-table-hook nil "Hook for functions attaching to `C-c C-c', if the table is sent. @@ -52,19 +79,19 @@ This can be used to add additional functionality after the table is sent to the receiver position, otherwise, if table is not sent, the functions are not run.") -(defvar org-table-TBLFM-begin-regexp "|\n[ \t]*#\\+TBLFM: ") +(defvar org-table-TBLFM-begin-regexp "^[ \t]*|.*\n[ \t]*#\\+TBLFM: ") -(defcustom orgtbl-optimized (eq org-enable-table-editor 'optimized) +(defcustom orgtbl-optimized t "Non-nil means use the optimized table editor version for `orgtbl-mode'. + In the optimized version, the table editor takes over all simple keys that normally just insert a character. In tables, the characters are inserted in a way to minimize disturbing the table structure (i.e. in overwrite mode for empty fields). Outside tables, the correct binding of the keys is restored. -The default for this option is t if the optimized version is also used in -Org-mode. See the variable `org-enable-table-editor' for details. Changing -this variable requires a restart of Emacs to become effective." +Changing this variable requires a restart of Emacs to become +effective." :group 'org-table :type 'boolean) @@ -118,7 +145,7 @@ table, obtained by prompting the user." (string :tag "Format")))) (defgroup org-table-settings nil - "Settings for tables in Org-mode." + "Settings for tables in Org mode." :tag "Org Table Settings" :group 'org-table) @@ -167,13 +194,13 @@ alignment to the right border applies." :type 'number) (defgroup org-table-editing nil - "Behavior of tables during editing in Org-mode." + "Behavior of tables during editing in Org mode." :tag "Org Table Editing" :group 'org-table) (defcustom org-table-automatic-realign t "Non-nil means automatically re-align table when pressing TAB or RETURN. -When nil, aligning is only done with \\[org-table-align], or after column +When nil, aligning is only done with `\\[org-table-align]', or after column removal/insertion." :group 'org-table-editing :type 'boolean) @@ -181,8 +208,7 @@ removal/insertion." (defcustom org-table-auto-blank-field t "Non-nil means automatically blank table field when starting to type into it. This only happens when typing immediately after a field motion -command (TAB, S-TAB or RET). -Only relevant when `org-enable-table-editor' is equal to `optimized'." +command (TAB, S-TAB or RET)." :group 'org-table-editing :type 'boolean) @@ -219,12 +245,12 @@ this line." :type 'boolean) (defgroup org-table-calculation nil - "Options concerning tables in Org-mode." + "Options concerning tables in Org mode." :tag "Org Table Calculation" :group 'org-table) (defcustom org-table-use-standard-references 'from - "Should org-mode work with table references like B3 instead of @3$2? + "Non-nil means using table references like B3 instead of @3$2. Possible values are: nil never use them from accept as input, do not present for editing @@ -236,9 +262,15 @@ t accept as input and present for editing" (const :tag "Convert user input, don't offer during editing" from))) (defcustom org-table-copy-increment t - "Non-nil means increment when copying current field with \\[org-table-copy-down]." + "Non-nil means increment when copying current field with \ +`\\[org-table-copy-down]'." :group 'org-table-calculation - :type 'boolean) + :version "26.1" + :package-version '(Org . "8.3") + :type '(choice + (const :tag "Use the difference between the current and the above fields" t) + (integer :tag "Use a number" 1) + (const :tag "Don't increment the value when copying a field" nil))) (defcustom org-calc-default-modes '(calc-internal-prec 12 @@ -251,23 +283,35 @@ t accept as input and present for editing" ) "List with Calc mode settings for use in `calc-eval' for table formulas. The list must contain alternating symbols (Calc modes variables and values). -Don't remove any of the default settings, just change the values. Org-mode +Don't remove any of the default settings, just change the values. Org mode relies on the variables to be present in the list." :group 'org-table-calculation :type 'plist) (defcustom org-table-duration-custom-format 'hours "Format for the output of calc computations like $1+$2;t. -The default value is 'hours, and will output the results as a -number of hours. Other allowed values are 'seconds, 'minutes and -'days, and the output will be a fraction of seconds, minutes or -days." +The default value is `hours', and will output the results as a +number of hours. Other allowed values are `seconds', `minutes' and +`days', and the output will be a fraction of seconds, minutes or +days. `hh:mm' selects to use hours and minutes, ignoring seconds. +The `U' flag in a table formula will select this specific format for +a single formula." :group 'org-table-calculation :version "24.1" :type '(choice (symbol :tag "Seconds" 'seconds) (symbol :tag "Minutes" 'minutes) (symbol :tag "Hours " 'hours) - (symbol :tag "Days " 'days))) + (symbol :tag "Days " 'days) + (symbol :tag "HH:MM " 'hh:mm))) + +(defcustom org-table-duration-hour-zero-padding t + "Non-nil means hours in table duration computations should be zero-padded. +So this is about 08:32:34 versus 8:33:34." + :group 'org-table-calculation + :version "26.1" + :package-version '(Org . "9.1") + :type 'boolean + :safe #'booleanp) (defcustom org-table-formula-field-format "%s" "Format for fields which contain the result of a formula. @@ -285,7 +329,7 @@ which should be evaluated as described in the manual and in the documentation string of the command `org-table-eval-formula'. This feature requires the Emacs calc package. When this variable is nil, formula calculation is only available through -the command \\[org-table-eval-formula]." +the command `\\[org-table-eval-formula]'." :group 'org-table-calculation :type 'boolean) @@ -317,15 +361,12 @@ Constants can also be defined on a per-file basis using a line like (defcustom org-table-allow-automatic-line-recalculation t "Non-nil means lines marked with |#| or |*| will be recomputed automatically. -Automatically means when TAB or RET or C-c C-c are pressed in the line." +\\<org-mode-map>\ +Automatically means when `TAB' or `RET' or `\\[org-ctrl-c-ctrl-c]' \ +are pressed in the line." :group 'org-table-calculation :type 'boolean) -(defcustom org-table-error-on-row-ref-crossing-hline t - "OBSOLETE VARIABLE, please see `org-table-relative-ref-may-cross-hline'." - :group 'org-table - :type 'boolean) - (defcustom org-table-relative-ref-may-cross-hline t "Non-nil means relative formula references may cross hlines. Here are the allowed values: @@ -345,8 +386,20 @@ portability of tables." (const :tag "Stick to hline" nil) (const :tag "Error on attempt to cross" error))) +(defcustom org-table-formula-create-columns nil + "Non-nil means that evaluation of a field formula can add new +columns if an out-of-bounds field is being set." + :group 'org-table-calculation + :version "26.1" + :package-version '(Org . "8.3") + :type '(choice + (const :tag "Setting an out-of-bounds field generates an error (default)" nil) + (const :tag "Setting an out-of-bounds field silently adds columns as needed" t) + (const :tag "Setting an out-of-bounds field adds columns as needed, but issues a warning message" warn) + (const :tag "When setting an out-of-bounds field, the user is prompted" prompt))) + (defgroup org-table-import-export nil - "Options concerning table import and export in Org-mode." + "Options concerning table import and export in Org mode." :tag "Org Table Import Export" :group 'org-table) @@ -359,38 +412,73 @@ available parameters." :group 'org-table-import-export :type 'string) +(defcustom org-table-convert-region-max-lines 999 + "Max lines that `org-table-convert-region' will attempt to process. + +The function can be slow on larger regions; this safety feature +prevents it from hanging emacs." + :group 'org-table-import-export + :type 'integer + :version "26.1" + :package-version '(Org . "8.3")) + (defconst org-table-auto-recalculate-regexp "^[ \t]*| *# *\\(|\\|$\\)" - "Detects a table line marked for automatic recalculation.") + "Regexp matching a line marked for automatic recalculation.") + (defconst org-table-recalculate-regexp "^[ \t]*| *[#*] *\\(|\\|$\\)" - "Detects a table line marked for automatic recalculation.") + "Regexp matching a line marked for recalculation.") + (defconst org-table-calculate-mark-regexp "^[ \t]*| *[!$^_#*] *\\(|\\|$\\)" - "Detects a table line marked for automatic recalculation.") + "Regexp matching a line marked for calculation.") + (defconst org-table-border-regexp "^[ \t]*[^| \t]" - "Searching from within a table (any type) this finds the first line outside the table.") + "Regexp matching any line outside an Org table.") + (defvar org-table-last-highlighted-reference nil) + (defvar org-table-formula-history nil) (defvar org-table-column-names nil - "Alist with column names, derived from the `!' line.") + "Alist with column names, derived from the `!' line. +This variable is initialized with `org-table-analyze'.") + (defvar org-table-column-name-regexp nil - "Regular expression matching the current column names.") + "Regular expression matching the current column names. +This variable is initialized with `org-table-analyze'.") + (defvar org-table-local-parameters nil - "Alist with parameter names, derived from the `$' line.") + "Alist with parameter names, derived from the `$' line. +This variable is initialized with `org-table-analyze'.") + (defvar org-table-named-field-locations nil - "Alist with locations of named fields.") + "Alist with locations of named fields. +Associations follow the pattern (NAME LINE COLUMN) where + NAME is the name of the field as a string, + LINE is the number of lines from the beginning of the table, + COLUMN is the column of the field, as an integer. +This variable is initialized with `org-table-analyze'.") (defvar org-table-current-line-types nil - "Table row types, non-nil only for the duration of a command.") -(defvar org-table-current-begin-line nil - "Table begin line, non-nil only for the duration of a command.") + "Table row types in current table. +This variable is initialized with `org-table-analyze'.") + (defvar org-table-current-begin-pos nil - "Table begin position, non-nil only for the duration of a command.") + "Current table begin position, as a marker. +This variable is initialized with `org-table-analyze'.") + (defvar org-table-current-ncol nil - "Number of columns in table, non-nil only for the duration of a command.") + "Number of columns in current table. +This variable is initialized with `org-table-analyze'.") + (defvar org-table-dlines nil - "Vector of data line line numbers in the current table.") + "Vector of data line line numbers in the current table. +Line numbers are counted from the beginning of the table. This +variable is initialized with `org-table-analyze'.") + (defvar org-table-hlines nil - "Vector of hline line numbers in the current table.") + "Vector of hline line numbers in the current table. +Line numbers are counted from the beginning of the table. This +variable is initialized with `org-table-analyze'.") (defconst org-table-range-regexp "@\\([-+]?I*[-+]?[0-9]*\\)?\\(\\$[-+]?[0-9]+\\)?\\(\\.\\.@?\\([-+]?I*[-+]?[0-9]*\\)?\\(\\$[-+]?[0-9]+\\)?\\)?" @@ -404,85 +492,33 @@ available parameters." "\\(" "@?[-0-9I$&]+" "\\|" "[a-zA-Z]\\{1,2\\}\\([0-9]+\\|&\\)" "\\|" "\\$[a-zA-Z0-9]+" "\\)") "Match a range for reference display.") -(defun org-table-colgroup-line-p (line) - "Is this a table line colgroup information?" - (save-match-data - (and (string-match "[<>]\\|&[lg]t;" line) - (string-match "\\`[ \t]*|[ \t]*/[ \t]*\\(|[ \t<>0-9|lgt&;]+\\)\\'" - line) - (not (delq - nil - (mapcar - (lambda (s) - (not (member s '("" "<" ">" "<>" "<" ">" "<>")))) - (org-split-string (match-string 1 line) "[ \t]*|[ \t]*"))))))) - -(defun org-table-cookie-line-p (line) - "Is this a table line with only alignment/width cookies?" - (save-match-data - (and (string-match "[<>]\\|&[lg]t;" line) - (or (string-match - "\\`[ \t]*|[ \t]*/[ \t]*\\(|[ \t<>0-9|lrcgt&;]+\\)\\'" line) - (string-match "\\(\\`[ \t<>lrc0-9|gt&;]+\\'\\)" line)) - (not (delq nil (mapcar - (lambda (s) - (not (or (equal s "") - (string-match - "\\`<\\([lrc]?[0-9]+\\|[lrc]\\)>\\'" s) - (string-match - "\\`<\\([lrc]?[0-9]+\\|[lrc]\\)>\\'" - s)))) - (org-split-string (match-string 1 line) - "[ \t]*|[ \t]*"))))))) - -(defvar org-table-clean-did-remove-column nil) ; dynamically scoped -(defun org-table-clean-before-export (lines &optional maybe-quoted) - "Check if the table has a marking column. -If yes remove the column and the special lines." - (let ((special (if maybe-quoted - "^[ \t]*| *\\\\?[#!$*_^/ ] *|" - "^[ \t]*| *[#!$*_^/ ] *|")) - (ignore (if maybe-quoted - "^[ \t]*| *\\\\?[!$_^/] *|" - "^[ \t]*| *[!$_^/] *|"))) - (setq org-table-clean-did-remove-column - (not (memq nil - (mapcar - (lambda (line) - (or (string-match org-table-hline-regexp line) - (string-match special line))) - lines)))) - (delq nil - (mapcar - (lambda (line) - (cond - ((or (org-table-colgroup-line-p line) ;; colgroup info - (org-table-cookie-line-p line) ;; formatting cookies - (and org-table-clean-did-remove-column - (string-match ignore line))) ;; non-exportable data - nil) - ((and org-table-clean-did-remove-column - (or (string-match "^\\([ \t]*\\)|-+\\+" line) - (string-match "^\\([ \t]*\\)|[^|]*|" line))) - ;; remove the first column - (replace-match "\\1|" t nil line)) - (t line))) - lines)))) - (defconst org-table-translate-regexp (concat "\\(" "@[-0-9I$]+" "\\|" "[a-zA-Z]\\{1,2\\}\\([0-9]+\\|&\\)" "\\)") "Match a reference that needs translation, for reference display.") +(defmacro org-table-save-field (&rest body) + "Save current field; execute BODY; restore field. +Field is restored even in case of abnormal exit." + (declare (debug (body))) + (org-with-gensyms (line column) + `(let ((,line (copy-marker (line-beginning-position))) + (,column (org-table-current-column))) + (unwind-protect + (progn ,@body) + (goto-char ,line) + (org-table-goto-column ,column) + (set-marker ,line nil))))) + ;;;###autoload (defun org-table-create-with-table.el () "Use the table.el package to insert a new table. -If there is already a table at point, convert between Org-mode tables +If there is already a table at point, convert between Org tables and table.el tables." (interactive) (require 'table) (cond ((org-at-table.el-p) - (if (y-or-n-p "Convert table to Org-mode table? ") + (if (y-or-n-p "Convert table to Org table? ") (org-table-convert))) ((org-at-table-p) (when (y-or-n-p "Convert table to table.el table? ") @@ -526,7 +562,7 @@ SIZE is a string Columns x Rows like for example \"3x2\"." (beginning-of-line 1) (newline)) ;; (mapcar (lambda (x) (insert line)) (make-list rows t)) - (dotimes (i rows) (insert line)) + (dotimes (_ rows) (insert line)) (goto-char pos) (if (> rows 1) ;; Insert a hline after the first row. @@ -539,15 +575,18 @@ SIZE is a string Columns x Rows like for example \"3x2\"." ;;;###autoload (defun org-table-convert-region (beg0 end0 &optional separator) "Convert region to a table. + The region goes from BEG0 to END0, but these borders will be moved slightly, to make sure a beginning of line in the first line is included. SEPARATOR specifies the field separator in the lines. It can have the following values: -(4) Use the comma as a field separator -(16) Use a TAB as field separator -integer When a number, use that many spaces as field separator +(4) Use the comma as a field separator +(16) Use a TAB as field separator +(64) Prompt for a regular expression as field separator +integer When a number, use that many spaces, or a TAB, as field separator +regexp When a regular expression, use it to match the separator nil When nil, the command tries to be smart and figure out the separator in the following way: - when each line contains a TAB, assume TAB-separated material @@ -557,45 +596,52 @@ nil When nil, the command tries to be smart and figure out the (let* ((beg (min beg0 end0)) (end (max beg0 end0)) re) - (goto-char beg) - (beginning-of-line 1) - (setq beg (point-marker)) - (goto-char end) - (if (bolp) (backward-char 1) (end-of-line 1)) - (setq end (point-marker)) - ;; Get the right field separator - (unless separator + (if (> (count-lines beg end) org-table-convert-region-max-lines) + (user-error "Region is longer than `org-table-convert-region-max-lines' (%s) lines; not converting" + org-table-convert-region-max-lines) + (if (equal separator '(64)) + (setq separator (read-regexp "Regexp for field separator"))) + (goto-char beg) + (beginning-of-line 1) + (setq beg (point-marker)) + (goto-char end) + (if (bolp) (backward-char 1) (end-of-line 1)) + (setq end (point-marker)) + ;; Get the right field separator + (unless separator + (goto-char beg) + (setq separator + (cond + ((not (re-search-forward "^[^\n\t]+$" end t)) '(16)) + ((not (re-search-forward "^[^\n,]+$" end t)) '(4)) + (t 1)))) (goto-char beg) - (setq separator + (if (equal separator '(4)) + (while (< (point) end) + ;; parse the csv stuff (cond - ((not (re-search-forward "^[^\n\t]+$" end t)) '(16)) - ((not (re-search-forward "^[^\n,]+$" end t)) '(4)) - (t 1)))) - (goto-char beg) - (if (equal separator '(4)) - (while (< (point) end) - ;; parse the csv stuff - (cond - ((looking-at "^") (insert "| ")) - ((looking-at "[ \t]*$") (replace-match " |") (beginning-of-line 2)) - ((looking-at "[ \t]*\"\\([^\"\n]*\\)\"") - (replace-match "\\1") - (if (looking-at "\"") (insert "\""))) - ((looking-at "[^,\n]+") (goto-char (match-end 0))) - ((looking-at "[ \t]*,") (replace-match " | ")) - (t (beginning-of-line 2)))) - (setq re (cond - ((equal separator '(4)) "^\\|\"?[ \t]*,[ \t]*\"?") - ((equal separator '(16)) "^\\|\t") - ((integerp separator) - (if (< separator 1) - (user-error "Number of spaces in separator must be >= 1") - (format "^ *\\| *\t *\\| \\{%d,\\}" separator))) - (t (error "This should not happen")))) - (while (re-search-forward re end t) - (replace-match "| " t t))) - (goto-char beg) - (org-table-align))) + ((looking-at "^") (insert "| ")) + ((looking-at "[ \t]*$") (replace-match " |") (beginning-of-line 2)) + ((looking-at "[ \t]*\"\\([^\"\n]*\\)\"") + (replace-match "\\1") + (if (looking-at "\"") (insert "\""))) + ((looking-at "[^,\n]+") (goto-char (match-end 0))) + ((looking-at "[ \t]*,") (replace-match " | ")) + (t (beginning-of-line 2)))) + (setq re (cond + ((equal separator '(4)) "^\\|\"?[ \t]*,[ \t]*\"?") + ((equal separator '(16)) "^\\|\t") + ((integerp separator) + (if (< separator 1) + (user-error "Number of spaces in separator must be >= 1") + (format "^ *\\| *\t *\\| \\{%d,\\}" separator))) + ((stringp separator) + (format "^ *\\|%s" separator)) + (t (error "This should not happen")))) + (while (re-search-forward re end t) + (replace-match "| " t t))) + (goto-char beg) + (org-table-align)))) ;;;###autoload (defun org-table-import (file arg) @@ -611,8 +657,6 @@ are found, lines will be split on whitespace into fields." (org-table-convert-region beg (+ (point) (- (point-max) pm)) arg))) -(defvar org-table-last-alignment) -(defvar org-table-last-column-widths) ;;;###autoload (defun org-table-export (&optional file format) "Export table to a file, with configurable format. @@ -630,77 +674,61 @@ extension of the given file name, and finally on the variable `org-table-export-default-format'." (interactive) (unless (org-at-table-p) (user-error "No table at point")) - (org-table-align) ;; make sure we have everything we need - (let* ((beg (org-table-begin)) - (end (org-table-end)) - (txt (buffer-substring-no-properties beg end)) - (file (or file (org-entry-get beg "TABLE_EXPORT_FILE" t))) - (formats '("orgtbl-to-tsv" "orgtbl-to-csv" - "orgtbl-to-latex" "orgtbl-to-html" - "orgtbl-to-generic" "orgtbl-to-texinfo" - "orgtbl-to-orgtbl")) - (format (or format - (org-entry-get beg "TABLE_EXPORT_FORMAT" t))) - buf deffmt-readable fileext) + (org-table-align) ; Make sure we have everything we need. + (let ((file (or file (org-entry-get (point) "TABLE_EXPORT_FILE" t)))) (unless file (setq file (read-file-name "Export table to: ")) (unless (or (not (file-exists-p file)) (y-or-n-p (format "Overwrite file %s? " file))) (user-error "File not written"))) - (if (file-directory-p file) - (user-error "This is a directory path, not a file")) - (if (and (buffer-file-name) - (equal (file-truename file) - (file-truename (buffer-file-name)))) - (user-error "Please specify a file name that is different from current")) - (setq fileext (concat (file-name-extension file) "$")) - (unless format - (setq deffmt-readable - (or (car (delq nil (mapcar (lambda(f) (if (string-match fileext f) f)) formats))) - org-table-export-default-format)) - (while (string-match "\t" deffmt-readable) - (setq deffmt-readable (replace-match "\\t" t t deffmt-readable))) - (while (string-match "\n" deffmt-readable) - (setq deffmt-readable (replace-match "\\n" t t deffmt-readable))) - (setq format (org-completing-read "Format: " formats nil nil deffmt-readable))) - (if (string-match "\\([^ \t\r\n]+\\)\\( +.*\\)?" format) - (let* ((transform (intern (match-string 1 format))) - (params (if (match-end 2) - (read (concat "(" (match-string 2 format) ")")))) - (skip (plist-get params :skip)) - (skipcols (plist-get params :skipcols)) - (lines (nthcdr (or skip 0) (org-split-string txt "[ \t]*\n[ \t]*"))) - (lines (org-table-clean-before-export lines)) - (i0 (if org-table-clean-did-remove-column 2 1)) - (table (mapcar - (lambda (x) - (if (string-match org-table-hline-regexp x) - 'hline - (org-remove-by-index - (org-split-string (org-trim x) "\\s-*|\\s-*") - skipcols i0))) - lines)) - (fun (if (= i0 2) 'cdr 'identity)) - (org-table-last-alignment - (org-remove-by-index (funcall fun org-table-last-alignment) - skipcols i0)) - (org-table-last-column-widths - (org-remove-by-index (funcall fun org-table-last-column-widths) - skipcols i0))) - - (unless (fboundp transform) - (user-error "No such transformation function %s" transform)) - (setq txt (funcall transform table params)) - - (with-current-buffer (find-file-noselect file) - (setq buf (current-buffer)) - (erase-buffer) - (fundamental-mode) - (insert txt "\n") - (save-buffer)) - (kill-buffer buf) - (message "Export done.")) - (user-error "TABLE_EXPORT_FORMAT invalid")))) + (when (file-directory-p file) + (user-error "This is a directory path, not a file")) + (when (and (buffer-file-name (buffer-base-buffer)) + (file-equal-p + (file-truename file) + (file-truename (buffer-file-name (buffer-base-buffer))))) + (user-error "Please specify a file name that is different from current")) + (let ((fileext (concat (file-name-extension file) "$")) + (format (or format (org-entry-get (point) "TABLE_EXPORT_FORMAT" t)))) + (unless format + (let* ((formats '("orgtbl-to-tsv" "orgtbl-to-csv" "orgtbl-to-latex" + "orgtbl-to-html" "orgtbl-to-generic" + "orgtbl-to-texinfo" "orgtbl-to-orgtbl" + "orgtbl-to-unicode")) + (deffmt-readable + (replace-regexp-in-string + "\t" "\\t" + (replace-regexp-in-string + "\n" "\\n" + (or (car (delq nil + (mapcar + (lambda (f) + (and (string-match-p fileext f) f)) + formats))) + org-table-export-default-format) + t t) t t))) + (setq format + (org-completing-read + "Format: " formats nil nil deffmt-readable)))) + (if (string-match "\\([^ \t\r\n]+\\)\\( +.*\\)?" format) + (let ((transform (intern (match-string 1 format))) + (params (and (match-end 2) + (read (concat "(" (match-string 2 format) ")")))) + (table (org-table-to-lisp + (buffer-substring-no-properties + (org-table-begin) (org-table-end))))) + (unless (fboundp transform) + (user-error "No such transformation function %s" transform)) + (let (buf) + (with-current-buffer (find-file-noselect file) + (setq buf (current-buffer)) + (erase-buffer) + (fundamental-mode) + (insert (funcall transform table params) "\n") + (save-buffer)) + (kill-buffer buf)) + (message "Export done.")) + (user-error "TABLE_EXPORT_FORMAT invalid"))))) (defvar org-table-aligned-begin-marker (make-marker) "Marker at the beginning of the table last aligned. @@ -714,13 +742,11 @@ This is being used to correctly align a single field after TAB or RET.") (defvar org-table-last-column-widths nil "List of max width of fields in each column. This is being used to correctly align a single field after TAB or RET.") -(defvar org-table-formula-debug nil +(defvar-local org-table-formula-debug nil "Non-nil means debug table formulas. When nil, simply write \"#ERROR\" in corrupted fields.") -(make-variable-buffer-local 'org-table-formula-debug) -(defvar org-table-overlay-coordinates nil +(defvar-local org-table-overlay-coordinates nil "Overlay coordinates after each align of a table.") -(make-variable-buffer-local 'org-table-overlay-coordinates) (defvar org-last-recalc-line nil) (defvar org-table-do-narrow t) ; for dynamic scoping @@ -731,216 +757,198 @@ When nil, simply write \"#ERROR\" in corrupted fields.") (defun org-table-align () "Align the table at point by aligning all vertical bars." (interactive) - (let* ( - ;; Limits of table - (beg (org-table-begin)) - (end (org-table-end)) - ;; Current cursor position - (linepos (org-current-line)) - (colpos (org-table-current-column)) - (winstart (window-start)) - (winstartline (org-current-line (min winstart (1- (point-max))))) - lines (new "") lengths l typenums ty fields maxfields i - column - (indent "") cnt frac - rfmt hfmt - (spaces '(1 . 1)) - (sp1 (car spaces)) - (sp2 (cdr spaces)) - (rfmt1 (concat - (make-string sp2 ?\ ) "%%%s%ds" (make-string sp1 ?\ ) "|")) - (hfmt1 (concat - (make-string sp2 ?-) "%s" (make-string sp1 ?-) "+")) - emptystrings links dates emph raise narrow - falign falign1 fmax f1 len c e space) - (untabify beg end) - (remove-text-properties beg end '(org-cwidth t org-dwidth t display t)) - ;; Check if we have links or dates - (goto-char beg) - (setq links (re-search-forward org-bracket-link-regexp end t)) - (goto-char beg) - (setq emph (and org-hide-emphasis-markers - (re-search-forward org-emph-re end t))) - (goto-char beg) - (setq raise (and org-use-sub-superscripts - (re-search-forward org-match-substring-regexp end t))) - (goto-char beg) - (setq dates (and org-display-custom-times - (re-search-forward org-ts-regexp-both end t))) - ;; Make sure the link properties are right - (when links (goto-char beg) (while (org-activate-bracket-links end))) - ;; Make sure the date properties are right - (when dates (goto-char beg) (while (org-activate-dates end))) - (when emph (goto-char beg) (while (org-do-emphasis-faces end))) - (when raise (goto-char beg) (while (org-raise-scripts end))) - - ;; Check if we are narrowing any columns - (goto-char beg) - (setq narrow (and org-table-do-narrow - org-format-transports-properties-p - (re-search-forward "<[lrc]?[0-9]+>" end t))) - (goto-char beg) - (setq falign (re-search-forward "<[lrc][0-9]*>" end t)) - (goto-char beg) - ;; Get the rows - (setq lines (org-split-string - (buffer-substring beg end) "\n")) - ;; Store the indentation of the first line - (if (string-match "^ *" (car lines)) - (setq indent (make-string (- (match-end 0) (match-beginning 0)) ?\ ))) - ;; Mark the hlines by setting the corresponding element to nil - ;; At the same time, we remove trailing space. - (setq lines (mapcar (lambda (l) - (if (string-match "^ *|-" l) - nil - (if (string-match "[ \t]+$" l) - (substring l 0 (match-beginning 0)) - l))) - lines)) - ;; Get the data fields by splitting the lines. - (setq fields (mapcar - (lambda (l) - (org-split-string l " *| *")) - (delq nil (copy-sequence lines)))) - ;; How many fields in the longest line? - (condition-case nil - (setq maxfields (apply 'max (mapcar 'length fields))) - (error - (kill-region beg end) - (org-table-create org-table-default-size) - (user-error "Empty table - created default table"))) - ;; A list of empty strings to fill any short rows on output - (setq emptystrings (make-list maxfields "")) - ;; Check for special formatting. - (setq i -1) - (while (< (setq i (1+ i)) maxfields) ;; Loop over all columns - (setq column (mapcar (lambda (x) (or (nth i x) "")) fields)) - ;; Check if there is an explicit width specified - (setq fmax nil) - (when (or narrow falign) - (setq c column fmax nil falign1 nil) - (while c - (setq e (pop c)) - (when (and (stringp e) (string-match "^<\\([lrc]\\)?\\([0-9]+\\)?>$" e)) - (if (match-end 1) (setq falign1 (match-string 1 e))) - (if (and org-table-do-narrow (match-end 2)) - (setq fmax (string-to-number (match-string 2 e)) c nil)))) - ;; Find fields that are wider than fmax, and shorten them - (when fmax - (loop for xx in column do - (when (and (stringp xx) - (> (org-string-width xx) fmax)) - (org-add-props xx nil - 'help-echo - (concat "Clipped table field, use C-c ` to edit. Full value is:\n" (org-no-properties (copy-sequence xx)))) - (setq f1 (min fmax (or (string-match org-bracket-link-regexp xx) fmax))) - (unless (> f1 1) - (user-error "Cannot narrow field starting with wide link \"%s\"" - (match-string 0 xx))) - (add-text-properties f1 (length xx) (list 'org-cwidth t) xx) - (add-text-properties (- f1 2) f1 - (list 'display org-narrow-column-arrow) - xx))))) - ;; Get the maximum width for each column - (push (apply 'max (or fmax 1) 1 (mapcar 'org-string-width column)) - lengths) - ;; Get the fraction of numbers, to decide about alignment of the column - (if falign1 - (push (equal (downcase falign1) "r") typenums) - (setq cnt 0 frac 0.0) - (loop for x in column do - (if (equal x "") - nil - (setq frac ( / (+ (* frac cnt) - (if (string-match org-table-number-regexp x) 1 0)) - (setq cnt (1+ cnt)))))) - (push (>= frac org-table-number-fraction) typenums))) - (setq lengths (nreverse lengths) typenums (nreverse typenums)) - - ;; Store the alignment of this table, for later editing of single fields - (setq org-table-last-alignment typenums - org-table-last-column-widths lengths) - - ;; With invisible characters, `format' does not get the field width right - ;; So we need to make these fields wide by hand. - (when (or links emph raise) - (loop for i from 0 upto (1- maxfields) do - (setq len (nth i lengths)) - (loop for j from 0 upto (1- (length fields)) do - (setq c (nthcdr i (car (nthcdr j fields)))) - (if (and (stringp (car c)) - (or (text-property-any 0 (length (car c)) - 'invisible 'org-link (car c)) - (text-property-any 0 (length (car c)) - 'org-dwidth t (car c))) - (< (org-string-width (car c)) len)) - (progn - (setq space (make-string (- len (org-string-width (car c))) ?\ )) - (setcar c (if (nth i typenums) - (concat space (car c)) - (concat (car c) space)))))))) - - ;; Compute the formats needed for output of the table - (setq rfmt (concat indent "|") hfmt (concat indent "|")) - (while (setq l (pop lengths)) - (setq ty (if (pop typenums) "" "-")) ; number types flushright - (setq rfmt (concat rfmt (format rfmt1 ty l)) - hfmt (concat hfmt (format hfmt1 (make-string l ?-))))) - (setq rfmt (concat rfmt "\n") - hfmt (concat (substring hfmt 0 -1) "|\n")) - - (setq new (mapconcat - (lambda (l) - (if l (apply 'format rfmt - (append (pop fields) emptystrings)) - hfmt)) - lines "")) - (move-marker org-table-aligned-begin-marker (point)) - (insert new) - ;; Replace the old one - (delete-region (point) end) - (move-marker end nil) - (move-marker org-table-aligned-end-marker (point)) - (when (and orgtbl-mode (not (derived-mode-p 'org-mode))) - (goto-char org-table-aligned-begin-marker) - (while (org-hide-wide-columns org-table-aligned-end-marker))) - ;; Try to move to the old location - (org-goto-line winstartline) - (setq winstart (point-at-bol)) - (org-goto-line linepos) - (when (eq (window-buffer (selected-window)) (current-buffer)) - (set-window-start (selected-window) winstart 'noforce)) - (org-table-goto-column colpos) - (and org-table-overlay-coordinates (org-table-overlay-coordinates)) - (setq org-table-may-need-update nil) - )) + (let* ((beg (org-table-begin)) + (end (copy-marker (org-table-end)))) + (org-table-save-field + ;; Make sure invisible characters in the table are at the right + ;; place since column widths take them into account. + (font-lock-fontify-region beg end) + (move-marker org-table-aligned-begin-marker beg) + (move-marker org-table-aligned-end-marker end) + (goto-char beg) + (let* ((indent (progn (looking-at "[ \t]*") (match-string 0))) + ;; Table's rows. Separators are replaced by nil. Trailing + ;; spaces are also removed. + (lines (mapcar (lambda (l) + (and (not (string-match-p "\\`[ \t]*|-" l)) + (let ((l (org-trim l))) + (remove-text-properties + 0 (length l) '(display t org-cwidth t) l) + l))) + (org-split-string (buffer-substring beg end) "\n"))) + ;; Get the data fields by splitting the lines. + (fields (mapcar (lambda (l) (org-split-string l " *| *")) + (remq nil lines))) + ;; Compute number of fields in the longest line. If the + ;; table contains no field, create a default table. + (maxfields (if fields (apply #'max (mapcar #'length fields)) + (kill-region beg end) + (org-table-create org-table-default-size) + (user-error "Empty table - created default table"))) + ;; A list of empty strings to fill any short rows on output. + (emptycells (make-list maxfields "")) + lengths typenums) + ;; Check for special formatting. + (dotimes (i maxfields) + (let ((column (mapcar (lambda (x) (or (nth i x) "")) fields)) + fmax falign) + ;; Look for an explicit width or alignment. + (when (save-excursion + (or (re-search-forward "| *<[lrc][0-9]*> *\\(|\\|$\\)" end t) + (and org-table-do-narrow + (re-search-forward + "| *<[lrc]?[0-9]+> *\\(|\\|$\\)" end t)))) + (catch :exit + (dolist (cell column) + (when (string-match "\\`<\\([lrc]\\)?\\([0-9]+\\)?>\\'" cell) + (when (match-end 1) (setq falign (match-string 1 cell))) + (when (and org-table-do-narrow (match-end 2)) + (setq fmax (string-to-number (match-string 2 cell)))) + (when (or falign fmax) (throw :exit nil))))) + ;; Find fields that are wider than FMAX, and shorten them. + (when fmax + (dolist (x column) + (when (> (string-width x) fmax) + (org-add-props x nil + 'help-echo + (concat + "Clipped table field, use `\\[org-table-edit-field]' to \ +edit. Full value is:\n" + (substring-no-properties x))) + (let ((l (length x)) + (f1 (min fmax + (or (string-match org-bracket-link-regexp x) + fmax))) + (f2 1)) + (unless (> f1 1) + (user-error + "Cannot narrow field starting with wide link \"%s\"" + (match-string 0 x))) + (if (= (org-string-width x) l) (setq f2 f1) + (setq f2 1) + (while (< (org-string-width (substring x 0 f2)) f1) + (cl-incf f2))) + (add-text-properties f2 l (list 'org-cwidth t) x) + (add-text-properties + (if (>= (string-width (substring x (1- f2) f2)) 2) (1- f2) + (- f2 2)) + f2 + (list 'display org-narrow-column-arrow) + x)))))) + ;; Get the maximum width for each column + (push (or fmax (apply #'max 1 (mapcar #'org-string-width column))) + lengths) + ;; Get the fraction of numbers among non-empty cells to + ;; decide about alignment of the column. + (if falign (push (equal (downcase falign) "r") typenums) + (let ((cnt 0) + (frac 0.0)) + (dolist (x column) + (unless (equal x "") + (setq frac + (/ (+ (* frac cnt) + (if (string-match-p org-table-number-regexp x) + 1 + 0)) + (cl-incf cnt))))) + (push (>= frac org-table-number-fraction) typenums))))) + (setq lengths (nreverse lengths)) + (setq typenums (nreverse typenums)) + ;; Store alignment of this table, for later editing of single + ;; fields. + (setq org-table-last-alignment typenums) + (setq org-table-last-column-widths lengths) + ;; With invisible characters, `format' does not get the field + ;; width right So we need to make these fields wide by hand. + ;; Invisible characters may be introduced by fontified links, + ;; emphasis, macros or sub/superscripts. + (when (or (text-property-any beg end 'invisible 'org-link) + (text-property-any beg end 'invisible t)) + (dotimes (i maxfields) + (let ((len (nth i lengths))) + (dotimes (j (length fields)) + (let* ((c (nthcdr i (nth j fields))) + (cell (car c))) + (when (and + (stringp cell) + (let ((l (length cell))) + (or (text-property-any 0 l 'invisible 'org-link cell) + (text-property-any beg end 'invisible t))) + (< (org-string-width cell) len)) + (let ((s (make-string (- len (org-string-width cell)) ?\s))) + (setcar c (if (nth i typenums) (concat s cell) + (concat cell s)))))))))) + + ;; Compute the formats needed for output of the table. + (let ((hfmt (concat indent "|")) + (rfmt (concat indent "|")) + (rfmt1 " %%%s%ds |") + (hfmt1 "-%s-+")) + (dolist (l lengths (setq hfmt (concat (substring hfmt 0 -1) "|"))) + (let ((ty (if (pop typenums) "" "-"))) ; Flush numbers right. + (setq rfmt (concat rfmt (format rfmt1 ty l))) + (setq hfmt (concat hfmt (format hfmt1 (make-string l ?-)))))) + ;; Replace modified lines only. Check not only contents, but + ;; also columns' width. + (dolist (l lines) + (let ((line + (if l (apply #'format rfmt (append (pop fields) emptycells)) + hfmt)) + (previous (buffer-substring (point) (line-end-position)))) + (if (and (equal previous line) + (let ((a 0) + (b 0)) + (while (and (progn + (setq a (next-single-property-change + a 'org-cwidth previous)) + (setq b (next-single-property-change + b 'org-cwidth line))) + (eq a b))) + (eq a b))) + (forward-line) + (insert line "\n") + (delete-region (point) (line-beginning-position 2)))))) + (when (and orgtbl-mode (not (derived-mode-p 'org-mode))) + (goto-char org-table-aligned-begin-marker) + (while (org-hide-wide-columns org-table-aligned-end-marker))) + (set-marker end nil) + (when org-table-overlay-coordinates (org-table-overlay-coordinates)) + (setq org-table-may-need-update nil))))) ;;;###autoload (defun org-table-begin (&optional table-type) "Find the beginning of the table and return its position. -With argument TABLE-TYPE, go to the beginning of a table.el-type table." - (save-excursion - (if (not (re-search-backward - (if table-type org-table-any-border-regexp - org-table-border-regexp) - nil t)) - (progn (goto-char (point-min)) (point)) - (goto-char (match-beginning 0)) - (beginning-of-line 2) - (point)))) +With a non-nil optional argument TABLE-TYPE, return the beginning +of a table.el-type table. This function assumes point is on +a table." + (cond (table-type + (org-element-property :post-affiliated (org-element-at-point))) + ((save-excursion + (and (re-search-backward org-table-border-regexp nil t) + (line-beginning-position 2)))) + (t (point-min)))) ;;;###autoload (defun org-table-end (&optional table-type) "Find the end of the table and return its position. -With argument TABLE-TYPE, go to the end of a table.el-type table." +With a non-nil optional argument TABLE-TYPE, return the end of +a table.el-type table. This function assumes point is on +a table." (save-excursion - (if (not (re-search-forward - (if table-type org-table-any-border-regexp - org-table-border-regexp) - nil t)) - (goto-char (point-max)) - (goto-char (match-beginning 0))) - (point-marker))) + (cond (table-type + (goto-char (org-element-property :end (org-element-at-point))) + (skip-chars-backward " \t\n") + (line-beginning-position 2)) + ((re-search-forward org-table-border-regexp nil t) + (match-beginning 0)) + ;; When the line right after the table is the last line in + ;; the buffer with trailing spaces but no final newline + ;; character, be sure to catch the correct ending at its + ;; beginning. In any other case, ending is expected to be + ;; at point max. + (t (goto-char (point-max)) + (skip-chars-backward " \t") + (if (bolp) (point) (line-end-position)))))) ;;;###autoload (defun org-table-justify-field-maybe (&optional new) @@ -950,38 +958,40 @@ Optional argument NEW may specify text to replace the current field content." ((and (not new) org-table-may-need-update)) ; Realignment will happen anyway ((org-at-table-hline-p)) ((and (not new) - (or (not (equal (marker-buffer org-table-aligned-begin-marker) - (current-buffer))) + (or (not (eq (marker-buffer org-table-aligned-begin-marker) + (current-buffer))) (< (point) org-table-aligned-begin-marker) (>= (point) org-table-aligned-end-marker))) - ;; This is not the same table, force a full re-align + ;; This is not the same table, force a full re-align. (setq org-table-may-need-update t)) - (t ;; realign the current field, based on previous full realign - (let* ((pos (point)) s - (col (org-table-current-column)) - (num (if (> col 0) (nth (1- col) org-table-last-alignment))) - l f n o e) + (t + ;; Realign the current field, based on previous full realign. + (let ((pos (point)) + (col (org-table-current-column))) (when (> col 0) - (skip-chars-backward "^|\n") - (if (looking-at " *\\([^|\n]*?\\) *\\(|\\|$\\)") - (progn - (setq s (match-string 1) - o (match-string 0) - l (max 1 (- (match-end 0) (match-beginning 0) 3)) - e (not (= (match-beginning 2) (match-end 2)))) - (setq f (format (if num " %%%ds %s" " %%-%ds %s") - l (if e "|" (setq org-table-may-need-update t) "")) - n (format f s)) - (if new - (if (<= (length new) l) ;; FIXME: length -> str-width? - (setq n (format f new)) - (setq n (concat new "|") org-table-may-need-update t))) - (if (equal (string-to-char n) ?-) (setq n (concat " " n))) - (or (equal n o) - (let (org-table-may-need-update) - (replace-match n t t)))) - (setq org-table-may-need-update t)) - (goto-char pos)))))) + (skip-chars-backward "^|") + (if (not (looking-at " *\\([^|\n]*?\\) *\\(|\\|$\\)")) + (setq org-table-may-need-update t) + (let* ((numbers? (nth (1- col) org-table-last-alignment)) + (cell (match-string 0)) + (field (match-string 1)) + (len (max 1 (- (org-string-width cell) 3))) + (properly-closed? (/= (match-beginning 2) (match-end 2))) + (fmt (format (if numbers? " %%%ds %s" " %%-%ds %s") + len + (if properly-closed? "|" + (setq org-table-may-need-update t) + ""))) + (new-cell + (cond ((not new) (format fmt field)) + ((<= (org-string-width new) len) (format fmt new)) + (t + (setq org-table-may-need-update t) + (format " %s |" new))))) + (unless (equal new-cell cell) + (let (org-table-may-need-update) + (replace-match new-cell t t))) + (goto-char pos)))))))) ;;;###autoload (defun org-table-next-field () @@ -1020,25 +1030,29 @@ Before doing so, re-align the table if necessary." (interactive) (org-table-justify-field-maybe) (org-table-maybe-recalculate-line) - (if (and org-table-automatic-realign - org-table-may-need-update) - (org-table-align)) - (if (org-at-table-hline-p) - (end-of-line 1)) - (condition-case nil - (progn - (re-search-backward "|" (org-table-begin)) - (re-search-backward "|" (org-table-begin))) - (error (user-error "Cannot move to previous table field"))) - (while (looking-at "|\\(-\\|[ \t]*$\\)") - (re-search-backward "|" (org-table-begin))) - (if (looking-at "| ?") - (goto-char (match-end 0)))) + (when (and org-table-automatic-realign + org-table-may-need-update) + (org-table-align)) + (when (org-at-table-hline-p) + (end-of-line)) + (let ((start (org-table-begin)) + (origin (point))) + (condition-case nil + (progn + (search-backward "|" start nil 2) + (while (looking-at-p "|\\(?:-\\|[ \t]*$\\)") + (search-backward "|" start))) + (error + (goto-char origin) + (user-error "Cannot move to previous table field")))) + (when (looking-at "| ?") + (goto-char (match-end 0)))) (defun org-table-beginning-of-field (&optional n) - "Move to the end of the current table field. -If already at or after the end, move to the end of the next table field. -With numeric argument N, move N-1 fields forward first." + "Move to the beginning of the current table field. +If already at or before the beginning, move to the beginning of the +previous field. +With numeric argument N, move N-1 fields backward first." (interactive "p") (let ((pos (point))) (while (> n 1) @@ -1051,10 +1065,9 @@ With numeric argument N, move N-1 fields forward first." (if (>= (point) pos) (org-table-beginning-of-field 2)))) (defun org-table-end-of-field (&optional n) - "Move to the beginning of the current table field. -If already at or before the beginning, move to the beginning of the -previous field. -With numeric argument N, move N-1 fields backward first." + "Move to the end of the current table field. +If already at or after the end, move to the end of the next table field. +With numeric argument N, move N-1 fields forward first." (interactive "p") (let ((pos (point))) (while (> n 1) @@ -1074,88 +1087,115 @@ Before doing so, re-align the table if necessary." (interactive) (org-table-maybe-eval-formula) (org-table-maybe-recalculate-line) - (if (or (looking-at "[ \t]*$") - (save-excursion (skip-chars-backward " \t") (bolp))) - (newline) - (if (and org-table-automatic-realign - org-table-may-need-update) - (org-table-align)) - (let ((col (org-table-current-column))) - (beginning-of-line 2) - (if (or (not (org-at-table-p)) + (if (and org-table-automatic-realign + org-table-may-need-update) + (org-table-align)) + (let ((col (org-table-current-column))) + (beginning-of-line 2) + (when (or (not (org-at-table-p)) (org-at-table-hline-p)) - (progn - (beginning-of-line 0) - (org-table-insert-row 'below))) - (org-table-goto-column col) - (skip-chars-backward "^|\n\r") - (if (looking-at " ") (forward-char 1))))) + (beginning-of-line 0) + (org-table-insert-row 'below)) + (org-table-goto-column col) + (skip-chars-backward "^|\n\r") + (when (looking-at " ") (forward-char)))) ;;;###autoload (defun org-table-copy-down (n) - "Copy a field down in the current column. -If the field at the cursor is empty, copy into it the content of -the nearest non-empty field above. With argument N, use the Nth -non-empty field. If the current field is not empty, it is copied -down to the next row, and the cursor is moved with it. -Therefore, repeating this command causes the column to be filled -row-by-row. + "Copy the value of the current field one row below. + +If the field at the cursor is empty, copy the content of the +nearest non-empty field above. With argument N, use the Nth +non-empty field. + +If the current field is not empty, it is copied down to the next +row, and the cursor is moved with it. Therefore, repeating this +command causes the column to be filled row-by-row. + If the variable `org-table-copy-increment' is non-nil and the field is an integer or a timestamp, it will be incremented while -copying. In the case of a timestamp, increment by one day." +copying. By default, increment by the difference between the +value in the current field and the one in the field above. To +increment using a fixed integer, set `org-table-copy-increment' +to a number. In the case of a timestamp, increment by days." (interactive "p") (let* ((colpos (org-table-current-column)) (col (current-column)) (field (save-excursion (org-table-get-field))) + (field-up (or (save-excursion + (org-table-get (1- (org-table-current-line)) + (org-table-current-column))) "")) (non-empty (string-match "[^ \t]" field)) + (non-empty-up (string-match "[^ \t]" field-up)) (beg (org-table-begin)) (orig-n n) - txt) + txt txt-up inc) (org-table-check-inside-data-field) - (if non-empty - (progn - (setq txt (org-trim field)) - (org-table-next-row) - (org-table-blank-field)) - (save-excursion - (setq txt - (catch 'exit - (while (progn (beginning-of-line 1) - (re-search-backward org-table-dataline-regexp - beg t)) - (org-table-goto-column colpos t) - (if (and (looking-at - "|[ \t]*\\([^| \t][^|]*?\\)[ \t]*|") - (<= (setq n (1- n)) 0)) - (throw 'exit (match-string 1)))))))) - (if txt - (progn - (if (and org-table-copy-increment - (not (equal orig-n 0)) - (string-match "^[0-9]+$" txt) - (< (string-to-number txt) 100000000)) - (setq txt (format "%d" (+ (string-to-number txt) 1)))) - (insert txt) - (org-move-to-column col) - (if (and org-table-copy-increment (org-at-timestamp-p t)) - (org-timestamp-up-day) - (org-table-maybe-recalculate-line)) - (org-table-align) - (org-move-to-column col)) - (user-error "No non-empty field found")))) + (if (not non-empty) + (save-excursion + (setq txt + (catch 'exit + (while (progn (beginning-of-line 1) + (re-search-backward org-table-dataline-regexp + beg t)) + (org-table-goto-column colpos t) + (if (and (looking-at + "|[ \t]*\\([^| \t][^|]*?\\)[ \t]*|") + (<= (setq n (1- n)) 0)) + (throw 'exit (match-string 1)))))) + (setq field-up + (catch 'exit + (while (progn (beginning-of-line 1) + (re-search-backward org-table-dataline-regexp + beg t)) + (org-table-goto-column colpos t) + (if (and (looking-at + "|[ \t]*\\([^| \t][^|]*?\\)[ \t]*|") + (<= (setq n (1- n)) 0)) + (throw 'exit (match-string 1)))))) + (setq non-empty-up (and field-up (string-match "[^ \t]" field-up)))) + ;; Above field was not empty, go down to the next row + (setq txt (org-trim field)) + (org-table-next-row) + (org-table-blank-field)) + (if non-empty-up (setq txt-up (org-trim field-up))) + (setq inc (cond + ((numberp org-table-copy-increment) org-table-copy-increment) + (txt-up (cond ((and (string-match org-ts-regexp3 txt-up) + (string-match org-ts-regexp3 txt)) + (- (org-time-string-to-absolute txt) + (org-time-string-to-absolute txt-up))) + ((string-match org-ts-regexp3 txt) 1) + ((string-match "\\([-+]\\)?[0-9]+\\(?:\.[0-9]+\\)?" txt-up) + (- (string-to-number txt) + (string-to-number (match-string 0 txt-up)))) + (t 1))) + (t 1))) + (if (not txt) + (user-error "No non-empty field found") + (if (and org-table-copy-increment + (not (equal orig-n 0)) + (string-match-p "^[-+^/*0-9eE.]+$" txt) + (< (string-to-number txt) 100000000)) + (setq txt (calc-eval (concat txt "+" (number-to-string inc))))) + (insert txt) + (org-move-to-column col) + (if (and org-table-copy-increment (org-at-timestamp-p 'lax)) + (org-timestamp-up-day inc) + (org-table-maybe-recalculate-line)) + (org-table-align) + (org-move-to-column col)))) (defun org-table-check-inside-data-field (&optional noerror) "Is point inside a table data field? I.e. not on a hline or before the first or after the last column? This actually throws an error, so it aborts the current command." - (if (or (not (org-at-table-p)) - (= (org-table-current-column) 0) - (org-at-table-hline-p) - (looking-at "[ \t]*$")) - (if noerror - nil - (user-error "Not in table data field")) - t)) + (cond ((and (org-at-table-p) + (not (save-excursion (skip-chars-backward " \t") (bolp))) + (not (org-at-table-hline-p)) + (not (looking-at "[ \t]*$")))) + (noerror nil) + (t (user-error "Not in table data field")))) (defvar org-table-clip nil "Clipboard for table regions.") @@ -1166,7 +1206,7 @@ If LINE is larger than the number of data lines in the table, the function returns nil. However, if COLUMN is too large, we will simply return an empty string. If LINE is nil, use the current line. -If column is nil, use the current column." +If COLUMN is nil, use the current column." (setq column (or column (org-table-current-column))) (save-excursion (and (or (not line) (org-table-goto-line line)) @@ -1206,7 +1246,7 @@ Return t when the line exists, nil if it does not exist." "Blank the current table field or active region." (interactive) (org-table-check-inside-data-field) - (if (and (org-called-interactively-p 'any) (org-region-active-p)) + (if (and (called-interactively-p 'any) (org-region-active-p)) (let (org-table-clip) (org-table-cut-region (region-beginning) (region-end))) (skip-chars-backward "^|") @@ -1221,52 +1261,53 @@ Return t when the line exists, nil if it does not exist." (defun org-table-get-field (&optional n replace) "Return the value of the field in column N of current row. -N defaults to current field. -If REPLACE is a string, replace field with this value. The return value -is always the old value." - (and n (org-table-goto-column n)) +N defaults to current column. If REPLACE is a string, replace +field with this value. The return value is always the old +value." + (when n (org-table-goto-column n)) (skip-chars-backward "^|\n") - (backward-char 1) - (if (looking-at "|[^|\r\n]*") - (let* ((pos (match-beginning 0)) - (val (buffer-substring (1+ pos) (match-end 0)))) - (if replace - (replace-match (concat "|" (if (equal replace "") " " replace)) - t t)) - (goto-char (min (point-at-eol) (+ 2 pos))) - val) - (forward-char 1) "")) + (if (or (bolp) (looking-at-p "[ \t]*$")) + ;; Before first column or after last one. + "" + (looking-at "[^|\r\n]*") + (let* ((pos (match-beginning 0)) + (val (buffer-substring pos (match-end 0)))) + (when replace + (replace-match (if (equal replace "") " " replace) t t)) + (goto-char (min (line-end-position) (1+ pos))) + val))) ;;;###autoload -(defun org-table-field-info (arg) +(defun org-table-field-info (_arg) "Show info about the current field, and highlight any reference at point." (interactive "P") (unless (org-at-table-p) (user-error "Not at a table")) - (org-table-get-specials) + (org-table-analyze) (save-excursion (let* ((pos (point)) (col (org-table-current-column)) (cname (car (rassoc (int-to-string col) org-table-column-names))) - (name (car (rassoc (list (org-current-line) col) + (name (car (rassoc (list (count-lines org-table-current-begin-pos + (line-beginning-position)) + col) org-table-named-field-locations))) (eql (org-table-expand-lhs-ranges (mapcar (lambda (e) - (cons (org-table-formula-handle-first/last-rc - (car e)) (cdr e))) + (cons (org-table-formula-handle-first/last-rc (car e)) + (cdr e))) (org-table-get-stored-formulas)))) (dline (org-table-current-dline)) (ref (format "@%d$%d" dline col)) (ref1 (org-table-convert-refs-to-an ref)) + ;; Prioritize field formulas over column formulas. (fequation (or (assoc name eql) (assoc ref eql))) - (cequation (assoc (int-to-string col) eql)) + (cequation (assoc (format "$%d" col) eql)) (eqn (or fequation cequation))) - (if (and eqn (get-text-property 0 :orig-eqn (car eqn))) - (setq eqn (get-text-property 0 :orig-eqn (car eqn)))) + (let ((p (and eqn (get-text-property 0 :orig-eqn (car eqn))))) + (when p (setq eqn p))) (goto-char pos) - (condition-case nil - (org-table-show-reference 'local) - (error nil)) + (ignore-errors (org-table-show-reference 'local)) (message "line @%d, col $%s%s, ref @%d$%d or %s%s%s" dline col (if cname (concat " or $" cname) "") @@ -1277,39 +1318,33 @@ is always the old value." (concat ", formula: " (org-table-formula-to-user (concat - (if (string-match "^[$@]"(car eqn)) "" "$") + (if (or (string-prefix-p "$" (car eqn)) + (string-prefix-p "@" (car eqn))) + "" + "$") (car eqn) "=" (cdr eqn)))) ""))))) (defun org-table-current-column () "Find out which column we are in." (interactive) - (if (org-called-interactively-p 'any) (org-table-check-inside-data-field)) (save-excursion - (let ((cnt 0) (pos (point))) - (beginning-of-line 1) - (while (search-forward "|" pos t) - (setq cnt (1+ cnt))) - (when (org-called-interactively-p 'interactive) - (message "In table column %d" cnt)) - cnt))) + (let ((column 0) (pos (point))) + (beginning-of-line) + (while (search-forward "|" pos t) (cl-incf column)) + column))) -;;;###autoload (defun org-table-current-dline () "Find out what table data line we are in. Only data lines count for this." - (interactive) - (when (org-called-interactively-p 'any) - (org-table-check-inside-data-field)) (save-excursion - (let ((cnt 0) (pos (point))) + (let ((c 0) + (pos (line-beginning-position))) (goto-char (org-table-begin)) (while (<= (point) pos) - (if (looking-at org-table-dataline-regexp) (setq cnt (1+ cnt))) - (beginning-of-line 2)) - (when (org-called-interactively-p 'any) - (message "This is table line %d" cnt)) - cnt))) + (when (looking-at org-table-dataline-regexp) (cl-incf c)) + (forward-line)) + c))) ;;;###autoload (defun org-table-goto-column (n &optional on-delim force) @@ -1338,25 +1373,19 @@ However, when FORCE is non-nil, create new columns if necessary." (defun org-table-insert-column () "Insert a new column into the table." (interactive) - (if (not (org-at-table-p)) - (user-error "Not at a table")) + (unless (org-at-table-p) (user-error "Not at a table")) (org-table-find-dataline) (let* ((col (max 1 (org-table-current-column))) (beg (org-table-begin)) - (end (org-table-end)) - ;; Current cursor position - (linepos (org-current-line)) - (colpos col)) - (goto-char beg) - (while (< (point) end) - (if (org-at-table-hline-p) - nil - (org-table-goto-column col t) - (insert "| ")) - (beginning-of-line 2)) - (move-marker end nil) - (org-goto-line linepos) - (org-table-goto-column colpos) + (end (copy-marker (org-table-end)))) + (org-table-save-field + (goto-char beg) + (while (< (point) end) + (unless (org-at-table-hline-p) + (org-table-goto-column col t) + (insert "| ")) + (forward-line))) + (set-marker end nil) (org-table-align) (when (or (not org-table-fix-formulas-confirm) (funcall org-table-fix-formulas-confirm "Fix formulas? ")) @@ -1384,58 +1413,55 @@ However, when FORCE is non-nil, create new columns if necessary." (defun org-table-line-to-dline (line &optional above) "Turn a buffer line number into a data line number. + If there is no data line in this line, return nil. -If there is no matching dline (most likely te reference was a hline), the -first dline below it is used. When ABOVE is non-nil, the one above is used." - (catch 'exit - (let ((ll (length org-table-dlines)) - i) - (if above - (progn - (setq i (1- ll)) - (while (> i 0) - (if (<= (aref org-table-dlines i) line) - (throw 'exit i)) - (setq i (1- i)))) - (setq i 1) - (while (< i ll) - (if (>= (aref org-table-dlines i) line) - (throw 'exit i)) - (setq i (1+ i))))) - nil)) + +If there is no matching dline (most likely the reference was +a hline), the first dline below it is used. When ABOVE is +non-nil, the one above is used." + (let ((min 1) + (max (1- (length org-table-dlines)))) + (cond ((or (> (aref org-table-dlines min) line) + (< (aref org-table-dlines max) line)) + nil) + ((= (aref org-table-dlines max) line) max) + (t (catch 'exit + (while (> (- max min) 1) + (let* ((mean (/ (+ max min) 2)) + (v (aref org-table-dlines mean))) + (cond ((= v line) (throw 'exit mean)) + ((> v line) (setq max mean)) + (t (setq min mean))))) + (if above min max)))))) ;;;###autoload (defun org-table-delete-column () "Delete a column from the table." (interactive) - (if (not (org-at-table-p)) - (user-error "Not at a table")) + (unless (org-at-table-p) (user-error "Not at a table")) (org-table-find-dataline) (org-table-check-inside-data-field) - (let* ((col (org-table-current-column)) - (beg (org-table-begin)) - (end (org-table-end)) - ;; Current cursor position - (linepos (org-current-line)) - (colpos col)) - (goto-char beg) - (while (< (point) end) - (if (org-at-table-hline-p) - nil - (org-table-goto-column col t) - (and (looking-at "|[^|\n]+|") - (replace-match "|"))) - (beginning-of-line 2)) - (move-marker end nil) - (org-goto-line linepos) - (org-table-goto-column colpos) + (let ((col (org-table-current-column)) + (beg (org-table-begin)) + (end (copy-marker (org-table-end)))) + (org-table-save-field + (goto-char beg) + (while (< (point) end) + (if (org-at-table-hline-p) + nil + (org-table-goto-column col t) + (and (looking-at "|[^|\n]+|") + (replace-match "|"))) + (forward-line))) + (set-marker end nil) + (org-table-goto-column (max 1 (1- col))) (org-table-align) (when (or (not org-table-fix-formulas-confirm) (funcall org-table-fix-formulas-confirm "Fix formulas? ")) - (org-table-fix-formulas "$" (list (cons (number-to-string col) "INVALID")) - col -1 col) - (org-table-fix-formulas "$LR" (list (cons (number-to-string col) "INVALID")) - col -1 col)))) + (org-table-fix-formulas + "$" (list (cons (number-to-string col) "INVALID")) col -1 col) + (org-table-fix-formulas + "$LR" (list (cons (number-to-string col) "INVALID")) col -1 col)))) ;;;###autoload (defun org-table-move-column-right () @@ -1452,31 +1478,29 @@ first dline below it is used. When ABOVE is non-nil, the one above is used." (defun org-table-move-column (&optional left) "Move the current column to the right. With arg LEFT, move to the left." (interactive "P") - (if (not (org-at-table-p)) - (user-error "Not at a table")) + (unless (org-at-table-p) (user-error "Not at a table")) (org-table-find-dataline) (org-table-check-inside-data-field) (let* ((col (org-table-current-column)) (col1 (if left (1- col) col)) + (colpos (if left (1- col) (1+ col))) (beg (org-table-begin)) - (end (org-table-end)) - ;; Current cursor position - (linepos (org-current-line)) - (colpos (if left (1- col) (1+ col)))) - (if (and left (= col 1)) - (user-error "Cannot move column further left")) - (if (and (not left) (looking-at "[^|\n]*|[^|\n]*$")) - (user-error "Cannot move column further right")) - (goto-char beg) - (while (< (point) end) - (if (org-at-table-hline-p) - nil - (org-table-goto-column col1 t) - (and (looking-at "|\\([^|\n]+\\)|\\([^|\n]+\\)|") - (replace-match "|\\2|\\1|"))) - (beginning-of-line 2)) - (move-marker end nil) - (org-goto-line linepos) + (end (copy-marker (org-table-end)))) + (when (and left (= col 1)) + (user-error "Cannot move column further left")) + (when (and (not left) (looking-at "[^|\n]*|[^|\n]*$")) + (user-error "Cannot move column further right")) + (org-table-save-field + (goto-char beg) + (while (< (point) end) + (unless (org-at-table-hline-p) + (org-table-goto-column col1 t) + (when (looking-at "|\\([^|\n]+\\)|\\([^|\n]+\\)|") + (transpose-regions + (match-beginning 1) (match-end 1) + (match-beginning 2) (match-end 2)))) + (forward-line))) + (set-marker end nil) (org-table-goto-column colpos) (org-table-align) (when (or (not org-table-fix-formulas-confirm) @@ -1510,47 +1534,52 @@ first dline below it is used. When ABOVE is non-nil, the one above is used." (dline1 (org-table-current-dline)) (dline2 (+ dline1 (if up -1 1))) (tonew (if up 0 2)) - txt hline2p) + hline2p) + (when (and up (= (point-min) (line-beginning-position))) + (user-error "Cannot move row further")) (beginning-of-line tonew) - (unless (org-at-table-p) + (when (or (and (not up) (eobp)) (not (org-at-table-p))) (goto-char pos) (user-error "Cannot move row further")) (setq hline2p (looking-at org-table-hline-regexp)) (goto-char pos) - (beginning-of-line 1) - (setq pos (point)) - (setq txt (buffer-substring (point) (1+ (point-at-eol)))) - (delete-region (point) (1+ (point-at-eol))) - (beginning-of-line tonew) - (insert txt) - (beginning-of-line 0) - (org-move-to-column col) - (unless (or hline1p hline2p - (not (or (not org-table-fix-formulas-confirm) - (funcall org-table-fix-formulas-confirm - "Fix formulas? ")))) - (org-table-fix-formulas - "@" (list (cons (number-to-string dline1) (number-to-string dline2)) - (cons (number-to-string dline2) (number-to-string dline1))))))) + (let ((row (delete-and-extract-region (line-beginning-position) + (line-beginning-position 2)))) + (beginning-of-line tonew) + (unless (bolp) (insert "\n")) ;at eob without a newline + (insert row) + (unless (bolp) (insert "\n")) ;missing final newline in ROW + (beginning-of-line 0) + (org-move-to-column col) + (unless (or hline1p hline2p + (not (or (not org-table-fix-formulas-confirm) + (funcall org-table-fix-formulas-confirm + "Fix formulas? ")))) + (org-table-fix-formulas + "@" (list + (cons (number-to-string dline1) (number-to-string dline2)) + (cons (number-to-string dline2) (number-to-string dline1)))))))) ;;;###autoload (defun org-table-insert-row (&optional arg) "Insert a new row above the current line into the table. With prefix ARG, insert below the current line." (interactive "P") - (if (not (org-at-table-p)) - (user-error "Not at a table")) - (let* ((line (buffer-substring (point-at-bol) (point-at-eol))) + (unless (org-at-table-p) (user-error "Not at a table")) + (let* ((line (buffer-substring (line-beginning-position) (line-end-position))) (new (org-table-clean-line line))) ;; Fix the first field if necessary (if (string-match "^[ \t]*| *[#$] *|" line) (setq new (replace-match (match-string 0 line) t t new))) (beginning-of-line (if arg 2 1)) + ;; Buffer may not end of a newline character, so ensure + ;; (beginning-of-line 2) moves point to a new line. + (unless (bolp) (insert "\n")) (let (org-table-may-need-update) (insert-before-markers new "\n")) (beginning-of-line 0) - (re-search-forward "| ?" (point-at-eol) t) - (and (or org-table-may-need-update org-table-overlay-coordinates) - (org-table-align)) + (re-search-forward "| ?" (line-end-position) t) + (when (or org-table-may-need-update org-table-overlay-coordinates) + (org-table-align)) (when (or (not org-table-fix-formulas-confirm) (funcall org-table-fix-formulas-confirm "Fix formulas? ")) (org-table-fix-formulas "@" nil (1- (org-table-current-dline)) 1)))) @@ -1563,7 +1592,7 @@ With prefix ABOVE, insert above the current line." (if (not (org-at-table-p)) (user-error "Not at a table")) (when (eobp) (insert "\n") (backward-char 1)) - (if (not (string-match "|[ \t]*$" (org-current-line-string))) + (if (not (string-match-p "|[ \t]*$" (org-current-line-string))) (org-table-align)) (let ((line (org-table-clean-line (buffer-substring (point-at-bol) (point-at-eol)))) @@ -1613,17 +1642,20 @@ In particular, this does handle wide and invisible characters." (if (not (org-at-table-p)) (user-error "Not at a table")) (let ((col (current-column)) - (dline (org-table-current-dline))) + (dline (and (not (org-match-line org-table-hline-regexp)) + (org-table-current-dline)))) (kill-region (point-at-bol) (min (1+ (point-at-eol)) (point-max))) (if (not (org-at-table-p)) (beginning-of-line 0)) (org-move-to-column col) - (when (or (not org-table-fix-formulas-confirm) - (funcall org-table-fix-formulas-confirm "Fix formulas? ")) + (when (and dline + (or (not org-table-fix-formulas-confirm) + (funcall org-table-fix-formulas-confirm "Fix formulas? "))) (org-table-fix-formulas "@" (list (cons (number-to-string dline) "INVALID")) dline -1 dline)))) ;;;###autoload -(defun org-table-sort-lines (with-case &optional sorting-type) +(defun org-table-sort-lines + (&optional with-case sorting-type getkey-func compare-func interactive?) "Sort table lines according to the column at point. The position of point indicates the column to be used for @@ -1636,76 +1668,113 @@ should be in the last line to be included into the sorting. The command then prompts for the sorting type which can be alphabetically, numerically, or by time (as given in a time stamp -in the field). Sorting in reverse order is also possible. +in the field, or as a HH:MM value). Sorting in reverse order is +also possible. With prefix argument WITH-CASE, alphabetic sorting will be case-sensitive. If SORTING-TYPE is specified when this function is called from a Lisp program, no prompting will take place. SORTING-TYPE must be a character, -any of (?a ?A ?n ?N ?t ?T) where the capital letter indicate that sorting -should be done in reverse order." - (interactive "P") - (let* ((thisline (org-current-line)) - (thiscol (org-table-current-column)) - (otc org-table-overlay-coordinates) - beg end bcol ecol tend tbeg column lns pos) - (when (equal thiscol 0) - (if (org-called-interactively-p 'any) - (setq thiscol - (string-to-number - (read-string "Use column N for sorting: "))) - (setq thiscol 1)) - (org-table-goto-column thiscol)) - (org-table-check-inside-data-field) - (if (org-region-active-p) - (progn - (setq beg (region-beginning) end (region-end)) - (goto-char beg) - (setq column (org-table-current-column) - beg (point-at-bol)) - (goto-char end) - (setq end (point-at-bol 2))) - (setq column (org-table-current-column) - pos (point) - tbeg (org-table-begin) - tend (org-table-end)) - (if (re-search-backward org-table-hline-regexp tbeg t) - (setq beg (point-at-bol 2)) - (goto-char tbeg) - (setq beg (point-at-bol 1))) - (goto-char pos) - (if (re-search-forward org-table-hline-regexp tend t) - (setq end (point-at-bol 1)) - (goto-char tend) - (setq end (point-at-bol)))) - (setq beg (move-marker (make-marker) beg) - end (move-marker (make-marker) end)) - (untabify beg end) - (goto-char beg) - (org-table-goto-column column) - (skip-chars-backward "^|") - (setq bcol (current-column)) - (org-table-goto-column (1+ column)) - (skip-chars-backward "^|") - (setq ecol (1- (current-column))) - (org-table-goto-column column) - (setq lns (mapcar (lambda(x) (cons - (org-sort-remove-invisible - (nth (1- column) - (org-split-string x "[ \t]*|[ \t]*"))) - x)) - (org-split-string (buffer-substring beg end) "\n"))) - (setq lns (org-do-sort lns "Table" with-case sorting-type)) - (when org-table-overlay-coordinates - (org-table-toggle-coordinate-overlays)) - (delete-region beg end) - (move-marker beg nil) - (move-marker end nil) - (insert (mapconcat 'cdr lns "\n") "\n") - (org-goto-line thisline) - (org-table-goto-column thiscol) - (when otc (org-table-toggle-coordinate-overlays)) - (message "%d lines sorted, based on column %d" (length lns) column))) +any of (?a ?A ?n ?N ?t ?T ?f ?F) where the capital letters indicate that +sorting should be done in reverse order. + +If the SORTING-TYPE is ?f or ?F, then GETKEY-FUNC specifies +a function to be called to extract the key. It must return a value +that is compatible with COMPARE-FUNC, the function used to compare +entries. + +A non-nil value for INTERACTIVE? is used to signal that this +function is being called interactively." + (interactive (list current-prefix-arg nil nil nil t)) + (when (org-region-active-p) (goto-char (region-beginning))) + ;; Point must be either within a field or before a data line. + (save-excursion + (skip-chars-backward " \t") + (when (bolp) (search-forward "|" (line-end-position) t)) + (org-table-check-inside-data-field)) + ;; Set appropriate case sensitivity and column used for sorting. + (let ((column (let ((c (org-table-current-column))) + (cond ((> c 0) c) + (interactive? + (read-number "Use column N for sorting: ")) + (t 1)))) + (sorting-type + (or sorting-type + (read-char-exclusive "Sort Table: [a]lphabetic, [n]umeric, \ +\[t]ime, [f]unc. A/N/T/F means reversed: ")))) + (save-restriction + ;; Narrow buffer to appropriate sorting area. + (if (org-region-active-p) + (progn (goto-char (region-beginning)) + (narrow-to-region + (point) + (save-excursion (goto-char (region-end)) + (line-beginning-position 2)))) + (let ((start (org-table-begin)) + (end (org-table-end))) + (narrow-to-region + (save-excursion + (if (re-search-backward org-table-hline-regexp start t) + (line-beginning-position 2) + start)) + (if (save-excursion (re-search-forward org-table-hline-regexp end t)) + (match-beginning 0) + end)))) + ;; Determine arguments for `sort-subr'. Also record original + ;; position. `org-table-save-field' cannot help here since + ;; sorting is too much destructive. + (let* ((sort-fold-case (not with-case)) + (coordinates + (cons (count-lines (point-min) (line-beginning-position)) + (current-column))) + (extract-key-from-field + ;; Function to be called on the contents of the field + ;; used for sorting in the current row. + (cl-case sorting-type + ((?n ?N) #'string-to-number) + ((?a ?A) #'org-sort-remove-invisible) + ((?t ?T) + (lambda (f) + (cond ((string-match org-ts-regexp-both f) + (float-time + (org-time-string-to-time (match-string 0 f)))) + ((org-duration-p f) (org-duration-to-minutes f)) + ((string-match "\\<[0-9]+:[0-9]\\{2\\}\\>" f) + (org-duration-to-minutes (match-string 0 f))) + (t 0)))) + ((?f ?F) + (or getkey-func + (and interactive? + (org-read-function "Function for extracting keys: ")) + (error "Missing key extractor to sort rows"))) + (t (user-error "Invalid sorting type `%c'" sorting-type)))) + (predicate + (cl-case sorting-type + ((?n ?N ?t ?T) #'<) + ((?a ?A) #'string<) + ((?f ?F) + (or compare-func + (and interactive? + (org-read-function + (concat "Function for comparing keys " + "(empty for default `sort-subr' predicate): ") + 'allow-empty))))))) + (goto-char (point-min)) + (sort-subr (memq sorting-type '(?A ?N ?T ?F)) + (lambda () + (forward-line) + (while (and (not (eobp)) + (not (looking-at org-table-dataline-regexp))) + (forward-line))) + #'end-of-line + (lambda () + (funcall extract-key-from-field + (org-trim (org-table-get-field column)))) + nil + predicate) + ;; Move back to initial field. + (forward-line (car coordinates)) + (move-to-column (cdr coordinates)))))) ;;;###autoload (defun org-table-cut-region (beg end) @@ -1725,34 +1794,31 @@ with `org-table-paste-rectangle'." (if (org-region-active-p) (region-beginning) (point)) (if (org-region-active-p) (region-end) (point)) current-prefix-arg)) - (let* (l01 c01 l02 c02 l1 c1 l2 c2 ic1 ic2 - region cols - (rpl (if cut " " nil))) - (goto-char beg) - (org-table-check-inside-data-field) - (setq l01 (org-current-line) - c01 (org-table-current-column)) - (goto-char end) + (goto-char (min beg end)) + (org-table-check-inside-data-field) + (let ((beg (line-beginning-position)) + (c01 (org-table-current-column)) + region) + (goto-char (max beg end)) (org-table-check-inside-data-field) - (setq l02 (org-current-line) - c02 (org-table-current-column)) - (setq l1 (min l01 l02) l2 (max l01 l02) - c1 (min c01 c02) c2 (max c01 c02)) - (catch 'exit - (while t - (catch 'nextline - (if (> l1 l2) (throw 'exit t)) - (org-goto-line l1) - (if (org-at-table-hline-p) (throw 'nextline (setq l1 (1+ l1)))) - (setq cols nil ic1 c1 ic2 c2) - (while (< ic1 (1+ ic2)) - (push (org-table-get-field ic1 rpl) cols) - (setq ic1 (1+ ic1))) - (push (nreverse cols) region) - (setq l1 (1+ l1))))) - (setq org-table-clip (nreverse region)) - (if cut (org-table-align)) - org-table-clip)) + (let* ((end (copy-marker (line-end-position))) + (c02 (org-table-current-column)) + (column-start (min c01 c02)) + (column-end (max c01 c02)) + (column-number (1+ (- column-end column-start))) + (rpl (and cut " "))) + (goto-char beg) + (while (< (point) end) + (unless (org-at-table-hline-p) + ;; Collect every cell between COLUMN-START and COLUMN-END. + (let (cols) + (dotimes (c column-number) + (push (org-table-get-field (+ c column-start) rpl) cols)) + (push (nreverse cols) region))) + (forward-line)) + (set-marker end nil)) + (when cut (org-table-align)) + (setq org-table-clip (nreverse region)))) ;;;###autoload (defun org-table-paste-rectangle () @@ -1762,45 +1828,42 @@ will be overwritten. If the rectangle does not fit into the present table, the table is enlarged as needed. The process ignores horizontal separator lines." (interactive) - (unless (and org-table-clip (listp org-table-clip)) + (unless (consp org-table-clip) (user-error "First cut/copy a region to paste!")) (org-table-check-inside-data-field) - (let* ((clip org-table-clip) - (line (org-current-line)) - (col (org-table-current-column)) - (org-enable-table-editor t) - (org-table-automatic-realign nil) - c cols field) - (while (setq cols (pop clip)) - (while (org-at-table-hline-p) (beginning-of-line 2)) - (if (not (org-at-table-p)) - (progn (end-of-line 0) (org-table-next-field))) - (setq c col) - (while (setq field (pop cols)) - (org-table-goto-column c nil 'force) - (org-table-get-field nil field) - (setq c (1+ c))) - (beginning-of-line 2)) - (org-goto-line line) - (org-table-goto-column col) + (let* ((column (org-table-current-column)) + (org-table-automatic-realign nil)) + (org-table-save-field + (dolist (row org-table-clip) + (while (org-at-table-hline-p) (forward-line)) + ;; If we left the table, create a new row. + (when (and (bolp) (not (looking-at "[ \t]*|"))) + (end-of-line 0) + (org-table-next-field)) + (let ((c column)) + (dolist (field row) + (org-table-goto-column c nil 'force) + (org-table-get-field nil field) + (cl-incf c))) + (forward-line))) (org-table-align))) ;;;###autoload (defun org-table-convert () "Convert from `org-mode' table to table.el and back. -Obviously, this only works within limits. When an Org-mode table is -converted to table.el, all horizontal separator lines get lost, because -table.el uses these as cell boundaries and has no notion of horizontal lines. -A table.el table can be converted to an Org-mode table only if it does not -do row or column spanning. Multiline cells will become multiple cells. -Beware, Org-mode does not test if the table can be successfully converted - it -blindly applies a recipe that works for simple tables." +Obviously, this only works within limits. When an Org table is converted +to table.el, all horizontal separator lines get lost, because table.el uses +these as cell boundaries and has no notion of horizontal lines. A table.el +table can be converted to an Org table only if it does not do row or column +spanning. Multiline cells will become multiple cells. Beware, Org mode +does not test if the table can be successfully converted - it blindly +applies a recipe that works for simple tables." (interactive) (require 'table) (if (org-at-table.el-p) - ;; convert to Org-mode table - (let ((beg (move-marker (make-marker) (org-table-begin t))) - (end (move-marker (make-marker) (org-table-end t)))) + ;; convert to Org table + (let ((beg (copy-marker (org-table-begin t))) + (end (copy-marker (org-table-end t)))) (table-unrecognize-region beg end) (goto-char beg) (while (re-search-forward "^\\([ \t]*\\)\\+-.*\n" end t) @@ -1808,8 +1871,8 @@ blindly applies a recipe that works for simple tables." (goto-char beg)) (if (org-at-table-p) ;; convert to table.el table - (let ((beg (move-marker (make-marker) (org-table-begin))) - (end (move-marker (make-marker) (org-table-end)))) + (let ((beg (copy-marker (org-table-begin))) + (end (copy-marker (org-table-end)))) ;; first, get rid of all horizontal lines (goto-char beg) (while (re-search-forward "^\\([ \t]*\\)|-.*\n" end t) @@ -1832,7 +1895,7 @@ blindly applies a recipe that works for simple tables." (goto-char beg))))) (defun org-table-transpose-table-at-point () - "Transpose orgmode table at point and eliminate hlines. + "Transpose Org table at point and eliminate hlines. So a table like | 1 | 2 | 4 | 5 | @@ -1847,22 +1910,31 @@ will be transposed as | 4 | c | g | | 5 | d | h | -Note that horizontal lines disappeared." +Note that horizontal lines disappear." (interactive) (let* ((table (delete 'hline (org-table-to-lisp))) - (contents (mapcar (lambda (p) + (dline_old (org-table-current-line)) + (col_old (org-table-current-column)) + (contents (mapcar (lambda (_) (let ((tp table)) (mapcar - (lambda (rown) + (lambda (_) (prog1 (pop (car tp)) (setq tp (cdr tp)))) table))) (car table)))) - (delete-region (org-table-begin) (org-table-end)) - (insert (mapconcat (lambda(x) (concat "| " (mapconcat 'identity x " | " ) " |\n" )) - contents "")) - (org-table-align))) + (goto-char (org-table-begin)) + (re-search-forward "|") + (backward-char) + (delete-region (point) (org-table-end)) + (insert (mapconcat + (lambda(x) + (concat "| " (mapconcat 'identity x " | " ) " |\n" )) + contents "")) + (org-table-goto-line col_old) + (org-table-goto-column dline_old)) + (org-table-align)) ;;;###autoload (defun org-table-wrap-region (arg) @@ -1873,7 +1945,8 @@ lines, in order to keep the table compact. If there is an active region, and both point and mark are in the same column, the text in the column is wrapped to minimum width for the given number of lines. Generally, this makes the table more compact. A prefix ARG may be -used to change the number of desired lines. For example, `C-2 \\[org-table-wrap]' +used to change the number of desired lines. For example, \ +`C-2 \\[org-table-wrap-region]' formats the selected text to two lines. If the region was longer than two lines, the remaining lines remain empty. A negative prefix argument reduces the current number of lines by that amount. The wrapped text is pasted back @@ -1890,48 +1963,43 @@ blank, and the content is appended to the field above." (interactive "P") (org-table-check-inside-data-field) (if (org-region-active-p) - ;; There is a region: fill as a paragraph - (let* ((beg (region-beginning)) - (cline (save-excursion (goto-char beg) (org-current-line))) - (ccol (save-excursion (goto-char beg) (org-table-current-column))) - nlines) + ;; There is a region: fill as a paragraph. + (let ((start (region-beginning))) (org-table-cut-region (region-beginning) (region-end)) - (if (> (length (car org-table-clip)) 1) - (user-error "Region must be limited to single column")) - (setq nlines (if arg - (if (< arg 1) - (+ (length org-table-clip) arg) - arg) - (length org-table-clip))) - (setq org-table-clip - (mapcar 'list (org-wrap (mapconcat 'car org-table-clip " ") - nil nlines))) - (org-goto-line cline) - (org-table-goto-column ccol) + (when (> (length (car org-table-clip)) 1) + (user-error "Region must be limited to single column")) + (let ((nlines (cond ((not arg) (length org-table-clip)) + ((< arg 1) (+ (length org-table-clip) arg)) + (t arg)))) + (setq org-table-clip + (mapcar #'list + (org-wrap (mapconcat #'car org-table-clip " ") + nil + nlines)))) + (goto-char start) (org-table-paste-rectangle)) - ;; No region, split the current field at point + ;; No region, split the current field at point. (unless (org-get-alist-option org-M-RET-may-split-line 'table) (skip-chars-forward "^\r\n|")) - (if arg - ;; combine with field above - (let ((s (org-table-blank-field)) - (col (org-table-current-column))) - (beginning-of-line 0) - (while (org-at-table-hline-p) (beginning-of-line 0)) - (org-table-goto-column col) - (skip-chars-forward "^|") - (skip-chars-backward " ") - (insert " " (org-trim s)) - (org-table-align)) - ;; split field - (if (looking-at "\\([^|]+\\)+|") - (let ((s (match-string 1))) - (replace-match " |") - (goto-char (match-beginning 0)) - (org-table-next-row) - (insert (org-trim s) " ") - (org-table-align)) - (org-table-next-row))))) + (cond + (arg ; Combine with field above. + (let ((s (org-table-blank-field)) + (col (org-table-current-column))) + (forward-line -1) + (while (org-at-table-hline-p) (forward-line -1)) + (org-table-goto-column col) + (skip-chars-forward "^|") + (skip-chars-backward " ") + (insert " " (org-trim s)) + (org-table-align))) + ((looking-at "\\([^|]+\\)+|") ; Split field. + (let ((s (match-string 1))) + (replace-match " |") + (goto-char (match-beginning 0)) + (org-table-next-row) + (insert (org-trim s) " ") + (org-table-align))) + (t (org-table-next-row))))) (defvar org-field-marker nil) @@ -1939,9 +2007,14 @@ blank, and the content is appended to the field above." (defun org-table-edit-field (arg) "Edit table field in a different window. This is mainly useful for fields that contain hidden parts. -When called with a \\[universal-argument] prefix, just make the full field visible so that -it can be edited in place." + +When called with a `\\[universal-argument]' prefix, just make the full field +visible so that it can be edited in place. + +When called with a `\\[universal-argument] \\[universal-argument]' prefix, \ +toggle `org-table-follow-field-mode'." (interactive "P") + (unless (org-at-table-p) (user-error "Not at a table")) (cond ((equal arg '(16)) (org-table-follow-field-mode (if org-table-follow-field-mode -1 1))) @@ -1980,9 +2053,9 @@ it can be edited in place." '(invisible t org-cwidth t display t intangible t)) (goto-char p) - (org-set-local 'org-finish-function 'org-table-finish-edit-field) - (org-set-local 'org-window-configuration cw) - (org-set-local 'org-field-marker pos) + (setq-local org-finish-function 'org-table-finish-edit-field) + (setq-local org-window-configuration cw) + (setq-local org-field-marker pos) (message "Edit and finish with C-c C-c"))))) (defun org-table-finish-edit-field () @@ -2015,8 +2088,8 @@ current field. The mode exits automatically when the cursor leaves the table (but see `org-table-exit-follow-field-mode-when-leaving-table')." nil " TblFollow" nil (if org-table-follow-field-mode - (org-add-hook 'post-command-hook 'org-table-follow-fields-with-editor - 'append 'local) + (add-hook 'post-command-hook 'org-table-follow-fields-with-editor + 'append 'local) (remove-hook 'post-command-hook 'org-table-follow-fields-with-editor 'local) (let* ((buf (get-buffer "*Org Table Edit Field*")) (win (and buf (get-buffer-window buf)))) @@ -2091,11 +2164,10 @@ If NLAST is a number, only the NLAST fields will actually be summed." s diff) (format "%.0f:%02.0f:%02.0f" h m s)))) (kill-new sres) - (if (org-called-interactively-p 'interactive) - (message "%s" - (substitute-command-keys - (format "Sum of %d items: %-20s (\\[yank] will insert result into buffer)" - (length numbers) sres)))) + (when (called-interactively-p 'interactive) + (message "%s" (substitute-command-keys + (format "Sum of %d items: %-20s \ +\(\\[yank] will insert result into buffer)" (length numbers) sres)))) sres)))) (defun org-table-get-number-for-summing (s) @@ -2120,57 +2192,58 @@ If NLAST is a number, only the NLAST fields will actually be summed." (defun org-table-current-field-formula (&optional key noerror) "Return the formula active for the current field. -Assumes that specials are in place. -If KEY is given, return the key to this formula. -Otherwise return the formula preceded with \"=\" or \":=\"." - (let* ((name (car (rassoc (list (org-current-line) - (org-table-current-column)) - org-table-named-field-locations))) - (col (org-table-current-column)) - (scol (int-to-string col)) - (ref (format "@%d$%d" (org-table-current-dline) col)) - (stored-list (org-table-get-stored-formulas noerror)) - (ass (or (assoc name stored-list) - (assoc ref stored-list) - (assoc scol stored-list)))) - (if key - (car ass) - (if ass (concat (if (string-match "^[0-9]+$" (car ass)) "=" ":=") - (cdr ass)))))) + +Assumes that table is already analyzed. If KEY is given, return +the key to this formula. Otherwise return the formula preceded +with \"=\" or \":=\"." + (let* ((line (count-lines org-table-current-begin-pos + (line-beginning-position))) + (row (org-table-line-to-dline line))) + (cond + (row + (let* ((col (org-table-current-column)) + (name (car (rassoc (list line col) + org-table-named-field-locations))) + (scol (format "$%d" col)) + (ref (format "@%d$%d" (org-table-current-dline) col)) + (stored-list (org-table-get-stored-formulas noerror)) + (ass (or (assoc name stored-list) + (assoc ref stored-list) + (assoc scol stored-list)))) + (cond (key (car ass)) + (ass (concat (if (string-match-p "^[0-9]+$" (car ass)) "=" ":=") + (cdr ass)))))) + (noerror nil) + (t (error "No formula active for the current field"))))) (defun org-table-get-formula (&optional equation named) "Read a formula from the minibuffer, offer stored formula as default. When NAMED is non-nil, look for a named equation." (let* ((stored-list (org-table-get-stored-formulas)) - (name (car (rassoc (list (org-current-line) + (name (car (rassoc (list (count-lines org-table-current-begin-pos + (line-beginning-position)) (org-table-current-column)) org-table-named-field-locations))) - (ref (format "@%d$%d" (org-table-current-dline) + (ref (format "@%d$%d" + (org-table-current-dline) (org-table-current-column))) - (refass (assoc ref stored-list)) - (nameass (assoc name stored-list)) - (scol (if named - (if (and name (not (string-match "^LR[0-9]+$" name))) - name - ref) - (int-to-string (org-table-current-column)))) - (dummy (and (or nameass refass) (not named) - (not (y-or-n-p "Replace existing field formula with column formula? " )) - (message "Formula not replaced"))) + (scol (cond + ((not named) (format "$%d" (org-table-current-column))) + ((and name (not (string-match "\\`LR[0-9]+\\'" name))) name) + (t ref))) (name (or name ref)) (org-table-may-need-update nil) (stored (cdr (assoc scol stored-list))) (eq (cond - ((and stored equation (string-match "^ *=? *$" equation)) + ((and stored equation (string-match-p "^ *=? *$" equation)) stored) ((stringp equation) equation) (t (org-table-formula-from-user (read-string (org-table-formula-to-user - (format "%s formula %s%s=" + (format "%s formula %s=" (if named "Field" "Column") - (if (member (string-to-char scol) '(?$ ?@)) "" "$") scol)) (if stored (org-table-formula-to-user stored) "") 'org-table-formula-history @@ -2194,25 +2267,27 @@ When NAMED is non-nil, look for a named equation." (org-table-store-formulas stored-list)) eq)) -(defun org-table-store-formulas (alist) - "Store the list of formulas below the current table." - (setq alist (sort alist 'org-table-formula-less-p)) - (let ((case-fold-search t)) - (save-excursion - (goto-char (org-table-end)) - (if (looking-at "\\([ \t]*\n\\)*[ \t]*\\(#\\+tblfm:\\)\\(.*\n?\\)") +(defun org-table-store-formulas (alist &optional location) + "Store the list of formulas below the current table. +If optional argument LOCATION is a buffer position, insert it at +LOCATION instead." + (save-excursion + (if location + (progn (goto-char location) (beginning-of-line)) + (goto-char (org-table-end))) + (let ((case-fold-search t)) + (if (looking-at "\\([ \t]*\n\\)*[ \t]*\\(#\\+TBLFM:\\)\\(.*\n?\\)") (progn - ;; don't overwrite TBLFM, we might use text properties to store stuff + ;; Don't overwrite TBLFM, we might use text properties to + ;; store stuff. (goto-char (match-beginning 3)) (delete-region (match-beginning 3) (match-end 0))) (org-indent-line) (insert (or (match-string 2) "#+TBLFM:"))) (insert " " - (mapconcat (lambda (x) - (concat - (if (equal (string-to-char (car x)) ?@) "" "$") - (car x) "=" (cdr x))) - alist "::") + (mapconcat (lambda (x) (concat (car x) "=" (cdr x))) + (sort alist #'org-table-formula-less-p) + "::") "\n")))) (defsubst org-table-formula-make-cmp-string (a) @@ -2241,33 +2316,47 @@ When NAMED is non-nil, look for a named equation." (and as bs (string< as bs)))) ;;;###autoload -(defun org-table-get-stored-formulas (&optional noerror) - "Return an alist with the stored formulas directly after current table." - (interactive) ;; FIXME interactive? - (let ((case-fold-search t) scol eq eq-alist strings string seen) - (save-excursion - (goto-char (org-table-end)) - (when (looking-at "\\([ \t]*\n\\)*[ \t]*#\\+tblfm: *\\(.*\\)") - (setq strings (org-split-string (org-match-string-no-properties 2) - " *:: *")) - (while (setq string (pop strings)) - (when (string-match "\\`\\(@[-+I<>0-9.$@]+\\|@?[0-9]+\\|\\$\\([a-zA-Z0-9]+\\|[<>]+\\)\\) *= *\\(.*[^ \t]\\)" string) - (setq scol (if (match-end 2) - (match-string 2 string) - (match-string 1 string)) - scol (if (member (string-to-char scol) '(?< ?>)) - (concat "$" scol) scol) - eq (match-string 3 string) - eq-alist (cons (cons scol eq) eq-alist)) - (if (member scol seen) - (if noerror - (progn - (message "Double definition `$%s=' in TBLFM line, please fix by hand" scol) - (ding) - (sit-for 2)) - (user-error "Double definition `$%s=' in TBLFM line, please fix by hand" scol)) - (push scol seen)))))) - (nreverse eq-alist))) +(defun org-table-get-stored-formulas (&optional noerror location) + "Return an alist with the stored formulas directly after current table. +By default, only return active formulas, i.e., formulas located +on the first line after the table. However, if optional argument +LOCATION is a buffer position, consider the formulas there." + (save-excursion + (if location + (progn (goto-char location) (beginning-of-line)) + (goto-char (org-table-end))) + (let ((case-fold-search t)) + (when (looking-at "\\([ \t]*\n\\)*[ \t]*#\\+TBLFM: *\\(.*\\)") + (let ((strings (org-split-string (match-string-no-properties 2) + " *:: *")) + eq-alist seen) + (dolist (string strings (nreverse eq-alist)) + (when (string-match "\\`\\(@[-+I<>0-9.$@]+\\|\\$\\([_a-zA-Z0-9]+\\|\ +[<>]+\\)\\) *= *\\(.*[^ \t]\\)" + string) + (let ((lhs + (let ((m (match-string 1 string))) + (cond + ((not (match-end 2)) m) + ;; Is it a column reference? + ((string-match-p "\\`$\\([0-9]+\\|[<>]+\\)\\'" m) m) + ;; Since named columns are not possible in + ;; LHS, assume this is a named field. + (t (match-string 2 string))))) + (rhs (match-string 3 string))) + (push (cons lhs rhs) eq-alist) + (cond + ((not (member lhs seen)) (push lhs seen)) + (noerror + (message + "Double definition `%s=' in TBLFM line, please fix by hand" + lhs) + (ding) + (sit-for 2)) + (t + (user-error + "Double definition `%s=' in TBLFM line, please fix by hand" + lhs))))))))))) (defun org-table-fix-formulas (key replace &optional limit delta remove) "Modify the equations after the table structure has been edited. @@ -2305,83 +2394,6 @@ For all numbers larger than LIMIT, shift them by DELTA." (message msg)))))) (forward-line)))) -(defun org-table-get-specials () - "Get the column names and local parameters for this table." - (save-excursion - (let ((beg (org-table-begin)) (end (org-table-end)) - names name fields fields1 field cnt - c v l line col types dlines hlines last-dline) - (setq org-table-column-names nil - org-table-local-parameters nil - org-table-named-field-locations nil - org-table-current-begin-line nil - org-table-current-begin-pos nil - org-table-current-line-types nil - org-table-current-ncol 0) - (goto-char beg) - (when (re-search-forward "^[ \t]*| *! *\\(|.*\\)" end t) - (setq names (org-split-string (match-string 1) " *| *") - cnt 1) - (while (setq name (pop names)) - (setq cnt (1+ cnt)) - (if (string-match "^[a-zA-Z][_a-zA-Z0-9]*$" name) - (push (cons name (int-to-string cnt)) org-table-column-names)))) - (setq org-table-column-names (nreverse org-table-column-names)) - (setq org-table-column-name-regexp - (concat "\\$\\(" (mapconcat 'car org-table-column-names "\\|") "\\)\\>")) - (goto-char beg) - (while (re-search-forward "^[ \t]*| *\\$ *\\(|.*\\)" end t) - (setq fields (org-split-string (match-string 1) " *| *")) - (while (setq field (pop fields)) - (if (string-match "^\\([a-zA-Z][_a-zA-Z0-9]*\\|%\\) *= *\\(.*\\)" field) - (push (cons (match-string 1 field) (match-string 2 field)) - org-table-local-parameters)))) - (goto-char beg) - (while (re-search-forward "^[ \t]*| *\\([_^]\\) *\\(|.*\\)" end t) - (setq c (match-string 1) - fields (org-split-string (match-string 2) " *| *")) - (save-excursion - (beginning-of-line (if (equal c "_") 2 0)) - (setq line (org-current-line) col 1) - (and (looking-at "^[ \t]*|[^|]*\\(|.*\\)") - (setq fields1 (org-split-string (match-string 1) " *| *")))) - (while (and fields1 (setq field (pop fields))) - (setq v (pop fields1) col (1+ col)) - (when (and (stringp field) (stringp v) - (string-match "^[a-zA-Z][_a-zA-Z0-9]*$" field)) - (push (cons field v) org-table-local-parameters) - (push (list field line col) org-table-named-field-locations)))) - ;; Analyze the line types. - (goto-char beg) - (setq org-table-current-begin-line (org-current-line) - org-table-current-begin-pos (point) - l org-table-current-begin-line) - (while (looking-at "[ \t]*|\\(-\\)?") - (push (if (match-end 1) 'hline 'dline) types) - (if (match-end 1) (push l hlines) (push l dlines)) - (beginning-of-line 2) - (setq l (1+ l))) - (push 'hline types) ;; add an imaginary extra hline to the end - (setq org-table-current-line-types (apply 'vector (nreverse types)) - last-dline (car dlines) - org-table-dlines (apply 'vector (cons nil (nreverse dlines))) - org-table-hlines (apply 'vector (cons nil (nreverse hlines)))) - (org-goto-line last-dline) - (let* ((l last-dline) - (fields (org-split-string - (buffer-substring (point-at-bol) (point-at-eol)) - "[ \t]*|[ \t]*")) - (nfields (length fields)) - al al2) - (setq org-table-current-ncol nfields) - (loop for i from 1 to nfields do - (push (list (format "LR%d" i) l i) al) - (push (cons (format "LR%d" i) (nth (1- i) fields)) al2)) - (setq org-table-named-field-locations - (append org-table-named-field-locations al)) - (setq org-table-local-parameters - (append org-table-local-parameters al2)))))) - ;;;###autoload (defun org-table-maybe-eval-formula () "Check if the current field starts with \"=\" or \":=\". @@ -2394,11 +2406,8 @@ If yes, store the formula and apply it." (when (string-match "^:?=\\(.*[^=]\\)$" field) (setq named (equal (string-to-char field) ?:) eq (match-string 1 field)) - (if (or (fboundp 'calc-eval) - (equal (substring eq 0 (min 2 (length eq))) "'(")) - (org-table-eval-formula (if named '(4) nil) - (org-table-formula-from-user eq)) - (user-error "Calc does not seem to be installed, and is needed to evaluate the formula")))))) + (org-table-eval-formula (and named '(4)) + (org-table-formula-from-user eq)))))) (defvar org-recalc-commands nil "List of commands triggering the recalculation of a line. @@ -2424,56 +2433,199 @@ After each change, a message will be displayed indicating the meaning of the new mark." (interactive) (unless (org-at-table-p) (user-error "Not at a table")) - (let* ((marks (append (mapcar 'car org-recalc-marks) '(" "))) - (beg (org-table-begin)) - (end (org-table-end)) - (l (org-current-line)) - (l1 (if (org-region-active-p) (org-current-line (region-beginning)))) - (l2 (if (org-region-active-p) (org-current-line (region-end)))) - (have-col - (save-excursion - (goto-char beg) - (not (re-search-forward "^[ \t]*|[^-|][^|]*[^#!$*_^| \t][^|]*|" end t)))) + (let* ((region (org-region-active-p)) + (l1 (and region + (save-excursion (goto-char (region-beginning)) + (copy-marker (line-beginning-position))))) + (l2 (and region + (save-excursion (goto-char (region-end)) + (copy-marker (line-beginning-position))))) + (l (copy-marker (line-beginning-position))) (col (org-table-current-column)) - (forcenew (car (assoc newchar org-recalc-marks))) - epos new) - (when l1 - (message "Change region to what mark? Type # * ! $ or SPC: ") - (setq newchar (char-to-string (read-char-exclusive)) - forcenew (car (assoc newchar org-recalc-marks)))) - (if (and newchar (not forcenew)) - (user-error "Invalid NEWCHAR `%s' in `org-table-rotate-recalc-marks'" - newchar)) - (if l1 (org-goto-line l1)) + (newchar (if region + (char-to-string + (read-char-exclusive + "Change region to what mark? Type # * ! $ or SPC: ")) + newchar)) + (no-special-column + (save-excursion + (goto-char (org-table-begin)) + (re-search-forward + "^[ \t]*|[^-|][^|]*[^#!$*_^| \t][^|]*|" (org-table-end) t)))) + (when (and newchar (not (assoc newchar org-recalc-marks))) + (user-error "Invalid character `%s' in `org-table-rotate-recalc-marks'" + newchar)) + (when l1 (goto-char l1)) (save-excursion - (beginning-of-line 1) + (beginning-of-line) (unless (looking-at org-table-dataline-regexp) (user-error "Not at a table data line"))) - (unless have-col + (when no-special-column (org-table-goto-column 1) - (org-table-insert-column) - (org-table-goto-column (1+ col))) - (setq epos (point-at-eol)) + (org-table-insert-column)) + (let ((previous-line-end (line-end-position)) + (newchar + (save-excursion + (beginning-of-line) + (cond ((not (looking-at "^[ \t]*| *\\([#!$*^_ ]\\) *|")) "#") + (newchar) + (t (cadr (member (match-string 1) + (append (mapcar #'car org-recalc-marks) + '(" "))))))))) + ;; Rotate mark in first row. + (org-table-get-field 1 (format " %s " newchar)) + ;; Rotate marks in additional rows if a region is active. + (when region + (save-excursion + (forward-line) + (while (<= (point) l2) + (when (looking-at org-table-dataline-regexp) + (org-table-get-field 1 (format " %s " newchar))) + (forward-line)))) + ;; Only align if rotation actually changed lines' length. + (when (/= previous-line-end (line-end-position)) (org-table-align))) + (goto-char l) + (org-table-goto-column (if no-special-column (1+ col) col)) + (when l1 (set-marker l1 nil)) + (when l2 (set-marker l2 nil)) + (set-marker l nil) + (when (called-interactively-p 'interactive) + (message "%s" (cdr (assoc newchar org-recalc-marks)))))) + +;;;###autoload +(defun org-table-analyze () + "Analyze table at point and store results. + +This function sets up the following dynamically scoped variables: + + `org-table-column-name-regexp', + `org-table-column-names', + `org-table-current-begin-pos', + `org-table-current-line-types', + `org-table-current-ncol', + `org-table-dlines', + `org-table-hlines', + `org-table-local-parameters', + `org-table-named-field-locations'." + (let ((beg (org-table-begin)) + (end (org-table-end))) (save-excursion - (beginning-of-line 1) - (org-table-get-field - 1 (if (looking-at "^[ \t]*| *\\([#!$*^_ ]\\) *|") - (concat " " - (setq new (or forcenew - (cadr (member (match-string 1) marks)))) - " ") - " # "))) - (if (and l1 l2) - (progn - (org-goto-line l1) - (while (progn (beginning-of-line 2) (not (= (org-current-line) l2))) - (and (looking-at org-table-dataline-regexp) - (org-table-get-field 1 (concat " " new " ")))) - (org-goto-line l1))) - (if (not (= epos (point-at-eol))) (org-table-align)) - (org-goto-line l) - (and (org-called-interactively-p 'interactive) - (message "%s" (cdr (assoc new org-recalc-marks)))))) + (goto-char beg) + ;; Extract column names. + (setq org-table-column-names nil) + (when (save-excursion + (re-search-forward "^[ \t]*| *! *\\(|.*\\)" end t)) + (let ((c 1)) + (dolist (name (org-split-string (match-string 1) " *| *")) + (cl-incf c) + (when (string-match "\\`[a-zA-Z][_a-zA-Z0-9]*\\'" name) + (push (cons name (int-to-string c)) org-table-column-names))))) + (setq org-table-column-names (nreverse org-table-column-names)) + (setq org-table-column-name-regexp + (format "\\$\\(%s\\)\\>" + (regexp-opt (mapcar #'car org-table-column-names) t))) + ;; Extract local parameters. + (setq org-table-local-parameters nil) + (save-excursion + (while (re-search-forward "^[ \t]*| *\\$ *\\(|.*\\)" end t) + (dolist (field (org-split-string (match-string 1) " *| *")) + (when (string-match + "\\`\\([a-zA-Z][_a-zA-Z0-9]*\\|%\\) *= *\\(.*\\)" field) + (push (cons (match-string 1 field) (match-string 2 field)) + org-table-local-parameters))))) + ;; Update named fields locations. We minimize `count-lines' + ;; processing by storing last known number of lines in LAST. + (setq org-table-named-field-locations nil) + (save-excursion + (let ((last (cons (point) 0))) + (while (re-search-forward "^[ \t]*| *\\([_^]\\) *\\(|.*\\)" end t) + (let ((c (match-string 1)) + (fields (org-split-string (match-string 2) " *| *"))) + (save-excursion + (forward-line (if (equal c "_") 1 -1)) + (let ((fields1 + (and (looking-at "^[ \t]*|[^|]*\\(|.*\\)") + (org-split-string (match-string 1) " *| *"))) + (line (cl-incf (cdr last) (count-lines (car last) (point)))) + (col 1)) + (setcar last (point)) ; Update last known position. + (while (and fields fields1) + (let ((field (pop fields)) + (v (pop fields1))) + (cl-incf col) + (when (and (stringp field) + (stringp v) + (string-match "\\`[a-zA-Z][_a-zA-Z0-9]*\\'" + field)) + (push (cons field v) org-table-local-parameters) + (push (list field line col) + org-table-named-field-locations)))))))))) + ;; Re-use existing markers when possible. + (if (markerp org-table-current-begin-pos) + (move-marker org-table-current-begin-pos (point)) + (setq org-table-current-begin-pos (point-marker))) + ;; Analyze the line types. + (let ((l 0) hlines dlines types) + (while (looking-at "[ \t]*|\\(-\\)?") + (push (if (match-end 1) 'hline 'dline) types) + (if (match-end 1) (push l hlines) (push l dlines)) + (forward-line) + (cl-incf l)) + (push 'hline types) ; Add an imaginary extra hline to the end. + (setq org-table-current-line-types (apply #'vector (nreverse types))) + (setq org-table-dlines (apply #'vector (cons nil (nreverse dlines)))) + (setq org-table-hlines (apply #'vector (cons nil (nreverse hlines))))) + ;; Get the number of columns from the first data line in table. + (goto-char beg) + (forward-line (aref org-table-dlines 1)) + (let* ((fields + (org-split-string + (buffer-substring (line-beginning-position) (line-end-position)) + "[ \t]*|[ \t]*")) + (nfields (length fields)) + al al2) + (setq org-table-current-ncol nfields) + (let ((last-dline + (aref org-table-dlines (1- (length org-table-dlines))))) + (dotimes (i nfields) + (let ((column (1+ i))) + (push (list (format "LR%d" column) last-dline column) al) + (push (cons (format "LR%d" column) (nth i fields)) al2)))) + (setq org-table-named-field-locations + (append org-table-named-field-locations al)) + (setq org-table-local-parameters + (append org-table-local-parameters al2)))))) + +(defun org-table-goto-field (ref &optional create-column-p) + "Move point to a specific field in the current table. + +REF is either the name of a field its absolute reference, as +a string. No column is created unless CREATE-COLUMN-P is +non-nil. If it is a function, it is called with the column +number as its argument as is used as a predicate to know if the +column can be created. + +This function assumes the table is already analyzed (i.e., using +`org-table-analyze')." + (let* ((coordinates + (cond + ((cdr (assoc ref org-table-named-field-locations))) + ((string-match "\\`@\\([1-9][0-9]*\\)\\$\\([1-9][0-9]*\\)\\'" ref) + (list (condition-case nil + (aref org-table-dlines + (string-to-number (match-string 1 ref))) + (error (user-error "Invalid row number in %s" ref))) + (string-to-number (match-string 2 ref)))) + (t (user-error "Unknown field: %s" ref)))) + (line (car coordinates)) + (column (nth 1 coordinates)) + (create-new-column (if (functionp create-column-p) + (funcall create-column-p column) + create-column-p))) + (when coordinates + (goto-char org-table-current-begin-pos) + (forward-line line) + (org-table-goto-column column nil create-new-column)))) ;;;###autoload (defun org-table-maybe-recalculate-line () @@ -2481,7 +2633,7 @@ of the new mark." (interactive) (and org-table-allow-automatic-line-recalculation (not (and (memq last-command org-recalc-commands) - (equal org-last-recalc-line (org-current-line)))) + (eq org-last-recalc-line (line-beginning-position)))) (save-excursion (beginning-of-line 1) (looking-at org-table-auto-recalculate-regexp)) (org-table-recalculate) t)) @@ -2505,20 +2657,18 @@ of the new mark." suppress-store suppress-analysis) "Replace the table field value at the cursor by the result of a calculation. -This function makes use of Dave Gillespie's Calc package, in my view the -most exciting program ever written for GNU Emacs. So you need to have Calc -installed in order to use this function. - In a table, this command replaces the value in the current field with the result of a formula. It also installs the formula as the \"current\" column formula, by storing it in a special line below the table. When called -with a `C-u' prefix, the current field must be a named field, and the -formula is installed as valid in only this specific field. +with a `\\[universal-argument]' prefix the formula is installed as a \ +field formula. -When called with two `C-u' prefixes, insert the active equation -for the field back into the current field, so that it can be -edited there. This is useful in order to use \\[org-table-show-reference] -to check the referenced fields. +When called with a `\\[universal-argument] \\[universal-argument]' prefix, \ +insert the active equation for the field +back into the current field, so that it can be edited there. This is \ +useful +in order to use \\<org-table-fedit-map>`\\[org-table-show-reference]' to \ +check the referenced fields. When called, the command first prompts for a formula, which is read in the minibuffer. Previously entered formulas are available through the @@ -2527,23 +2677,31 @@ These stored formulas are adapted correctly when moving, inserting, or deleting columns with the corresponding commands. The formula can be any algebraic expression understood by the Calc package. -For details, see the Org-mode manual. +For details, see the Org mode manual. This function can also be called from Lisp programs and offers additional arguments: EQUATION can be the formula to apply. If this -argument is given, the user will not be prompted. SUPPRESS-ALIGN is -used to speed-up recursive calls by by-passing unnecessary aligns. +argument is given, the user will not be prompted. + +SUPPRESS-ALIGN is used to speed-up recursive calls by by-passing +unnecessary aligns. + SUPPRESS-CONST suppresses the interpretation of constants in the -formula, assuming that this has been done already outside the function. -SUPPRESS-STORE means the formula should not be stored, either because -it is already stored, or because it is a modified equation that should -not overwrite the stored one." +formula, assuming that this has been done already outside the +function. + +SUPPRESS-STORE means the formula should not be stored, either +because it is already stored, or because it is a modified +equation that should not overwrite the stored one. + +SUPPRESS-ANALYSIS prevents analyzing the table and checking +location of point." (interactive "P") - (org-table-check-inside-data-field) - (or suppress-analysis (org-table-get-specials)) + (unless suppress-analysis + (org-table-check-inside-data-field) + (org-table-analyze)) (if (equal arg '(16)) (let ((eq (org-table-current-field-formula))) - (or eq (user-error "No equation active for current field")) (org-table-get-field nil eq) (org-table-align) (setq org-table-may-need-update t)) @@ -2557,7 +2715,7 @@ not overwrite the stored one." (org-table-get-formula equation (equal arg '(4))))) (n0 (org-table-current-column)) (org-tbl-calc-modes (copy-sequence org-calc-default-modes)) - (numbers nil) ; was a variable, now fixed default + (numbers nil) ; was a variable, now fixed default (keep-empty nil) n form form0 formrpl formrg bw fmt x ev orig c lispp literal duration duration-output-format) @@ -2580,15 +2738,14 @@ not overwrite the stored one." (?s . sci) (?e . eng)))) n)))) (setq fmt (replace-match "" t t fmt))) - (if (string-match "T" fmt) - (setq duration t numbers t - duration-output-format nil - fmt (replace-match "" t t fmt))) - (if (string-match "t" fmt) - (setq duration t - duration-output-format org-table-duration-custom-format - numbers t - fmt (replace-match "" t t fmt))) + (if (string-match "[tTU]" fmt) + (let ((ff (match-string 0 fmt))) + (setq duration t numbers t + duration-output-format + (cond ((equal ff "T") nil) + ((equal ff "t") org-table-duration-custom-format) + ((equal ff "U") 'hh:mm)) + fmt (replace-match "" t t fmt)))) (if (string-match "N" fmt) (setq numbers t fmt (replace-match "" t t fmt))) @@ -2603,12 +2760,15 @@ not overwrite the stored one." (setq fmt (replace-match "" t t fmt))) (unless (string-match "\\S-" fmt) (setq fmt nil)))) - (if (and (not suppress-const) org-table-formula-use-constants) - (setq formula (org-table-formula-substitute-names formula))) + (when (and (not suppress-const) org-table-formula-use-constants) + (setq formula (org-table-formula-substitute-names formula))) (setq orig (or (get-text-property 1 :orig-formula formula) "?")) + (setq formula (org-table-formula-handle-first/last-rc formula)) (while (> ndown 0) (setq fields (org-split-string - (buffer-substring-no-properties (point-at-bol) (point-at-eol)) + (org-trim + (buffer-substring-no-properties + (line-beginning-position) (line-end-position))) " *| *")) ;; replace fields with duration values if relevant (if duration @@ -2641,9 +2801,10 @@ not overwrite the stored one." t t form))) ;; Check for old vertical references - (setq form (org-table-rewrite-old-row-references form)) + (org-table--error-on-old-row-references form) ;; Insert remote references - (while (string-match "\\<remote([ \t]*\\([-_a-zA-Z0-9]+\\)[ \t]*,[ \t]*\\([^\n)]+\\))" form) + (setq form (org-table-remote-reference-indirection form)) + (while (string-match "\\<remote([ \t]*\\([^,)]+\\)[ \t]*,[ \t]*\\([^\n)]+\\))" form) (setq form (replace-match (save-match-data @@ -2660,8 +2821,10 @@ not overwrite the stored one." ;; Insert complex ranges (while (and (string-match org-table-range-regexp form) (> (length (match-string 0 form)) 1)) - (setq formrg (save-match-data - (org-table-get-range (match-string 0 form) nil n0))) + (setq formrg + (save-match-data + (org-table-get-range + (match-string 0 form) org-table-current-begin-pos n0))) (setq formrpl (save-match-data (org-table-make-reference @@ -2676,15 +2839,20 @@ not overwrite the stored one." (string-match (regexp-quote form) formrpl))) (setq form (replace-match formrpl t t form)) (user-error "Spreadsheet error: invalid reference \"%s\"" form))) - ;; Insert simple ranges - (while (string-match "\\$\\([0-9]+\\)\\.\\.\\$\\([0-9]+\\)" form) + ;; Insert simple ranges, i.e. included in the current row. + (while (string-match + "\\$\\(\\([-+]\\)?[0-9]+\\)\\.\\.\\$\\(\\([-+]\\)?[0-9]+\\)" + form) (setq form (replace-match (save-match-data (org-table-make-reference - (org-sublist - fields (string-to-number (match-string 1 form)) - (string-to-number (match-string 2 form))) + (cl-subseq fields + (+ (if (match-end 2) n0 0) + (string-to-number (match-string 1 form)) + -1) + (+ (if (match-end 4) n0 0) + (string-to-number (match-string 3 form)))) keep-empty numbers lispp)) t t form))) (setq form0 form) @@ -2692,14 +2860,16 @@ not overwrite the stored one." (while (string-match "\\$\\(\\([-+]\\)?[0-9]+\\)" form) (setq n (+ (string-to-number (match-string 1 form)) (if (match-end 2) n0 0)) - x (nth (1- (if (= n 0) n0 (max n 1))) fields)) - (unless x (user-error "Invalid field specifier \"%s\"" - (match-string 0 form))) - (setq form (replace-match - (save-match-data - (org-table-make-reference - x keep-empty numbers lispp)) - t t form))) + x (nth (1- (if (= n 0) n0 (max n 1))) fields) + formrpl (save-match-data + (org-table-make-reference + x keep-empty numbers lispp))) + (when (or (not x) + (save-match-data + (string-match (regexp-quote formula) formrpl))) + (user-error "Invalid field specifier \"%s\"" + (match-string 0 form))) + (setq form (replace-match formrpl t t form))) (if lispp (setq ev (condition-case nil @@ -2709,20 +2879,23 @@ not overwrite the stored one." ev (if duration (org-table-time-seconds-to-string (string-to-number ev) duration-output-format) ev)) - (or (fboundp 'calc-eval) - (user-error "Calc does not seem to be installed, and is needed to evaluate the formula")) - ;; Use <...> time-stamps so that Calc can handle them - (while (string-match (concat "\\[" org-ts-regexp1 "\\]") form) - (setq form (replace-match "<\\1>" nil nil form))) - ;; I18n-ize local time-stamps by setting (system-time-locale "C") - (when (string-match org-ts-regexp2 form) - (let* ((ts (match-string 0 form)) - (tsp (apply 'encode-time (save-match-data (org-parse-time-string ts)))) - (system-time-locale "C") - (tf (or (and (save-match-data (string-match "[0-9]\\{1,2\\}:[0-9]\\{2\\}" ts)) - (cdr org-time-stamp-formats)) - (car org-time-stamp-formats)))) - (setq form (replace-match (format-time-string tf tsp) t t form)))) + + ;; Use <...> time-stamps so that Calc can handle them. + (setq form + (replace-regexp-in-string org-ts-regexp-inactive "<\\1>" form)) + ;; Internationalize local time-stamps by setting locale to + ;; "C". + (setq form + (replace-regexp-in-string + org-ts-regexp + (lambda (ts) + (let ((system-time-locale "C")) + (format-time-string + (org-time-stamp-format + (string-match-p "[0-9]\\{1,2\\}:[0-9]\\{2\\}" ts)) + (apply #'encode-time + (save-match-data (org-parse-time-string ts)))))) + form t t)) (setq ev (if (and duration (string-match "^[0-9]+:[0-9]+\\(?::[0-9]+\\)?$" form)) form @@ -2742,7 +2915,7 @@ Orig: %s $xyz-> %s @r$c-> %s $1-> %s\n" orig formula form0 form)) - (if (listp ev) + (if (consp ev) (princ (format " %s^\nError: %s" (make-string (car ev) ?\-) (nth 1 ev))) (princ (format "Result: %s\nFormat: %s\nFinal: %s" @@ -2750,17 +2923,24 @@ $1-> %s\n" orig formula form0 form)) (if fmt (format fmt (string-to-number ev)) ev))))) (setq bw (get-buffer-window "*Substitution History*")) (org-fit-window-to-buffer bw) - (unless (and (org-called-interactively-p 'any) (not ndown)) + (unless (and (called-interactively-p 'any) (not ndown)) (unless (let (inhibit-redisplay) (y-or-n-p "Debugging Formula. Continue to next? ")) (org-table-align) (user-error "Abort")) (delete-window bw) (message ""))) - (if (listp ev) (setq fmt nil ev "#ERROR")) + (when (consp ev) (setq fmt nil ev "#ERROR")) (org-table-justify-field-maybe (format org-table-formula-field-format - (if fmt (format fmt (string-to-number ev)) ev))) + (cond + ((not (stringp ev)) ev) + (fmt (format fmt (string-to-number ev))) + ;; Replace any active time stamp in the result with + ;; an inactive one. Dates in tables are likely + ;; piece of regular data, not meant to appear in the + ;; agenda. + (t (replace-regexp-in-string org-ts-regexp "[\\1]" ev))))) (if (and down (> ndown 0) (looking-at ".*\n[ \t]*|[^-]")) (call-interactively 'org-return) (setq ndown 0))) @@ -2776,146 +2956,152 @@ $1-> %s\n" orig formula form0 form)) (defun org-table-get-range (desc &optional tbeg col highlight corners-only) "Get a calc vector from a column, according to descriptor DESC. + Optional arguments TBEG and COL can give the beginning of the table and the current column, to avoid unnecessary parsing. HIGHLIGHT means just highlight the range. When CORNERS-ONLY is set, only return the corners of the range as -a list (line1 column1 line2 column2) where line1 and line2 are line numbers -in the buffer and column1 and column2 are table column numbers." - (if (not (equal (string-to-char desc) ?@)) - (setq desc (concat "@" desc))) - (save-excursion - (or tbeg (setq tbeg (org-table-begin))) - (or col (setq col (org-table-current-column))) - (let ((thisline (org-current-line)) - beg end c1 c2 r1 r2 rangep tmp) - (unless (string-match org-table-range-regexp desc) - (user-error "Invalid table range specifier `%s'" desc)) - (setq rangep (match-end 3) - r1 (and (match-end 1) (match-string 1 desc)) - r2 (and (match-end 4) (match-string 4 desc)) - c1 (and (match-end 2) (substring (match-string 2 desc) 1)) - c2 (and (match-end 5) (substring (match-string 5 desc) 1))) - - (and c1 (setq c1 (+ (string-to-number c1) - (if (memq (string-to-char c1) '(?- ?+)) col 0)))) - (and c2 (setq c2 (+ (string-to-number c2) - (if (memq (string-to-char c2) '(?- ?+)) col 0)))) - (if (equal r1 "") (setq r1 nil)) - (if (equal r2 "") (setq r2 nil)) - (if r1 (setq r1 (org-table-get-descriptor-line r1))) - (if r2 (setq r2 (org-table-get-descriptor-line r2))) - ; (setq r2 (or r2 r1) c2 (or c2 c1)) - (if (not r1) (setq r1 thisline)) - (if (not r2) (setq r2 thisline)) - (if (or (not c1) (= 0 c1)) (setq c1 col)) - (if (or (not c2) (= 0 c2)) (setq c2 col)) - (if (and (not corners-only) - (or (not rangep) (and (= r1 r2) (= c1 c2)))) - ;; just one field - (progn - (org-goto-line r1) - (while (not (looking-at org-table-dataline-regexp)) - (beginning-of-line 2)) - (prog1 (org-trim (org-table-get-field c1)) - (if highlight (org-table-highlight-rectangle (point) (point))))) - ;; A range, return a vector - ;; First sort the numbers to get a regular rectangle - (if (< r2 r1) (setq tmp r1 r1 r2 r2 tmp)) - (if (< c2 c1) (setq tmp c1 c1 c2 c2 tmp)) - (if corners-only - ;; Only return the corners of the range - (list r1 c1 r2 c2) - ;; Copy the range values into a list - (org-goto-line r1) - (while (not (looking-at org-table-dataline-regexp)) - (beginning-of-line 2)) - (org-table-goto-column c1) - (setq beg (point)) - (org-goto-line r2) - (while (not (looking-at org-table-dataline-regexp)) - (beginning-of-line 0)) - (org-table-goto-column c2) - (setq end (point)) - (if highlight - (org-table-highlight-rectangle - beg (progn (skip-chars-forward "^|\n") (point)))) - ;; return string representation of calc vector - (mapcar 'org-trim - (apply 'append (org-table-copy-region beg end)))))))) - -(defun org-table-get-descriptor-line (desc &optional cline bline table) - "Analyze descriptor DESC and retrieve the corresponding line number. -The cursor is currently in line CLINE, the table begins in line BLINE, -and TABLE is a vector with line types." - (if (string-match "^[0-9]+$" desc) +a list (line1 column1 line2 column2) where line1 and line2 are +line numbers relative to beginning of table, or TBEG, and column1 +and column2 are table column numbers." + (let* ((desc (if (string-match-p "\\`\\$[0-9]+\\.\\.\\$[0-9]+\\'" desc) + (replace-regexp-in-string "\\$" "@0$" desc) + desc)) + (col (or col (org-table-current-column))) + (tbeg (or tbeg (org-table-begin))) + (thisline (count-lines tbeg (line-beginning-position)))) + (unless (string-match org-table-range-regexp desc) + (user-error "Invalid table range specifier `%s'" desc)) + (let ((rangep (match-end 3)) + (r1 (let ((r (and (match-end 1) (match-string 1 desc)))) + (or (save-match-data + (and (org-string-nw-p r) + (org-table--descriptor-line r thisline))) + thisline))) + (r2 (let ((r (and (match-end 4) (match-string 4 desc)))) + (or (save-match-data + (and (org-string-nw-p r) + (org-table--descriptor-line r thisline))) + thisline))) + (c1 (let ((c (and (match-end 2) (substring (match-string 2 desc) 1)))) + (if (or (not c) (= (string-to-number c) 0)) col + (+ (string-to-number c) + (if (memq (string-to-char c) '(?- ?+)) col 0))))) + (c2 (let ((c (and (match-end 5) (substring (match-string 5 desc) 1)))) + (if (or (not c) (= (string-to-number c) 0)) col + (+ (string-to-number c) + (if (memq (string-to-char c) '(?- ?+)) col 0)))))) + (save-excursion + (if (and (not corners-only) + (or (not rangep) (and (= r1 r2) (= c1 c2)))) + ;; Just one field. + (progn + (forward-line (- r1 thisline)) + (while (not (looking-at org-table-dataline-regexp)) + (forward-line)) + (prog1 (org-trim (org-table-get-field c1)) + (when highlight (org-table-highlight-rectangle)))) + ;; A range, return a vector. First sort the numbers to get + ;; a regular rectangle. + (let ((first-row (min r1 r2)) + (last-row (max r1 r2)) + (first-column (min c1 c2)) + (last-column (max c1 c2))) + (if corners-only (list first-row first-column last-row last-column) + ;; Copy the range values into a list. + (forward-line (- first-row thisline)) + (while (not (looking-at org-table-dataline-regexp)) + (forward-line) + (cl-incf first-row)) + (org-table-goto-column first-column) + (let ((beg (point))) + (forward-line (- last-row first-row)) + (while (not (looking-at org-table-dataline-regexp)) + (forward-line -1)) + (org-table-goto-column last-column) + (let ((end (point))) + (when highlight + (org-table-highlight-rectangle + beg (progn (skip-chars-forward "^|\n") (point)))) + ;; Return string representation of calc vector. + (mapcar #'org-trim + (apply #'append + (org-table-copy-region beg end)))))))))))) + +(defun org-table--descriptor-line (desc cline) + "Return relative line number corresponding to descriptor DESC. +The cursor is currently in relative line number CLINE." + (if (string-match "\\`[0-9]+\\'" desc) (aref org-table-dlines (string-to-number desc)) - (setq cline (or cline (org-current-line)) - bline (or bline org-table-current-begin-line) - table (or table org-table-current-line-types)) - (if (or - (not (string-match "^\\(\\([-+]\\)?\\(I+\\)\\)?\\(\\([-+]\\)?\\([0-9]+\\)\\)?" desc)) - ;; 1 2 3 4 5 6 - (and (not (match-end 3)) (not (match-end 6))) - (and (match-end 3) (match-end 6) (not (match-end 5)))) - (user-error "Invalid row descriptor `%s'" desc)) - (let* ((hdir (and (match-end 2) (match-string 2 desc))) - (hn (if (match-end 3) (- (match-end 3) (match-beginning 3)) nil)) - (odir (and (match-end 5) (match-string 5 desc))) - (on (if (match-end 6) (string-to-number (match-string 6 desc)))) - (i (- cline bline)) + (when (or (not (string-match + "^\\(\\([-+]\\)?\\(I+\\)\\)?\\(\\([-+]\\)?\\([0-9]+\\)\\)?" + ;; 1 2 3 4 5 6 + desc)) + (and (not (match-end 3)) (not (match-end 6))) + (and (match-end 3) (match-end 6) (not (match-end 5)))) + (user-error "Invalid row descriptor `%s'" desc)) + (let* ((hn (and (match-end 3) (- (match-end 3) (match-beginning 3)))) + (hdir (match-string 2 desc)) + (odir (match-string 5 desc)) + (on (and (match-end 6) (string-to-number (match-string 6 desc)))) (rel (and (match-end 6) (or (and (match-end 1) (not (match-end 3))) (match-end 5))))) - (if (and hn (not hdir)) - (progn - (setq i 0 hdir "+") - (if (eq (aref table 0) 'hline) (setq hn (1- hn))))) - (if (and (not hn) on (not odir)) - (user-error "Should never happen");;(aref org-table-dlines on) - (if (and hn (> hn 0)) - (setq i (org-table-find-row-type table i 'hline (equal hdir "-") - nil hn cline desc))) - (if on - (setq i (org-table-find-row-type table i 'dline (equal odir "-") - rel on cline desc))) - (+ bline i))))) - -(defun org-table-find-row-type (table i type backwards relative n cline desc) - "FIXME: Needs more documentation." - (let ((l (length table))) - (while (> n 0) - (while (and (setq i (+ i (if backwards -1 1))) - (>= i 0) (< i l) - (not (eq (aref table i) type)) - (if (and relative (eq (aref table i) 'hline)) - (cond - ((eq org-table-relative-ref-may-cross-hline t) t) - ((eq org-table-relative-ref-may-cross-hline 'error) - (user-error "Row descriptor %s used in line %d crosses hline" desc cline)) - (t (setq i (- i (if backwards -1 1)) - n 1) - nil)) - t))) - (setq n (1- n))) - (if (or (< i 0) (>= i l)) - (user-error "Row descriptor %s used in line %d leads outside table" - desc cline) - i))) - -(defun org-table-rewrite-old-row-references (s) - (if (string-match "&[-+0-9I]" s) - (user-error "Formula contains old &row reference, please rewrite using @-syntax") - s)) + (when (and hn (not hdir)) + (setq cline 0) + (setq hdir "+") + (when (eq (aref org-table-current-line-types 0) 'hline) (cl-decf hn))) + (when (and (not hn) on (not odir)) (user-error "Should never happen")) + (when hn + (setq cline + (org-table--row-type 'hline hn cline (equal hdir "-") nil desc))) + (when on + (setq cline + (org-table--row-type 'dline on cline (equal odir "-") rel desc))) + cline))) + +(defun org-table--row-type (type n i backwards relative desc) + "Return relative line of Nth row with type TYPE. +Search starts from relative line I. When BACKWARDS in non-nil, +look before I. When RELATIVE is non-nil, the reference is +relative. DESC is the original descriptor that started the +search, as a string." + (let ((l (length org-table-current-line-types))) + (catch :exit + (dotimes (_ n) + (while (and (cl-incf i (if backwards -1 1)) + (>= i 0) + (< i l) + (not (eq (aref org-table-current-line-types i) type)) + ;; We are going to cross a hline. Check if this is + ;; an authorized move. + (cond + ((not relative)) + ((not (eq (aref org-table-current-line-types i) 'hline))) + ((eq org-table-relative-ref-may-cross-hline t)) + ((eq org-table-relative-ref-may-cross-hline 'error) + (user-error "Row descriptor %s crosses hline" desc)) + (t (cl-decf i (if backwards -1 1)) ; Step back. + (throw :exit nil))))))) + (cond ((or (< i 0) (>= i l)) + (user-error "Row descriptor %s leads outside table" desc)) + ;; The last hline doesn't exist. Instead, point to last row + ;; in table. + ((= i (1- l)) (1- i)) + (t i)))) + +(defun org-table--error-on-old-row-references (s) + (when (string-match "&[-+0-9I]" s) + (user-error "Formula contains old &row reference, please rewrite using @-syntax"))) (defun org-table-make-reference (elements keep-empty numbers lispp) "Convert list ELEMENTS to something appropriate to insert into formula. KEEP-EMPTY indicated to keep empty fields, default is to skip them. NUMBERS indicates that everything should be converted to numbers. LISPP non-nil means to return something appropriate for a Lisp -list, 'literal is for the format specifier L." +list, `literal' is for the format specifier L." ;; Calc nan (not a number) is used for the conversion of the empty ;; field to a reference for several reasons: (i) It is accepted in a ;; Calc formula (e. g. "" or "()" would result in a Calc error). @@ -2961,162 +3147,185 @@ list, 'literal is for the format specifier L." elements ",") "]")))) -;;;###autoload -(defun org-table-set-constants () - "Set `org-table-formula-constants-local' in the current buffer." - (let (cst consts const-str) - (save-excursion - (goto-char (point-min)) - (while (re-search-forward "^[ \t]*#\\+CONSTANTS: \\(.*\\)" nil t) - (setq const-str (substring-no-properties (match-string 1))) - (setq consts (append consts (org-split-string const-str "[ \t]+"))) - (when consts - (let (e) - (while (setq e (pop consts)) - (when (string-match "^\\([a-zA-Z0][_a-zA-Z0-9]*\\)=\\(.*\\)" e) - (if (assoc-string (match-string 1 e) cst) - (setq cst (delete (assoc-string (match-string 1 e) cst) cst))) - (push (cons (match-string 1 e) (match-string 2 e)) cst))) - (setq org-table-formula-constants-local cst))))))) +(defun org-table-message-once-per-second (t1 &rest args) + "If there has been more than one second since T1, display message. +ARGS are passed as arguments to the `message' function. Returns +current time if a message is printed, otherwise returns T1. If +T1 is nil, always messages." + (let ((curtime (current-time))) + (if (or (not t1) (< 0 (nth 1 (time-subtract curtime t1)))) + (progn (apply 'message args) + curtime) + t1))) ;;;###autoload (defun org-table-recalculate (&optional all noalign) "Recalculate the current table line by applying all stored formulas. + With prefix arg ALL, do this for all lines in the table. -With the prefix argument ALL is `(16)' \ -\(a double \\[universal-prefix] \\[universal-prefix] prefix), or if -it is the symbol `iterate', recompute the table until it no longer changes. + +When called with a `\\[universal-argument] \\[universal-argument]' prefix, or \ +if ALL is the symbol `iterate', +recompute the table until it no longer changes. + If NOALIGN is not nil, do not re-align the table after the computations are done. This is typically used internally to save time, if it is known that the table will be realigned a little later anyway." (interactive "P") - (or (memq this-command org-recalc-commands) - (setq org-recalc-commands (cons this-command org-recalc-commands))) + (unless (memq this-command org-recalc-commands) + (push this-command org-recalc-commands)) (unless (org-at-table-p) (user-error "Not at a table")) (if (or (eq all 'iterate) (equal all '(16))) (org-table-iterate) - (org-table-get-specials) + (org-table-analyze) (let* ((eqlist (sort (org-table-get-stored-formulas) (lambda (a b) (string< (car a) (car b))))) - (eqlist1 (copy-sequence eqlist)) (inhibit-redisplay (not debug-on-error)) (line-re org-table-dataline-regexp) - (thisline (org-current-line)) - (thiscol (org-table-current-column)) - seen-fields lhs1 - beg end entry eqlnum eqlname eqlname1 eql (cnt 0) eq a name name1) - ;; Insert constants in all formulas - (setq eqlist - (mapcar (lambda (x) - (when (string-match "\\`$[<>]" (car x)) - (setq lhs1 (car x)) - (setq x (cons (substring - (org-table-formula-handle-first/last-rc - (car x)) 1) - (cdr x))) - (if (assoc (car x) eqlist1) - (user-error "\"%s=\" formula tries to overwrite existing formula for column %s" - lhs1 (car x)))) - (cons - (org-table-formula-handle-first/last-rc (car x)) - (org-table-formula-substitute-names - (org-table-formula-handle-first/last-rc (cdr x))))) - eqlist)) - ;; Split the equation list - (while (setq eq (pop eqlist)) - (if (<= (string-to-char (car eq)) ?9) - (push eq eqlnum) - (push eq eqlname))) - (setq eqlnum (nreverse eqlnum) eqlname (nreverse eqlname)) - ;; Expand ranges in lhs of formulas - (setq eqlname (org-table-expand-lhs-ranges eqlname)) - - ;; Get the correct line range to process - (if all - (progn - (setq end (move-marker (make-marker) (1+ (org-table-end)))) - (goto-char (setq beg (org-table-begin))) - (if (re-search-forward org-table-calculate-mark-regexp end t) - ;; This is a table with marked lines, compute selected lines - (setq line-re org-table-recalculate-regexp) - ;; Move forward to the first non-header line - (if (and (re-search-forward org-table-dataline-regexp end t) - (re-search-forward org-table-hline-regexp end t) - (re-search-forward org-table-dataline-regexp end t)) - (setq beg (match-beginning 0)) - nil))) ;; just leave beg where it is - (setq beg (point-at-bol) - end (move-marker (make-marker) (1+ (point-at-eol))))) - (goto-char beg) - (and all (message "Re-applying formulas to full table...")) - - ;; First find the named fields, and mark them untouchable. - ;; Also check if several field/range formulas try to set the same field. - (remove-text-properties beg end '(org-untouchable t)) - (while (setq eq (pop eqlname)) - (setq name (car eq) - a (assoc name org-table-named-field-locations)) - (setq name1 name) - (if a (setq name1 (format "@%d$%d" (org-table-line-to-dline (nth 1 a)) - (nth 2 a)))) - (when (member name1 seen-fields) - (user-error "Several field/range formulas try to set %s" name1)) - (push name1 seen-fields) - - (and (not a) - (string-match "@\\([0-9]+\\)\\$\\([0-9]+\\)" name) - (setq a (list name - (condition-case nil - (aref org-table-dlines - (string-to-number (match-string 1 name))) - (error (user-error "Invalid row number in %s" - name))) - (string-to-number (match-string 2 name))))) - (when (and a (or all (equal (nth 1 a) thisline))) - (message "Re-applying formula to field: %s" name) - (org-goto-line (nth 1 a)) - (org-table-goto-column (nth 2 a)) - (push (append a (list (cdr eq))) eqlname1) - (org-table-put-field-property :org-untouchable t))) - (setq eqlname1 (nreverse eqlname1)) - - ;; Now evaluate the column formulas, but skip fields covered by - ;; field formulas - (goto-char beg) - (while (re-search-forward line-re end t) - (unless (string-match "^ *[_^!$/] *$" (org-table-get-field 1)) - ;; Unprotected line, recalculate - (and all (message "Re-applying formulas to full table...(line %d)" - (setq cnt (1+ cnt)))) - (setq org-last-recalc-line (org-current-line)) - (setq eql eqlnum) - (while (setq entry (pop eql)) - (org-goto-line org-last-recalc-line) - (org-table-goto-column (string-to-number (car entry)) nil 'force) - (unless (get-text-property (point) :org-untouchable) - (org-table-eval-formula nil (cdr entry) - 'noalign 'nocst 'nostore 'noanalysis))))) - - ;; Now evaluate the field formulas - (while (setq eq (pop eqlname1)) - (message "Re-applying formula to field: %s" (car eq)) - (org-goto-line (nth 1 eq)) - (org-table-goto-column (nth 2 eq)) - (org-table-eval-formula nil (nth 3 eq) 'noalign 'nocst - 'nostore 'noanalysis)) - - (org-goto-line thisline) - (org-table-goto-column thiscol) - (remove-text-properties (point-min) (point-max) '(org-untouchable t)) - (or noalign (and org-table-may-need-update (org-table-align)) - (and all (message "Re-applying formulas to %d lines...done" cnt))) - - ;; back to initial position - (message "Re-applying formulas...done") - (org-goto-line thisline) - (org-table-goto-column thiscol) - (or noalign (and org-table-may-need-update (org-table-align)) - (and all (message "Re-applying formulas...done")))))) + (log-first-time (current-time)) + (log-last-time log-first-time) + (cnt 0) + beg end eqlcol eqlfield) + ;; Insert constants in all formulas. + (when eqlist + (org-table-save-field + ;; Expand equations, then split the equation list between + ;; column formulas and field formulas. + (dolist (eq eqlist) + (let* ((rhs (org-table-formula-substitute-names + (org-table-formula-handle-first/last-rc (cdr eq)))) + (old-lhs (car eq)) + (lhs + (org-table-formula-handle-first/last-rc + (cond + ((string-match "\\`@-?I+" old-lhs) + (user-error "Can't assign to hline relative reference")) + ((string-match "\\`$[<>]" old-lhs) + (let ((new (org-table-formula-handle-first/last-rc + old-lhs))) + (when (assoc new eqlist) + (user-error "\"%s=\" formula tries to overwrite \ +existing formula for column %s" + old-lhs + new)) + new)) + (t old-lhs))))) + (if (string-match-p "\\`\\$[0-9]+\\'" lhs) + (push (cons lhs rhs) eqlcol) + (push (cons lhs rhs) eqlfield)))) + (setq eqlcol (nreverse eqlcol)) + ;; Expand ranges in lhs of formulas + (setq eqlfield (org-table-expand-lhs-ranges (nreverse eqlfield))) + ;; Get the correct line range to process. + (if all + (progn + (setq end (copy-marker (org-table-end))) + (goto-char (setq beg org-table-current-begin-pos)) + (cond + ((re-search-forward org-table-calculate-mark-regexp end t) + ;; This is a table with marked lines, compute selected + ;; lines. + (setq line-re org-table-recalculate-regexp)) + ;; Move forward to the first non-header line. + ((and (re-search-forward org-table-dataline-regexp end t) + (re-search-forward org-table-hline-regexp end t) + (re-search-forward org-table-dataline-regexp end t)) + (setq beg (match-beginning 0))) + ;; Just leave BEG at the start of the table. + (t nil))) + (setq beg (line-beginning-position) + end (copy-marker (line-beginning-position 2)))) + (goto-char beg) + ;; Mark named fields untouchable. Also check if several + ;; field/range formulas try to set the same field. + (remove-text-properties beg end '(:org-untouchable t)) + (let ((current-line (count-lines org-table-current-begin-pos + (line-beginning-position))) + seen-fields) + (dolist (eq eqlfield) + (let* ((name (car eq)) + (location (assoc name org-table-named-field-locations)) + (eq-line (or (nth 1 location) + (and (string-match "\\`@\\([0-9]+\\)" name) + (aref org-table-dlines + (string-to-number + (match-string 1 name)))))) + (reference + (if location + ;; Turn field coordinates associated to NAME + ;; into an absolute reference. + (format "@%d$%d" + (org-table-line-to-dline eq-line) + (nth 2 location)) + name))) + (when (member reference seen-fields) + (user-error "Several field/range formulas try to set %s" + reference)) + (push reference seen-fields) + (when (or all (eq eq-line current-line)) + (org-table-goto-field name) + (org-table-put-field-property :org-untouchable t))))) + ;; Evaluate the column formulas, but skip fields covered by + ;; field formulas. + (goto-char beg) + (while (re-search-forward line-re end t) + (unless (string-match "\\` *[_^!$/] *\\'" (org-table-get-field 1)) + ;; Unprotected line, recalculate. + (cl-incf cnt) + (when all + (setq log-last-time + (org-table-message-once-per-second + log-last-time + "Re-applying formulas to full table...(line %d)" cnt))) + (if (markerp org-last-recalc-line) + (move-marker org-last-recalc-line (line-beginning-position)) + (setq org-last-recalc-line + (copy-marker (line-beginning-position)))) + (dolist (entry eqlcol) + (goto-char org-last-recalc-line) + (org-table-goto-column + (string-to-number (substring (car entry) 1)) nil 'force) + (unless (get-text-property (point) :org-untouchable) + (org-table-eval-formula + nil (cdr entry) 'noalign 'nocst 'nostore 'noanalysis))))) + ;; Evaluate the field formulas. + (dolist (eq eqlfield) + (let ((reference (car eq)) + (formula (cdr eq))) + (setq log-last-time + (org-table-message-once-per-second + (and all log-last-time) + "Re-applying formula to field: %s" (car eq))) + (org-table-goto-field + reference + ;; Possibly create a new column, as long as + ;; `org-table-formula-create-columns' allows it. + (let ((column-count (progn (end-of-line) + (1- (org-table-current-column))))) + (lambda (column) + (when (> column 1000) + (user-error "Formula column target too large")) + (and (> column column-count) + (or (eq org-table-formula-create-columns t) + (and (eq org-table-formula-create-columns 'warn) + (progn + (org-display-warning + "Out-of-bounds formula added columns") + t)) + (and (eq org-table-formula-create-columns 'prompt) + (yes-or-no-p + "Out-of-bounds formula. Add columns? "))))))) + (org-table-eval-formula nil formula t t t t)))) + ;; Clean up markers and internal text property. + (remove-text-properties (point-min) (point-max) '(org-untouchable t)) + (set-marker end nil) + (unless noalign + (when org-table-may-need-update (org-table-align)) + (when all + (org-table-message-once-per-second + log-first-time "Re-applying formulas to %d lines... done" cnt))) + (org-table-message-once-per-second + (and all log-first-time) "Re-applying formulas... done"))))) ;;;###autoload (defun org-table-iterate (&optional arg) @@ -3145,10 +3354,15 @@ with the prefix ARG." (defun org-table-recalculate-buffer-tables () "Recalculate all tables in the current buffer." (interactive) - (save-excursion - (save-restriction - (widen) - (org-table-map-tables (lambda () (org-table-recalculate t)) t)))) + (org-with-wide-buffer + (org-table-map-tables + (lambda () + ;; Reason for separate `org-table-align': When repeating + ;; (org-table-recalculate t) `org-table-may-need-update' gets in + ;; the way. + (org-table-recalculate t t) + (org-table-align)) + t))) ;;;###autoload (defun org-table-iterate-buffer-tables () @@ -3158,85 +3372,90 @@ with the prefix ARG." (i imax) (checksum (md5 (buffer-string))) c1) - (save-excursion - (save-restriction - (widen) - (catch 'exit - (while (> i 0) - (setq i (1- i)) - (org-table-map-tables (lambda () (org-table-recalculate t)) t) - (if (equal checksum (setq c1 (md5 (buffer-string)))) - (progn - (message "Convergence after %d iterations" (- imax i)) - (throw 'exit t)) - (setq checksum c1))) - (user-error "No convergence after %d iterations" imax)))))) + (org-with-wide-buffer + (catch 'exit + (while (> i 0) + (setq i (1- i)) + (org-table-map-tables (lambda () (org-table-recalculate t t)) t) + (if (equal checksum (setq c1 (md5 (buffer-string)))) + (progn + (org-table-map-tables #'org-table-align t) + (message "Convergence after %d iterations" (- imax i)) + (throw 'exit t)) + (setq checksum c1))) + (org-table-map-tables #'org-table-align t) + (user-error "No convergence after %d iterations" imax))))) (defun org-table-calc-current-TBLFM (&optional arg) "Apply the #+TBLFM in the line at point to the table." (interactive "P") (unless (org-at-TBLFM-p) (user-error "Not at a #+TBLFM line")) (let ((formula (buffer-substring - (point-at-bol) - (point-at-eol))) - s e) + (line-beginning-position) + (line-end-position)))) (save-excursion ;; Insert a temporary formula at right after the table (goto-char (org-table-TBLFM-begin)) - (setq s (point-marker)) - (insert (concat formula "\n")) - (setq e (point-marker)) - ;; Recalculate the table - (beginning-of-line 0) ; move to the inserted line - (skip-chars-backward " \r\n\t") - (if (org-at-table-p) + (let ((s (point-marker))) + (insert formula "\n") + (let ((e (point-marker))) + ;; Recalculate the table. + (beginning-of-line 0) ; move to the inserted line + (skip-chars-backward " \r\n\t") (unwind-protect - (org-call-with-arg 'org-table-recalculate (or arg t)) - ;; delete the formula inserted temporarily - (delete-region s e)))))) + (org-call-with-arg #'org-table-recalculate (or arg t)) + ;; Delete the formula inserted temporarily. + (delete-region s e) + (set-marker s nil) + (set-marker e nil))))))) (defun org-table-TBLFM-begin () "Find the beginning of the TBLFM lines and return its position. Return nil when the beginning of TBLFM line was not found." (save-excursion (when (progn (forward-line 1) - (re-search-backward - org-table-TBLFM-begin-regexp - nil t)) - (point-at-bol 2)))) + (re-search-backward org-table-TBLFM-begin-regexp nil t)) + (line-beginning-position 2)))) (defun org-table-expand-lhs-ranges (equations) "Expand list of formulas. -If some of the RHS in the formulas are ranges or a row reference, expand -them to individual field equations for each field." - (let (e res lhs rhs range r1 r2 c1 c2) - (while (setq e (pop equations)) - (setq lhs (car e) rhs (cdr e)) - (cond - ((string-match "^@-?[-+0-9]+\\$-?[0-9]+$" lhs) - ;; This just refers to one fixed field - (push e res)) - ((string-match "^[a-zA-Z][_a-zA-Z0-9]*$" lhs) - ;; This just refers to one fixed named field - (push e res)) - ((string-match "^@[0-9]+$" lhs) - (loop for ic from 1 to org-table-current-ncol do - (push (cons (format "%s$%d" lhs ic) rhs) res) - (put-text-property 0 (length (caar res)) - :orig-eqn e (caar res)))) - (t - (setq range (org-table-get-range lhs org-table-current-begin-pos - 1 nil 'corners)) - (setq r1 (nth 0 range) c1 (nth 1 range) - r2 (nth 2 range) c2 (nth 3 range)) - (setq r1 (org-table-line-to-dline r1)) - (setq r2 (org-table-line-to-dline r2 'above)) - (loop for ir from r1 to r2 do - (loop for ic from c1 to c2 do - (push (cons (format "@%d$%d" ir ic) rhs) res) - (put-text-property 0 (length (caar res)) - :orig-eqn e (caar res))))))) - (nreverse res))) +If some of the RHS in the formulas are ranges or a row reference, +expand them to individual field equations for each field. This +function assumes the table is already analyzed (i.e., using +`org-table-analyze')." + (let (res) + (dolist (e equations (nreverse res)) + (let ((lhs (car e)) + (rhs (cdr e))) + (cond + ((string-match-p "\\`@-?[-+0-9]+\\$-?[0-9]+\\'" lhs) + ;; This just refers to one fixed field. + (push e res)) + ((string-match-p "\\`[a-zA-Z][_a-zA-Z0-9]*\\'" lhs) + ;; This just refers to one fixed named field. + (push e res)) + ((string-match-p "\\`\\$[0-9]+\\'" lhs) + ;; Column formulas are treated specially and are not + ;; expanded. + (push e res)) + ((string-match "\\`@[0-9]+\\'" lhs) + (dotimes (ic org-table-current-ncol) + (push (cons (propertize (format "%s$%d" lhs (1+ ic)) :orig-eqn e) + rhs) + res))) + (t + (let* ((range (org-table-get-range + lhs org-table-current-begin-pos 1 nil 'corners)) + (r1 (org-table-line-to-dline (nth 0 range))) + (c1 (nth 1 range)) + (r2 (org-table-line-to-dline (nth 2 range) 'above)) + (c2 (nth 3 range))) + (cl-loop for ir from r1 to r2 do + (cl-loop for ic from c1 to c2 do + (push (cons (propertize + (format "@%d$%d" ir ic) :orig-eqn e) + rhs) + res)))))))))) (defun org-table-formula-handle-first/last-rc (s) "Replace @<, @>, $<, $> with first/last row/column of the table. @@ -3262,32 +3481,40 @@ borders of the table using the @< @> $< $> makers." (- nmax len -1))) (if (or (< n 1) (> n nmax)) (user-error "Reference \"%s\" in expression \"%s\" points outside table" - (match-string 0 s) s)) + (match-string 0 s) s)) (setq start (match-beginning 0)) (setq s (replace-match (format "%s%d" (match-string 1 s) n) t t s))))) s) (defun org-table-formula-substitute-names (f) "Replace $const with values in string F." - (let ((start 0) a (f1 f) (pp (/= (string-to-char f) ?'))) - ;; First, check for column names - (while (setq start (string-match org-table-column-name-regexp f start)) - (setq start (1+ start)) - (setq a (assoc (match-string 1 f) org-table-column-names)) - (setq f (replace-match (concat "$" (cdr a)) t t f))) - ;; Parameters and constants - (setq start 0) - (while (setq start (string-match "\\$\\([a-zA-Z][_a-zA-Z0-9]*\\)\\|\\(\\<remote([^)]*)\\)" f start)) - (if (match-end 2) - (setq start (match-end 2)) - (setq start (1+ start)) - (if (setq a (save-match-data - (org-table-get-constant (match-string 1 f)))) - (setq f (replace-match - (concat (if pp "(") a (if pp ")")) t t f))))) - (if org-table-formula-debug - (put-text-property 0 (length f) :orig-formula f1 f)) - f)) + (let ((start 0) + (pp (/= (string-to-char f) ?')) + (duration (string-match-p ";.*[Tt].*\\'" f)) + (new (replace-regexp-in-string ; Check for column names. + org-table-column-name-regexp + (lambda (m) + (concat "$" (cdr (assoc (match-string 1 m) + org-table-column-names)))) + f t t))) + ;; Parameters and constants. + (while (setq start + (string-match + "\\$\\([a-zA-Z][_a-zA-Z0-9]*\\)\\|\\(\\<remote([^)]*)\\)" + new start)) + (if (match-end 2) (setq start (match-end 2)) + (cl-incf start) + ;; When a duration is expected, convert value on the fly. + (let ((value + (save-match-data + (let ((v (org-table-get-constant (match-string 1 new)))) + (if (and (org-string-nw-p v) duration) + (org-table-time-string-to-seconds v) + v))))) + (when value + (setq new (replace-match + (concat (and pp "(") value (and pp ")")) t t new)))))) + (if org-table-formula-debug (propertize new :orig-formula f) new))) (defun org-table-get-constant (const) "Find the value for a parameter or constant in a formula. @@ -3353,66 +3580,75 @@ Parameters get priority." :style toggle :selected org-table-buffer-is-an])) (defvar org-pos) +(defvar org-table--fedit-source nil + "Position of the TBLFM line being edited.") ;;;###autoload (defun org-table-edit-formulas () "Edit the formulas of the current table in a separate buffer." (interactive) - (when (save-excursion (beginning-of-line 1) (let ((case-fold-search t)) (looking-at "[ \t]*#\\+TBLFM"))) - (beginning-of-line 0)) - (unless (org-at-table-p) (user-error "Not at a table")) - (org-table-get-specials) - (let ((key (org-table-current-field-formula 'key 'noerror)) - (eql (sort (org-table-get-stored-formulas 'noerror) - 'org-table-formula-less-p)) - (pos (point-marker)) - (startline 1) - (wc (current-window-configuration)) - (sel-win (selected-window)) - (titles '((column . "# Column Formulas\n") - (field . "# Field and Range Formulas\n") - (named . "# Named Field Formulas\n"))) - entry s type title) - (org-switch-to-buffer-other-window "*Edit Formulas*") - (erase-buffer) - ;; Keep global-font-lock-mode from turning on font-lock-mode - (let ((font-lock-global-modes '(not fundamental-mode))) - (fundamental-mode)) - (org-set-local 'font-lock-global-modes (list 'not major-mode)) - (org-set-local 'org-pos pos) - (org-set-local 'org-window-configuration wc) - (org-set-local 'org-selected-window sel-win) - (use-local-map org-table-fedit-map) - (org-add-hook 'post-command-hook 'org-table-fedit-post-command t t) - (easy-menu-add org-table-fedit-menu) - (setq startline (org-current-line)) - (while (setq entry (pop eql)) - (setq type (cond - ((string-match "\\`$[<>]" (car entry)) 'column) - ((equal (string-to-char (car entry)) ?@) 'field) - ((string-match "^[0-9]" (car entry)) 'column) - (t 'named))) - (when (setq title (assq type titles)) - (or (bobp) (insert "\n")) - (insert (org-add-props (cdr title) nil 'face font-lock-comment-face)) - (setq titles (remove title titles))) - (if (equal key (car entry)) (setq startline (org-current-line))) - (setq s (concat (if (member (string-to-char (car entry)) '(?@ ?$)) "" "$") - (car entry) " = " (cdr entry) "\n")) - (remove-text-properties 0 (length s) '(face nil) s) - (insert s)) - (if (eq org-table-use-standard-references t) + (let ((at-tblfm (org-at-TBLFM-p))) + (unless (or at-tblfm (org-at-table-p)) + (user-error "Not at a table")) + (save-excursion + ;; Move point within the table before analyzing it. + (when at-tblfm (re-search-backward "^[ \t]*|")) + (org-table-analyze)) + (let ((key (org-table-current-field-formula 'key 'noerror)) + (eql (sort (org-table-get-stored-formulas t (and at-tblfm (point))) + #'org-table-formula-less-p)) + (pos (point-marker)) + (source (copy-marker (line-beginning-position))) + (startline 1) + (wc (current-window-configuration)) + (sel-win (selected-window)) + (titles '((column . "# Column Formulas\n") + (field . "# Field and Range Formulas\n") + (named . "# Named Field Formulas\n")))) + (org-switch-to-buffer-other-window "*Edit Formulas*") + (erase-buffer) + ;; Keep global-font-lock-mode from turning on font-lock-mode + (let ((font-lock-global-modes '(not fundamental-mode))) + (fundamental-mode)) + (setq-local font-lock-global-modes (list 'not major-mode)) + (setq-local org-pos pos) + (setq-local org-table--fedit-source source) + (setq-local org-window-configuration wc) + (setq-local org-selected-window sel-win) + (use-local-map org-table-fedit-map) + (add-hook 'post-command-hook #'org-table-fedit-post-command t t) + (easy-menu-add org-table-fedit-menu) + (setq startline (org-current-line)) + (dolist (entry eql) + (let* ((type (cond + ((string-match "\\`$\\([0-9]+\\|[<>]+\\)\\'" (car entry)) + 'column) + ((equal (string-to-char (car entry)) ?@) 'field) + (t 'named))) + (title (assq type titles))) + (when title + (unless (bobp) (insert "\n")) + (insert + (org-add-props (cdr title) nil 'face font-lock-comment-face)) + (setq titles (remove title titles))) + (when (equal key (car entry)) (setq startline (org-current-line))) + (let ((s (concat + (if (memq (string-to-char (car entry)) '(?@ ?$)) "" "$") + (car entry) " = " (cdr entry) "\n"))) + (remove-text-properties 0 (length s) '(face nil) s) + (insert s)))) + (when (eq org-table-use-standard-references t) (org-table-fedit-toggle-ref-type)) - (org-goto-line startline) - (message "%s" "Edit formulas, finish with C-c C-c or C-c '. See menu for more commands."))) + (org-goto-line startline) + (message "%s" (substitute-command-keys "\\<org-mode-map>\ +Edit formulas, finish with `\\[org-ctrl-c-ctrl-c]' or `\\[org-edit-special]'. \ +See menu for more commands."))))) (defun org-table-fedit-post-command () (when (not (memq this-command '(lisp-complete-symbol))) (let ((win (selected-window))) (save-excursion - (condition-case nil - (org-table-show-reference) - (error nil)) + (ignore-errors (org-table-show-reference)) (select-window win))))) (defun org-table-formula-to-user (s) @@ -3537,23 +3773,34 @@ minutes or seconds." (format "%.1f" (/ (float secs0) 60))) ((eq output-format 'seconds) (format "%d" secs0)) - (t (org-format-seconds "%.2h:%.2m:%.2s" secs0))))) + ((eq output-format 'hh:mm) + ;; Ignore seconds + (substring (format-seconds + (if org-table-duration-hour-zero-padding + "%.2h:%.2m:%.2s" "%h:%.2m:%.2s") + secs0) + 0 -3)) + (t (format-seconds + (if org-table-duration-hour-zero-padding + "%.2h:%.2m:%.2s" "%h:%.2m:%.2s") + secs0))))) (if (< secs 0) (concat "-" res) res))) (defun org-table-fedit-convert-buffer (function) "Convert all references in this buffer, using FUNCTION." - (let ((line (org-current-line))) + (let ((origin (copy-marker (line-beginning-position)))) (goto-char (point-min)) (while (not (eobp)) - (insert (funcall function (buffer-substring (point) (point-at-eol)))) - (delete-region (point) (point-at-eol)) - (or (eobp) (forward-char 1))) - (org-goto-line line))) + (insert (funcall function (buffer-substring (point) (line-end-position)))) + (delete-region (point) (line-end-position)) + (forward-line)) + (goto-char origin) + (set-marker origin nil))) (defun org-table-fedit-toggle-ref-type () "Convert all references in the buffer from B3 to @3$2 and back." (interactive) - (org-set-local 'org-table-buffer-is-an (not org-table-buffer-is-an)) + (setq-local org-table-buffer-is-an (not org-table-buffer-is-an)) (org-table-fedit-convert-buffer (if org-table-buffer-is-an 'org-table-convert-refs-to-an 'org-table-convert-refs-to-rc)) @@ -3579,16 +3826,16 @@ minutes or seconds." (defun org-table-fedit-shift-reference (dir) (cond - ((org-at-regexp-p "\\(\\<[a-zA-Z]\\)&") + ((org-in-regexp "\\(\\<[a-zA-Z]\\)&") (if (memq dir '(left right)) (org-rematch-and-replace 1 (eq dir 'left)) (user-error "Cannot shift reference in this direction"))) - ((org-at-regexp-p "\\(\\<[a-zA-Z]\\{1,2\\}\\)\\([0-9]+\\)") + ((org-in-regexp "\\(\\<[a-zA-Z]\\{1,2\\}\\)\\([0-9]+\\)") ;; A B3-like reference (if (memq dir '(up down)) (org-rematch-and-replace 2 (eq dir 'up)) (org-rematch-and-replace 1 (eq dir 'left)))) - ((org-at-regexp-p + ((org-in-regexp "\\(@\\|\\.\\.\\)\\([-+]?\\(I+\\>\\|[0-9]+\\)\\)\\(\\$\\([-+]?[0-9]+\\)\\)?") ;; An internal reference (if (memq dir '(up down)) @@ -3649,32 +3896,31 @@ a translation reference." With prefix ARG, apply the new formulas to the table." (interactive "P") (org-table-remove-rectangle-highlight) - (if org-table-use-standard-references - (progn - (org-table-fedit-convert-buffer 'org-table-convert-refs-to-rc) - (setq org-table-buffer-is-an nil))) - (let ((pos org-pos) (sel-win org-selected-window) eql var form) + (when org-table-use-standard-references + (org-table-fedit-convert-buffer 'org-table-convert-refs-to-rc) + (setq org-table-buffer-is-an nil)) + (let ((pos org-pos) + (sel-win org-selected-window) + (source org-table--fedit-source) + eql) (goto-char (point-min)) (while (re-search-forward "^\\(@[-+I<>0-9.$@]+\\|@?[0-9]+\\|\\$\\([a-zA-Z0-9]+\\|[<>]+\\)\\) *= *\\(.*\\(\n[ \t]+.*$\\)*\\)" nil t) - (setq var (if (match-end 2) (match-string 2) (match-string 1)) - form (match-string 3)) - (setq form (org-trim form)) - (when (not (equal form "")) - (while (string-match "[ \t]*\n[ \t]*" form) - (setq form (replace-match " " t t form))) - (when (assoc var eql) - (user-error "Double formulas for %s" var)) - (push (cons var form) eql))) - (setq org-pos nil) + (let ((var (match-string 1)) + (form (org-trim (match-string 3)))) + (unless (equal form "") + (while (string-match "[ \t]*\n[ \t]*" form) + (setq form (replace-match " " t t form))) + (when (assoc var eql) + (user-error "Double formulas for %s" var)) + (push (cons var form) eql)))) (set-window-configuration org-window-configuration) (select-window sel-win) - (goto-char pos) - (unless (org-at-table-p) - (user-error "Lost table position - cannot install formulas")) + (goto-char source) (org-table-store-formulas eql) - (move-marker pos nil) + (set-marker pos nil) + (set-marker source nil) (kill-buffer "*Edit Formulas*") (if arg (org-table-recalculate 'all) @@ -3733,9 +3979,11 @@ With prefix ARG, apply the new formulas to the table." (defvar org-show-positions nil) (defun org-table-show-reference (&optional local) - "Show the location/value of the $ expression at point." + "Show the location/value of the $ expression at point. +When LOCAL is non-nil, show references for the table at point." (interactive) (org-table-remove-rectangle-highlight) + (when local (org-table-analyze)) (catch 'exit (let ((pos (if local (point) org-pos)) (face2 'highlight) @@ -3743,41 +3991,41 @@ With prefix ARG, apply the new formulas to the table." (win (selected-window)) (org-show-positions nil) var name e what match dest) - (if local (org-table-get-specials)) (setq what (cond - ((org-at-regexp-p "^@[0-9]+[ \t=]") + ((org-in-regexp "^@[0-9]+[ \t=]") (setq match (concat (substring (match-string 0) 0 -1) "$1.." (substring (match-string 0) 0 -1) "$100")) 'range) - ((or (org-at-regexp-p org-table-range-regexp2) - (org-at-regexp-p org-table-translate-regexp) - (org-at-regexp-p org-table-range-regexp)) + ((or (org-in-regexp org-table-range-regexp2) + (org-in-regexp org-table-translate-regexp) + (org-in-regexp org-table-range-regexp)) (setq match (save-match-data (org-table-convert-refs-to-rc (match-string 0)))) 'range) - ((org-at-regexp-p "\\$[a-zA-Z][a-zA-Z0-9]*") 'name) - ((org-at-regexp-p "\\$[0-9]+") 'column) + ((org-in-regexp "\\$[a-zA-Z][a-zA-Z0-9]*") 'name) + ((org-in-regexp "\\$[0-9]+") 'column) ((not local) nil) (t (user-error "No reference at point"))) match (and what (or match (match-string 0)))) (when (and match (not (equal (match-beginning 0) (point-at-bol)))) (org-table-add-rectangle-overlay (match-beginning 0) (match-end 0) 'secondary-selection)) - (org-add-hook 'before-change-functions - 'org-table-remove-rectangle-highlight) - (if (eq what 'name) (setq var (substring match 1))) + (add-hook 'before-change-functions + #'org-table-remove-rectangle-highlight) + (when (eq what 'name) (setq var (substring match 1))) (when (eq what 'range) - (or (equal (string-to-char match) ?@) (setq match (concat "@" match))) + (unless (eq (string-to-char match) ?@) (setq match (concat "@" match))) (setq match (org-table-formula-substitute-names match))) (unless local (save-excursion - (end-of-line 1) + (end-of-line) (re-search-backward "^\\S-" nil t) - (beginning-of-line 1) - (when (looking-at "\\(\\$[0-9a-zA-Z]+\\|@[0-9]+\\$[0-9]+\\|[a-zA-Z]+\\([0-9]+\\|&\\)\\) *=") + (beginning-of-line) + (when (looking-at "\\(\\$[0-9a-zA-Z]+\\|@[0-9]+\\$[0-9]+\\|[a-zA-Z]+\ +\\([0-9]+\\|&\\)\\) *=") (setq dest (save-match-data (org-table-convert-refs-to-rc (match-string 1)))) @@ -3790,60 +4038,52 @@ With prefix ARG, apply the new formulas to the table." (marker-buffer pos))))) (goto-char pos) (org-table-force-dataline) - (when dest - (setq name (substring dest 1)) - (cond - ((string-match "^\\$[a-zA-Z][a-zA-Z0-9]*" dest) - (setq e (assoc name org-table-named-field-locations)) - (org-goto-line (nth 1 e)) - (org-table-goto-column (nth 2 e))) - ((string-match "^@\\([0-9]+\\)\\$\\([0-9]+\\)" dest) - (let ((l (string-to-number (match-string 1 dest))) - (c (string-to-number (match-string 2 dest)))) - (org-goto-line (aref org-table-dlines l)) - (org-table-goto-column c))) - (t (org-table-goto-column (string-to-number name)))) - (move-marker pos (point)) - (org-table-highlight-rectangle nil nil face2)) - (cond - ((equal dest match)) - ((not match)) - ((eq what 'range) - (condition-case nil - (save-excursion - (org-table-get-range match nil nil 'highlight)) - (error nil))) - ((setq e (assoc var org-table-named-field-locations)) - (org-goto-line (nth 1 e)) - (org-table-goto-column (nth 2 e)) - (org-table-highlight-rectangle (point) (point)) - (message "Named field, column %d of line %d" (nth 2 e) (nth 1 e))) - ((setq e (assoc var org-table-column-names)) - (org-table-goto-column (string-to-number (cdr e))) - (org-table-highlight-rectangle (point) (point)) - (goto-char (org-table-begin)) - (if (re-search-forward (concat "^[ \t]*| *! *.*?| *\\(" var "\\) *|") - (org-table-end) t) - (progn - (goto-char (match-beginning 1)) - (org-table-highlight-rectangle) - (message "Named column (column %s)" (cdr e))) - (user-error "Column name not found"))) - ((eq what 'column) - ;; column number - (org-table-goto-column (string-to-number (substring match 1))) - (org-table-highlight-rectangle (point) (point)) - (message "Column %s" (substring match 1))) - ((setq e (assoc var org-table-local-parameters)) - (goto-char (org-table-begin)) - (if (re-search-forward (concat "^[ \t]*| *\\$ *.*?| *\\(" var "=\\)") nil t) - (progn - (goto-char (match-beginning 1)) - (org-table-highlight-rectangle) - (message "Local parameter.")) - (user-error "Parameter not found"))) - (t + (let ((table-start + (if local org-table-current-begin-pos (org-table-begin)))) + (when dest + (setq name (substring dest 1)) + (cond + ((string-match-p "\\`\\$[a-zA-Z][a-zA-Z0-9]*" dest) + (org-table-goto-field dest)) + ((string-match-p "\\`@\\([1-9][0-9]*\\)\\$\\([1-9][0-9]*\\)\\'" + dest) + (org-table-goto-field dest)) + (t (org-table-goto-column (string-to-number name)))) + (move-marker pos (point)) + (org-table-highlight-rectangle nil nil face2)) (cond + ((equal dest match)) + ((not match)) + ((eq what 'range) + (ignore-errors (org-table-get-range match table-start nil 'highlight))) + ((setq e (assoc var org-table-named-field-locations)) + (org-table-goto-field var) + (org-table-highlight-rectangle) + (message "Named field, column %d of line %d" (nth 2 e) (nth 1 e))) + ((setq e (assoc var org-table-column-names)) + (org-table-goto-column (string-to-number (cdr e))) + (org-table-highlight-rectangle) + (goto-char table-start) + (if (re-search-forward (concat "^[ \t]*| *! *.*?| *\\(" var "\\) *|") + (org-table-end) t) + (progn + (goto-char (match-beginning 1)) + (org-table-highlight-rectangle) + (message "Named column (column %s)" (cdr e))) + (user-error "Column name not found"))) + ((eq what 'column) + ;; Column number. + (org-table-goto-column (string-to-number (substring match 1))) + (org-table-highlight-rectangle) + (message "Column %s" (substring match 1))) + ((setq e (assoc var org-table-local-parameters)) + (goto-char table-start) + (if (re-search-forward (concat "^[ \t]*| *\\$ *.*?| *\\(" var "=\\)") nil t) + (progn + (goto-char (match-beginning 1)) + (org-table-highlight-rectangle) + (message "Local parameter.")) + (user-error "Parameter not found"))) ((not var) (user-error "No reference at point")) ((setq e (assoc var org-table-formula-constants-local)) (message "Local Constant: $%s=%s in #+CONSTANTS line." @@ -3854,19 +4094,19 @@ With prefix ARG, apply the new formulas to the table." ((setq e (and (fboundp 'constants-get) (constants-get var))) (message "Constant: $%s=%s, from `constants.el'%s." var e (format " (%s units)" constants-unit-system))) - (t (user-error "Undefined name $%s" var))))) - (goto-char pos) - (when (and org-show-positions - (not (memq this-command '(org-table-fedit-scroll - org-table-fedit-scroll-down)))) - (push pos org-show-positions) - (push org-table-current-begin-pos org-show-positions) - (let ((min (apply 'min org-show-positions)) - (max (apply 'max org-show-positions))) - (set-window-start (selected-window) min) - (goto-char max) - (or (pos-visible-in-window-p max) - (set-window-start (selected-window) max)))) + (t (user-error "Undefined name $%s" var))) + (goto-char pos) + (when (and org-show-positions + (not (memq this-command '(org-table-fedit-scroll + org-table-fedit-scroll-down)))) + (push pos org-show-positions) + (push table-start org-show-positions) + (let ((min (apply 'min org-show-positions)) + (max (apply 'max org-show-positions))) + (set-window-start (selected-window) min) + (goto-char max) + (or (pos-visible-in-window-p max) + (set-window-start (selected-window) max))))) (select-window win)))) (defun org-table-force-dataline () @@ -3926,43 +4166,49 @@ Use COMMAND to do the motion, repeat if necessary to end up in a data line." (push ov org-table-rectangle-overlays))) (defun org-table-highlight-rectangle (&optional beg end face) - "Highlight rectangular region in a table." - (setq beg (or beg (point)) end (or end (point))) - (let ((b (min beg end)) - (e (max beg end)) - l1 c1 l2 c2 tmp) - (and (boundp 'org-show-positions) - (setq org-show-positions (cons b (cons e org-show-positions)))) - (goto-char (min beg end)) - (setq l1 (org-current-line) - c1 (org-table-current-column)) - (goto-char (max beg end)) - (setq l2 (org-current-line) - c2 (org-table-current-column)) - (if (> c1 c2) (setq tmp c1 c1 c2 c2 tmp)) - (org-goto-line l1) - (beginning-of-line 1) - (loop for line from l1 to l2 do - (when (looking-at org-table-dataline-regexp) - (org-table-goto-column c1) - (skip-chars-backward "^|\n") (setq beg (point)) - (org-table-goto-column c2) - (skip-chars-forward "^|\n") (setq end (point)) - (org-table-add-rectangle-overlay beg end face)) - (beginning-of-line 2)) - (goto-char b)) - (add-hook 'before-change-functions 'org-table-remove-rectangle-highlight)) - -(defun org-table-remove-rectangle-highlight (&rest ignore) + "Highlight rectangular region in a table. +When buffer positions BEG and END are provided, use them to +delimit the region to highlight. Otherwise, refer to point. Use +FACE, when non-nil, for the highlight." + (let* ((beg (or beg (point))) + (end (or end (point))) + (b (min beg end)) + (e (max beg end)) + (start-coordinates + (save-excursion + (goto-char b) + (cons (line-beginning-position) (org-table-current-column)))) + (end-coordinates + (save-excursion + (goto-char e) + (cons (line-beginning-position) (org-table-current-column))))) + (when (boundp 'org-show-positions) + (setq org-show-positions (cons b (cons e org-show-positions)))) + (goto-char (car start-coordinates)) + (let ((column-start (min (cdr start-coordinates) (cdr end-coordinates))) + (column-end (max (cdr start-coordinates) (cdr end-coordinates))) + (last-row (car end-coordinates))) + (while (<= (point) last-row) + (when (looking-at org-table-dataline-regexp) + (org-table-goto-column column-start) + (skip-chars-backward "^|\n") + (let ((p (point))) + (org-table-goto-column column-end) + (skip-chars-forward "^|\n") + (org-table-add-rectangle-overlay p (point) face))) + (forward-line))) + (goto-char (car start-coordinates))) + (add-hook 'before-change-functions #'org-table-remove-rectangle-highlight)) + +(defun org-table-remove-rectangle-highlight (&rest _ignore) "Remove the rectangle overlays." (unless org-inhibit-highlight-removal (remove-hook 'before-change-functions 'org-table-remove-rectangle-highlight) (mapc 'delete-overlay org-table-rectangle-overlays) (setq org-table-rectangle-overlays nil))) -(defvar org-table-coordinate-overlays nil +(defvar-local org-table-coordinate-overlays nil "Collects the coordinate grid overlays, so that they can be removed.") -(make-variable-buffer-local 'org-table-coordinate-overlays) (defun org-table-overlay-coordinates () "Add overlays to the table at point, to show row/column coordinates." @@ -4017,19 +4263,20 @@ Use COMMAND to do the motion, repeat if necessary to end up in a data line." ;;; The orgtbl minor mode ;; Define a minor mode which can be used in other modes in order to -;; integrate the org-mode table editor. - -;; This is really a hack, because the org-mode table editor uses several -;; keys which normally belong to the major mode, for example the TAB and -;; RET keys. Here is how it works: The minor mode defines all the keys -;; necessary to operate the table editor, but wraps the commands into a -;; function which tests if the cursor is currently inside a table. If that -;; is the case, the table editor command is executed. However, when any of -;; those keys is used outside a table, the function uses `key-binding' to -;; look up if the key has an associated command in another currently active -;; keymap (minor modes, major mode, global), and executes that command. -;; There might be problems if any of the keys used by the table editor is -;; otherwise used as a prefix key. +;; integrate the Org table editor. + +;; This is really a hack, because the Org table editor uses several +;; keys which normally belong to the major mode, for example the TAB +;; and RET keys. Here is how it works: The minor mode defines all the +;; keys necessary to operate the table editor, but wraps the commands +;; into a function which tests if the cursor is currently inside +;; a table. If that is the case, the table editor command is +;; executed. However, when any of those keys is used outside a table, +;; the function uses `key-binding' to look up if the key has an +;; associated command in another currently active keymap (minor modes, +;; major mode, global), and executes that command. There might be +;; problems if any of the keys used by the table editor is otherwise +;; used as a prefix key. ;; Another challenge is that the key binding for TAB can be tab or \C-i, ;; likewise the binding for RET can be return or \C-m. Orgtbl-mode @@ -4079,16 +4326,16 @@ Use COMMAND to do the motion, repeat if necessary to end up in a data line." ;; FIXME: maybe it should use emulation-mode-map-alists? (and c (setq minor-mode-map-alist (cons c (delq c minor-mode-map-alist))))) - (org-set-local (quote org-table-may-need-update) t) - (org-add-hook 'before-change-functions 'org-before-change-function - nil 'local) - (org-set-local 'org-old-auto-fill-inhibit-regexp - auto-fill-inhibit-regexp) - (org-set-local 'auto-fill-inhibit-regexp - (if auto-fill-inhibit-regexp - (concat orgtbl-line-start-regexp "\\|" - auto-fill-inhibit-regexp) - orgtbl-line-start-regexp)) + (setq-local org-table-may-need-update t) + (add-hook 'before-change-functions 'org-before-change-function + nil 'local) + (setq-local org-old-auto-fill-inhibit-regexp + auto-fill-inhibit-regexp) + (setq-local auto-fill-inhibit-regexp + (if auto-fill-inhibit-regexp + (concat orgtbl-line-start-regexp "\\|" + auto-fill-inhibit-regexp) + orgtbl-line-start-regexp)) (add-to-invisibility-spec '(org-cwidth)) (when (fboundp 'font-lock-add-keywords) (font-lock-add-keywords nil orgtbl-extra-font-lock-keywords) @@ -4188,27 +4435,26 @@ to execute outside of tables." cmd (orgtbl-make-binding fun nfunc key)) (org-defkey orgtbl-mode-map key cmd)) - ;; Special treatment needed for TAB and RET + ;; Special treatment needed for TAB, RET and DEL (org-defkey orgtbl-mode-map [(return)] (orgtbl-make-binding 'orgtbl-ret 100 [(return)] "\C-m")) (org-defkey orgtbl-mode-map "\C-m" (orgtbl-make-binding 'orgtbl-ret 101 "\C-m" [(return)])) - (org-defkey orgtbl-mode-map [(tab)] (orgtbl-make-binding 'orgtbl-tab 102 [(tab)] "\C-i")) (org-defkey orgtbl-mode-map "\C-i" (orgtbl-make-binding 'orgtbl-tab 103 "\C-i" [(tab)])) - (org-defkey orgtbl-mode-map [(shift tab)] (orgtbl-make-binding 'org-table-previous-field 104 [(shift tab)] [(tab)] "\C-i")) + (org-defkey orgtbl-mode-map [backspace] + (orgtbl-make-binding 'org-delete-backward-char 109 + [backspace] (kbd "DEL"))) - - (unless (featurep 'xemacs) - (org-defkey orgtbl-mode-map [S-iso-lefttab] - (orgtbl-make-binding 'org-table-previous-field 107 - [S-iso-lefttab] [backtab] [(shift tab)] - [(tab)] "\C-i"))) + (org-defkey orgtbl-mode-map [S-iso-lefttab] + (orgtbl-make-binding 'org-table-previous-field 107 + [S-iso-lefttab] [backtab] [(shift tab)] + [(tab)] "\C-i")) (org-defkey orgtbl-mode-map [backtab] (orgtbl-make-binding 'org-table-previous-field 108 @@ -4290,7 +4536,10 @@ to execute outside of tables." org-table-toggle-coordinate-overlays :active (org-at-table-p) :keys "C-c }" :style toggle :selected org-table-overlay-coordinates] - )) + "--" + ("Plot" + ["Ascii plot" orgtbl-ascii-plot :active (org-at-table-p) :keys "C-c \" a"] + ["Gnuplot" org-plot/gnuplot :active (org-at-table-p) :keys "C-c \" g"]))) t)) (defun orgtbl-ctrl-c-ctrl-c (arg) @@ -4316,7 +4565,6 @@ With prefix arg, also recompute table." (when (orgtbl-send-table 'maybe) (run-hooks 'orgtbl-after-send-table-hook))) ((eq action 'recalc) - (org-table-set-constants) (save-excursion (beginning-of-line 1) (skip-chars-backward " \r\n\t") @@ -4325,7 +4573,7 @@ With prefix arg, also recompute table." (t (let (orgtbl-mode) (call-interactively (key-binding "\C-c\C-c"))))))) -(defun orgtbl-create-or-convert-from-region (arg) +(defun orgtbl-create-or-convert-from-region (_arg) "Create table or convert region to table, if no conflicting binding. This installs the table binding `C-c |', but only if there is no conflicting binding to this key outside orgtbl-mode." @@ -4369,11 +4617,9 @@ overwritten, and the table is not marked as requiring realignment." (org-table-blank-field)) t) (eq N 1) - (looking-at "[^|\n]* +|")) + (looking-at "[^|\n]* \\( \\)|")) (let (org-table-may-need-update) - (goto-char (1- (match-end 0))) - (org-delete-backward-char 1) - (goto-char (match-beginning 0)) + (delete-region (match-beginning 1) (match-end 1)) (self-insert-command N)) (setq org-table-may-need-update t) (let* (orgtbl-mode @@ -4398,6 +4644,7 @@ overwritten, and the table is not marked as requiring realignment." (setq org-self-insert-command-undo-counter (1+ org-self-insert-command-undo-counter)))))))) +;;;###autoload (defvar orgtbl-exp-regexp "^\\([-+]?[0-9][0-9.]*\\)[eE]\\([-+]?[0-9]+\\)$" "Regular expression matching exponentials as produced by calc.") @@ -4418,23 +4665,24 @@ a radio table." (beginning-of-line 0))) rtn))) -(defun orgtbl-send-replace-tbl (name txt) - "Find and replace table NAME with TXT." +(defun orgtbl-send-replace-tbl (name text) + "Find and replace table NAME with TEXT." (save-excursion (goto-char (point-min)) - (unless (re-search-forward - (concat "BEGIN +RECEIVE +ORGTBL +" name "\\([ \t]\\|$\\)") nil t) - (user-error "Don't know where to insert translated table")) - (goto-char (match-beginning 0)) - (beginning-of-line 2) - (save-excursion - (let ((beg (point))) - (unless (re-search-forward - (concat "END +RECEIVE +ORGTBL +" name) nil t) - (user-error "Cannot find end of insertion region")) - (beginning-of-line 1) - (delete-region beg (point)))) - (insert txt "\n"))) + (let* ((location-flag nil) + (name (regexp-quote name)) + (begin-re (format "BEGIN +RECEIVE +ORGTBL +%s\\([ \t]\\|$\\)" name)) + (end-re (format "END +RECEIVE +ORGTBL +%s\\([ \t]\\|$\\)" name))) + (while (re-search-forward begin-re nil t) + (unless location-flag (setq location-flag t)) + (let ((beg (line-beginning-position 2))) + (unless (re-search-forward end-re nil t) + (user-error "Cannot find end of receiver location at %d" beg)) + (beginning-of-line) + (delete-region beg (point)) + (insert text "\n"))) + (unless location-flag + (user-error "No valid receiver location found in the buffer"))))) ;;;###autoload (defun org-table-to-lisp (&optional txt) @@ -4442,76 +4690,43 @@ a radio table." The structure will be a list. Each item is either the symbol `hline' for a horizontal separator line, or a list of field values as strings. The table is taken from the parameter TXT, or from the buffer at point." - (unless txt - (unless (org-at-table-p) - (user-error "No table at point"))) - (let* ((txt (or txt - (buffer-substring-no-properties (org-table-begin) - (org-table-end)))) - (lines (org-split-string txt "[ \t]*\n[ \t]*"))) - - (mapcar - (lambda (x) - (if (string-match org-table-hline-regexp x) - 'hline - (org-split-string (org-trim x) "\\s-*|\\s-*"))) - lines))) + (unless (or txt (org-at-table-p)) (user-error "No table at point")) + (let ((txt (or txt + (buffer-substring-no-properties (org-table-begin) + (org-table-end))))) + (mapcar (lambda (x) + (if (string-match org-table-hline-regexp x) 'hline + (org-split-string (org-trim x) "\\s-*|\\s-*"))) + (org-split-string txt "[ \t]*\n[ \t]*")))) (defun orgtbl-send-table (&optional maybe) - "Send a transformed version of this table to the receiver position. -With argument MAYBE, fail quietly if no transformation is defined for -this table." + "Send a transformed version of table at point to the receiver position. +With argument MAYBE, fail quietly if no transformation is defined +for this table." (interactive) (catch 'exit (unless (org-at-table-p) (user-error "Not at a table")) ;; when non-interactive, we assume align has just happened. - (when (org-called-interactively-p 'any) (org-table-align)) + (when (called-interactively-p 'any) (org-table-align)) (let ((dests (orgtbl-gather-send-defs)) - (txt (buffer-substring-no-properties (org-table-begin) - (org-table-end))) + (table (org-table-to-lisp + (buffer-substring-no-properties (org-table-begin) + (org-table-end)))) (ntbl 0)) - (unless dests (if maybe (throw 'exit nil) - (user-error "Don't know how to transform this table"))) + (unless dests + (if maybe (throw 'exit nil) + (user-error "Don't know how to transform this table"))) (dolist (dest dests) - (let* ((name (plist-get dest :name)) - (transform (plist-get dest :transform)) - (params (plist-get dest :params)) - (skip (plist-get params :skip)) - (skipcols (plist-get params :skipcols)) - (no-escape (plist-get params :no-escape)) - beg - (lines (org-table-clean-before-export - (nthcdr (or skip 0) - (org-split-string txt "[ \t]*\n[ \t]*")))) - (i0 (if org-table-clean-did-remove-column 2 1)) - (lines (if no-escape lines - (mapcar (lambda(l) (replace-regexp-in-string - "\\([&%#_^]\\)" "\\\\\\1{}" l)) lines))) - (table (mapcar - (lambda (x) - (if (string-match org-table-hline-regexp x) - 'hline - (org-remove-by-index - (org-split-string (org-trim x) "\\s-*|\\s-*") - skipcols i0))) - lines)) - (fun (if (= i0 2) 'cdr 'identity)) - (org-table-last-alignment - (org-remove-by-index (funcall fun org-table-last-alignment) - skipcols i0)) - (org-table-last-column-widths - (org-remove-by-index (funcall fun org-table-last-column-widths) - skipcols i0)) - (txt (if (fboundp transform) - (funcall transform table params) - (user-error "No such transformation function %s" transform)))) - (orgtbl-send-replace-tbl name txt)) - (setq ntbl (1+ ntbl))) + (let ((name (plist-get dest :name)) + (transform (plist-get dest :transform)) + (params (plist-get dest :params))) + (unless (fboundp transform) + (user-error "No such transformation function %s" transform)) + (orgtbl-send-replace-tbl name (funcall transform table params))) + (cl-incf ntbl)) (message "Table converted and installed at %d receiver location%s" ntbl (if (> ntbl 1) "s" "")) - (if (> ntbl 0) - ntbl - nil)))) + (and (> ntbl 0) ntbl)))) (defun org-remove-by-index (list indices &optional i0) "Remove the elements in LIST with indices in INDICES. @@ -4561,356 +4776,524 @@ First element has index 0, or I0 if given." (insert txt) (goto-char pos))) -;; Dynamically bound input and output for table formatting. -(defvar *orgtbl-table* nil - "Carries the current table through formatting routines.") -(defvar *orgtbl-rtn* nil - "Formatting routines push the output lines here.") -;; Formatting parameters for the current table section. -(defvar *orgtbl-hline* nil "Text used for horizontal lines.") -(defvar *orgtbl-sep* nil "Text used as a column separator.") -(defvar *orgtbl-default-fmt* nil "Default format for each entry.") -(defvar *orgtbl-fmt* nil "Format for each entry.") -(defvar *orgtbl-efmt* nil "Format for numbers.") -(defvar *orgtbl-lfmt* nil "Format for an entire line, overrides fmt.") -(defvar *orgtbl-llfmt* nil "Specializes lfmt for the last row.") -(defvar *orgtbl-lstart* nil "Text starting a row.") -(defvar *orgtbl-llstart* nil "Specializes lstart for the last row.") -(defvar *orgtbl-lend* nil "Text ending a row.") -(defvar *orgtbl-llend* nil "Specializes lend for the last row.") - -(defsubst orgtbl-get-fmt (fmt i) - "Retrieve the format from FMT corresponding to the Ith column." - (if (and (not (functionp fmt)) (consp fmt)) - (plist-get fmt i) - fmt)) - -(defsubst orgtbl-apply-fmt (fmt &rest args) - "Apply format FMT to arguments ARGS. -When FMT is nil, return the first argument from ARGS." - (cond ((functionp fmt) (apply fmt args)) - (fmt (apply 'format fmt args)) - (args (car args)) - (t args))) - -(defsubst orgtbl-eval-str (str) - "If STR is a function, evaluate it with no arguments." - (if (functionp str) - (funcall str) - str)) - -(defun orgtbl-format-line (line) - "Format LINE as a table row." - (if (eq line 'hline) (if *orgtbl-hline* (push *orgtbl-hline* *orgtbl-rtn*)) - (let* ((i 0) - (line - (mapcar - (lambda (f) - (setq i (1+ i)) - (let* ((efmt (orgtbl-get-fmt *orgtbl-efmt* i)) - (f (if (and efmt (string-match orgtbl-exp-regexp f)) - (orgtbl-apply-fmt efmt (match-string 1 f) - (match-string 2 f)) - f))) - (orgtbl-apply-fmt (or (orgtbl-get-fmt *orgtbl-fmt* i) - *orgtbl-default-fmt*) - f))) - line))) - (push (if *orgtbl-lfmt* - (apply #'orgtbl-apply-fmt *orgtbl-lfmt* line) - (concat (orgtbl-eval-str *orgtbl-lstart*) - (mapconcat 'identity line *orgtbl-sep*) - (orgtbl-eval-str *orgtbl-lend*))) - *orgtbl-rtn*)))) - -(defun orgtbl-format-section (section-stopper) - "Format lines until the first occurrence of SECTION-STOPPER." - (let (prevline) - (progn - (while (not (eq (car *orgtbl-table*) section-stopper)) - (if prevline (orgtbl-format-line prevline)) - (setq prevline (pop *orgtbl-table*))) - (if prevline (let ((*orgtbl-lstart* *orgtbl-llstart*) - (*orgtbl-lend* *orgtbl-llend*) - (*orgtbl-lfmt* *orgtbl-llfmt*)) - (orgtbl-format-line prevline)))))) - ;;;###autoload -(defun orgtbl-to-generic (table params &optional backend) +(defun orgtbl-to-generic (table params) "Convert the orgtbl-mode TABLE to some other format. + This generic routine can be used for many standard cases. -TABLE is a list, each entry either the symbol `hline' for a horizontal -separator line, or a list of fields for that line. -PARAMS is a property list of parameters that can influence the conversion. -A third optional argument BACKEND can be used to convert the content of -the cells using a specific export back-end. -For the generic converter, some parameters are obligatory: you need to -specify either :lfmt, or all of (:lstart :lend :sep). +TABLE is a list, each entry either the symbol `hline' for +a horizontal separator line, or a list of fields for that +line. PARAMS is a property list of parameters that can +influence the conversion. Valid parameters are: -:splice When set to t, return only table body lines, don't wrap - them into :tstart and :tend. Default is nil. When :splice - is non-nil, this also means that the exporter should not look - for and interpret header and footer sections. +:backend, :raw + + Export back-end used as a basis to transcode elements of the + table, when no specific parameter applies to it. It is also + used to translate cells contents. You can prevent this by + setting :raw property to a non-nil value. + +:splice + + When non-nil, only convert rows, not the table itself. This is + equivalent to setting to the empty string both :tstart + and :tend, which see. -:hline String to be inserted on horizontal separation lines. - May be nil to ignore hlines. +:skip -:sep Separator between two fields -:remove-nil-lines Do not include lines that evaluate to nil. + When set to an integer N, skip the first N lines of the table. + Horizontal separation lines do count for this parameter! + +:skipcols + + List of columns that should be skipped. If the table has + a column with calculation marks, that column is automatically + discarded beforehand. + +:hline + + String to be inserted on horizontal separation lines. May be + nil to ignore these lines altogether. + +:sep + + Separator between two fields, as a string. Each in the following group may be either a string or a function of no arguments returning a string: -:tstart String to start the table. Ignored when :splice is t. -:tend String to end the table. Ignored when :splice is t. -:lstart String to start a new table line. -:llstart String to start the last table line, defaults to :lstart. -:lend String to end a table line -:llend String to end the last table line, defaults to :lend. - -Each in the following group may be a string, a function of one -argument (the field or line) returning a string, or a plist -mapping columns to either of the above: - -:lfmt Format for entire line, with enough %s to capture all fields. - If this is present, :lstart, :lend, and :sep are ignored. -:llfmt Format for the entire last line, defaults to :lfmt. -:fmt A format to be used to wrap the field, should contain - %s for the original field value. For example, to wrap - everything in dollars, you could use :fmt \"$%s$\". - This may also be a property list with column numbers and - formats. For example :fmt (2 \"$%s$\" 4 \"%s%%\") -:hlstart :hllstart :hlend :hllend :hlsep :hlfmt :hllfmt :hfmt - Same as above, specific for the header lines in the table. - All lines before the first hline are treated as header. - If any of these is not present, the data line value is used. +:tstart, :tend + + Strings to start and end the table. Ignored when :splice is t. + +:lstart, :lend + + Strings to start and end a new table line. + +:llstart, :llend + + Strings to start and end the last table line. Default, + respectively, to :lstart and :lend. + +Each in the following group may be a string or a function of one +argument (either the cells in the current row, as a list of +strings, or the current cell) returning a string: + +:lfmt + + Format string for an entire row, with enough %s to capture all + fields. When non-nil, :lstart, :lend, and :sep are ignored. + +:llfmt + + Format for the entire last line, defaults to :lfmt. + +:fmt + + A format to be used to wrap the field, should contain %s for + the original field value. For example, to wrap everything in + dollars, you could use :fmt \"$%s$\". This may also be + a property list with column numbers and format strings, or + functions, e.g., + + (:fmt (2 \"$%s$\" 4 (lambda (c) (format \"$%s$\" c)))) + +:hlstart :hllstart :hlend :hllend :hsep :hlfmt :hllfmt :hfmt + + Same as above, specific for the header lines in the table. + All lines before the first hline are treated as header. If + any of these is not present, the data line value is used. This may be either a string or a function of two arguments: -:efmt Use this format to print numbers with exponentials. - The format should have %s twice for inserting mantissa - and exponent, for example \"%s\\\\times10^{%s}\". This - may also be a property list with column numbers and - formats. :fmt will still be applied after :efmt. - -In addition to this, the parameters :skip and :skipcols are always handled -directly by `orgtbl-send-table'. See manual." - (let* ((splicep (plist-get params :splice)) - (hline (plist-get params :hline)) - (skipheadrule (plist-get params :skipheadrule)) - (remove-nil-linesp (plist-get params :remove-nil-lines)) - (remove-newlines (plist-get params :remove-newlines)) - (*orgtbl-hline* hline) - (*orgtbl-table* table) - (*orgtbl-sep* (plist-get params :sep)) - (*orgtbl-efmt* (plist-get params :efmt)) - (*orgtbl-lstart* (plist-get params :lstart)) - (*orgtbl-llstart* (or (plist-get params :llstart) *orgtbl-lstart*)) - (*orgtbl-lend* (plist-get params :lend)) - (*orgtbl-llend* (or (plist-get params :llend) *orgtbl-lend*)) - (*orgtbl-lfmt* (plist-get params :lfmt)) - (*orgtbl-llfmt* (or (plist-get params :llfmt) *orgtbl-lfmt*)) - (*orgtbl-fmt* (plist-get params :fmt)) - *orgtbl-rtn*) - ;; Convert cells content to backend BACKEND - (when backend - (setq *orgtbl-table* - (mapcar - (lambda(r) - (if (listp r) - (mapcar - (lambda (c) - (org-trim (org-export-string-as c backend t '(:with-tables t)))) - r) - r)) - *orgtbl-table*))) - ;; Put header - (unless splicep - (when (plist-member params :tstart) - (let ((tstart (orgtbl-eval-str (plist-get params :tstart)))) - (if tstart (push tstart *orgtbl-rtn*))))) - ;; If we have a heading, format it and handle the trailing hline. - (if (and (not splicep) - (or (consp (car *orgtbl-table*)) - (consp (nth 1 *orgtbl-table*))) - (memq 'hline (cdr *orgtbl-table*))) - (progn - (when (eq 'hline (car *orgtbl-table*)) - ;; There is a hline before the first data line - (and hline (push hline *orgtbl-rtn*)) - (pop *orgtbl-table*)) - (let* ((*orgtbl-lstart* (or (plist-get params :hlstart) - *orgtbl-lstart*)) - (*orgtbl-llstart* (or (plist-get params :hllstart) - *orgtbl-llstart*)) - (*orgtbl-lend* (or (plist-get params :hlend) *orgtbl-lend*)) - (*orgtbl-llend* (or (plist-get params :hllend) - (plist-get params :hlend) *orgtbl-llend*)) - (*orgtbl-lfmt* (or (plist-get params :hlfmt) *orgtbl-lfmt*)) - (*orgtbl-llfmt* (or (plist-get params :hllfmt) - (plist-get params :hlfmt) *orgtbl-llfmt*)) - (*orgtbl-sep* (or (plist-get params :hlsep) *orgtbl-sep*)) - (*orgtbl-fmt* (or (plist-get params :hfmt) *orgtbl-fmt*))) - (orgtbl-format-section 'hline)) - (if (and hline (not skipheadrule)) (push hline *orgtbl-rtn*)) - (pop *orgtbl-table*))) - ;; Now format the main section. - (orgtbl-format-section nil) - (unless splicep - (when (plist-member params :tend) - (let ((tend (orgtbl-eval-str (plist-get params :tend)))) - (if tend (push tend *orgtbl-rtn*))))) - (mapconcat (if remove-newlines - (lambda (tend) - (replace-regexp-in-string "[\n\r\t\f]" "\\\\n" tend)) - 'identity) - (nreverse (if remove-nil-linesp - (remq nil *orgtbl-rtn*) - *orgtbl-rtn*)) "\n"))) +:efmt + + Use this format to print numbers with exponential. The format + should have %s twice for inserting mantissa and exponent, for + example \"%s\\\\times10^{%s}\". This may also be a property + list with column numbers and format strings or functions. + :fmt will still be applied after :efmt." + ;; Make sure `org-export-create-backend' is available. + (require 'ox) + (let* ((backend (plist-get params :backend)) + (custom-backend + ;; Build a custom back-end according to PARAMS. Before + ;; defining a translator, check if there is anything to do. + ;; When there isn't, let BACKEND handle the element. + (org-export-create-backend + :parent (or backend 'org) + :transcoders + `((table . ,(org-table--to-generic-table params)) + (table-row . ,(org-table--to-generic-row params)) + (table-cell . ,(org-table--to-generic-cell params)) + ;; Macros are not going to be expanded. However, no + ;; regular back-end has a transcoder for them. We + ;; provide one so they are not ignored, but displayed + ;; as-is instead. + (macro . (lambda (m c i) (org-element-macro-interpreter m nil)))))) + data info) + ;; Store TABLE as Org syntax in DATA. Tolerate non-string cells. + ;; Initialize communication channel in INFO. + (with-temp-buffer + (let ((org-inhibit-startup t)) (org-mode)) + (let ((standard-output (current-buffer)) + (org-element-use-cache nil)) + (dolist (e table) + (cond ((eq e 'hline) (princ "|--\n")) + ((consp e) + (princ "| ") (dolist (c e) (princ c) (princ " |")) + (princ "\n"))))) + ;; Add back-end specific filters, but not user-defined ones. In + ;; particular, make sure to call parse-tree filters on the + ;; table. + (setq info + (let ((org-export-filters-alist nil)) + (org-export-install-filters + (org-combine-plists + (org-export-get-environment backend nil params) + `(:back-end ,(org-export-get-backend backend)))))) + (setq data + (org-export-filter-apply-functions + (plist-get info :filter-parse-tree) + (org-element-map (org-element-parse-buffer) 'table + #'identity nil t) + info))) + (when (and backend (symbolp backend) (not (org-export-get-backend backend))) + (user-error "Unknown :backend value")) + (when (or (not backend) (plist-get info :raw)) (require 'ox-org)) + ;; Handle :skip parameter. + (let ((skip (plist-get info :skip))) + (when skip + (unless (wholenump skip) (user-error "Wrong :skip value")) + (let ((n 0)) + (org-element-map data 'table-row + (lambda (row) + (if (>= n skip) t + (org-element-extract-element row) + (cl-incf n) + nil)) + nil t)))) + ;; Handle :skipcols parameter. + (let ((skipcols (plist-get info :skipcols))) + (when skipcols + (unless (consp skipcols) (user-error "Wrong :skipcols value")) + (org-element-map data 'table + (lambda (table) + (let ((specialp (org-export-table-has-special-column-p table))) + (dolist (row (org-element-contents table)) + (when (eq (org-element-property :type row) 'standard) + (let ((c 1)) + (dolist (cell (nthcdr (if specialp 1 0) + (org-element-contents row))) + (when (memq c skipcols) + (org-element-extract-element cell)) + (cl-incf c)))))))))) + ;; Since we are going to export using a low-level mechanism, + ;; ignore special column and special rows manually. + (let ((special? (org-export-table-has-special-column-p data)) + ignore) + (org-element-map data (if special? '(table-cell table-row) 'table-row) + (lambda (datum) + (when (if (eq (org-element-type datum) 'table-row) + (org-export-table-row-is-special-p datum nil) + (org-export-first-sibling-p datum nil)) + (push datum ignore)))) + (setq info (plist-put info :ignore-list ignore))) + ;; We use a low-level mechanism to export DATA so as to skip all + ;; usual pre-processing and post-processing, i.e., hooks, Babel + ;; code evaluation, include keywords and macro expansion. Only + ;; back-end specific filters are retained. + (let ((output (org-export-data-with-backend data custom-backend info))) + ;; Remove final newline. + (if (org-string-nw-p output) (substring-no-properties output 0 -1) "")))) + +(defun org-table--generic-apply (value name &optional with-cons &rest args) + (cond ((null value) nil) + ((functionp value) `(funcall ',value ,@args)) + ((stringp value) + (cond ((consp (car args)) `(apply #'format ,value ,@args)) + (args `(format ,value ,@args)) + (t value))) + ((and with-cons (consp value)) + `(let ((val (cadr (memq column ',value)))) + (cond ((null val) contents) + ((stringp val) (format val ,@args)) + ((functionp val) (funcall val ,@args)) + (t (user-error "Wrong %s value" ,name))))) + (t (user-error "Wrong %s value" name)))) + +(defun org-table--to-generic-table (params) + "Return custom table transcoder according to PARAMS. +PARAMS is a plist. See `orgtbl-to-generic' for more +information." + (let ((backend (plist-get params :backend)) + (splice (plist-get params :splice)) + (tstart (plist-get params :tstart)) + (tend (plist-get params :tend))) + `(lambda (table contents info) + (concat + ,(and tstart (not splice) + `(concat ,(org-table--generic-apply tstart ":tstart") "\n")) + ,(if (or (not backend) tstart tend splice) 'contents + `(org-export-with-backend ',backend table contents info)) + ,(org-table--generic-apply (and (not splice) tend) ":tend"))))) + +(defun org-table--to-generic-row (params) + "Return custom table row transcoder according to PARAMS. +PARAMS is a plist. See `orgtbl-to-generic' for more +information." + (let* ((backend (plist-get params :backend)) + (lstart (plist-get params :lstart)) + (llstart (plist-get params :llstart)) + (hlstart (plist-get params :hlstart)) + (hllstart (plist-get params :hllstart)) + (lend (plist-get params :lend)) + (llend (plist-get params :llend)) + (hlend (plist-get params :hlend)) + (hllend (plist-get params :hllend)) + (lfmt (plist-get params :lfmt)) + (llfmt (plist-get params :llfmt)) + (hlfmt (plist-get params :hlfmt)) + (hllfmt (plist-get params :hllfmt))) + `(lambda (row contents info) + (if (eq (org-element-property :type row) 'rule) + ,(cond + ((plist-member params :hline) + (org-table--generic-apply (plist-get params :hline) ":hline")) + (backend `(org-export-with-backend ',backend row nil info))) + (let ((headerp ,(and (or hlfmt hlstart hlend) + '(org-export-table-row-in-header-p row info))) + (last-header-p + ,(and (or hllfmt hllstart hllend) + '(org-export-table-row-ends-header-p row info))) + (lastp (not (org-export-get-next-element row info)))) + (when contents + ;; Check if we can apply `:lfmt', `:llfmt', `:hlfmt', or + ;; `:hllfmt' to CONTENTS. Otherwise, fallback on + ;; `:lstart', `:lend' and their relatives. + ,(let ((cells + '(org-element-map row 'table-cell + (lambda (cell) + ;; Export all cells, without separators. + ;; + ;; Use `org-export-data-with-backend' + ;; instead of `org-export-data' to eschew + ;; cached values, which + ;; ignore :orgtbl-ignore-sep parameter. + (org-export-data-with-backend + cell + (plist-get info :back-end) + (org-combine-plists info '(:orgtbl-ignore-sep t)))) + info))) + `(cond + ,(and hllfmt + `(last-header-p ,(org-table--generic-apply + hllfmt ":hllfmt" nil cells))) + ,(and hlfmt + `(headerp ,(org-table--generic-apply + hlfmt ":hlfmt" nil cells))) + ,(and llfmt + `(lastp ,(org-table--generic-apply + llfmt ":llfmt" nil cells))) + (t + ,(if lfmt (org-table--generic-apply lfmt ":lfmt" nil cells) + `(concat + (cond + ,(and + (or hllstart hllend) + `(last-header-p + (concat + ,(org-table--generic-apply hllstart ":hllstart") + contents + ,(org-table--generic-apply hllend ":hllend")))) + ,(and + (or hlstart hlend) + `(headerp + (concat + ,(org-table--generic-apply hlstart ":hlstart") + contents + ,(org-table--generic-apply hlend ":hlend")))) + ,(and + (or llstart llend) + `(lastp + (concat + ,(org-table--generic-apply llstart ":llstart") + contents + ,(org-table--generic-apply llend ":llend")))) + (t + ,(cond + ((or lstart lend) + `(concat + ,(org-table--generic-apply lstart ":lstart") + contents + ,(org-table--generic-apply lend ":lend"))) + (backend + `(org-export-with-backend + ',backend row contents info)) + (t 'contents))))))))))))))) + +(defun org-table--to-generic-cell (params) + "Return custom table cell transcoder according to PARAMS. +PARAMS is a plist. See `orgtbl-to-generic' for more +information." + (let* ((backend (plist-get params :backend)) + (efmt (plist-get params :efmt)) + (fmt (plist-get params :fmt)) + (hfmt (plist-get params :hfmt)) + (sep (plist-get params :sep)) + (hsep (plist-get params :hsep))) + `(lambda (cell contents info) + ;; Make sure that contents are exported as Org data when :raw + ;; parameter is non-nil. + ,(when (and backend (plist-get params :raw)) + `(setq contents + ;; Since we don't know what are the pseudo object + ;; types defined in backend, we cannot pass them to + ;; `org-element-interpret-data'. As a consequence, + ;; they will be treated as pseudo elements, and will + ;; have newlines appended instead of spaces. + ;; Therefore, we must make sure :post-blank value is + ;; really turned into spaces. + (replace-regexp-in-string + "\n" " " + (org-trim + (org-element-interpret-data + (org-element-contents cell)))))) + + (let ((headerp ,(and (or hfmt hsep) + '(org-export-table-row-in-header-p + (org-export-get-parent-element cell) info))) + (column + ;; Call costly `org-export-table-cell-address' only if + ;; absolutely necessary, i.e., if one + ;; of :fmt :efmt :hfmt has a "plist type" value. + ,(and (cl-some (lambda (v) (integerp (car-safe v))) + (list efmt hfmt fmt)) + '(1+ (cdr (org-export-table-cell-address cell info)))))) + (when contents + ;; Check if we can apply `:efmt' on CONTENTS. + ,(when efmt + `(when (string-match orgtbl-exp-regexp contents) + (let ((mantissa (match-string 1 contents)) + (exponent (match-string 2 contents))) + (setq contents ,(org-table--generic-apply + efmt ":efmt" t 'mantissa 'exponent))))) + ;; Check if we can apply FMT (or HFMT) on CONTENTS. + (cond + ,(and hfmt `(headerp (setq contents ,(org-table--generic-apply + hfmt ":hfmt" t 'contents)))) + ,(and fmt `(t (setq contents ,(org-table--generic-apply + fmt ":fmt" t 'contents)))))) + ;; If a separator is provided, use it instead of BACKEND's. + ;; Separators are ignored when LFMT (or equivalent) is + ;; provided. + ,(cond + ((or hsep sep) + `(if (or ,(and (not sep) '(not headerp)) + (plist-get info :orgtbl-ignore-sep) + (not (org-export-get-next-element cell info))) + ,(if (not backend) 'contents + `(org-export-with-backend ',backend cell contents info)) + (concat contents + ,(if (and sep hsep) `(if headerp ,hsep ,sep) + (or hsep sep))))) + (backend `(org-export-with-backend ',backend cell contents info)) + (t 'contents)))))) ;;;###autoload (defun orgtbl-to-tsv (table params) "Convert the orgtbl-mode table to TAB separated material." (orgtbl-to-generic table (org-combine-plists '(:sep "\t") params))) + ;;;###autoload (defun orgtbl-to-csv (table params) "Convert the orgtbl-mode table to CSV material. This does take care of the proper quoting of fields with comma or quotes." - (orgtbl-to-generic table (org-combine-plists - '(:sep "," :fmt org-quote-csv-field) - params))) + (orgtbl-to-generic table + (org-combine-plists '(:sep "," :fmt org-quote-csv-field) + params))) ;;;###autoload (defun orgtbl-to-latex (table params) "Convert the orgtbl-mode TABLE to LaTeX. -TABLE is a list, each entry either the symbol `hline' for a horizontal -separator line, or a list of fields for that line. -PARAMS is a property list of parameters that can influence the conversion. -Supports all parameters from `orgtbl-to-generic'. Most important for -LaTeX are: - -:splice When set to t, return only table body lines, don't wrap - them into a tabular environment. Default is nil. - -:fmt A format to be used to wrap the field, should contain %s for the - original field value. For example, to wrap everything in dollars, - use :fmt \"$%s$\". This may also be a property list with column - numbers and formats. For example :fmt (2 \"$%s$\" 4 \"%s%%\") - The format may also be a function that formats its one argument. - -:efmt Format for transforming numbers with exponentials. The format - should have %s twice for inserting mantissa and exponent, for - example \"%s\\\\times10^{%s}\". LaTeX default is \"%s\\\\,(%s)\". - This may also be a property list with column numbers and formats. - The format may also be a function that formats its two arguments. - -:llend If you find too much space below the last line of a table, - pass a value of \"\" for :llend to suppress the final \\\\. - -The general parameters :skip and :skipcols have already been applied when -this function is called." - (let* ((alignment (mapconcat (lambda (x) (if x "r" "l")) - org-table-last-alignment "")) - (params2 - (list - :tstart (concat "\\begin{tabular}{" alignment "}") - :tend "\\end{tabular}" - :lstart "" :lend " \\\\" :sep " & " - :efmt "%s\\,(%s)" :hline "\\hline"))) - (require 'ox-latex) - (orgtbl-to-generic table (org-combine-plists params2 params) 'latex))) + +TABLE is a list, each entry either the symbol `hline' for +a horizontal separator line, or a list of fields for that line. +PARAMS is a property list of parameters that can influence the +conversion. All parameters from `orgtbl-to-generic' are +supported. It is also possible to use the following ones: + +:booktabs + + When non-nil, use formal \"booktabs\" style. + +:environment + + Specify environment to use, as a string. If you use + \"longtable\", you may also want to specify :language property, + as a string, to get proper continuation strings." + (require 'ox-latex) + (orgtbl-to-generic + table + (org-combine-plists + ;; Provide sane default values. + (list :backend 'latex + :latex-default-table-mode 'table + :latex-tables-centered nil + :latex-tables-booktabs (plist-get params :booktabs) + :latex-table-scientific-notation nil + :latex-default-table-environment + (or (plist-get params :environment) "tabular")) + params))) ;;;###autoload (defun orgtbl-to-html (table params) "Convert the orgtbl-mode TABLE to HTML. -TABLE is a list, each entry either the symbol `hline' for a horizontal -separator line, or a list of fields for that line. -PARAMS is a property list of parameters that can influence the conversion. -Currently this function recognizes the following parameters: -:splice When set to t, return only table body lines, don't wrap - them into a <table> environment. Default is nil. +TABLE is a list, each entry either the symbol `hline' for +a horizontal separator line, or a list of fields for that line. +PARAMS is a property list of parameters that can influence the +conversion. All parameters from `orgtbl-to-generic' are +supported. It is also possible to use the following one: + +:attributes -The general parameters :skip and :skipcols have already been applied when -this function is called. The function does *not* use `orgtbl-to-generic', -so you cannot specify parameters for it." + Attributes and values, as a plist, which will be used in + <table> tag." (require 'ox-html) - (let ((output (org-export-string-as - (orgtbl-to-orgtbl table nil) 'html t '(:with-tables t)))) - (if (not (plist-get params :splice)) output - (org-trim - (replace-regexp-in-string - "\\`<table .*>\n" "" - (replace-regexp-in-string "</table>\n*\\'" "" output)))))) + (orgtbl-to-generic + table + (org-combine-plists + ;; Provide sane default values. + (list :backend 'html + :html-table-data-tags '("<td%s>" . "</td>") + :html-table-use-header-tags-for-first-column nil + :html-table-align-individual-fields t + :html-table-row-tags '("<tr>" . "</tr>") + :html-table-attributes + (if (plist-member params :attributes) + (plist-get params :attributes) + '(:border "2" :cellspacing "0" :cellpadding "6" :rules "groups" + :frame "hsides"))) + params))) ;;;###autoload (defun orgtbl-to-texinfo (table params) - "Convert the orgtbl-mode TABLE to TeXInfo. -TABLE is a list, each entry either the symbol `hline' for a horizontal -separator line, or a list of fields for that line. -PARAMS is a property list of parameters that can influence the conversion. -Supports all parameters from `orgtbl-to-generic'. Most important for -TeXInfo are: - -:splice nil/t When set to t, return only table body lines, don't wrap - them into a multitable environment. Default is nil. - -:fmt fmt A format to be used to wrap the field, should contain - %s for the original field value. For example, to wrap - everything in @kbd{}, you could use :fmt \"@kbd{%s}\". - This may also be a property list with column numbers and - formats. For example :fmt (2 \"@kbd{%s}\" 4 \"@code{%s}\"). - Each format also may be a function that formats its one - argument. - -:cf \"f1 f2..\" The column fractions for the table. By default these - are computed automatically from the width of the columns - under org-mode. - -The general parameters :skip and :skipcols have already been applied when -this function is called." - (let* ((total (float (apply '+ org-table-last-column-widths))) - (colfrac (or (plist-get params :cf) - (mapconcat - (lambda (x) (format "%.3f" (/ (float x) total))) - org-table-last-column-widths " "))) - (params2 - (list - :tstart (concat "@multitable @columnfractions " colfrac) - :tend "@end multitable" - :lstart "@item " :lend "" :sep " @tab " - :hlstart "@headitem "))) - (require 'ox-texinfo) - (orgtbl-to-generic table (org-combine-plists params2 params) 'texinfo))) + "Convert the orgtbl-mode TABLE to Texinfo. + +TABLE is a list, each entry either the symbol `hline' for +a horizontal separator line, or a list of fields for that line. +PARAMS is a property list of parameters that can influence the +conversion. All parameters from `orgtbl-to-generic' are +supported. It is also possible to use the following one: + +:columns + + Column widths, as a string. When providing column fractions, + \"@columnfractions\" command can be omitted." + (require 'ox-texinfo) + (let ((output + (orgtbl-to-generic + table + (org-combine-plists + (list :backend 'texinfo + :texinfo-tables-verbatim nil + :texinfo-table-scientific-notation nil) + params))) + (columns (let ((w (plist-get params :columns))) + (cond ((not w) nil) + ((string-match-p "{\\|@columnfractions " w) w) + (t (concat "@columnfractions " w)))))) + (if (not columns) output + (replace-regexp-in-string + "@multitable \\(.*\\)" columns output t nil 1)))) ;;;###autoload (defun orgtbl-to-orgtbl (table params) "Convert the orgtbl-mode TABLE into another orgtbl-mode table. + +TABLE is a list, each entry either the symbol `hline' for +a horizontal separator line, or a list of fields for that line. +PARAMS is a property list of parameters that can influence the +conversion. All parameters from `orgtbl-to-generic' are +supported. + Useful when slicing one table into many. The :hline, :sep, -:lstart, and :lend provide orgtbl framing. The default nil :tstart -and :tend suppress strings without splicing; they can be set to -provide ORGTBL directives for the generated table." - (let* ((params2 - (list - :remove-newlines t - :tstart nil :tend nil - :hline "|---" - :sep " | " - :lstart "| " - :lend " |")) - (params (org-combine-plists params2 params))) - (with-temp-buffer - (insert (orgtbl-to-generic table params)) - (goto-char (point-min)) - (while (re-search-forward org-table-hline-regexp nil t) - (org-table-align)) - (buffer-substring 1 (buffer-size))))) +:lstart, and :lend provide orgtbl framing. :tstart and :tend can +be set to provide ORGTBL directives for the generated table." + (require 'ox-org) + (orgtbl-to-generic table (org-combine-plists params (list :backend 'org)))) (defun orgtbl-to-table.el (table params) - "Convert the orgtbl-mode TABLE into a table.el table." + "Convert the orgtbl-mode TABLE into a table.el table. +TABLE is a list, each entry either the symbol `hline' for +a horizontal separator line, or a list of fields for that line. +PARAMS is a property list of parameters that can influence the +conversion. All parameters from `orgtbl-to-generic' are +supported." (with-temp-buffer (insert (orgtbl-to-orgtbl table params)) (org-table-align) @@ -4920,19 +5303,137 @@ provide ORGTBL directives for the generated table." (defun orgtbl-to-unicode (table params) "Convert the orgtbl-mode TABLE into a table with unicode characters. -You need the ascii-art-to-unicode.el package for this. You can download -it here: http://gnuvola.org/software/j/aa2u/ascii-art-to-unicode.el." - (with-temp-buffer - (insert (orgtbl-to-table.el table params)) - (goto-char (point-min)) - (if (or (featurep 'ascii-art-to-unicode) - (require 'ascii-art-to-unicode nil t)) - (aa2u) - (unless (delq nil (mapcar (lambda (l) (string-match "aa2u" (car l))) org-stored-links)) - (push '("http://gnuvola.org/software/j/aa2u/ascii-art-to-unicode.el" - "Link to ascii-art-to-unicode.el") org-stored-links)) - (user-error "Please download ascii-art-to-unicode.el (use C-c C-l to insert the link to it)")) - (buffer-string))) + +TABLE is a list, each entry either the symbol `hline' for +a horizontal separator line, or a list of fields for that line. +PARAMS is a property list of parameters that can influence the +conversion. All parameters from `orgtbl-to-generic' are +supported. It is also possible to use the following ones: + +:ascii-art + + When non-nil, use \"ascii-art-to-unicode\" package to translate + the table. You can download it here: + http://gnuvola.org/software/j/aa2u/ascii-art-to-unicode.el. + +:narrow + + When non-nil, narrow columns width than provided width cookie, + using \"=>\" as an ellipsis, just like in an Org mode buffer." + (require 'ox-ascii) + (orgtbl-to-generic + table + (org-combine-plists + (list :backend 'ascii + :ascii-charset 'utf-8 + :ascii-table-widen-columns (not (plist-get params :narrow)) + :ascii-table-use-ascii-art (plist-get params :ascii-art)) + params))) + +;; Put the cursor in a column containing numerical values +;; of an Org table, +;; type C-c " a +;; A new column is added with a bar plot. +;; When the table is refreshed (C-u C-c *), +;; the plot is updated to reflect the new values. + +(defun orgtbl-ascii-draw (value min max &optional width characters) + "Draw an ascii bar in a table. +VALUE is the value to plot, it determines the width of the bar to draw. +MIN is the value that will be displayed as empty (zero width bar). +MAX is the value that will draw a bar filling all the WIDTH. +WIDTH is the span in characters from MIN to MAX. +CHARACTERS is a string that will compose the bar, with shades of grey +from pure white to pure black. It defaults to a 10 characters string +of regular ascii characters." + (let* ((width (ceiling (or width 12))) + (characters (or characters " .:;c!lhVHW")) + (len (1- (length characters))) + (value (float (if (numberp value) + value (string-to-number value)))) + (relative (/ (- value min) (- max min))) + (steps (round (* relative width len)))) + (cond ((< steps 0) "too small") + ((> steps (* width len)) "too large") + (t (let* ((int-division (/ steps len)) + (remainder (- steps (* int-division len)))) + (concat (make-string int-division (elt characters len)) + (string (elt characters remainder)))))))) + +;;;###autoload +(defun orgtbl-ascii-plot (&optional ask) + "Draw an ASCII bar plot in a column. + +With cursor in a column containing numerical values, this function +will draw a plot in a new column. + +ASK, if given, is a numeric prefix to override the default 12 +characters width of the plot. ASK may also be the `\\[universal-argument]' \ +prefix, +which will prompt for the width." + (interactive "P") + (let ((col (org-table-current-column)) + (min 1e999) ; 1e999 will be converted to infinity + (max -1e999) ; which is the desired result + (table (org-table-to-lisp)) + (length + (cond ((consp ask) + (read-number "Length of column " 12)) + ((numberp ask) ask) + (t 12)))) + ;; Skip any hline a the top of table. + (while (eq (car table) 'hline) (setq table (cdr table))) + ;; Skip table header if any. + (dolist (x (or (cdr (memq 'hline table)) table)) + (when (consp x) + (setq x (nth (1- col) x)) + (when (string-match + "^[-+]?\\([0-9]*[.]\\)?[0-9]*\\([eE][+-]?[0-9]+\\)?$" + x) + (setq x (string-to-number x)) + (when (> min x) (setq min x)) + (when (< max x) (setq max x))))) + (org-table-insert-column) + (org-table-move-column-right) + (org-table-store-formulas + (cons + (cons + (concat "$" (number-to-string (1+ col))) + (format "'(%s $%s %s %s %s)" + "orgtbl-ascii-draw" col min max length)) + (org-table-get-stored-formulas))) + (org-table-recalculate t))) + +;; Example of extension: unicode characters +;; Here are two examples of different styles. + +;; Unicode block characters are used to give a smooth effect. +;; See http://en.wikipedia.org/wiki/Block_Elements +;; Use one of those drawing functions +;; - orgtbl-ascii-draw (the default ascii) +;; - orgtbl-uc-draw-grid (unicode with a grid effect) +;; - orgtbl-uc-draw-cont (smooth unicode) + +;; This is best viewed with the "DejaVu Sans Mono" font +;; (use M-x set-default-font). + +(defun orgtbl-uc-draw-grid (value min max &optional width) + "Draw a bar in a table using block unicode characters. +It is a variant of orgtbl-ascii-draw with Unicode block +characters, for a smooth display. Bars appear as grids (to the +extent the font allows)." + ;; http://en.wikipedia.org/wiki/Block_Elements + ;; best viewed with the "DejaVu Sans Mono" font. + (orgtbl-ascii-draw value min max width + " \u258F\u258E\u258D\u258C\u258B\u258A\u2589")) + +(defun orgtbl-uc-draw-cont (value min max &optional width) + "Draw a bar in a table using block unicode characters. +It is a variant of orgtbl-ascii-draw with Unicode block +characters, for a smooth display. Bars are solid (to the extent +the font allows)." + (orgtbl-ascii-draw value min max width + " \u258F\u258E\u258D\u258C\u258B\u258A\u2589\u2588")) (defun org-table-get-remote-range (name-or-id form) "Get a field value or a list of values in a range from table at ID. @@ -4949,57 +5450,74 @@ The return value is either a single string for a single field, or a list of the fields in the rectangle." (save-match-data (let ((case-fold-search t) (id-loc nil) - ;; Protect a bunch of variables from being overwritten - ;; by the context of the remote table + ;; Protect a bunch of variables from being overwritten by + ;; the context of the remote table. org-table-column-names org-table-column-name-regexp org-table-local-parameters org-table-named-field-locations - org-table-current-line-types org-table-current-begin-line + org-table-current-line-types org-table-current-begin-pos org-table-dlines org-table-current-ncol org-table-hlines org-table-last-alignment org-table-last-column-widths org-table-last-alignment - org-table-last-column-widths tbeg + org-table-last-column-widths buffer loc) (setq form (org-table-convert-refs-to-rc form)) - (save-excursion - (save-restriction - (widen) - (save-excursion - (goto-char (point-min)) - (if (re-search-forward - (concat "^[ \t]*#\\+\\(tbl\\)?name:[ \t]*" - (regexp-quote name-or-id) "[ \t]*$") - nil t) - (setq buffer (current-buffer) loc (match-beginning 0)) - (setq id-loc (org-id-find name-or-id 'marker)) - (unless (and id-loc (markerp id-loc)) - (user-error "Can't find remote table \"%s\"" name-or-id)) - (setq buffer (marker-buffer id-loc) - loc (marker-position id-loc)) - (move-marker id-loc nil))) - (with-current-buffer buffer - (save-excursion - (save-restriction - (widen) - (goto-char loc) - (forward-char 1) - (unless (and (re-search-forward "^\\(\\*+ \\)\\|[ \t]*|" nil t) - (not (match-beginning 1))) - (user-error "Cannot find a table at NAME or ID %s" name-or-id)) - (setq tbeg (point-at-bol)) - (org-table-get-specials) - (setq form (org-table-formula-substitute-names - (org-table-formula-handle-first/last-rc form))) - (if (and (string-match org-table-range-regexp form) - (> (length (match-string 0 form)) 1)) - (save-match-data - (org-table-get-range (match-string 0 form) tbeg 1)) - form))))))))) + (org-with-wide-buffer + (goto-char (point-min)) + (if (re-search-forward + (concat "^[ \t]*#\\+\\(tbl\\)?name:[ \t]*" + (regexp-quote name-or-id) "[ \t]*$") + nil t) + (setq buffer (current-buffer) loc (match-beginning 0)) + (setq id-loc (org-id-find name-or-id 'marker)) + (unless (and id-loc (markerp id-loc)) + (user-error "Can't find remote table \"%s\"" name-or-id)) + (setq buffer (marker-buffer id-loc) + loc (marker-position id-loc)) + (move-marker id-loc nil)) + (with-current-buffer buffer + (org-with-wide-buffer + (goto-char loc) + (forward-char 1) + (unless (and (re-search-forward "^\\(\\*+ \\)\\|^[ \t]*|" nil t) + (not (match-beginning 1))) + (user-error "Cannot find a table at NAME or ID %s" name-or-id)) + (org-table-analyze) + (setq form (org-table-formula-substitute-names + (org-table-formula-handle-first/last-rc form))) + (if (and (string-match org-table-range-regexp form) + (> (length (match-string 0 form)) 1)) + (org-table-get-range + (match-string 0 form) org-table-current-begin-pos 1) + form))))))) + +(defun org-table-remote-reference-indirection (form) + "Return formula with table remote references substituted by indirection. +For example \"remote($1, @>$2)\" => \"remote(year_2013, @>$1)\". +This indirection works only with the format @ROW$COLUMN. The +format \"B3\" is not supported because it can not be +distinguished from a plain table name or ID." + (let ((regexp + ;; Same as in `org-table-eval-formula'. + (concat "\\<remote([ \t]*\\(" + ;; Allow "$1", "@<", "$-1", "@<<$1" etc. + "[@$][^ \t,]+" + "\\)[ \t]*,[ \t]*\\([^\n)]+\\))"))) + (replace-regexp-in-string + regexp + (lambda (m) + (save-match-data + (let ((eq (org-table-formula-handle-first/last-rc (match-string 1 m)))) + (org-table-get-range + (if (string-match-p "\\`\\$[0-9]+\\'" eq) + (concat "@0" eq) + eq))))) + form t t 1))) (defmacro org-define-lookup-function (mode) (let ((mode-str (symbol-name mode)) - (first-p (equal mode 'first)) - (all-p (equal mode 'all))) + (first-p (eq mode 'first)) + (all-p (eq mode 'all))) (let ((plural-str (if all-p "s" ""))) `(defun ,(intern (format "org-lookup-%s" mode-str)) (val s-list r-list &optional predicate) ,(format "Find %s occurrence%s of VAL in S-LIST; return corresponding element%s of R-LIST. @@ -5012,16 +5530,13 @@ This function is generated by a call to the macro `org-define-lookup-function'." (sl s-list) (rl (or r-list s-list)) (ret nil)))) - (if first-p (add-to-list 'lvars '(match-p nil))) - lvars) + (if first-p (cons '(match-p nil) lvars) lvars)) (while ,(if first-p '(and (not match-p) sl) 'sl) - (progn - (if (funcall p val (car sl)) - (progn - ,(if first-p '(setq match-p t)) - (let ((rval (car rl))) - (setq ret ,(if all-p '(append ret (list rval)) 'rval))))) - (setq sl (cdr sl) rl (cdr rl)))) + (when (funcall p val (car sl)) + ,(when first-p '(setq match-p t)) + (let ((rval (car rl))) + (setq ret ,(if all-p '(append ret (list rval)) 'rval)))) + (setq sl (cdr sl) rl (cdr rl))) ret))))) (org-define-lookup-function first) diff --git a/lisp/org/org-timer.el b/lisp/org/org-timer.el index 95737479010..5acf526f183 100644 --- a/lisp/org/org-timer.el +++ b/lisp/org/org-timer.el @@ -1,4 +1,4 @@ -;;; org-timer.el --- The relative timer code for Org-mode +;;; org-timer.el --- Timer code for Org mode -*- lexical-binding: t; -*- ;; Copyright (C) 2008-2017 Free Software Foundation, Inc. @@ -19,18 +19,25 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Commentary: -;; This file contains the relative timer code for Org-mode +;; This file implements two types of timers for Org buffers: +;; +;; - A relative timer that counts up (from 0 or a specified offset) +;; - A countdown timer that counts down from a specified time +;; +;; The relative and countdown timers differ in their entry points. +;; Use `org-timer' or `org-timer-start' to start the relative timer, +;; and `org-timer-set-timer' to start the countdown timer. ;;; Code: -(require 'org) +(require 'cl-lib) +(require 'org-clock) -(declare-function org-notify "org-clock" (notification &optional play-sound)) (declare-function org-agenda-error "org-agenda" ()) (defvar org-timer-start-time nil @@ -39,27 +46,37 @@ (defvar org-timer-pause-time nil "Time when the timer was paused.") +(defvar org-timer-countdown-timer nil + "Current countdown timer. +This is a timer object if there is an active countdown timer, +`paused' if there is a paused countdown timer, and nil +otherwise.") + +(defvar org-timer-countdown-timer-title nil + "Title for notification displayed when a countdown finishes.") + (defconst org-timer-re "\\([-+]?[0-9]+\\):\\([0-9]\\{2\\}\\):\\([0-9]\\{2\\}\\)" "Regular expression used to match timer stamps.") (defcustom org-timer-format "%s " "The format to insert the time of the timer. This format must contain one instance of \"%s\" which will be replaced by -the value of the relative timer." +the value of the timer." :group 'org-time :type 'string) -(defcustom org-timer-default-timer 0 - "The default timer when a timer is set. +(defcustom org-timer-default-timer "0" + "The default timer when a timer is set, in minutes or hh:mm:ss format. When 0, the user is prompted for a value." :group 'org-time - :version "24.1" - :type 'number) + :version "26.1" + :package-version '(Org . "8.3") + :type 'string) (defcustom org-timer-display 'mode-line - "When a timer is running, org-mode can display it in the mode -line and/or frame title. -Allowed values are: + "Define where running timer is displayed, if at all. +When a timer is running, Org can display it in the mode line +and/or frame title. Allowed values are: both displays in both mode line and frame title mode-line displays only in mode line (default) @@ -76,13 +93,13 @@ nil current timer is not displayed" "Hook run after relative timer is started.") (defvar org-timer-stop-hook nil - "Hook run before relative timer is stopped.") + "Hook run before relative or countdown timer is stopped.") (defvar org-timer-pause-hook nil - "Hook run before relative timer is paused.") + "Hook run before relative or countdown timer is paused.") (defvar org-timer-continue-hook nil - "Hook run after relative timer is continued.") + "Hook run after relative or countdown timer is continued.") (defvar org-timer-set-hook nil "Hook run after countdown timer is set.") @@ -90,9 +107,6 @@ nil current timer is not displayed" (defvar org-timer-done-hook nil "Hook run after countdown timer reaches zero.") -(defvar org-timer-cancel-hook nil - "Hook run before countdown timer is canceled.") - ;;;###autoload (defun org-timer-start (&optional offset) "Set the starting time for the relative timer to now. @@ -105,8 +119,12 @@ region will be shifted by a specific amount. You will be prompted for the amount, with the default to make the first timer string in the region 0:00:00." (interactive "P") - (if (equal offset '(16)) - (call-interactively 'org-timer-change-times-in-region) + (cond + ((equal offset '(16)) + (call-interactively 'org-timer-change-times-in-region)) + (org-timer-countdown-timer + (user-error "Countdown timer is running. Cancel first")) + (t (let (delta def s) (if (not offset) (setq org-timer-start-time (current-time)) @@ -123,67 +141,90 @@ the region 0:00:00." (setq delta (org-timer-hms-to-secs (org-timer-fix-incomplete s))))) (setq org-timer-start-time (seconds-to-time - (- (float-time) delta)))) + ;; Pass `current-time' result to `float-time' (instead + ;; of calling without arguments) so that only + ;; `current-time' has to be overridden in tests. + (- (float-time (current-time)) delta)))) + (setq org-timer-pause-time nil) (org-timer-set-mode-line 'on) (message "Timer start time set to %s, current value is %s" (format-time-string "%T" org-timer-start-time) (org-timer-secs-to-hms (or delta 0))) - (run-hooks 'org-timer-start-hook)))) + (run-hooks 'org-timer-start-hook))))) (defun org-timer-pause-or-continue (&optional stop) - "Pause or continue the relative timer. + "Pause or continue the relative or countdown timer. With prefix arg STOP, stop it entirely." (interactive "P") (cond (stop (org-timer-stop)) ((not org-timer-start-time) (error "No timer is running")) (org-timer-pause-time - ;; timer is paused, continue - (setq org-timer-start-time - (seconds-to-time - (- - (float-time) - (- (float-time org-timer-pause-time) - (float-time org-timer-start-time)))) - org-timer-pause-time nil) - (org-timer-set-mode-line 'on) - (run-hooks 'org-timer-continue-hook) - (message "Timer continues at %s" (org-timer-value-string))) + (let ((start-secs (float-time org-timer-start-time)) + (pause-secs (float-time org-timer-pause-time))) + (if org-timer-countdown-timer + (let ((new-secs (- start-secs pause-secs))) + (setq org-timer-countdown-timer + (org-timer--run-countdown-timer + new-secs org-timer-countdown-timer-title)) + (setq org-timer-start-time + (time-add (current-time) (seconds-to-time new-secs)))) + (setq org-timer-start-time + ;; Pass `current-time' result to `float-time' (instead + ;; of calling without arguments) so that only + ;; `current-time' has to be overridden in tests. + (seconds-to-time (- (float-time (current-time)) + (- pause-secs start-secs))))) + (setq org-timer-pause-time nil) + (org-timer-set-mode-line 'on) + (run-hooks 'org-timer-continue-hook) + (message "Timer continues at %s" (org-timer-value-string)))) (t ;; pause timer + (when org-timer-countdown-timer + (cancel-timer org-timer-countdown-timer) + (setq org-timer-countdown-timer 'paused)) (run-hooks 'org-timer-pause-hook) (setq org-timer-pause-time (current-time)) - (org-timer-set-mode-line 'pause) + (org-timer-set-mode-line 'paused) (message "Timer paused at %s" (org-timer-value-string))))) -(defvar org-timer-current-timer nil) (defun org-timer-stop () - "Stop the relative timer." + "Stop the relative or countdown timer." (interactive) + (unless org-timer-start-time + (user-error "No timer running")) + (when (timerp org-timer-countdown-timer) + (cancel-timer org-timer-countdown-timer)) (run-hooks 'org-timer-stop-hook) (setq org-timer-start-time nil org-timer-pause-time nil - org-timer-current-timer nil) + org-timer-countdown-timer nil) (org-timer-set-mode-line 'off) (message "Timer stopped")) ;;;###autoload -(defun org-timer (&optional restart no-insert-p) +(defun org-timer (&optional restart no-insert) "Insert a H:MM:SS string from the timer into the buffer. -The first time this command is used, the timer is started. When used with -a \\[universal-argument] prefix, force restarting the timer. -When used with a double prefix argument \\[universal-argument], change all the timer string -in the region by a fixed amount. This can be used to recalibrate a timer -that was not started at the correct moment. +The first time this command is used, the timer is started. + +When used with a `\\[universal-argument]' prefix, force restarting the timer. + +When used with a `\\[universal-argument] \\[universal-argument]' \ +prefix, change all the timer strings +in the region by a fixed amount. This can be used to re-calibrate +a timer that was not started at the correct moment. -If NO-INSERT-P is non-nil, return the string instead of inserting +If NO-INSERT is non-nil, return the string instead of inserting it in the buffer." (interactive "P") - (when (or (equal restart '(4)) (not org-timer-start-time)) - (org-timer-start)) - (if no-insert-p - (org-timer-value-string) - (insert (org-timer-value-string)))) + (if (equal restart '(16)) + (org-timer-start restart) + (when (or (equal restart '(4)) (not org-timer-start-time)) + (org-timer-start)) + (if no-insert + (org-timer-value-string) + (insert (org-timer-value-string))))) (defun org-timer-value-string () "Set the timer string." @@ -191,12 +232,14 @@ it in the buffer." (org-timer-secs-to-hms (abs (floor (org-timer-seconds)))))) -(defvar org-timer-timer-is-countdown nil) (defun org-timer-seconds () - (if org-timer-timer-is-countdown + ;; Pass `current-time' result to `float-time' (instead of calling + ;; without arguments) so that only `current-time' has to be + ;; overridden in tests. + (if org-timer-countdown-timer (- (float-time org-timer-start-time) - (float-time)) - (- (float-time org-timer-pause-time) + (float-time (or org-timer-pause-time (current-time)))) + (- (float-time (or org-timer-pause-time (current-time))) (float-time org-timer-start-time)))) ;;;###autoload @@ -290,8 +333,8 @@ If the integer is negative, the string will start with \"-\"." (defvar org-timer-mode-line-string nil) (defun org-timer-set-mode-line (value) - "Set the mode-line display of the relative timer. -VALUE can be `on', `off', or `pause'." + "Set the mode-line display for relative or countdown timer. +VALUE can be `on', `off', or `paused'." (when (or (eq org-timer-display 'mode-line) (eq org-timer-display 'both)) (or global-mode-string (setq global-mode-string '(""))) @@ -303,43 +346,43 @@ VALUE can be `on', `off', or `pause'." (or (memq 'org-timer-mode-line-string frame-title-format) (setq frame-title-format (append frame-title-format '(org-timer-mode-line-string))))) - (cond - ((equal value 'off) - (when org-timer-mode-line-timer - (cancel-timer org-timer-mode-line-timer) - (setq org-timer-mode-line-timer nil)) - (when (or (eq org-timer-display 'mode-line) - (eq org-timer-display 'both)) - (setq global-mode-string - (delq 'org-timer-mode-line-string global-mode-string))) - (when (or (eq org-timer-display 'frame-title) - (eq org-timer-display 'both)) - (setq frame-title-format - (delq 'org-timer-mode-line-string frame-title-format))) - (force-mode-line-update)) - ((equal value 'pause) - (when org-timer-mode-line-timer - (cancel-timer org-timer-mode-line-timer) - (setq org-timer-mode-line-timer nil))) - ((equal value 'on) - (when (or (eq org-timer-display 'mode-line) - (eq org-timer-display 'both)) - (or global-mode-string (setq global-mode-string '(""))) - (or (memq 'org-timer-mode-line-string global-mode-string) - (setq global-mode-string - (append global-mode-string '(org-timer-mode-line-string))))) - (when (or (eq org-timer-display 'frame-title) - (eq org-timer-display 'both)) - (or (memq 'org-timer-mode-line-string frame-title-format) - (setq frame-title-format - (append frame-title-format '(org-timer-mode-line-string))))) - (org-timer-update-mode-line) - (when org-timer-mode-line-timer - (cancel-timer org-timer-mode-line-timer) - (setq org-timer-mode-line-timer nil)) - (when org-timer-display - (setq org-timer-mode-line-timer - (run-with-timer 1 1 'org-timer-update-mode-line)))))) + (cl-case value + (off + (when org-timer-mode-line-timer + (cancel-timer org-timer-mode-line-timer) + (setq org-timer-mode-line-timer nil)) + (when (or (eq org-timer-display 'mode-line) + (eq org-timer-display 'both)) + (setq global-mode-string + (delq 'org-timer-mode-line-string global-mode-string))) + (when (or (eq org-timer-display 'frame-title) + (eq org-timer-display 'both)) + (setq frame-title-format + (delq 'org-timer-mode-line-string frame-title-format))) + (force-mode-line-update)) + (paused + (when org-timer-mode-line-timer + (cancel-timer org-timer-mode-line-timer) + (setq org-timer-mode-line-timer nil))) + (on + (when (or (eq org-timer-display 'mode-line) + (eq org-timer-display 'both)) + (or global-mode-string (setq global-mode-string '(""))) + (or (memq 'org-timer-mode-line-string global-mode-string) + (setq global-mode-string + (append global-mode-string '(org-timer-mode-line-string))))) + (when (or (eq org-timer-display 'frame-title) + (eq org-timer-display 'both)) + (or (memq 'org-timer-mode-line-string frame-title-format) + (setq frame-title-format + (append frame-title-format '(org-timer-mode-line-string))))) + (org-timer-update-mode-line) + (when org-timer-mode-line-timer + (cancel-timer org-timer-mode-line-timer) + (setq org-timer-mode-line-timer nil)) + (when org-timer-display + (setq org-timer-mode-line-timer + (run-with-timer 1 1 'org-timer-update-mode-line)))))) (defun org-timer-update-mode-line () "Update the timer time in the mode line." @@ -349,102 +392,113 @@ VALUE can be `on', `off', or `pause'." (concat " <" (substring (org-timer-value-string) 0 -1) ">")) (force-mode-line-update))) -(defun org-timer-cancel-timer () - "Cancel the current timer." - (interactive) - (when (eval org-timer-current-timer) - (run-hooks 'org-timer-cancel-hook) - (cancel-timer org-timer-current-timer) - (setq org-timer-current-timer nil) - (setq org-timer-timer-is-countdown nil) - (org-timer-set-mode-line 'off)) - (message "Last timer canceled")) - (defun org-timer-show-remaining-time () "Display the remaining time before the timer ends." (interactive) (require 'time) - (if (not org-timer-current-timer) + (if (not org-timer-countdown-timer) (message "No timer set") (let* ((rtime (decode-time - (time-subtract (timer--time org-timer-current-timer) + (time-subtract (timer--time org-timer-countdown-timer) (current-time)))) (rsecs (nth 0 rtime)) (rmins (nth 1 rtime))) (message "%d minute(s) %d seconds left before next time out" rmins rsecs)))) -(defvar org-clock-sound) - ;;;###autoload (defun org-timer-set-timer (&optional opt) - "Prompt for a duration and set a timer. + "Prompt for a duration in minutes or hh:mm:ss and set a timer. -If `org-timer-default-timer' is not zero, suggest this value as +If `org-timer-default-timer' is not \"0\", suggest this value as the default duration for the timer. If a timer is already set, prompt the user if she wants to replace it. Called with a numeric prefix argument, use this numeric value as -the duration of the timer. +the duration of the timer in minutes. Called with a `C-u' prefix arguments, use `org-timer-default-timer' without prompting the user for a duration. With two `C-u' prefix arguments, use `org-timer-default-timer' without prompting the user for a duration and automatically -replace any running timer." +replace any running timer. + +By default, the timer duration will be set to the number of +minutes in the Effort property, if any. You can ignore this by +using three `C-u' prefix arguments." (interactive "P") - (let ((minutes (or (and (numberp opt) (number-to-string opt)) - (and (listp opt) (not (null opt)) - (number-to-string org-timer-default-timer)) - (read-from-minibuffer - "How many minutes left? " - (if (not (eq org-timer-default-timer 0)) - (number-to-string org-timer-default-timer)))))) + (when (and org-timer-start-time + (not org-timer-countdown-timer)) + (user-error "Relative timer is running. Stop first")) + (let* ((default-timer + ;; `org-timer-default-timer' used to be a number, don't choke: + (if (numberp org-timer-default-timer) + (number-to-string org-timer-default-timer) + org-timer-default-timer)) + (effort-minutes (ignore-errors (floor (org-get-at-eol 'effort-minutes 1)))) + (minutes (or (and (numberp opt) (number-to-string opt)) + (and (not (equal opt '(64))) + effort-minutes + (number-to-string effort-minutes)) + (and (consp opt) default-timer) + (and (stringp opt) opt) + (read-from-minibuffer + "How much time left? (minutes or h:mm:ss) " + (and (not (string-equal default-timer "0")) default-timer))))) + (when (string-match "\\`[0-9]+\\'" minutes) + (setq minutes (concat minutes ":00"))) (if (not (string-match "[0-9]+" minutes)) (org-timer-show-remaining-time) - (let* ((mins (string-to-number (match-string 0 minutes))) - (secs (* mins 60)) - (hl (cond - ((string-match "Org Agenda" (buffer-name)) - (let* ((marker (or (get-text-property (point) 'org-marker) - (org-agenda-error))) - (hdmarker (or (get-text-property (point) 'org-hd-marker) - marker)) - (pos (marker-position marker))) - (with-current-buffer (marker-buffer marker) - (widen) - (goto-char pos) - (org-show-entry) - (or (ignore-errors (org-get-heading)) - (concat "File:" (file-name-nondirectory (buffer-file-name))))))) - ((derived-mode-p 'org-mode) - (or (ignore-errors (org-get-heading)) - (concat "File:" (file-name-nondirectory (buffer-file-name))))) - (t (error "Not in an Org buffer")))) - timer-set) - (if (or (and org-timer-current-timer - (or (equal opt '(16)) - (y-or-n-p "Replace current timer? "))) - (not org-timer-current-timer)) - (progn - (require 'org-clock) - (when org-timer-current-timer - (cancel-timer org-timer-current-timer)) - (setq org-timer-current-timer - (run-with-timer - secs nil `(lambda () - (setq org-timer-current-timer nil) - (org-notify ,(format "%s: time out" hl) ,org-clock-sound) - (setq org-timer-timer-is-countdown nil) - (org-timer-set-mode-line 'off) - (run-hooks 'org-timer-done-hook)))) - (run-hooks 'org-timer-set-hook) - (setq org-timer-timer-is-countdown t - org-timer-start-time - (time-add (current-time) (seconds-to-time (* mins 60)))) - (org-timer-set-mode-line 'on)) - (message "No timer set")))))) + (let ((secs (org-timer-hms-to-secs (org-timer-fix-incomplete minutes)))) + (if (and org-timer-countdown-timer + (not (or (equal opt '(16)) + (y-or-n-p "Replace current timer? ")))) + (message "No timer set") + (when (timerp org-timer-countdown-timer) + (cancel-timer org-timer-countdown-timer)) + (setq org-timer-countdown-timer-title + (org-timer--get-timer-title)) + (setq org-timer-countdown-timer + (org-timer--run-countdown-timer + secs org-timer-countdown-timer-title)) + (run-hooks 'org-timer-set-hook) + (setq org-timer-start-time + (time-add (current-time) (seconds-to-time secs))) + (setq org-timer-pause-time nil) + (org-timer-set-mode-line 'on)))))) + +(defun org-timer--run-countdown-timer (secs title) + "Start countdown timer that will last SECS. +TITLE will be appended to the notification message displayed when +time is up." + (let ((msg (format "%s: time out" title))) + (run-with-timer + secs nil `(lambda () + (setq org-timer-countdown-timer nil + org-timer-start-time nil) + (org-notify ,msg ,org-clock-sound) + (org-timer-set-mode-line 'off) + (run-hooks 'org-timer-done-hook))))) + +(defun org-timer--get-timer-title () + "Construct timer title from heading or file name of Org buffer." + (cond + ((derived-mode-p 'org-agenda-mode) + (let* ((marker (or (get-text-property (point) 'org-marker) + (org-agenda-error))) + (hdmarker (or (get-text-property (point) 'org-hd-marker) + marker))) + (with-current-buffer (marker-buffer marker) + (org-with-wide-buffer + (goto-char hdmarker) + (org-show-entry) + (or (ignore-errors (org-get-heading)) + (buffer-name (buffer-base-buffer))))))) + ((derived-mode-p 'org-mode) + (or (ignore-errors (org-get-heading)) + (buffer-name (buffer-base-buffer)))) + (t (error "Not in an Org buffer")))) (provide 'org-timer) diff --git a/lisp/org/org-version.el b/lisp/org/org-version.el index aae65cc6d37..749cbe063e8 100644 --- a/lisp/org/org-version.el +++ b/lisp/org/org-version.el @@ -3,15 +3,15 @@ ;;; Code: ;;;###autoload (defun org-release () - "The release version of org-mode. - Inserted by installing org-mode or when a release is made." - (let ((org-release "8.2.10")) + "The release version of Org. +Inserted by installing Org mode or when a release is made." + (let ((org-release "9.1.4")) org-release)) ;;;###autoload (defun org-git-version () "The Git version of org-mode. - Inserted by installing org-mode or when a release is made." - (let ((org-git-version "release_8.2.10")) +Inserted by installing Org or when a release is made." + (let ((org-git-version "release_9.1.4-44-gfe7310")) org-git-version)) (provide 'org-version) diff --git a/lisp/org/org-w3m.el b/lisp/org/org-w3m.el index 8360bd07fe4..f396814dacc 100644 --- a/lisp/org/org-w3m.el +++ b/lisp/org/org-w3m.el @@ -1,4 +1,4 @@ -;;; org-w3m.el --- Support from copy and paste from w3m to Org-mode +;;; org-w3m.el --- Support from Copy and Paste From w3m -*- lexical-binding: t; -*- ;; Copyright (C) 2008-2017 Free Software Foundation, Inc. @@ -19,15 +19,15 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Commentary: ;; This file implements copying HTML content from a w3m buffer and -;; transforming the text on the fly so that it can be pasted into -;; an org-mode buffer with hot links. It will also work for regions -;; in gnus buffers that have been washed with w3m. +;; transforming the text on the fly so that it can be pasted into an +;; Org buffer with hot links. It will also work for regions in gnus +;; buffers that have been washed with w3m. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; @@ -35,7 +35,7 @@ ;; Richard Riley <rileyrgdev at googlemail dot com> ;; -;; The idea of transforming the HTML content with org-mode style is +;; The idea of transforming the HTML content with Org syntax is ;; proposed by Richard, I'm just coding it. ;; @@ -46,7 +46,7 @@ (defvar w3m-current-url) (defvar w3m-current-title) -(add-hook 'org-store-link-functions 'org-w3m-store-link) +(org-link-set-parameters "w3m" :store #'org-w3m-store-link) (defun org-w3m-store-link () "Store a link to a w3m buffer." (when (eq major-mode 'w3m-mode) @@ -60,7 +60,7 @@ "Copy current buffer content or active region with `org-mode' style links. This will encode `link-title' and `link-location' with `org-make-link-string', and insert the transformed test into the kill ring, -so that it can be yanked into an Org-mode buffer with links working correctly." +so that it can be yanked into an Org buffer with links working correctly." (interactive) (let* ((regionp (org-region-active-p)) (transform-start (point-min)) @@ -107,7 +107,7 @@ so that it can be yanked into an Org-mode buffer with links working correctly." (concat return-content (buffer-substring (point) transform-end)))) (org-kill-new return-content) - (message "Transforming links...done, use C-y to insert text into Org-mode file") + (message "Transforming links...done, use C-y to insert text into Org file") (message "Copy with link transformation complete.")))) (defun org-w3m-get-anchor-start () diff --git a/lisp/org/org.el b/lisp/org/org.el index 02a7a0c09af..07727f68c40 100644 --- a/lisp/org/org.el +++ b/lisp/org/org.el @@ -1,4 +1,4 @@ -;;; org.el --- Outline-based notes management and organizer +;;; org.el --- Outline-based notes management and organizer -*- lexical-binding: t; -*- ;; Carstens outline-mode for keeping track of everything. ;; Copyright (C) 2004-2017 Free Software Foundation, Inc. @@ -21,27 +21,28 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; ;;; Commentary: ;; -;; Org-mode is a mode for keeping notes, maintaining ToDo lists, and doing +;; Org is a mode for keeping notes, maintaining ToDo lists, and doing ;; project planning with a fast and effective plain-text system. ;; -;; Org-mode develops organizational tasks around NOTES files that contain -;; information about projects as plain text. Org-mode is implemented on -;; top of outline-mode, which makes it possible to keep the content of -;; large files well structured. Visibility cycling and structure editing -;; help to work with the tree. Tables are easily created with a built-in -;; table editor. Org-mode supports ToDo items, deadlines, time stamps, -;; and scheduling. It dynamically compiles entries into an agenda that -;; utilizes and smoothly integrates much of the Emacs calendar and diary. -;; Plain text URL-like links connect to websites, emails, Usenet -;; messages, BBDB entries, and any files related to the projects. For -;; printing and sharing of notes, an Org-mode file can be exported as a -;; structured ASCII file, as HTML, or (todo and agenda items only) as an -;; iCalendar file. It can also serve as a publishing tool for a set of -;; linked webpages. +;; Org mode develops organizational tasks around NOTES files that +;; contain information about projects as plain text. Org mode is +;; implemented on top of outline-mode, which makes it possible to keep +;; the content of large files well structured. Visibility cycling and +;; structure editing help to work with the tree. Tables are easily +;; created with a built-in table editor. Org mode supports ToDo +;; items, deadlines, time stamps, and scheduling. It dynamically +;; compiles entries into an agenda that utilizes and smoothly +;; integrates much of the Emacs calendar and diary. Plain text +;; URL-like links connect to websites, emails, Usenet messages, BBDB +;; entries, and any files related to the projects. For printing and +;; sharing of notes, an Org file can be exported as a structured ASCII +;; file, as HTML, or (todo and agenda items only) as an iCalendar +;; file. It can also serve as a publishing tool for a set of linked +;; webpages. ;; ;; Installation and Activation ;; --------------------------- @@ -51,11 +52,11 @@ ;; ;; Documentation ;; ------------- -;; The documentation of Org-mode can be found in the TeXInfo file. The +;; The documentation of Org mode can be found in the TeXInfo file. The ;; distribution also contains a PDF version of it. At the homepage of -;; Org-mode, you can read the same text online as HTML. There is also an +;; Org mode, you can read the same text online as HTML. There is also an ;; excellent reference card made by Philip Rooke. This card can be found -;; in the etc/ directory of Emacs 22. +;; in the doc/ directory. ;; ;; A list of recent changes can be found at ;; http://orgmode.org/Changes.html @@ -63,21 +64,29 @@ ;;; Code: (defvar org-inhibit-highlight-removal nil) ; dynamically scoped param -(defvar org-table-formula-constants-local nil +(defvar-local org-table-formula-constants-local nil "Local version of `org-table-formula-constants'.") -(make-variable-buffer-local 'org-table-formula-constants-local) ;;;; Require other packages -(eval-when-compile - (require 'cl) - (require 'gnus-sum)) +(require 'cl-lib) + +(eval-when-compile (require 'gnus-sum)) (require 'calendar) (require 'find-func) (require 'format-spec) -(load "org-loaddefs.el" t t t) +(or (eq this-command 'eval-buffer) + (condition-case nil + (load (concat (file-name-directory load-file-name) + "org-loaddefs.el") + nil t t t) + (error + (message "WARNING: No org-loaddefs.el file could be found from where org.el is loaded.") + (sit-for 3) + (message "You need to run \"make\" or \"make autoloads\" from Org lisp directory") + (sit-for 3)))) (require 'org-macs) (require 'org-compat) @@ -101,75 +110,96 @@ sure that we are at the beginning of the line.") "Matches a headline, putting stars and text into groups. Stars are put in group 1 and the trimmed body in group 2.") -;; Emacs 22 calendar compatibility: Make sure the new variables are available -(unless (boundp 'calendar-view-holidays-initially-flag) - (org-defvaralias 'calendar-view-holidays-initially-flag - 'view-calendar-holidays-initially)) -(unless (boundp 'calendar-view-diary-initially-flag) - (org-defvaralias 'calendar-view-diary-initially-flag - 'view-diary-entries-initially)) -(unless (boundp 'diary-fancy-buffer) - (org-defvaralias 'diary-fancy-buffer 'fancy-diary-buffer)) - +(declare-function calendar-check-holidays "holidays" (date)) +(declare-function cdlatex-environment "ext:cdlatex" (environment item)) +(declare-function isearch-no-upper-case-p "isearch" (string regexp-flag)) (declare-function org-add-archive-files "org-archive" (files)) - -(declare-function org-inlinetask-at-task-p "org-inlinetask" ()) -(declare-function org-inlinetask-outline-regexp "org-inlinetask" ()) -(declare-function org-inlinetask-toggle-visibility "org-inlinetask" ()) -(declare-function org-pop-to-buffer-same-window "org-compat" (&optional buffer-or-name norecord label)) +(declare-function org-agenda-entry-get-agenda-timestamp "org-agenda" (pom)) +(declare-function org-agenda-list "org-agenda" (&optional arg start-day span with-hour)) +(declare-function org-agenda-redo "org-agenda" (&optional all)) +(declare-function org-babel-do-in-edit-buffer "ob-core" (&rest body) t) +(declare-function org-babel-tangle-file "ob-tangle" (file &optional target-file lang)) +(declare-function org-beamer-mode "ox-beamer" (&optional prefix) t) (declare-function org-clock-get-last-clock-out-time "org-clock" ()) -(declare-function org-clock-timestamps-up "org-clock" (&optional n)) -(declare-function org-clock-timestamps-down "org-clock" (&optional n)) +(declare-function org-clock-out "org-clock" (&optional switch-to-state fail-quietly at-time)) (declare-function org-clock-remove-overlays "org-clock" (&optional beg end noremove)) +(declare-function org-clock-sum "org-clock" (&optional tstart tend headline-filter propname)) (declare-function org-clock-sum-current-item "org-clock" (&optional tstart)) +(declare-function org-clock-timestamps-down "org-clock" (&optional n)) +(declare-function org-clock-timestamps-up "org-clock" (&optional n)) (declare-function org-clock-update-time-maybe "org-clock" ()) +(declare-function org-clocking-buffer "org-clock" ()) (declare-function org-clocktable-shift "org-clock" (dir n)) - -(declare-function orgtbl-mode "org-table" (&optional arg)) -(declare-function org-clock-out "org-clock" (&optional switch-to-state fail-quietly at-time)) -(declare-function org-beamer-mode "ox-beamer" (&optional prefix) t) -(declare-function org-table-edit-field "org-table" (arg)) -(declare-function org-table-justify-field-maybe "org-table" (&optional new)) -(declare-function org-table-set-constants "org-table" ()) -(declare-function org-table-calc-current-TBLFM "org-table" (&optional arg)) -(declare-function org-id-get-create "org-id" (&optional force)) +(declare-function + org-duration-from-minutes "org-duration" (minutes &optional fmt canonical)) +(declare-function org-element-at-point "org-element" ()) +(declare-function org-element-cache-refresh "org-element" (pos)) +(declare-function org-element-cache-reset "org-element" (&optional all)) +(declare-function org-element-contents "org-element" (element)) +(declare-function org-element-context "org-element" (&optional element)) +(declare-function org-element-copy "org-element" (datum)) +(declare-function org-element-interpret-data "org-element" (data)) +(declare-function org-element-lineage "org-element" (blob &optional types with-self)) +(declare-function org-element-link-parser "org-element" ()) +(declare-function org-element-nested-p "org-element" (elem-a elem-b)) +(declare-function org-element-parse-buffer "org-element" (&optional granularity visible-only)) +(declare-function org-element-property "org-element" (property element)) +(declare-function org-element-put-property "org-element" (element property value)) +(declare-function org-element-swap-A-B "org-element" (elem-a elem-b)) +(declare-function org-element-type "org-element" (element)) +(declare-function org-element-update-syntax "org-element" ()) (declare-function org-id-find-id-file "org-id" (id)) -(declare-function org-tags-view "org-agenda" (&optional todo-only match)) -(declare-function org-agenda-list "org-agenda" - (&optional arg start-day span with-hour)) -(declare-function org-agenda-redo "org-agenda" (&optional all)) +(declare-function org-id-get-create "org-id" (&optional force)) +(declare-function org-inlinetask-at-task-p "org-inlinetask" ()) +(declare-function org-inlinetask-outline-regexp "org-inlinetask" ()) +(declare-function org-inlinetask-toggle-visibility "org-inlinetask" ()) +(declare-function org-plot/gnuplot "org-plot" (&optional params)) (declare-function org-table-align "org-table" ()) (declare-function org-table-begin "org-table" (&optional table-type)) +(declare-function org-table-beginning-of-field "org-table" (&optional n)) (declare-function org-table-blank-field "org-table" ()) +(declare-function org-table-calc-current-TBLFM "org-table" (&optional arg)) +(declare-function org-table-copy-region "org-table" (beg end &optional cut)) +(declare-function org-table-cut-region "org-table" (beg end)) +(declare-function org-table-edit-field "org-table" (arg)) (declare-function org-table-end "org-table" (&optional table-type)) +(declare-function org-table-end-of-field "org-table" (&optional n)) (declare-function org-table-insert-row "org-table" (&optional arg)) -(declare-function org-table-paste-rectangle "org-table" ()) +(declare-function org-table-justify-field-maybe "org-table" (&optional new)) (declare-function org-table-maybe-eval-formula "org-table" ()) (declare-function org-table-maybe-recalculate-line "org-table" ()) +(declare-function org-table-next-row "org-table" ()) +(declare-function org-table-paste-rectangle "org-table" ()) +(declare-function org-table-recalculate "org-table" (&optional all noalign)) +(declare-function + org-table-sort-lines "org-table" + (&optional with-case sorting-type getkey-func compare-func interactive?)) +(declare-function org-table-wrap-region "org-table" (arg)) +(declare-function org-tags-view "org-agenda" (&optional todo-only match)) +(declare-function orgtbl-ascii-plot "org-table" (&optional ask)) +(declare-function orgtbl-mode "org-table" (&optional arg)) +(declare-function org-export-get-backend "ox" (name)) +(declare-function org-export-get-environment "ox" (&optional backend subtreep ext-plist)) +(declare-function org-latex-make-preamble "ox-latex" (info &optional template snippet?)) -(declare-function org-element--parse-objects "org-element" - (beg end acc restriction)) -(declare-function org-element-at-point "org-element" (&optional keep-trail)) -(declare-function org-element-contents "org-element" (element)) -(declare-function org-element-context "org-element" (&optional element)) -(declare-function org-element-interpret-data "org-element" - (data &optional parent)) -(declare-function org-element-map "org-element" - (data types fun &optional - info first-match no-recursion with-affiliated)) -(declare-function org-element-nested-p "org-element" (elem-a elem-b)) -(declare-function org-element-parse-buffer "org-element" - (&optional granularity visible-only)) -(declare-function org-element-property "org-element" (property element)) -(declare-function org-element-put-property "org-element" - (element property value)) -(declare-function org-element-swap-A-B "org-element" (elem-a elem-b)) -(declare-function org-element--parse-objects "org-element" - (beg end acc restriction)) -(declare-function org-element-parse-buffer "org-element" - (&optional granularity visible-only)) -(declare-function org-element-restriction "org-element" (element)) -(declare-function org-element-type "org-element" (element)) +(defvar ffap-url-regexp) +(defvar org-element-paragraph-separate) + +(defsubst org-uniquify (list) + "Non-destructively remove duplicate elements from LIST." + (let ((res (copy-sequence list))) (delete-dups res))) + +(defsubst org-get-at-bol (property) + "Get text property PROPERTY at the beginning of line." + (get-text-property (point-at-bol) property)) + +(defsubst org-trim (s &optional keep-lead) + "Remove whitespace at the beginning and the end of string S. +When optional argument KEEP-LEAD is non-nil, removing blank lines +at the beginning of the string does not affect leading indentation." + (replace-regexp-in-string + (if keep-lead "\\`\\([ \t]*\n\\)+" "\\`[ \t\n\r]+") "" + (replace-regexp-in-string "[ \t\n\r]+\\'" "" s))) ;; load languages based on value of `org-babel-load-languages' (defvar org-babel-load-languages) @@ -178,28 +208,24 @@ Stars are put in group 1 and the trimmed body in group 2.") (defun org-babel-do-load-languages (sym value) "Load the languages defined in `org-babel-load-languages'." (set-default sym value) - (mapc (lambda (pair) - (let ((active (cdr pair)) (lang (symbol-name (car pair)))) - (if active - (progn - (require (intern (concat "ob-" lang)))) - (progn - (funcall 'fmakunbound - (intern (concat "org-babel-execute:" lang))) - (funcall 'fmakunbound - (intern (concat "org-babel-expand-body:" lang))))))) - org-babel-load-languages)) + (dolist (pair org-babel-load-languages) + (let ((active (cdr pair)) (lang (symbol-name (car pair)))) + (if active + (require (intern (concat "ob-" lang))) + (funcall 'fmakunbound + (intern (concat "org-babel-execute:" lang))) + (funcall 'fmakunbound + (intern (concat "org-babel-expand-body:" lang))))))) (declare-function org-babel-tangle-file "ob-tangle" (file &optional target-file lang)) ;;;###autoload (defun org-babel-load-file (file &optional compile) - "Load Emacs Lisp source code blocks in the Org-mode FILE. + "Load Emacs Lisp source code blocks in the Org FILE. This function exports the source code using `org-babel-tangle' and then loads the resulting file using `load-file'. With prefix arg (noninteractively: 2nd arg) COMPILE the tangled Emacs Lisp file to byte-code before it is loaded." (interactive "fFile to load: \nP") - (require 'ob-core) (let* ((age (lambda (file) (float-time (time-subtract (current-time) @@ -207,11 +233,13 @@ file to byte-code before it is loaded." (file-attributes file))))))) (base-name (file-name-sans-extension file)) (exported-file (concat base-name ".el"))) - ;; tangle if the org-mode file is newer than the elisp file + ;; tangle if the Org file is newer than the elisp file (unless (and (file-exists-p exported-file) (> (funcall age file) (funcall age exported-file))) + ;; Tangle-file traversal returns reversed list of tangled files + ;; and we want to evaluate the first target. (setq exported-file - (car (org-babel-tangle-file file exported-file "emacs-lisp")))) + (car (last (org-babel-tangle-file file exported-file "emacs-lisp"))))) (message "%s %s" (if compile (progn (byte-compile-file exported-file 'load) @@ -220,7 +248,7 @@ file to byte-code before it is loaded." exported-file))) (defcustom org-babel-load-languages '((emacs-lisp . t)) - "Languages which can be evaluated in Org-mode buffers. + "Languages which can be evaluated in Org buffers. This list can be used to load support for any of the languages below, note that each language will depend on a different set of system executables and/or Emacs modes. When a language is @@ -245,11 +273,15 @@ requirements) is loaded." (const :tag "CSS" css) (const :tag "Ditaa" ditaa) (const :tag "Dot" dot) + (const :tag "Ebnf2ps" ebnf2ps) (const :tag "Emacs Lisp" emacs-lisp) + (const :tag "Forth" forth) (const :tag "Fortran" fortran) (const :tag "Gnuplot" gnuplot) (const :tag "Haskell" haskell) + (const :tag "hledger" hledger) (const :tag "IO" io) + (const :tag "J" J) (const :tag "Java" java) (const :tag "Javascript" js) (const :tag "LaTeX" latex) @@ -272,10 +304,12 @@ requirements) is loaded." (const :tag "Scala" scala) (const :tag "Scheme" scheme) (const :tag "Screen" screen) - (const :tag "Shell Script" sh) + (const :tag "Shell Script" shell) (const :tag "Shen" shen) (const :tag "Sql" sql) - (const :tag "Sqlite" sqlite)) + (const :tag "Sqlite" sqlite) + (const :tag "Stan" stan) + (const :tag "Vala" vala)) :value-type (boolean :tag "Activate" :value t))) ;;;; Customization variables @@ -293,41 +327,319 @@ identifier." ;;;###autoload (defun org-version (&optional here full message) - "Show the org-mode version in the echo area. -With prefix argument HERE, insert it at point. -When FULL is non-nil, use a verbose version string. -When MESSAGE is non-nil, display a message with the version." - (interactive "P") - (let* ((org-dir (ignore-errors (org-find-library-dir "org"))) - (save-load-suffixes (when (boundp 'load-suffixes) load-suffixes)) - (load-suffixes (list ".el")) - (org-install-dir (ignore-errors (org-find-library-dir "org-loaddefs"))) - (org-trash (or - (and (fboundp 'org-release) (fboundp 'org-git-version)) - (org-load-noerror-mustsuffix (concat org-dir "org-version")))) - (load-suffixes save-load-suffixes) - (org-version (org-release)) - (git-version (org-git-version)) - (version (format "Org-mode version %s (%s @ %s)" - org-version - git-version - (if org-install-dir - (if (string= org-dir org-install-dir) - org-install-dir - (concat "mixed installation! " org-install-dir " and " org-dir)) - "org-loaddefs.el can not be found!"))) - (version1 (if full version org-version))) - (if (org-called-interactively-p 'interactive) - (if here - (insert version) - (message version)) - (if message (message version1)) + "Show the Org version. +Interactively, or when MESSAGE is non-nil, show it in echo area. +With prefix argument, or when HERE is non-nil, insert it at point. +In non-interactive uses, a reduced version string is output unless +FULL is given." + (interactive (list current-prefix-arg t (not current-prefix-arg))) + (let ((org-dir (ignore-errors (org-find-library-dir "org"))) + (save-load-suffixes (when (boundp 'load-suffixes) load-suffixes)) + (load-suffixes (list ".el")) + (org-install-dir + (ignore-errors (org-find-library-dir "org-loaddefs")))) + (unless (and (fboundp 'org-release) (fboundp 'org-git-version)) + (org-load-noerror-mustsuffix (concat org-dir "org-version"))) + (let* ((load-suffixes save-load-suffixes) + (release (org-release)) + (git-version (org-git-version)) + (version (format "Org mode version %s (%s @ %s)" + release + git-version + (if org-install-dir + (if (string= org-dir org-install-dir) + org-install-dir + (concat "mixed installation! " + org-install-dir + " and " + org-dir)) + "org-loaddefs.el can not be found!"))) + (version1 (if full version release))) + (when here (insert version1)) + (when message (message "%s" version1)) version1))) (defconst org-version (org-version)) -;;; Compatibility constants + +;;; Syntax Constants + +;;;; Block +(defconst org-block-regexp + "^[ \t]*#\\+begin_?\\([^ \n]+\\)\\(\\([^\n]+\\)\\)?\n\\([^\000]+?\\)#\\+end_?\\1[ \t]*$" + "Regular expression for hiding blocks.") + +(defconst org-dblock-start-re + "^[ \t]*#\\+\\(?:BEGIN\\|begin\\):[ \t]+\\(\\S-+\\)\\([ \t]+\\(.*\\)\\)?" + "Matches the start line of a dynamic block, with parameters.") + +(defconst org-dblock-end-re "^[ \t]*#\\+\\(?:END\\|end\\)\\([: \t\r\n]\\|$\\)" + "Matches the end of a dynamic block.") + +;;;; Clock and Planning + +(defconst org-clock-string "CLOCK:" + "String used as prefix for timestamps clocking work hours on an item.") + +(defvar org-closed-string "CLOSED:" + "String used as the prefix for timestamps logging closing a TODO entry.") + +(defvar org-deadline-string "DEADLINE:" + "String to mark deadline entries. +\\<org-mode-map> +A deadline is this string, followed by a time stamp. It must be +a word, terminated by a colon. You can insert a schedule keyword +and a timestamp with `\\[org-deadline]'.") + +(defvar org-scheduled-string "SCHEDULED:" + "String to mark scheduled TODO entries. +\\<org-mode-map> +A schedule is this string, followed by a time stamp. It must be +a word, terminated by a colon. You can insert a schedule keyword +and a timestamp with `\\[org-schedule]'.") + +(defconst org-ds-keyword-length + (+ 2 + (apply #'max + (mapcar #'length + (list org-deadline-string org-scheduled-string + org-clock-string org-closed-string)))) + "Maximum length of the DEADLINE and SCHEDULED keywords.") + +(defconst org-planning-line-re + (concat "^[ \t]*" + (regexp-opt + (list org-closed-string org-deadline-string org-scheduled-string) + t)) + "Matches a line with planning info. +Matched keyword is in group 1.") + +(defconst org-clock-line-re + (concat "^[ \t]*" org-clock-string) + "Matches a line with clock info.") + +(defconst org-deadline-regexp (concat "\\<" org-deadline-string) + "Matches the DEADLINE keyword.") + +(defconst org-deadline-time-regexp + (concat "\\<" org-deadline-string " *<\\([^>]+\\)>") + "Matches the DEADLINE keyword together with a time stamp.") + +(defconst org-deadline-time-hour-regexp + (concat "\\<" org-deadline-string + " *<\\([^>]+[0-9]\\{1,2\\}:[0-9]\\{2\\}[0-9-+:hdwmy \t.]*\\)>") + "Matches the DEADLINE keyword together with a time-and-hour stamp.") + +(defconst org-deadline-line-regexp + (concat "\\<\\(" org-deadline-string "\\).*") + "Matches the DEADLINE keyword and the rest of the line.") + +(defconst org-scheduled-regexp (concat "\\<" org-scheduled-string) + "Matches the SCHEDULED keyword.") + +(defconst org-scheduled-time-regexp + (concat "\\<" org-scheduled-string " *<\\([^>]+\\)>") + "Matches the SCHEDULED keyword together with a time stamp.") + +(defconst org-scheduled-time-hour-regexp + (concat "\\<" org-scheduled-string + " *<\\([^>]+[0-9]\\{1,2\\}:[0-9]\\{2\\}[0-9-+:hdwmy \t.]*\\)>") + "Matches the SCHEDULED keyword together with a time-and-hour stamp.") + +(defconst org-closed-time-regexp + (concat "\\<" org-closed-string " *\\[\\([^]]+\\)\\]") + "Matches the CLOSED keyword together with a time stamp.") + +(defconst org-keyword-time-regexp + (concat "\\<" + (regexp-opt + (list org-scheduled-string org-deadline-string org-closed-string + org-clock-string) + t) + " *[[<]\\([^]>]+\\)[]>]") + "Matches any of the 4 keywords, together with the time stamp.") + +(defconst org-keyword-time-not-clock-regexp + (concat + "\\<" + (regexp-opt + (list org-scheduled-string org-deadline-string org-closed-string) t) + " *[[<]\\([^]>]+\\)[]>]") + "Matches any of the 3 keywords, together with the time stamp.") + +(defconst org-maybe-keyword-time-regexp + (concat "\\(\\<" + (regexp-opt + (list org-scheduled-string org-deadline-string org-closed-string + org-clock-string) + t) + "\\)?" + " *\\([[<][0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} ?[^]\r\n>]*?[]>]" + "\\|" + "<%%([^\r\n>]*>\\)") + "Matches a timestamp, possibly preceded by a keyword.") + +(defconst org-all-time-keywords + (mapcar (lambda (w) (substring w 0 -1)) + (list org-scheduled-string org-deadline-string + org-clock-string org-closed-string)) + "List of time keywords.") + +;;;; Drawer + +(defconst org-drawer-regexp "^[ \t]*:\\(\\(?:\\w\\|[-_]\\)+\\):[ \t]*$" + "Matches first or last line of a hidden block. +Group 1 contains drawer's name or \"END\".") + +(defconst org-property-start-re "^[ \t]*:PROPERTIES:[ \t]*$" + "Regular expression matching the first line of a property drawer.") + +(defconst org-property-end-re "^[ \t]*:END:[ \t]*$" + "Regular expression matching the last line of a property drawer.") + +(defconst org-clock-drawer-start-re "^[ \t]*:CLOCK:[ \t]*$" + "Regular expression matching the first line of a clock drawer.") + +(defconst org-clock-drawer-end-re "^[ \t]*:END:[ \t]*$" + "Regular expression matching the last line of a clock drawer.") + +(defconst org-property-drawer-re + (concat "^[ \t]*:PROPERTIES:[ \t]*\n" + "\\(?:[ \t]*:\\S-+:\\(?: .*\\)?[ \t]*\n\\)*?" + "[ \t]*:END:[ \t]*$") + "Matches an entire property drawer.") + +(defconst org-clock-drawer-re + (concat "\\(" org-clock-drawer-start-re "\\)[^\000]*?\\(" + org-clock-drawer-end-re "\\)\n?") + "Matches an entire clock drawer.") + +;;;; Headline + +(defconst org-heading-keyword-regexp-format + "^\\(\\*+\\)\\(?: +%s\\)\\(?: +\\(.*?\\)\\)?[ \t]*$" + "Printf format for a regexp matching a headline with some keyword. +This regexp will match the headline of any node which has the +exact keyword that is put into the format. The keyword isn't in +any group by default, but the stars and the body are.") + +(defconst org-heading-keyword-maybe-regexp-format + "^\\(\\*+\\)\\(?: +%s\\)?\\(?: +\\(.*?\\)\\)?[ \t]*$" + "Printf format for a regexp matching a headline, possibly with some keyword. +This regexp can match any headline with the specified keyword, or +without a keyword. The keyword isn't in any group by default, +but the stars and the body are.") + +(defconst org-archive-tag "ARCHIVE" + "The tag that marks a subtree as archived. +An archived subtree does not open during visibility cycling, and does +not contribute to the agenda listings.") + +(eval-and-compile + (defconst org-comment-string "COMMENT" + "Entries starting with this keyword will never be exported. +\\<org-mode-map> +An entry can be toggled between COMMENT and normal with +`\\[org-toggle-comment]'.")) + + +;;;; LaTeX Environments and Fragments + +(defconst org-latex-regexps + '(("begin" "^[ \t]*\\(\\\\begin{\\([a-zA-Z0-9\\*]+\\)[^\000]+?\\\\end{\\2}\\)" 1 t) + ;; ("$" "\\([ \t(]\\|^\\)\\(\\(\\([$]\\)\\([^ \t\n,.$].*?\\(\n.*?\\)\\{0,5\\}[^ \t\n,.$]\\)\\4\\)\\)\\([ \t.,?;:'\")]\\|$\\)" 2 nil) + ;; \000 in the following regex is needed for org-inside-LaTeX-fragment-p + ("$1" "\\([^$]\\|^\\)\\(\\$[^ \t\r\n,;.$]\\$\\)\\(\\s.\\|\\s-\\|\\s(\\|\\s)\\|\\s\"\\|\000\\|'\\|$\\)" 2 nil) + ("$" "\\([^$]\\|^\\)\\(\\(\\$\\([^ \t\n,;.$][^$\n\r]*?\\(\n[^$\n\r]*?\\)\\{0,2\\}[^ \t\n,.$]\\)\\$\\)\\)\\(\\s.\\|\\s-\\|\\s(\\|\\s)\\|\\s\"\\|\000\\|'\\|$\\)" 2 nil) + ("\\(" "\\\\([^\000]*?\\\\)" 0 nil) + ("\\[" "\\\\\\[[^\000]*?\\\\\\]" 0 nil) + ("$$" "\\$\\$[^\000]*?\\$\\$" 0 nil)) + "Regular expressions for matching embedded LaTeX.") + +;;;; Node Property + +(defconst org-effort-property "Effort" + "The property that is being used to keep track of effort estimates. +Effort estimates given in this property need to have the format H:MM.") + +;;;; Table + +(defconst org-table-any-line-regexp "^[ \t]*\\(|\\|\\+-[-+]\\)" + "Detect an org-type or table-type table.") + +(defconst org-table-line-regexp "^[ \t]*|" + "Detect an org-type table line.") + +(defconst org-table-dataline-regexp "^[ \t]*|[^-]" + "Detect an org-type table line.") + +(defconst org-table-hline-regexp "^[ \t]*|-" + "Detect an org-type table hline.") + +(defconst org-table1-hline-regexp "^[ \t]*\\+-[-+]" + "Detect a table-type table hline.") + +(defconst org-table-any-border-regexp "^[ \t]*[^|+ \t]" + "Detect the first line outside a table when searching from within it. +This works for both table types.") + +(defconst org-TBLFM-regexp "^[ \t]*#\\+TBLFM: " + "Detect a #+TBLFM line.") + +;;;; Timestamp + +(defconst org-ts-regexp "<\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} ?[^\r\n>]*?\\)>" + "Regular expression for fast time stamp matching.") + +(defconst org-ts-regexp-inactive + "\\[\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} ?[^\r\n>]*?\\)\\]" + "Regular expression for fast inactive time stamp matching.") + +(defconst org-ts-regexp-both "[[<]\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} ?[^]\r\n>]*?\\)[]>]" + "Regular expression for fast time stamp matching.") + +(defconst org-ts-regexp0 + "\\(\\([0-9]\\{4\\}\\)-\\([0-9]\\{2\\}\\)-\\([0-9]\\{2\\}\\)\\( +[^]+0-9>\r\n -]+\\)?\\( +\\([0-9]\\{1,2\\}\\):\\([0-9]\\{2\\}\\)\\)?\\)" + "Regular expression matching time strings for analysis. +This one does not require the space after the date, so it can be used +on a string that terminates immediately after the date.") + +(defconst org-ts-regexp1 "\\(\\([0-9]\\{4\\}\\)-\\([0-9]\\{2\\}\\)-\\([0-9]\\{2\\}\\) *\\([^]+0-9>\r\n -]*\\)\\( \\([0-9]\\{1,2\\}\\):\\([0-9]\\{2\\}\\)\\)?\\)" + "Regular expression matching time strings for analysis.") + +(defconst org-ts-regexp2 (concat "<" org-ts-regexp1 "[^>\n]\\{0,16\\}>") + "Regular expression matching time stamps, with groups.") + +(defconst org-ts-regexp3 (concat "[[<]" org-ts-regexp1 "[^]>\n]\\{0,16\\}[]>]") + "Regular expression matching time stamps (also [..]), with groups.") + +(defconst org-tr-regexp (concat org-ts-regexp "--?-?" org-ts-regexp) + "Regular expression matching a time stamp range.") + +(defconst org-tr-regexp-both + (concat org-ts-regexp-both "--?-?" org-ts-regexp-both) + "Regular expression matching a time stamp range.") + +(defconst org-tsr-regexp (concat org-ts-regexp "\\(--?-?" + org-ts-regexp "\\)?") + "Regular expression matching a time stamp or time stamp range.") + +(defconst org-tsr-regexp-both + (concat org-ts-regexp-both "\\(--?-?" + org-ts-regexp-both "\\)?") + "Regular expression matching a time stamp or time stamp range. +The time stamps may be either active or inactive.") + +(defconst org-repeat-re + "<[0-9]\\{4\\}-[0-9][0-9]-[0-9][0-9] [^>\n]*?\\([.+]?\\+[0-9]+[hdwmy]\\(/[0-9]+[hdwmy]\\)?\\)" + "Regular expression for specifying repeated events. +After a match, group 1 contains the repeat expression.") + +(defconst org-time-stamp-formats '("<%Y-%m-%d %a>" . "<%Y-%m-%d %a %H:%M>") + "Formats for `format-time-string' which are used for time stamps.") + + ;;; The custom variables (defgroup org nil @@ -337,7 +649,7 @@ When MESSAGE is non-nil, display a message with the version." :group 'calendar) (defcustom org-mode-hook nil - "Mode hook for Org-mode, run after the mode was turned on." + "Mode hook for Org mode, run after the mode was turned on." :group 'org :type 'hook) @@ -359,17 +671,17 @@ When MESSAGE is non-nil, display a message with the version." (defun org-load-modules-maybe (&optional force) "Load all extensions listed in `org-modules'." (when (or force (not org-modules-loaded)) - (mapc (lambda (ext) - (condition-case nil (require ext) - (error (message "Problems while trying to load feature `%s'" ext)))) - org-modules) + (dolist (ext org-modules) + (condition-case nil (require ext) + (error (message "Problems while trying to load feature `%s'" ext)))) (setq org-modules-loaded t))) (defun org-set-modules (var value) "Set VAR to VALUE and call `org-load-modules-maybe' with the force flag." (set var value) (when (featurep 'org) - (org-load-modules-maybe 'force))) + (org-load-modules-maybe 'force) + (org-element-cache-reset 'all))) (defcustom org-modules '(org-w3m org-bbdb org-bibtex org-docview org-gnus org-info org-irc org-mhe org-rmail) "Modules that should always be loaded together with org.el. @@ -397,6 +709,7 @@ For export specific modules, see also `org-export-backends'." (const :tag " crypt: Encryption of subtrees" org-crypt) (const :tag " ctags: Access to Emacs tags with links" org-ctags) (const :tag " docview: Links to doc-view buffers" org-docview) + (const :tag " eww: Store link to url of eww" org-eww) (const :tag " gnus: Links to GNUS folders/messages" org-gnus) (const :tag " habit: Track your consistency with habits" org-habit) (const :tag " id: Global IDs for identifying entries" org-id) @@ -407,52 +720,49 @@ For export specific modules, see also `org-export-backends'." (const :tag " mouse: Additional mouse support" org-mouse) (const :tag " protocol: Intercept calls from emacsclient" org-protocol) (const :tag " rmail: Links to RMAIL folders/messages" org-rmail) - (const :tag " w3m: Special cut/paste from w3m to Org-mode." org-w3m) + (const :tag " w3m: Special cut/paste from w3m to Org mode." org-w3m) (const :tag "C annotate-file: Annotate a file with org syntax" org-annotate-file) - (const :tag "C bookmark: Org-mode links to bookmarks" org-bookmark) - (const :tag "C bullets: Add overlays to headlines stars" org-bullets) + (const :tag "C bookmark: Org links to bookmarks" org-bookmark) (const :tag "C checklist: Extra functions for checklists in repeated tasks" org-checklist) (const :tag "C choose: Use TODO keywords to mark decisions states" org-choose) (const :tag "C collector: Collect properties into tables" org-collector) - (const :tag "C depend: TODO dependencies for Org-mode\n\t\t\t(PARTIALLY OBSOLETE, see built-in dependency support))" org-depend) - (const :tag "C drill: Flashcards and spaced repetition for Org-mode" org-drill) - (const :tag "C elisp-symbol: Org-mode links to emacs-lisp symbols" org-elisp-symbol) + (const :tag "C depend: TODO dependencies for Org mode\n\t\t\t(PARTIALLY OBSOLETE, see built-in dependency support))" org-depend) + (const :tag "C drill: Flashcards and spaced repetition for Org mode" org-drill) + (const :tag "C elisp-symbol: Org links to emacs-lisp symbols" org-elisp-symbol) (const :tag "C eshell Support for links to working directories in eshell" org-eshell) (const :tag "C eval-light: Evaluate inbuffer-code on demand" org-eval-light) (const :tag "C eval: Include command output as text" org-eval) - (const :tag "C expiry: Expiry mechanism for Org-mode entries" org-expiry) + (const :tag "C expiry: Expiry mechanism for Org entries" org-expiry) (const :tag "C favtable: Lookup table of favorite references and links" org-favtable) (const :tag "C git-link: Provide org links to specific file version" org-git-link) (const :tag "C interactive-query: Interactive modification of tags query\n\t\t\t(PARTIALLY OBSOLETE, see secondary filtering)" org-interactive-query) - (const :tag "C invoice: Help manage client invoices in Org-mode" org-invoice) - (const :tag "C jira: Add a jira:ticket protocol to Org-mode" org-jira) + (const :tag "C invoice: Help manage client invoices in Org mode" org-invoice) (const :tag "C learn: SuperMemo's incremental learning algorithm" org-learn) (const :tag "C mac-iCal Imports events from iCal.app to the Emacs diary" org-mac-iCal) (const :tag "C mac-link: Grab links and url from various mac Applications" org-mac-link) - (const :tag "C mairix: Hook mairix search into Org-mode for different MUAs" org-mairix) - (const :tag "C man: Support for links to manpages in Org-mode" org-man) + (const :tag "C mairix: Hook mairix search into Org for different MUAs" org-mairix) + (const :tag "C man: Support for links to manpages in Org mode" org-man) (const :tag "C mew: Links to Mew folders/messages" org-mew) (const :tag "C mtags: Support for muse-like tags" org-mtags) (const :tag "C notmuch: Provide org links to notmuch searches or messages" org-notmuch) (const :tag "C panel: Simple routines for us with bad memory" org-panel) - (const :tag "C registry: A registry for Org-mode links" org-registry) - (const :tag "C screen: Visit screen sessions through Org-mode links" org-screen) + (const :tag "C registry: A registry for Org links" org-registry) + (const :tag "C screen: Visit screen sessions through Org links" org-screen) (const :tag "C secretary: Team management with org-mode" org-secretary) - (const :tag "C sqlinsert: Convert Org-mode tables to SQL insertions" orgtbl-sqlinsert) - (const :tag "C toc: Table of contents for Org-mode buffer" org-toc) - (const :tag "C track: Keep up with Org-mode development" org-track) + (const :tag "C sqlinsert: Convert Org tables to SQL insertions" orgtbl-sqlinsert) + (const :tag "C toc: Table of contents for Org buffer" org-toc) + (const :tag "C track: Keep up with Org mode development" org-track) (const :tag "C velocity Something like Notational Velocity for Org" org-velocity) (const :tag "C vm: Links to VM folders/messages" org-vm) (const :tag "C wikinodes: CamelCase wiki-like links" org-wikinodes) (const :tag "C wl: Links to Wanderlust folders/messages" org-wl) (repeat :tag "External packages" :inline t (symbol :tag "Package")))) -(defvar org-export--registered-backends) ; From ox.el. +(defvar org-export-registered-backends) ; From ox.el. (declare-function org-export-derived-backend-p "ox" (backend &rest backends)) (declare-function org-export-backend-name "ox" (backend) t) -(declare-function org-export-backend-options "ox" (cl-x) t) -(defcustom org-export-backends '(ascii html icalendar latex) +(defcustom org-export-backends '(ascii html icalendar latex odt) "List of export back-ends that should be always available. If a description starts with <C>, the file is not part of Emacs @@ -469,8 +779,8 @@ interface or run the following code, where VAL stands for the new value of the variable, after updating it: (progn - (setq org-export--registered-backends - (org-remove-if-not + (setq org-export-registered-backends + (cl-remove-if-not (lambda (backend) (let ((name (org-export-backend-name backend))) (or (memq name val) @@ -478,9 +788,9 @@ value of the variable, after updating it: (dolist (b val) (and (org-export-derived-backend-p b name) (throw \\='parentp t))))))) - org-export--registered-backends)) - (let ((new-list (mapcar \\='org-export-backend-name - org-export--registered-backends))) + org-export-registered-backends)) + (let ((new-list (mapcar #\\='org-export-backend-name + org-export-registered-backends))) (dolist (backend val) (cond ((not (load (format \"ox-%s\" backend) t t)) @@ -493,16 +803,16 @@ Adding a back-end to this list will also pull the back-end it depends on, if any." :group 'org :group 'org-export - :version "24.4" - :package-version '(Org . "8.0") + :version "26.1" + :package-version '(Org . "9.0") :initialize 'custom-initialize-set :set (lambda (var val) (if (not (featurep 'ox)) (set-default var val) ;; Any back-end not required anymore (not present in VAL and not ;; a parent of any back-end in the new value) is removed from the ;; list of registered back-ends. - (setq org-export--registered-backends - (org-remove-if-not + (setq org-export-registered-backends + (cl-remove-if-not (lambda (backend) (let ((name (org-export-backend-name backend))) (or (memq name val) @@ -510,11 +820,11 @@ depends on, if any." (dolist (b val) (and (org-export-derived-backend-p b name) (throw 'parentp t))))))) - org-export--registered-backends)) + org-export-registered-backends)) ;; Now build NEW-LIST of both new back-ends and required ;; parents. - (let ((new-list (mapcar 'org-export-backend-name - org-export--registered-backends))) + (let ((new-list (mapcar #'org-export-backend-name + org-export-registered-backends))) (dolist (backend val) (cond ((not (load (format "ox-%s" backend) t t)) @@ -544,19 +854,18 @@ depends on, if any." (const :tag "C taskjuggler Export buffer to TaskJuggler format" taskjuggler))) (eval-after-load 'ox - '(mapc - (lambda (backend) - (condition-case nil (require (intern (format "ox-%s" backend))) - (error (message "Problems while trying to load export back-end `%s'" - backend)))) - org-export-backends)) + '(dolist (backend org-export-backends) + (condition-case nil (require (intern (format "ox-%s" backend))) + (error (message "Problems while trying to load export back-end `%s'" + backend))))) (defcustom org-support-shift-select nil "Non-nil means make shift-cursor commands select text when possible. +\\<org-mode-map>\ In Emacs 23, when `shift-select-mode' is on, shifted cursor keys start selecting a region, or enlarge regions started in this way. -In Org-mode, in special contexts, these same keys are used for +In Org mode, in special contexts, these same keys are used for other purposes, important enough to compete with shift selection. Org tries to balance these needs by supporting `shift-select-mode' outside these special contexts, under control of this variable. @@ -571,7 +880,7 @@ cursor keys will then execute Org commands in the following contexts: Outside these contexts, the commands will throw an error. When this variable is t and the cursor is not in a special -context, Org-mode will support shift-selection for making and +context, Org mode will support shift-selection for making and enlarging regions. To make this more effective, the bullet cycling will no longer happen anywhere in an item line, but only if the cursor is exactly on the bullet. @@ -579,16 +888,16 @@ if the cursor is exactly on the bullet. If you set this variable to the symbol `always', then the keys will not be special in headlines, property lines, and item lines, to make shift selection work there as well. If this is what you -want, you can use the following alternative commands: `C-c C-t' -and `C-c ,' to change TODO state and priority, `C-u C-u C-c C-t' -can be used to switch TODO sets, `C-c -' to cycle item bullet -types, and properties can be edited by hand or in column view. +want, you can use the following alternative commands: +`\\[org-todo]' and `\\[org-priority]' \ +to change TODO state and priority, +`\\[universal-argument] \\[universal-argument] \\[org-todo]' \ +can be used to switch TODO sets, +`\\[org-ctrl-c-minus]' to cycle item bullet types, +and properties can be edited by hand or in column view. However, when the cursor is on a timestamp, shift-cursor commands -will still edit the time stamp - this is just too good to give up. - -XEmacs user should have this variable set to nil, because -`shift-select-mode' is in Emacs 23 or later only." +will still edit the time stamp - this is just too good to give up." :group 'org :type '(choice (const :tag "Never" nil) @@ -622,12 +931,13 @@ already archived entries." :group 'org-archive) (defgroup org-startup nil - "Options concerning startup of Org-mode." + "Options concerning startup of Org mode." :tag "Org Startup" :group 'org) (defcustom org-startup-folded t - "Non-nil means entering Org-mode will switch to OVERVIEW. + "Non-nil means entering Org mode will switch to OVERVIEW. + This can also be configured on a per-file basis by adding one of the following lines anywhere in the buffer: @@ -636,9 +946,9 @@ the following lines anywhere in the buffer: #+STARTUP: content #+STARTUP: showeverything -By default, this option is ignored when Org opens agenda files -for the first time. If you want the agenda to honor the startup -option, set `org-agenda-inhibit-startup' to nil." +Set `org-agenda-inhibit-startup' to a non-nil value if you want +to ignore this option when Org opens agenda files for the first +time." :group 'org-startup :type '(choice (const :tag "nofold: show all" nil) @@ -647,9 +957,18 @@ option, set `org-agenda-inhibit-startup' to nil." (const :tag "show everything, even drawers" showeverything))) (defcustom org-startup-truncated t - "Non-nil means entering Org-mode will set `truncate-lines'. + "Non-nil means entering Org mode will set `truncate-lines'. This is useful since some lines containing links can be very long and -uninteresting. Also tables look terrible when wrapped." +uninteresting. Also tables look terrible when wrapped. + +The variable `org-startup-truncated' allows to configure +truncation for Org mode different to the other modes that use the +variable `truncate-lines' and as a shortcut instead of putting +the variable `truncate-lines' into the `org-mode-hook'. If one +wants to configure truncation for Org mode not statically but +dynamically e. g. in a hook like `ediff-prepare-buffer-hook' then +the variable `truncate-lines' has to be used because in such a +case it is too late to set the variable `org-startup-truncated'." :group 'org-startup :type 'boolean) @@ -742,26 +1061,26 @@ the following lines anywhere in the buffer: :type 'boolean) (defcustom org-insert-mode-line-in-empty-file nil - "Non-nil means insert the first line setting Org-mode in empty files. + "Non-nil means insert the first line setting Org mode in empty files. When the function `org-mode' is called interactively in an empty file, this -normally means that the file name does not automatically trigger Org-mode. -To ensure that the file will always be in Org-mode in the future, a -line enforcing Org-mode will be inserted into the buffer, if this option +normally means that the file name does not automatically trigger Org mode. +To ensure that the file will always be in Org mode in the future, a +line enforcing Org mode will be inserted into the buffer, if this option has been set." :group 'org-startup :type 'boolean) (defcustom org-replace-disputed-keys nil "Non-nil means use alternative key bindings for some keys. -Org-mode uses S-<cursor> keys for changing timestamps and priorities. +Org mode uses S-<cursor> keys for changing timestamps and priorities. These keys are also used by other packages like shift-selection-mode' \(built into Emacs 23), `CUA-mode' or `windmove.el'. -If you want to use Org-mode together with one of these other modes, -or more generally if you would like to move some Org-mode commands to +If you want to use Org mode together with one of these other modes, +or more generally if you would like to move some Org mode commands to other keys, set this variable and configure the keys with the variable `org-disputed-keys'. -This option is only relevant at load-time of Org-mode, and must be set +This option is only relevant at load-time of Org mode, and must be set *before* org.el is loaded. Changing it requires a restart of Emacs to become effective." :group 'org-startup @@ -769,18 +1088,13 @@ become effective." (defcustom org-use-extra-keys nil "Non-nil means use extra key sequence definitions for certain commands. -This happens automatically if you run XEmacs or if `window-system' -is nil. This variable lets you do the same manually. You must -set it before loading org. - -Example: on Carbon Emacs 22 running graphically, with an external -keyboard on a Powerbook, the default way of setting M-left might -not work for either Alt or ESC. Setting this variable will make -it work for ESC." +This happens automatically if `window-system' is nil. This +variable lets you do the same manually. You must set it before +loading Org." :group 'org-startup :type 'boolean) -(org-defvaralias 'org-CUA-compatible 'org-replace-disputed-keys) +(defvaralias 'org-CUA-compatible 'org-replace-disputed-keys) (defcustom org-disputed-keys '(([(shift up)] . [(meta p)]) @@ -789,90 +1103,52 @@ it work for ESC." ([(shift right)] . [(meta +)]) ([(control shift right)] . [(meta shift +)]) ([(control shift left)] . [(meta shift -)])) - "Keys for which Org-mode and other modes compete. + "Keys for which Org mode and other modes compete. This is an alist, cars are the default keys, second element specifies the alternative to use when `org-replace-disputed-keys' is t. Keys can be specified in any syntax supported by `define-key'. -The value of this option takes effect only at Org-mode's startup, +The value of this option takes effect only at Org mode startup, therefore you'll have to restart Emacs to apply it after changing." :group 'org-startup :type 'alist) (defun org-key (key) "Select key according to `org-replace-disputed-keys' and `org-disputed-keys'. -Or return the original if not disputed. -Also apply the translations defined in `org-xemacs-key-equivalents'." +Or return the original if not disputed." (when org-replace-disputed-keys (let* ((nkey (key-description key)) - (x (org-find-if (lambda (x) - (equal (key-description (car x)) nkey)) - org-disputed-keys))) + (x (cl-find-if (lambda (x) (equal (key-description (car x)) nkey)) + org-disputed-keys))) (setq key (if x (cdr x) key)))) - (when (featurep 'xemacs) - (setq key (or (cdr (assoc key org-xemacs-key-equivalents)) key))) key) -(defun org-find-if (predicate seq) - (catch 'exit - (while seq - (if (funcall predicate (car seq)) - (throw 'exit (car seq)) - (pop seq))))) - (defun org-defkey (keymap key def) "Define a key, possibly translated, as returned by `org-key'." (define-key keymap (org-key key) def)) (defcustom org-ellipsis nil - "The ellipsis to use in the Org-mode outline. -When nil, just use the standard three dots. -When a string, use that string instead. -When a face, use the standard 3 dots, but with the specified face. -The change affects only Org-mode (which will then use its own display table). + "The ellipsis to use in the Org mode outline. + +When nil, just use the standard three dots. When a non-empty string, +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." :group 'org-startup :type '(choice (const :tag "Default" nil) - (face :tag "Face" :value org-warning) - (string :tag "String" :value "...#"))) + (string :tag "String" :value "...#")) + :safe (lambda (v) (and (string-or-null-p v) (not (equal "" v))))) (defvar org-display-table nil - "The display table for org-mode, in case `org-ellipsis' is non-nil.") + "The display table for Org mode, in case `org-ellipsis' is non-nil.") (defgroup org-keywords nil - "Keywords in Org-mode." + "Keywords in Org mode." :tag "Org Keywords" :group 'org) -(defcustom org-deadline-string "DEADLINE:" - "String to mark deadline entries. -A deadline is this string, followed by a time stamp. Should be a word, -terminated by a colon. You can insert a schedule keyword and -a timestamp with \\[org-deadline]. -Changes become only effective after restarting Emacs." - :group 'org-keywords - :type 'string) - -(defcustom org-scheduled-string "SCHEDULED:" - "String to mark scheduled TODO entries. -A schedule is this string, followed by a time stamp. Should be a word, -terminated by a colon. You can insert a schedule keyword and -a timestamp with \\[org-schedule]. -Changes become only effective after restarting Emacs." - :group 'org-keywords - :type 'string) - -(defcustom org-closed-string "CLOSED:" - "String used as the prefix for timestamps logging closing a TODO entry." - :group 'org-keywords - :type 'string) - -(defcustom org-clock-string "CLOCK:" - "String used as prefix for timestamps clocking work hours on an item." - :group 'org-keywords - :type 'string) - (defcustom org-closed-keep-when-no-todo nil "Remove CLOSED: time-stamp when switching back to a non-todo state?" :group 'org-todo @@ -881,37 +1157,8 @@ Changes become only effective after restarting Emacs." :package-version '(Org . "8.0") :type 'boolean) -(defconst org-planning-or-clock-line-re (concat "^[ \t]*\\(" - org-scheduled-string "\\|" - org-deadline-string "\\|" - org-closed-string "\\|" - org-clock-string "\\)") - "Matches a line with planning or clock info.") - -(defcustom org-comment-string "COMMENT" - "Entries starting with this keyword will never be exported. -An entry can be toggled between COMMENT and normal with -\\[org-toggle-comment]. -Changes become only effective after restarting Emacs." - :group 'org-keywords - :type 'string) - -(defcustom org-quote-string "QUOTE" - "Entries starting with this keyword will be exported in fixed-width font. -Quoting applies only to the text in the entry following the headline, and does -not extend beyond the next headline, even if that is lower level. -An entry can be toggled between QUOTE and normal with -\\[org-toggle-fixed-width-section]." - :group 'org-keywords - :type 'string) - -(defconst org-repeat-re - "<[0-9]\\{4\\}-[0-9][0-9]-[0-9][0-9] [^>\n]*?\\([.+]?\\+[0-9]+[hdwmy]\\(/[0-9]+[hdwmy]\\)?\\)" - "Regular expression for specifying repeated events. -After a match, group 1 contains the repeat expression.") - (defgroup org-structure nil - "Options concerning the general structure of Org-mode files." + "Options concerning the general structure of Org files." :tag "Org Structure" :group 'org) @@ -920,92 +1167,88 @@ After a match, group 1 contains the repeat expression.") :tag "Org Reveal Location" :group 'org-structure) -(defconst org-context-choice - '(choice - (const :tag "Always" t) - (const :tag "Never" nil) - (repeat :greedy t :tag "Individual contexts" - (cons - (choice :tag "Context" - (const agenda) - (const org-goto) - (const occur-tree) - (const tags-tree) - (const link-search) - (const mark-goto) - (const bookmark-jump) - (const isearch) - (const default)) - (boolean)))) - "Contexts for the reveal options.") - -(defcustom org-show-hierarchy-above '((default . t)) - "Non-nil means show full hierarchy when revealing a location. -Org-mode often shows locations in an org-mode file which might have -been invisible before. When this is set, the hierarchy of headings -above the exposed location is shown. -Turning this off for example for sparse trees makes them very compact. -Instead of t, this can also be an alist specifying this option for different -contexts. Valid contexts are +(defcustom org-show-context-detail '((agenda . local) + (bookmark-jump . lineage) + (isearch . lineage) + (default . ancestors)) + "Alist between context and visibility span when revealing a location. + +\\<org-mode-map>Some actions may move point into invisible +locations. As a consequence, Org always expose a neighborhood +around point. How much is shown depends on the initial action, +or context. Valid contexts are + agenda when exposing an entry from the agenda - org-goto when using the command `org-goto' on key C-c C-j - occur-tree when using the command `org-occur' on key C-c / + org-goto when using the command `org-goto' (`\\[org-goto]') + occur-tree when using the command `org-occur' (`\\[org-sparse-tree] /') tags-tree when constructing a sparse tree based on tags matches link-search when exposing search matches associated with a link mark-goto when exposing the jump goal of a mark bookmark-jump when exposing a bookmark location isearch when exiting from an incremental search - default default for all contexts not set explicitly" - :group 'org-reveal-location - :type org-context-choice) - -(defcustom org-show-following-heading '((default . nil)) - "Non-nil means show following heading when revealing a location. -Org-mode often shows locations in an org-mode file which might have -been invisible before. When this is set, the heading following the -match is shown. -Turning this off for example for sparse trees makes them very compact, -but makes it harder to edit the location of the match. In such a case, -use the command \\[org-reveal] to show more context. -Instead of t, this can also be an alist specifying this option for different -contexts. See `org-show-hierarchy-above' for valid contexts." - :group 'org-reveal-location - :type org-context-choice) - -(defcustom org-show-siblings '((default . nil) (isearch t) (bookmark-jump t)) - "Non-nil means show all sibling heading when revealing a location. -Org-mode often shows locations in an org-mode file which might have -been invisible before. When this is set, the sibling of the current entry -heading are all made visible. If `org-show-hierarchy-above' is t, -the same happens on each level of the hierarchy above the current entry. - -By default this is on for the isearch context, off for all other contexts. -Turning this off for example for sparse trees makes them very compact, -but makes it harder to edit the location of the match. In such a case, -use the command \\[org-reveal] to show more context. -Instead of t, this can also be an alist specifying this option for different -contexts. See `org-show-hierarchy-above' for valid contexts." - :group 'org-reveal-location - :type org-context-choice - :version "24.4" - :package-version '(Org . "8.0")) + default default for all contexts not set explicitly + +Allowed visibility spans are + + minimal show current headline; if point is not on headline, + also show entry + + local show current headline, entry and next headline + + ancestors show current headline and its direct ancestors; if + point is not on headline, also show entry + + lineage show current headline, its direct ancestors and all + their children; if point is not on headline, also show + entry and first child + + tree show current headline, its direct ancestors and all + their children; if point is not on headline, also show + entry and all children + + canonical show current headline, its direct ancestors along with + their entries and children; if point is not located on + the headline, also show current entry and all children -(defcustom org-show-entry-below '((default . nil)) - "Non-nil means show the entry below a headline when revealing a location. -Org-mode often shows locations in an org-mode file which might have -been invisible before. When this is set, the text below the headline that is -exposed is also shown. +As special cases, a nil or t value means show all contexts in +`minimal' or `canonical' view, respectively. -By default this is off for all contexts. -Instead of t, this can also be an alist specifying this option for different -contexts. See `org-show-hierarchy-above' for valid contexts." +Some views can make displayed information very compact, but also +make it harder to edit the location of the match. In such +a case, use the command `org-reveal' (`\\[org-reveal]') to show +more context." :group 'org-reveal-location - :type org-context-choice) + :version "26.1" + :package-version '(Org . "9.0") + :type '(choice + (const :tag "Canonical" t) + (const :tag "Minimal" nil) + (repeat :greedy t :tag "Individual contexts" + (cons + (choice :tag "Context" + (const agenda) + (const org-goto) + (const occur-tree) + (const tags-tree) + (const link-search) + (const mark-goto) + (const bookmark-jump) + (const isearch) + (const default)) + (choice :tag "Detail level" + (const minimal) + (const local) + (const ancestors) + (const lineage) + (const tree) + (const canonical)))))) (defcustom org-indirect-buffer-display 'other-window "How should indirect tree buffers be displayed? + This applies to indirect buffers created with the commands -\\[org-tree-to-indirect-buffer] and \\[org-agenda-tree-to-indirect-buffer]. +`org-tree-to-indirect-buffer' and `org-agenda-tree-to-indirect-buffer'. + Valid values are: current-window Display in the current window other-window Just display in another window. @@ -1024,7 +1267,13 @@ new-frame Make a new frame each time. Note that in this case (defcustom org-use-speed-commands nil "Non-nil means activate single letter commands at beginning of a headline. This may also be a function to test for appropriate locations where speed -commands should be active." +commands should be active. + +For example, to activate speed commands when the point is on any +star at the beginning of the headline, you can do this: + + (setq org-use-speed-commands + (lambda () (and (looking-at org-outline-regexp) (looking-back \"^\\**\"))))" :group 'org-structure :type '(choice (const :tag "Never" nil) @@ -1036,7 +1285,7 @@ commands should be active." This list will be checked before `org-speed-commands-default' when the variable `org-use-speed-commands' is non-nil and when the cursor is at the beginning of a headline. -The car if each entry is a string with a single letter, which must +The car of each entry is a string with a single letter, which must be assigned to `self-insert-command' in the global map. The cdr is either a command to be called interactively, a function to be called, or a form to be evaluated. @@ -1054,10 +1303,10 @@ commands in the Help buffer using the `?' speed command." (sexp)))))) (defcustom org-bookmark-names-plist - '(:last-capture "org-capture-last-stored" - :last-refile "org-refile-last-stored" - :last-capture-marker "org-capture-last-stored-marker") - "Names for bookmarks automatically set by some Org commands. + '(:last-capture "org-capture-last-stored" + :last-refile "org-refile-last-stored" + :last-capture-marker "org-capture-last-stored-marker") + "Names for bookmarks automatically set by some Org commands. This can provide strings as names for a number of bookmarks Org sets automatically. The following keys are currently implemented: :last-capture @@ -1065,11 +1314,11 @@ automatically. The following keys are currently implemented: :last-refile When a key does not show up in the property list, the corresponding bookmark is not set." - :group 'org-structure - :type 'plist) + :group 'org-structure + :type 'plist) (defgroup org-cycle nil - "Options concerning visibility cycling in Org-mode." + "Options concerning visibility cycling in Org mode." :tag "Org Cycle" :group 'org-structure) @@ -1093,25 +1342,8 @@ than its value." (const :tag "No limit" nil) (integer :tag "Maximum level"))) -(defcustom org-drawers '("PROPERTIES" "CLOCK" "LOGBOOK" "RESULTS") - "Names of drawers. Drawers are not opened by cycling on the headline above. -Drawers only open with a TAB on the drawer line itself. A drawer looks like -this: - :DRAWERNAME: - ..... - :END: -The drawer \"PROPERTIES\" is special for capturing properties through -the property API. - -Drawers can be defined on the per-file basis with a line like: - -#+DRAWERS: HIDDEN STATE PROPERTIES" - :group 'org-structure - :group 'org-cycle - :type '(repeat (string :tag "Drawer Name"))) - (defcustom org-hide-block-startup nil - "Non-nil means entering Org-mode will fold all blocks. + "Non-nil means entering Org mode will fold all blocks. This can also be set in on a per-file basis with #+STARTUP: hideblocks @@ -1122,12 +1354,17 @@ This can also be set in on a per-file basis with (defcustom org-cycle-global-at-bob nil "Cycle globally if cursor is at beginning of buffer and not at a headline. -This makes it possible to do global cycling without having to use S-TAB or -\\[universal-argument] TAB. For this special case to work, the first line -of the buffer must not be a headline -- it may be empty or some other text. + +This makes it possible to do global cycling without having to use `S-TAB' +or `\\[universal-argument] TAB'. For this special case to work, the first \ +line of the buffer +must not be a headline -- it may be empty or some other text. + When used in this way, `org-cycle-hook' is disabled temporarily to make -sure the cursor stays at the beginning of the buffer. When this option is -nil, don't do anything special at the beginning of the buffer." +sure the cursor stays at the beginning of the buffer. + +When this option is nil, don't do anything special at the beginning of +the buffer." :group 'org-cycle :type 'boolean) @@ -1166,7 +1403,7 @@ visibility is cycled." "Number of empty lines needed to keep an empty line between collapsed trees. If you leave an empty line between the end of a subtree and the following headline, this empty line is hidden when the subtree is folded. -Org-mode will leave (exactly) one empty line visible if the number of +Org mode will leave (exactly) one empty line visible if the number of empty lines is equal or larger to the number given in this variable. So the default 2 means at least 2 empty lines after the end of a subtree are needed to produce free space between a collapsed subtree and the @@ -1192,7 +1429,6 @@ the values `folded', `children', or `subtree'." (defcustom org-cycle-hook '(org-cycle-hide-archived-subtrees org-cycle-hide-drawers - org-cycle-hide-inline-tasks org-cycle-show-empty-lines org-optimize-window-after-visibility-change) "Hook that is run after `org-cycle' has changed the buffer visibility. @@ -1202,10 +1438,12 @@ argument is a symbol. After a global state change, it can have the values `overview', `contents', or `all'. After a local state change, it can have the values `folded', `children', or `subtree'." :group 'org-cycle - :type 'hook) + :type 'hook + :version "26.1" + :package-version '(Org . "8.3")) (defgroup org-edit-structure nil - "Options concerning structure editing in Org-mode." + "Options concerning structure editing in Org mode." :tag "Org Edit Structure" :group 'org-structure) @@ -1229,23 +1467,25 @@ lines to the buffer: "Non-nil means adapt indentation to outline node level. When this variable is set, Org assumes that you write outlines by -indenting text in each node to align with the headline (after the stars). -The following issues are influenced by this variable: +indenting text in each node to align with the headline (after the +stars). The following issues are influenced by this variable: -- When this is set and the *entire* text in an entry is indented, the - indentation is increased by one space in a demotion command, and - decreased by one in a promotion command. If any line in the entry - body starts with text at column 0, indentation is not changed at all. +- The indentation is increased by one space in a demotion + command, and decreased by one in a promotion command. However, + in the latter case, if shifting some line in the entry body + would alter document structure (e.g., insert a new headline), + indentation is not changed at all. -- Property drawers and planning information is inserted indented when - this variable s set. When nil, they will not be indented. +- Property drawers and planning information is inserted indented + when this variable is set. When nil, they will not be indented. -- TAB indents a line relative to context. The lines below a headline - will be indented when this variable is set. +- TAB indents a line relative to current level. The lines below + a headline will be indented when this variable is set. -Note that this is all about true indentation, by adding and removing -space characters. See also `org-indent.el' which does level-dependent -indentation in a virtual way, i.e. at display time in Emacs." +Note that this is all about true indentation, by adding and +removing space characters. See also `org-indent.el' which does +level-dependent indentation in a virtual way, i.e. at display +time in Emacs." :group 'org-edit-structure :type 'boolean) @@ -1286,7 +1526,7 @@ This may also be a cons cell where the behavior for `C-a' and (const :tag "off" nil) (const :tag "on: before tags first" t) (const :tag "reversed: after tags first" reversed))))) -(org-defvaralias 'org-special-ctrl-a 'org-special-ctrl-a/e) +(defvaralias 'org-special-ctrl-a 'org-special-ctrl-a/e) (defcustom org-special-ctrl-k nil "Non-nil means `C-k' will behave specially in headlines. @@ -1386,9 +1626,11 @@ default the value to be used for all contexts not explicitly (defcustom org-insert-heading-respect-content nil "Non-nil means insert new headings after the current subtree. +\\<org-mode-map> When nil, the new heading is created directly after the current line. -The commands \\[org-insert-heading-respect-content] and \\[org-insert-todo-heading-respect-content] turn -this variable on for the duration of the command." +The commands `\\[org-insert-heading-respect-content]' and \ +`\\[org-insert-todo-heading-respect-content]' turn this variable on +for the duration of the command." :group 'org-structure :type 'boolean) @@ -1398,11 +1640,7 @@ this variable on for the duration of the command." The value is an alist, with `heading' and `plain-list-item' as CAR, and a boolean flag as CDR. The cdr may also be the symbol `auto', in which case Org will look at the surrounding headings/items and try to -make an intelligent decision whether to insert a blank line or not. - -For plain lists, if `org-list-empty-line-terminates-plain-lists' is set, -the setting here is ignored and no empty line is inserted to avoid breaking -the list structure." +make an intelligent decision whether to insert a blank line or not." :group 'org-edit-structure :type '(list (cons (const heading) @@ -1422,8 +1660,7 @@ the list structure." (defcustom org-enable-fixed-width-editor t "Non-nil means lines starting with \":\" are treated as fixed-width. This currently only means they are never auto-wrapped. -When nil, such lines will be treated like ordinary lines. -See also the QUOTE keyword." +When nil, such lines will be treated like ordinary lines." :group 'org-edit-structure :type 'boolean) @@ -1441,7 +1678,7 @@ When nil, you can use these keybindings to navigate the buffer: :type 'boolean) (defgroup org-sparse-trees nil - "Options concerning sparse trees in Org-mode." + "Options concerning sparse trees in Org mode." :tag "Org Sparse Trees" :group 'org-structure) @@ -1454,14 +1691,26 @@ changed by an edit command." (defcustom org-remove-highlights-with-change t "Non-nil means any change to the buffer will remove temporary highlights. +\\<org-mode-map>\ Such highlights are created by `org-occur' and `org-clock-display'. -When nil, `C-c C-c' needs to be used to get rid of the highlights. -The highlights created by `org-preview-latex-fragment' always need -`C-c C-c' to be removed." +When nil, `\\[org-ctrl-c-ctrl-c]' needs to be used \ +to get rid of the highlights. +The highlights created by `org-toggle-latex-fragment' always need +`\\[org-toggle-latex-fragment]' to be removed." :group 'org-sparse-trees :group 'org-time :type 'boolean) +(defcustom org-occur-case-fold-search t + "Non-nil means `org-occur' should be case-insensitive. +If set to `smart' the search will be case-insensitive only if it +doesn't specify any upper case character." + :group 'org-sparse-trees + :version "26.1" + :type '(choice + (const :tag "Case-sensitive" nil) + (const :tag "Case-insensitive" t) + (const :tag "Case-insensitive for lower case searches only" 'smart))) (defcustom org-occur-hook '(org-first-headline-recenter) "Hook that is run after `org-occur' has constructed a sparse tree. @@ -1471,54 +1720,22 @@ as possible." :type 'hook) (defgroup org-imenu-and-speedbar nil - "Options concerning imenu and speedbar in Org-mode." + "Options concerning imenu and speedbar in Org mode." :tag "Org Imenu and Speedbar" :group 'org-structure) (defcustom org-imenu-depth 2 - "The maximum level for Imenu access to Org-mode headlines. + "The maximum level for Imenu access to Org headlines. This also applied for speedbar access." :group 'org-imenu-and-speedbar :type 'integer) (defgroup org-table nil - "Options concerning tables in Org-mode." + "Options concerning tables in Org mode." :tag "Org Table" :group 'org) -(defcustom org-enable-table-editor 'optimized - "Non-nil means lines starting with \"|\" are handled by the table editor. -When nil, such lines will be treated like ordinary lines. - -When equal to the symbol `optimized', the table editor will be optimized to -do the following: -- Automatic overwrite mode in front of whitespace in table fields. - This makes the structure of the table stay in tact as long as the edited - field does not exceed the column width. -- Minimize the number of realigns. Normally, the table is aligned each time - TAB or RET are pressed to move to another field. With optimization this - happens only if changes to a field might have changed the column width. -Optimization requires replacing the functions `self-insert-command', -`delete-char', and `backward-delete-char' in Org-mode buffers, with a -slight (in fact: unnoticeable) speed impact for normal typing. Org-mode is -very good at guessing when a re-align will be necessary, but you can always -force one with \\[org-ctrl-c-ctrl-c]. - -If you would like to use the optimized version in Org-mode, but the -un-optimized version in OrgTbl-mode, see the variable `orgtbl-optimized'. - -This variable can be used to turn on and off the table editor during a session, -but in order to toggle optimization, a restart is required. - -See also the variable `org-table-auto-blank-field'." - :group 'org-table - :type '(choice - (const :tag "off" nil) - (const :tag "on" t) - (const :tag "on, optimized" optimized))) - -(defcustom org-self-insert-cluster-for-undo (or (featurep 'xemacs) - (version<= emacs-version "24.1")) +(defcustom org-self-insert-cluster-for-undo nil "Non-nil means cluster self-insert commands for undo when possible. If this is set, then, like in the Emacs command loop, 20 consecutive characters will be undone together. @@ -1534,24 +1751,96 @@ calls `table-recognize-table'." :type 'boolean) (defgroup org-link nil - "Options concerning links in Org-mode." + "Options concerning links in Org mode." :tag "Org Link" :group 'org) -(defvar org-link-abbrev-alist-local nil +(defvar-local org-link-abbrev-alist-local nil "Buffer-local version of `org-link-abbrev-alist', which see. The value of this is taken from the #+LINK lines.") -(make-variable-buffer-local 'org-link-abbrev-alist-local) + +(defcustom org-link-parameters + '(("doi" :follow org--open-doi-link) + ("elisp" :follow org--open-elisp-link) + ("file" :complete org-file-complete-link) + ("ftp" :follow (lambda (path) (browse-url (concat "ftp:" path)))) + ("help" :follow org--open-help-link) + ("http" :follow (lambda (path) (browse-url (concat "http:" path)))) + ("https" :follow (lambda (path) (browse-url (concat "https:" path)))) + ("mailto" :follow (lambda (path) (browse-url (concat "mailto:" path)))) + ("news" :follow (lambda (path) (browse-url (concat "news:" path)))) + ("shell" :follow org--open-shell-link)) + "An alist of properties that defines all the links in Org mode. +The key in each association is a string of the link type. +Subsequent optional elements make up a p-list of link properties. + +:follow - A function that takes the link path as an argument. + +:export - A function that takes the link path, description and +export-backend as arguments. + +:store - A function responsible for storing the link. See the +function `org-store-link-functions'. + +:complete - A function that inserts a link with completion. The +function takes one optional prefix arg. + +:face - A face for the link, or a function that returns a face. +The function takes one argument which is the link path. The +default face is `org-link'. + +:mouse-face - The mouse-face. The default is `highlight'. + +:display - `full' will not fold the link in descriptive +display. Default is `org-link'. + +:help-echo - A string or function that takes (window object position) +as arguments and returns a string. + +:keymap - A keymap that is active on the link. The default is +`org-mouse-map'. + +:htmlize-link - A function for the htmlize-link. Defaults +to (list :uri \"type:path\") + +:activate-func - A function to run at the end of font-lock +activation. The function must accept (link-start link-end path bracketp) +as arguments." + :group 'org-link + :type '(alist :tag "Link display parameters" + :value-type plist) + :version "26.1" + :package-version '(Org . "9.1")) + +(defun org-link-get-parameter (type key) + "Get TYPE link property for KEY. +TYPE is a string and KEY is a plist keyword." + (plist-get + (cdr (assoc type org-link-parameters)) + key)) + +(defun org-link-set-parameters (type &rest parameters) + "Set link TYPE properties to PARAMETERS. + PARAMETERS should be :key val pairs." + (let ((data (assoc type org-link-parameters))) + (if data (setcdr data (org-combine-plists (cdr data) parameters)) + (push (cons type parameters) org-link-parameters) + (org-make-link-regexps) + (org-element-update-syntax)))) + +(defun org-link-types () + "Return a list of known link types." + (mapcar #'car org-link-parameters)) (defcustom org-link-abbrev-alist nil "Alist of link abbreviations. The car of each element is a string, to be replaced at the start of a link. The cdrs are replacement values, like (\"linkkey\" . REPLACE). Abbreviated -links in Org-mode buffers can have an optional tag after a double colon, e.g. +links in Org buffers can have an optional tag after a double colon, e.g., [[linkkey:tag][description]] -The `linkkey' must be a word word, starting with a letter, followed +The `linkkey' must be a single word, starting with a letter, followed by letters, numbers, `-' or `_'. If REPLACE is a string, the tag will simply be appended to create the link. @@ -1603,11 +1892,18 @@ adaptive Use relative path for files in the current directory and sub- (const noabbrev) (const adaptive))) -(defcustom org-activate-links '(bracket angle plain radio tag date footnote) - "Types of links that should be activated in Org-mode files. -This is a list of symbols, each leading to the activation of a certain link -type. In principle, it does not hurt to turn on most link types - there may -be a small gain when turning off unused link types. The types are: +(defvaralias 'org-activate-links 'org-highlight-links) +(defcustom org-highlight-links '(bracket angle plain radio tag date footnote) + "Types of links that should be highlighted in Org files. + +This is a list of symbols, each one of them leading to the +highlighting of a certain link type. + +You can still open links that are not highlighted. + +In principle, it does not hurt to turn on highlighting for all +link types. There may be a small gain when turning off unused +link types. The types are: bracket The recommended [[link][description]] or [[link]] links with hiding. angle Links in angular brackets that may contain whitespace like @@ -1618,8 +1914,10 @@ tag Tag settings in a headline (link to tag search). date Time stamps (link to calendar). footnote Footnote labels. -Changing this variable requires a restart of Emacs to become effective." +If you set this variable during an Emacs session, use `org-mode-restart' +in the Org buffer so that the change takes effect." :group 'org-link + :group 'org-appearance :type '(set :greedy t (const :tag "Double bracket links" bracket) (const :tag "Angular bracket links" angle) @@ -1631,15 +1929,15 @@ Changing this variable requires a restart of Emacs to become effective." (defcustom org-make-link-description-function nil "Function to use for generating link descriptions from links. -When nil, the link location will be used. This function must take -two parameters: the first one is the link, the second one is the -description generated by `org-insert-link'. The function should -return the description to use." +This function must take two parameters: the first one is the +link, the second one is the description generated by +`org-insert-link'. The function should return the description to +use." :group 'org-link :type '(choice (const nil) (function))) (defgroup org-link-store nil - "Options concerning storing links in Org-mode." + "Options concerning storing links in Org mode." :tag "Org Store Link" :group 'org-link) @@ -1684,32 +1982,36 @@ It should match if the message is from the user him/herself." (defcustom org-context-in-file-links t "Non-nil means file links from `org-store-link' contain context. -A search string will be added to the file name with :: as separator and -used to find the context when the link is activated by the command +\\<org-mode-map> +A search string will be added to the file name with :: as separator +and used to find the context when the link is activated by the command `org-open-at-point'. When this option is t, the entire active region will be placed in the search string of the file link. If set to a positive integer, only the first n lines of context will be stored. -Using a prefix arg to the command \\[org-store-link] (`org-store-link') +Using a prefix arg to the command `org-store-link' (`\\[universal-argument] \ +\\[org-store-link]') negates this setting for the duration of the command." :group 'org-link-store :type '(choice boolean integer)) (defcustom org-keep-stored-link-after-insertion nil "Non-nil means keep link in list for entire session. - +\\<org-mode-map> The command `org-store-link' adds a link pointing to the current location to an internal list. These links accumulate during a session. The command `org-insert-link' can be used to insert links into any -Org-mode file (offering completion for all stored links). When this -option is nil, every link which has been inserted once using \\[org-insert-link] -will be removed from the list, to make completing the unused links -more efficient." +Org file (offering completion for all stored links). + +When this option is nil, every link which has been inserted once using +`\\[org-insert-link]' will be removed from the list, to make completing the \ +unused +links more efficient." :group 'org-link-store :type 'boolean) (defgroup org-link-follow nil - "Options concerning following links in Org-mode." + "Options concerning following links in Org mode." :tag "Org Follow Link" :group 'org-link) @@ -1749,10 +2051,10 @@ In tables, the special behavior of RET has precedence." (defcustom org-mouse-1-follows-link (if (boundp 'mouse-1-click-follows-link) mouse-1-click-follows-link t) "Non-nil means mouse-1 on a link will follow the link. -A longer mouse click will still set point. Does not work on XEmacs. -Needs to be set before org.el is loaded." +A longer mouse click will still set point. Needs to be set +before org.el is loaded." :group 'org-link-follow - :version "24.4" + :version "26.1" :package-version '(Org . "8.3") :type '(choice (const :tag "A double click follows the link" double) @@ -1766,16 +2068,22 @@ Changing this requires a restart of Emacs to work correctly." :type 'integer) (defcustom org-link-search-must-match-exact-headline 'query-to-create - "Non-nil means internal links in Org files must exactly match a headline. -When nil, the link search tries to match a phrase with all words -in the search text." + "Non-nil means internal fuzzy links can only match headlines. + +When nil, the a fuzzy link may point to a target or a named +construct in the document. When set to the special value +`query-to-create', offer to create a new headline when none +matched. + +Spaces and statistics cookies are ignored during heading searches." :group 'org-link-follow :version "24.1" :type '(choice (const :tag "Use fuzzy text search" nil) (const :tag "Match only exact headline" t) (const :tag "Match exact headline or query to create it" - query-to-create))) + query-to-create)) + :safe #'symbolp) (defcustom org-link-frame-setup '((vm . vm-visit-folder-other-frame) @@ -1836,7 +2144,7 @@ another window." "Non-nil means use indirect buffer to display infile links. Activating internal links (from one location in a file to another location in the same file) normally just jumps to the location. When the link is -activated with a \\[universal-argument] prefix (or with mouse-3), the link \ +activated with a `\\[universal-argument]' prefix (or with mouse-3), the link \ is displayed in another window. When this option is set, the other window actually displays an indirect buffer clone of the current buffer, to avoid any visibility @@ -1860,26 +2168,13 @@ window on that directory." :group 'org-link-follow :type 'boolean) -(defcustom org-link-mailto-program '(browse-url "mailto:%a?subject=%s") - "Function and arguments to call for following mailto links. -This is a list with the first element being a Lisp function, and the -remaining elements being arguments to the function. In string arguments, -%a will be replaced by the address, and %s will be replaced by the subject -if one was given like in <mailto:arthur@galaxy.org::this subject>." - :group 'org-link-follow - :type '(choice - (const :tag "browse-url" (browse-url-mail "mailto:%a?subject=%s")) - (const :tag "compose-mail" (compose-mail "%a" "%s")) - (const :tag "message-mail" (message-mail "%a" "%s")) - (cons :tag "other" (function) (repeat :tag "argument" sexp)))) - (defcustom org-confirm-shell-link-function 'yes-or-no-p "Non-nil means ask for confirmation before executing shell links. Shell links can be dangerous: just think about a link [[shell:rm -rf ~/*][Google Search]] -This link would show up in your Org-mode document as \"Google Search\", +This link would show up in your Org document as \"Google Search\", but really it would remove your entire home directory. Therefore we advise against setting this variable to nil. Just change it to `y-or-n-p' if you want to confirm with a @@ -1891,7 +2186,7 @@ single keystroke rather than having to type \"yes\"." (const :tag "no confirmation (dangerous)" nil))) (put 'org-confirm-shell-link-function 'safe-local-variable - #'(lambda (x) (member x '(yes-or-no-p y-or-n-p)))) + (lambda (x) (member x '(yes-or-no-p y-or-n-p)))) (defcustom org-confirm-shell-link-not-regexp "" "A regexp to skip confirmation for shell links." @@ -1905,7 +2200,7 @@ Elisp links can be dangerous: just think about a link [[elisp:(shell-command \"rm -rf ~/*\")][Google Search]] -This link would show up in your Org-mode document as \"Google Search\", +This link would show up in your Org document as \"Google Search\", but really it would remove your entire home directory. Therefore we advise against setting this variable to nil. Just change it to `y-or-n-p' if you want to confirm with a @@ -1917,7 +2212,7 @@ single keystroke rather than having to type \"yes\"." (const :tag "no confirmation (dangerous)" nil))) (put 'org-confirm-shell-link-function 'safe-local-variable - #'(lambda (x) (member x '(yes-or-no-p y-or-n-p)))) + (lambda (x) (member x '(yes-or-no-p y-or-n-p)))) (defcustom org-confirm-elisp-link-not-regexp "" "A regexp to skip confirmation for Elisp links." @@ -1934,30 +2229,23 @@ See `org-file-apps'.") (defconst org-file-apps-defaults-macosx '((remote . emacs) - (t . "open %s") (system . "open %s") ("ps.gz" . "gv %s") ("eps.gz" . "gv %s") ("dvi" . "xdvi %s") - ("fig" . "xfig %s")) + ("fig" . "xfig %s") + (t . "open %s")) "Default file applications on a macOS system. The system \"open\" is known as a default, but we use X11 applications for some files for which the OS does not have a good default. See `org-file-apps'.") (defconst org-file-apps-defaults-windowsnt - (list - '(remote . emacs) - (cons t - (list (if (featurep 'xemacs) - 'mswindows-shell-execute - 'w32-shell-execute) - "open" 'file)) - (cons 'system - (list (if (featurep 'xemacs) - 'mswindows-shell-execute - 'w32-shell-execute) - "open" 'file))) + (list '(remote . emacs) + (cons 'system (lambda (file _path) + (with-no-warnings (w32-shell-execute "open" file)))) + (cons t (lambda (file _path) + (with-no-warnings (w32-shell-execute "open" file))))) "Default file applications on a Windows NT system. The system \"open\" is used for most files. See `org-file-apps'.") @@ -1968,11 +2256,15 @@ See `org-file-apps'.") ("\\.x?html?\\'" . default) ("\\.pdf\\'" . default)) "External applications for opening `file:path' items in a document. -Org-mode uses system defaults for different file types, but +\\<org-mode-map>\ + +Org mode uses system defaults for different file types, but you can use this variable to set the application for a given file extension. The entries in this list are cons cells where the car identifies -files and the cdr the corresponding command. Possible values for the -file identifier are +files and the cdr the corresponding command. + +Possible values for the file identifier are: + \"string\" A string as a file identifier can be interpreted in different ways, depending on its contents: @@ -1985,8 +2277,8 @@ file identifier are filename matches the regexp. If you want to use groups here, use shy groups. - Example: (\"\\.x?html\\\\='\" . \"firefox %s\") - (\"\\(?:xhtml\\|html\\)\" . \"firefox %s\") + Example: (\"\\\\.x?html\\\\\\='\" . \"firefox %s\") + (\"\\\\(?:xhtml\\\\|html\\\\)\\\\\\='\" . \"firefox %s\") to open *.html and *.xhtml with firefox. - Regular expression which contains (non-shy) groups: @@ -1998,10 +2290,11 @@ file identifier are that does not use any of the group matches, this case is handled identically to the second one (i.e. match against file name only). - In a custom lisp form, you can access the group matches with + In a custom function, you can access the group matches with (match-string n link). - Example: (\"\\.pdf::\\(\\d+\\)\\\\='\" . \"evince -p %1 %s\") + Example: (\"\\\\.pdf::\\\\(\\\\d+\\\\)\\\\\\='\" . \ +\"evince -p %1 %s\") to open [[file:document.pdf::5]] with evince at page 5. `directory' Matches a directory @@ -2013,28 +2306,32 @@ file identifier are command `emacs' will open most files in Emacs. Beware that this will also open html files inside Emacs, unless you add (\"html\" . default) to the list as well. - t Default for files not matched by any of the other options. `system' The system command to open files, like `open' on Windows and macOS, and mailcap under GNU/Linux. This is the command - that will be selected if you call `C-c C-o' with a double - \\[universal-argument] \\[universal-argument] prefix. + that will be selected if you call `org-open-at-point' with a + double prefix argument (`\\[universal-argument] \ +\\[universal-argument] \\[org-open-at-point]'). + t Default for files not matched by any of the other options. Possible values for the command are: + `emacs' The file will be visited by the current Emacs process. `default' Use the default application for this file type, which is the association for t in the list, most likely in the system-specific - part. - This can be used to overrule an unwanted setting in the + part. This can be used to overrule an unwanted setting in the system-specific variable. `system' Use the system command for opening files, like \"open\". This command is specified by the entry whose car is `system'. Most likely, the system-specific version of this variable does define this command, but you can overrule/replace it here. +`mailcap' Use command specified in the mailcaps. string A command to be executed by a shell; %s will be replaced by the path to the file. - sexp A Lisp form which will be evaluated. The file path will - be available in the Lisp variable `file'. + function A Lisp function, which will be called with two arguments: + the file path and the original link string, without the + \"file:\" prefix. + For more examples, see the system specific constants `org-file-apps-defaults-macosx' `org-file-apps-defaults-windowsnt' @@ -2054,7 +2351,7 @@ For more examples, see the system specific constants (const :tag "Use default" default) (const :tag "Use the system command" system) (string :tag "Command") - (sexp :tag "Lisp form"))))) + (function :tag "Function"))))) (defcustom org-doi-server-url "http://dx.doi.org/" "The URL of the DOI server." @@ -2063,22 +2360,22 @@ For more examples, see the system specific constants :group 'org-link-follow) (defgroup org-refile nil - "Options concerning refiling entries in Org-mode." + "Options concerning refiling entries in Org mode." :tag "Org Refile" :group 'org) (defcustom org-directory "~/org" - "Directory with org files. + "Directory with Org files. This is just a default location to look for Org files. There is no need -at all to put your files into this directory. It is only used in the +at all to put your files into this directory. It is used in the following situations: 1. When a capture template specifies a target file that is not an absolute path. The path will then be interpreted relative to `org-directory' -2. When a capture note is filed away in an interactive way (when exiting the - note buffer with `C-1 C-c C-c'. The user is prompted for an org file, - with `org-directory' as the default path." +2. When the value of variable `org-agenda-files' is a single file, any + relative paths in this file will be taken as relative to + `org-directory'." :group 'org-refile :group 'org-capture :type 'directory) @@ -2089,9 +2386,7 @@ Used as a fall back file for org-capture.el, for templates that do not specify a target file." :group 'org-refile :group 'org-capture - :type '(choice - (const :tag "Default from remember-data-file" nil) - file)) + :type 'file) (defcustom org-goto-interface 'outline "The default interface to be used for `org-goto'. @@ -2154,7 +2449,7 @@ will temporarily be changed to `time'." (const :tag "Record timestamp with note." note))) (defcustom org-refile-targets nil - "Targets for refiling entries with \\[org-refile]. + "Targets for refiling entries with `\\[org-refile]'. This is a list of cons cells. Each cell contains: - a specification of the files to be considered, either a list of files, or a symbol whose function or variable value will be used to retrieve @@ -2218,12 +2513,15 @@ of the subtree." (defcustom org-refile-use-cache nil "Non-nil means cache refile targets to speed up the process. +\\<org-mode-map>\ The cache for a particular file will be updated automatically when the buffer has been killed, or when any of the marker used for flagging refile targets no longer points at a live buffer. If you have added new entries to a buffer that might themselves be targets, -you need to clear the cache manually by pressing `C-0 C-c C-w' or, if you -find that easier, `C-u C-u C-u C-c C-w'." +you need to clear the cache manually by pressing `C-0 \\[org-refile]' or, +if you find that easier, \ +`\\[universal-argument] \\[universal-argument] \\[universal-argument] \ +\\[org-refile]'." :group 'org-refile :version "24.1" :type 'boolean) @@ -2236,23 +2534,26 @@ 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 `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 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-mode uses the refile interface to select an outline path -\(see variable `org-refile-use-outline-path'), the completion of -the path can be done is a single go, or if can be done in steps down -the headline hierarchy. Going in steps is probably the best if you -do not use a special completion package like `ido' or `icicles'. -However, when using these packages, going in one step can be very -fast, while still showing the whole path to the entry." +When Org uses the refile interface to select an outline path (see +`org-refile-use-outline-path'), the completion of the path can be +done in a single go, or it can be done in steps down the headline +hierarchy. Going in steps is probably the best if you do not use +a special completion package like `ido' or `icicles'. However, +when using these packages, going in one step can be very fast, +while still showing the whole path to the entry." :group 'org-refile :type 'boolean) @@ -2285,12 +2586,12 @@ converted to a headline before refiling." :type 'boolean) (defgroup org-todo nil - "Options concerning TODO items in Org-mode." + "Options concerning TODO items in Org mode." :tag "Org TODO" :group 'org) (defgroup org-progress nil - "Options concerning Progress logging in Org-mode." + "Options concerning Progress logging in Org mode." :tag "Org Progress" :group 'org-time) @@ -2308,12 +2609,12 @@ Each sequence starts with a symbol, either `sequence' or `type', indicating if the keywords should be interpreted as a sequence of action steps, or as different types of TODO items. The first keywords are states requiring action - these states will select a headline -for inclusion into the global TODO list Org-mode produces. If one of -the \"keywords\" is the vertical bar, \"|\", the remaining keywords +for inclusion into the global TODO list Org produces. If one of the +\"keywords\" is the vertical bar, \"|\", the remaining keywords signify that no further action is necessary. If \"|\" is not found, the last keyword is treated as the only DONE state of the sequence. -The command \\[org-todo] cycles an entry through these states, and one +The command `\\[org-todo]' cycles an entry through these states, and one additional state where no keyword is present. For details about this cycling, see the manual. @@ -2356,44 +2657,37 @@ taken from the (otherwise obsolete) variable `org-todo-interpretation'." (lambda (widget) (widget-put widget :args (mapcar - #'(lambda (x) - (widget-convert - (cons 'const x))) + (lambda (x) + (widget-convert + (cons 'const x))) org-todo-interpretation-widgets)) widget)) (repeat (string :tag "Keyword")))))) -(defvar org-todo-keywords-1 nil +(defvar-local org-todo-keywords-1 nil "All TODO and DONE keywords active in a buffer.") -(make-variable-buffer-local 'org-todo-keywords-1) (defvar org-todo-keywords-for-agenda nil) (defvar org-done-keywords-for-agenda nil) -(defvar org-drawers-for-agenda nil) (defvar org-todo-keyword-alist-for-agenda nil) (defvar org-tag-alist-for-agenda nil "Alist of all tags from all agenda files.") (defvar org-tag-groups-alist-for-agenda nil "Alist of all groups tags from all current agenda files.") -(defvar org-tag-groups-alist nil) -(make-variable-buffer-local 'org-tag-groups-alist) +(defvar-local org-tag-groups-alist nil) (defvar org-agenda-contributing-files nil) -(defvar org-not-done-keywords nil) -(make-variable-buffer-local 'org-not-done-keywords) -(defvar org-done-keywords nil) -(make-variable-buffer-local 'org-done-keywords) -(defvar org-todo-heads nil) -(make-variable-buffer-local 'org-todo-heads) -(defvar org-todo-sets nil) -(make-variable-buffer-local 'org-todo-sets) -(defvar org-todo-log-states nil) -(make-variable-buffer-local 'org-todo-log-states) -(defvar org-todo-kwd-alist nil) -(make-variable-buffer-local 'org-todo-kwd-alist) -(defvar org-todo-key-alist nil) -(make-variable-buffer-local 'org-todo-key-alist) -(defvar org-todo-key-trigger nil) -(make-variable-buffer-local 'org-todo-key-trigger) +(defvar-local org-current-tag-alist nil + "Alist of all tag groups in current buffer. +This variable takes into consideration `org-tag-alist', +`org-tag-persistent-alist' and TAGS keywords in the buffer.") +(defvar-local org-not-done-keywords nil) +(defvar-local org-done-keywords nil) +(defvar-local org-todo-heads nil) +(defvar-local org-todo-sets nil) +(defvar-local org-todo-log-states nil) +(defvar-local org-todo-kwd-alist nil) +(defvar-local org-todo-key-alist nil) +(defvar-local org-todo-key-trigger nil) (defcustom org-todo-interpretation 'sequence "Controls how TODO keywords are interpreted. @@ -2407,7 +2701,8 @@ more information." (const type))) (defcustom org-use-fast-todo-selection t - "Non-nil means use the fast todo selection scheme with C-c C-t. + "\\<org-mode-map>\ +Non-nil means use the fast todo selection scheme with `\\[org-todo]'. This variable describes if and under what circumstances the cycling mechanism for TODO keywords will be replaced by a single-key, direct selection scheme. @@ -2415,8 +2710,9 @@ selection scheme. When nil, fast selection is never used. When the symbol `prefix', it will be used when `org-todo' is called -with a prefix argument, i.e. `C-u C-c C-t' in an Org-mode buffer, and -`C-u t' in an agenda buffer. +with a prefix argument, i.e. `\\[universal-argument] \\[org-todo]' \ +in an Org buffer, and +`\\[universal-argument] t' in an agenda buffer. When t, fast selection is used by default. In this case, the prefix argument forces cycling instead. @@ -2436,6 +2732,9 @@ ALL-HEADLINES means update todo statistics by including headlines with no TODO keyword as well, counting them as not done. A list of TODO keywords means the same, but skip keywords that are not in this list. +When set to a list of two lists, the first list contains keywords +to consider as TODO keywords, the second list contains keywords +to consider as DONE keywords. When this is set, todo statistics is updated in the parent of the current entry each time a todo state is changed." @@ -2445,6 +2744,9 @@ current entry each time a todo state is changed." (const :tag "Yes, including all entries" all-headlines) (repeat :tag "Yes, for TODOs in this list" (string :tag "TODO keyword")) + (list :tag "Yes, for TODOs and DONEs in these lists" + (repeat (string :tag "TODO keyword")) + (repeat (string :tag "DONE keyword"))) (other :tag "No TODO statistics" nil))) (defcustom org-hierarchical-todo-statistics t @@ -2529,7 +2831,7 @@ to change is while Emacs is running is through the customize interface." (defcustom org-treat-insert-todo-heading-as-state-change nil "Non-nil means inserting a TODO heading is treated as state change. -So when the command \\[org-insert-todo-heading] is used, state change +So when the command `\\[org-insert-todo-heading]' is used, state change logging will apply if appropriate. When nil, the new TODO item will be inserted directly, and no logging will take place." :group 'org-todo @@ -2667,20 +2969,23 @@ When nil, only the date will be recorded." (refile . "Refiled on %t") (clock-out . "")) "Headings for notes added to entries. -The value is an alist, with the car being a symbol indicating the note -context, and the cdr is the heading to be used. The heading may also be the -empty string. -%t in the heading will be replaced by a time stamp. -%T will be an active time stamp instead the default inactive one -%d will be replaced by a short-format time stamp. -%D will be replaced by an active short-format time stamp. -%s will be replaced by the new TODO state, in double quotes. -%S will be replaced by the old TODO state, in double quotes. -%u will be replaced by the user name. -%U will be replaced by the full user name. - -In fact, it is not a good idea to change the `state' entry, because -agenda log mode depends on the format of these entries." + +The value is an alist, with the car being a symbol indicating the +note context, and the cdr is the heading to be used. The heading +may also be the empty string. The following placeholders can be +used: + + %t a time stamp. + %T an active time stamp instead the default inactive one + %d a short-format time stamp. + %D an active short-format time stamp. + %s the new TODO state or time stamp (inactive), in double quotes. + %S the old TODO state or time stamp (inactive), in double quotes. + %u the user name. + %U full user name. + +In fact, it is not a good idea to change the `state' entry, +because Agenda Log mode depends on the format of these entries." :group 'org-todo :group 'org-progress :type '(list :greedy t @@ -2719,7 +3024,10 @@ If this variable is set, `org-log-state-notes-insert-after-drawers' will be ignored. You can set the property LOG_INTO_DRAWER to overrule this setting for -a subtree." +a subtree. + +Do not check directly this variable in a Lisp program. Call +function `org-log-into-drawer' instead." :group 'org-todo :group 'org-progress :type '(choice @@ -2727,18 +3035,20 @@ a subtree." (const :tag "LOGBOOK" t) (string :tag "Other"))) -(org-defvaralias 'org-log-state-notes-into-drawer 'org-log-into-drawer) +(defvaralias 'org-log-state-notes-into-drawer 'org-log-into-drawer) (defun org-log-into-drawer () - "Return the value of `org-log-into-drawer', but let properties overrule. -If the current entry has or inherits a LOG_INTO_DRAWER property, it will be -used instead of the default value." + "Name of the log drawer, as a string, or nil. +This is the value of `org-log-into-drawer'. However, if the +current entry has or inherits a LOG_INTO_DRAWER property, it will +be used instead of the default value." (let ((p (org-entry-get nil "LOG_INTO_DRAWER" 'inherit t))) - (cond - ((not p) org-log-into-drawer) - ((equal p "nil") nil) - ((equal p "t") "LOGBOOK") - (t p)))) + (cond ((equal p "nil") nil) + ((equal p "t") "LOGBOOK") + ((stringp p) p) + (p "LOGBOOK") + ((stringp org-log-into-drawer) org-log-into-drawer) + (org-log-into-drawer "LOGBOOK")))) (defcustom org-log-state-notes-insert-after-drawers nil "Non-nil means insert state change notes after any drawers in entry. @@ -2804,7 +3114,7 @@ property to one or more of these keywords." (defgroup org-priorities nil - "Priorities in Org-mode." + "Priorities in Org mode." :tag "Org Priorities" :group 'org-todo) @@ -2862,24 +3172,13 @@ as an argument and return the numeric priority." (function))) (defgroup org-time nil - "Options concerning time stamps and deadlines in Org-mode." + "Options concerning time stamps and deadlines in Org mode." :tag "Org Time" :group 'org) -(defcustom org-insert-labeled-timestamps-at-point nil - "Non-nil means SCHEDULED and DEADLINE timestamps are inserted at point. -When nil, these labeled time stamps are forces into the second line of an -entry, just after the headline. When scheduling from the global TODO list, -the time stamp will always be forced into the second line." - :group 'org-time - :type 'boolean) - -(defconst org-time-stamp-formats '("<%Y-%m-%d %a>" . "<%Y-%m-%d %a %H:%M>") - "Formats for `format-time-string' which are used for time stamps. -It is not recommended to change this constant.") - (defcustom org-time-stamp-rounding-minutes '(0 5) "Number of minutes to round time stamps to. +\\<org-mode-map>\ These are two values, the first applies when first creating a time stamp. The second applies when changing it with the commands `S-up' and `S-down'. When changing the time stamp, this means that it will change in steps @@ -2889,14 +3188,15 @@ When a setting is 0 or 1, insert the time unmodified. Useful rounding numbers should be factors of 60, so for example 5, 10, 15. When this is larger than 1, you can still force an exact time stamp by using -a double prefix argument to a time stamp command like `C-c .' or `C-c !', +a double prefix argument to a time stamp command like \ +`\\[org-time-stamp]' or `\\[org-time-stamp-inactive], and by using a prefix arg to `S-up/down' to specify the exact number of minutes to shift." :group 'org-time - :get #'(lambda (var) ; Make sure both elements are there - (if (integerp (default-value var)) - (list (default-value var) 5) - (default-value var))) + :get (lambda (var) ; Make sure both elements are there + (if (integerp (default-value var)) + (list (default-value var) 5) + (default-value var))) :type '(list (integer :tag "when inserting times") (integer :tag "when modifying times"))) @@ -2935,135 +3235,6 @@ commands, if custom time display is turned on at the time of export." (concat "[" (substring f 1 -1) "]") f))) -(defcustom org-time-clocksum-format - '(:days "%dd " :hours "%d" :require-hours t :minutes ":%02d" :require-minutes t) - "The format string used when creating CLOCKSUM lines. -This is also used when Org mode generates a time duration. - -The value can be a single format string containing two -%-sequences, which will be filled with the number of hours and -minutes in that order. - -Alternatively, the value can be a plist associating any of the -keys :years, :months, :weeks, :days, :hours or :minutes with -format strings. The time duration is formatted using only the -time components that are needed and concatenating the results. -If a time unit in absent, it falls back to the next smallest -unit. - -The keys :require-years, :require-months, :require-days, -:require-weeks, :require-hours, :require-minutes are also -meaningful. A non-nil value for these keys indicates that the -corresponding time component should always be included, even if -its value is 0. - - -For example, - - (:days \"%dd\" :hours \"%d\" :require-hours t :minutes \":%02d\" - :require-minutes t) - -means durations longer than a day will be expressed in days, -hours and minutes, and durations less than a day will always be -expressed in hours and minutes (even for durations less than an -hour). - -The value - - (:days \"%dd\" :minutes \"%dm\") - -means durations longer than a day will be expressed in days and -minutes, and durations less than a day will be expressed entirely -in minutes (even for durations longer than an hour)." - :group 'org-time - :group 'org-clock - :version "24.4" - :package-version '(Org . "8.0") - :type '(choice (string :tag "Format string") - (set :tag "Plist" - (group :inline t (const :tag "Years" :years) - (string :tag "Format string")) - (group :inline t - (const :tag "Always show years" :require-years) - (const t)) - (group :inline t (const :tag "Months" :months) - (string :tag "Format string")) - (group :inline t - (const :tag "Always show months" :require-months) - (const t)) - (group :inline t (const :tag "Weeks" :weeks) - (string :tag "Format string")) - (group :inline t - (const :tag "Always show weeks" :require-weeks) - (const t)) - (group :inline t (const :tag "Days" :days) - (string :tag "Format string")) - (group :inline t - (const :tag "Always show days" :require-days) - (const t)) - (group :inline t (const :tag "Hours" :hours) - (string :tag "Format string")) - (group :inline t - (const :tag "Always show hours" :require-hours) - (const t)) - (group :inline t (const :tag "Minutes" :minutes) - (string :tag "Format string")) - (group :inline t - (const :tag "Always show minutes" :require-minutes) - (const t))))) - -(defcustom org-time-clocksum-use-fractional nil - "When non-nil, \\[org-clock-display] uses fractional times. -See `org-time-clocksum-format' for more on time clock formats." - :group 'org-time - :group 'org-clock - :version "24.3" - :type 'boolean) - -(defcustom org-time-clocksum-use-effort-durations nil - "When non-nil, \\[org-clock-display] uses effort durations. -E.g. by default, one day is considered to be a 8 hours effort, -so a task that has been clocked for 16 hours will be displayed -as during 2 days in the clock display or in the clocktable. - -See `org-effort-durations' on how to set effort durations -and `org-time-clocksum-format' for more on time clock formats." - :group 'org-time - :group 'org-clock - :version "24.4" - :package-version '(Org . "8.0") - :type 'boolean) - -(defcustom org-time-clocksum-fractional-format "%.2f" - "The format string used when creating CLOCKSUM lines, -or when Org mode generates a time duration, if -`org-time-clocksum-use-fractional' is enabled. - -The value can be a single format string containing one -%-sequence, which will be filled with the number of hours as -a float. - -Alternatively, the value can be a plist associating any of the -keys :years, :months, :weeks, :days, :hours or :minutes with -a format string. The time duration is formatted using the -largest time unit which gives a non-zero integer part. If all -specified formats have zero integer part, the smallest time unit -is used." - :group 'org-time - :type '(choice (string :tag "Format string") - (set (group :inline t (const :tag "Years" :years) - (string :tag "Format string")) - (group :inline t (const :tag "Months" :months) - (string :tag "Format string")) - (group :inline t (const :tag "Weeks" :weeks) - (string :tag "Format string")) - (group :inline t (const :tag "Days" :days) - (string :tag "Format string")) - (group :inline t (const :tag "Hours" :hours) - (string :tag "Format string")) - (group :inline t (const :tag "Minutes" :minutes) - (string :tag "Format string"))))) - (defcustom org-deadline-warning-days 14 "Number of days before expiration during which a deadline becomes active. This variable governs the display in sparse trees and in the agenda. @@ -3097,8 +3268,8 @@ This affects the following situations: For example, if it is April and you enter \"feb 2\", this will be read as Feb 2, *next* year. \"May 5\", however, will be this year. 2. The user gives a day, but no month. - For example, if today is the 15th, and you enter \"3\", Org-mode will - read this as the third of *next* month. However, if you enter \"17\", + For example, if today is the 15th, and you enter \"3\", Org will read + this as the third of *next* month. However, if you enter \"17\", it will be considered as *this* month. If you set this variable to the symbol `time', then also the following @@ -3177,22 +3348,9 @@ minibuffer will also be active, and you can simply enter the date as well. When nil, only the minibuffer will be available." :group 'org-time :type 'boolean) -(org-defvaralias 'org-popup-calendar-for-date-prompt +(defvaralias 'org-popup-calendar-for-date-prompt 'org-read-date-popup-calendar) -(make-obsolete-variable - 'org-read-date-minibuffer-setup-hook - "Set `org-read-date-minibuffer-local-map' instead." "24.4") -(defcustom org-read-date-minibuffer-setup-hook nil - "Hook to be used to set up keys for the date/time interface. -Add key definitions to `minibuffer-local-map', which will be a -temporary copy. - -WARNING: This option is obsolete, you should use -`org-read-date-minibuffer-local-map' to set up keys." - :group 'org-time - :type 'hook) - (defcustom org-extend-today-until 0 "The hour when your day really ends. Must be an integer. This has influence for the following applications: @@ -3240,52 +3398,80 @@ moved to the new date." :type 'boolean) (defgroup org-tags nil - "Options concerning tags in Org-mode." + "Options concerning tags in Org mode." :tag "Org Tags" :group 'org) (defcustom org-tag-alist nil - "List of tags allowed in Org-mode files. -When this list is nil, Org-mode will base TAG input on what is already in the -buffer. -The value of this variable is an alist, the car of each entry must be a -keyword as a string, the cdr may be a character that is used to select -that tag through the fast-tag-selection interface. -See the manual for details." + "Default tags available in Org files. + +The value of this variable is an alist. Associations either: + + (TAG) + (TAG . SELECT) + (SPECIAL) + +where TAG is a tag as a string, SELECT is character, used to +select that tag through the fast tag selection interface, and +SPECIAL is one of the following keywords: `:startgroup', +`:startgrouptag', `:grouptags', `:engroup', `:endgrouptag' or +`:newline'. These keywords are used to define a hierarchy of +tags. See manual for details. + +When this variable is nil, Org mode bases tag input on what is +already in the buffer. The value can be overridden locally by +using a TAGS keyword, e.g., + + #+TAGS: tag1 tag2 + +See also `org-tag-persistent-alist' to sidestep this behavior." :group 'org-tags :type '(repeat (choice - (cons (string :tag "Tag name") - (character :tag "Access char")) - (list :tag "Start radio group" - (const :startgroup) - (option (string :tag "Group description"))) - (list :tag "Group tags delimiter" - (const :grouptags)) - (list :tag "End radio group" - (const :endgroup) - (option (string :tag "Group description"))) + (cons :tag "Tag with key" + (string :tag "Tag name") + (character :tag "Access char")) + (list :tag "Tag" (string :tag "Tag name")) + (const :tag "Start radio group" (:startgroup)) + (const :tag "Start tag group, non distinct" (:startgrouptag)) + (const :tag "Group tags delimiter" (:grouptags)) + (const :tag "End radio group" (:endgroup)) + (const :tag "End tag group, non distinct" (:endgrouptag)) (const :tag "New line" (:newline))))) (defcustom org-tag-persistent-alist nil - "List of tags that will always appear in all Org-mode files. -This is in addition to any in buffer settings or customizations -of `org-tag-alist'. -When this list is nil, Org-mode will base TAG input on `org-tag-alist'. -The value of this variable is an alist, the car of each entry must be a -keyword as a string, the cdr may be a character that is used to select -that tag through the fast-tag-selection interface. -See the manual for details. -To disable these tags on a per-file basis, insert anywhere in the file: - #+STARTUP: noptag" + "Tags always available in Org files. + +The value of this variable is an alist. Associations either: + + (TAG) + (TAG . SELECT) + (SPECIAL) + +where TAG is a tag as a string, SELECT is a character, used to +select that tag through the fast tag selection interface, and +SPECIAL is one of the following keywords: `:startgroup', +`:startgrouptag', `:grouptags', `:engroup', `:endgrouptag' or +`:newline'. These keywords are used to define a hierarchy of +tags. See manual for details. + +Unlike to `org-tag-alist', tags defined in this variable do not +depend on a local TAGS keyword. Instead, to disable these tags +on a per-file basis, insert anywhere in the file: + + #+STARTUP: noptag" :group 'org-tags :type '(repeat (choice - (cons (string :tag "Tag name") - (character :tag "Access char")) + (cons :tag "Tag with key" + (string :tag "Tag name") + (character :tag "Access char")) + (list :tag "Tag" (string :tag "Tag name")) (const :tag "Start radio group" (:startgroup)) + (const :tag "Start tag group, non distinct" (:startgrouptag)) (const :tag "Group tags delimiter" (:grouptags)) (const :tag "End radio group" (:endgroup)) + (const :tag "End tag group, non distinct" (:endgrouptag)) (const :tag "New line" (:newline))))) (defcustom org-complete-tags-always-offer-all-agenda-tags nil @@ -3296,9 +3482,7 @@ tags in that file can be created dynamically (there are none). (add-hook \\='org-capture-mode-hook (lambda () - (set (make-local-variable - \\='org-complete-tags-always-offer-all-agenda-tags) - t)))" + (setq-local org-complete-tags-always-offer-all-agenda-tags t)))" :group 'org-tags :version "24.1" :type 'boolean) @@ -3340,7 +3524,7 @@ displaying the tags menu is not even shown, until you press C-c again." "Non-nil means fast tags selection interface will also offer TODO states. This is an undocumented feature, you should not rely on it.") -(defcustom org-tags-column (if (featurep 'xemacs) -76 -77) +(defcustom org-tags-column -77 "The column to which tags should be indented in a headline. If this number is positive, it specifies the column. If it is negative, it means that the tags should be flushright to that column. For example, @@ -3437,7 +3621,7 @@ is better to limit inheritance to certain tags using the variables "Hook that is run after the tags in a line have changed.") (defgroup org-properties nil - "Options concerning properties in Org-mode." + "Options concerning properties in Org mode." :tag "Org Properties" :group 'org) @@ -3465,7 +3649,7 @@ and the clock summary: ((\"Remaining\" (lambda(value) (let ((clocksum (org-clock-sum-current-item)) - (effort (org-duration-string-to-minutes + (effort (org-duration-to-minutes (org-entry-get (point) \"Effort\")))) (org-minutes-to-clocksum-string (- effort clocksum))))))" :group 'org-properties @@ -3504,14 +3688,14 @@ in this variable)." (regexp :tag "Properties matched by regexp"))) (defun org-property-inherit-p (property) - "Check if PROPERTY is one that should be inherited." + "Return a non-nil value if PROPERTY should be inherited." (cond ((eq org-use-property-inheritance t) t) ((not org-use-property-inheritance) nil) ((stringp org-use-property-inheritance) (string-match org-use-property-inheritance property)) ((listp org-use-property-inheritance) - (member property org-use-property-inheritance)) + (member-ignore-case property org-use-property-inheritance)) (t (error "Invalid setting of `org-use-property-inheritance'")))) (defcustom org-columns-default-format "%25ITEM %TODO %3PRIORITY %TAGS" @@ -3532,26 +3716,6 @@ ellipses string, only part of the ellipses string will be shown." :group 'org-properties :type 'string) -(defcustom org-columns-modify-value-for-display-function nil - "Function that modifies values for display in column view. -For example, it can be used to cut out a certain part from a time stamp. -The function must take 2 arguments: - -column-title The title of the column (*not* the property name) -value The value that should be modified. - -The function should return the value that should be displayed, -or nil if the normal value should be used." - :group 'org-properties - :type '(choice (const nil) (function))) - -(defcustom org-effort-property "Effort" - "The property that is being used to keep track of effort estimates. -Effort estimates given in this property need to have the format H:MM." - :group 'org-properties - :group 'org-progress - :type '(string :tag "Property")) - (defconst org-global-properties-fixed '(("VISIBILITY_ALL" . "folded children content all") ("CLOCK_MODELINE_TOTAL_ALL" . "current today repeat all auto")) @@ -3582,18 +3746,17 @@ You can set buffer-local values for the same purpose in the variable (cons (string :tag "Property") (string :tag "Value")))) -(defvar org-file-properties nil +(defvar-local org-file-properties nil "List of property/value pairs that can be inherited by any entry. Valid for the current buffer. This variable is populated from #+PROPERTY lines.") -(make-variable-buffer-local 'org-file-properties) (defgroup org-agenda nil - "Options concerning agenda views in Org-mode." + "Options concerning agenda views in Org mode." :tag "Org Agenda" :group 'org) -(defvar org-category nil +(defvar-local org-category nil "Variable used by org files to set a category for agenda display. Such files should use a file variable to set it, for example @@ -3605,22 +3768,22 @@ or contain a special line If the file does not specify a category, then file's base name is used instead.") -(make-variable-buffer-local 'org-category) -(put 'org-category 'safe-local-variable #'(lambda (x) (or (symbolp x) (stringp x)))) +(put 'org-category 'safe-local-variable (lambda (x) (or (symbolp x) (stringp x)))) (defcustom org-agenda-files nil "The files to be used for agenda display. -Entries may be added to this list with \\[org-agenda-file-to-front] and removed with -\\[org-remove-file]. You can also use customize to edit the list. -If an entry is a directory, all files in that directory that are matched by -`org-agenda-file-regexp' will be part of the file list. +If an entry is a directory, all files in that directory that are matched +by `org-agenda-file-regexp' will be part of the file list. If the value of the variable is not a list but a single file name, then -the list of agenda files is actually stored and maintained in that file, one -agenda file per line. In this file paths can be given relative to +the list of agenda files is actually stored and maintained in that file, +one agenda file per line. In this file paths can be given relative to `org-directory'. Tilde expansion and environment variable substitution -are also made." +are also made. + +Entries may be added to this list with `\\[org-agenda-file-to-front]' +and removed with `\\[org-remove-file]'." :group 'org-agenda :type '(choice (repeat :tag "List of files and directories" file) @@ -3637,7 +3800,8 @@ regular expression will be included." (defcustom org-agenda-text-search-extra-files nil "List of extra files to be searched by text search commands. These files will be searched in addition to the agenda files by the -commands `org-search-view' (`C-c a s') and `org-occur-in-agenda-files'. +commands `org-search-view' (`\\[org-agenda] s') \ +and `org-occur-in-agenda-files'. Note that these files will only be searched for text search commands, not for the other agenda views like todo lists, tag searches or the weekly agenda. This variable is intended to list notes and possibly archive files @@ -3650,7 +3814,7 @@ scope." (const :tag "Agenda Archives" agenda-archives) (repeat :inline t (file)))) -(org-defvaralias 'org-agenda-multi-occur-extra-files +(defvaralias 'org-agenda-multi-occur-extra-files 'org-agenda-text-search-extra-files) (defcustom org-agenda-skip-unavailable-files nil @@ -3670,7 +3834,7 @@ forth between agenda and calendar." (defcustom org-calendar-insert-diary-entry-key [?i] "The key to be installed in `calendar-mode-map' for adding diary entries. This option is irrelevant until `org-agenda-diary-file' has been configured -to point to an Org-mode file. When that is the case, the command +to point to an Org file. When that is the case, the command `org-agenda-diary-entry' will be bound to the key given here, by default `i'. In the calendar, `i' normally adds entries to `diary-file'. So if you want to continue doing this, you need to change this to a different @@ -3700,7 +3864,7 @@ points to a file, `org-agenda-diary-entry' will be used instead." 'org-agenda-diary-entry)))))) (defgroup org-latex nil - "Options for embedding LaTeX code into Org-mode." + "Options for embedding LaTeX code into Org mode." :tag "Org LaTeX" :group 'org) @@ -3755,39 +3919,131 @@ Replace format-specifiers in the command as noted below and use `shell-command' to convert LaTeX to MathML. %j: Executable file in fully expanded form as specified by `org-latex-to-mathml-jar-file'. -%I: Input LaTeX file in fully expanded form -%o: Output MathML file +%I: Input LaTeX file in fully expanded form. +%i: The latex fragment to be converted. +%o: Output MathML file. + This command is used by `org-create-math-formula'. -When using MathToWeb as the converter, set this to -\"java -jar %j -unicode -force -df %o %I\"." +When using MathToWeb as the converter, set this option to +\"java -jar %j -unicode -force -df %o %I\". + +When using LaTeXML set this option to +\"latexmlmath \"%i\" --presentationmathml=%o\"." :group 'org-latex :version "24.1" :type '(choice (const :tag "None" nil) (string :tag "\nShell command"))) -(defcustom org-latex-create-formula-image-program 'dvipng - "Program to convert LaTeX fragments with. - -dvipng Process the LaTeX fragments to dvi file, then convert - dvi files to png files using dvipng. - This will also include processing of non-math environments. -imagemagick Convert the LaTeX fragments to pdf files and use imagemagick - to convert pdf files to png files" +(defcustom org-preview-latex-default-process 'dvipng + "The default process to convert LaTeX fragments to image files. +All available processes and theirs documents can be found in +`org-preview-latex-process-alist', which see." :group 'org-latex - :version "24.1" - :type '(choice - (const :tag "dvipng" dvipng) - (const :tag "imagemagick" imagemagick))) + :version "26.1" + :package-version '(Org . "9.0") + :type 'symbol) + +(defcustom org-preview-latex-process-alist + '((dvipng + :programs ("latex" "dvipng") + :description "dvi > png" + :message "you need to install the programs: latex and dvipng." + :image-input-type "dvi" + :image-output-type "png" + :image-size-adjust (1.0 . 1.0) + :latex-compiler ("latex -interaction nonstopmode -output-directory %o %f") + :image-converter ("dvipng -fg %F -bg %B -D %D -T tight -o %O %f")) + (dvisvgm + :programs ("latex" "dvisvgm") + :description "dvi > svg" + :message "you need to install the programs: latex and dvisvgm." + :use-xcolor t + :image-input-type "dvi" + :image-output-type "svg" + :image-size-adjust (1.7 . 1.5) + :latex-compiler ("latex -interaction nonstopmode -output-directory %o %f") + :image-converter ("dvisvgm %f -n -b min -c %S -o %O")) + (imagemagick + :programs ("latex" "convert") + :description "pdf > png" + :message "you need to install the programs: latex and imagemagick." + :use-xcolor t + :image-input-type "pdf" + :image-output-type "png" + :image-size-adjust (1.0 . 1.0) + :latex-compiler ("pdflatex -interaction nonstopmode -output-directory %o %f") + :image-converter + ("convert -density %D -trim -antialias %f -quality 100 %O"))) + "Definitions of external processes for LaTeX previewing. +Org mode can use some external commands to generate TeX snippet's images for +previewing or inserting into HTML files, e.g., \"dvipng\". This variable tells +`org-create-formula-image' how to call them. + +The value is an alist with the pattern (NAME . PROPERTIES). NAME is a symbol. +PROPERTIES accepts the following attributes: + + :programs list of strings, required programs. + :description string, describe the process. + :message string, message it when required programs cannot be found. + :image-input-type string, input file type of image converter (e.g., \"dvi\"). + :image-output-type string, output file type of image converter (e.g., \"png\"). + :use-xcolor boolean, when non-nil, LaTeX \"xcolor\" macro is used to + deal with background and foreground color of image. + Otherwise, dvipng style background and foreground color + format are generated. You may then refer to them in + command options with \"%F\" and \"%B\". + :image-size-adjust cons of numbers, the car element is used to adjust LaTeX + image size showed in buffer and the cdr element is for + HTML file. This option is only useful for process + developers, users should use variable + `org-format-latex-options' instead. + :post-clean list of strings, files matched are to be cleaned up once + the image is generated. When nil, the files with \".dvi\", + \".xdv\", \".pdf\", \".tex\", \".aux\", \".log\", \".svg\", + \".png\", \".jpg\", \".jpeg\" or \".out\" extension will + be cleaned up. + :latex-header list of strings, the LaTeX header of the snippet file. + When nil, the fallback value is used instead, which is + controlled by `org-format-latex-header', + `org-latex-default-packages-alist' and + `org-latex-packages-alist', which see. + :latex-compiler list of LaTeX commands, as strings. Each of them is given + to the shell. Place-holders \"%t\", \"%b\" and \"%o\" are + replaced with values defined below. + :image-converter list of image converter commands strings. Each of them is + given to the shell and supports any of the following + place-holders defined below. + +Place-holders used by `:image-converter' and `:latex-compiler': + + %f input file name + %b base name of input file + %o base directory of input file + %O absolute output file name + +Place-holders only used by `:image-converter': + + %F foreground of image + %B background of image + %D dpi, which is used to adjust image size by some processing commands. + %S the image size scale ratio, which is used to adjust image size by some + processing commands." + :group 'org-latex + :version "26.1" + :package-version '(Org . "9.0") + :type '(alist :tag "LaTeX to image backends" + :value-type (plist))) -(defcustom org-latex-preview-ltxpng-directory "ltxpng/" +(defcustom org-preview-latex-image-directory "ltximg/" "Path to store latex preview images. A relative path here creates many directories relative to the processed org files paths. An absolute path puts all preview images at the same place." :group 'org-latex - :version "24.3" + :version "26.1" + :package-version '(Org . "9.0") :type 'string) (defun org-format-latex-mathml-available-p () @@ -3805,8 +4061,8 @@ images at the same place." (defcustom org-format-latex-header "\\documentclass{article} \\usepackage[usenames]{color} -[PACKAGES] -[DEFAULT-PACKAGES] +\[PACKAGES] +\[DEFAULT-PACKAGES] \\pagestyle{empty} % do not remove % The settings below are copied from fullpage.sty \\setlength{\\textwidth}{\\paperwidth} @@ -3847,22 +4103,19 @@ header, or they will be appended." (default-value var))) (defcustom org-latex-default-packages-alist - '(("AUTO" "inputenc" t) - ("T1" "fontenc" t) - ("" "fixltx2e" nil) + '(("AUTO" "inputenc" t ("pdflatex")) + ("T1" "fontenc" t ("pdflatex")) ("" "graphicx" t) + ("" "grffile" t) ("" "longtable" nil) - ("" "float" nil) ("" "wrapfig" nil) ("" "rotating" nil) ("normalem" "ulem" t) ("" "amsmath" t) ("" "textcomp" t) - ("" "marvosym" t) - ("" "wasysym" t) ("" "amssymb" t) - ("" "hyperref" nil) - "\\tolerance=1000") + ("" "capt-of" nil) + ("" "hyperref" nil)) "Alist of default packages to be inserted in the header. Change this only if one of the packages here causes an @@ -3872,16 +4125,17 @@ The packages in this list are needed by one part or another of Org mode to function properly: - inputenc, fontenc: for basic font and character selection -- fixltx2e: Important patches of LaTeX itself - graphicx: for including images +- grffile: allow periods and spaces in graphics file names - longtable: For multipage tables -- float, wrapfig: for figure placement +- wrapfig: for figure placement - rotating: for sideways figures and tables - ulem: for underline and strike-through - amsmath: for subscript and superscript and math environments -- textcomp, marvosymb, wasysym, amssymb: for various symbols used +- textcomp, amssymb: for various symbols used for interpreting the entities in `org-entities'. You can skip some of these packages if you don't use any of their symbols. +- capt-of: for captions outside of floats - hyperref: for cross references Therefore you should not modify this variable unless you know @@ -3890,20 +4144,24 @@ you might be loading some other package that conflicts with one of the default packages. Each element is either a cell or a string. -A cell is of the format: +A cell is of the format - ( \"options\" \"package\" SNIPPET-FLAG). + (\"options\" \"package\" SNIPPET-FLAG COMPILERS) If SNIPPET-FLAG is non-nil, the package also needs to be included when compiling LaTeX snippets into images for inclusion into -non-LaTeX output. +non-LaTeX output. COMPILERS is a list of compilers that should +include the package, see `org-latex-compiler'. If the document +compiler is not in the list, and the list is non-nil, the package +will not be inserted in the final document. A string will be inserted as-is in the header of the document." :group 'org-latex :group 'org-export-latex :set 'org-set-packages-alist :get 'org-get-packages-alist - :version "24.1" + :version "26.1" + :package-version '(Org . "8.3") :type '(repeat (choice (list :tag "options/package pair" @@ -3947,7 +4205,7 @@ Make sure that you only list packages here which: (string :tag "A line of LaTeX")))) (defgroup org-appearance nil - "Settings for Org-mode appearance." + "Settings for Org mode appearance." :tag "Org Appearance" :group 'org) @@ -4038,6 +4296,11 @@ following symbols: :group 'org-appearance :type 'boolean) +(defcustom org-hide-macro-markers nil + "Non-nil mean font-lock should hide the brackets marking macro calls." + :group 'org-appearance + :type 'boolean) + (defcustom org-pretty-entities nil "Non-nil means show entities as UTF8 characters. When nil, the \\name form remains in the buffer." @@ -4061,8 +4324,10 @@ After a match, the match groups contain these elements: 3 The leading marker like * or /, indicating the type of highlighting 4 The text between the emphasis markers, not including the markers 5 The character after the match, empty at the end of a line") + (defvar org-verbatim-re nil "Regular expression for matching verbatim text.") + (defvar org-emphasis-regexp-components) ; defined just below (defvar org-emphasis-alist) ; defined just below (defun org-set-emph-re (var val) @@ -4071,60 +4336,23 @@ After a match, the match groups contain these elements: (when (and (boundp 'org-emphasis-alist) (boundp 'org-emphasis-regexp-components) org-emphasis-alist org-emphasis-regexp-components) - (let* ((e org-emphasis-regexp-components) - (pre (car e)) - (post (nth 1 e)) - (border (nth 2 e)) - (body (nth 3 e)) - (nl (nth 4 e)) - (body1 (concat body "*?")) - (markers (mapconcat 'car org-emphasis-alist "")) - (vmarkers (mapconcat - (lambda (x) (if (eq (nth 2 x) 'verbatim) (car x) "")) - org-emphasis-alist ""))) - ;; make sure special characters appear at the right position in the class - (if (string-match "\\^" markers) - (setq markers (concat (replace-match "" t t markers) "^"))) - (if (string-match "-" markers) - (setq markers (concat (replace-match "" t t markers) "-"))) - (if (string-match "\\^" vmarkers) - (setq vmarkers (concat (replace-match "" t t vmarkers) "^"))) - (if (string-match "-" vmarkers) - (setq vmarkers (concat (replace-match "" t t vmarkers) "-"))) - (if (> nl 0) - (setq body1 (concat body1 "\\(?:\n" body "*?\\)\\{0," - (int-to-string nl) "\\}"))) - ;; Make the regexp - (setq org-emph-re - (concat "\\([" pre "]\\|^\\)" - "\\(" - "\\([" markers "]\\)" - "\\(" - "[^" border "]\\|" - "[^" border "]" - body1 - "[^" border "]" - "\\)" - "\\3\\)" - "\\([" post "]\\|$\\)")) - (setq org-verbatim-re - (concat "\\([" pre "]\\|^\\)" - "\\(" - "\\([" vmarkers "]\\)" - "\\(" - "[^" border "]\\|" - "[^" border "]" - body1 - "[^" border "]" - "\\)" - "\\3\\)" - "\\([" post "]\\|$\\)"))))) + (pcase-let* + ((`(,pre ,post ,border ,body ,nl) org-emphasis-regexp-components) + (body (if (<= nl 0) body + (format "%s*?\\(?:\n%s*?\\)\\{0,%d\\}" body body nl))) + (template + (format (concat "\\([%s]\\|^\\)" ;before markers + "\\(\\([%%s]\\)\\([^%s]\\|[^%s]%s[^%s]\\)\\3\\)" + "\\([%s]\\|$\\)") ;after markers + pre border border body border post))) + (setq org-emph-re (format template "*/_+")) + (setq org-verbatim-re (format template "=~"))))) ;; 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 (defvar org-emphasis-regexp-components - '(" \t('\"{" "- \t.,:!?;'\")}\\" " \t\r\n,\"'" "." 1) + '("- \t('\"{" "- \t.,:!?;'\")}\\[" " \t\r\n" "." 1) "Components used to build the regular expression for emphasis. This is a list with five entries. Terminology: In an emphasis string like \" *strong word* \", we call the initial space PREMATCH, the final @@ -4142,17 +4370,17 @@ newline The maximum number of newlines allowed in an emphasis exp. You need to reload Org or to restart Emacs after customizing this.") (defcustom org-emphasis-alist - `(("*" bold) + '(("*" bold) ("/" italic) ("_" underline) ("=" org-verbatim verbatim) ("~" org-code verbatim) - ("+" ,(if (featurep 'xemacs) 'org-table '(:strike-through t)))) + ("+" (:strike-through t))) "Alist of characters and faces to emphasize text. Text starting and ending with a special character will be emphasized, for example *bold*, _underlined_ and /italic/. This variable sets the marker characters and the face to be used by font-lock for highlighting -in Org-mode Emacs buffers. +in Org buffers. You need to reload Org or to restart Emacs after customizing this." :group 'org-appearance @@ -4167,122 +4395,68 @@ You need to reload Org or to restart Emacs after customizing this." (plist :tag "Face property list")) (option (const verbatim))))) -(defvar org-protecting-blocks - '("src" "example" "latex" "ascii" "html" "ditaa" "dot" "r" "R") +(defvar org-protecting-blocks '("src" "example" "export") "Blocks that contain text that is quoted, i.e. not processed as Org syntax. This is needed for font-lock setup.") -;;; Miscellaneous options - -(defgroup org-completion nil - "Completion in Org-mode." - :tag "Org Completion" - :group 'org) - -(defcustom org-completion-use-ido nil - "Non-nil means use ido completion wherever possible. -Note that `ido-mode' must be active for this variable to be relevant. -If you decide to turn this variable on, you might well want to turn off -`org-outline-path-complete-in-steps'. -See also `org-completion-use-iswitchb'." - :group 'org-completion - :type 'boolean) - -(defcustom org-completion-use-iswitchb nil - "Non-nil means use iswitchb completion wherever possible. -Note that `iswitchb-mode' must be active for this variable to be relevant. -If you decide to turn this variable on, you might well want to turn off -`org-outline-path-complete-in-steps'. -Note that this variable has only an effect if `org-completion-use-ido' is nil." - :group 'org-completion - :type 'boolean) - -(defcustom org-completion-fallback-command 'hippie-expand - "The expansion command called by \\[pcomplete] in normal context. -Normal means, no org-mode-specific context." - :group 'org-completion - :type 'function) - ;;; Functions and variables from their packages ;; Declared here to avoid compiler warnings - -;; XEmacs only -(defvar outline-mode-menu-heading) -(defvar outline-mode-menu-show) -(defvar outline-mode-menu-hide) -(defvar zmacs-regions) ; XEmacs regions - -;; Emacs only (defvar mark-active) ;; Various packages -(declare-function calendar-iso-to-absolute "cal-iso" (date)) -(declare-function calendar-forward-day "cal-move" (arg)) -(declare-function calendar-goto-date "cal-move" (date)) -(declare-function calendar-goto-today "cal-move" ()) -(declare-function calendar-iso-from-absolute "cal-iso" (date)) -(defvar calc-embedded-close-formula) -(defvar calc-embedded-open-formula) -(declare-function cdlatex-tab "ext:cdlatex" ()) +(declare-function calc-eval "calc" (str &optional separator &rest args)) +(declare-function calendar-forward-day "cal-move" (arg)) +(declare-function calendar-goto-date "cal-move" (date)) +(declare-function calendar-goto-today "cal-move" ()) +(declare-function calendar-iso-from-absolute "cal-iso" (date)) +(declare-function calendar-iso-to-absolute "cal-iso" (date)) (declare-function cdlatex-compute-tables "ext:cdlatex" ()) -(declare-function dired-get-filename "dired" (&optional localp no-error-if-not-filep)) -(defvar font-lock-unfontify-region-function) -(declare-function iswitchb-read-buffer "iswitchb" - (prompt &optional - default require-match _predicate start matches-set)) -(defvar iswitchb-temp-buflist) -(declare-function org-gnus-follow-link "org-gnus" (&optional group article)) -(defvar org-agenda-tags-todo-honor-ignore-options) -(declare-function org-agenda-skip "org-agenda" ()) -(declare-function - org-agenda-format-item "org-agenda" - (extra txt &optional level category tags dotime remove-re habitp)) -(declare-function org-agenda-new-marker "org-agenda" (&optional pos)) -(declare-function org-agenda-change-all-lines "org-agenda" +(declare-function cdlatex-tab "ext:cdlatex" ()) +(declare-function dired-get-filename + "dired" + (&optional localp no-error-if-not-filep)) +(declare-function iswitchb-read-buffer + "iswitchb" + (prompt &optional + default require-match _predicate start matches-set)) +(declare-function org-agenda-change-all-lines + "org-agenda" (newhead hdmarker &optional fixface just-this)) -(declare-function org-agenda-set-restriction-lock "org-agenda" (&optional type)) +(declare-function org-agenda-check-for-timestamp-as-reason-to-ignore-todo-item + "org-agenda" + (&optional end)) +(declare-function org-agenda-copy-local-variable "org-agenda" (var)) +(declare-function org-agenda-format-item + "org-agenda" + (extra txt &optional level category tags dotime + remove-re habitp)) (declare-function org-agenda-maybe-redo "org-agenda" ()) -(declare-function org-agenda-save-markers-for-cut-and-paste "org-agenda" +(declare-function org-agenda-new-marker "org-agenda" (&optional pos)) +(declare-function org-agenda-save-markers-for-cut-and-paste + "org-agenda" (beg end)) -(declare-function org-agenda-copy-local-variable "org-agenda" (var)) -(declare-function org-agenda-check-for-timestamp-as-reason-to-ignore-todo-item - "org-agenda" (&optional end)) -(declare-function org-inlinetask-remove-END-maybe "org-inlinetask" ()) -(declare-function org-inlinetask-in-task-p "org-inlinetask" ()) +(declare-function org-agenda-set-restriction-lock "org-agenda" (&optional type)) +(declare-function org-agenda-skip "org-agenda" ()) +(declare-function org-attach-reveal "org-attach" (&optional if-exists)) +(declare-function org-gnus-follow-link "org-gnus" (&optional group article)) +(declare-function org-indent-mode "org-indent" (&optional arg)) (declare-function org-inlinetask-goto-beginning "org-inlinetask" ()) (declare-function org-inlinetask-goto-end "org-inlinetask" ()) -(declare-function org-indent-mode "org-indent" (&optional arg)) -(declare-function parse-time-string "parse-time" (string)) -(declare-function org-attach-reveal "org-attach" (&optional if-exists)) +(declare-function org-inlinetask-in-task-p "org-inlinetask" ()) +(declare-function org-inlinetask-remove-END-maybe "org-inlinetask" ()) (declare-function orgtbl-send-table "org-table" (&optional maybe)) -(defvar remember-data-file) -(defvar texmathp-why) +(declare-function parse-time-string "parse-time" (string)) (declare-function speedbar-line-directory "speedbar" (&optional depth)) -(declare-function table--at-cell-p "table" (position &optional object at-column)) - -(defvar org-latex-regexps) - -;;; Autoload and prepare some org modules - -;; Some table stuff that needs to be defined here, because it is used -;; by the functions setting up org-mode or checking for table context. -(defconst org-table-any-line-regexp "^[ \t]*\\(|\\|\\+-[-+]\\)" - "Detect an org-type or table-type table.") -(defconst org-table-line-regexp "^[ \t]*|" - "Detect an org-type table line.") -(defconst org-table-dataline-regexp "^[ \t]*|[^-]" - "Detect an org-type table line.") -(defconst org-table-hline-regexp "^[ \t]*|-" - "Detect an org-type table hline.") -(defconst org-table1-hline-regexp "^[ \t]*\\+-[-+]" - "Detect a table-type table hline.") -(defconst org-table-any-border-regexp "^[ \t]*[^|+ \t]" - "Detect the first line outside a table when searching from within it. -This works for both table types.") - -(defconst org-TBLFM-regexp "^[ \t]*#\\+TBLFM: " - "Detect a #+TBLFM line.") +(defvar align-mode-rules-list) +(defvar calc-embedded-close-formula) +(defvar calc-embedded-open-formula) +(defvar calc-embedded-open-mode) +(defvar font-lock-unfontify-region-function) +(defvar iswitchb-temp-buflist) +(defvar org-agenda-tags-todo-honor-ignore-options) +(defvar remember-data-file) +(defvar texmathp-why) ;;;###autoload (defun turn-on-orgtbl () @@ -4291,75 +4465,42 @@ This works for both table types.") (orgtbl-mode 1)) (defun org-at-table-p (&optional table-type) - "Return t if the cursor is inside an org-type table. + "Non-nil if the cursor is inside an Org table. If TABLE-TYPE is non-nil, also check for table.el-type tables." - (if org-enable-table-editor - (save-excursion - (beginning-of-line 1) - (looking-at (if table-type org-table-any-line-regexp - org-table-line-regexp))) - nil)) -(defsubst org-table-p () (org-at-table-p)) + (and (org-match-line (if table-type "[ \t]*[|+]" "[ \t]*|")) + (or (not (derived-mode-p 'org-mode)) + (let ((e (org-element-lineage (org-element-at-point) '(table) t))) + (and e (or table-type + (eq 'org (org-element-property :type e)))))))) (defun org-at-table.el-p () - "Return t if and only if we are at a table.el table." - (and (org-at-table-p 'any) - (save-excursion - (goto-char (org-table-begin 'any)) - (looking-at org-table1-hline-regexp)))) - -(defun org-table-recognize-table.el () - "If there is a table.el table nearby, recognize it and move into it." - (if org-table-tab-recognizes-table.el - (if (org-at-table.el-p) - (progn - (beginning-of-line 1) - (if (looking-at org-table-dataline-regexp) - nil - (if (looking-at org-table1-hline-regexp) - (progn - (beginning-of-line 2) - (if (looking-at org-table-any-border-regexp) - (beginning-of-line -1))))) - (if (re-search-forward "|" (org-table-end t) t) - (progn - (require 'table) - (if (table--at-cell-p (point)) - t - (message "recognizing table.el table...") - (table-recognize-table) - (message "recognizing table.el table...done"))) - (error "This should not happen")) - t) - nil) - nil)) + "Non-nil when point is at a table.el table." + (and (org-match-line "[ \t]*[|+]") + (let ((element (org-element-at-point))) + (and (eq (org-element-type element) 'table) + (eq (org-element-property :type element) 'table.el))))) (defun org-at-table-hline-p () - "Return t if the cursor is inside a hline in a table." - (if org-enable-table-editor - (save-excursion - (beginning-of-line 1) - (looking-at org-table-hline-regexp)) - nil)) + "Non-nil when point is inside a hline in a table. +Assume point is already in a table." + (org-match-line org-table-hline-regexp)) (defun org-table-map-tables (function &optional quietly) "Apply FUNCTION to the start of all tables in the buffer." - (save-excursion - (save-restriction - (widen) - (goto-char (point-min)) - (while (re-search-forward org-table-any-line-regexp nil t) - (unless quietly - (message "Mapping tables: %d%%" - (floor (* 100.0 (point)) (buffer-size)))) - (beginning-of-line 1) - (when (and (looking-at org-table-line-regexp) - ;; Exclude tables in src/example/verbatim/clocktable blocks - (not (org-in-block-p '("src" "example" "verbatim" "clocktable")))) - (save-excursion (funcall function)) - (or (looking-at org-table-line-regexp) - (forward-char 1))) - (re-search-forward org-table-any-border-regexp nil 1)))) + (org-with-wide-buffer + (goto-char (point-min)) + (while (re-search-forward org-table-any-line-regexp nil t) + (unless quietly + (message "Mapping tables: %d%%" + (floor (* 100.0 (point)) (buffer-size)))) + (beginning-of-line 1) + (when (and (looking-at org-table-line-regexp) + ;; Exclude tables in src/example/verbatim/clocktable blocks + (not (org-in-block-p '("src" "example" "verbatim" "clocktable")))) + (save-excursion (funcall function)) + (or (looking-at org-table-line-regexp) + (forward-char 1))) + (re-search-forward org-table-any-border-regexp nil 1))) (unless quietly (message "Mapping tables: done"))) (declare-function org-clock-save-markers-for-cut-and-paste "org-clock" (beg end)) @@ -4368,12 +4509,12 @@ If TABLE-TYPE is non-nil, also check for table.el-type tables." (&optional also-non-dangling-p prompt last-valid)) (defun org-at-TBLFM-p (&optional pos) - "Return t when point (or POS) is in #+TBLFM line." + "Non-nil when point (or POS) is in #+TBLFM line." (save-excursion - (let ((pos pos))) (goto-char (or pos (point))) - (beginning-of-line 1) - (looking-at org-TBLFM-regexp))) + (beginning-of-line) + (and (let ((case-fold-search t)) (looking-at org-TBLFM-regexp)) + (eq (org-element-type (org-element-at-point)) 'table)))) (defvar org-clock-start-time) (defvar org-clock-marker (make-marker) @@ -4410,7 +4551,7 @@ If yes, offer to stop it and to save the buffer with the changes." (add-hook 'kill-emacs-hook 'org-clock-save)) (defgroup org-archive nil - "Options concerning archiving in Org-mode." + "Options concerning archiving in Org mode." :tag "Org Archive" :group 'org-structure) @@ -4425,7 +4566,7 @@ When the filename is omitted, archiving happens in the same file. %s in the filename will be replaced by the current file name (without the directory part). Archiving to a different file is useful to keep archived entries from contributing to the -Org-mode Agenda. +Org Agenda. The archived entries will be filed as subtrees of the specified headline. When the headline is omitted, the subtrees are simply @@ -4473,16 +4614,6 @@ the hierarchy, it will be used." :group 'org-archive :type 'string) -(defcustom org-archive-tag "ARCHIVE" - "The tag that marks a subtree as archived. -An archived subtree does not open during visibility cycling, and does -not contribute to the agenda listings. -After changing this, font-lock must be restarted in the relevant buffers to -get the proper fontification." - :group 'org-archive - :group 'org-keywords - :type 'string) - (defcustom org-agenda-skip-archived-trees t "Non-nil means the agenda will skip any items located in archived trees. An archived tree is a tree marked with the tag ARCHIVE. The use of this @@ -4515,28 +4646,31 @@ collapsed state." :group 'org-sparse-trees :type 'boolean) -(defcustom org-sparse-tree-default-date-type 'scheduled-or-deadline +(defcustom org-sparse-tree-default-date-type nil "The default date type when building a sparse tree. When this is nil, a date is a scheduled or a deadline timestamp. Otherwise, these types are allowed: all: all timestamps active: only active timestamps (<...>) - inactive: only inactive timestamps (<...) + inactive: only inactive timestamps ([...]) scheduled: only scheduled timestamps deadline: only deadline timestamps" - :type '(choice (const :tag "Scheduled or deadline" scheduled-or-deadline) + :type '(choice (const :tag "Scheduled or deadline" nil) (const :tag "All timestamps" all) (const :tag "Only active timestamps" active) (const :tag "Only inactive timestamps" inactive) (const :tag "Only scheduled timestamps" scheduled) (const :tag "Only deadline timestamps" deadline) (const :tag "Only closed timestamps" closed)) - :version "24.3" + :version "26.1" + :package-version '(Org . "8.3") :group 'org-sparse-trees) (defun org-cycle-hide-archived-subtrees (state) - "Re-hide all archived subtrees after a visibility state change." + "Re-hide all archived subtrees after a visibility state change. +STATE should be one of the symbols listed in the docstring of +`org-cycle-hook'." (when (and (not org-cycle-open-archived-trees) (not (memq state '(overview folded)))) (save-excursion @@ -4545,9 +4679,10 @@ Otherwise, these types are allowed: (end (if globalp (point-max) (org-end-of-subtree t)))) (org-hide-archived-subtrees beg end) (goto-char beg) - (if (looking-at (concat ".*:" org-archive-tag ":")) - (message "%s" (substitute-command-keys - "Subtree is archived and stays closed. Use \\[org-force-cycle-archived] to cycle it anyway."))))))) + (when (looking-at-p (concat ".*:" org-archive-tag ":")) + (message "%s" (substitute-command-keys + "Subtree is archived and stays closed. Use \ +`\\[org-force-cycle-archived]' to cycle it anyway."))))))) (defun org-force-cycle-archived () "Cycle subtree even if it is archived." @@ -4558,13 +4693,16 @@ Otherwise, these types are allowed: (defun org-hide-archived-subtrees (beg end) "Re-hide all archived subtrees after a visibility state change." - (save-excursion - (let* ((re (concat ":" org-archive-tag ":"))) - (goto-char beg) - (while (re-search-forward re end t) - (when (org-at-heading-p) - (org-flag-subtree t) - (org-end-of-subtree t)))))) + (org-with-wide-buffer + (let ((case-fold-search nil) + (re (concat org-outline-regexp-bol ".*:" org-archive-tag ":"))) + (goto-char beg) + ;; Include headline point is currently on. + (beginning-of-line) + (while (and (< (point) end) (re-search-forward re end t)) + (when (member org-archive-tag (org-get-tags)) + (org-flag-subtree t) + (org-end-of-subtree t)))))) (declare-function outline-end-of-heading "outline" ()) (declare-function outline-flag-region "outline" (from to flag)) @@ -4580,7 +4718,6 @@ Otherwise, these types are allowed: ;; Declare Column View Code -(declare-function org-columns-number-to-string "org-colview" (n fmt &optional printf)) (declare-function org-columns-get-format-and-top-level "org-colview" ()) (declare-function org-columns-compute "org-colview" (property)) @@ -4593,79 +4730,47 @@ Otherwise, these types are allowed: ;;; Variables for pre-computed regular expressions, all buffer local -(defvar org-drawer-regexp "^[ \t]*:PROPERTIES:[ \t]*$" - "Matches first line of a hidden block.") -(make-variable-buffer-local 'org-drawer-regexp) -(defvar org-todo-regexp nil - "Matches any of the TODO state keywords.") -(make-variable-buffer-local 'org-todo-regexp) -(defvar org-not-done-regexp nil - "Matches any of the TODO state keywords except the last one.") -(make-variable-buffer-local 'org-not-done-regexp) -(defvar org-not-done-heading-regexp nil - "Matches a TODO headline that is not done.") -(make-variable-buffer-local 'org-not-done-regexp) -(defvar org-todo-line-regexp nil - "Matches a headline and puts TODO state into group 2 if present.") -(make-variable-buffer-local 'org-todo-line-regexp) -(defvar org-complex-heading-regexp nil +(defvar-local org-todo-regexp nil + "Matches any of the TODO state keywords. +Since TODO keywords are case-sensitive, `case-fold-search' is +expected to be bound to nil when matching against this regexp.") + +(defvar-local org-not-done-regexp nil + "Matches any of the TODO state keywords except the last one. +Since TODO keywords are case-sensitive, `case-fold-search' is +expected to be bound to nil when matching against this regexp.") + +(defvar-local org-not-done-heading-regexp nil + "Matches a TODO headline that is not done. +Since TODO keywords are case-sensitive, `case-fold-search' is +expected to be bound to nil when matching against this regexp.") + +(defvar-local org-todo-line-regexp nil + "Matches a headline and puts TODO state into group 2 if present. +Since TODO keywords are case-sensitive, `case-fold-search' is +expected to be bound to nil when matching against this regexp.") + +(defvar-local org-complex-heading-regexp nil "Matches a headline and puts everything into groups: -group 1: the stars -group 2: The todo keyword, maybe + +group 1: Stars +group 2: The TODO keyword, maybe group 3: Priority cookie group 4: True headline -group 5: Tags") -(make-variable-buffer-local 'org-complex-heading-regexp) -(defvar org-complex-heading-regexp-format nil +group 5: Tags + +Since TODO keywords are case-sensitive, `case-fold-search' is +expected to be bound to nil when matching against this regexp.") + +(defvar-local org-complex-heading-regexp-format nil "Printf format to make regexp to match an exact headline. This regexp will match the headline of any node which has the exact headline text that is put into the format, but may have any TODO state, priority and tags.") -(make-variable-buffer-local 'org-complex-heading-regexp-format) -(defvar org-todo-line-tags-regexp nil + +(defvar-local org-todo-line-tags-regexp nil "Matches a headline and puts TODO state into group 2 if present. Also put tags into group 4 if tags are present.") -(make-variable-buffer-local 'org-todo-line-tags-regexp) -(defvar org-ds-keyword-length 12 - "Maximum length of the DEADLINE and SCHEDULED keywords.") -(make-variable-buffer-local 'org-ds-keyword-length) -(defvar org-deadline-regexp nil - "Matches the DEADLINE keyword.") -(make-variable-buffer-local 'org-deadline-regexp) -(defvar org-deadline-time-regexp nil - "Matches the DEADLINE keyword together with a time stamp.") -(make-variable-buffer-local 'org-deadline-time-regexp) -(defvar org-deadline-time-hour-regexp nil - "Matches the DEADLINE keyword together with a time-and-hour stamp.") -(make-variable-buffer-local 'org-deadline-time-hour-regexp) -(defvar org-deadline-line-regexp nil - "Matches the DEADLINE keyword and the rest of the line.") -(make-variable-buffer-local 'org-deadline-line-regexp) -(defvar org-scheduled-regexp nil - "Matches the SCHEDULED keyword.") -(make-variable-buffer-local 'org-scheduled-regexp) -(defvar org-scheduled-time-regexp nil - "Matches the SCHEDULED keyword together with a time stamp.") -(make-variable-buffer-local 'org-scheduled-time-regexp) -(defvar org-scheduled-time-hour-regexp nil - "Matches the SCHEDULED keyword together with a time-and-hour stamp.") -(make-variable-buffer-local 'org-scheduled-time-hour-regexp) -(defvar org-closed-time-regexp nil - "Matches the CLOSED keyword together with a time stamp.") -(make-variable-buffer-local 'org-closed-time-regexp) - -(defvar org-keyword-time-regexp nil - "Matches any of the 4 keywords, together with the time stamp.") -(make-variable-buffer-local 'org-keyword-time-regexp) -(defvar org-keyword-time-not-clock-regexp nil - "Matches any of the 3 keywords, together with the time stamp.") -(make-variable-buffer-local 'org-keyword-time-not-clock-regexp) -(defvar org-maybe-keyword-time-regexp nil - "Matches a timestamp, possibly preceded by a keyword.") -(make-variable-buffer-local 'org-maybe-keyword-time-regexp) -(defvar org-all-time-keywords nil - "List of time keywords.") -(make-variable-buffer-local 'org-all-time-keywords) (defconst org-plain-time-of-day-regexp (concat @@ -4771,32 +4876,6 @@ in the #+STARTUP line), the corresponding variable, and the value to set this variable to if the option is found. An optional forth element PUSH means to push this value onto the list in the variable.") -(defun org-update-property-plist (key val props) - "Update PROPS with KEY and VAL." - (let* ((appending (string= "+" (substring key (- (length key) 1)))) - (key (if appending (substring key 0 (- (length key) 1)) key)) - (remainder (org-remove-if (lambda (p) (string= (car p) key)) props)) - (previous (cdr (assoc key props)))) - (if appending - (cons (cons key (if previous (concat previous " " val) val)) remainder) - (cons (cons key val) remainder)))) - -(defconst org-block-regexp - "^[ \t]*#\\+begin_?\\([^ \n]+\\)\\(\\([^\n]+\\)\\)?\n\\([^\000]+?\\)#\\+end_?\\1[ \t]*$" - "Regular expression for hiding blocks.") -(defconst org-heading-keyword-regexp-format - "^\\(\\*+\\)\\(?: +%s\\)\\(?: +\\(.*?\\)\\)?[ \t]*$" - "Printf format for a regexp matching a headline with some keyword. -This regexp will match the headline of any node which has the -exact keyword that is put into the format. The keyword isn't in -any group by default, but the stars and the body are.") -(defconst org-heading-keyword-maybe-regexp-format - "^\\(\\*+\\)\\(?: +%s\\)?\\(?: +\\(.*?\\)\\)?[ \t]*$" - "Printf format for a regexp matching a headline, possibly with some keyword. -This regexp can match any headline with the specified keyword, or -without a keyword. The keyword isn't in any group by default, -but the stars and the body are.") - (defcustom org-group-tags t "When non-nil (the default), use group tags. This can be turned on/off through `org-toggle-tags-groups'." @@ -4820,386 +4899,425 @@ Support for group tags is controlled by the option (message "Groups tags support has been turned %s" (if org-group-tags "on" "off"))) -(defun org-set-regexps-and-options-for-tags () - "Precompute variables used for tags." - (when (derived-mode-p 'org-mode) - (org-set-local 'org-file-tags nil) - (let ((re (org-make-options-regexp '("FILETAGS" "TAGS"))) - (splitre "[ \t]+") - (start 0) - tags ftags key value) - (save-excursion - (save-restriction - (widen) - (goto-char (point-min)) - (while (re-search-forward re nil t) - (setq key (upcase (org-match-string-no-properties 1)) - value (org-match-string-no-properties 2)) - (if (stringp value) (setq value (org-trim value))) - (cond - ((equal key "TAGS") - (setq tags (append tags (if tags '("\\n") nil) - (org-split-string value splitre)))) - ((equal key "FILETAGS") - (when (string-match "\\S-" value) - (setq ftags - (append - ftags - (apply 'append - (mapcar (lambda (x) (org-split-string x ":")) - (org-split-string value))))))))))) - ;; Process the file tags. - (and ftags (org-set-local 'org-file-tags - (mapcar 'org-add-prop-inherited ftags))) - (org-set-local 'org-tag-groups-alist nil) - ;; Process the tags. - (when (and (not tags) org-tag-alist) - (setq tags - (mapcar - (lambda (tg) (cond ((eq (car tg) :startgroup) "{") - ((eq (car tg) :endgroup) "}") - ((eq (car tg) :grouptags) ":") - ((eq (car tg) :newline) "\n") - (t (concat (car tg) - (if (characterp (cdr tg)) - (format "(%s)" (char-to-string (cdr tg))) ""))))) - org-tag-alist))) - (let (tgs g) - (dolist (e tags) - (cond - ((equal e "{") - (progn (push '(:startgroup) tgs) - (when (equal (nth 1 tags) ":") - (push (list (replace-regexp-in-string - "(.+)$" "" (nth 0 tags))) - org-tag-groups-alist) - (setq g 0)))) - ((equal e ":") (push '(:grouptags) tgs)) - ((equal e "}") (push '(:endgroup) tgs) (if g (setq g nil))) - ((equal e "\\n") (push '(:newline) tgs)) - ((string-match (org-re "^\\([[:alnum:]_@#%]+\\)(\\(.\\))$") e) - (push (cons (match-string 1 e) - (string-to-char (match-string 2 e))) - tgs) - (if (and g (> g 0)) - (setcar org-tag-groups-alist - (append (car org-tag-groups-alist) - (list (match-string 1 e))))) - (if g (setq g (1+ g)))) - (t (push (list e) tgs) - (if (and g (> g 0)) - (setcar org-tag-groups-alist - (append (car org-tag-groups-alist) (list e)))) - (if g (setq g (1+ g)))))) - (org-set-local 'org-tag-alist nil) - (dolist (e tgs) - (or (and (stringp (car e)) - (assoc (car e) org-tag-alist)) - (push e org-tag-alist))) - ;; Return a list with tag variables - (list org-file-tags org-tag-alist org-tag-groups-alist))))) - -(defvar org-ota nil) -(defun org-set-regexps-and-options () - "Precompute regular expressions used in the current buffer." +(defun org-set-regexps-and-options (&optional tags-only) + "Precompute regular expressions used in the current buffer. +When optional argument TAGS-ONLY is non-nil, only compute tags +related expressions." (when (derived-mode-p 'org-mode) - (org-set-local 'org-todo-kwd-alist nil) - (org-set-local 'org-todo-key-alist nil) - (org-set-local 'org-todo-key-trigger nil) - (org-set-local 'org-todo-keywords-1 nil) - (org-set-local 'org-done-keywords nil) - (org-set-local 'org-todo-heads nil) - (org-set-local 'org-todo-sets nil) - (org-set-local 'org-todo-log-states nil) - (org-set-local 'org-file-properties nil) - (let ((re (org-make-options-regexp - '("CATEGORY" "TODO" "COLUMNS" "STARTUP" "ARCHIVE" - "LINK" "PRIORITIES" "CONSTANTS" "PROPERTY" "DRAWERS" - "SETUPFILE" "OPTIONS") - "\\(?:[a-zA-Z][0-9a-zA-Z_]*_TODO\\)")) - (splitre "[ \t]+") - (scripts org-use-sub-superscripts) - kwds kws0 kwsa key log value cat arch const links hw dws - tail sep kws1 prio props drawers ext-setup-or-nil setup-contents - (start 0)) - (save-excursion - (save-restriction - (widen) - (goto-char (point-min)) - (while - (or (and - ext-setup-or-nil - (not org-ota) - (let (ret) - (with-temp-buffer - (insert ext-setup-or-nil) - (let ((major-mode 'org-mode) org-ota) - (setq ret (save-match-data - (org-set-regexps-and-options-for-tags))))) - ;; Append setupfile tags to existing tags - (setq org-ota t) - (setq org-file-tags - (delq nil (append org-file-tags (nth 0 ret))) - org-tag-alist - (delq nil (append org-tag-alist (nth 1 ret))) - org-tag-groups-alist - (delq nil (append org-tag-groups-alist (nth 2 ret)))))) - (and ext-setup-or-nil - (string-match re ext-setup-or-nil start) - (setq start (match-end 0))) - (and (setq ext-setup-or-nil nil start 0) - (re-search-forward re nil t))) - (setq key (upcase (match-string 1 ext-setup-or-nil)) - value (org-match-string-no-properties 2 ext-setup-or-nil)) - (if (stringp value) (setq value (org-trim value))) - (cond - ((equal key "CATEGORY") - (setq cat value)) - ((member key '("SEQ_TODO" "TODO")) - (push (cons 'sequence (org-split-string value splitre)) kwds)) - ((equal key "TYP_TODO") - (push (cons 'type (org-split-string value splitre)) kwds)) - ((string-match "\\`\\([a-zA-Z][0-9a-zA-Z_]*\\)_TODO\\'" key) - ;; general TODO-like setup - (push (cons (intern (downcase (match-string 1 key))) - (org-split-string value splitre)) - kwds)) - ((equal key "COLUMNS") - (org-set-local 'org-columns-default-format value)) - ((equal key "LINK") - (when (string-match "^\\(\\S-+\\)[ \t]+\\(.+\\)" value) - (push (cons (match-string 1 value) - (org-trim (match-string 2 value))) - links))) - ((equal key "PRIORITIES") - (setq prio (org-split-string value " +"))) - ((equal key "PROPERTY") - (when (string-match "\\(\\S-+\\)\\s-+\\(.*\\)" value) - (setq props (org-update-property-plist (match-string 1 value) - (match-string 2 value) - props)))) - ((equal key "DRAWERS") - (setq drawers (delete-dups (append org-drawers (org-split-string value splitre))))) - ((equal key "CONSTANTS") - (org-table-set-constants)) - ((equal key "STARTUP") - (let ((opts (org-split-string value splitre)) - var val) - (dolist (l opts) - (when (setq l (assoc l org-startup-options)) - (setq var (nth 1 l) val (nth 2 l)) - (if (not (nth 3 l)) - (set (make-local-variable var) val) - (if (not (listp (symbol-value var))) - (set (make-local-variable var) nil)) - (set (make-local-variable var) (symbol-value var)) - (add-to-list var val)))))) - ((equal key "ARCHIVE") - (setq arch value) - (remove-text-properties 0 (length arch) - '(face t fontified t) arch)) - ((equal key "OPTIONS") - (if (string-match "\\([ \t]\\|\\`\\)\\^:\\(t\\|nil\\|{}\\)" value) - (setq scripts (read (match-string 2 value))))) - ((and (equal key "SETUPFILE") - ;; Prevent checking in Gnus messages - (not buffer-read-only)) - (setq setup-contents (org-file-contents - (expand-file-name - (org-remove-double-quotes value)) - 'noerror)) - (if (not ext-setup-or-nil) - (setq ext-setup-or-nil setup-contents start 0) - (setq ext-setup-or-nil - (concat (substring ext-setup-or-nil 0 start) - "\n" setup-contents "\n" - (substring ext-setup-or-nil start))))))) - ;; search for property blocks - (goto-char (point-min)) - (while (re-search-forward org-block-regexp nil t) - (when (equal "PROPERTY" (upcase (match-string 1))) - (setq value (replace-regexp-in-string - "[\n\r]" " " (match-string 4))) - (when (string-match "\\(\\S-+\\)\\s-+\\(.*\\)" value) - (setq props (org-update-property-plist (match-string 1 value) - (match-string 2 value) - props))))))) - (org-set-local 'org-use-sub-superscripts scripts) - (when cat - (org-set-local 'org-category (intern cat)) - (push (cons "CATEGORY" cat) props)) - (when prio - (if (< (length prio) 3) (setq prio '("A" "C" "B"))) - (setq prio (mapcar 'string-to-char prio)) - (org-set-local 'org-highest-priority (nth 0 prio)) - (org-set-local 'org-lowest-priority (nth 1 prio)) - (org-set-local 'org-default-priority (nth 2 prio))) - (and props (org-set-local 'org-file-properties (nreverse props))) - (and drawers (org-set-local 'org-drawers drawers)) - (and arch (org-set-local 'org-archive-location arch)) - (and links (setq org-link-abbrev-alist-local (nreverse links))) - ;; Process the TODO keywords - (unless kwds - ;; Use the global values as if they had been given locally. - (setq kwds (default-value 'org-todo-keywords)) - (if (stringp (car kwds)) - (setq kwds (list (cons org-todo-interpretation - (default-value 'org-todo-keywords))))) - (setq kwds (reverse kwds))) - (setq kwds (nreverse kwds)) - (let (inter kw) - (dolist (kws kwds) - (let ((kws (or - (run-hook-with-args-until-success - 'org-todo-setup-filter-hook kws) - kws))) - (setq inter (pop kws) sep (member "|" kws) - kws0 (delete "|" (copy-sequence kws)) - kwsa nil - kws1 (mapcar - (lambda (x) - ;; 1 2 - (if (string-match "^\\(.*?\\)\\(?:(\\([^!@/]\\)?.*?)\\)?$" x) - (progn - (setq kw (match-string 1 x) - key (and (match-end 2) (match-string 2 x)) - log (org-extract-log-state-settings x)) - (push (cons kw (and key (string-to-char key))) kwsa) - (and log (push log org-todo-log-states)) - kw) - (error "Invalid TODO keyword %s" x))) - kws0) - kwsa (if kwsa (append '((:startgroup)) - (nreverse kwsa) - '((:endgroup)))) - hw (car kws1) - dws (if sep (org-remove-keyword-keys (cdr sep)) (last kws1)) - tail (list inter hw (car dws) (org-last dws)))) - (add-to-list 'org-todo-heads hw 'append) - (push kws1 org-todo-sets) - (setq org-done-keywords (append org-done-keywords dws nil)) - (setq org-todo-key-alist (append org-todo-key-alist kwsa)) - (mapc (lambda (x) (push (cons x tail) org-todo-kwd-alist)) kws1) - (setq org-todo-keywords-1 (append org-todo-keywords-1 kws1 nil))) + (let ((alist (org--setup-collect-keywords + (org-make-options-regexp + (append '("FILETAGS" "TAGS" "SETUPFILE") + (and (not tags-only) + '("ARCHIVE" "CATEGORY" "COLUMNS" "CONSTANTS" + "LINK" "OPTIONS" "PRIORITIES" "PROPERTY" + "SEQ_TODO" "STARTUP" "TODO" "TYP_TODO"))))))) + ;; Startup options. Get this early since it does change + ;; behavior for other options (e.g., tags). + (let ((startup (cdr (assq 'startup alist)))) + (dolist (option startup) + (let ((entry (assoc-string option org-startup-options t))) + (when entry + (let ((var (nth 1 entry)) + (val (nth 2 entry))) + (if (not (nth 3 entry)) (set (make-local-variable var) val) + (unless (listp (symbol-value var)) + (set (make-local-variable var) nil)) + (add-to-list var val))))))) + (setq-local org-file-tags + (mapcar #'org-add-prop-inherited + (cdr (assq 'filetags alist)))) + (setq org-current-tag-alist + (append org-tag-persistent-alist + (let ((tags (cdr (assq 'tags alist)))) + (if tags (org-tag-string-to-alist tags) + org-tag-alist)))) + (setq org-tag-groups-alist + (org-tag-alist-to-groups org-current-tag-alist)) + (unless tags-only + ;; File properties. + (setq-local org-file-properties (cdr (assq 'property alist))) + ;; Archive location. + (let ((archive (cdr (assq 'archive alist)))) + (when archive (setq-local org-archive-location archive))) + ;; Category. + (let ((cat (org-string-nw-p (cdr (assq 'category alist))))) + (when cat + (setq-local org-category (intern cat)) + (setq-local org-file-properties + (org--update-property-plist + "CATEGORY" cat org-file-properties)))) + ;; Columns. + (let ((column (cdr (assq 'columns alist)))) + (when column (setq-local org-columns-default-format column))) + ;; Constants. + (setq org-table-formula-constants-local (cdr (assq 'constants alist))) + ;; Link abbreviations. + (let ((links (cdr (assq 'link alist)))) + (when links (setq org-link-abbrev-alist-local (nreverse links)))) + ;; Priorities. + (let ((priorities (cdr (assq 'priorities alist)))) + (when priorities + (setq-local org-highest-priority (nth 0 priorities)) + (setq-local org-lowest-priority (nth 1 priorities)) + (setq-local org-default-priority (nth 2 priorities)))) + ;; Scripts. + (let ((scripts (assq 'scripts alist))) + (when scripts + (setq-local org-use-sub-superscripts (cdr scripts)))) + ;; TODO keywords. + (setq-local org-todo-kwd-alist nil) + (setq-local org-todo-key-alist nil) + (setq-local org-todo-key-trigger nil) + (setq-local org-todo-keywords-1 nil) + (setq-local org-done-keywords nil) + (setq-local org-todo-heads nil) + (setq-local org-todo-sets nil) + (setq-local org-todo-log-states nil) + (let ((todo-sequences + (or (nreverse (cdr (assq 'todo alist))) + (let ((d (default-value 'org-todo-keywords))) + (if (not (stringp (car d))) d + ;; XXX: Backward compatibility code. + (list (cons org-todo-interpretation d))))))) + (dolist (sequence todo-sequences) + (let* ((sequence (or (run-hook-with-args-until-success + 'org-todo-setup-filter-hook sequence) + sequence)) + (sequence-type (car sequence)) + (keywords (cdr sequence)) + (sep (member "|" keywords)) + names alist) + (dolist (k (remove "|" keywords)) + (unless (string-match "^\\(.*?\\)\\(?:(\\([^!@/]\\)?.*?)\\)?$" + k) + (error "Invalid TODO keyword %s" k)) + (let ((name (match-string 1 k)) + (key (match-string 2 k)) + (log (org-extract-log-state-settings k))) + (push name names) + (push (cons name (and key (string-to-char key))) alist) + (when log (push log org-todo-log-states)))) + (let* ((names (nreverse names)) + (done (if sep (org-remove-keyword-keys (cdr sep)) + (last names))) + (head (car names)) + (tail (list sequence-type head (car done) (org-last done)))) + (add-to-list 'org-todo-heads head 'append) + (push names org-todo-sets) + (setq org-done-keywords (append org-done-keywords done nil)) + (setq org-todo-keywords-1 (append org-todo-keywords-1 names nil)) + (setq org-todo-key-alist + (append org-todo-key-alist + (and alist + (append '((:startgroup)) + (nreverse alist) + '((:endgroup)))))) + (dolist (k names) (push (cons k tail) org-todo-kwd-alist)))))) (setq org-todo-sets (nreverse org-todo-sets) org-todo-kwd-alist (nreverse org-todo-kwd-alist) - org-todo-key-trigger (delq nil (mapcar 'cdr org-todo-key-alist)) - org-todo-key-alist (org-assign-fast-keys org-todo-key-alist))) - ;; Compute the regular expressions and other local variables. - ;; Using `org-outline-regexp-bol' would complicate them much, - ;; because of the fixed white space at the end of that string. - (if (not org-done-keywords) - (setq org-done-keywords (and org-todo-keywords-1 - (list (org-last org-todo-keywords-1))))) - (setq org-ds-keyword-length (+ 2 (max (length org-deadline-string) - (length org-scheduled-string) - (length org-clock-string) - (length org-closed-string))) - org-drawer-regexp - (concat "^[ \t]*:\\(" - (mapconcat 'regexp-quote org-drawers "\\|") - "\\):[ \t]*$") - org-not-done-keywords - (org-delete-all org-done-keywords (copy-sequence org-todo-keywords-1)) - org-todo-regexp - (concat "\\(" - (mapconcat 'regexp-quote org-todo-keywords-1 "\\|") - "\\)") - org-not-done-regexp - (concat "\\(" - (mapconcat 'regexp-quote org-not-done-keywords "\\|") - "\\)") - org-not-done-heading-regexp - (format org-heading-keyword-regexp-format org-not-done-regexp) - org-todo-line-regexp - (format org-heading-keyword-maybe-regexp-format org-todo-regexp) - org-complex-heading-regexp - (concat "^\\(\\*+\\)" - "\\(?: +" org-todo-regexp "\\)?" - "\\(?: +\\(\\[#.\\]\\)\\)?" - "\\(?: +\\(.*?\\)\\)??" - (org-re "\\(?:[ \t]+\\(:[[:alnum:]_@#%:]+:\\)\\)?") - "[ \t]*$") - org-complex-heading-regexp-format - (concat "^\\(\\*+\\)" - "\\(?: +" org-todo-regexp "\\)?" - "\\(?: +\\(\\[#.\\]\\)\\)?" - "\\(?: +" - ;; Stats cookies can be stuck to body. - "\\(?:\\[[0-9%%/]+\\] *\\)*" - "\\(%s\\)" - "\\(?: *\\[[0-9%%/]+\\]\\)*" - "\\)" - (org-re "\\(?:[ \t]+\\(:[[:alnum:]_@#%%:]+:\\)\\)?") - "[ \t]*$") - org-todo-line-tags-regexp - (concat "^\\(\\*+\\)" - "\\(?: +" org-todo-regexp "\\)?" - "\\(?: +\\(.*?\\)\\)??" - (org-re "\\(?:[ \t]+\\(:[[:alnum:]:_@#%]+:\\)\\)?") - "[ \t]*$") - org-deadline-regexp (concat "\\<" org-deadline-string) - org-deadline-time-regexp - (concat "\\<" org-deadline-string " *<\\([^>]+\\)>") - org-deadline-time-hour-regexp - (concat "\\<" org-deadline-string - " *<\\([^>]+[0-9]\\{1,2\\}:[0-9]\\{2\\}[0-9-+:hdwmy \t.]*\\)>") - org-deadline-line-regexp - (concat "\\<\\(" org-deadline-string "\\).*") - org-scheduled-regexp - (concat "\\<" org-scheduled-string) - org-scheduled-time-regexp - (concat "\\<" org-scheduled-string " *<\\([^>]+\\)>") - org-scheduled-time-hour-regexp - (concat "\\<" org-scheduled-string - " *<\\([^>]+[0-9]\\{1,2\\}:[0-9]\\{2\\}[0-9-+:hdwmy \t.]*\\)>") - org-closed-time-regexp - (concat "\\<" org-closed-string " *\\[\\([^]]+\\)\\]") - org-keyword-time-regexp - (concat "\\<\\(" org-scheduled-string - "\\|" org-deadline-string - "\\|" org-closed-string - "\\|" org-clock-string "\\)" - " *[[<]\\([^]>]+\\)[]>]") - org-keyword-time-not-clock-regexp - (concat "\\<\\(" org-scheduled-string - "\\|" org-deadline-string - "\\|" org-closed-string - "\\)" - " *[[<]\\([^]>]+\\)[]>]") - org-maybe-keyword-time-regexp - (concat "\\(\\<\\(" org-scheduled-string - "\\|" org-deadline-string - "\\|" org-closed-string - "\\|" org-clock-string "\\)\\)?" - " *\\([[<][0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} ?[^]\r\n>]*?[]>]\\|<%%([^\r\n>]*>\\)") - org-all-time-keywords - (mapcar (lambda (w) (substring w 0 -1)) - (list org-scheduled-string org-deadline-string - org-clock-string org-closed-string))) - (setq org-ota nil) - (org-compute-latex-and-related-regexp)))) - -(defun org-file-contents (file &optional noerror) - "Return the contents of FILE, as a string." - (if (or (not file) (not (file-readable-p file))) - (if (not noerror) - (error "Cannot read file \"%s\"" file) - (message "Cannot read file \"%s\"" file) - "") - (with-temp-buffer - (insert-file-contents file) - (buffer-string)))) + org-todo-key-trigger (delq nil (mapcar #'cdr org-todo-key-alist)) + org-todo-key-alist (org-assign-fast-keys org-todo-key-alist)) + ;; Compute the regular expressions and other local variables. + ;; Using `org-outline-regexp-bol' would complicate them much, + ;; because of the fixed white space at the end of that string. + (unless org-done-keywords + (setq org-done-keywords + (and org-todo-keywords-1 (last org-todo-keywords-1)))) + (setq org-not-done-keywords + (org-delete-all org-done-keywords + (copy-sequence org-todo-keywords-1)) + org-todo-regexp (regexp-opt org-todo-keywords-1 t) + org-not-done-regexp (regexp-opt org-not-done-keywords t) + org-not-done-heading-regexp + (format org-heading-keyword-regexp-format org-not-done-regexp) + org-todo-line-regexp + (format org-heading-keyword-maybe-regexp-format org-todo-regexp) + org-complex-heading-regexp + (concat "^\\(\\*+\\)" + "\\(?: +" org-todo-regexp "\\)?" + "\\(?: +\\(\\[#.\\]\\)\\)?" + "\\(?: +\\(.*?\\)\\)??" + "\\(?:[ \t]+\\(:[[:alnum:]_@#%:]+:\\)\\)?" + "[ \t]*$") + org-complex-heading-regexp-format + (concat "^\\(\\*+\\)" + "\\(?: +" org-todo-regexp "\\)?" + "\\(?: +\\(\\[#.\\]\\)\\)?" + "\\(?: +" + ;; Stats cookies can be stuck to body. + "\\(?:\\[[0-9%%/]+\\] *\\)*" + "\\(%s\\)" + "\\(?: *\\[[0-9%%/]+\\]\\)*" + "\\)" + "\\(?:[ \t]+\\(:[[:alnum:]_@#%%:]+:\\)\\)?" + "[ \t]*$") + org-todo-line-tags-regexp + (concat "^\\(\\*+\\)" + "\\(?: +" org-todo-regexp "\\)?" + "\\(?: +\\(.*?\\)\\)??" + "\\(?:[ \t]+\\(:[[:alnum:]:_@#%]+:\\)\\)?" + "[ \t]*$")) + (org-compute-latex-and-related-regexp))))) + +(defun org--setup-collect-keywords (regexp &optional files alist) + "Return setup keywords values as an alist. + +REGEXP matches a subset of setup keywords. FILES is a list of +file names already visited. It is used to avoid circular setup +files. ALIST, when non-nil, is the alist computed so far. + +Return value contains the following keys: `archive', `category', +`columns', `constants', `filetags', `link', `priorities', +`property', `scripts', `startup', `tags' and `todo'." + (org-with-wide-buffer + (goto-char (point-min)) + (let ((case-fold-search t)) + (while (re-search-forward regexp nil t) + (let ((element (org-element-at-point))) + (when (eq (org-element-type element) 'keyword) + (let ((key (org-element-property :key element)) + (value (org-element-property :value element))) + (cond + ((equal key "ARCHIVE") + (when (org-string-nw-p value) + (push (cons 'archive value) alist))) + ((equal key "CATEGORY") (push (cons 'category value) alist)) + ((equal key "COLUMNS") (push (cons 'columns value) alist)) + ((equal key "CONSTANTS") + (let* ((constants (assq 'constants alist)) + (store (cdr constants))) + (dolist (pair (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-unbracket-string "\"" "\"" value))))) + (when (and f (file-readable-p f) (not (member f files))) + (with-temp-buffer + (setq default-directory (file-name-directory f)) + (insert-file-contents f) + (setq alist + ;; Fake Org mode to benefit from cache + ;; without recurring needlessly. + (let ((major-mode 'org-mode)) + (org--setup-collect-keywords + regexp (cons f files) alist))))))))))))))) + alist) + +(defun org-tag-string-to-alist (s) + "Return tag alist associated to string S. +S is a value for TAGS keyword or produced with +`org-tag-alist-to-string'. Return value is an alist suitable for +`org-tag-alist' or `org-tag-persistent-alist'." + (let ((lines (mapcar #'split-string (split-string s "\n" t))) + (tag-re (concat "\\`\\([[:alnum:]_@#%]+" + "\\|{.+?}\\)" ; regular expression + "\\(?:(\\(.\\))\\)?\\'")) + alist group-flag) + (dolist (tokens lines (cdr (nreverse alist))) + (push '(:newline) alist) + (while tokens + (let ((token (pop tokens))) + (pcase token + ("{" + (push '(:startgroup) alist) + (when (equal (nth 1 tokens) ":") (setq group-flag t))) + ("}" + (push '(:endgroup) alist) + (setq group-flag nil)) + ("[" + (push '(:startgrouptag) alist) + (when (equal (nth 1 tokens) ":") (setq group-flag t))) + ("]" + (push '(:endgrouptag) alist) + (setq group-flag nil)) + (":" + (push '(:grouptags) alist)) + ((guard (string-match tag-re token)) + (let ((tag (match-string 1 token)) + (key (and (match-beginning 2) + (string-to-char (match-string 2 token))))) + ;; Push all tags in groups, no matter if they already + ;; appear somewhere else in the list. + (when (or group-flag (not (assoc tag alist))) + (push (cons tag key) alist)))))))))) + +(defun org-tag-alist-to-string (alist &optional skip-key) + "Return tag string associated to ALIST. + +ALIST is an alist, as defined in `org-tag-alist' or +`org-tag-persistent-alist', or produced with +`org-tag-string-to-alist'. + +Return value is a string suitable as a value for \"TAGS\" +keyword. + +When optional argument SKIP-KEY is non-nil, skip selection keys +next to tags." + (mapconcat (lambda (token) + (pcase token + (`(:startgroup) "{") + (`(:endgroup) "}") + (`(:startgrouptag) "[") + (`(:endgrouptag) "]") + (`(:grouptags) ":") + (`(:newline) "\\n") + ((and + (guard (not skip-key)) + `(,(and tag (pred stringp)) . ,(and key (pred characterp)))) + (format "%s(%c)" tag key)) + (`(,(and tag (pred stringp)) . ,_) tag) + (_ (user-error "Invalid tag token: %S" token)))) + alist + " ")) + +(defun org-tag-alist-to-groups (alist) + "Return group alist from tag ALIST. +ALIST is an alist, as defined in `org-tag-alist' or +`org-tag-persistent-alist', or produced with +`org-tag-string-to-alist'. Return value is an alist following +the pattern (GROUP-TAG TAGS) where GROUP-TAG is the tag, as +a string, summarizing TAGS, as a list of strings." + (let (groups group-status current-group) + (dolist (token alist (nreverse groups)) + (pcase token + (`(,(or :startgroup :startgrouptag)) (setq group-status t)) + (`(,(or :endgroup :endgrouptag)) + (when (eq group-status 'append) + (push (nreverse current-group) groups)) + (setq group-status nil)) + (`(:grouptags) (setq group-status 'append)) + ((and `(,tag . ,_) (guard group-status)) + (if (eq group-status 'append) (push tag current-group) + (setq current-group (list tag)))) + (_ nil))))) + +(defvar org--file-cache (make-hash-table :test #'equal) + "Hash table to store contents of files referenced via a URL. +This is the cache of file URLs read using `org-file-contents'.") + +(defun org-reset-file-cache () + "Reset the cache of files downloaded by `org-file-contents'." + (clrhash org--file-cache)) + +(defun org-file-url-p (file) + "Non-nil if FILE is a URL." + (require 'ffap) + (string-match-p ffap-url-regexp file)) + +(defun org-file-contents (file &optional noerror nocache) + "Return the contents of FILE, as a string. + +FILE can be a file name or URL. + +If FILE is a URL, download the contents. If the URL contents are +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. + +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." + (let* ((is-url (org-file-url-p file)) + (cache (and is-url + (not nocache) + (gethash file org--file-cache)))) + (cond + (cache) + (is-url + (with-current-buffer (url-retrieve-synchronously file) + (goto-char (point-min)) + ;; Move point to after the url-retrieve header. + (search-forward "\n\n" nil :move) + ;; Search for the success code only in the url-retrieve header. + (if (save-excursion + (re-search-backward "HTTP.*\\s-+200\\s-OK" nil :noerror)) + ;; Update the cache `org--file-cache' and return contents. + (puthash file + (buffer-substring-no-properties (point) (point-max)) + org--file-cache) + (funcall (if noerror #'message #'user-error) + "Unable to fetch file from %S" + file)))) + (t + (with-temp-buffer + (condition-case nil + (progn + (insert-file-contents file) + (buffer-string)) + (file-error + (funcall (if noerror #'message #'user-error) + "Unable to read file %S" + file)))))))) (defun org-extract-log-state-settings (x) "Extract the log state setting from a TODO keyword string. This will extract info from a string like \"WAIT(w@/!)\"." - (let (kw key log1 log2) - (when (string-match "^\\(.*?\\)\\(?:(\\([^!@/]\\)?\\([!@]\\)?\\(?:/\\([!@]\\)\\)?)\\)?$" x) - (setq kw (match-string 1 x) - key (and (match-end 2) (match-string 2 x)) - log1 (and (match-end 3) (match-string 3 x)) - log2 (and (match-end 4) (match-string 4 x))) + (when (string-match "^\\(.*?\\)\\(?:(\\([^!@/]\\)?\\([!@]\\)?\\(?:/\\([!@]\\)\\)?)\\)?$" x) + (let ((kw (match-string 1 x)) + (log1 (and (match-end 3) (match-string 3 x))) + (log2 (and (match-end 4) (match-string 4 x)))) (and (or log1 log2) (list kw (and log1 (if (equal log1 "!") 'time 'note)) @@ -5216,8 +5334,8 @@ This will extract info from a string like \"WAIT(w@/!)\"." (defun org-assign-fast-keys (alist) "Assign fast keys to a keyword-key alist. Respect keys that are already there." - (let (new (alt ?0)) - (dolist (e alist) + (let (new e (alt ?0)) + (while (setq e (pop alist)) (if (or (memq (car e) '(:newline :grouptags :endgroup :startgroup)) (cdr e)) ;; Key already assigned. (push e new) @@ -5229,7 +5347,7 @@ Respect keys that are already there." (pop clist)) (unless clist (while (rassoc alt used) - (incf alt))) + (cl-incf alt))) (push (cons (car e) (or (car clist) alt)) new)))) (nreverse new))) @@ -5242,13 +5360,7 @@ Respect keys that are already there." (defvar org-finish-function nil "Function to be called when `C-c C-c' is used. This is for getting out of special buffers like capture.") - - -;; FIXME: Occasionally check by commenting these, to make sure -;; no other functions uses these, forgetting to let-bind them. -(org-no-warnings (defvar entry)) ;; unprefixed, from calendar.el (defvar org-last-state) -(org-no-warnings (defvar date)) ;; unprefixed, from calendar.el ;; Defined somewhere in this file, but used before definition. (defvar org-entities) ;; defined in org-entities.el @@ -5256,7 +5368,7 @@ This is for getting out of special buffers like capture.") (defvar org-org-menu) (defvar org-tbl-menu) -;;;; Define the Org-mode +;;;; Define the Org mode ;; We use a before-change function to check if a table might need ;; an update. @@ -5264,7 +5376,7 @@ This is for getting out of special buffers like capture.") "Indicates that a table might need an update. This variable is set by `org-before-change-function'. `org-table-align' sets it back to nil.") -(defun org-before-change-function (beg end) +(defun org-before-change-function (_beg _end) "Every change indicates that a table might need an update." (setq org-table-may-need-update t)) (defvar org-mode-map) @@ -5278,13 +5390,12 @@ This variable is set by `org-before-change-function'. (defvar buffer-face-mode-face) (require 'outline) -(if (and (not (keymapp outline-mode-map)) (featurep 'allout)) - (error "Conflict with outdated version of allout.el. Load org.el before allout.el, or upgrade to newer allout, for example by switching to Emacs 22")) -(require 'noutline "noutline" 'noerror) ;; stock XEmacs does not have it ;; Other stuff we need. (require 'time-date) +(unless (fboundp 'time-subtract) (defalias 'time-subtract 'subtract-time)) (require 'easymenu) +(autoload 'easy-menu-add "easymenu") (require 'overlay) ;; (require 'org-macs) moved higher up in the file before it is first used @@ -5305,15 +5416,15 @@ This variable is set by `org-before-change-function'. "Outline-based notes management and organizer, alias \"Carsten's outline-mode for keeping track of everything.\" -Org-mode develops organizational tasks around a NOTES file which -contains information about projects as plain text. Org-mode is -implemented on top of outline-mode, which is ideal to keep the content +Org mode develops organizational tasks around a NOTES file which +contains information about projects as plain text. Org mode is +implemented on top of Outline mode, which is ideal to keep the content of large files well structured. It supports ToDo items, deadlines and time stamps, which magically appear in the diary listing of the Emacs calendar. Tables are easily created with a built-in table editor. Plain text URL-like links connect to websites, emails (VM), Usenet messages (Gnus), BBDB entries, and any files related to the project. -For printing and sharing of notes, an Org-mode file (or a part of it) +For printing and sharing of notes, an Org file (or a part of it) can be exported as a structured ASCII or HTML file. The following commands are available: @@ -5323,86 +5434,68 @@ The following commands are available: ;; Get rid of Outline menus, they are not needed ;; Need to do this here because define-derived-mode sets up ;; the keymap so late. Still, it is a waste to call this each time - ;; we switch another buffer into org-mode. - (if (featurep 'xemacs) - (when (boundp 'outline-mode-menu-heading) - ;; Assume this is Greg's port, it uses easymenu - (easy-menu-remove outline-mode-menu-heading) - (easy-menu-remove outline-mode-menu-show) - (easy-menu-remove outline-mode-menu-hide)) - (define-key org-mode-map [menu-bar headings] 'undefined) - (define-key org-mode-map [menu-bar hide] 'undefined) - (define-key org-mode-map [menu-bar show] 'undefined)) + ;; we switch another buffer into Org mode. + (define-key org-mode-map [menu-bar headings] 'undefined) + (define-key org-mode-map [menu-bar hide] 'undefined) + (define-key org-mode-map [menu-bar show] 'undefined) (org-load-modules-maybe) - (when (featurep 'xemacs) - (easy-menu-add org-org-menu) - (easy-menu-add org-tbl-menu)) (org-install-agenda-files-menu) - (if org-descriptive-links (add-to-invisibility-spec '(org-link))) + (when org-descriptive-links (add-to-invisibility-spec '(org-link))) (add-to-invisibility-spec '(org-cwidth)) (add-to-invisibility-spec '(org-hide-block . t)) - (when (featurep 'xemacs) - (org-set-local 'line-move-ignore-invisible t)) - (org-set-local 'outline-regexp org-outline-regexp) - (org-set-local 'outline-level 'org-outline-level) + (setq-local outline-regexp org-outline-regexp) + (setq-local outline-level 'org-outline-level) (setq bidi-paragraph-direction 'left-to-right) - (when (and org-ellipsis - (fboundp 'set-display-table-slot) (boundp 'buffer-display-table) - (fboundp 'make-glyph-code)) + (when (and (stringp org-ellipsis) (not (equal "" org-ellipsis))) (unless org-display-table (setq org-display-table (make-display-table))) (set-display-table-slot org-display-table 4 - (vconcat (mapcar - (lambda (c) (make-glyph-code c (and (not (stringp org-ellipsis)) - org-ellipsis))) - (if (stringp org-ellipsis) org-ellipsis "...")))) + (vconcat (mapcar (lambda (c) (make-glyph-code c 'org-ellipsis)) + org-ellipsis))) (setq buffer-display-table org-display-table)) - (org-set-regexps-and-options-for-tags) (org-set-regexps-and-options) (org-set-font-lock-defaults) (when (and org-tag-faces (not org-tags-special-faces-re)) ;; tag faces set outside customize.... force initialization. (org-set-tag-faces 'org-tag-faces org-tag-faces)) ;; Calc embedded - (org-set-local 'calc-embedded-open-mode "# ") + (setq-local calc-embedded-open-mode "# ") ;; Modify a few syntax entries (modify-syntax-entry ?@ "w") (modify-syntax-entry ?\" "\"") (modify-syntax-entry ?\\ "_") (modify-syntax-entry ?~ "_") - (if org-startup-truncated (setq truncate-lines t)) - (when org-startup-indented (require 'org-indent) (org-indent-mode 1)) - (org-set-local 'font-lock-unfontify-region-function - 'org-unfontify-region) + (setq-local font-lock-unfontify-region-function 'org-unfontify-region) ;; Activate before-change-function - (org-set-local 'org-table-may-need-update t) - (org-add-hook 'before-change-functions 'org-before-change-function nil - 'local) + (setq-local org-table-may-need-update t) + (add-hook 'before-change-functions 'org-before-change-function nil 'local) ;; Check for running clock before killing a buffer - (org-add-hook 'kill-buffer-hook 'org-check-running-clock nil 'local) + (add-hook 'kill-buffer-hook 'org-check-running-clock nil 'local) ;; Initialize macros templates. (org-macro-initialize-templates) ;; Initialize radio targets. (org-update-radio-target-regexp) ;; Indentation. - (org-set-local 'indent-line-function 'org-indent-line) - (org-set-local 'indent-region-function 'org-indent-region) + (setq-local indent-line-function 'org-indent-line) + (setq-local indent-region-function 'org-indent-region) ;; Filling and auto-filling. (org-setup-filling) ;; Comments. (org-setup-comments-handling) + ;; Initialize cache. + (org-element-cache-reset) ;; Beginning/end of defun - (org-set-local 'beginning-of-defun-function 'org-backward-element) - (org-set-local 'end-of-defun-function - (lambda () - (if (not (org-at-heading-p)) - (org-forward-element) - (org-forward-element) - (forward-char -1)))) + (setq-local beginning-of-defun-function 'org-backward-element) + (setq-local end-of-defun-function + (lambda () + (if (not (org-at-heading-p)) + (org-forward-element) + (org-forward-element) + (forward-char -1)))) ;; Next error for sparse trees - (org-set-local 'next-error-function 'org-occur-next-match) + (setq-local next-error-function 'org-occur-next-match) ;; Make sure dependence stuff works reliably, even for users who set it ;; too late :-( (if org-enforce-todo-dependencies @@ -5417,78 +5510,68 @@ The following commands are available: 'org-block-todo-from-checkboxes)) ;; Align options lines - (org-set-local - 'align-mode-rules-list + (setq-local + align-mode-rules-list '((org-in-buffer-settings - (regexp . "^#\\+[A-Z_]+:\\(\\s-*\\)\\S-+") + (regexp . "^[ \t]*#\\+[A-Z_]+:\\(\\s-*\\)\\S-+") (modes . '(org-mode))))) ;; Imenu - (org-set-local 'imenu-create-index-function - 'org-imenu-get-tree) + (setq-local imenu-create-index-function 'org-imenu-get-tree) ;; Make isearch reveal context - (if (or (featurep 'xemacs) - (not (boundp 'outline-isearch-open-invisible-function))) - ;; Emacs 21 and XEmacs make use of the hook - (org-add-hook 'isearch-mode-end-hook 'org-isearch-end 'append 'local) - ;; Emacs 22 deals with this through a special variable - (org-set-local 'outline-isearch-open-invisible-function - (lambda (&rest ignore) (org-show-context 'isearch)))) + (setq-local outline-isearch-open-invisible-function + (lambda (&rest _) (org-show-context 'isearch))) ;; Setup the pcomplete hooks - (set (make-local-variable 'pcomplete-command-completion-function) - 'org-pcomplete-initial) - (set (make-local-variable 'pcomplete-command-name-function) - 'org-command-at-point) - (set (make-local-variable 'pcomplete-default-completion-function) - 'ignore) - (set (make-local-variable 'pcomplete-parse-arguments-function) - 'org-parse-arguments) - (set (make-local-variable 'pcomplete-termination-string) "") - (when (>= emacs-major-version 23) - (set (make-local-variable 'buffer-face-mode-face) 'org-default)) - - ;; If empty file that did not turn on org-mode automatically, make it to. - (if (and org-insert-mode-line-in-empty-file - (org-called-interactively-p 'any) - (= (point-min) (point-max))) - (insert "# -*- mode: org -*-\n\n")) + (setq-local pcomplete-command-completion-function 'org-pcomplete-initial) + (setq-local pcomplete-command-name-function 'org-command-at-point) + (setq-local pcomplete-default-completion-function 'ignore) + (setq-local pcomplete-parse-arguments-function 'org-parse-arguments) + (setq-local pcomplete-termination-string "") + (setq-local buffer-face-mode-face 'org-default) + + ;; If empty file that did not turn on Org mode automatically, make + ;; it to. + (when (and org-insert-mode-line-in-empty-file + (called-interactively-p 'any) + (= (point-min) (point-max))) + (insert "# -*- mode: org -*-\n\n")) (unless org-inhibit-startup (org-unmodified - (and org-startup-with-beamer-mode (org-beamer-mode)) + (when org-startup-with-beamer-mode (org-beamer-mode)) (when org-startup-align-all-tables - (org-table-map-tables 'org-table-align 'quietly)) - (when org-startup-with-inline-images - (org-display-inline-images)) - (when org-startup-with-latex-preview - (org-preview-latex-fragment)) - (unless org-inhibit-startup-visibility-stuff - (org-set-startup-visibility)))) - ;; Try to set org-hide correctly + (org-table-map-tables #'org-table-align t)) + (when org-startup-with-inline-images (org-display-inline-images)) + (when org-startup-with-latex-preview (org-toggle-latex-fragment '(16))) + (unless org-inhibit-startup-visibility-stuff (org-set-startup-visibility)) + (when org-startup-truncated (setq truncate-lines t)) + (when org-startup-indented (require 'org-indent) (org-indent-mode 1)) + (org-refresh-effort-properties))) + ;; Try to set `org-hide' face correctly. (let ((foreground (org-find-invisible-foreground))) - (if foreground - (set-face-foreground 'org-hide foreground)))) + (when foreground + (set-face-foreground 'org-hide foreground)))) ;; Update `customize-package-emacs-version-alist' (add-to-list 'customize-package-emacs-version-alist - '(Org ("6.21b" . "23.1") ("6.33x" . "23.2") - ("7.8.11" . "24.1") ("7.9.4" . "24.3") - ("8.2.6" . "24.4"))) + '(Org ("8.0" . "24.4") + ("8.1" . "24.4") + ("8.2" . "24.4") + ("8.2.7" . "24.4") + ("8.3" . "26.1") + ("9.0" . "26.1") + ("9.1" . "26.1"))) (defvar org-mode-transpose-word-syntax-table - (let ((st (make-syntax-table))) - (mapc (lambda(c) (modify-syntax-entry - (string-to-char (car c)) "w p" st)) - org-emphasis-alist) - st)) + (let ((st (make-syntax-table text-mode-syntax-table))) + (dolist (c org-emphasis-alist st) + (modify-syntax-entry (string-to-char (car c)) "w p" st)))) (when (fboundp 'abbrev-table-put) (abbrev-table-put org-mode-abbrev-table :parents (list text-mode-abbrev-table))) -(put 'org-mode 'flyspell-mode-predicate 'org-mode-flyspell-verify) - (defun org-find-invisible-foreground () (let ((candidates (remove "unspecified-bg" @@ -5498,7 +5581,7 @@ The following commands are available: (mapcar (lambda (alist) (when (boundp alist) - (cdr (assoc 'background-color (symbol-value alist))))) + (cdr (assq 'background-color (symbol-value alist))))) '(default-frame-alist initial-frame-alist window-system-default-frame-alist)) (list (face-foreground 'org-hide)))))) (car (remove nil candidates)))) @@ -5541,8 +5624,6 @@ the rounding returns a past time." (require 'font-lock) (defconst org-non-link-chars "]\t\n\r<>") -(defvar org-link-types '("http" "https" "ftp" "mailto" "file" "news" - "shell" "elisp" "doi" "message")) (defvar org-link-types-re nil "Matches a link that has a url-like prefix like \"http:\"") (defvar org-link-re-with-space nil @@ -5591,27 +5672,26 @@ stacked delimiters is N. Escaping delimiters is not possible." next (concat "\\(?:" nothing left next right "\\)+" nothing))) (concat left "\\(" re "\\)" right))) -(defvar org-match-substring-regexp +(defconst org-match-substring-regexp (concat "\\(\\S-\\)\\([_^]\\)\\(" - "\\(" (org-create-multibrace-regexp "{" "}" org-match-sexp-depth) "\\)" + "\\(?:" (org-create-multibrace-regexp "{" "}" org-match-sexp-depth) "\\)" "\\|" - "\\(" (org-create-multibrace-regexp "(" ")" org-match-sexp-depth) "\\)" + "\\(?:" (org-create-multibrace-regexp "(" ")" org-match-sexp-depth) "\\)" "\\|" - "\\(\\(?:\\*\\|[-+]?[^-+*!@#$%^_ \t\r\n,:\"?<>~;./{}=()]+\\)\\)\\)") + "\\(?:\\*\\|[+-]?[[:alnum:].,\\]*[[:alnum:]]\\)\\)") "The regular expression matching a sub- or superscript.") -(defvar org-match-substring-with-braces-regexp +(defconst org-match-substring-with-braces-regexp (concat - "\\(\\S-\\)\\([_^]\\)\\(" - "\\(" (org-create-multibrace-regexp "{" "}" org-match-sexp-depth) "\\)" - "\\)") + "\\(\\S-\\)\\([_^]\\)" + "\\(" (org-create-multibrace-regexp "{" "}" org-match-sexp-depth) "\\)") "The regular expression matching a sub- or superscript, forcing braces.") (defun org-make-link-regexps () "Update the link regular expressions. -This should be called after the variable `org-link-types' has changed." - (let ((types-re (regexp-opt org-link-types t))) +This should be called after the variable `org-link-parameters' has changed." + (let ((types-re (regexp-opt (org-link-types) t))) (setq org-link-types-re (concat "\\`" types-re ":") org-link-re-with-space @@ -5629,14 +5709,12 @@ This should be called after the variable `org-link-types' has changed." "\\([^" org-non-link-chars " ]" "[^\t\n\r]*\\)") org-angle-link-re - (concat "<" types-re ":" - "\\([^" org-non-link-chars " ]" - "[^" org-non-link-chars "]*" - "\\)>") + (format "<%s:\\([^>\n]*\\(?:\n[ \t]*[^> \t\n][^>\n]*\\)*\\)>" + types-re) org-plain-link-re (concat "\\<" types-re ":" - (org-re "\\([^ \t\n()<>]+\\(?:([[:word:]0-9_]+)\\|\\([^[:punct:] \t\n]\\|/\\)\\)\\)")) + "\\([^][ \t\n()<>]+\\(?:([[:word:]0-9_]+)\\|\\([^[:punct:] \t\n]\\|/\\)\\)\\)") ;; "\\([^]\t\n\r<>() ]+[^]\t\n\r<>,.;() ]\\)") org-bracket-link-regexp "\\[\\[\\([^][]+\\)\\]\\(\\[\\([^][]+\\)\\]\\)?\\]" @@ -5651,7 +5729,7 @@ This should be called after the variable `org-link-types' has changed." org-bracket-link-analytic-regexp++ (concat "\\[\\[" - "\\(" (regexp-opt (cons "coderef" org-link-types) t) ":\\)?" + "\\(" (regexp-opt (cons "coderef" (org-link-types)) t) ":\\)?" "\\([^]]+\\)" "\\]" "\\(\\[" "\\([^]]+\\)" "\\]\\)?" @@ -5663,67 +5741,53 @@ This should be called after the variable `org-link-types' has changed." (org-make-link-regexps) -(defconst org-ts-regexp "<\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} ?[^\r\n>]*?\\)>" - "Regular expression for fast time stamp matching.") -(defconst org-ts-regexp-both "[[<]\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} ?[^]\r\n>]*?\\)[]>]" - "Regular expression for fast time stamp matching.") -(defconst org-ts-regexp0 - "\\(\\([0-9]\\{4\\}\\)-\\([0-9]\\{2\\}\\)-\\([0-9]\\{2\\}\\)\\( +[^]+0-9>\r\n -]+\\)?\\( +\\([0-9]\\{1,2\\}\\):\\([0-9]\\{2\\}\\)\\)?\\)" - "Regular expression matching time strings for analysis. -This one does not require the space after the date, so it can be used -on a string that terminates immediately after the date.") -(defconst org-ts-regexp1 "\\(\\([0-9]\\{4\\}\\)-\\([0-9]\\{2\\}\\)-\\([0-9]\\{2\\}\\) *\\([^]+0-9>\r\n -]*\\)\\( \\([0-9]\\{1,2\\}\\):\\([0-9]\\{2\\}\\)\\)?\\)" - "Regular expression matching time strings for analysis.") -(defconst org-ts-regexp2 (concat "<" org-ts-regexp1 "[^>\n]\\{0,16\\}>") - "Regular expression matching time stamps, with groups.") -(defconst org-ts-regexp3 (concat "[[<]" org-ts-regexp1 "[^]>\n]\\{0,16\\}[]>]") - "Regular expression matching time stamps (also [..]), with groups.") -(defconst org-tr-regexp (concat org-ts-regexp "--?-?" org-ts-regexp) - "Regular expression matching a time stamp range.") -(defconst org-tr-regexp-both - (concat org-ts-regexp-both "--?-?" org-ts-regexp-both) - "Regular expression matching a time stamp range.") -(defconst org-tsr-regexp (concat org-ts-regexp "\\(--?-?" - org-ts-regexp "\\)?") - "Regular expression matching a time stamp or time stamp range.") -(defconst org-tsr-regexp-both - (concat org-ts-regexp-both "\\(--?-?" - org-ts-regexp-both "\\)?") - "Regular expression matching a time stamp or time stamp range. -The time stamps may be either active or inactive.") - (defvar org-emph-face nil) (defun org-do-emphasis-faces (limit) - "Run through the buffer and add overlays to emphasized strings." - (let (rtn a) - (while (and (not rtn) (re-search-forward org-emph-re limit t)) - (let* ((border (char-after (match-beginning 3))) - (bre (regexp-quote (char-to-string border)))) - (if (and (not (= border (char-after (match-beginning 4)))) - (not (save-match-data - (string-match (concat bre ".*" bre) - (replace-regexp-in-string - "\n" " " - (substring (match-string 2) 1 -1)))))) - (progn - (setq rtn t) - (setq a (assoc (match-string 3) org-emphasis-alist)) - (font-lock-prepend-text-property (match-beginning 2) (match-end 2) - 'face - (nth 1 a)) - (and (nth 2 a) - (org-remove-flyspell-overlays-in - (match-beginning 0) (match-end 0))) + "Run through the buffer and emphasize strings." + (let ((quick-re (format "\\([%s]\\|^\\)\\([~=*/_+]\\)" + (car org-emphasis-regexp-components)))) + (catch :exit + (while (re-search-forward quick-re limit t) + (let* ((marker (match-string 2)) + (verbatim? (member marker '("~" "=")))) + (when (save-excursion + (goto-char (match-beginning 0)) + (and + ;; Do not match table hlines. + (not (and (equal marker "+") + (org-match-line + "[ \t]*\\(|[-+]+|?\\|\\+[-+]+\\+\\)[ \t]*$"))) + ;; Do not match headline stars. Do not consider + ;; stars of a headline as closing marker for bold + ;; markup either. + (not (and (equal marker "*") + (save-excursion + (forward-char) + (skip-chars-backward "*") + (looking-at-p org-outline-regexp-bol)))) + ;; Match full emphasis markup regexp. + (looking-at (if verbatim? org-verbatim-re org-emph-re)) + ;; Do not span over paragraph boundaries. + (not (string-match-p org-element-paragraph-separate + (match-string 2))) + ;; 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))) + (font-lock-prepend-text-property + (match-beginning 2) (match-end 2) 'face face) + (when verbatim? + (org-remove-flyspell-overlays-in + (match-beginning 0) (match-end 0))) (add-text-properties (match-beginning 2) (match-end 2) '(font-lock-multiline t org-emphasis t)) (when org-hide-emphasis-markers (add-text-properties (match-end 4) (match-beginning 5) '(invisible org-link)) (add-text-properties (match-beginning 3) (match-end 3) - '(invisible org-link)))))) - (goto-char (1+ (match-beginning 0)))) - rtn)) + '(invisible org-link))) + (throw :exit t)))))))) (defun org-emphasize (&optional char) "Insert or change an emphasis, i.e. a font like bold or italic. @@ -5736,19 +5800,20 @@ If CHAR is not given (for example in an interactive call) it will be prompted for." (interactive) (let ((erc org-emphasis-regexp-components) - (prompt "") - (string "") beg end move c s) + (string "") beg end move s) (if (org-region-active-p) - (setq beg (region-beginning) end (region-end) + (setq beg (region-beginning) + end (region-end) string (buffer-substring beg end)) (setq move t)) (unless char (message "Emphasis marker or tag: [%s]" - (mapconcat (lambda(e) (car e)) org-emphasis-alist "")) + (mapconcat #'car org-emphasis-alist "")) (setq char (read-char-exclusive))) - (if (equal char ?\ ) - (setq s "" move nil) + (if (equal char ?\s) + (setq s "" + move nil) (unless (assoc (char-to-string char) org-emphasis-alist) (user-error "No such emphasis marker: \"%c\"" char)) (setq s (char-to-string char))) @@ -5757,7 +5822,7 @@ prompted for." (assoc (substring string 0 1) org-emphasis-alist)) (setq string (substring string 1 -1))) (setq string (concat s string s)) - (if beg (delete-region beg end)) + (when beg (delete-region beg end)) (unless (or (bolp) (string-match (concat "[" (nth 0 erc) "\n]") (char-to-string (char-before (point))))) @@ -5775,37 +5840,86 @@ prompted for." (defsubst org-rear-nonsticky-at (pos) (add-text-properties (1- pos) pos (list 'rear-nonsticky org-nonsticky-props))) -(defun org-activate-plain-links (limit) - "Run through the buffer and add overlays to links." - (let (f hl) - (when (and (re-search-forward (concat org-plain-link-re) limit t) - (not (org-in-src-block-p))) - (org-remove-flyspell-overlays-in (match-beginning 0) (match-end 0)) - (setq f (get-text-property (match-beginning 0) 'face)) - (setq hl (org-match-string-no-properties 0)) - (if (or (eq f 'org-tag) - (and (listp f) (memq 'org-tag f))) - nil - (add-text-properties (match-beginning 0) (match-end 0) - (list 'mouse-face 'highlight - 'face 'org-link - 'htmlize-link `(:uri ,hl) - 'keymap org-mouse-map)) - (org-rear-nonsticky-at (match-end 0))) - t))) +(defun org-activate-links (limit) + "Add link properties to links. +This includes angle, plain, and bracket links." + (catch :exit + (while (re-search-forward org-any-link-re limit t) + (let* ((start (match-beginning 0)) + (end (match-end 0)) + (style (cond ((eq ?< (char-after start)) 'angle) + ((eq ?\[ (char-after (1+ start))) 'bracket) + (t 'plain)))) + (when (and (memq style org-highlight-links) + ;; Do not confuse plain links with tags. + (not (and (eq style 'plain) + (let ((face (get-text-property + (max (1- start) (point-min)) 'face))) + (if (consp face) (memq 'org-tag face) + (eq 'org-tag face)))))) + (let* ((link-object (save-excursion + (goto-char start) + (save-match-data (org-element-link-parser)))) + (link (org-element-property :raw-link link-object)) + (type (org-element-property :type link-object)) + (path (org-element-property :path link-object)) + (properties ;for link's visible part + (list + 'face (pcase (org-link-get-parameter type :face) + ((and (pred functionp) face) (funcall face path)) + ((and (pred facep) face) face) + ((and (pred consp) face) face) ;anonymous + (_ 'org-link)) + 'mouse-face (or (org-link-get-parameter type :mouse-face) + 'highlight) + 'keymap (or (org-link-get-parameter type :keymap) + org-mouse-map) + 'help-echo (pcase (org-link-get-parameter type :help-echo) + ((and (pred stringp) echo) echo) + ((and (pred functionp) echo) echo) + (_ (concat "LINK: " link))) + 'htmlize-link (pcase (org-link-get-parameter type + :htmlize-link) + ((and (pred functionp) f) (funcall f)) + (_ `(:uri ,link))) + 'font-lock-multiline t))) + (org-remove-flyspell-overlays-in start end) + (org-rear-nonsticky-at end) + (if (not (eq 'bracket style)) + (add-text-properties start end properties) + ;; Handle invisible parts in bracket links. + (remove-text-properties start end '(invisible nil)) + (let ((hidden + (append `(invisible + ,(or (org-link-get-parameter type :display) + 'org-link)) + properties)) + (visible-start (or (match-beginning 4) (match-beginning 2))) + (visible-end (or (match-end 4) (match-end 2)))) + (add-text-properties start visible-start hidden) + (add-text-properties visible-start visible-end properties) + (add-text-properties visible-end end hidden) + (org-rear-nonsticky-at visible-start) + (org-rear-nonsticky-at visible-end))) + (let ((f (org-link-get-parameter type :activate-func))) + (when (functionp f) + (funcall f start end path (eq style 'bracket)))) + (throw :exit t))))) ;signal success + nil)) (defun org-activate-code (limit) - (if (re-search-forward "^[ \t]*\\(:\\(?: .*\\|$\\)\n?\\)" limit t) - (progn - (org-remove-flyspell-overlays-in (match-beginning 0) (match-end 0)) - (remove-text-properties (match-beginning 0) (match-end 0) - '(display t invisible t intangible t)) - t))) + (when (re-search-forward "^[ \t]*\\(:\\(?: .*\\|$\\)\n?\\)" limit t) + (org-remove-flyspell-overlays-in (match-beginning 0) (match-end 0)) + (remove-text-properties (match-beginning 0) (match-end 0) + '(display t invisible t intangible t)) + t)) -(defcustom org-src-fontify-natively nil - "When non-nil, fontify code in code blocks." +(defcustom org-src-fontify-natively t + "When non-nil, fontify code in code blocks. +See also the `org-block' face." :type 'boolean - :version "24.1" + :version "26.1" + :package-version '(Org . "8.3") :group 'org-appearance :group 'org-babel) @@ -5820,221 +5934,249 @@ by a #." (defun org-fontify-meta-lines-and-blocks (limit) (condition-case nil (org-fontify-meta-lines-and-blocks-1 limit) - (error (message "org-mode fontification error")))) + (error (message "org-mode fontification error in %S at %d" + (current-buffer) + (line-number-at-pos))))) (defun org-fontify-meta-lines-and-blocks-1 (limit) "Fontify #+ lines and blocks." (let ((case-fold-search t)) - (if (re-search-forward - "^\\([ \t]*#\\(\\(\\+[a-zA-Z]+:?\\| \\|$\\)\\(_\\([a-zA-Z]+\\)\\)?\\)[ \t]*\\(\\([^ \t\n]*\\)[ \t]*\\(.*\\)\\)\\)" - limit t) - (let ((beg (match-beginning 0)) - (block-start (match-end 0)) - (block-end nil) - (lang (match-string 7)) - (beg1 (line-beginning-position 2)) - (dc1 (downcase (match-string 2))) - (dc3 (downcase (match-string 3))) - end end1 quoting block-type ovl) - (cond - ((member dc1 '("+html:" "+ascii:" "+latex:")) - ;; a single line of backend-specific content - (org-remove-flyspell-overlays-in (match-beginning 0) (match-end 0)) - (remove-text-properties (match-beginning 0) (match-end 0) - '(display t invisible t intangible t)) - (add-text-properties (match-beginning 1) (match-end 3) - '(font-lock-fontified t face org-meta-line)) - (add-text-properties (match-beginning 6) (+ (match-end 6) 1) - '(font-lock-fontified t face org-block)) - ; for backend-specific code - t) - ((and (match-end 4) (equal dc3 "+begin")) - ;; Truly a block - (setq block-type (downcase (match-string 5)) - quoting (member block-type org-protecting-blocks)) - (when (re-search-forward - (concat "^[ \t]*#\\+end" (match-string 4) "\\>.*") - nil t) ;; on purpose, we look further than LIMIT - (setq end (min (point-max) (match-end 0)) - end1 (min (point-max) (1- (match-beginning 0)))) - (setq block-end (match-beginning 0)) - (when quoting - (remove-text-properties beg end - '(display t invisible t intangible t))) - (add-text-properties - beg end - '(font-lock-fontified t font-lock-multiline t)) - (add-text-properties beg beg1 '(face org-meta-line)) - (add-text-properties end1 (min (point-max) (1+ end)) - '(face org-meta-line)) ; for end_src - (cond - ((and lang (not (string= lang "")) org-src-fontify-natively) - (org-src-font-lock-fontify-block lang block-start block-end) - ;; remove old background overlays - (mapc (lambda (ov) - (if (eq (overlay-get ov 'face) 'org-block-background) - (delete-overlay ov))) - (overlays-at (/ (+ beg1 block-end) 2))) - ;; add a background overlay - (setq ovl (make-overlay beg1 block-end)) - (overlay-put ovl 'face 'org-block-background) - (overlay-put ovl 'evaporate t)) ;; make it go away when empty - (quoting - (add-text-properties beg1 (min (point-max) (1+ end1)) - '(face org-block))) ; end of source block - ((not org-fontify-quote-and-verse-blocks)) - ((string= block-type "quote") - (add-text-properties beg1 (min (point-max) (1+ end1)) '(face org-quote))) - ((string= block-type "verse") - (add-text-properties beg1 (min (point-max) (1+ end1)) '(face org-verse)))) - (add-text-properties beg beg1 '(face org-block-begin-line)) - (add-text-properties (min (point-max) (1+ end)) (min (point-max) (1+ end1)) - '(face org-block-end-line)) - t)) - ((member dc1 '("+title:" "+author:" "+email:" "+date:")) - (add-text-properties - beg (match-end 3) - (if (member (intern (substring dc1 1 -1)) org-hidden-keywords) - '(font-lock-fontified t invisible t) - '(font-lock-fontified t face org-document-info-keyword))) - (add-text-properties - (match-beginning 6) (min (point-max) (1+ (match-end 6))) - (if (string-equal dc1 "+title:") - '(font-lock-fontified t face org-document-title) - '(font-lock-fontified t face org-document-info)))) - ((or (equal dc1 "+results") - (member dc1 '("+begin:" "+end:" "+caption:" "+label:" - "+orgtbl:" "+tblfm:" "+tblname:" "+results:" - "+call:" "+header:" "+headers:" "+name:")) - (and (match-end 4) (equal dc3 "+attr"))) - (add-text-properties - beg (match-end 0) - '(font-lock-fontified t face org-meta-line)) - t) - ((member dc3 '(" " "")) - (add-text-properties - beg (match-end 0) - '(font-lock-fontified t face font-lock-comment-face))) - ((not (member (char-after beg) '(?\ ?\t))) - ;; just any other in-buffer setting, but not indented + (when (re-search-forward + "^\\([ \t]*#\\(\\(\\+[a-zA-Z]+:?\\| \\|$\\)\\(_\\([a-zA-Z]+\\)\\)?\\)[ \t]*\\(\\([^ \t\n]*\\)[ \t]*\\(.*\\)\\)\\)" + limit t) + (let ((beg (match-beginning 0)) + (block-start (match-end 0)) + (block-end nil) + (lang (match-string 7)) + (beg1 (line-beginning-position 2)) + (dc1 (downcase (match-string 2))) + (dc3 (downcase (match-string 3))) + end end1 quoting block-type) + (cond + ((and (match-end 4) (equal dc3 "+begin")) + ;; Truly a block + (setq block-type (downcase (match-string 5)) + quoting (member block-type org-protecting-blocks)) + (when (re-search-forward + (concat "^[ \t]*#\\+end" (match-string 4) "\\>.*") + nil t) ;; on purpose, we look further than LIMIT + (setq end (min (point-max) (match-end 0)) + end1 (min (point-max) (1- (match-beginning 0)))) + (setq block-end (match-beginning 0)) + (when quoting + (org-remove-flyspell-overlays-in beg1 end1) + (remove-text-properties beg end + '(display t invisible t intangible t))) (add-text-properties - beg (match-end 0) - '(font-lock-fontified t face org-meta-line)) - t) - (t nil)))))) - -(defun org-activate-angle-links (limit) - "Run through the buffer and add overlays to links." - (if (and (re-search-forward org-angle-link-re limit t) - (not (org-in-src-block-p))) - (progn - (org-remove-flyspell-overlays-in (match-beginning 0) (match-end 0)) - (add-text-properties (match-beginning 0) (match-end 0) - (list 'mouse-face 'highlight - 'keymap org-mouse-map)) - (org-rear-nonsticky-at (match-end 0)) - t))) + beg end '(font-lock-fontified t font-lock-multiline t)) + (add-text-properties beg beg1 '(face org-meta-line)) + (org-remove-flyspell-overlays-in beg beg1) + (add-text-properties ; For end_src + end1 (min (point-max) (1+ end)) '(face org-meta-line)) + (org-remove-flyspell-overlays-in end1 end) + (cond + ((and lang (not (string= lang "")) org-src-fontify-natively) + (org-src-font-lock-fontify-block lang block-start block-end) + (add-text-properties beg1 block-end '(src-block t))) + (quoting + (add-text-properties beg1 (min (point-max) (1+ end1)) + (list 'face + (list :inherit + (let ((face-name + (intern (format "org-block-%s" lang)))) + (append (and (facep face-name) (list face-name)) + '(org-block))))))) ; end of source block + ((not org-fontify-quote-and-verse-blocks)) + ((string= block-type "quote") + (add-face-text-property + beg1 (min (point-max) (1+ end1)) 'org-quote t)) + ((string= block-type "verse") + (add-face-text-property + beg1 (min (point-max) (1+ end1)) 'org-verse t))) + (add-text-properties beg beg1 '(face org-block-begin-line)) + (add-text-properties (min (point-max) (1+ end)) (min (point-max) (1+ end1)) + '(face org-block-end-line)) + t)) + ((member dc1 '("+title:" "+author:" "+email:" "+date:")) + (org-remove-flyspell-overlays-in + (match-beginning 0) + (if (equal "+title:" dc1) (match-end 2) (match-end 0))) + (add-text-properties + beg (match-end 3) + (if (member (intern (substring dc1 1 -1)) org-hidden-keywords) + '(font-lock-fontified t invisible t) + '(font-lock-fontified t face org-document-info-keyword))) + (add-text-properties + (match-beginning 6) (min (point-max) (1+ (match-end 6))) + (if (string-equal dc1 "+title:") + '(font-lock-fontified t face org-document-title) + '(font-lock-fontified t face org-document-info)))) + ((string-prefix-p "+caption" dc1) + (org-remove-flyspell-overlays-in (match-end 2) (match-end 0)) + (remove-text-properties (match-beginning 0) (match-end 0) + '(display t invisible t intangible t)) + ;; Handle short captions. + (save-excursion + (beginning-of-line) + (looking-at "\\([ \t]*#\\+caption\\(?:\\[.*\\]\\)?:\\)[ \t]*")) + (add-text-properties (line-beginning-position) (match-end 1) + '(font-lock-fontified t face org-meta-line)) + (add-text-properties (match-end 0) (line-end-position) + '(font-lock-fontified t face org-block)) + t) + ((member dc3 '(" " "")) + (org-remove-flyspell-overlays-in beg (match-end 0)) + (add-text-properties + beg (match-end 0) + '(font-lock-fontified t face font-lock-comment-face))) + (t ;; just any other in-buffer setting, but not indented + (org-remove-flyspell-overlays-in (match-beginning 0) (match-end 0)) + (remove-text-properties (match-beginning 0) (match-end 0) + '(display t invisible t intangible t)) + (add-text-properties beg (match-end 0) + '(font-lock-fontified t face org-meta-line)) + t)))))) + +(defun org-fontify-drawers (limit) + "Fontify drawers." + (when (re-search-forward org-drawer-regexp limit t) + (add-text-properties + (match-beginning 0) (match-end 0) + '(font-lock-fontified t face org-special-keyword)) + (org-remove-flyspell-overlays-in (match-beginning 0) (match-end 0)) + t)) + +(defun org-fontify-macros (limit) + "Fontify macros." + (when (re-search-forward "\\({{{\\).+?\\(}}}\\)" limit t) + (add-text-properties + (match-beginning 0) (match-end 0) + '(font-lock-fontified t face org-macro)) + (when org-hide-macro-markers + (add-text-properties (match-end 2) (match-beginning 2) + '(invisible t)) + (add-text-properties (match-beginning 1) (match-end 1) + '(invisible t))) + (org-remove-flyspell-overlays-in (match-beginning 0) (match-end 0)) + t)) (defun org-activate-footnote-links (limit) - "Run through the buffer and add overlays to footnotes." + "Add text properties for footnotes." (let ((fn (org-footnote-next-reference-or-definition limit))) (when fn - (let ((beg (nth 1 fn)) (end (nth 2 fn))) - (org-remove-flyspell-overlays-in beg end) + (let* ((beg (nth 1 fn)) + (end (nth 2 fn)) + (label (car fn)) + (referencep (/= (line-beginning-position) beg))) + (when (and referencep (nth 3 fn)) + (save-excursion + (goto-char beg) + (search-forward (or label "fn:")) + (org-remove-flyspell-overlays-in beg (match-end 0)))) (add-text-properties beg end (list 'mouse-face 'highlight 'keymap org-mouse-map 'help-echo - (if (= (point-at-bol) beg) - "Footnote definition" - "Footnote reference") + (if referencep "Footnote reference" + "Footnote definition") 'font-lock-fontified t 'font-lock-multiline t 'face 'org-footnote)))))) -(defun org-activate-bracket-links (limit) - "Run through the buffer and add overlays to bracketed links." - (if (and (re-search-forward org-bracket-link-regexp limit t) - (not (org-in-src-block-p))) - (let* ((hl (org-match-string-no-properties 1)) - (help (concat "LINK: " (save-match-data (org-link-unescape hl)))) - (ip (org-maybe-intangible - (list 'invisible 'org-link - 'keymap org-mouse-map 'mouse-face 'highlight - 'font-lock-multiline t 'help-echo help - 'htmlize-link `(:uri ,hl)))) - (vp (list 'keymap org-mouse-map 'mouse-face 'highlight - 'font-lock-multiline t 'help-echo help - 'htmlize-link `(:uri ,hl)))) - ;; We need to remove the invisible property here. Table narrowing - ;; may have made some of this invisible. - (org-remove-flyspell-overlays-in (match-beginning 0) (match-end 0)) - (remove-text-properties (match-beginning 0) (match-end 0) - '(invisible nil)) - (if (match-end 3) - (progn - (add-text-properties (match-beginning 0) (match-beginning 3) ip) - (org-rear-nonsticky-at (match-beginning 3)) - (add-text-properties (match-beginning 3) (match-end 3) vp) - (org-rear-nonsticky-at (match-end 3)) - (add-text-properties (match-end 3) (match-end 0) ip) - (org-rear-nonsticky-at (match-end 0))) - (add-text-properties (match-beginning 0) (match-beginning 1) ip) - (org-rear-nonsticky-at (match-beginning 1)) - (add-text-properties (match-beginning 1) (match-end 1) vp) - (org-rear-nonsticky-at (match-end 1)) - (add-text-properties (match-end 1) (match-end 0) ip) - (org-rear-nonsticky-at (match-end 0))) - t))) - (defun org-activate-dates (limit) - "Run through the buffer and add overlays to dates." - (if (and (re-search-forward org-tsr-regexp-both limit t) - (not (equal (char-before (match-beginning 0)) 91))) - (progn - (org-remove-flyspell-overlays-in (match-beginning 0) (match-end 0)) - (add-text-properties (match-beginning 0) (match-end 0) - (list 'mouse-face 'highlight - 'keymap org-mouse-map)) - (org-rear-nonsticky-at (match-end 0)) - (when org-display-custom-times - (if (match-end 3) - (org-display-custom-time (match-beginning 3) (match-end 3))) - (org-display-custom-time (match-beginning 1) (match-end 1))) - t))) - -(defvar org-target-link-regexp nil + "Add text properties for dates." + (when (and (re-search-forward org-tsr-regexp-both limit t) + (not (equal (char-before (match-beginning 0)) 91))) + (org-remove-flyspell-overlays-in (match-beginning 0) (match-end 0)) + (add-text-properties (match-beginning 0) (match-end 0) + (list 'mouse-face 'highlight + 'keymap org-mouse-map)) + (org-rear-nonsticky-at (match-end 0)) + (when org-display-custom-times + ;; If it's a date range, activate custom time for second date. + (when (match-end 3) + (org-display-custom-time (match-beginning 3) (match-end 3))) + (org-display-custom-time (match-beginning 1) (match-end 1))) + t)) + +(defvar-local org-target-link-regexp nil "Regular expression matching radio targets in plain text.") -(make-variable-buffer-local 'org-target-link-regexp) -(defvar org-target-regexp "<<\\([^<>\n\r]+\\)>>" + +(defconst org-target-regexp (let ((border "[^<>\n\r \t]")) + (format "<<\\(%s\\|%s[^<>\n\r]*%s\\)>>" + border border border)) "Regular expression matching a link target.") -(defvar org-radio-target-regexp "<<<\\([^<>\n\r]+\\)>>>" + +(defconst org-radio-target-regexp (format "<%s>" org-target-regexp) "Regular expression matching a radio target.") -(defvar org-any-target-regexp "<<<?\\([^<>\n\r]+\\)>>>?" ; FIXME, not exact, would match <<<aaa>> as a radio target. + +(defconst org-any-target-regexp + (format "%s\\|%s" org-radio-target-regexp org-target-regexp) "Regular expression matching any target.") (defun org-activate-target-links (limit) - "Run through the buffer and add overlays to target matches." + "Add text properties for target matches." (when org-target-link-regexp (let ((case-fold-search t)) - (if (re-search-forward org-target-link-regexp limit t) - (progn - (org-remove-flyspell-overlays-in (match-beginning 0) (match-end 0)) - (add-text-properties (match-beginning 0) (match-end 0) - (list 'mouse-face 'highlight - 'keymap org-mouse-map - 'help-echo "Radio target link" - 'org-linked-text t)) - (org-rear-nonsticky-at (match-end 0)) - t))))) + (when (re-search-forward org-target-link-regexp limit t) + (org-remove-flyspell-overlays-in (match-beginning 1) (match-end 1)) + (add-text-properties (match-beginning 1) (match-end 1) + (list 'mouse-face 'highlight + 'keymap org-mouse-map + 'help-echo "Radio target link" + 'org-linked-text t)) + (org-rear-nonsticky-at (match-end 1)) + t)))) (defun org-update-radio-target-regexp () - "Find all radio targets in this file and update the regular expression." + "Find all radio targets in this file and update the regular expression. +Also refresh fontification if needed." (interactive) - (when (memq 'radio org-activate-links) + (let ((old-regexp org-target-link-regexp) + (before-re "\\(?:^\\|[^[:alnum:]]\\)\\(") + (after-re "\\)\\(?:$\\|[^[:alnum:]]\\)") + (targets + (org-with-wide-buffer + (goto-char (point-min)) + (let (rtn) + (while (re-search-forward org-radio-target-regexp nil t) + ;; Make sure point is really within the object. + (backward-char) + (let ((obj (org-element-context))) + (when (eq (org-element-type obj) 'radio-target) + (cl-pushnew (org-element-property :value obj) rtn + :test #'equal)))) + rtn)))) (setq org-target-link-regexp - (org-make-target-link-regexp (org-all-targets 'radio))) - (org-restart-font-lock))) + (and targets + (concat before-re + (mapconcat + (lambda (x) + (replace-regexp-in-string + " +" "\\s-+" (regexp-quote x) t t)) + targets + "\\|") + after-re))) + (unless (equal old-regexp org-target-link-regexp) + ;; Clean-up cache. + (let ((regexp (cond ((not old-regexp) org-target-link-regexp) + ((not org-target-link-regexp) old-regexp) + (t + (concat before-re + (mapconcat + (lambda (re) + (substring re (length before-re) + (- (length after-re)))) + (list old-regexp org-target-link-regexp) + "\\|") + after-re))))) + (org-with-wide-buffer + (goto-char (point-min)) + (while (re-search-forward regexp nil t) + (org-element-cache-refresh (match-beginning 1))))) + ;; Re fontify buffer. + (when (memq 'radio org-highlight-links) + (org-restart-font-lock))))) (defun org-hide-wide-columns (limit) (let (s e) @@ -6042,20 +6184,18 @@ by a #." 'org-cwidth t)) (when s (setq e (next-single-property-change s 'org-cwidth)) - (add-text-properties s e (org-maybe-intangible '(invisible org-cwidth))) + (add-text-properties s e '(invisible org-cwidth)) (goto-char e) t))) (defvar org-latex-and-related-regexp nil "Regular expression for highlighting LaTeX, entities and sub/superscript.") -(defvar org-match-substring-regexp) -(defvar org-match-substring-with-braces-regexp) (defun org-compute-latex-and-related-regexp () "Compute regular expression for LaTeX, entities and sub/superscript. Result depends on variable `org-highlight-latex-and-related'." - (org-set-local - 'org-latex-and-related-regexp + (setq-local + org-latex-and-related-regexp (let* ((re-sub (cond ((not (memq 'script org-highlight-latex-and-related)) nil) ((eq org-use-sub-superscripts '{}) @@ -6081,9 +6221,13 @@ done, nil otherwise." (when (org-string-nw-p org-latex-and-related-regexp) (catch 'found (while (re-search-forward org-latex-and-related-regexp limit t) - (unless (memq (car-safe (get-text-property (1+ (match-beginning 0)) - 'face)) - '(org-code org-verbatim underline)) + (unless + (cl-some + (lambda (f) + (memq f '(org-code org-verbatim underline org-special-keyword))) + (save-excursion + (goto-char (1+ (match-beginning 0))) + (face-at-point nil t))) (let ((offset (if (memq (char-after (1+ (match-beginning 0))) '(?_ ?^)) 1 @@ -6102,63 +6246,32 @@ done, nil otherwise." (font-lock-mode -1) (font-lock-mode 1))) -(defun org-all-targets (&optional radio) - "Return a list of all targets in this file. -When optional argument RADIO is non-nil, only find radio -targets." - (let ((re (if radio org-radio-target-regexp org-target-regexp)) rtn) - (save-excursion - (goto-char (point-min)) - (while (re-search-forward re nil t) - ;; Make sure point is really within the object. - (backward-char) - (let ((obj (org-element-context))) - (when (memq (org-element-type obj) '(radio-target target)) - (add-to-list 'rtn (downcase (org-element-property :value obj)))))) - rtn))) - -(defun org-make-target-link-regexp (targets) - "Make regular expression matching all strings in TARGETS. -The regular expression finds the targets also if there is a line break -between words." - (and targets - (concat - "\\_<\\(" - (mapconcat - (lambda (x) - (setq x (regexp-quote x)) - (while (string-match " +" x) - (setq x (replace-match "\\s-+" t t x))) - x) - targets - "\\|") - "\\)\\_>"))) - (defun org-activate-tags (limit) - (if (re-search-forward (org-re "^\\*+.*[ \t]\\(:[[:alnum:]_@#%:]+:\\)[ \r\n]") limit t) - (progn - (org-remove-flyspell-overlays-in (match-beginning 1) (match-end 1)) - (add-text-properties (match-beginning 1) (match-end 1) - (list 'mouse-face 'highlight - 'keymap org-mouse-map)) - (org-rear-nonsticky-at (match-end 1)) - t))) + (when (re-search-forward + "^\\*+.*[ \t]\\(:[[:alnum:]_@#%:]+:\\)[ \t]*$" limit t) + (org-remove-flyspell-overlays-in (match-beginning 1) (match-end 1)) + (add-text-properties (match-beginning 1) (match-end 1) + (list 'mouse-face 'highlight + 'keymap org-mouse-map)) + (org-rear-nonsticky-at (match-end 1)) + t)) (defun org-outline-level () "Compute the outline level of the heading at point. -If this is called at a normal headline, the level is the number of stars. -Use `org-reduced-level' to remove the effect of `org-odd-levels'." - (save-excursion - (if (not (condition-case nil - (org-back-to-heading t) - (error nil))) - 0 - (looking-at org-outline-regexp) - (1- (- (match-end 0) (match-beginning 0)))))) + +If this is called at a normal headline, the level is the number +of stars. Use `org-reduced-level' to remove the effect of +`org-odd-levels'. Unlike to `org-current-level', this function +takes into consideration inlinetasks." + (org-with-wide-buffer + (end-of-line) + (if (re-search-backward org-outline-regexp-bol nil t) + (1- (- (match-end 0) (match-beginning 0))) + 0))) (defvar org-font-lock-keywords nil) -(defsubst org-re-property (property &optional literal allow-null) +(defsubst org-re-property (property &optional literal allow-null value) "Return a regexp matching a PROPERTY line. When optional argument LITERAL is non-nil, do not quote PROPERTY. @@ -6166,17 +6279,25 @@ This is useful when PROPERTY is a regexp. When ALLOW-NULL is non-nil, match properties even without a value. Match group 3 is set to the value when it exists. If there is no -value and ALLOW-NULL is non-nil, it is set to the empty string." +value and ALLOW-NULL is non-nil, it is set to the empty string. + +With optional argument VALUE, match only property lines with +that value; in this case, ALLOW-NULL is ignored. VALUE is quoted +unless LITERAL is non-nil." (concat "^\\(?4:[ \t]*\\)" (format "\\(?1::\\(?2:%s\\):\\)" (if literal property (regexp-quote property))) - (if allow-null - "\\(?:\\(?3:$\\)\\|[ \t]+\\(?3:.*?\\)\\)\\(?5:[ \t]*\\)$" - "[ \t]+\\(?3:[^ \r\t\n]+.*?\\)\\(?5:[ \t]*\\)$"))) + (cond (value + (format "[ \t]+\\(?3:%s\\)\\(?5:[ \t]*\\)$" + (if literal value (regexp-quote value)))) + (allow-null + "\\(?:\\(?3:$\\)\\|[ \t]+\\(?3:.*?\\)\\)\\(?5:[ \t]*\\)$") + (t + "[ \t]+\\(?3:[^ \r\t\n]+.*?\\)\\(?5:[ \t]*\\)$")))) (defconst org-property-re - (org-re-property ".*?" 'literal t) + (org-re-property "\\S-+" 'literal t) "Regular expression matching a property line. There are four matching groups: 1: :PROPKEY: including the leading and trailing colon, @@ -6188,6 +6309,8 @@ There are four matching groups: (defvar org-font-lock-hook nil "Functions to be called for special font lock stuff.") +(defvar org-font-lock-extra-keywords nil) ;Dynamically scoped. + (defvar org-font-lock-set-keywords-hook nil "Functions that can manipulate `org-font-lock-extra-keywords'. This is called after `org-font-lock-extra-keywords' is defined, but before @@ -6201,7 +6324,7 @@ needs to be inserted at a specific position in the font-lock sequence.") (defun org-set-font-lock-defaults () "Set font lock defaults for the current buffer." (let* ((em org-fontify-emphasized-text) - (lk org-activate-links) + (lk org-highlight-links) (org-font-lock-extra-keywords (list ;; Call the hook @@ -6222,26 +6345,23 @@ needs to be inserted at a specific position in the font-lock sequence.") '("^[ \t]*|\\( *\\([$!_^/]\\) *|.*\\)|" (1 'org-formula t)) '("| *\\(<[lrc]?[0-9]*>\\)" (1 'org-formula t)) ;; Drawers - (list org-drawer-regexp '(0 'org-special-keyword t)) - (list "^[ \t]*:END:" '(0 'org-special-keyword t)) + '(org-fontify-drawers) ;; Properties (list org-property-re '(1 'org-special-keyword t) '(3 'org-property-value t)) - ;; Links - (if (memq 'tag lk) '(org-activate-tags (1 'org-tag prepend))) - (if (memq 'angle lk) '(org-activate-angle-links (0 'org-link t))) - (if (memq 'plain lk) '(org-activate-plain-links (0 'org-link t))) - (if (memq 'bracket lk) '(org-activate-bracket-links (0 'org-link t))) - (if (memq 'radio lk) '(org-activate-target-links (0 'org-link t))) - (if (memq 'date lk) '(org-activate-dates (0 'org-date t))) - (if (memq 'footnote lk) '(org-activate-footnote-links)) + ;; Link related fontification. + '(org-activate-links) + (when (memq 'tag lk) '(org-activate-tags (1 'org-tag prepend))) + (when (memq 'radio lk) '(org-activate-target-links (1 'org-link t))) + (when (memq 'date lk) '(org-activate-dates (0 'org-date t))) + (when (memq 'footnote lk) '(org-activate-footnote-links)) ;; Targets. (list org-any-target-regexp '(0 'org-target t)) ;; Diary sexps. '("^&?%%(.*\\|<%%([^>\n]*?>" (0 'org-sexp-date t)) ;; Macro - '("{{{.+}}}" (0 'org-macro t)) + '(org-fontify-macros) '(org-hide-wide-columns (0 nil append)) ;; TODO keyword (list (format org-heading-keyword-regexp-format @@ -6261,27 +6381,24 @@ needs to be inserted at a specific position in the font-lock sequence.") ;; Tags '(org-font-lock-add-tag-faces) ;; Tags groups - (if (and org-group-tags org-tag-groups-alist) - (list (concat org-outline-regexp-bol ".+\\(:" - (regexp-opt (mapcar 'car org-tag-groups-alist)) - ":\\).*$") - '(1 'org-tag-group prepend))) + (when (and org-group-tags org-tag-groups-alist) + (list (concat org-outline-regexp-bol ".+\\(:" + (regexp-opt (mapcar 'car org-tag-groups-alist)) + ":\\).*$") + '(1 'org-tag-group prepend))) ;; Special keywords (list (concat "\\<" org-deadline-string) '(0 'org-special-keyword t)) (list (concat "\\<" org-scheduled-string) '(0 'org-special-keyword t)) (list (concat "\\<" org-closed-string) '(0 'org-special-keyword t)) (list (concat "\\<" org-clock-string) '(0 'org-special-keyword t)) ;; Emphasis - (if em - (if (featurep 'xemacs) - '(org-do-emphasis-faces (0 nil append)) - '(org-do-emphasis-faces))) + (when em '(org-do-emphasis-faces)) ;; Checkboxes '("^[ \t]*\\(?:[-+*]\\|[0-9]+[.)]\\)[ \t]+\\(?:\\[@\\(?:start:\\)?[0-9]+\\][ \t]*\\)?\\(\\[[- X]\\]\\)" 1 'org-checkbox prepend) - (if (cdr (assq 'checkbox org-list-automatic-rules)) - '("\\[\\([0-9]*%\\)\\]\\|\\[\\([0-9]*\\)/\\([0-9]*\\)\\]" - (0 (org-get-checkbox-statistics-face) t))) + (when (cdr (assq 'checkbox org-list-automatic-rules)) + '("\\[\\([0-9]*%\\)\\]\\|\\[\\([0-9]*\\)/\\([0-9]*\\)\\]" + (0 (org-get-checkbox-statistics-face) t))) ;; Description list items '("^[ \t]*[-+*][ \t]+\\(.*?[ \t]+::\\)\\([ \t]+\\|$\\)" 1 'org-list-dt prepend) @@ -6297,83 +6414,92 @@ needs to be inserted at a specific position in the font-lock sequence.") ;; Code '(org-activate-code (1 'org-code t)) ;; COMMENT - (list (format org-heading-keyword-regexp-format - (concat "\\(" - org-comment-string "\\|" org-quote-string - "\\)")) - '(2 'org-special-keyword t)) + (list (format + "^\\*+\\(?: +%s\\)?\\(?: +\\[#[A-Z0-9]\\]\\)? +\\(?9:%s\\)\\(?: \\|$\\)" + org-todo-regexp + org-comment-string) + '(9 'org-special-keyword t)) ;; Blocks and meta lines '(org-fontify-meta-lines-and-blocks)))) (setq org-font-lock-extra-keywords (delq nil org-font-lock-extra-keywords)) (run-hooks 'org-font-lock-set-keywords-hook) ;; Now set the full font-lock-keywords - (org-set-local 'org-font-lock-keywords org-font-lock-extra-keywords) - (org-set-local 'font-lock-defaults - '(org-font-lock-keywords t nil nil backward-paragraph)) - (kill-local-variable 'font-lock-keywords) nil)) + (setq-local org-font-lock-keywords org-font-lock-extra-keywords) + (setq-local font-lock-defaults + '(org-font-lock-keywords t nil nil backward-paragraph)) + (kill-local-variable 'font-lock-keywords) + nil)) (defun org-toggle-pretty-entities () "Toggle the composition display of entities as UTF8 characters." (interactive) - (org-set-local 'org-pretty-entities (not org-pretty-entities)) + (setq-local org-pretty-entities (not org-pretty-entities)) (org-restart-font-lock) (if org-pretty-entities (message "Entities are now displayed as UTF8 characters") (save-restriction (widen) - (org-decompose-region (point-min) (point-max)) + (decompose-region (point-min) (point-max)) (message "Entities are now displayed as plain text")))) -(defvar org-custom-properties-overlays nil +(defvar-local org-custom-properties-overlays nil "List of overlays used for custom properties.") -(make-variable-buffer-local 'org-custom-properties-overlays) (defun org-toggle-custom-properties-visibility () "Display or hide properties in `org-custom-properties'." (interactive) (if org-custom-properties-overlays - (progn (mapc 'delete-overlay org-custom-properties-overlays) + (progn (mapc #'delete-overlay org-custom-properties-overlays) (setq org-custom-properties-overlays nil)) - (unless (not org-custom-properties) - (save-excursion - (save-restriction - (widen) - (goto-char (point-min)) - (while (re-search-forward org-property-re nil t) - (mapc (lambda(p) - (when (equal p (substring (match-string 1) 1 -1)) - (let ((o (make-overlay (match-beginning 0) (1+ (match-end 0))))) - (overlay-put o 'invisible t) - (overlay-put o 'org-custom-property t) - (push o org-custom-properties-overlays)))) - org-custom-properties))))))) + (when org-custom-properties + (org-with-wide-buffer + (goto-char (point-min)) + (let ((regexp (org-re-property (regexp-opt org-custom-properties) t t))) + (while (re-search-forward regexp nil t) + (let ((end (cdr (save-match-data (org-get-property-block))))) + (when (and end (< (point) end)) + ;; Hide first custom property in current drawer. + (let ((o (make-overlay (match-beginning 0) (1+ (match-end 0))))) + (overlay-put o 'invisible t) + (overlay-put o 'org-custom-property t) + (push o org-custom-properties-overlays)) + ;; Hide additional custom properties in the same drawer. + (while (re-search-forward regexp end t) + (let ((o (make-overlay (match-beginning 0) (1+ (match-end 0))))) + (overlay-put o 'invisible t) + (overlay-put o 'org-custom-property t) + (push o org-custom-properties-overlays))))) + ;; Each entry is limited to a single property drawer. + (outline-next-heading))))))) (defun org-fontify-entities (limit) "Find an entity to fontify." (let (ee) (when org-pretty-entities (catch 'match + ;; "\_ "-family is left out on purpose. Only the first one, + ;; i.e., "\_ ", could be fontified anyway, and it would be + ;; confusing when adding a second white space character. (while (re-search-forward "\\\\\\(there4\\|sup[123]\\|frac[13][24]\\|[a-zA-Z]+\\)\\($\\|{}\\|[^[:alpha:]\n]\\)" limit t) - (if (and (not (org-in-indented-comment-line)) - (setq ee (org-entity-get (match-string 1))) - (= (length (nth 6 ee)) 1)) - (let* - ((end (if (equal (match-string 2) "{}") + (when (and (not (org-at-comment-p)) + (setq ee (org-entity-get (match-string 1))) + (= (length (nth 6 ee)) 1)) + (let* ((end (if (equal (match-string 2) "{}") (match-end 2) (match-end 1)))) - (add-text-properties - (match-beginning 0) end - (list 'font-lock-fontified t)) - (compose-region (match-beginning 0) end - (nth 6 ee) nil) - (backward-char 1) - (throw 'match t)))) + (add-text-properties + (match-beginning 0) end + (list 'font-lock-fontified t)) + (compose-region (match-beginning 0) end + (nth 6 ee) nil) + (backward-char 1) + (throw 'match t)))) nil)))) (defun org-fontify-like-in-org-mode (s &optional odd-levels) - "Fontify string S like in Org-mode." + "Fontify string S like in Org mode." (with-temp-buffer (insert s) (let ((org-odd-levels-only odd-levels)) @@ -6387,33 +6513,55 @@ needs to be inserted at a specific position in the font-lock sequence.") (defun org-get-level-face (n) "Get the right face for match N in font-lock matching of headlines." (setq org-l (- (match-end 2) (match-beginning 1) 1)) - (if org-odd-levels-only (setq org-l (1+ (/ org-l 2)))) + (when org-odd-levels-only (setq org-l (1+ (/ org-l 2)))) (if org-cycle-level-faces (setq org-f (nth (% (1- org-l) org-n-level-faces) org-level-faces)) (setq org-f (nth (1- (min org-l org-n-level-faces)) org-level-faces))) (cond ((eq n 1) (if org-hide-leading-stars 'org-hide org-f)) ((eq n 2) org-f) - (t (if org-level-color-stars-only nil org-f)))) + (t (unless org-level-color-stars-only org-f)))) +(defun org-face-from-face-or-color (context inherit face-or-color) + "Create a face list that inherits INHERIT, but sets the foreground color. +When FACE-OR-COLOR is not a string, just return it." + (if (stringp face-or-color) + (list :inherit inherit + (cdr (assoc context org-faces-easy-properties)) + face-or-color) + face-or-color)) (defun org-get-todo-face (kwd) "Get the right face for a TODO keyword KWD. If KWD is a number, get the corresponding match group." - (if (numberp kwd) (setq kwd (match-string kwd))) + (when (numberp kwd) (setq kwd (match-string kwd))) (or (org-face-from-face-or-color 'todo 'org-todo (cdr (assoc kwd org-todo-keyword-faces))) (and (member kwd org-done-keywords) 'org-done) 'org-todo)) -(defun org-face-from-face-or-color (context inherit face-or-color) - "Create a face list that inherits INHERIT, but sets the foreground color. -When FACE-OR-COLOR is not a string, just return it." - (if (stringp face-or-color) - (list :inherit inherit - (cdr (assoc context org-faces-easy-properties)) - face-or-color) - face-or-color)) +(defun org-get-priority-face (priority) + "Get the right face for PRIORITY. +PRIORITY is a character." + (or (org-face-from-face-or-color + 'priority 'org-priority (cdr (assq priority org-priority-faces))) + 'org-priority)) + +(defun org-get-tag-face (tag) + "Get the right face for TAG. +If TAG is a number, get the corresponding match group." + (let ((tag (if (wholenump tag) (match-string tag) tag))) + (or (org-face-from-face-or-color + 'tag 'org-tag (cdr (assoc tag org-tag-faces))) + 'org-tag))) + +(defun org-font-lock-add-priority-faces (limit) + "Add the special priority faces." + (while (re-search-forward "^\\*+ .*?\\(\\[#\\(.\\)\\]\\)" limit t) + (add-text-properties + (match-beginning 1) (match-end 1) + (list 'face (org-get-priority-face (string-to-char (match-string 2))) + 'font-lock-fontified t)))) (defun org-font-lock-add-tag-faces (limit) "Add the special tag faces." @@ -6424,39 +6572,18 @@ When FACE-OR-COLOR is not a string, just return it." 'font-lock-fontified t)) (backward-char 1)))) -(defun org-font-lock-add-priority-faces (limit) - "Add the special priority faces." - (while (re-search-forward "\\[#\\([A-Z0-9]\\)\\]" limit t) - (when (save-match-data (org-at-heading-p)) - (add-text-properties - (match-beginning 0) (match-end 0) - (list 'face (or (org-face-from-face-or-color - 'priority 'org-priority - (cdr (assoc (char-after (match-beginning 1)) - org-priority-faces))) - 'org-priority) - 'font-lock-fontified t))))) - -(defun org-get-tag-face (kwd) - "Get the right face for a TODO keyword KWD. -If KWD is a number, get the corresponding match group." - (if (numberp kwd) (setq kwd (match-string kwd))) - (or (org-face-from-face-or-color - 'tag 'org-tag (cdr (assoc kwd org-tag-faces))) - 'org-tag)) - -(defun org-unfontify-region (beg end &optional maybe_loudly) +(defun org-unfontify-region (beg end &optional _maybe_loudly) "Remove fontification and activation overlays from links." (font-lock-default-unfontify-region beg end) (let* ((buffer-undo-list t) (inhibit-read-only t) (inhibit-point-motion-hooks t) (inhibit-modification-hooks t) deactivate-mark buffer-file-name buffer-file-truename) - (org-decompose-region beg end) + (decompose-region beg end) (remove-text-properties beg end '(mouse-face t keymap t org-linked-text t invisible t intangible t - org-no-flyspell t org-emphasis t)) + org-emphasis t)) (org-remove-font-lock-display-properties beg end))) (defconst org-script-display '(((raise -0.3) (height 0.7)) @@ -6473,59 +6600,52 @@ and subscripts." (while (< beg end) (setq next (next-single-property-change beg 'display nil end) prop (get-text-property beg 'display)) - (if (member prop org-script-display) - (put-text-property beg next 'display nil)) + (when (member prop org-script-display) + (put-text-property beg next 'display nil)) (setq beg next)))) (defun org-raise-scripts (limit) "Add raise properties to sub/superscripts." - (when (and org-pretty-entities org-pretty-entities-include-sub-superscripts) - (if (re-search-forward - (if (eq org-use-sub-superscripts t) - org-match-substring-regexp - org-match-substring-with-braces-regexp) - limit t) - (let* ((pos (point)) table-p comment-p - (mpos (match-beginning 3)) - (emph-p (get-text-property mpos 'org-emphasis)) - (link-p (get-text-property mpos 'mouse-face)) - (keyw-p (eq 'org-special-keyword (get-text-property mpos 'face)))) - (goto-char (point-at-bol)) - (setq table-p (org-looking-at-p org-table-dataline-regexp) - comment-p (org-looking-at-p "^[ \t]*#[ +]")) - (goto-char pos) - ;; Handle a_b^c - (if (member (char-after) '(?_ ?^)) (goto-char (1- pos))) - (if (or comment-p emph-p link-p keyw-p) - t - (put-text-property (match-beginning 3) (match-end 0) - 'display - (if (equal (char-after (match-beginning 2)) ?^) - (nth (if table-p 3 1) org-script-display) - (nth (if table-p 2 0) org-script-display))) - (add-text-properties (match-beginning 2) (match-end 2) - (list 'invisible t - 'org-dwidth t 'org-dwidth-n 1)) - (if (and (eq (char-after (match-beginning 3)) ?{) - (eq (char-before (match-end 3)) ?})) - (progn - (add-text-properties - (match-beginning 3) (1+ (match-beginning 3)) - (list 'invisible t 'org-dwidth t 'org-dwidth-n 1)) - (add-text-properties - (1- (match-end 3)) (match-end 3) - (list 'invisible t 'org-dwidth t 'org-dwidth-n 1)))) - t))))) + (when (and org-pretty-entities org-pretty-entities-include-sub-superscripts + (re-search-forward + (if (eq org-use-sub-superscripts t) + org-match-substring-regexp + org-match-substring-with-braces-regexp) + limit t)) + (let* ((pos (point)) table-p comment-p + (mpos (match-beginning 3)) + (emph-p (get-text-property mpos 'org-emphasis)) + (link-p (get-text-property mpos 'mouse-face)) + (keyw-p (eq 'org-special-keyword (get-text-property mpos 'face)))) + (goto-char (point-at-bol)) + (setq table-p (looking-at-p org-table-dataline-regexp) + comment-p (looking-at-p "^[ \t]*#[ +]")) + (goto-char pos) + ;; Handle a_b^c + (when (member (char-after) '(?_ ?^)) (goto-char (1- pos))) + (unless (or comment-p emph-p link-p keyw-p) + (put-text-property (match-beginning 3) (match-end 0) + 'display + (if (equal (char-after (match-beginning 2)) ?^) + (nth (if table-p 3 1) org-script-display) + (nth (if table-p 2 0) org-script-display))) + (add-text-properties (match-beginning 2) (match-end 2) + (list 'invisible t)) + (when (and (eq (char-after (match-beginning 3)) ?{) + (eq (char-before (match-end 3)) ?})) + (add-text-properties (match-beginning 3) (1+ (match-beginning 3)) + (list 'invisible t)) + (add-text-properties (1- (match-end 3)) (match-end 3) + (list 'invisible t)))) + t))) ;;;; Visibility cycling, including org-goto and indirect buffer ;;; Cycling -(defvar org-cycle-global-status nil) -(make-variable-buffer-local 'org-cycle-global-status) +(defvar-local org-cycle-global-status nil) (put 'org-cycle-global-status 'org-state t) -(defvar org-cycle-subtree-status nil) -(make-variable-buffer-local 'org-cycle-subtree-status) +(defvar-local org-cycle-subtree-status nil) (put 'org-cycle-subtree-status 'org-state t) (defvar org-inlinetask-min-level) @@ -6537,52 +6657,58 @@ and subscripts." ;;;###autoload (defun org-cycle (&optional arg) - "TAB-action and visibility cycling for Org-mode. + "TAB-action and visibility cycling for Org mode. -This is the command invoked in Org-mode by the TAB key. Its main purpose -is outline visibility cycling, but it also invokes other actions +This is the command invoked in Org mode by the `TAB' key. Its main +purpose is outline visibility cycling, but it also invokes other actions in special contexts. -- When this function is called with a prefix argument, rotate the entire - buffer through 3 states (global cycling) +When this function is called with a `\\[universal-argument]' prefix, rotate \ +the entire +buffer through 3 states (global cycling) 1. OVERVIEW: Show only top-level headlines. 2. CONTENTS: Show all headlines of all levels, but no body text. 3. SHOW ALL: Show everything. - When called with two `C-u C-u' prefixes, switch to the startup visibility, - determined by the variable `org-startup-folded', and by any VISIBILITY - properties in the buffer. - When called with three `C-u C-u C-u' prefixed, show the entire buffer, - including any drawers. -- When inside a table, re-align the table and move to the next field. +With a `\\[universal-argument] \\[universal-argument]' prefix argument, \ +switch to the startup visibility, +determined by the variable `org-startup-folded', and by any VISIBILITY +properties in the buffer. + +With a `\\[universal-argument] \\[universal-argument] \ +\\[universal-argument]' prefix argument, show the entire buffer, including +any drawers. -- When point is at the beginning of a headline, rotate the subtree started - by this line through 3 different states (local cycling) +When inside a table, re-align the table and move to the next field. + +When point is at the beginning of a headline, rotate the subtree started +by this line through 3 different states (local cycling) 1. FOLDED: Only the main headline is shown. 2. CHILDREN: The main headline and the direct children are shown. From this state, you can move to one of the children and zoom in further. 3. SUBTREE: Show the entire subtree, including body text. - If there is no subtree, switch directly from CHILDREN to FOLDED. - -- When point is at the beginning of an empty headline and the variable - `org-cycle-level-after-item/entry-creation' is set, cycle the level - of the headline by demoting and promoting it to likely levels. This - speeds up creation document structure by pressing TAB once or several - times right after creating a new headline. - -- When there is a numeric prefix, go up to a heading with level ARG, do - a `show-subtree' and return to the previous cursor position. If ARG - is negative, go up that many levels. - -- When point is not at the beginning of a headline, execute the global - binding for TAB, which is re-indenting the line. See the option - `org-cycle-emulate-tab' for details. - -- Special case: if point is at the beginning of the buffer and there is - no headline in line 1, this function will act as if called with prefix arg - (C-u TAB, same as S-TAB) also when called without prefix arg. - But only if also the variable `org-cycle-global-at-bob' is t." +If there is no subtree, switch directly from CHILDREN to FOLDED. + +When point is at the beginning of an empty headline and the variable +`org-cycle-level-after-item/entry-creation' is set, cycle the level +of the headline by demoting and promoting it to likely levels. This +speeds up creation document structure by pressing `TAB' once or several +times right after creating a new headline. + +When there is a numeric prefix, go up to a heading with level ARG, do +a `show-subtree' and return to the previous cursor position. If ARG +is negative, go up that many levels. + +When point is not at the beginning of a headline, execute the global +binding for `TAB', which is re-indenting the line. See the option +`org-cycle-emulate-tab' for details. + +As a special case, if point is at the beginning of the buffer and there is +no headline in line 1, this function will act as if called with prefix arg +\(`\\[universal-argument] TAB', same as `S-TAB') also when called without \ +prefix arg, but only +if the variable `org-cycle-global-at-bob' is t." (interactive "P") (org-load-modules-maybe) (unless (or (run-hook-with-args-until-success 'org-tab-first-hook) @@ -6611,10 +6737,6 @@ in special contexts. org-cycle-hook)) (pos (point))) - (if (or bob-special (equal arg '(4))) - ;; special case: use global cycling - (setq arg t)) - (cond ((equal arg '(16)) @@ -6623,32 +6745,36 @@ in special contexts. (org-unlogged-message "Startup visibility, plus VISIBILITY properties")) ((equal arg '(64)) - (show-all) + (outline-show-all) (org-unlogged-message "Entire buffer visible, including drawers")) + ((equal arg '(4)) (org-cycle-internal-global)) + + ;; Try hiding block at point. + ((org-hide-block-toggle-maybe)) + ;; Try cdlatex TAB completion ((org-try-cdlatex-tab)) ;; Table: enter it or move to the next field. ((org-at-table-p 'any) (if (org-at-table.el-p) - (message "%s" "Use C-c ' to edit table.el tables") + (message "%s" (substitute-command-keys "\\<org-mode-map>\ +Use `\\[org-edit-special]' to edit table.el tables")) (if arg (org-table-edit-field t) (org-table-justify-field-maybe) (call-interactively 'org-table-next-field)))) - ((run-hook-with-args-until-success - 'org-tab-after-check-for-table-hook)) + ((run-hook-with-args-until-success 'org-tab-after-check-for-table-hook)) ;; Global cycling: delegate to `org-cycle-internal-global'. - ((eq arg t) (org-cycle-internal-global)) + (bob-special (org-cycle-internal-global)) ;; Drawers: delegate to `org-flag-drawer'. - ((and org-drawers org-drawer-regexp - (save-excursion - (beginning-of-line 1) - (looking-at org-drawer-regexp))) - (org-flag-drawer ; toggle block visibility + ((save-excursion + (beginning-of-line 1) + (looking-at org-drawer-regexp)) + (org-flag-drawer ; toggle block visibility (not (get-char-property (match-end 0) 'invisible)))) ;; Show-subtree, ARG levels up from here. @@ -6667,7 +6793,7 @@ in special contexts. ;; At an item/headline: delegate to `org-cycle-internal-local'. ((and (or (and org-cycle-include-plain-lists (org-at-item-p)) - (save-excursion (beginning-of-line 1) + (save-excursion (move-beginning-of-line 1) (looking-at org-outline-regexp))) (or (bolp) (not (eq org-cycle-emulate-tab 'exc-hl-bol)))) (org-cycle-internal-local)) @@ -6722,7 +6848,7 @@ in special contexts. (eq org-cycle-global-status 'contents)) ;; We just showed the table of contents - now show everything (run-hook-with-args 'org-pre-cycle-hook 'all) - (show-all) + (outline-show-all) (unless ga (org-unlogged-message "SHOW ALL")) (setq org-cycle-global-status 'all) (run-hook-with-args 'org-cycle-hook 'all)) @@ -6738,6 +6864,11 @@ in special contexts. (defvar org-called-with-limited-levels nil "Non-nil when `org-with-limited-levels' is currently active.") +(defun org-invisible-p (&optional pos) + "Non-nil if the character after POS is invisible. +If POS is nil, use `point' instead." + (get-char-property (or pos (point)) 'invisible)) + (defun org-cycle-internal-local () "Do the local cycling action." (let ((goal-column 0) eoh eol eos has-children children-skipped struct) @@ -6765,15 +6896,10 @@ in special contexts. (org-list-search-forward (org-item-beginning-re) eos t))))) ;; Determine end invisible part of buffer (EOL) (beginning-of-line 2) - ;; XEmacs doesn't have `next-single-char-property-change' - (if (featurep 'xemacs) - (while (and (not (eobp)) ;; this is like `next-line' - (get-char-property (1- (point)) 'invisible)) - (beginning-of-line 2)) - (while (and (not (eobp)) ;; this is like `next-line' - (get-char-property (1- (point)) 'invisible)) - (goto-char (next-single-char-property-change (point) 'invisible)) - (and (eolp) (beginning-of-line 2)))) + (while (and (not (eobp)) ;This is like `next-line'. + (get-char-property (1- (point)) 'invisible)) + (goto-char (next-single-char-property-change (point) 'invisible)) + (and (eolp) (beginning-of-line 2))) (setq eol (point))) ;; Find out what to do next and set `this-command' (cond @@ -6786,7 +6912,7 @@ in special contexts. (save-excursion (goto-char eos) (outline-next-heading) - (if (outline-invisible-p) (org-flag-heading nil)))) + (when (org-invisible-p) (org-flag-heading nil)))) ((and (or (>= eol eos) (not (string-match "\\S-" (buffer-substring eol eos)))) (or has-children @@ -6798,7 +6924,7 @@ in special contexts. (if (org-at-item-p) (org-list-set-item-visibility (point-at-bol) struct 'children) (org-show-entry) - (org-with-limited-levels (show-children)) + (org-with-limited-levels (org-show-children)) ;; FIXME: This slows down the func way too much. ;; How keep drawers hidden in subtree anyway? ;; (when (memq 'org-cycle-hide-drawers org-cycle-hook) @@ -6813,14 +6939,14 @@ in special contexts. (let* ((struct (org-list-struct)) (prevs (org-list-prevs-alist struct)) (end (org-list-get-bottom-point struct))) - (mapc (lambda (e) (org-list-set-item-visibility e struct 'folded)) - (org-list-get-all-items (point) struct prevs)) + (dolist (e (org-list-get-all-items (point) struct prevs)) + (org-list-set-item-visibility e struct 'folded)) (goto-char (if (< end eos) end eos))))))) (org-unlogged-message "CHILDREN") (save-excursion (goto-char eos) (outline-next-heading) - (if (outline-invisible-p) (org-flag-heading nil))) + (when (org-invisible-p) (org-flag-heading nil))) (setq org-cycle-subtree-status 'children) (unless (org-before-first-heading-p) (run-hook-with-args 'org-cycle-hook 'children))) @@ -6849,15 +6975,15 @@ in special contexts. ;;;###autoload (defun org-global-cycle (&optional arg) "Cycle the global visibility. For details see `org-cycle'. -With \\[universal-argument] prefix arg, switch to startup visibility. +With `\\[universal-argument]' prefix ARG, switch to startup visibility. With a numeric prefix, show all headlines up to that level." (interactive "P") (let ((org-cycle-include-plain-lists (if (derived-mode-p 'org-mode) org-cycle-include-plain-lists nil))) (cond ((integerp arg) - (show-all) - (hide-sublevels arg) + (outline-show-all) + (outline-hide-sublevels arg) (setq org-cycle-global-status 'contents)) ((equal arg '(4)) (org-set-startup-visibility) @@ -6874,9 +7000,9 @@ With a numeric prefix, show all headlines up to that level." (org-content)) ((or (eq org-startup-folded 'showeverything) (eq org-startup-folded nil)) - (show-all))) + (outline-show-all))) (unless (eq org-startup-folded 'showeverything) - (if org-hide-block-startup (org-hide-block-all)) + (when org-hide-block-startup (org-hide-block-all)) (org-set-visibility-according-to-property 'no-cleanup) (org-cycle-hide-archived-subtrees 'all) (org-cycle-hide-drawers 'all) @@ -6885,34 +7011,32 @@ With a numeric prefix, show all headlines up to that level." (defun org-set-visibility-according-to-property (&optional no-cleanup) "Switch subtree visibilities according to :VISIBILITY: property." (interactive) - (let (org-show-entry-below state) - (save-excursion - (goto-char (point-min)) - (while (re-search-forward - "^[ \t]*:VISIBILITY:[ \t]+\\([a-z]+\\)" - nil t) - (setq state (match-string 1)) - (save-excursion - (org-back-to-heading t) - (hide-subtree) - (org-reveal) - (cond - ((equal state '("fold" "folded")) - (hide-subtree)) - ((equal state "children") - (org-show-hidden-entry) - (show-children)) - ((equal state "content") - (save-excursion - (save-restriction - (org-narrow-to-subtree) - (org-content)))) - ((member state '("all" "showall")) - (show-subtree))))) - (unless no-cleanup - (org-cycle-hide-archived-subtrees 'all) - (org-cycle-hide-drawers 'all) - (org-cycle-show-empty-lines 'all))))) + (org-with-wide-buffer + (goto-char (point-min)) + (while (re-search-forward "^[ \t]*:VISIBILITY:" nil t) + (if (not (org-at-property-p)) (outline-next-heading) + (let ((state (match-string 3))) + (save-excursion + (org-back-to-heading t) + (outline-hide-subtree) + (org-reveal) + (cond + ((equal state "folded") + (outline-hide-subtree)) + ((equal state "children") + (org-show-hidden-entry) + (org-show-children)) + ((equal state "content") + (save-excursion + (save-restriction + (org-narrow-to-subtree) + (org-content)))) + ((member state '("all" "showall")) + (outline-show-subtree))))))) + (unless no-cleanup + (org-cycle-hide-archived-subtrees 'all) + (org-cycle-hide-drawers 'all) + (org-cycle-show-empty-lines 'all)))) ;; This function uses outline-regexp instead of the more fundamental ;; org-outline-regexp so that org-cycle-global works outside of Org @@ -6928,11 +7052,10 @@ results." (let ((level (save-excursion (goto-char (point-min)) - (if (re-search-forward (concat "^" outline-regexp) nil t) - (progn - (goto-char (match-beginning 0)) - (funcall outline-level)))))) - (and level (hide-sublevels level))))) + (when (re-search-forward (concat "^" outline-regexp) nil t) + (goto-char (match-beginning 0)) + (funcall outline-level))))) + (and level (outline-hide-sublevels level))))) (defun org-content (&optional arg) "Show all headlines in the buffer, like a table of contents. @@ -6950,9 +7073,9 @@ With numerical argument N, show content up to level N." t) (looking-at org-outline-regexp)) (if (integerp arg) - (show-children (1- arg)) - (show-branches)) - (if (bobp) (throw 'exit nil)))))) + (org-show-children (1- arg)) + (outline-show-branches)) + (when (bobp) (throw 'exit nil)))))) (defun org-optimize-window-after-visibility-change (state) "Adjust the window after a change in outline visibility. @@ -6967,13 +7090,11 @@ This function is the default value of the hook `org-cycle-hook'." (defun org-remove-empty-overlays-at (pos) "Remove outline overlays that do not contain non-white stuff." - (mapc - (lambda (o) - (and (eq 'outline (overlay-get o 'invisible)) - (not (string-match "\\S-" (buffer-substring (overlay-start o) - (overlay-end o)))) - (delete-overlay o))) - (overlays-at pos))) + (dolist (o (overlays-at pos)) + (and (eq 'outline (overlay-get o 'invisible)) + (not (string-match "\\S-" (buffer-substring (overlay-start o) + (overlay-end o)))) + (delete-overlay o)))) (defun org-clean-visibility-after-subtree-move () "Fix visibility issues after moving a subtree." @@ -6991,7 +7112,7 @@ This function is the default value of the hook `org-cycle-hook'." (point-at-eol) (point)))) (level (looking-at "\\*+")) - (re (if level (concat "^" (regexp-quote (match-string 0)) " ")))) + (re (when level (concat "^" (regexp-quote (match-string 0)) " ")))) (save-excursion (save-restriction (narrow-to-region beg end) @@ -6999,10 +7120,10 @@ This function is the default value of the hook `org-cycle-hook'." ;; Properly fold already folded siblings (goto-char (point-min)) (while (re-search-forward re nil t) - (if (and (not (outline-invisible-p)) - (save-excursion - (goto-char (point-at-eol)) (outline-invisible-p))) - (hide-entry)))) + (when (and (not (org-invisible-p)) + (save-excursion + (goto-char (point-at-eol)) (org-invisible-p))) + (outline-hide-entry)))) (org-cycle-show-empty-lines 'overview) (org-cycle-hide-drawers 'overview))))) @@ -7012,7 +7133,7 @@ The region to be covered depends on STATE when called through `org-cycle-hook'. Lisp program can use t for STATE to get the entire buffer covered. Note that an empty line is only shown if there are at least `org-cycle-separator-lines' empty lines before the headline." - (when (not (= org-cycle-separator-lines 0)) + (when (/= org-cycle-separator-lines 0) (save-excursion (let* ((n (abs org-cycle-separator-lines)) (re (cond @@ -7021,38 +7142,34 @@ are at least `org-cycle-separator-lines' empty lines before the headline." (t (let ((ns (number-to-string (- n 2)))) (concat "^\\(?:[ \t]*\n\\)\\{" ns "," ns "\\}" "[ \t]*\\(\n[ \t]*\n\\*+\\) "))))) - beg end b e) + beg end) (cond ((memq state '(overview contents t)) (setq beg (point-min) end (point-max))) ((memq state '(children folded)) - (setq beg (point) end (progn (org-end-of-subtree t t) - (beginning-of-line 2) - (point))))) + (setq beg (point) + end (progn (org-end-of-subtree t t) + (line-beginning-position 2))))) (when beg (goto-char beg) (while (re-search-forward re end t) (unless (get-char-property (match-end 1) 'invisible) - (setq e (match-end 1)) - (if (< org-cycle-separator-lines 0) - (setq b (save-excursion - (goto-char (match-beginning 0)) - (org-back-over-empty-lines) - (if (save-excursion - (goto-char (max (point-min) (1- (point)))) - (org-at-heading-p)) - (1- (point)) - (point)))) - (setq b (match-beginning 1))) - (outline-flag-region b e nil))))))) + (let ((e (match-end 1)) + (b (if (>= org-cycle-separator-lines 0) + (match-beginning 1) + (save-excursion + (goto-char (match-beginning 0)) + (skip-chars-backward " \t\n") + (line-end-position))))) + (outline-flag-region b e nil)))))))) ;; Never hide empty lines at the end of the file. (save-excursion (goto-char (point-max)) (outline-previous-heading) (outline-end-of-heading) - (if (and (looking-at "[ \t\n]+") - (= (match-end 0) (point-max))) - (outline-flag-region (point) (match-end 0) nil)))) + (when (and (looking-at "[ \t\n]+") + (= (match-end 0) (point-max))) + (outline-flag-region (point) (match-end 0) nil)))) (defun org-show-empty-lines-in-parent () "Move to the parent and re-show empty lines before visible headlines." @@ -7061,68 +7178,72 @@ are at least `org-cycle-separator-lines' empty lines before the headline." (org-cycle-show-empty-lines context)))) (defun org-files-list () - "Return `org-agenda-files' list, plus all open org-mode files. + "Return `org-agenda-files' list, plus all open Org files. This is useful for operations that need to scan all of a user's open and agenda-wise Org files." (let ((files (mapcar 'expand-file-name (org-agenda-files)))) (dolist (buf (buffer-list)) (with-current-buffer buf - (if (and (derived-mode-p 'org-mode) (buffer-file-name)) - (let ((file (expand-file-name (buffer-file-name)))) - (unless (member file files) - (push file files)))))) + (when (and (derived-mode-p 'org-mode) (buffer-file-name)) + (cl-pushnew (expand-file-name (buffer-file-name)) files)))) files)) (defsubst org-entry-beginning-position () "Return the beginning position of the current entry." - (save-excursion (outline-back-to-heading t) (point))) + (save-excursion (org-back-to-heading t) (point))) (defsubst org-entry-end-position () "Return the end position of the current entry." (save-excursion (outline-next-heading) (point))) -(defun org-cycle-hide-drawers (state) - "Re-hide all drawers after a visibility state change." +(defun org-cycle-hide-drawers (state &optional exceptions) + "Re-hide all drawers after a visibility state change. +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." (when (and (derived-mode-p 'org-mode) (not (memq state '(overview folded contents)))) (save-excursion - (let* ((globalp (memq state '(contents all))) + (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))))) (goto-char beg) - (while (re-search-forward org-drawer-regexp end t) - (org-flag-drawer t)))))) - -(defun org-cycle-hide-inline-tasks (state) - "Re-hide inline tasks when switching to `contents' or `children' -visibility state." - (case state - (contents - (when (org-bound-and-true-p org-inlinetask-min-level) - (hide-sublevels (1- org-inlinetask-min-level)))) - (children - (when (featurep 'org-inlinetask) - (save-excursion - (while (and (outline-next-heading) - (org-inlinetask-at-task-p)) - (org-inlinetask-toggle-visibility) - (org-inlinetask-goto-end))))))) - -(defun org-flag-drawer (flag) - "When FLAG is non-nil, hide the drawer we are within. -Otherwise make it visible." - (save-excursion - (beginning-of-line 1) - (when (looking-at "^[ \t]*:[a-zA-Z][a-zA-Z0-9]*:") - (let ((b (match-end 0))) - (if (re-search-forward - "^[ \t]*:END:" - (save-excursion (outline-next-heading) (point)) t) - (outline-flag-region b (point-at-eol) flag) - (user-error ":END: line missing at position %s" b)))))) + (while (re-search-forward org-drawer-regexp (max end (point)) t) + (unless (member-ignore-case (match-string 1) exceptions) + (let ((drawer (org-element-at-point))) + (when (memq (org-element-type drawer) '(drawer property-drawer)) + (org-flag-drawer t drawer) + ;; Make sure to skip drawer entirely or we might flag + ;; it another time when matching its ending line with + ;; `org-drawer-regexp'. + (goto-char (org-element-property :end drawer)))))))))) + +(defun org-flag-drawer (flag &optional element) + "When FLAG is non-nil, hide the drawer we are at. +Otherwise make it visible. When optional argument ELEMENT is +a parsed drawer, as returned by `org-element-at-point', hide or +show that drawer instead." + (let ((drawer (or element + (and (save-excursion + (beginning-of-line) + (looking-at-p org-drawer-regexp)) + (org-element-at-point))))) + (when (memq (org-element-type drawer) '(drawer property-drawer)) + (let ((post (org-element-property :post-affiliated drawer))) + (save-excursion + (outline-flag-region + (progn (goto-char post) (line-end-position)) + (progn (goto-char (org-element-property :end drawer)) + (skip-chars-backward " \r\t\n") + (line-end-position)) + flag)) + ;; When the drawer is hidden away, make sure point lies in + ;; a visible part of the buffer. + (when (and flag (> (line-beginning-position) post)) + (goto-char post)))))) (defun org-subtree-end-visible-p () "Is the end of the current subtree visible?" @@ -7131,9 +7252,11 @@ Otherwise make it visible." (defun org-first-headline-recenter () "Move cursor to the first headline and recenter the headline." - (goto-char (point-min)) - (when (re-search-forward (concat "^\\(" org-outline-regexp "\\)") nil t) - (set-window-start (selected-window) (point-at-bol)))) + (let ((window (get-buffer-window))) + (when window + (goto-char (point-min)) + (when (re-search-forward (concat "^\\(" org-outline-regexp "\\)") nil t) + (set-window-start window (line-beginning-position)))))) ;;; Saving and restoring visibility @@ -7144,38 +7267,30 @@ The return value is a list of cons cells, with start and stop positions for each overlay. If USE-MARKERS is set, return the positions as markers." (let (beg end) - (save-excursion - (save-restriction - (widen) - (delq nil - (mapcar (lambda (o) - (when (eq (overlay-get o 'invisible) 'outline) - (setq beg (overlay-start o) - end (overlay-end o)) - (and beg end (> end beg) - (if use-markers - (cons (move-marker (make-marker) beg) - (move-marker (make-marker) end)) - (cons beg end))))) - (overlays-in (point-min) (point-max)))))))) + (org-with-wide-buffer + (delq nil + (mapcar (lambda (o) + (when (eq (overlay-get o 'invisible) 'outline) + (setq beg (overlay-start o) + end (overlay-end o)) + (and beg end (> end beg) + (if use-markers + (cons (copy-marker beg) + (copy-marker end t)) + (cons beg end))))) + (overlays-in (point-min) (point-max))))))) (defun org-set-outline-overlay-data (data) "Create visibility overlays for all positions in DATA. DATA should have been made by `org-outline-overlay-data'." - (let (o) - (save-excursion - (save-restriction - (widen) - (show-all) - (mapc (lambda (c) - (outline-flag-region (car c) (cdr c) t)) - data))))) + (org-with-wide-buffer + (outline-show-all) + (dolist (c data) (outline-flag-region (car c) (cdr c) t)))) ;;; Folding of blocks -(defvar org-hide-block-overlays nil +(defvar-local org-hide-block-overlays nil "Overlays hiding blocks.") -(make-variable-buffer-local 'org-hide-block-overlays) (defun org-block-map (function &optional start end) "Call FUNCTION at the head of all source blocks in the current buffer. @@ -7192,74 +7307,85 @@ Optional arguments START and END can be used to limit the range." (defun org-hide-block-toggle-all () "Toggle the visibility of all blocks in the current buffer." - (org-block-map #'org-hide-block-toggle)) + (org-block-map 'org-hide-block-toggle)) (defun org-hide-block-all () "Fold all blocks in the current buffer." (interactive) (org-show-block-all) - (org-block-map #'org-hide-block-toggle-maybe)) + (org-block-map 'org-hide-block-toggle-maybe)) (defun org-show-block-all () "Unfold all blocks in the current buffer." (interactive) - (mapc 'delete-overlay org-hide-block-overlays) + (mapc #'delete-overlay org-hide-block-overlays) (setq org-hide-block-overlays nil)) (defun org-hide-block-toggle-maybe () - "Toggle visibility of block at point." + "Toggle visibility of block at point. +Unlike to `org-hide-block-toggle', this function does not throw +an error. Return a non-nil value when toggling is successful." (interactive) - (let ((case-fold-search t)) - (if (save-excursion - (beginning-of-line 1) - (looking-at org-block-regexp)) - (progn (org-hide-block-toggle) - t) ;; to signal that we took action - nil))) ;; to signal that we did not + (ignore-errors (org-hide-block-toggle))) (defun org-hide-block-toggle (&optional force) - "Toggle the visibility of the current block." + "Toggle the visibility of the current block. +When optional argument FORCE is `off', make block visible. If it +is non-nil, hide it unconditionally. Throw an error when not at +a block. Return a non-nil value when toggling is successful." (interactive) - (save-excursion - (beginning-of-line) - (if (re-search-forward org-block-regexp nil t) - (let ((start (- (match-beginning 4) 1)) ;; beginning of body - (end (match-end 0)) ;; end of entire body - ov) - (if (memq t (mapcar (lambda (overlay) - (eq (overlay-get overlay 'invisible) - 'org-hide-block)) - (overlays-at start))) - (if (or (not force) (eq force 'off)) - (mapc (lambda (ov) - (when (member ov org-hide-block-overlays) - (setq org-hide-block-overlays - (delq ov org-hide-block-overlays))) - (when (eq (overlay-get ov 'invisible) - 'org-hide-block) - (delete-overlay ov))) - (overlays-at start))) - (setq ov (make-overlay start end)) - (overlay-put ov 'invisible 'org-hide-block) - ;; make the block accessible to isearch - (overlay-put - ov 'isearch-open-invisible - (lambda (ov) - (when (member ov org-hide-block-overlays) - (setq org-hide-block-overlays - (delq ov org-hide-block-overlays))) - (when (eq (overlay-get ov 'invisible) - 'org-hide-block) - (delete-overlay ov)))) - (push ov org-hide-block-overlays))) - (user-error "Not looking at a source block")))) - -;; org-tab-after-check-for-cycling-hook -(add-hook 'org-tab-first-hook 'org-hide-block-toggle-maybe) + (let ((element (org-element-at-point))) + (unless (memq (org-element-type element) + '(center-block comment-block dynamic-block example-block + export-block quote-block special-block + src-block verse-block)) + (user-error "Not at a block")) + (let* ((start (save-excursion + (goto-char (org-element-property :post-affiliated element)) + (line-end-position))) + (end (save-excursion + (goto-char (org-element-property :end element)) + (skip-chars-backward " \r\t\n") + (line-end-position))) + (overlays (overlays-at start))) + (cond + ;; Do nothing when not before or at the block opening line or + ;; at the block closing line. + ((let ((eol (line-end-position))) (and (> eol start) (/= eol end))) nil) + ((and (not (eq force 'off)) + (not (memq t (mapcar + (lambda (o) + (eq (overlay-get o 'invisible) 'org-hide-block)) + overlays)))) + (let ((ov (make-overlay start end))) + (overlay-put ov 'invisible 'org-hide-block) + ;; Make the block accessible to `isearch'. + (overlay-put + ov 'isearch-open-invisible + (lambda (ov) + (when (memq ov org-hide-block-overlays) + (setq org-hide-block-overlays (delq ov org-hide-block-overlays))) + (when (eq (overlay-get ov 'invisible) 'org-hide-block) + (delete-overlay ov)))) + (push ov org-hide-block-overlays) + ;; When the block is hidden away, make sure point is left in + ;; a visible part of the buffer. + (when (> (line-beginning-position) start) + (goto-char start) + (beginning-of-line)) + ;; Signal successful toggling. + t)) + ((or (not force) (eq force 'off)) + (dolist (ov overlays t) + (when (memq ov org-hide-block-overlays) + (setq org-hide-block-overlays (delq ov org-hide-block-overlays))) + (when (eq (overlay-get ov 'invisible) 'org-hide-block) + (delete-overlay ov)))))))) + ;; Remove overlays when changing major mode (add-hook 'org-mode-hook - (lambda () (org-add-hook 'change-major-mode-hook - 'org-show-block-all 'append 'local))) + (lambda () (add-hook 'change-major-mode-hook + 'org-show-block-all 'append 'local))) ;;; Org-goto @@ -7305,7 +7431,7 @@ Optional arguments START and END can be used to limit the range." (defconst org-goto-help "Browse buffer copy, to find location or copy text.%s RET=jump to location C-g=quit and return to previous location -[Up]/[Down]=next/prev headline TAB=cycle visibility [/] org-occur") +\[Up]/[Down]=next/prev headline TAB=cycle visibility [/] org-occur") (defvar org-goto-start-pos) ; dynamically scoped parameter @@ -7343,23 +7469,23 @@ With a prefix argument, use the alternative interface: e.g., if (selected-point (if (eq interface 'outline) (car (org-get-location (current-buffer) org-goto-help)) - (let ((pa (org-refile-get-location "Goto" nil nil t))) + (let ((pa (org-refile-get-location "Goto"))) (org-refile-check-position pa) (nth 3 pa))))) (if selected-point (progn (org-mark-ring-push org-goto-start-pos) (goto-char selected-point) - (if (or (outline-invisible-p) (org-invisible-p2)) - (org-show-context 'org-goto))) + (when (or (org-invisible-p) (org-invisible-p2)) + (org-show-context 'org-goto))) (message "Quit")))) (defvar org-goto-selected-point nil) ; dynamically scoped parameter (defvar org-goto-exit-command nil) ; dynamically scoped parameter (defvar org-goto-local-auto-isearch-map) ; defined below -(defun org-get-location (buf help) - "Let the user select a location in the Org-mode buffer BUF. +(defun org-get-location (_buf help) + "Let the user select a location in current buffer. This function uses a recursive edit. It returns the selected position or nil." (org-no-popups @@ -7372,7 +7498,7 @@ or nil." (save-window-excursion (delete-other-windows) (and (get-buffer "*org-goto*") (kill-buffer "*org-goto*")) - (org-pop-to-buffer-same-window + (pop-to-buffer-same-window (condition-case nil (make-indirect-buffer (current-buffer) "*org-goto*") (error (make-indirect-buffer (current-buffer) "*org-goto*")))) @@ -7390,11 +7516,9 @@ or nil." (setq buffer-read-only t) (if (and (boundp 'org-goto-start-pos) (integer-or-marker-p org-goto-start-pos)) - (let ((org-show-hierarchy-above t) - (org-show-siblings t) - (org-show-following-heading t)) - (goto-char org-goto-start-pos) - (and (outline-invisible-p) (org-show-context))) + (progn (goto-char org-goto-start-pos) + (when (org-invisible-p) + (org-show-set-visibility 'lineage))) (goto-char (point-min))) (let (org-special-ctrl-a/e) (org-beginning-of-line)) (message "Select location and press RET") @@ -7405,8 +7529,14 @@ or nil." (defvar org-goto-local-auto-isearch-map (make-sparse-keymap)) (set-keymap-parent org-goto-local-auto-isearch-map isearch-mode-map) -(define-key org-goto-local-auto-isearch-map "\C-i" 'isearch-other-control-char) -(define-key org-goto-local-auto-isearch-map "\C-m" 'isearch-other-control-char) +;; `isearch-other-control-char' was removed in Emacs 24.4. +(if (fboundp 'isearch-other-control-char) + (progn + (define-key org-goto-local-auto-isearch-map "\C-i" 'isearch-other-control-char) + (define-key org-goto-local-auto-isearch-map "\C-m" 'isearch-other-control-char)) + (define-key org-goto-local-auto-isearch-map "\C-i" nil) + (define-key org-goto-local-auto-isearch-map "\C-m" nil) + (define-key org-goto-local-auto-isearch-map [return] nil)) (defun org-goto-local-search-headings (string bound noerror) "Search and make sure that any matches are in headlines." @@ -7414,9 +7544,12 @@ or nil." (while (if isearch-forward (search-forward string bound noerror) (search-backward string bound noerror)) - (when (let ((context (mapcar 'car (save-match-data (org-context))))) - (and (member :headline context) - (not (member :tags context)))) + (when (save-match-data + (and (save-excursion + (beginning-of-line) + (looking-at org-complex-heading-regexp)) + (or (not (match-beginning 5)) + (< (point) (match-beginning 5))))) (throw 'return (point)))))) (defun org-goto-local-auto-isearch () @@ -7428,11 +7561,11 @@ or nil." (isearch-mode t) (isearch-process-search-char (string-to-char keys))))) -(defun org-goto-ret (&optional arg) +(defun org-goto-ret (&optional _arg) "Finish `org-goto' by going to the new location." (interactive "P") - (setq org-goto-selected-point (point) - org-goto-exit-command 'return) + (setq org-goto-selected-point (point)) + (setq org-goto-exit-command 'return) (throw 'exit nil)) (defun org-goto-left () @@ -7471,17 +7604,18 @@ or nil." (defun org-tree-to-indirect-buffer (&optional arg) "Create indirect buffer and narrow it to current subtree. + With a numerical prefix ARG, go up to this level and then take that tree. If ARG is negative, go up that many levels. If `org-indirect-buffer-display' is not `new-frame', the command removes the indirect buffer previously made with this command, to avoid proliferation of indirect buffers. However, when you call the command with a \ -\\[universal-argument] prefix, or -when `org-indirect-buffer-display' is `new-frame', the last buffer -is kept so that you can work with several indirect buffers at the same time. -If `org-indirect-buffer-display' is `dedicated-frame', the \ -\\[universal-argument] prefix also +`\\[universal-argument]' prefix, or +when `org-indirect-buffer-display' is `new-frame', the last buffer is kept +so that you can work with several indirect buffers at the same time. If +`org-indirect-buffer-display' is `dedicated-frame', the \ +`\\[universal-argument]' prefix also requests that a new frame be made for the new buffer, so that the dedicated frame is not changed." (interactive "P") @@ -7493,26 +7627,26 @@ frame is not changed." (org-back-to-heading t) (when (numberp arg) (setq level (org-outline-level)) - (if (< arg 0) (setq arg (+ level arg))) + (when (< arg 0) (setq arg (+ level arg))) (while (> (setq level (org-outline-level)) arg) (org-up-heading-safe))) (setq beg (point) - heading (org-get-heading)) + heading (org-get-heading 'no-tags)) (org-end-of-subtree t t) - (if (org-at-heading-p) (backward-char 1)) + (when (org-at-heading-p) (backward-char 1)) (setq end (point))) - (if (and (buffer-live-p org-last-indirect-buffer) - (not (eq org-indirect-buffer-display 'new-frame)) - (not arg)) - (kill-buffer org-last-indirect-buffer)) - (setq ibuf (org-get-indirect-buffer cbuf) + (when (and (buffer-live-p org-last-indirect-buffer) + (not (eq org-indirect-buffer-display 'new-frame)) + (not arg)) + (kill-buffer org-last-indirect-buffer)) + (setq ibuf (org-get-indirect-buffer cbuf heading) org-last-indirect-buffer ibuf) (cond ((or (eq org-indirect-buffer-display 'new-frame) (and arg (eq org-indirect-buffer-display 'dedicated-frame))) (select-frame (make-frame)) (delete-other-windows) - (org-pop-to-buffer-same-window ibuf) + (pop-to-buffer-same-window ibuf) (org-set-frame-title heading)) ((eq org-indirect-buffer-display 'dedicated-frame) (raise-frame @@ -7521,26 +7655,28 @@ frame is not changed." org-indirect-dedicated-frame) (setq org-indirect-dedicated-frame (make-frame))))) (delete-other-windows) - (org-pop-to-buffer-same-window ibuf) + (pop-to-buffer-same-window ibuf) (org-set-frame-title (concat "Indirect: " heading))) ((eq org-indirect-buffer-display 'current-window) - (org-pop-to-buffer-same-window ibuf)) + (pop-to-buffer-same-window ibuf)) ((eq org-indirect-buffer-display 'other-window) (pop-to-buffer ibuf)) (t (error "Invalid value"))) - (if (featurep 'xemacs) - (save-excursion (org-mode) (turn-on-font-lock))) (narrow-to-region beg end) - (show-all) + (outline-show-all) (goto-char pos) (run-hook-with-args 'org-cycle-hook 'all) (and (window-live-p cwin) (select-window cwin)))) -(defun org-get-indirect-buffer (&optional buffer) +(defun org-get-indirect-buffer (&optional buffer heading) (setq buffer (or buffer (current-buffer))) (let ((n 1) (base (buffer-name buffer)) bname) (while (buffer-live-p - (get-buffer (setq bname (concat base "-" (number-to-string n))))) + (get-buffer + (setq bname + (concat base "-" + (if heading (concat heading "-" (number-to-string n)) + (number-to-string n)))))) (setq n (1+ n))) (condition-case nil (make-indirect-buffer buffer bname 'clone) @@ -7548,224 +7684,189 @@ frame is not changed." (defun org-set-frame-title (title) "Set the title of the current frame to the string TITLE." - ;; FIXME: how to name a single frame in XEmacs??? - (unless (featurep 'xemacs) - (modify-frame-parameters (selected-frame) (list (cons 'name title))))) + (modify-frame-parameters (selected-frame) (list (cons 'name title)))) ;;;; Structure editing ;;; Inserting headlines -(defun org-previous-line-empty-p (&optional next) - "Is the previous line a blank line? -When NEXT is non-nil, check the next line instead." +(defun org--line-empty-p (n) + "Is the Nth next line empty? + +Counts the current line as N = 1 and the previous line as N = 0; +see `beginning-of-line'." (save-excursion (and (not (bobp)) - (or (beginning-of-line (if next 2 0)) t) + (or (beginning-of-line n) t) (save-match-data (looking-at "[ \t]*$"))))) -(defun org-insert-heading (&optional arg invisible-ok) +(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 +headline instead of current one." + (pcase (assq 'heading org-blank-before-new-entry) + (`(heading . auto) + (save-excursion + (org-with-limited-levels + (unless (and (org-before-first-heading-p) + (not (outline-next-heading))) + (org-back-to-heading t) + (when parent (org-up-heading-safe)) + (cond ((not (bobp)) + (org-previous-line-empty-p)) + ((outline-next-heading) + (org-previous-line-empty-p)) + ;; Ignore trailing spaces on last buffer line. + ((progn (skip-chars-backward " \t") (bolp)) + (org-previous-line-empty-p)) + (t nil)))))) + (`(heading . ,value) value) + (_ nil))) + +(defun org-insert-heading (&optional arg invisible-ok top) "Insert a new heading or an item with the same depth at point. -If point is at the beginning of a heading or a list item, insert -a new heading or a new item above the current one. If point is -at the beginning of a normal line, turn the line into a heading. +If point is at the beginning of a heading, insert a new heading +or a new headline above the current one. When at the beginning +of a regular line of text, turn it into a heading. -If point is in the middle of a headline or a list item, split the -headline or the item and create a new headline/item with the text -in the current line after point \(see `org-M-RET-may-split-line' -on how to modify this behavior). +If point is in the middle of a line, split it and create a new +headline with the text in the current line after point (see +`org-M-RET-may-split-line' on how to modify this behavior). As +a special case, on a headline, splitting can only happen on the +title itself. E.g., this excludes breaking stars or tags. -With one universal prefix argument, set the user option -`org-insert-heading-respect-content' to t for the duration of -the command. This modifies the behavior described above in this -ways: on list items and at the beginning of normal lines, force -the insertion of a heading after the current subtree. +With a `\\[universal-argument]' prefix, set \ +`org-insert-heading-respect-content' to +a non-nil value for the duration of the command. This forces the +insertion of a heading after the current subtree, independently +on the location of point. -With two universal prefix arguments, insert the heading at the -end of the grandparent subtree. For example, if point is within -a 2nd-level heading, then it will insert a 2nd-level heading at -the end of the 1st-level parent heading. +With a `\\[universal-argument] \\[universal-argument]' prefix, \ +insert the heading at the end of the tree +above the current heading. For example, if point is within a +2nd-level heading, then it will insert a 2nd-level heading at +the end of the 1st-level parent subtree. When INVISIBLE-OK is set, stop at invisible headlines when going back. This is important for non-interactive uses of the -command." - (interactive "P") - (if (org-called-interactively-p 'any) (org-reveal)) - (let ((itemp (org-in-item-p)) - (may-split (org-get-alist-option org-M-RET-may-split-line 'headline)) - (respect-content (or org-insert-heading-respect-content - (equal arg '(4)))) - (initial-content "") - (adjust-empty-lines t)) +command. +When optional argument TOP is non-nil, insert a level 1 heading, +unconditionally." + (interactive "P") + (let* ((blank? (org--blank-before-heading-p (equal arg '(16)))) + (level (org-current-level)) + (stars (make-string (if (and level (not top)) level 1) ?*))) (cond - - ((or (= (buffer-size) 0) - (and (not (save-excursion - (and (ignore-errors (org-back-to-heading invisible-ok)) - (org-at-heading-p)))) - (or arg (not itemp)))) - ;; At beginning of buffer or so high up that only a heading - ;; makes sense. - (cond ((and (bolp) (not respect-content)) (insert "* ")) - ((not respect-content) - (unless may-split (end-of-line)) - (insert "\n* ")) - ((re-search-forward org-outline-regexp-bol nil t) - (beginning-of-line) - (insert "* \n") - (backward-char)) - (t (goto-char (point-max)) - (insert "\n* "))) - (run-hooks 'org-insert-heading-hook)) - - ((and itemp (not (member arg '((4) (16))))) - ;; Insert an item - (org-insert-item)) - + ((or org-insert-heading-respect-content + (member arg '((4) (16))) + (and (not invisible-ok) + (invisible-p (max (1- (point)) (point-min))))) + ;; Position point at the location of insertion. + (if (not level) ;before first headline + (org-with-limited-levels (outline-next-heading)) + ;; Make sure we end up on a visible headline if INVISIBLE-OK + ;; is nil. + (org-with-limited-levels (org-back-to-heading invisible-ok)) + (cond ((equal arg '(16)) + (org-up-heading-safe) + (org-end-of-subtree t t)) + (t + (org-end-of-subtree t t)))) + (unless (bolp) (insert "\n")) ;ensure final newline + (unless (and blank? (org-previous-line-empty-p)) + (org-N-empty-lines-before-current (if blank? 1 0))) + (insert stars " \n") + (forward-char -1)) + ;; At a headline... + ((org-at-heading-p) + (cond ((bolp) + (when blank? (save-excursion (insert "\n"))) + (save-excursion (insert stars " \n")) + (unless (and blank? (org-previous-line-empty-p)) + (org-N-empty-lines-before-current (if blank? 1 0))) + (end-of-line)) + ((and (org-get-alist-option org-M-RET-may-split-line 'headline) + (org-match-line org-complex-heading-regexp) + (org-pos-in-match-range (point) 4)) + ;; Grab the text that should moved to the new headline. + ;; Preserve tags. + (let ((split (delete-and-extract-region (point) (match-end 4)))) + (if (looking-at "[ \t]*$") (replace-match "") + (org-set-tags nil t)) + (end-of-line) + (when blank? (insert "\n")) + (insert "\n" stars " ") + (when (org-string-nw-p split) (insert split)) + (when (eobp) (save-excursion (insert "\n"))))) + (t + (end-of-line) + (when blank? (insert "\n")) + (insert "\n" stars " ") + (when (eobp) (save-excursion (insert "\n")))))) + ;; On regular text, turn line into a headline or split, if + ;; appropriate. + ((bolp) + (insert stars " ") + (unless (and blank? (org-previous-line-empty-p)) + (org-N-empty-lines-before-current (if blank? 1 0)))) (t - ;; Maybe move at the end of the subtree - (when (equal arg '(16)) - (org-up-heading-safe) - (org-end-of-subtree t)) - ;; Insert a heading - (save-restriction - (widen) - (let* ((level nil) - (on-heading (org-at-heading-p)) - (empty-line-p (if on-heading - (org-previous-line-empty-p) - ;; We will decide later - nil)) - ;; Get a level string to fall back on - (fix-level - (if (org-before-first-heading-p) "*" - (save-excursion - (org-back-to-heading t) - (if (org-previous-line-empty-p) (setq empty-line-p t)) - (looking-at org-outline-regexp) - (make-string (1- (length (match-string 0))) ?*)))) - (stars - (save-excursion - (condition-case nil - (progn - (org-back-to-heading invisible-ok) - (when (and (not on-heading) - (featurep 'org-inlinetask) - (integerp org-inlinetask-min-level) - (>= (length (match-string 0)) - org-inlinetask-min-level)) - ;; Find a heading level before the inline task - (while (and (setq level (org-up-heading-safe)) - (>= level org-inlinetask-min-level))) - (if (org-at-heading-p) - (org-back-to-heading invisible-ok) - (error "This should not happen"))) - (unless (and (save-excursion - (save-match-data - (org-backward-heading-same-level - 1 invisible-ok)) - (= (point) (match-beginning 0))) - (not (org-previous-line-empty-p t))) - (setq empty-line-p (or empty-line-p - (org-previous-line-empty-p)))) - (match-string 0)) - (error (or fix-level "* "))))) - (blank-a (cdr (assq 'heading org-blank-before-new-entry))) - (blank (if (eq blank-a 'auto) empty-line-p blank-a)) - pos hide-previous previous-pos) - - ;; If we insert after content, move there and clean up whitespace - (when (and respect-content - (not (org-looking-at-p org-outline-regexp-bol))) - (if (not (org-before-first-heading-p)) - (org-end-of-subtree nil t) - (re-search-forward org-outline-regexp-bol) - (beginning-of-line 0)) - (skip-chars-backward " \r\n") - (and (not (looking-back "^\\*+" (line-beginning-position))) - (looking-at "[ \t]+") (replace-match "")) - (unless (eobp) (forward-char 1)) - (when (looking-at "^\\*") - (unless (bobp) (backward-char 1)) - (insert "\n"))) - - ;; If we are splitting, grab the text that should be moved to the new headline - (when may-split - (if (org-on-heading-p) - ;; This is a heading, we split intelligently (keeping tags) - (let ((pos (point))) - (goto-char (point-at-bol)) - (unless (looking-at org-complex-heading-regexp) - (error "This should not happen")) - (when (and (match-beginning 4) - (> pos (match-beginning 4)) - (< pos (match-end 4))) - (setq initial-content (buffer-substring pos (match-end 4))) - (goto-char pos) - (delete-region (point) (match-end 4)) - (if (looking-at "[ \t]*$") - (replace-match "") - (insert (make-string (length initial-content) ?\ ))) - (setq initial-content (org-trim initial-content))) - (goto-char pos)) - ;; a normal line - (setq initial-content - (org-trim (buffer-substring (point) (point-at-eol)))) - (delete-region (point) (point-at-eol)))) - - ;; If we are at the beginning of the line, insert before it. Else after - (cond - ((and (bolp) (looking-at "[ \t]*$"))) - ((and (bolp) (not (looking-at "[ \t]*$"))) - (open-line 1)) - (t - (goto-char (point-at-eol)) - (insert "\n"))) - - ;; Insert the new heading - (insert stars) - (just-one-space) - (insert initial-content) - (when adjust-empty-lines - (if (or (not blank) - (and blank (not (org-previous-line-empty-p)))) - (org-N-empty-lines-before-current (if blank 1 0)))) - (run-hooks 'org-insert-heading-hook))))))) - -(defun org-N-empty-lines-before-current (N) + (unless (org-get-alist-option org-M-RET-may-split-line 'headline) + (end-of-line)) + (insert "\n" stars " ") + (unless (and blank? (org-previous-line-empty-p)) + (org-N-empty-lines-before-current (if blank? 1 0)))))) + (run-hooks 'org-insert-heading-hook)) + +(defun org-N-empty-lines-before-current (n) "Make the number of empty lines before current exactly N. So this will delete or add empty lines." - (save-excursion + (let ((column (current-column))) (beginning-of-line) - (let ((p (point))) - (skip-chars-backward " \r\t\n") - (unless (bolp) (forward-line)) - (delete-region (point) p)) - (when (> N 0) (insert (make-string N ?\n))))) - -(defun org-get-heading (&optional no-tags no-todo) + (unless (bobp) + (let ((start (save-excursion + (skip-chars-backward " \r\t\n") + (line-end-position)))) + (delete-region start (line-end-position 0)))) + (insert (make-string n ?\n)) + (move-to-column column))) + +(defun org-get-heading (&optional no-tags no-todo no-priority no-comment) "Return the heading of the current entry, without the stars. When NO-TAGS is non-nil, don't include tags. -When NO-TODO is non-nil, don't include TODO keywords." +When NO-TODO is non-nil, don't include TODO keywords. +When NO-PRIORITY is non-nil, don't include priority cookie. +When NO-COMMENT is non-nil, don't include COMMENT string." (save-excursion (org-back-to-heading t) - (cond - ((and no-tags no-todo) + (let ((case-fold-search nil)) (looking-at org-complex-heading-regexp) - (match-string 4)) - (no-tags - (looking-at (concat org-outline-regexp - "\\(.*?\\)" - "\\(?:[ \t]+:[[:alnum:]:_@#%]+:\\)?[ \t]*$")) - (match-string 1)) - (no-todo - (looking-at org-todo-line-regexp) - (match-string 3)) - (t (looking-at org-heading-regexp) - (match-string 2))))) + (let ((todo (and (not no-todo) (match-string 2))) + (priority (and (not no-priority) (match-string 3))) + (headline (pcase (match-string 4) + (`nil "") + ((and (guard no-comment) h) + (replace-regexp-in-string + (eval-when-compile + (format "\\`%s[ \t]+" org-comment-string)) + "" h)) + (h h))) + (tags (and (not no-tags) (match-string 5)))) + (mapconcat #'identity + (delq nil (list todo priority headline tags)) + " "))))) (defvar orgstruct-mode) ; defined below @@ -7780,24 +7881,24 @@ This is a list with the following elements: - the tags string, or nil." (save-excursion (org-back-to-heading t) - (if (let (case-fold-search) - (looking-at - (if orgstruct-mode - org-heading-regexp - org-complex-heading-regexp))) - (if orgstruct-mode - (list (length (match-string 1)) - (org-reduced-level (length (match-string 1))) - nil - nil - (match-string 2) - nil) - (list (length (match-string 1)) - (org-reduced-level (length (match-string 1))) - (org-match-string-no-properties 2) - (and (match-end 3) (aref (match-string 3) 2)) - (org-match-string-no-properties 4) - (org-match-string-no-properties 5)))))) + (when (let (case-fold-search) + (looking-at + (if orgstruct-mode + org-heading-regexp + org-complex-heading-regexp))) + (if orgstruct-mode + (list (length (match-string 1)) + (org-reduced-level (length (match-string 1))) + nil + nil + (match-string 2) + nil) + (list (length (match-string 1)) + (org-reduced-level (length (match-string 1))) + (match-string-no-properties 2) + (and (match-end 3) (aref (match-string 3) 2)) + (match-string-no-properties 4) + (match-string-no-properties 5)))))) (defun org-get-entry () "Get the entry text, after heading, entire subtree." @@ -7805,6 +7906,24 @@ This is a list with the following elements: (org-back-to-heading t) (buffer-substring (point-at-bol 2) (org-end-of-subtree t)))) +(defun org-edit-headline (&optional heading) + "Edit the current headline. +Set it to HEADING when provided." + (interactive) + (org-with-wide-buffer + (org-back-to-heading t) + (let ((case-fold-search nil)) + (when (looking-at org-complex-heading-regexp) + (let* ((old (match-string-no-properties 4)) + (new (save-match-data + (org-trim (or heading (read-string "Edit: " old)))))) + (unless (equal old new) + (if old (replace-match new t t nil 4) + (goto-char (or (match-end 3) (match-end 2) (match-end 1))) + (insert " " new)) + (org-set-tags nil t) + (when (looking-at "[ \t]*$") (replace-match "")))))))) + (defun org-insert-heading-after-current () "Insert a new heading with same level as current, after current subtree." (interactive) @@ -7825,29 +7944,32 @@ This is a list with the following elements: (defun org-insert-todo-heading (arg &optional force-heading) "Insert a new heading with the same level and TODO state as current heading. -If the heading has no TODO state, or if the state is DONE, use the first -state (TODO by default). Also with one prefix arg, force first state. With -two prefix args, force inserting at the end of the parent subtree." + +If the heading has no TODO state, or if the state is DONE, use +the first state (TODO by default). Also with one prefix arg, +force first state. With two prefix args, force inserting at the +end of the parent subtree. + +When called at a plain list item, insert a new item with an +unchecked check box." (interactive "P") (when (or force-heading (not (org-insert-item 'checkbox))) (org-insert-heading (or (and (equal arg '(16)) '(16)) force-heading)) (save-excursion - (org-back-to-heading) - (outline-previous-heading) - (looking-at org-todo-line-regexp)) - (let* - ((new-mark-x - (if (or (equal arg '(4)) - (not (match-beginning 2)) - (member (match-string 2) org-done-keywords)) - (car org-todo-keywords-1) - (match-string 2))) - (new-mark - (or - (run-hook-with-args-until-success - 'org-todo-get-default-hook new-mark-x nil) - new-mark-x))) + (org-forward-heading-same-level -1) + (let ((case-fold-search nil)) (looking-at org-todo-line-regexp))) + (let* ((new-mark-x + (if (or (equal arg '(4)) + (not (match-beginning 2)) + (member (match-string 2) org-done-keywords)) + (car org-todo-keywords-1) + (match-string 2))) + (new-mark + (or + (run-hook-with-args-until-success + 'org-todo-get-default-hook new-mark-x nil) + new-mark-x))) (beginning-of-line 1) (and (looking-at org-outline-regexp) (goto-char (match-end 0)) (if org-treat-insert-todo-heading-as-state-change @@ -7895,18 +8017,17 @@ See also `org-promote'." (org-fix-position-after-promote)) (defun org-demote-subtree () - "Demote the entire subtree. See `org-demote'. -See also `org-promote'." + "Demote the entire subtree. +See `org-demote' and `org-promote'." (interactive) (save-excursion (org-with-limited-levels (org-map-tree 'org-demote))) (org-fix-position-after-promote)) - (defun org-do-promote () "Promote the current heading higher up the tree. -If the region is active in `transient-mark-mode', promote all headings -in the region." +If the region is active in `transient-mark-mode', promote all +headings in the region." (interactive) (save-excursion (if (org-region-active-p) @@ -7916,8 +8037,8 @@ in the region." (defun org-do-demote () "Demote the current heading lower down the tree. -If the region is active in `transient-mark-mode', demote all headings -in the region." +If the region is active in `transient-mark-mode', demote all +headings in the region." (interactive) (save-excursion (if (org-region-active-p) @@ -7926,23 +8047,24 @@ in the region." (org-fix-position-after-promote)) (defun org-fix-position-after-promote () - "Make sure that after pro/demotion cursor position is right." + "Fix cursor position and indentation after demoting/promoting." (let ((pos (point))) (when (save-excursion - (beginning-of-line 1) - (looking-at org-todo-line-regexp) - (or (equal pos (match-end 1)) (equal pos (match-end 2)))) + (beginning-of-line) + (let ((case-fold-search nil)) (looking-at org-todo-line-regexp)) + (or (eq pos (match-end 1)) (eq pos (match-end 2)))) (cond ((eobp) (insert " ")) ((eolp) (insert " ")) - ((equal (char-after) ?\ ) (forward-char 1)))))) + ((equal (char-after) ?\s) (forward-char 1)))))) (defun org-current-level () "Return the level of the current entry, or nil if before the first headline. -The level is the number of stars at the beginning of the headline." - (save-excursion - (org-with-limited-levels - (if (ignore-errors (org-back-to-heading t)) - (funcall outline-level))))) +The level is the number of stars at the beginning of the +headline. Use `org-reduced-level' to remove the effect of +`org-odd-levels'. Unlike to `org-outline-level', this function +ignores inlinetasks." + (let ((level (org-with-limited-levels (org-outline-level)))) + (and (> level 0) level))) (defun org-get-previous-line-level () "Return the outline depth of the last headline before the current line. @@ -7968,60 +8090,50 @@ time to headlines when structure editing, based on the value of (if org-odd-levels-only 2 1)) (defun org-get-valid-level (level &optional change) - "Rectify a level change under the influence of `org-odd-levels-only' -LEVEL is a current level, CHANGE is by how much the level should be -modified. Even if CHANGE is nil, LEVEL may be returned modified because -even level numbers will become the next higher odd number." + "Rectify a level change under the influence of `org-odd-levels-only'. +LEVEL is a current level, CHANGE is by how much the level should +be modified. Even if CHANGE is nil, LEVEL may be returned +modified because even level numbers will become the next higher +odd number. Returns values greater than 0." (if org-odd-levels-only (cond ((or (not change) (= 0 change)) (1+ (* 2 (/ level 2)))) - ((> change 0) (1+ (* 2 (/ (+ level (* 2 change)) 2)))) + ((> change 0) (1+ (* 2 (/ (+ (1- level) (* 2 change)) 2)))) ((< change 0) (max 1 (1+ (* 2 (/ (+ level (* 2 change)) 2)))))) (max 1 (+ level (or change 0))))) -(if (boundp 'define-obsolete-function-alias) - (if (or (featurep 'xemacs) (< emacs-major-version 23)) - (define-obsolete-function-alias 'org-get-legal-level - 'org-get-valid-level) - (define-obsolete-function-alias 'org-get-legal-level - 'org-get-valid-level "23.1"))) - (defun org-promote () - "Promote the current heading higher up the tree. -If the region is active in `transient-mark-mode', promote all headings -in the region." - (org-back-to-heading t) - (let* ((level (save-match-data (funcall outline-level))) - (after-change-functions (remove 'flyspell-after-change-function - after-change-functions)) - (up-head (concat (make-string (org-get-valid-level level -1) ?*) " ")) - (diff (abs (- level (length up-head) -1)))) - (cond ((and (= level 1) org-called-with-limited-levels - org-allow-promoting-top-level-subtree) - (replace-match "# " nil t)) - ((= level 1) - (user-error "Cannot promote to level 0. UNDO to recover if necessary")) - (t (replace-match up-head nil t))) - ;; Fixup tag positioning - (unless (= level 1) - (and org-auto-align-tags (org-set-tags nil 'ignore-column)) - (if org-adapt-indentation (org-fixup-indentation (- diff)))) - (run-hooks 'org-after-promote-entry-hook))) + "Promote the current heading higher up the tree." + (org-with-wide-buffer + (org-back-to-heading t) + (let* ((after-change-functions (remq 'flyspell-after-change-function + after-change-functions)) + (level (save-match-data (funcall outline-level))) + (up-head (concat (make-string (org-get-valid-level level -1) ?*) " ")) + (diff (abs (- level (length up-head) -1)))) + (cond + ((and (= level 1) org-allow-promoting-top-level-subtree) + (replace-match "# " nil t)) + ((= level 1) + (user-error "Cannot promote to level 0. UNDO to recover if necessary")) + (t (replace-match up-head nil t))) + (unless (= level 1) + (when org-auto-align-tags (org-set-tags nil 'ignore-column)) + (when org-adapt-indentation (org-fixup-indentation (- diff)))) + (run-hooks 'org-after-promote-entry-hook)))) (defun org-demote () - "Demote the current heading lower down the tree. -If the region is active in `transient-mark-mode', demote all headings -in the region." - (org-back-to-heading t) - (let* ((level (save-match-data (funcall outline-level))) - (after-change-functions (remove 'flyspell-after-change-function - after-change-functions)) - (down-head (concat (make-string (org-get-valid-level level 1) ?*) " ")) - (diff (abs (- level (length down-head) -1)))) - (replace-match down-head nil t) - ;; Fixup tag positioning - (and org-auto-align-tags (org-set-tags nil 'ignore-column)) - (if org-adapt-indentation (org-fixup-indentation diff)) - (run-hooks 'org-after-demote-entry-hook))) + "Demote the current heading lower down the tree." + (org-with-wide-buffer + (org-back-to-heading t) + (let* ((after-change-functions (remq 'flyspell-after-change-function + after-change-functions)) + (level (save-match-data (funcall outline-level))) + (down-head (concat (make-string (org-get-valid-level level 1) ?*) " ")) + (diff (abs (- level (length down-head) -1)))) + (replace-match down-head nil t) + (when org-auto-align-tags (org-set-tags nil 'ignore-column)) + (when org-adapt-indentation (org-fixup-indentation diff)) + (run-hooks 'org-after-demote-entry-hook)))) (defun org-cycle-level () "Cycle the level of an empty headline through possible states. @@ -8036,32 +8148,32 @@ After top level, it switches back to sibling level." (cond ;; If first headline in file, promote to top-level. ((= prev-level 0) - (loop repeat (/ (- cur-level 1) (org-level-increment)) - do (org-do-promote))) + (cl-loop repeat (/ (- cur-level 1) (org-level-increment)) + do (org-do-promote))) ;; If same level as prev, demote one. ((= prev-level cur-level) (org-do-demote)) ;; If parent is top-level, promote to top level if not already. ((= prev-level 1) - (loop repeat (/ (- cur-level 1) (org-level-increment)) - do (org-do-promote))) + (cl-loop repeat (/ (- cur-level 1) (org-level-increment)) + do (org-do-promote))) ;; If top-level, return to prev-level. ((= cur-level 1) - (loop repeat (/ (- prev-level 1) (org-level-increment)) - do (org-do-demote))) + (cl-loop repeat (/ (- prev-level 1) (org-level-increment)) + do (org-do-demote))) ;; If less than prev-level, promote one. ((< cur-level prev-level) (org-do-promote)) ;; If deeper than prev-level, promote until higher than ;; prev-level. ((> cur-level prev-level) - (loop repeat (+ 1 (/ (- cur-level prev-level) (org-level-increment))) - do (org-do-promote)))) + (cl-loop repeat (+ 1 (/ (- cur-level prev-level) (org-level-increment))) + do (org-do-promote)))) t)))) (defun org-map-tree (fun) "Call FUN for every heading underneath the current one." - (org-back-to-heading) + (org-back-to-heading t) (let ((level (funcall outline-level))) (save-excursion (funcall fun) @@ -8077,39 +8189,123 @@ After top level, it switches back to sibling level." (save-excursion (setq end (copy-marker end)) (goto-char beg) - (if (and (re-search-forward org-outline-regexp-bol nil t) - (< (point) end)) - (funcall fun)) + (when (and (re-search-forward org-outline-regexp-bol nil t) + (< (point) end)) + (funcall fun)) (while (and (progn (outline-next-heading) (< (point) end)) (not (eobp))) (funcall fun))))) -(defvar org-property-end-re) ; silence byte-compiler (defun org-fixup-indentation (diff) "Change the indentation in the current entry by DIFF. -However, if any line in the current entry has no indentation, or if it -would end up with no indentation after the change, nothing at all is done." - (save-excursion - (let ((end (save-excursion (outline-next-heading) - (point-marker))) - (prohibit (if (> diff 0) - "^\\S-" - (concat "^ \\{0," (int-to-string (- diff)) "\\}\\S-"))) - col) - (unless (save-excursion (end-of-line 1) - (re-search-forward prohibit end t)) - (while (and (< (point) end) - (re-search-forward "^[ \t]+" end t)) - (goto-char (match-end 0)) - (setq col (current-column)) - (if (< diff 0) (replace-match "")) - (org-indent-to-column (+ diff col)))) - (move-marker end nil)))) + +DIFF is an integer. Indentation is done according to the +following rules: + + - Planning information and property drawers are always indented + according to the new level of the headline; + + - Footnote definitions and their contents are ignored; + + - Inlinetasks' boundaries are not shifted; + + - Empty lines are ignored; + + - Other lines' indentation are shifted by DIFF columns, unless + it would introduce a structural change in the document, in + which case no shifting is done at all. + +Assume point is at a heading or an inlinetask beginning." + (org-with-wide-buffer + (narrow-to-region (line-beginning-position) + (save-excursion + (if (org-with-limited-levels (org-at-heading-p)) + (org-with-limited-levels (outline-next-heading)) + (org-inlinetask-goto-end)) + (point))) + (forward-line) + ;; Indent properly planning info and property drawer. + (when (looking-at-p org-planning-line-re) + (org-indent-line) + (forward-line)) + (when (looking-at org-property-drawer-re) + (goto-char (match-end 0)) + (forward-line) + (save-excursion (org-indent-region (match-beginning 0) (match-end 0)))) + (catch 'no-shift + (when (zerop diff) (throw 'no-shift nil)) + ;; If DIFF is negative, first check if a shift is possible at all + ;; (e.g., it doesn't break structure). This can only happen if + ;; some contents are not properly indented. + (let ((case-fold-search t)) + (when (< diff 0) + (let ((diff (- diff)) + (forbidden-re (concat org-outline-regexp + "\\|" + (substring org-footnote-definition-re 1)))) + (save-excursion + (while (not (eobp)) + (cond + ((looking-at-p "[ \t]*$") (forward-line)) + ((and (looking-at-p org-footnote-definition-re) + (let ((e (org-element-at-point))) + (and (eq (org-element-type e) 'footnote-definition) + (goto-char (org-element-property :end e)))))) + ((looking-at-p org-outline-regexp) (forward-line)) + ;; Give up if shifting would move before column 0 or + ;; if it would introduce a headline or a footnote + ;; definition. + (t + (skip-chars-forward " \t") + (let ((ind (current-column))) + (when (or (< ind diff) + (and (= ind diff) (looking-at-p forbidden-re))) + (throw 'no-shift nil))) + ;; Ignore contents of example blocks and source + ;; blocks if their indentation is meant to be + ;; preserved. Jump to block's closing line. + (beginning-of-line) + (or (and (looking-at-p "[ \t]*#\\+BEGIN_\\(EXAMPLE\\|SRC\\)") + (let ((e (org-element-at-point))) + (and (memq (org-element-type e) + '(example-block src-block)) + (or org-src-preserve-indentation + (org-element-property :preserve-indent e)) + (goto-char (org-element-property :end e)) + (progn (skip-chars-backward " \r\t\n") + (beginning-of-line) + t)))) + (forward-line)))))))) + ;; Shift lines but footnote definitions, inlinetasks boundaries + ;; by DIFF. Also skip contents of source or example blocks + ;; when indentation is meant to be preserved. + (while (not (eobp)) + (cond + ((and (looking-at-p org-footnote-definition-re) + (let ((e (org-element-at-point))) + (and (eq (org-element-type e) 'footnote-definition) + (goto-char (org-element-property :end e)))))) + ((looking-at-p org-outline-regexp) (forward-line)) + ((looking-at-p "[ \t]*$") (forward-line)) + (t + (indent-line-to (+ (org-get-indentation) diff)) + (beginning-of-line) + (or (and (looking-at-p "[ \t]*#\\+BEGIN_\\(EXAMPLE\\|SRC\\)") + (let ((e (org-element-at-point))) + (and (memq (org-element-type e) + '(example-block src-block)) + (or org-src-preserve-indentation + (org-element-property :preserve-indent e)) + (goto-char (org-element-property :end e)) + (progn (skip-chars-backward " \r\t\n") + (beginning-of-line) + t)))) + (forward-line))))))))) (defun org-convert-to-odd-levels () - "Convert an org-mode file with all levels allowed to one with odd levels. + "Convert an Org file with all levels allowed to one with odd levels. This will leave level 1 alone, convert level 2 to level 3, level 3 to level 5 etc." (interactive) @@ -8125,7 +8321,7 @@ level 5 etc." (end-of-line 1)))))) (defun org-convert-to-oddeven-levels () - "Convert an org-mode file with only odd levels to one with odd/even levels. + "Convert an Org file with only odd levels to one with odd/even levels. This promotes level 3 to level 2, level 5 to level 3 etc. If the file contains a section with an even level, conversion would destroy the structure of the file. An error is signaled in this @@ -8134,7 +8330,7 @@ case." (goto-char (point-min)) ;; First check if there are no even levels (when (re-search-forward "^\\(\\*\\*\\)+ " nil t) - (org-show-context t) + (org-show-set-visibility 'canonical) (error "Not all levels are odd in this file. Conversion not possible")) (when (yes-or-no-p "Are you sure you want to globally change levels to odd-even? ") (let ((outline-regexp org-outline-regexp) @@ -8177,7 +8373,7 @@ case." (setq beg (point))) (save-match-data (save-excursion (outline-end-of-heading) - (setq folded (outline-invisible-p))) + (setq folded (org-invisible-p))) (progn (org-end-of-subtree nil t) (unless (eobp) (backward-char)))) (outline-next-heading) @@ -8196,12 +8392,12 @@ case." (progn (goto-char beg0) (user-error "Cannot move past superior level or buffer limit"))) (setq cnt (1- cnt))) - (if (> arg 0) - ;; Moving forward - still need to move over subtree - (progn (org-end-of-subtree t t) - (save-excursion - (org-back-over-empty-lines) - (or (bolp) (newline))))) + (when (> arg 0) + ;; Moving forward - still need to move over subtree + (org-end-of-subtree t t) + (save-excursion + (org-back-over-empty-lines) + (or (bolp) (newline)))) (setq ne-ins (org-back-over-empty-lines)) (move-marker ins-point (point)) (setq txt (buffer-substring beg end)) @@ -8230,9 +8426,9 @@ case." (insert (make-string (- ne-ins ne-beg) ?\n))) (move-marker ins-point nil) (if folded - (hide-subtree) + (outline-hide-subtree) (org-show-entry) - (show-children) + (org-show-children) (org-cycle-hide-drawers 'children)) (org-clean-visibility-after-subtree-move) ;; move back to the initial column we were at @@ -8264,7 +8460,7 @@ of some markers in the region, even if CUT is non-nil. This is useful if the caller implements cut-and-paste as copy-then-paste-then-cut." (interactive "p") (let (beg end folded (beg0 (point))) - (if (org-called-interactively-p 'any) + (if (called-interactively-p 'any) (org-back-to-heading nil) ; take what looks like a subtree (org-back-to-heading t)) ; take what is really there (setq beg (point)) @@ -8273,11 +8469,14 @@ useful if the caller implements cut-and-paste as copy-then-paste-then-cut." (if nosubtrees (outline-next-heading) (save-excursion (outline-end-of-heading) - (setq folded (outline-invisible-p))) - (condition-case nil - (org-forward-heading-same-level (1- n) t) - (error nil)) + (setq folded (org-invisible-p))) + (ignore-errors (org-forward-heading-same-level (1- n) t)) (org-end-of-subtree t t))) + ;; Include the end of an inlinetask + (when (and (featurep 'org-inlinetask) + (looking-at-p (concat (org-inlinetask-outline-regexp) + "END[ \t]*$"))) + (end-of-line)) (setq end (point)) (goto-char beg0) (when (> end beg) @@ -8290,7 +8489,7 @@ useful if the caller implements cut-and-paste as copy-then-paste-then-cut." (if cut "Cut" "Copied") (length org-subtree-clip))))) -(defun org-paste-subtree (&optional level tree for-yank) +(defun org-paste-subtree (&optional level tree for-yank remove) "Paste the clipboard as a subtree, with modification of headline level. The entire subtree is promoted or demoted in order to match a new headline level. @@ -8313,15 +8512,17 @@ If optional TREE is given, use this text instead of the kill ring. When FOR-YANK is set, this is called by `org-yank'. In this case, do not move back over whitespace before inserting, and move point to the end of -the inserted text when done." +the inserted text when done. + +When REMOVE is non-nil, remove the subtree from the clipboard." (interactive "P") (setq tree (or tree (and kill-ring (current-kill 0)))) (unless (org-kill-is-subtree-p tree) (user-error "%s" - (substitute-command-keys - "The kill is not a (set of) tree(s) - please use \\[yank] to yank anyway"))) + (substitute-command-keys + "The kill is not a (set of) tree(s) - please use \\[yank] to yank anyway"))) (org-with-limited-levels - (let* ((visp (not (outline-invisible-p))) + (let* ((visp (not (org-invisible-p))) (txt tree) (^re_ "\\(\\*+\\)[ \t]*") (old-level (if (string-match org-outline-regexp-bol txt) @@ -8364,22 +8565,22 @@ the inserted text when done." (org-odd-levels-only nil) beg end newend) ;; Remove the forced level indicator - (if force-level - (delete-region (point-at-bol) (point))) + (when force-level + (delete-region (point-at-bol) (point))) ;; Paste (beginning-of-line (if (bolp) 1 2)) (setq beg (point)) (and (fboundp 'org-id-paste-tracker) (org-id-paste-tracker txt)) (insert-before-markers txt) - (unless (string-match "\n\\'" txt) (insert "\n")) + (unless (string-suffix-p "\n" txt) (insert "\n")) (setq newend (point)) (org-reinstall-markers-in-region beg) (setq end (point)) (goto-char beg) (skip-chars-forward " \t\n\r") (setq beg (point)) - (if (and (outline-invisible-p) visp) - (save-excursion (outline-show-heading))) + (when (and (org-invisible-p) visp) + (save-excursion (outline-show-heading))) ;; Shift if necessary (unless (= shift 0) (save-restriction @@ -8389,15 +8590,16 @@ the inserted text when done." (setq shift (+ delta shift))) (goto-char (point-min)) (setq newend (point-max)))) - (when (or (org-called-interactively-p 'interactive) for-yank) + (when (or (called-interactively-p 'interactive) for-yank) (message "Clipboard pasted as level %d subtree" new-level)) - (if (and (not for-yank) ; in this case, org-yank will decide about folding - kill-ring - (eq org-subtree-clip (current-kill 0)) - org-subtree-clip-folded) - ;; The tree was folded before it was killed/copied - (hide-subtree)) - (and for-yank (goto-char newend))))) + (when (and (not for-yank) ; in this case, org-yank will decide about folding + kill-ring + (eq org-subtree-clip (current-kill 0)) + org-subtree-clip-folded) + ;; The tree was folded before it was killed/copied + (outline-hide-subtree)) + (and for-yank (goto-char newend)) + (and remove (setq kill-ring (cdr kill-ring)))))) (defun org-kill-is-subtree-p (&optional txt) "Check if the current kill is an outline subtree, or a set of trees. @@ -8447,15 +8649,14 @@ called immediately, to move the markers with the entries." "Check if MARKER is between BEG and END. If yes, remember the marker and the distance to BEG." (when (and (marker-buffer marker) - (equal (marker-buffer marker) (current-buffer))) - (if (and (>= marker beg) (< marker end)) - (push (cons marker (- marker beg)) org-markers-to-move)))) + (equal (marker-buffer marker) (current-buffer)) + (>= marker beg) (< marker end)) + (push (cons marker (- marker beg)) org-markers-to-move))) (defun org-reinstall-markers-in-region (beg) "Move all remembered markers to their position relative to BEG." - (mapc (lambda (x) - (move-marker (car x) (+ beg (cdr x)))) - org-markers-to-move) + (dolist (x org-markers-to-move) + (move-marker (car x) (+ beg (cdr x)))) (setq org-markers-to-move nil)) (defun org-narrow-to-subtree () @@ -8467,7 +8668,7 @@ If yes, remember the marker and the distance to BEG." (narrow-to-region (progn (org-back-to-heading t) (point)) (progn (org-end-of-subtree t t) - (if (and (org-at-heading-p) (not (eobp))) (backward-char 1)) + (when (and (org-at-heading-p) (not (eobp))) (backward-char 1)) (point))))))) (defun org-narrow-to-block () @@ -8480,10 +8681,6 @@ If yes, remember the marker and the distance to BEG." (narrow-to-region (car blockp) (cdr blockp)) (user-error "Not in a block")))) -(eval-when-compile - (defvar org-property-drawer-re)) - -(defvar org-property-start-re) ;; defined below (defun org-clone-subtree-with-time-shift (n &optional shift) "Clone the task (subtree) at point N times. The clones will be inserted as siblings. @@ -8500,6 +8697,9 @@ stamps in the subtree shifted for each clone produced. If SHIFT is nil or the empty string, time stamps will be left alone. The ID property of the original subtree is removed. +In each clone, all the CLOCK entries will be removed. This +prevents Org from considering that the clocked times overlap. + If the original subtree did contain time stamps with a repeater, the following will happen: - the repeater will be removed in each clone @@ -8510,107 +8710,109 @@ the following will happen: - the start days in the repeater in the original entry will be shifted to past the last clone. In this way you can spell out a number of instances of a repeating task, -and still retain the repeater to cover future instances of the task." +and still retain the repeater to cover future instances of the task. + +As described above, N+1 clones are produced when the original +subtree has a repeater. Setting N to 0, then, can be used to +remove the repeater from a subtree and create a shifted clone +with the original repeater." (interactive "nNumber of clones to produce: ") - (let ((shift - (or shift - (if (and (not (equal current-prefix-arg '(4))) - (save-excursion - (re-search-forward org-ts-regexp-both - (save-excursion - (org-end-of-subtree t) - (point)) t))) - (read-from-minibuffer - "Date shift per clone (e.g. +1w, empty to copy unchanged): ") - ""))) ;; No time shift - (n-no-remove -1) - (drawer-re org-drawer-regexp) - beg end template task idprop - shift-n shift-what doshift nmin nmax) - (if (not (and (integerp n) (> n 0))) - (error "Invalid number of replications %s" n)) - (if (and (setq doshift (and (stringp shift) (string-match "\\S-" shift))) - (not (string-match "\\`[ \t]*\\+?\\([0-9]+\\)\\([hdwmy]\\)[ \t]*\\'" - shift))) - (error "Invalid shift specification %s" shift)) - (when doshift - (setq shift-n (string-to-number (match-string 1 shift)) - shift-what (cdr (assoc (match-string 2 shift) - '(("d" . day) ("w" . week) - ("m" . month) ("y" . year)))))) - (if (eq shift-what 'week) (setq shift-n (* 7 shift-n) shift-what 'day)) - (setq nmin 1 nmax n) - (org-back-to-heading t) - (setq beg (point)) - (setq idprop (org-entry-get nil "ID")) - (org-end-of-subtree t t) - (or (bolp) (insert "\n")) - (setq end (point)) - (setq template (buffer-substring beg end)) - (when (and doshift - (string-match "<[^<>\n]+ [.+]?\\+[0-9]+[hdwmy][^<>\n]*>" template)) - (delete-region beg end) - (setq end beg) - (setq nmin 0 nmax (1+ nmax) n-no-remove nmax)) - (goto-char end) - (loop for n from nmin to nmax do - ;; prepare clone - (with-temp-buffer - (insert template) - (org-mode) - (goto-char (point-min)) - (org-show-subtree) - (and idprop (if org-clone-delete-id - (org-entry-delete nil "ID") - (org-id-get-create t))) - (unless (= n 0) - (while (re-search-forward "^[ \t]*CLOCK:.*$" nil t) - (kill-whole-line)) - (goto-char (point-min)) - (while (re-search-forward drawer-re nil t) - (mapc (lambda (d) - (org-remove-empty-drawer-at d (point))) - org-drawers))) - (goto-char (point-min)) - (when doshift - (while (re-search-forward org-ts-regexp-both nil t) - (org-timestamp-change (* n shift-n) shift-what)) - (unless (= n n-no-remove) - (goto-char (point-min)) - (while (re-search-forward org-ts-regexp nil t) - (save-excursion - (goto-char (match-beginning 0)) - (if (looking-at "<[^<>\n]+\\( +[.+]?\\+[0-9]+[hdwmy]\\)") - (delete-region (match-beginning 1) (match-end 1))))))) - (setq task (buffer-string))) - (insert task)) + (unless (wholenump n) (user-error "Invalid number of replications %s" n)) + (when (org-before-first-heading-p) (user-error "No subtree to clone")) + (let* ((beg (save-excursion (org-back-to-heading t) (point))) + (end-of-tree (save-excursion (org-end-of-subtree t t) (point))) + (shift + (or shift + (if (and (not (equal current-prefix-arg '(4))) + (save-excursion + (goto-char beg) + (re-search-forward org-ts-regexp-both end-of-tree t))) + (read-from-minibuffer + "Date shift per clone (e.g. +1w, empty to copy unchanged): ") + ""))) ;No time shift + (doshift + (and (org-string-nw-p shift) + (or (string-match "\\`[ \t]*\\+?\\([0-9]+\\)\\([dwmy]\\)[ \t]*\\'" + shift) + (user-error "Invalid shift specification %s" shift))))) + (goto-char end-of-tree) + (unless (bolp) (insert "\n")) + (let* ((end (point)) + (template (buffer-substring beg end)) + (shift-n (and doshift (string-to-number (match-string 1 shift)))) + (shift-what (pcase (and doshift (match-string 2 shift)) + (`nil nil) + ("d" 'day) + ("w" (setq shift-n (* 7 shift-n)) 'day) + ("m" 'month) + ("y" 'year) + (_ (error "Unsupported time unit")))) + (nmin 1) + (nmax n) + (n-no-remove -1) + (idprop (org-entry-get nil "ID"))) + (when (and doshift + (string-match-p "<[^<>\n]+ [.+]?\\+[0-9]+[hdwmy][^<>\n]*>" + template)) + (delete-region beg end) + (setq end beg) + (setq nmin 0) + (setq nmax (1+ nmax)) + (setq n-no-remove nmax)) + (goto-char end) + (cl-loop for n from nmin to nmax do + (insert + ;; Prepare clone. + (with-temp-buffer + (insert template) + (org-mode) + (goto-char (point-min)) + (org-show-subtree) + (and idprop (if org-clone-delete-id + (org-entry-delete nil "ID") + (org-id-get-create t))) + (unless (= n 0) + (while (re-search-forward org-clock-line-re nil t) + (delete-region (line-beginning-position) + (line-beginning-position 2))) + (goto-char (point-min)) + (while (re-search-forward org-drawer-regexp nil t) + (org-remove-empty-drawer-at (point)))) + (goto-char (point-min)) + (when doshift + (while (re-search-forward org-ts-regexp-both nil t) + (org-timestamp-change (* n shift-n) shift-what)) + (unless (= n n-no-remove) + (goto-char (point-min)) + (while (re-search-forward org-ts-regexp nil t) + (save-excursion + (goto-char (match-beginning 0)) + (when (looking-at "<[^<>\n]+\\( +[.+]?\\+[0-9]+[hdwmy]\\)") + (delete-region (match-beginning 1) (match-end 1))))))) + (buffer-string))))) (goto-char beg))) ;;; Outline Sorting -(defun org-sort (with-case) +(defun org-sort (&optional with-case) "Call `org-sort-entries', `org-table-sort-lines' or `org-sort-list'. Optional argument WITH-CASE means sort case-sensitively." (interactive "P") - (cond - ((org-at-table-p) (org-call-with-arg 'org-table-sort-lines with-case)) - ((org-at-item-p) (org-call-with-arg 'org-sort-list with-case)) - (t - (org-call-with-arg 'org-sort-entries with-case)))) + (org-call-with-arg + (cond ((org-at-table-p) #'org-table-sort-lines) + ((org-at-item-p) #'org-sort-list) + (t #'org-sort-entries)) + with-case)) (defun org-sort-remove-invisible (s) - "Remove invisible links from string S." + "Remove invisible part of links and emphasis markers from string S." (remove-text-properties 0 (length s) org-rm-props s) - (while (string-match org-bracket-link-regexp s) - (setq s (replace-match (if (match-end 2) - (match-string 3 s) - (match-string 1 s)) - t t s))) - (let ((st (format " %s " s))) - (while (string-match org-emph-re st) - (setq st (replace-match (format " %s " (match-string 4 st)) t t st))) - (setq s (substring st 1 -1))) - s) + (replace-regexp-in-string + org-verbatim-re (lambda (m) (format "%s " (match-string 4 m))) + (replace-regexp-in-string + org-emph-re (lambda (m) (format " %s " (match-string 4 m))) + (org-link-display-format s) + t t) t t)) (defvar org-priority-regexp) ; defined later in the file @@ -8621,7 +8823,8 @@ hook gets called. When a region or a plain list is sorted, the cursor will be in the first entry of the sorted region/list.") (defun org-sort-entries - (&optional with-case sorting-type getkey-func compare-func property) + (&optional with-case sorting-type getkey-func compare-func property + interactive?) "Sort entries on a certain level of an outline tree. If there is an active region, the entries in the region are sorted. Else, if the cursor is before the first entry, sort the top-level items. @@ -8632,42 +8835,41 @@ a time stamp, by a property, by priority order, or by a custom function. The command prompts for the sorting type unless it has been given to the function through the SORTING-TYPE argument, which needs to be a character, -\(?n ?N ?a ?A ?t ?T ?s ?S ?d ?D ?p ?P ?o ?O ?r ?R ?f ?F). Here is the -precise meaning of each character: +\(?n ?N ?a ?A ?t ?T ?s ?S ?d ?D ?p ?P ?o ?O ?r ?R ?f ?F ?k ?K). Here is +the precise meaning of each character: -n Numerically, by converting the beginning of the entry/item to a number. a Alphabetically, ignoring the TODO keyword and the priority, if any. -o By order of TODO keywords. -t By date/time, either the first active time stamp in the entry, or, if - none exist, by the first inactive one. -s By the scheduled date/time. -d By deadline date/time. c By creation time, which is assumed to be the first inactive time stamp at the beginning of a line. +d By deadline date/time. +k By clocking time. +n Numerically, by converting the beginning of the entry/item to a number. +o By order of TODO keywords. p By priority according to the cookie. r By the value of a property. +s By scheduled date/time. +t By date/time, either the first active time stamp in the entry, or, if + none exist, by the first inactive one. Capital letters will reverse the sort order. If the SORTING-TYPE is ?f or ?F, then GETKEY-FUNC specifies a function to be -called with point at the beginning of the record. It must return either -a string or a number that should serve as the sorting key for that record. +called with point at the beginning of the record. It must return a +value that is compatible with COMPARE-FUNC, the function used to +compare entries. Comparing entries ignores case by default. However, with an optional argument WITH-CASE, the sorting considers case as well. Sorting is done against the visible part of the headlines, it ignores hidden -links." - (interactive "P") +links. + +When sorting is done, call `org-after-sorting-entries-or-items-hook'. + +A non-nil value for INTERACTIVE? is used to signal that this +function is being called interactively." + (interactive (list current-prefix-arg nil nil nil nil t)) (let ((case-func (if with-case 'identity 'downcase)) - (cmstr - ;; The clock marker is lost when using `sort-subr', let's - ;; store the clocking string. - (when (equal (marker-buffer org-clock-marker) (current-buffer)) - (save-excursion - (goto-char org-clock-marker) - (buffer-substring-no-properties (line-beginning-position) - (point))))) start beg end stars re re2 txt what tmp) ;; Find beginning and end of region to sort @@ -8677,10 +8879,10 @@ links." (setq end (region-end) what "region") (goto-char (region-beginning)) - (if (not (org-at-heading-p)) (outline-next-heading)) + (unless (org-at-heading-p) (outline-next-heading)) (setq start (point))) ((or (org-at-heading-p) - (condition-case nil (progn (org-back-to-heading) t) (error nil))) + (ignore-errors (progn (org-back-to-heading) t))) ;; we will sort the children of the current headline (org-back-to-heading) (setq start (point) @@ -8691,7 +8893,7 @@ links." (point)) what "children") (goto-char start) - (show-subtree) + (outline-show-subtree) (outline-next-heading)) (t ;; we will sort the top-level entries in this file @@ -8707,7 +8909,7 @@ links." (setq end (point-max)) (setq what "top-level") (goto-char start) - (show-all))) + (outline-show-all))) (setq beg (point)) (when (>= beg end) (goto-char start) (user-error "Nothing to sort")) @@ -8717,39 +8919,52 @@ links." re (concat "^" (regexp-quote stars) " +") re2 (concat "^" (regexp-quote (substring stars 0 -1)) "[ \t\n]") txt (buffer-substring beg end)) - (if (not (equal (substring txt -1) "\n")) (setq txt (concat txt "\n"))) - (if (and (not (equal stars "*")) (string-match re2 txt)) - (user-error "Region to sort contains a level above the first entry")) + (unless (equal (substring txt -1) "\n") (setq txt (concat txt "\n"))) + (when (and (not (equal stars "*")) (string-match re2 txt)) + (user-error "Region to sort contains a level above the first entry")) (unless sorting-type (message "Sort %s: [a]lpha [n]umeric [p]riority p[r]operty todo[o]rder [f]unc - [t]ime [s]cheduled [d]eadline [c]reated - A/N/P/R/O/F/T/S/D/C means reversed:" + [t]ime [s]cheduled [d]eadline [c]reated cloc[k]ing + A/N/P/R/O/F/T/S/D/C/K means reversed:" what) - (setq sorting-type (read-char-exclusive)) - - (unless getkey-func - (and (= (downcase sorting-type) ?f) - (setq getkey-func - (org-icompleting-read "Sort using function: " - obarray 'fboundp t nil nil)) - (setq getkey-func (intern getkey-func)))) - - (and (= (downcase sorting-type) ?r) - (not property) - (setq property - (org-icompleting-read "Property: " - (mapcar 'list (org-buffer-property-keys t)) - nil t)))) - + (setq sorting-type (read-char-exclusive))) + + (unless getkey-func + (and (= (downcase sorting-type) ?f) + (setq getkey-func + (or (and interactive? + (org-read-function + "Function for extracting keys: ")) + (error "Missing key extractor"))))) + + (and (= (downcase sorting-type) ?r) + (not property) + (setq property + (completing-read "Property: " + (mapcar #'list (org-buffer-property-keys t)) + nil t))) + + (when (member sorting-type '(?k ?K)) (org-clock-sum)) (message "Sorting entries...") (save-restriction (narrow-to-region start end) - (let ((dcst (downcase sorting-type)) + (let ((restore-clock? + ;; The clock marker is lost when using `sort-subr'; mark + ;; the clock with temporary `:org-clock-marker-backup' + ;; text property. + (when (and (eq (org-clock-is-active) (current-buffer)) + (<= start (marker-position org-clock-marker)) + (>= end (marker-position org-clock-marker))) + (org-with-silent-modifications + (put-text-property (1- org-clock-marker) org-clock-marker + :org-clock-marker-backup t)) + t)) + (dcst (downcase sorting-type)) (case-fold-search nil) - (now (current-time))) + (now (current-time))) (sort-subr (/= dcst sorting-type) ;; This function moves to the beginning character of the "record" to @@ -8777,6 +8992,8 @@ links." (if (looking-at org-complex-heading-regexp) (funcall case-func (org-sort-remove-invisible (match-string 4))) nil)) + ((= dcst ?k) + (or (get-text-property (point) :org-clock-minutes) 0)) ((= dcst ?t) (let ((end (save-excursion (outline-next-heading) (point)))) (if (or (re-search-forward org-ts-regexp end t) @@ -8807,85 +9024,50 @@ links." ((= dcst ?r) (or (org-entry-get nil property) "")) ((= dcst ?o) - (if (looking-at org-complex-heading-regexp) - (- 9999 (length (member (match-string 2) - org-todo-keywords-1))))) + (when (looking-at org-complex-heading-regexp) + (let* ((m (match-string 2)) + (s (if (member m org-done-keywords) '- '+))) + (- 99 (funcall s (length (member m org-todo-keywords-1))))))) ((= dcst ?f) (if getkey-func (progn (setq tmp (funcall getkey-func)) - (if (stringp tmp) (setq tmp (funcall case-func tmp))) + (when (stringp tmp) (setq tmp (funcall case-func tmp))) tmp) (error "Invalid key function `%s'" getkey-func))) (t (error "Invalid sorting type `%c'" sorting-type)))) nil (cond ((= dcst ?a) 'string<) - ((= dcst ?f) compare-func) - ((member dcst '(?p ?t ?s ?d ?c)) '<))))) + ((= dcst ?f) + (or compare-func + (and interactive? + (org-read-function + (concat "Function for comparing keys " + "(empty for default `sort-subr' predicate): ") + 'allow-empty)))) + ((member dcst '(?p ?t ?s ?d ?c ?k)) '<))) + (when restore-clock? + (move-marker org-clock-marker + (1+ (next-single-property-change + start :org-clock-marker-backup))) + (remove-text-properties (1- org-clock-marker) org-clock-marker + '(:org-clock-marker-backup t))))) (run-hooks 'org-after-sorting-entries-or-items-hook) - ;; Reset the clock marker if needed - (when cmstr - (save-excursion - (goto-char start) - (search-forward cmstr nil t) - (move-marker org-clock-marker (point)))) (message "Sorting entries...done"))) -(defun org-do-sort (table what &optional with-case sorting-type) - "Sort TABLE of WHAT according to SORTING-TYPE. -The user will be prompted for the SORTING-TYPE if the call to this -function does not specify it. WHAT is only for the prompt, to indicate -what is being sorted. The sorting key will be extracted from -the car of the elements of the table. -If WITH-CASE is non-nil, the sorting will be case-sensitive." - (unless sorting-type - (message - "Sort %s: [a]lphabetic, [n]umeric, [t]ime. A/N/T means reversed:" - what) - (setq sorting-type (read-char-exclusive))) - (let ((dcst (downcase sorting-type)) - extractfun comparefun) - ;; Define the appropriate functions - (cond - ((= dcst ?n) - (setq extractfun 'string-to-number - comparefun (if (= dcst sorting-type) '< '>))) - ((= dcst ?a) - (setq extractfun (if with-case (lambda(x) (org-sort-remove-invisible x)) - (lambda(x) (downcase (org-sort-remove-invisible x)))) - comparefun (if (= dcst sorting-type) - 'string< - (lambda (a b) (and (not (string< a b)) - (not (string= a b))))))) - ((= dcst ?t) - (setq extractfun - (lambda (x) - (if (or (string-match org-ts-regexp x) - (string-match org-ts-regexp-both x)) - (float-time - (org-time-string-to-time (match-string 0 x))) - 0)) - comparefun (if (= dcst sorting-type) '< '>))) - (t (error "Invalid sorting type `%c'" sorting-type))) - - (sort (mapcar (lambda (x) (cons (funcall extractfun (car x)) (cdr x))) - table) - (lambda (a b) (funcall comparefun (car a) (car b)))))) - - ;;; The orgstruct minor mode ;; Define a minor mode which can be used in other modes in order to -;; integrate the org-mode structure editing commands. +;; integrate the Org mode structure editing commands. -;; This is really a hack, because the org-mode structure commands use +;; This is really a hack, because the Org mode structure commands use ;; keys which normally belong to the major mode. Here is how it ;; works: The minor mode defines all the keys necessary to operate the ;; structure commands, but wraps the commands into a function which ;; tests if the cursor is currently at a headline or a plain list ;; item. If that is the case, the structure command is used, -;; temporarily setting many Org-mode variables like regular +;; temporarily setting many Org mode variables like regular ;; expressions for filling etc. However, when any of those keys is ;; used at a different location, function uses `key-binding' to look ;; up if the key has an associated command in another currently active @@ -8897,7 +9079,7 @@ If WITH-CASE is non-nil, the sorting will be case-sensitive." "Regexp that matches the custom prefix of Org headlines in orgstruct(++)-mode." :group 'org - :version "24.4" + :version "26.1" :package-version '(Org . "8.3") :type 'regexp) ;;;###autoload(put 'orgstruct-heading-prefix-regexp 'safe-local-variable 'stringp) @@ -8917,10 +9099,10 @@ orgstruct(++)-mode." ;;;###autoload (define-minor-mode orgstruct-mode "Toggle the minor mode `orgstruct-mode'. -This mode is for using Org-mode structure commands in other -modes. The following keys behave as if Org-mode were active, if +This mode is for using Org mode structure commands in other +modes. The following keys behave as if Org mode were active, if the cursor is on a headline, or on a plain list item (both as -defined by Org-mode)." +defined by Org mode)." nil " OrgStruct" (make-sparse-keymap) (funcall (if orgstruct-mode 'add-to-invisibility-spec @@ -8937,40 +9119,38 @@ defined by Org-mode)." "Unconditionally turn on `orgstruct-mode'." (orgstruct-mode 1)) -(defvar org-fb-vars nil) -(make-variable-buffer-local 'org-fb-vars) +(defvar-local orgstruct-is-++ nil + "Is `orgstruct-mode' in ++ version in the current-buffer?") +(defvar-local org-fb-vars nil) (defun orgstruct++-mode (&optional arg) "Toggle `orgstruct-mode', the enhanced version of it. In addition to setting orgstruct-mode, this also exports all -indentation and autofilling variables from org-mode into the +indentation and autofilling variables from Org mode into the buffer. It will also recognize item context in multiline items." (interactive "P") (setq arg (prefix-numeric-value (or arg (if orgstruct-mode -1 1)))) (if (< arg 1) (progn (orgstruct-mode -1) - (mapc (lambda(v) - (org-set-local (car v) - (if (eq (car-safe (cadr v)) 'quote) (cadadr v) (cadr v)))) - org-fb-vars)) + (dolist (v org-fb-vars) + (set (make-local-variable (car v)) + (if (eq (car-safe (cadr v)) 'quote) + (cl-cadadr v) + (nth 1 v))))) (orgstruct-mode 1) (setq org-fb-vars nil) (unless org-local-vars (setq org-local-vars (org-get-local-variables))) (let (var val) - (mapc - (lambda (x) - (when (string-match - "^\\(paragraph-\\|auto-fill\\|normal-auto-fill\\|fill-paragraph\\|fill-prefix\\|indent-\\)" - (symbol-name (car x))) - (setq var (car x) val (nth 1 x)) - (push (list var `(quote ,(eval var))) org-fb-vars) - (org-set-local var (if (eq (car-safe val) 'quote) (nth 1 val) val)))) - org-local-vars) - (org-set-local 'orgstruct-is-++ t)))) - -(defvar orgstruct-is-++ nil - "Is `orgstruct-mode' in ++ version in the current-buffer?") -(make-variable-buffer-local 'orgstruct-is-++) + (dolist (x org-local-vars) + (when (string-match + "^\\(paragraph-\\|auto-fill\\|normal-auto-fill\\|fill-paragraph\ +\\|fill-prefix\\|indent-\\)" + (symbol-name (car x))) + (setq var (car x) val (nth 1 x)) + (push (list var `(quote ,(eval var))) org-fb-vars) + (set (make-local-variable var) + (if (eq (car-safe val) 'quote) (nth 1 val) val)))) + (setq-local orgstruct-is-++ t)))) ;;;###autoload (defun turn-on-orgstruct++ () @@ -8999,6 +9179,7 @@ buffer. It will also recognize item context in multiline items." org-ctrl-c-minus org-ctrl-c-star org-cycle + org-force-cycle-archived org-forward-heading-same-level org-insert-heading org-insert-heading-respect-content @@ -9018,6 +9199,7 @@ buffer. It will also recognize item context in multiline items." org-shifttab org-shifttab org-shiftup + org-show-children org-show-subtree org-sort org-up-element @@ -9025,8 +9207,7 @@ buffer. It will also recognize item context in multiline items." outline-next-visible-heading outline-previous-visible-heading outline-promote - outline-up-heading - show-children)) + outline-up-heading)) (let ((f (or (car-safe cell) cell)) (disable-when-heading-prefix (cdr-safe cell))) (when (fboundp f) @@ -9045,15 +9226,15 @@ buffer. It will also recognize item context in multiline items." (regexp-quote (cdr rep)) (car rep) (key-description binding))))) - (pushnew binding new-bindings :test 'equal))) + (cl-pushnew binding new-bindings :test 'equal))) (dolist (binding new-bindings) (let ((key (lookup-key orgstruct-mode-map binding))) (when (or (not key) (numberp key)) - (condition-case nil - (org-defkey orgstruct-mode-map - binding - (orgstruct-make-binding f binding disable-when-heading-prefix)) - (error nil))))))))) + (ignore-errors + (org-defkey orgstruct-mode-map + binding + (orgstruct-make-binding + f binding disable-when-heading-prefix)))))))))) (run-hooks 'orgstruct-setup-hook)) (defun orgstruct-make-binding (fun key disable-when-heading-prefix) @@ -9152,9 +9333,9 @@ definitions." ;; normalize contexts (mapcar (lambda(c) (cond ((listp (cadr c)) - (list (car c) (car c) (cadr c))) + (list (car c) (car c) (nth 1 c))) ((string= "" (cadr c)) - (list (car c) (car c) (caddr c))) + (list (car c) (car c) (nth 2 c))) (t c))) contexts)) (a alist) r s) @@ -9168,7 +9349,7 @@ definitions." (setq vrules (org-contextualize-validate-key (car c) contexts))) (mapc (lambda (vr) - (when (not (equal (car vr) (cadr vr))) + (unless (equal (car vr) (cadr vr)) (setq repl vr))) vrules) (if (not repl) (push c r) @@ -9185,39 +9366,37 @@ definitions." (delete-dups (mapcar (lambda (x) (let ((tpl (car x))) - (when (not (delq - nil - (mapcar (lambda (y) - (equal y tpl)) - s))) + (unless (delq + nil + (mapcar (lambda (y) + (equal y tpl)) + s)) x))) (reverse r)))))) (defun org-contextualize-validate-key (key contexts) "Check CONTEXTS for agenda or capture KEY." - (let (rr res) + (let (res) (dolist (r contexts) - (mapc - (lambda (rr) - (when - (and (equal key (car r)) - (if (functionp rr) (funcall rr) - (or (and (eq (car rr) 'in-file) - (buffer-file-name) - (string-match (cdr rr) (buffer-file-name))) - (and (eq (car rr) 'in-mode) - (string-match (cdr rr) (symbol-name major-mode))) - (and (eq (car rr) 'in-buffer) - (string-match (cdr rr) (buffer-name))) - (when (and (eq (car rr) 'not-in-file) - (buffer-file-name)) - (not (string-match (cdr rr) (buffer-file-name)))) - (when (eq (car rr) 'not-in-mode) - (not (string-match (cdr rr) (symbol-name major-mode)))) - (when (eq (car rr) 'not-in-buffer) - (not (string-match (cdr rr) (buffer-name))))))) - (push r res))) - (car (last r)))) + (dolist (rr (car (last r))) + (when + (and (equal key (car r)) + (if (functionp rr) (funcall rr) + (or (and (eq (car rr) 'in-file) + (buffer-file-name) + (string-match (cdr rr) (buffer-file-name))) + (and (eq (car rr) 'in-mode) + (string-match (cdr rr) (symbol-name major-mode))) + (and (eq (car rr) 'in-buffer) + (string-match (cdr rr) (buffer-name))) + (when (and (eq (car rr) 'not-in-file) + (buffer-file-name)) + (not (string-match (cdr rr) (buffer-file-name)))) + (when (eq (car rr) 'not-in-mode) + (not (string-match (cdr rr) (symbol-name major-mode)))) + (when (eq (car rr) 'not-in-buffer) + (not (string-match (cdr rr) (buffer-name))))))) + (push r res)))) (delete-dups (delq nil res)))) (defun org-context-p (&rest contexts) @@ -9235,45 +9414,11 @@ Possible values in the list of contexts are `table', `headline', and `item'." (org-in-item-p))) (goto-char pos)))) -(defun org-get-local-variables () - "Return a list of all local variables in an Org mode buffer." - (let (varlist) - (with-current-buffer (get-buffer-create "*Org tmp*") - (erase-buffer) - (org-mode) - (setq varlist (buffer-local-variables))) - (kill-buffer "*Org tmp*") - (delq nil - (mapcar - (lambda (x) - (setq x - (if (symbolp x) - (list x) - (list (car x) (cdr x)))) - (if (and (not (get (car x) 'org-state)) - (string-match - "^\\(org-\\|orgtbl-\\|outline-\\|comment-\\|paragraph-\\|auto-fill\\|normal-auto-fill\\|fill-paragraph\\|indent-\\)" - (symbol-name (car x)))) - x nil)) - varlist)))) - -(defun org-clone-local-variables (from-buffer &optional regexp) - "Clone local variables from FROM-BUFFER. -Optional argument REGEXP selects variables to clone." - (mapc - (lambda (pair) - (and (symbolp (car pair)) - (or (null regexp) - (string-match regexp (symbol-name (car pair)))) - (set (make-local-variable (car pair)) - (cdr pair)))) - (buffer-local-variables from-buffer))) - ;;;###autoload (defun org-run-like-in-org-mode (cmd) - "Run a command, pretending that the current buffer is in Org-mode. + "Run a command, pretending that the current buffer is in Org mode. This will temporarily bind local variables that are typically bound in -Org-mode to the values they have in Org-mode, and then interactively +Org mode to the values they have in Org mode, and then interactively call CMD." (org-load-modules-maybe) (unless org-local-vars @@ -9287,67 +9432,119 @@ call CMD." (eval `(let ,binds (call-interactively (quote ,cmd)))))) -;;;; Archiving - (defun org-get-category (&optional pos force-refresh) "Get the category applying to position POS." (save-match-data - (if force-refresh (org-refresh-category-properties)) + (when force-refresh (org-refresh-category-properties)) (let ((pos (or pos (point)))) (or (get-text-property pos 'org-category) (progn (org-refresh-category-properties) (get-text-property pos 'org-category)))))) -(defun org-refresh-category-properties () - "Refresh category text properties in the buffer." - (let ((case-fold-search t) - (inhibit-read-only t) - (def-cat (cond - ((null org-category) - (if buffer-file-name - (file-name-sans-extension - (file-name-nondirectory buffer-file-name)) - "???")) - ((symbolp org-category) (symbol-name org-category)) - (t org-category))) - beg end cat pos optionp) - (org-with-silent-modifications - (save-excursion - (save-restriction - (widen) - (goto-char (point-min)) - (put-text-property (point) (point-max) 'org-category def-cat) - (while (re-search-forward - "^\\(#\\+CATEGORY:\\|[ \t]*:CATEGORY:\\)\\(.*\\)" nil t) - (setq pos (match-end 0) - optionp (equal (char-after (match-beginning 0)) ?#) - cat (org-trim (match-string 2))) - (if optionp - (setq beg (point-at-bol) end (point-max)) - (org-back-to-heading t) - (setq beg (point) end (org-end-of-subtree t t))) - (put-text-property beg end 'org-category cat) - (put-text-property beg end 'org-category-position beg) - (goto-char pos))))))) +;;; Refresh properties (defun org-refresh-properties (dprop tprop) "Refresh buffer text properties. -DPROP is the drawer property and TPROP is the corresponding text -property to set." - (let ((case-fold-search t) - (inhibit-read-only t) p) +DPROP is the drawer property and TPROP is either the +corresponding text property to set, or an alist with each element +being a text property (as a symbol) and a function to apply to +the value of the drawer property." + (let* ((case-fold-search t) + (inhibit-read-only t) + (inherit? (org-property-inherit-p dprop)) + (property-re (org-re-property (concat (regexp-quote dprop) "\\+?") t)) + (global (and inherit? (org--property-global-value dprop nil)))) (org-with-silent-modifications - (save-excursion - (save-restriction - (widen) - (goto-char (point-min)) - (while (re-search-forward (concat "^[ \t]*:" dprop ": +\\(.*\\)[ \t]*$") nil t) - (setq p (org-match-string-no-properties 1)) - (save-excursion - (org-back-to-heading t) - (put-text-property - (point-at-bol) (or (outline-next-heading) (point-max)) tprop p)))))))) + (org-with-point-at 1 + ;; Set global values (e.g., values defined through + ;; "#+PROPERTY:" keywords) to the whole buffer. + (when global (put-text-property (point-min) (point-max) tprop global)) + ;; Set local values. + (while (re-search-forward property-re nil t) + (when (org-at-property-p) + (org-refresh-property tprop (org-entry-get (point) dprop) inherit?)) + (outline-next-heading)))))) + +(defun org-refresh-property (tprop p &optional inherit) + "Refresh the buffer text property TPROP from the drawer property P. +The refresh happens only for the current headline, or the whole +sub-tree if optional argument INHERIT is non-nil." + (unless (org-before-first-heading-p) + (save-excursion + (org-back-to-heading t) + (let ((start (point)) + (end (save-excursion + (if inherit (org-end-of-subtree t t) + (or (outline-next-heading) (point-max)))))) + (if (symbolp tprop) + ;; TPROP is a text property symbol. + (put-text-property start end tprop p) + ;; TPROP is an alist with (property . function) elements. + (pcase-dolist (`(,prop . ,f) tprop) + (put-text-property start end prop (funcall f p)))))))) +(defun org-refresh-category-properties () + "Refresh category text properties in the buffer." + (let ((case-fold-search t) + (inhibit-read-only t) + (default-category + (cond ((null org-category) + (if buffer-file-name + (file-name-sans-extension + (file-name-nondirectory buffer-file-name)) + "???")) + ((symbolp org-category) (symbol-name org-category)) + (t org-category)))) + (org-with-silent-modifications + (org-with-wide-buffer + ;; Set buffer-wide category. Search last #+CATEGORY keyword. + ;; This is the default category for the buffer. If none is + ;; found, fall-back to `org-category' or buffer file name. + (put-text-property + (point-min) (point-max) + 'org-category + (catch 'buffer-category + (goto-char (point-max)) + (while (re-search-backward "^[ \t]*#\\+CATEGORY:" (point-min) t) + (let ((element (org-element-at-point))) + (when (eq (org-element-type element) 'keyword) + (throw 'buffer-category + (org-element-property :value element))))) + default-category)) + ;; Set sub-tree specific categories. + (goto-char (point-min)) + (let ((regexp (org-re-property "CATEGORY"))) + (while (re-search-forward regexp nil t) + (let ((value (match-string-no-properties 3))) + (when (org-at-property-p) + (put-text-property + (save-excursion (org-back-to-heading t) (point)) + (save-excursion (org-end-of-subtree t t) (point)) + 'org-category + value))))))))) + +(defun org-refresh-stats-properties () + "Refresh stats text properties in the buffer." + (org-with-silent-modifications + (org-with-point-at 1 + (let ((regexp (concat org-outline-regexp-bol + ".*\\[\\([0-9]*\\)\\(?:%\\|/\\([0-9]*\\)\\)\\]"))) + (while (re-search-forward regexp nil t) + (let* ((numerator (string-to-number (match-string 1))) + (denominator (and (match-end 2) + (string-to-number (match-string 2)))) + (stats (cond ((not denominator) numerator) ;percent + ((= denominator 0) 0) + (t (/ (* numerator 100) denominator))))) + (put-text-property (point) (progn (org-end-of-subtree t t) (point)) + 'org-stats stats))))))) + +(defun org-refresh-effort-properties () + "Refresh effort properties" + (org-refresh-properties + org-effort-property + '((effort . identity) + (effort-minutes . org-duration-to-minutes)))) ;;;; Link Stuff @@ -9387,78 +9584,54 @@ property to set." (defvar org-store-link-plist nil "Plist with info about the most recently link created with `org-store-link'.") -(defvar org-link-protocols nil - "Link protocols added to Org-mode using `org-add-link-type'.") +(defun org-store-link-functions () + "Return a list of functions that are called to create and store a link. +The functions defined in the :store property of +`org-link-parameters'. -(defvar org-store-link-functions nil - "List of functions that are called to create and store a link. Each function will be called in turn until one returns a non-nil -value. Each function should check if it is responsible for creating -this link (for example by looking at the major mode). -If not, it must exit and return nil. -If yes, it should return a non-nil value after a calling -`org-store-link-props' with a list of properties and values. -Special properties are: +value. Each function should check if it is responsible for +creating this link (for example by looking at the major mode). +If not, it must exit and return nil. If yes, it should return +a non-nil value after calling `org-store-link-props' with a list +of properties and values. Special properties are: :type The link prefix, like \"http\". This must be given. :link The link, like \"http://www.astro.uva.nl/~dominik\". This is obligatory as well. :description Optional default description for the second pair - of brackets in an Org-mode link. The user can still change - this when inserting this link into an Org-mode buffer. + of brackets in an Org mode link. The user can still change + this when inserting this link into an Org mode buffer. In addition to these, any additional properties can be specified -and then used in capture templates.") - -(defun org-add-link-type (type &optional follow export) - "Add TYPE to the list of `org-link-types'. -Re-compute all regular expressions depending on `org-link-types' - -FOLLOW and EXPORT are two functions. - -FOLLOW should take the link path as the single argument and do whatever -is necessary to follow the link, for example find a file or display -a mail message. - -EXPORT should format the link path for export to one of the export formats. -It should be a function accepting three arguments: - - path the path of the link, the text after the prefix (like \"http:\") - desc the description of the link, if any, or a description added by - org-export-normalize-links if there is none - format the export format, a symbol like `html' or `latex' or `ascii'.. - -The function may use the FORMAT information to return different values -depending on the format. The return value will be put literally into -the exported file. If the return value is nil, this means Org should -do what it normally does with links which do not have EXPORT defined. - -Org-mode has a built-in default for exporting links. If you are happy with -this default, there is no need to define an export function for the link -type. For a simple example of an export function, see `org-bbdb.el'." - (add-to-list 'org-link-types type t) - (org-make-link-regexps) - (if (assoc type org-link-protocols) - (setcdr (assoc type org-link-protocols) (list follow export)) - (push (list type follow export) org-link-protocols))) +and then used in capture templates." + (cl-loop for link in org-link-parameters + with store-func + do (setq store-func (org-link-get-parameter (car link) :store)) + if store-func + collect store-func)) (defvar org-agenda-buffer-name) ; Defined in org-agenda.el (defvar org-id-link-to-org-use-id) ; Defined in org-id.el ;;;###autoload (defun org-store-link (arg) - "\\<org-mode-map>Store an org-link to the current location. + "Store an org-link to the current location. +\\<org-mode-map> This link is added to `org-stored-links' and can later be inserted -into an org-buffer with \\[org-insert-link]. +into an Org buffer with `org-insert-link' (`\\[org-insert-link]'). -For some link types, a prefix arg is interpreted. -For links to Usenet articles, arg negates `org-gnus-prefer-web-links'. -For file links, arg negates `org-context-in-file-links'. +For some link types, a `\\[universal-argument]' prefix ARG is interpreted. \ +A single +`\\[universal-argument]' negates `org-context-in-file-links' for file links or +`org-gnus-prefer-web-links' for links to Usenet articles. -A double prefix arg force skipping storing functions that are not -part of Org's core. +A `\\[universal-argument] \\[universal-argument]' prefix ARG forces \ +skipping storing functions that are not +part of Org core. -A triple prefix arg force storing a link for each line in the +A `\\[universal-argument] \\[universal-argument] \\[universal-argument]' \ +prefix ARG forces storing a link for each line in the active region." (interactive "P") (org-load-modules-maybe) @@ -9473,111 +9646,120 @@ active region." (call-interactively 'org-store-link)) (move-beginning-of-line 2) (set-mark (point))))) - (org-with-limited-levels - (setq org-store-link-plist nil) - (let (link cpltxt desc description search - txt custom-id agenda-link sfuns sfunsn) - (cond - - ;; Store a link using an external link type - ((and (not (equal arg '(16))) - (setq sfuns - (delq - nil (mapcar (lambda (f) - (let (fs) (if (funcall f) (push f fs)))) - org-store-link-functions)) - sfunsn (mapcar (lambda (fu) (symbol-name (car fu))) sfuns)) - (or (and (cdr sfuns) - (funcall (intern - (completing-read - "Which function for creating the link? " - sfunsn nil t (car sfunsn))))) - (funcall (caar sfuns))) - (setq link (plist-get org-store-link-plist :link) - desc (or (plist-get org-store-link-plist - :description) - link)))) - - ;; Store a link from a source code buffer - ((org-src-edit-buffer-p) - (let (label gc) - (while (or (not label) - (save-excursion - (save-restriction - (widen) - (goto-char (point-min)) - (re-search-forward - (regexp-quote (format org-coderef-label-format label)) - nil t)))) - (when label (message "Label exists already") (sit-for 2)) - (setq label (read-string "Code line label: " label))) - (end-of-line 1) - (setq link (format org-coderef-label-format label)) - (setq gc (- 79 (length link))) - (if (< (current-column) gc) (org-move-to-column gc t) (insert " ")) - (insert link) - (setq link (concat "(" label ")") desc nil))) - - ;; We are in the agenda, link to referenced location - ((equal (org-bound-and-true-p org-agenda-buffer-name) (buffer-name)) - (let ((m (or (get-text-property (point) 'org-hd-marker) - (get-text-property (point) 'org-marker)))) - (when m - (org-with-point-at m - (setq agenda-link - (if (org-called-interactively-p 'any) - (call-interactively 'org-store-link) - (org-store-link nil))))))) - - ((eq major-mode 'calendar-mode) - (let ((cd (calendar-cursor-to-date))) - (setq link - (format-time-string - (car org-time-stamp-formats) - (apply 'encode-time - (list 0 0 0 (nth 1 cd) (nth 0 cd) (nth 2 cd) - nil nil nil)))) - (org-store-link-props :type "calendar" :date cd))) - - ((eq major-mode 'help-mode) - (setq link (concat "help:" (save-excursion - (goto-char (point-min)) - (looking-at "^[^ ]+") - (match-string 0)))) - (org-store-link-props :type "help")) - - ((eq major-mode 'w3-mode) - (setq cpltxt (if (and (buffer-name) - (not (string-match "Untitled" (buffer-name)))) - (buffer-name) - (url-view-url t)) - link (url-view-url t)) - (org-store-link-props :type "w3" :url (url-view-url t))) - - ((eq major-mode 'image-mode) - (setq cpltxt (concat "file:" - (abbreviate-file-name buffer-file-name)) - link cpltxt) - (org-store-link-props :type "image" :file buffer-file-name)) - - ;; In dired, store a link to the file of the current line - ((eq major-mode 'dired-mode) - (let ((file (dired-get-filename nil t))) - (setq file (if file - (abbreviate-file-name - (expand-file-name (dired-get-filename nil t))) - ;; otherwise, no file so use current directory. - default-directory)) - (setq cpltxt (concat "file:" file) - link cpltxt))) - - ((setq search (run-hook-with-args-until-success - 'org-create-file-search-functions)) - (setq link (concat "file:" (abbreviate-file-name buffer-file-name) - "::" search)) - (setq cpltxt (or description link))) - - ((and (buffer-file-name (buffer-base-buffer)) (derived-mode-p 'org-mode)) + (setq org-store-link-plist nil) + (let (link cpltxt desc description search txt 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 + ;; location, ask which one to use. + ((and (not (equal arg '(16))) + (let ((results-alist nil)) + (dolist (f (org-store-link-functions)) + (when (funcall f) + ;; XXX: return value is not link's plist, so we + ;; store the new value before it is modified. It + ;; would be cleaner to ask store link functions to + ;; return the plist instead. + (push (cons f (copy-sequence org-store-link-plist)) + results-alist))) + (pcase results-alist + (`nil nil) + (`((,_ . ,_)) t) ;single choice: nothing to do + (`((,name . ,_) . ,_) + ;; Reinstate link plist associated to the chosen + ;; function. + (apply #'org-store-link-props + (cdr (assoc-string + (completing-read + "Which function for creating the link? " + (mapcar #'car results-alist) nil t name) + results-alist))) + t)))) + (setq link (plist-get org-store-link-plist :link)) + (setq desc (or (plist-get org-store-link-plist :description) + link))) + + ;; Store a link from a source code buffer. + ((org-src-edit-buffer-p) + (let ((coderef-format (org-src-coderef-format))) + (cond ((save-excursion + (beginning-of-line) + (looking-at (org-src-coderef-regexp coderef-format))) + (setq link (format "(%s)" (match-string-no-properties 3)))) + ((called-interactively-p 'any) + (let ((label (read-string "Code line label: "))) + (end-of-line) + (setq link (format coderef-format label)) + (let ((gc (- 79 (length link)))) + (if (< (current-column) gc) + (org-move-to-column gc t) + (insert " "))) + (insert link) + (setq link (concat "(" label ")")) + (setq desc nil))) + (t (setq link nil))))) + + ;; We are in the agenda, link to referenced location + ((equal (bound-and-true-p org-agenda-buffer-name) (buffer-name)) + (let ((m (or (get-text-property (point) 'org-hd-marker) + (get-text-property (point) 'org-marker)))) + (when m + (org-with-point-at m + (setq agenda-link + (if (called-interactively-p 'any) + (call-interactively 'org-store-link) + (org-store-link nil))))))) + + ((eq major-mode 'calendar-mode) + (let ((cd (calendar-cursor-to-date))) + (setq link + (format-time-string + (car org-time-stamp-formats) + (apply 'encode-time + (list 0 0 0 (nth 1 cd) (nth 0 cd) (nth 2 cd) + nil nil nil)))) + (org-store-link-props :type "calendar" :date cd))) + + ((eq major-mode 'help-mode) + (setq link (concat "help:" (save-excursion + (goto-char (point-min)) + (looking-at "^[^ ]+") + (match-string 0)))) + (org-store-link-props :type "help")) + + ((eq major-mode 'w3-mode) + (setq cpltxt (if (and (buffer-name) + (not (string-match "Untitled" (buffer-name)))) + (buffer-name) + (url-view-url t)) + link (url-view-url t)) + (org-store-link-props :type "w3" :url (url-view-url t))) + + ((eq major-mode 'image-mode) + (setq cpltxt (concat "file:" + (abbreviate-file-name buffer-file-name)) + link cpltxt) + (org-store-link-props :type "image" :file buffer-file-name)) + + ;; In dired, store a link to the file of the current line + ((derived-mode-p 'dired-mode) + (let ((file (dired-get-filename nil t))) + (setq file (if file + (abbreviate-file-name + (expand-file-name (dired-get-filename nil t))) + ;; otherwise, no file so use current directory. + default-directory)) + (setq cpltxt (concat "file:" file) + link cpltxt))) + + ((setq search (run-hook-with-args-until-success + 'org-create-file-search-functions)) + (setq link (concat "file:" (abbreviate-file-name buffer-file-name) + "::" search)) + (setq cpltxt (or description link))) + + ((and (buffer-file-name (buffer-base-buffer)) (derived-mode-p 'org-mode)) + (org-with-limited-levels (setq custom-id (org-entry-get nil "CUSTOM_ID")) (cond ;; Store a link using the target at point @@ -9590,7 +9772,7 @@ active region." link cpltxt)) ((and (featurep 'org-id) (or (eq org-id-link-to-org-use-id t) - (and (org-called-interactively-p 'any) + (and (called-interactively-p 'any) (or (eq org-id-link-to-org-use-id 'create-if-interactive) (and (eq org-id-link-to-org-use-id 'create-if-interactive-and-no-custom-id) @@ -9613,15 +9795,13 @@ active region." (abbreviate-file-name (buffer-file-name (buffer-base-buffer))))) ;; Add a context search string - (when (org-xor org-context-in-file-links arg) - (let* ((ee (org-element-at-point)) - (et (org-element-type ee)) - (ev (plist-get (cadr ee) :value)) - (ek (plist-get (cadr ee) :key)) - (eok (and (stringp ek) (string-match "name" ek)))) + (when (org-xor org-context-in-file-links + (equal arg '(4))) + (let* ((element (org-element-at-point)) + (name (org-element-property :name element))) (setq txt (cond ((org-at-heading-p) nil) - ((and (eq et 'keyword) eok) ev) + (name) ((org-region-active-p) (buffer-substring (region-beginning) (region-end))))) (when (or (null txt) (string-match "\\S-" txt)) @@ -9630,74 +9810,82 @@ active region." (condition-case nil (org-make-org-heading-search-string txt) (error ""))) - desc (or (and (eq et 'keyword) eok ev) + desc (or name (nth 4 (ignore-errors (org-heading-components))) "NONE"))))) - (if (string-match "::\\'" cpltxt) - (setq cpltxt (substring cpltxt 0 -2))) - (setq link cpltxt)))) - - ((buffer-file-name (buffer-base-buffer)) - ;; Just link to this file here. - (setq cpltxt (concat "file:" - (abbreviate-file-name - (buffer-file-name (buffer-base-buffer))))) - ;; Add a context string. - (when (org-xor org-context-in-file-links arg) - (setq txt (if (org-region-active-p) - (buffer-substring (region-beginning) (region-end)) - (buffer-substring (point-at-bol) (point-at-eol)))) - ;; Only use search option if there is some text. - (when (string-match "\\S-" txt) - (setq cpltxt - (concat cpltxt "::" (org-make-org-heading-search-string txt)) - desc "NONE"))) - (setq link cpltxt)) - - ((org-called-interactively-p 'interactive) - (user-error "No method for storing a link from this buffer")) - - (t (setq link nil))) - - ;; We're done setting link and desc, clean up - (if (consp link) (setq cpltxt (car link) link (cdr link))) - (setq link (or link cpltxt) - desc (or desc cpltxt)) - (cond ((equal desc "NONE") (setq desc nil)) - ((and desc (string-match org-bracket-link-analytic-regexp desc)) - (let ((d0 (match-string 3 desc)) - (p0 (match-string 5 desc))) - (setq desc - (replace-regexp-in-string - org-bracket-link-regexp - (concat (or p0 d0) - (if (equal (length (match-string 0 desc)) - (length desc)) "*" "")) desc))))) - - ;; Return the link - (if (not (and (or (org-called-interactively-p 'any) - executing-kbd-macro) - link)) - (or agenda-link (and link (org-make-link-string link desc))) - (push (list link desc) org-stored-links) - (message "Stored: %s" (or desc link)) - (when custom-id - (setq link (concat "file:" (abbreviate-file-name - (buffer-file-name)) "::#" custom-id)) - (push (list link desc) org-stored-links)) - (car org-stored-links)))))) + (when (string-match "::\\'" cpltxt) + (setq cpltxt (substring cpltxt 0 -2))) + (setq link cpltxt))))) + + ((buffer-file-name (buffer-base-buffer)) + ;; Just link to this file here. + (setq cpltxt (concat "file:" + (abbreviate-file-name + (buffer-file-name (buffer-base-buffer))))) + ;; Add a context string. + (when (org-xor org-context-in-file-links + (equal arg '(4))) + (setq txt (if (org-region-active-p) + (buffer-substring (region-beginning) (region-end)) + (buffer-substring (point-at-bol) (point-at-eol)))) + ;; Only use search option if there is some text. + (when (string-match "\\S-" txt) + (setq cpltxt + (concat cpltxt "::" (org-make-org-heading-search-string txt)) + desc "NONE"))) + (setq link cpltxt)) + + ((called-interactively-p 'interactive) + (user-error "No method for storing a link from this buffer")) + + (t (setq link nil))) + + ;; We're done setting link and desc, clean up + (when (consp link) (setq cpltxt (car link) link (cdr link))) + (setq link (or link cpltxt) + desc (or desc cpltxt)) + (cond ((not desc)) + ((equal desc "NONE") (setq desc nil)) + (t (setq desc + (replace-regexp-in-string + org-bracket-link-analytic-regexp + (lambda (m) (or (match-string 5 m) (match-string 3 m))) + desc)))) + ;; Return the link + (if (not (and (or (called-interactively-p 'any) + executing-kbd-macro) + link)) + (or agenda-link (and link (org-make-link-string link desc))) + (push (list link desc) org-stored-links) + (message "Stored: %s" (or desc link)) + (when custom-id + (setq link (concat "file:" (abbreviate-file-name + (buffer-file-name)) "::#" custom-id)) + (push (list link desc) org-stored-links)) + (car org-stored-links))))) (defun org-store-link-props (&rest plist) - "Store link properties, extract names and addresses." - (let (x adr) - (when (setq x (plist-get plist :from)) - (setq adr (mail-extract-address-components x)) - (setq plist (plist-put plist :fromname (car adr))) - (setq plist (plist-put plist :fromaddress (nth 1 adr)))) - (when (setq x (plist-get plist :to)) - (setq adr (mail-extract-address-components x)) - (setq plist (plist-put plist :toname (car adr))) - (setq plist (plist-put plist :toaddress (nth 1 adr))))) + "Store link properties. +The properties are pre-processed by extracting names, addresses +and dates." + (let ((x (plist-get plist :from))) + (when x + (let ((adr (mail-extract-address-components x))) + (setq plist (plist-put plist :fromname (car adr))) + (setq plist (plist-put plist :fromaddress (nth 1 adr)))))) + (let ((x (plist-get plist :to))) + (when x + (let ((adr (mail-extract-address-components x))) + (setq plist (plist-put plist :toname (car adr))) + (setq plist (plist-put plist :toaddress (nth 1 adr)))))) + (let ((x (ignore-errors (date-to-time (plist-get plist :date))))) + (when x + (setq plist (plist-put plist :date-timestamp + (format-time-string + (org-time-stamp-format t) x))) + (setq plist (plist-put plist :date-timestamp-inactive + (format-time-string + (org-time-stamp-format t t) x))))) (let ((from (plist-get plist :from)) (to (plist-get plist :to))) (when (and from to org-from-is-user-regexp) @@ -9750,7 +9938,7 @@ according to FMT (default from `org-email-link-description-format')." (org-back-to-heading t) (org-element-property :raw-value (org-element-at-point)))))) (lines org-context-in-file-links)) - (or string (setq s (concat "*" s))) ; Add * for headlines + (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"))) @@ -9759,49 +9947,38 @@ according to FMT (default from `org-email-link-description-format')." 'identity (reverse (nthcdr (- (length slines) lines) (reverse slines))) "\n"))))) - (mapconcat 'identity (org-split-string s "[ \t]+") " "))) + (mapconcat #'identity (split-string s) " "))) (defun org-make-link-string (link &optional description) "Make a link with brackets, consisting of LINK and DESCRIPTION." - (unless (string-match "\\S-" link) - (error "Empty link")) - (when (and description - (stringp description) - (not (string-match "\\S-" description))) - (setq description nil)) - (when (stringp description) - ;; Remove brackets from the description, they are fatal. - (while (string-match "\\[" description) - (setq description (replace-match "{" t t description))) - (while (string-match "\\]" description) - (setq description (replace-match "}" t t description)))) - (when (equal link description) - ;; No description needed, it is identical - (setq description nil)) - (when (and (not description) - (not (string-match (org-image-file-name-regexp) link)) - (not (equal link (org-link-escape link)))) - (setq description (org-extract-attributes link))) - (setq link - (cond ((string-match (org-image-file-name-regexp) link) link) - ((string-match org-link-types-re link) - (concat (match-string 1 link) - (org-link-escape (substring link (match-end 1))))) - (t (org-link-escape link)))) - (concat "[[" link "]" - (if description (concat "[" description "]") "") - "]")) + (unless (org-string-nw-p link) (error "Empty link")) + (let ((uri (cond ((string-match org-link-types-re link) + (concat (match-string 1 link) + (org-link-escape (substring link (match-end 1))))) + ;; For readability, url-encode internal links only + ;; when absolutely needed (i.e, when they contain + ;; square brackets). File links however, are + ;; encoded since, e.g., spaces are significant. + ((or (file-name-absolute-p link) + (string-match-p "\\`\\.\\.?/\\|[][]" link)) + (org-link-escape link)) + (t link))) + (description + (and (org-string-nw-p description) + ;; Remove brackets from description, as they are fatal. + (replace-regexp-in-string + "[][]" (lambda (m) (if (equal "[" m) "{" "}")) + (org-trim description))))) + (format "[[%s]%s]" + uri + (if description (format "[%s]" description) "")))) (defconst org-link-escape-chars - '(?\ ?\[ ?\] ?\; ?\= ?\+) - "List of characters that should be escaped in link. + ;;%20 %5B %5D %25 + '(?\s ?\[ ?\] ?%) + "List of characters that should be escaped in a link when stored to Org. This is the list that is used for internal purposes.") -(defconst org-link-escape-chars-browser - '(?\ ?\") - "List of escapes for characters that are problematic in links. -This is the list that is used before handing over to the browser.") - (defun org-link-escape (text &optional table merge) "Return percent escaped representation of TEXT. TEXT is a string with the text to escape. @@ -9809,35 +9986,29 @@ Optional argument TABLE is a list with characters that should be escaped. When nil, `org-link-escape-chars' is used. If optional argument MERGE is set, merge TABLE into `org-link-escape-chars'." - (cond - ((and table merge) - (mapc (lambda (defchr) - (unless (member defchr table) - (setq table (cons defchr table)))) org-link-escape-chars)) - ((null table) - (setq table org-link-escape-chars))) - (mapconcat - (lambda (char) - (if (or (member char table) - (and (or (< char 32) (= char 37) (> char 126)) - org-url-hexify-p)) - (mapconcat (lambda (sequence-element) - (format "%%%.2X" sequence-element)) - (or (encode-coding-char char 'utf-8) - (error "Unable to percent escape character: %s" - (char-to-string char))) "") - (char-to-string char))) text "")) + (let ((characters-to-encode + (cond ((null table) org-link-escape-chars) + (merge (append org-link-escape-chars table)) + (t table)))) + (mapconcat + (lambda (c) + (if (or (memq c characters-to-encode) + (and org-url-hexify-p (or (< c 32) (> c 126)))) + (mapconcat (lambda (e) (format "%%%.2X" e)) + (or (encode-coding-char c 'utf-8) + (error "Unable to percent escape character: %c" c)) + "") + (char-to-string c))) + text ""))) (defun org-link-unescape (str) - "Unhex hexified Unicode strings as returned from the JavaScript function -encodeURIComponent. E.g. `%C3%B6' is the german o-Umlaut." - (unless (and (null str) (string= "" str)) - (let ((pos 0) (case-fold-search t) unhexed) - (while (setq pos (string-match "\\(%[0-9a-f][0-9a-f]\\)+" str pos)) - (setq unhexed (org-link-unescape-compound (match-string 0 str))) - (setq str (replace-match unhexed t t str)) - (setq pos (+ pos (length unhexed)))))) - str) + "Unhex hexified Unicode parts in string STR. +E.g. `%C3%B6' becomes the german o-Umlaut. This is the +reciprocal of `org-link-escape', which see." + (if (org-string-nw-p str) + (replace-regexp-in-string + "\\(%[0-9A-Za-z]\\{2\\}\\)+" #'org-link-unescape-compound str t t) + str)) (defun org-link-unescape-compound (hex) "Unhexify Unicode hex-chars. E.g. `%C3%B6' is the German o-Umlaut. @@ -9860,18 +10031,17 @@ Note: this function also decodes single byte encodings like ((>= val 192) (cons 2 192)) (t (cons 0 0))) (cons 6 128)))) - (if (>= val 192) (setq eat (car shift-xor))) + (when (>= val 192) (setq eat (car shift-xor))) (setq val (logxor val (cdr shift-xor))) (setq sum (+ (lsh sum (car shift-xor)) val)) - (if (> eat 0) (setq eat (- eat 1))) + (when (> eat 0) (setq eat (- eat 1))) (cond ((= 0 eat) ;multi byte - (setq ret (concat ret (org-char-to-string sum))) + (setq ret (concat ret (char-to-string sum))) (setq sum 0)) ((not bytes) ; single byte(s) - (setq ret (org-link-unescape-single-byte-sequence hex)))) - )) ;; end (while bytes - ret ))) + (setq ret (org-link-unescape-single-byte-sequence hex)))))) + ret))) (defun org-link-unescape-single-byte-sequence (hex) "Unhexify hex-encoded single byte character sequences." @@ -9901,28 +10071,47 @@ Note: this function also decodes single byte encodings like (defun org-link-prettify (link) "Return a human-readable representation of LINK. -The car of LINK must be a raw link the cdr of LINK must be either -a link description or nil." +The car of LINK must be a raw link. +The cdr of LINK must be either a link description or nil." (let ((desc (or (cadr link) "<no description>"))) (concat (format "%-45s" (substring desc 0 (min (length desc) 40))) "<" (car link) ">"))) ;;;###autoload (defun org-insert-link-global () - "Insert a link like Org-mode does. -This command can be called in any mode to insert a link in Org-mode syntax." + "Insert a link like Org mode does. +This command can be called in any mode to insert a link in Org syntax." (interactive) (org-load-modules-maybe) (org-run-like-in-org-mode 'org-insert-link)) -(defun org-insert-all-links (&optional keep) - "Insert all links in `org-stored-links'." +(defun org-insert-all-links (arg &optional pre post) + "Insert all links in `org-stored-links'. +When a universal prefix, do not delete the links from `org-stored-links'. +When `ARG' is a number, insert the last N link(s). +`PRE' and `POST' are optional arguments to define a string to +prepend or to append." (interactive "P") - (let ((links (copy-sequence org-stored-links)) l) - (while (setq l (if keep (pop links) (pop org-stored-links))) - (insert "- ") - (org-insert-link nil (car l) (or (cadr l) "<no description>")) - (insert "\n")))) + (let ((org-keep-stored-link-after-insertion (equal arg '(4))) + (links (copy-sequence org-stored-links)) + (pr (or pre "- ")) + (po (or post "\n")) + (cnt 1) l) + (if (null org-stored-links) + (message "No link to insert") + (while (and (or (listp arg) (>= arg cnt)) + (setq l (if (listp arg) + (pop links) + (pop org-stored-links)))) + (setq cnt (1+ cnt)) + (insert pr) + (org-insert-link nil (car l) (or (cadr l) "<no description>")) + (insert po))))) + +(defun org-insert-last-stored-link (arg) + "Insert the last link stored in `org-stored-links'." + (interactive "p") + (org-insert-all-links arg "" "\n")) (defun org-link-fontify-links-to-this-file () "Fontify links to the current file in `org-stored-links'." @@ -9946,73 +10135,72 @@ This command can be called in any mode to insert a link in Org-mode syntax." (put-text-property 0 (length l) 'face 'font-lock-comment-face l)) (delq nil (append a b))))) -(defvar org-link-links-in-this-file nil) +(defvar org--links-history nil) (defun org-insert-link (&optional complete-file link-location default-description) "Insert a link. At the prompt, enter the link. -Completion can be used to insert any of the link protocol prefixes like -http or ftp in use. +Completion can be used to insert any of the link protocol prefixes in use. The history can be used to select a link previously stored with `org-store-link'. When the empty string is entered (i.e. if you just -press RET at the prompt), the link defaults to the most recently -stored link. As SPC triggers completion in the minibuffer, you need to -use M-SPC or C-q SPC to force the insertion of a space character. +press `RET' at the prompt), the link defaults to the most recently +stored link. As `SPC' triggers completion in the minibuffer, you need to +use `M-SPC' or `C-q SPC' to force the insertion of a space character. You will also be prompted for a description, and if one is given, it will be displayed in the buffer instead of the link. -If there is already a link at point, this command will allow you to edit link -and description parts. +If there is already a link at point, this command will allow you to edit +link and description parts. -With a \\[universal-argument] prefix, prompts for a file to link to. The file name can -be selected using completion. The path to the file will be relative to the +With a `\\[universal-argument]' prefix, prompts for a file to link to. The \ +file name can be +selected using completion. The path to the file will be relative to the current directory if the file is in the current directory or a subdirectory. Otherwise, the link will be the absolute path as completed in the minibuffer \(i.e. normally ~/path/to/file). You can configure this behavior using the option `org-link-file-path-type'. -With two \\[universal-argument] prefixes, enforce an absolute path even if the file is in +With a `\\[universal-argument] \\[universal-argument]' prefix, enforce an \ +absolute path even if the file is in the current directory or below. -With three \\[universal-argument] prefixes, negate the meaning of -`org-keep-stored-link-after-insertion'. +A `\\[universal-argument] \\[universal-argument] \\[universal-argument]' \ +prefix negates `org-keep-stored-link-after-insertion'. -If `org-make-link-description-function' is non-nil, this function will be -called with the link target, and the result will be the default -link description. - -If the LINK-LOCATION parameter is non-nil, this value will be -used as the link location instead of reading one interactively. +If the LINK-LOCATION parameter is non-nil, this value will be used as +the link location instead of reading one interactively. If the DEFAULT-DESCRIPTION parameter is non-nil, this value will -be used as the default description." +be used as the default description. Otherwise, if +`org-make-link-description-function' is non-nil, this function +will be called with the link target, and the result will be the +default link description." (interactive "P") (let* ((wcf (current-window-configuration)) (origbuf (current-buffer)) - (region (if (org-region-active-p) - (buffer-substring (region-beginning) (region-end)))) + (region (when (org-region-active-p) + (buffer-substring (region-beginning) (region-end)))) (remove (and region (list (region-beginning) (region-end)))) (desc region) - tmphist ; byte-compile incorrectly complains about this (link link-location) (abbrevs org-link-abbrev-alist-local) - entry file all-prefixes auto-desc) + entry all-prefixes auto-desc) (cond - (link-location) ; specified by arg, just use it. + (link-location) ; specified by arg, just use it. ((org-in-regexp org-bracket-link-regexp 1) ;; We do have a link at point, and we are going to edit it. (setq remove (list (match-beginning 0) (match-end 0))) - (setq desc (if (match-end 3) (org-match-string-no-properties 3))) + (setq desc (when (match-end 3) (match-string-no-properties 3))) (setq link (read-string "Link: " (org-link-unescape - (org-match-string-no-properties 1))))) + (match-string-no-properties 1))))) ((or (org-in-regexp org-angle-link-re) (org-in-regexp org-plain-link-re)) ;; Convert to bracket link (setq remove (list (match-beginning 0) (match-end 0)) link (read-string "Link: " - (org-remove-angle-brackets (match-string 0))))) + (org-unbracket-string "<" ">" (match-string 0))))) ((member complete-file '((4) (16))) ;; Completing read for file names. (setq link (org-file-complete-link complete-file))) @@ -10035,149 +10223,137 @@ Use TAB to complete link prefixes, then RET for type-specific completion support (unless (pos-visible-in-window-p (point-max)) (org-fit-window-to-buffer)) (and (window-live-p cw) (select-window cw))) - ;; Fake a link history, containing the stored links. - (setq tmphist (append (mapcar 'car org-stored-links) - org-insert-link-history)) (setq all-prefixes (append (mapcar 'car abbrevs) (mapcar 'car org-link-abbrev-alist) - org-link-types)) + (org-link-types))) (unwind-protect - (progn + ;; Fake a link history, containing the stored links. + (let ((org--links-history + (append (mapcar #'car org-stored-links) + org-insert-link-history))) (setq link (org-completing-read "Link: " (append - (mapcar (lambda (x) (concat x ":")) - all-prefixes) - (mapcar 'car org-stored-links)) + (mapcar (lambda (x) (concat x ":")) all-prefixes) + (mapcar #'car org-stored-links)) nil nil nil - 'tmphist + 'org--links-history (caar org-stored-links))) - (if (not (string-match "\\S-" link)) - (user-error "No link selected")) - (mapc (lambda(l) - (when (equal link (cadr l)) (setq link (car l) auto-desc t))) - org-stored-links) - (if (or (member link all-prefixes) - (and (equal ":" (substring link -1)) - (member (substring link 0 -1) all-prefixes) - (setq link (substring link 0 -1)))) - (setq link (with-current-buffer origbuf - (org-link-try-special-completion link))))) + (unless (org-string-nw-p link) (user-error "No link selected")) + (dolist (l org-stored-links) + (when (equal link (cadr l)) + (setq link (car l)) + (setq auto-desc t))) + (when (or (member link all-prefixes) + (and (equal ":" (substring link -1)) + (member (substring link 0 -1) all-prefixes) + (setq link (substring link 0 -1)))) + (setq link (with-current-buffer origbuf + (org-link-try-special-completion link))))) (set-window-configuration wcf) (kill-buffer "*Org Links*")) (setq entry (assoc link org-stored-links)) (or entry (push link org-insert-link-history)) (setq desc (or desc (nth 1 entry))))) - (if (funcall (if (equal complete-file '(64)) 'not 'identity) - (not org-keep-stored-link-after-insertion)) - (setq org-stored-links (delq (assoc link org-stored-links) - org-stored-links))) + (when (funcall (if (equal complete-file '(64)) 'not 'identity) + (not org-keep-stored-link-after-insertion)) + (setq org-stored-links (delq (assoc link org-stored-links) + org-stored-links))) - (if (and (string-match org-plain-link-re link) - (not (string-match org-ts-regexp link))) - ;; URL-like link, normalize the use of angular brackets. - (setq link (org-remove-angle-brackets link))) + (when (and (string-match org-plain-link-re link) + (not (string-match org-ts-regexp link))) + ;; URL-like link, normalize the use of angular brackets. + (setq link (org-unbracket-string "<" ">" link))) ;; Check if we are linking to the current file with a search ;; option If yes, simplify the link by using only the search ;; option. (when (and buffer-file-name - (string-match "^file:\\(.+?\\)::\\(.+\\)" link)) - (let* ((path (match-string 1 link)) - (case-fold-search nil) - (search (match-string 2 link))) + (let ((case-fold-search nil)) + (string-match "\\`file:\\(.+?\\)::" link))) + (let ((path (match-string-no-properties 1 link)) + (search (substring-no-properties link (match-end 0)))) (save-match-data - (if (equal (file-truename buffer-file-name) (file-truename path)) - ;; We are linking to this same file, with a search option - (setq link search))))) + (when (equal (file-truename buffer-file-name) (file-truename path)) + ;; We are linking to this same file, with a search option + (setq link search))))) ;; Check if we can/should use a relative path. If yes, simplify the link - (when (string-match "^\\(file:\\|docview:\\)\\(.*\\)" link) - (let* ((type (match-string 1 link)) - (path (match-string 2 link)) - (origpath path) - (case-fold-search nil)) - (cond - ((or (eq org-link-file-path-type 'absolute) - (equal complete-file '(16))) - (setq path (abbreviate-file-name (expand-file-name path)))) - ((eq org-link-file-path-type 'noabbrev) - (setq path (expand-file-name path))) - ((eq org-link-file-path-type 'relative) - (setq path (file-relative-name path))) - (t - (save-match-data - (if (string-match (concat "^" (regexp-quote - (expand-file-name - (file-name-as-directory - default-directory)))) - (expand-file-name path)) - ;; We are linking a file with relative path name. - (setq path (substring (expand-file-name path) - (match-end 0))) - (setq path (abbreviate-file-name (expand-file-name path))))))) - (setq link (concat type path)) - (if (equal desc origpath) - (setq desc path)))) - - (if org-make-link-description-function - (setq desc - (or (condition-case nil - (funcall org-make-link-description-function link desc) - (error (progn (message "Can't get link description from `%s'" - (symbol-name org-make-link-description-function)) - (sit-for 2) nil))) - (read-string "Description: " default-description))) - (if default-description (setq desc default-description) - (setq desc (or (and auto-desc desc) - (read-string "Description: " desc))))) + (let ((case-fold-search nil)) + (when (string-match "\\`\\(file\\|docview\\):" link) + (let* ((type (match-string-no-properties 0 link)) + (path (substring-no-properties link (match-end 0))) + (origpath path)) + (cond + ((or (eq org-link-file-path-type 'absolute) + (equal complete-file '(16))) + (setq path (abbreviate-file-name (expand-file-name path)))) + ((eq org-link-file-path-type 'noabbrev) + (setq path (expand-file-name path))) + ((eq org-link-file-path-type 'relative) + (setq path (file-relative-name path))) + (t + (save-match-data + (if (string-match (concat "^" (regexp-quote + (expand-file-name + (file-name-as-directory + default-directory)))) + (expand-file-name path)) + ;; We are linking a file with relative path name. + (setq path (substring (expand-file-name path) + (match-end 0))) + (setq path (abbreviate-file-name (expand-file-name path))))))) + (setq link (concat type path)) + (when (equal desc origpath) + (setq desc path))))) + + (unless auto-desc + (let ((initial-input + (cond + (default-description) + ((not org-make-link-description-function) desc) + (t (condition-case nil + (funcall org-make-link-description-function link desc) + (error + (message "Can't get link description from `%s'" + (symbol-name org-make-link-description-function)) + (sit-for 2) + nil)))))) + (setq desc (read-string "Description: " initial-input)))) (unless (string-match "\\S-" desc) (setq desc nil)) - (if remove (apply 'delete-region remove)) - (insert (org-make-link-string link desc)))) + (when remove (apply 'delete-region remove)) + (insert (org-make-link-string link desc)) + ;; Redisplay so as the new link has proper invisible characters. + (sit-for 0))) (defun org-link-try-special-completion (type) "If there is completion support for link type TYPE, offer it." - (let ((fun (intern (concat "org-" type "-complete-link")))) + (let ((fun (org-link-get-parameter type :complete))) (if (functionp fun) (funcall fun) (read-string "Link (no completion support): " (concat type ":"))))) (defun org-file-complete-link (&optional arg) "Create a file link using completion." - (let (file link) - (setq file (org-iread-file-name "File: ")) - (let ((pwd (file-name-as-directory (expand-file-name "."))) - (pwd1 (file-name-as-directory (abbreviate-file-name - (expand-file-name "."))))) - (cond - ((equal arg '(16)) - (setq link (concat - "file:" - (abbreviate-file-name (expand-file-name file))))) - ((string-match (concat "^" (regexp-quote pwd1) "\\(.+\\)") file) - (setq link (concat "file:" (match-string 1 file)))) - ((string-match (concat "^" (regexp-quote pwd) "\\(.+\\)") - (expand-file-name file)) - (setq link (concat - "file:" (match-string 1 (expand-file-name file))))) - (t (setq link (concat "file:" file))))) - link)) - -(defun org-iread-file-name (&rest args) - "Read-file-name using `ido-mode' speedup if available. -ARGS are arguments that may be passed to `ido-read-file-name' or `read-file-name'. -See `read-file-name' for a description of parameters." - (org-without-partial-completion - (if (and org-completion-use-ido - (fboundp 'ido-read-file-name) - (boundp 'ido-mode) ido-mode - (listp (second args))) - (let ((ido-enter-matching-directory nil)) - (apply 'ido-read-file-name args)) - (apply 'read-file-name args)))) + (let ((file (read-file-name "File: ")) + (pwd (file-name-as-directory (expand-file-name "."))) + (pwd1 (file-name-as-directory (abbreviate-file-name + (expand-file-name "."))))) + (cond ((equal arg '(16)) + (concat "file:" + (abbreviate-file-name (expand-file-name file)))) + ((string-match + (concat "^" (regexp-quote pwd1) "\\(.+\\)") file) + (concat "file:" (match-string 1 file))) + ((string-match + (concat "^" (regexp-quote pwd) "\\(.+\\)") + (expand-file-name file)) + (concat "file:" + (match-string 1 (expand-file-name file)))) + (t (concat "file:" file))))) (defun org-completing-read (&rest args) "Completing-read with SPACE being a normal character." @@ -10186,58 +10362,9 @@ See `read-file-name' for a description of parameters." (copy-keymap minibuffer-local-completion-map))) (org-defkey minibuffer-local-completion-map " " 'self-insert-command) (org-defkey minibuffer-local-completion-map "?" 'self-insert-command) - (org-defkey minibuffer-local-completion-map (kbd "C-c !") 'org-time-stamp-inactive) - (apply 'org-icompleting-read args))) - -(defun org-completing-read-no-i (&rest args) - (let (org-completion-use-ido org-completion-use-iswitchb) - (apply 'org-completing-read args))) - -(defun org-iswitchb-completing-read (prompt choices &rest args) - "Use iswitch as a completing-read replacement to choose from choices. -PROMPT is a string to prompt with. CHOICES is a list of strings to choose -from." - (let* ((iswitchb-use-virtual-buffers nil) - (iswitchb-make-buflist-hook - (lambda () - (setq iswitchb-temp-buflist choices)))) - (iswitchb-read-buffer prompt))) - -(defun org-icompleting-read (&rest args) - "Completing-read using `ido-mode' or `iswitchb' speedups if available." - (org-without-partial-completion - (if (and org-completion-use-ido - (fboundp 'ido-completing-read) - (boundp 'ido-mode) ido-mode - (listp (second args))) - (let ((ido-enter-matching-directory nil)) - (apply 'ido-completing-read (concat (car args)) - (if (consp (car (nth 1 args))) - (mapcar 'car (nth 1 args)) - (nth 1 args)) - (cddr args))) - (if (and org-completion-use-iswitchb - (boundp 'iswitchb-mode) iswitchb-mode - (listp (second args))) - (apply 'org-iswitchb-completing-read (concat (car args)) - (if (consp (car (nth 1 args))) - (mapcar 'car (nth 1 args)) - (nth 1 args)) - (cddr args)) - (apply 'completing-read args))))) - -(defun org-extract-attributes (s) - "Extract the attributes cookie from a string and set as text property." - (let (a attr (start 0) key value) - (save-match-data - (when (string-match "{{\\([^}]+\\)}}$" s) - (setq a (match-string 1 s) s (substring s 0 (match-beginning 0))) - (while (string-match "\\([a-zA-Z]+\\)=\"\\([^\"]*\\)\"" a start) - (setq key (match-string 1 a) value (match-string 2 a) - start (match-end 0) - attr (plist-put attr (intern key) value)))) - (org-add-props s nil 'org-attr attr)) - s)) + (org-defkey minibuffer-local-completion-map (kbd "C-c !") + 'org-time-stamp-inactive) + (apply #'completing-read args))) ;;; Opening/following a link @@ -10257,8 +10384,8 @@ handle this as a special case. When the function does handle the link, it must return a non-nil value. If it decides that it is not responsible for this link, it must return -nil to indicate that that Org-mode can continue with other options -like exact and fuzzy text search.") +nil to indicate that that Org can continue with other options like +exact and fuzzy text search.") (defun org-next-link (&optional search-backward) "Move forward to the next link. @@ -10270,7 +10397,7 @@ If the link is in hidden text, expose it." (setq org-link-search-failed nil) (let* ((pos (point)) (ct (org-context)) - (a (assoc :link ct)) + (a (assq :link ct)) (srch-fun (if search-backward 're-search-backward 're-search-forward))) (cond (a (goto-char (nth (if search-backward 1 2) a))) ((looking-at org-any-link-re) @@ -10279,7 +10406,7 @@ If the link is in hidden text, expose it." (if (funcall srch-fun org-any-link-re nil t) (progn (goto-char (match-beginning 0)) - (if (outline-invisible-p) (org-show-context))) + (when (org-invisible-p) (org-show-context))) (goto-char pos) (setq org-link-search-failed t) (message "No further link found")))) @@ -10292,14 +10419,9 @@ If the link is in hidden text, expose it." (defun org-translate-link (s) "Translate a link string if a translation function has been defined." - (if (and org-link-translation-function - (fboundp org-link-translation-function) - (string-match "\\([a-zA-Z0-9]+\\):\\(.*\\)" s)) - (progn - (setq s (funcall org-link-translation-function - (match-string 1 s) (match-string 2 s))) - (concat (car s) ":" (cdr s))) - s)) + (with-temp-buffer + (insert (org-trim s)) + (org-trim (org-element-interpret-data (org-element-context))))) (defun org-translate-link-from-planner (type path) "Translate a link from Emacs Planner syntax so that Org can follow it. @@ -10319,7 +10441,7 @@ This is still an experimental function, your mileage may vary." ;; A typical message link. Planner has the id after the final slash, ;; we separate it with a hash mark (setq path (concat (match-string 1 path) "#" - (org-remove-angle-brackets (match-string 2 path)))))) + (org-unbracket-string "<" ">" (match-string 2 path)))))) (cons type path)) (defun org-find-file-at-mouse (ev) @@ -10333,28 +10455,32 @@ This is still an experimental function, your mileage may vary." See the docstring of `org-open-file' for details." (interactive "e") (mouse-set-point ev) - (if (eq major-mode 'org-agenda-mode) - (org-agenda-copy-local-variable 'org-link-abbrev-alist-local)) + (when (eq major-mode 'org-agenda-mode) + (org-agenda-copy-local-variable 'org-link-abbrev-alist-local)) (org-open-at-point)) (defvar org-window-config-before-follow-link nil "The window configuration before following a link. This is saved in case the need arises to restore it.") -(defvar org-open-link-marker (make-marker) - "Marker pointing to the location where `org-open-at-point' was called.") - ;;;###autoload (defun org-open-at-point-global () - "Follow a link like Org-mode does. -This command can be called in any mode to follow a link that has -Org-mode syntax." + "Follow a link or time-stamp like Org mode does. +This command can be called in any mode to follow an external link +or a time-stamp that has Org mode syntax. Its behavior is +undefined when called on internal links (e.g., fuzzy links). +Raise an error when there is nothing to follow. " (interactive) - (org-run-like-in-org-mode 'org-open-at-point)) + (cond ((org-in-regexp org-any-link-re) + (org-open-link-from-string (match-string-no-properties 0))) + ((or (org-in-regexp org-ts-regexp-both nil t) + (org-in-regexp org-tsr-regexp-both nil t)) + (org-follow-timestamp-link)) + (t (user-error "No link found")))) ;;;###autoload (defun org-open-link-from-string (s &optional arg reference-buffer) - "Open a link in the string S, as if it was in Org-mode." + "Open a link in the string S, as if it was in Org mode." (interactive "sLink: \nP") (let ((reference-buffer (or reference-buffer (current-buffer)))) (with-temp-buffer @@ -10375,267 +10501,227 @@ Functions in this hook must return t if they identify and follow a link at point. If they don't find anything interesting at point, they must return nil.") -(defvar org-link-search-inhibit-query nil) ;; dynamically scoped -(defvar clean-buffer-list-kill-buffer-names) ; Defined in midnight.el +(defvar org-link-search-inhibit-query nil) +(defvar clean-buffer-list-kill-buffer-names) ;Defined in midnight.el +(defun org--open-doi-link (path) + "Open a \"doi\" type link. +PATH is a the path to search for, as a string." + (browse-url (url-encode-url (concat org-doi-server-url path)))) + +(defun org--open-elisp-link (path) + "Open a \"elisp\" type link. +PATH is the sexp to evaluate, as a string." + (let ((cmd path)) + (if (or (and (org-string-nw-p + org-confirm-elisp-link-not-regexp) + (string-match-p org-confirm-elisp-link-not-regexp cmd)) + (not org-confirm-elisp-link-function) + (funcall org-confirm-elisp-link-function + (format "Execute \"%s\" as elisp? " + (org-add-props cmd nil 'face 'org-warning)))) + (message "%s => %s" cmd + (if (eq (string-to-char cmd) ?\() + (eval (read cmd)) + (call-interactively (read cmd)))) + (user-error "Abort")))) + +(defun org--open-help-link (path) + "Open a \"help\" type link. +PATH is a symbol name, as a string." + (pcase (intern path) + ((and (pred fboundp) variable) (describe-function variable)) + ((and (pred boundp) function) (describe-variable function)) + (name (user-error "Unknown function or variable: %s" name)))) + +(defun org--open-shell-link (path) + "Open a \"shell\" type link. +PATH is the command to execute, as a string." + (let ((buf (generate-new-buffer "*Org Shell Output*")) + (cmd path)) + (if (or (and (org-string-nw-p + org-confirm-shell-link-not-regexp) + (string-match + org-confirm-shell-link-not-regexp cmd)) + (not org-confirm-shell-link-function) + (funcall org-confirm-shell-link-function + (format "Execute \"%s\" in shell? " + (org-add-props cmd nil + 'face 'org-warning)))) + (progn + (message "Executing %s" cmd) + (shell-command cmd buf) + (when (featurep 'midnight) + (setq clean-buffer-list-kill-buffer-names + (cons (buffer-name buf) + clean-buffer-list-kill-buffer-names)))) + (user-error "Abort")))) + (defun org-open-at-point (&optional arg reference-buffer) - "Open link at or after point. -If there is no link at point, this function will search forward up to -the end of the current line. -Normally, files will be opened by an appropriate application. If the -optional prefix argument ARG is non-nil, Emacs will visit the file. -With a double prefix argument, try to open outside of Emacs, in the -application the system uses for this file type." - (interactive "P") - ;; if in a code block, then open the block's results - (unless (call-interactively #'org-babel-open-src-block-result) - (org-load-modules-maybe) - (move-marker org-open-link-marker (point)) - (setq org-window-config-before-follow-link (current-window-configuration)) - (org-remove-occur-highlights nil nil t) - (cond - ((and (org-at-heading-p) - (not (org-at-timestamp-p t)) - (not (org-in-regexp - (concat org-plain-link-re "\\|" - org-bracket-link-regexp "\\|" - org-angle-link-re "\\|" - "[ \t]:[^ \t\n]+:[ \t]*$"))) - (not (get-text-property (point) 'org-linked-text))) - (or (let* ((lkall (org-offer-links-in-entry (current-buffer) (point) arg)) - (lk0 (car lkall)) - (lk (if (stringp lk0) (list lk0) lk0)) - (lkend (cdr lkall))) - (mapcar (lambda(l) - (search-forward l nil lkend) - (goto-char (match-beginning 0)) - (org-open-at-point)) - lk)) - (progn (require 'org-attach) (org-attach-reveal 'if-exists)))) - ((run-hook-with-args-until-success 'org-open-at-point-functions)) - ((and (org-at-timestamp-p t) - (not (org-in-regexp org-bracket-link-regexp))) - (org-follow-timestamp-link)) - ((and (or (org-footnote-at-reference-p) (org-footnote-at-definition-p)) - (not (org-in-regexp org-any-link-re))) - (org-footnote-action)) - (t - (let (type path link line search (pos (point))) - (catch 'match - (save-excursion - (or (org-in-regexp org-plain-link-re) - (skip-chars-forward "^]\n\r")) - (when (org-in-regexp org-bracket-link-regexp 1) - (setq link (org-extract-attributes - (org-link-unescape (org-match-string-no-properties 1)))) - (while (string-match " *\n *" link) - (setq link (replace-match " " t t link))) - (setq link (org-link-expand-abbrev link)) - (cond - ((or (file-name-absolute-p link) - (string-match "^\\.\\.?/" link)) - (setq type "file" path link)) - ((string-match org-link-re-with-space3 link) - (setq type (match-string 1 link) path (match-string 2 link))) - ((string-match "^help:+\\(.+\\)" link) - (setq type "help" path (match-string 1 link))) - (t (setq type "thisfile" path link))) - (throw 'match t))) - - (when (get-text-property (point) 'org-linked-text) - (setq type "thisfile" - pos (if (get-text-property (1+ (point)) 'org-linked-text) - (1+ (point)) (point)) - path (buffer-substring - (or (previous-single-property-change pos 'org-linked-text) - (point-min)) - (or (next-single-property-change pos 'org-linked-text) - (point-max))) - ;; Ensure we will search for a <<<radio>>> link, not - ;; a simple reference like <<ref>> - path (concat "<" path)) - (throw 'match t)) + "Open link, timestamp, footnote or tags at point. - (save-excursion - (when (or (org-in-regexp org-angle-link-re) - (let ((match (org-in-regexp org-plain-link-re))) - ;; Check a plain link is not within a bracket link - (and match - (save-excursion - (save-match-data - (progn - (goto-char (car match)) - (not (org-in-regexp org-bracket-link-regexp))))))) - (let ((line_ending (save-excursion (end-of-line) (point)))) - ;; We are in a line before a plain or bracket link - (or (re-search-forward org-plain-link-re line_ending t) - (re-search-forward org-bracket-link-regexp line_ending t)))) - (setq type (match-string 1) - path (org-link-unescape (match-string 2))) - (throw 'match t))) - (save-excursion - (when (org-in-regexp (org-re "\\(:[[:alnum:]_@#%:]+\\):[ \t]*$")) - (setq type "tags" - path (match-string 1)) - (while (string-match ":" path) - (setq path (replace-match "+" t t path))) - (throw 'match t))) - (when (org-in-regexp "<\\([^><\n]+\\)>") - (setq type "tree-match" - path (match-string 1)) - (throw 'match t))) - (unless path - (user-error "No link found")) +When point is on a link, follow it. Normally, files will be +opened by an appropriate application. If the optional prefix +argument ARG is non-nil, Emacs will visit the file. With +a double prefix argument, try to open outside of Emacs, in the +application the system uses for this file type. - ;; switch back to reference buffer - ;; needed when if called in a temporary buffer through - ;; org-open-link-from-string - (with-current-buffer (or reference-buffer (current-buffer)) +When point is on a timestamp, open the agenda at the day +specified. - ;; Remove any trailing spaces in path - (if (string-match " +\\'" path) - (setq path (replace-match "" t t path))) - (if (and org-link-translation-function - (fboundp org-link-translation-function)) - ;; Check if we need to translate the link - (let ((tmp (funcall org-link-translation-function type path))) - (setq type (car tmp) path (cdr tmp)))) +When point is a footnote definition, move to the first reference +found. If it is on a reference, move to the associated +definition. - (cond +When point is on a headline, display a list of every link in the +entry, so it is possible to pick one, or all, of them. If point +is on a tag, call `org-tags-view' instead. - ((assoc type org-link-protocols) - (funcall (nth 1 (assoc type org-link-protocols)) path)) - - ((equal type "help") - (let ((f-or-v (intern path))) - (cond ((fboundp f-or-v) - (describe-function f-or-v)) - ((boundp f-or-v) - (describe-variable f-or-v)) - (t (error "Not a known function or variable"))))) - - ((equal type "mailto") - (let ((cmd (car org-link-mailto-program)) - (args (cdr org-link-mailto-program)) args1 - (address path) (subject "") a) - (if (string-match "\\(.*\\)::\\(.*\\)" path) - (setq address (match-string 1 path) - subject (org-link-escape (match-string 2 path)))) - (while args - (cond - ((not (stringp (car args))) (push (pop args) args1)) - (t (setq a (pop args)) - (if (string-match "%a" a) - (setq a (replace-match address t t a))) - (if (string-match "%s" a) - (setq a (replace-match subject t t a))) - (push a args1)))) - (apply cmd (nreverse args1)))) - - ((member type '("http" "https" "ftp" "news")) - (browse-url - (concat type ":" - (if (org-string-match-p - (concat "[[:nonascii:]" - org-link-escape-chars-browser "]") - path) - (org-link-escape path org-link-escape-chars-browser) - path)))) - - ((string= type "doi") - (browse-url - (concat org-doi-server-url - (if (org-string-match-p - (concat "[[:nonascii:]" - org-link-escape-chars-browser "]") - path) - (org-link-escape path org-link-escape-chars-browser) - path)))) - - ((member type '("message")) - (browse-url (concat type ":" path))) - - ((string= type "tags") - (org-tags-view arg path)) - - ((string= type "tree-match") - (org-occur (concat "\\[" (regexp-quote path) "\\]"))) - - ((string= type "file") - (if (string-match "::\\([0-9]+\\)\\'" path) - (setq line (string-to-number (match-string 1 path)) - path (substring path 0 (match-beginning 0))) - (if (string-match "::\\(.+\\)\\'" path) - (setq search (match-string 1 path) - path (substring path 0 (match-beginning 0))))) - (if (string-match "[*?{]" (file-name-nondirectory path)) - (dired path) - (org-open-file path arg line search))) - - ((string= type "shell") - (let ((buf (generate-new-buffer "*Org Shell Output")) - (cmd path)) - (if (or (and (not (string= org-confirm-shell-link-not-regexp "")) - (string-match org-confirm-shell-link-not-regexp cmd)) - (not org-confirm-shell-link-function) - (funcall org-confirm-shell-link-function - (format "Execute \"%s\" in shell? " - (org-add-props cmd nil - 'face 'org-warning)))) - (progn - (message "Executing %s" cmd) - (shell-command cmd buf) - (if (featurep 'midnight) - (setq clean-buffer-list-kill-buffer-names - (cons buf clean-buffer-list-kill-buffer-names)))) - (error "Abort")))) - - ((string= type "elisp") - (let ((cmd path)) - (if (or (and (not (string= org-confirm-elisp-link-not-regexp "")) - (string-match org-confirm-elisp-link-not-regexp cmd)) - (not org-confirm-elisp-link-function) - (funcall org-confirm-elisp-link-function - (format "Execute \"%s\" as elisp? " - (org-add-props cmd nil - 'face 'org-warning)))) - (message "%s => %s" cmd - (if (equal (string-to-char cmd) ?\() - (eval (read cmd)) - (call-interactively (read cmd)))) - (error "Abort")))) - - ((and (string= type "thisfile") - (or (run-hook-with-args-until-success - 'org-open-link-functions path) - (and link - (string-match "^id:" link) - (or (featurep 'org-id) (require 'org-id)) - (progn - (funcall (nth 1 (assoc "id" org-link-protocols)) - (substring path 3)) - t))))) - - ((string= type "thisfile") - (if arg - (switch-to-buffer-other-window - (org-get-buffer-for-internal-link (current-buffer))) - (org-mark-ring-push)) - (let ((cmd `(org-link-search - ,path - ,(cond ((equal arg '(4)) ''occur) - ((equal arg '(16)) ''org-occur)) - ,pos))) - (condition-case nil (let ((org-link-search-inhibit-query t)) - (eval cmd)) - (error (progn (widen) (eval cmd)))))) - - (t (browse-url-at-point))))))) - (move-marker org-open-link-marker nil) - (run-hook-with-args 'org-follow-link-hook))) +When optional argument REFERENCE-BUFFER is non-nil, it should +specify a buffer from where the link search should happen. This +is used internally by `org-open-link-from-string'. -(defsubst org-uniquify (list) - "Non-destructively remove duplicate elements from LIST." - (let ((res (copy-sequence list))) (delete-dups res))) +On top of syntactically correct links, this function will also +try to open links and time-stamps in comments, example +blocks... i.e., whenever point is on something looking like +a timestamp or a link." + (interactive "P") + ;; On a code block, open block's results. + (unless (call-interactively 'org-babel-open-src-block-result) + (org-load-modules-maybe) + (setq org-window-config-before-follow-link (current-window-configuration)) + (org-remove-occur-highlights nil nil t) + (unless (run-hook-with-args-until-success 'org-open-at-point-functions) + (let* ((context + ;; Only consider supported types, even if they are not + ;; the closest one. + (org-element-lineage + (org-element-context) + '(clock footnote-definition footnote-reference headline + inlinetask link timestamp) + t)) + (type (org-element-type context)) + (value (org-element-property :value context))) + (cond + ;; On a headline or an inlinetask, but not on a timestamp, + ;; 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 (substring (match-string 5) 0 -1)) + ;; Not on tags. + (pcase (org-offer-links-in-entry (current-buffer) (point) arg) + (`(nil . ,_) + (require 'org-attach) + (org-attach-reveal 'if-exists)) + (`(,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)))))) + ;; On a footnote reference or at definition's label. + ((or (eq type 'footnote-reference) + (and (eq type 'footnote-definition) + (save-excursion + ;; Do not validate action when point is on the + ;; spaces right after the footnote label, in + ;; order to be on par with behaviour on links. + (skip-chars-forward " \t") + (let ((begin + (org-element-property :contents-begin context))) + (if begin (< (point) begin) + (= (org-element-property :post-affiliated context) + (line-beginning-position))))))) + (org-footnote-action)) + ;; No valid context. Ignore catch-all types like `headline'. + ;; If point is on something looking like a link or + ;; a time-stamp, try opening it. It may be useful in + ;; comments, example blocks... + ((memq type '(footnote-definition headline inlinetask nil)) + (call-interactively #'org-open-at-point-global)) + ;; On a clock line, make sure point is on the timestamp + ;; before opening it. + ((and (eq type 'clock) + value + (>= (point) (org-element-property :begin value)) + (<= (point) (org-element-property :end value))) + (org-follow-timestamp-link)) + ;; Do nothing on white spaces after an object. + ((>= (point) + (save-excursion + (goto-char (org-element-property :end context)) + (skip-chars-backward " \t") + (point))) + (user-error "No link found")) + ((eq type 'timestamp) (org-follow-timestamp-link)) + ((eq type 'link) + (let ((type (org-element-property :type context)) + (path (org-link-unescape (org-element-property :path context)))) + ;; Switch back to REFERENCE-BUFFER needed when called in + ;; a temporary buffer through `org-open-link-from-string'. + (with-current-buffer (or reference-buffer (current-buffer)) + (cond + ((equal type "file") + (if (string-match "[*?{]" (file-name-nondirectory path)) + (dired path) + ;; Look into `org-link-parameters' in order to find + ;; a DEDICATED-FUNCTION to open file. The function + ;; will be applied on raw link instead of parsed + ;; link due to the limitation in `org-add-link-type' + ;; ("open" function called with a single argument). + ;; If no such function is found, fallback to + ;; `org-open-file'. + (let* ((option (org-element-property :search-option context)) + (app (org-element-property :application context)) + (dedicated-function + (org-link-get-parameter + (if app (concat type "+" app) type) + :follow))) + (if dedicated-function + (funcall dedicated-function + (concat path + (and option (concat "::" option)))) + (apply #'org-open-file + path + (cond (arg) + ((equal app "emacs") 'emacs) + ((equal app "sys") 'system)) + (cond ((not option) nil) + ((string-match-p "\\`[0-9]+\\'" option) + (list (string-to-number option))) + (t (list nil + (org-link-unescape option))))))))) + ((functionp (org-link-get-parameter type :follow)) + (funcall (org-link-get-parameter type :follow) path)) + ((member type '("coderef" "custom-id" "fuzzy" "radio")) + (unless (run-hook-with-args-until-success + 'org-open-link-functions path) + (if (not arg) (org-mark-ring-push) + (switch-to-buffer-other-window + (org-get-buffer-for-internal-link (current-buffer)))) + (let ((destination + (org-with-wide-buffer + (if (equal type "radio") + (org-search-radio-target + (org-element-property :path context)) + (org-link-search + (if (member type '("custom-id" "coderef")) + (org-element-property :raw-link context) + path) + ;; Prevent fuzzy links from matching + ;; themselves. + (and (equal type "fuzzy") + (+ 2 (org-element-property :begin context))))) + (point)))) + (unless (and (<= (point-min) destination) + (>= (point-max) destination)) + (widen)) + (goto-char destination)))) + (t (browse-url-at-point)))))) + (t (user-error "No link found"))))) + (run-hook-with-args 'org-follow-link-hook))) (defun org-offer-links-in-entry (buffer marker &optional nth zero) "Offer links in the current entry and return the selected link. @@ -10644,65 +10730,57 @@ If NTH is an integer, return the NTH link found. If ZERO is a string, check also this string for a link, and if there is one, return it." (with-current-buffer buffer - (save-excursion - (save-restriction - (widen) - (goto-char marker) - (let ((re (concat "\\(" org-bracket-link-regexp "\\)\\|" - "\\(" org-angle-link-re "\\)\\|" - "\\(" org-plain-link-re "\\)")) - (cnt ?0) - (in-emacs (if (integerp nth) nil nth)) - have-zero end links link c) - (when (and (stringp zero) (string-match org-bracket-link-regexp zero)) - (push (match-string 0 zero) links) - (setq cnt (1- cnt) have-zero t)) - (save-excursion - (org-back-to-heading t) - (setq end (save-excursion (outline-next-heading) (point))) - (while (re-search-forward re end t) - (push (match-string 0) links)) - (setq links (org-uniquify (reverse links)))) - (cond - ((null links) - (message "No links")) - ((equal (length links) 1) - (setq link (car links))) - ((and (integerp nth) (>= (length links) (if have-zero (1+ nth) nth))) - (setq link (nth (if have-zero nth (1- nth)) links))) - (t ; we have to select a link - (save-excursion - (save-window-excursion - (delete-other-windows) - (with-output-to-temp-buffer "*Select Link*" - (mapc (lambda (l) - (if (not (string-match org-bracket-link-regexp l)) - (princ (format "[%c] %s\n" (incf cnt) - (org-remove-angle-brackets l))) - (if (match-end 3) - (princ (format "[%c] %s (%s)\n" (incf cnt) - (match-string 3 l) (match-string 1 l))) - (princ (format "[%c] %s\n" (incf cnt) - (match-string 1 l)))))) - links)) - (org-fit-window-to-buffer (get-buffer-window "*Select Link*")) - (message "Select link to open, RET to open all:") - (setq c (read-char-exclusive)) - (and (get-buffer "*Select Link*") (kill-buffer "*Select Link*")))) - (when (equal c ?q) (error "Abort")) - (if (equal c ?\C-m) - (setq link links) - (setq nth (- c ?0)) - (if have-zero (setq nth (1+ nth))) - (unless (and (integerp nth) (>= (length links) nth)) - (user-error "Invalid link selection")) - (setq link (nth (1- nth) links))))) - (cons link end)))))) - -;; Add special file links that specify the way of opening - -(org-add-link-type "file+sys" 'org-open-file-with-system) -(org-add-link-type "file+emacs" 'org-open-file-with-emacs) + (org-with-wide-buffer + (goto-char marker) + (let ((cnt ?0) + have-zero end links link c) + (when (and (stringp zero) (string-match org-bracket-link-regexp zero)) + (push (match-string 0 zero) links) + (setq cnt (1- cnt) have-zero t)) + (save-excursion + (org-back-to-heading t) + (setq end (save-excursion (outline-next-heading) (point))) + (while (re-search-forward org-any-link-re end t) + (push (match-string 0) links)) + (setq links (org-uniquify (reverse links)))) + (cond + ((null links) + (message "No links")) + ((equal (length links) 1) + (setq link (car links))) + ((and (integerp nth) (>= (length links) (if have-zero (1+ nth) nth))) + (setq link (nth (if have-zero nth (1- nth)) links))) + (t ; we have to select a link + (save-excursion + (save-window-excursion + (delete-other-windows) + (with-output-to-temp-buffer "*Select Link*" + (dolist (l links) + (cond + ((not (string-match org-bracket-link-regexp l)) + (princ (format "[%c] %s\n" (cl-incf cnt) + (org-unbracket-string "<" ">" l)))) + ((match-end 3) + (princ (format "[%c] %s (%s)\n" (cl-incf cnt) + (match-string 3 l) (match-string 1 l)))) + (t (princ (format "[%c] %s\n" (cl-incf cnt) + (match-string 1 l))))))) + (org-fit-window-to-buffer (get-buffer-window "*Select Link*")) + (message "Select link to open, RET to open all:") + (setq c (read-char-exclusive)) + (and (get-buffer "*Select Link*") (kill-buffer "*Select Link*")))) + (when (equal c ?q) (user-error "Abort")) + (if (equal c ?\C-m) + (setq link links) + (setq nth (- c ?0)) + (when have-zero (setq nth (1+ nth))) + (unless (and (integerp nth) (>= (length links) nth)) + (user-error "Invalid link selection")) + (setq link (nth (1- nth) links))))) + (cons link end))))) + +;; TODO: These functions are deprecated since `org-open-at-point' +;; hard-codes behaviour for "file+emacs" and "file+sys" types. (defun org-open-file-with-system (path) "Open file at PATH using the system way of opening it." (org-open-file path 'system)) @@ -10732,8 +10810,8 @@ which see. A function in this hook may also use `setq' to set the variable `description' to provide a suggestion for the descriptive text to -be used for this link when it gets inserted into an Org-mode -buffer with \\[org-insert-link].") +be used for this link when it gets inserted into an Org buffer +with \\[org-insert-link].") (defvar org-execute-file-search-functions nil "List of functions to execute a file search triggered by a link. @@ -10757,179 +10835,202 @@ the window configuration before `org-open-at-point' was called using: (set-window-configuration org-window-config-before-follow-link)") -(defun org-link-search (s &optional type avoid-pos stealth) - "Search for a link search option. -If S is surrounded by forward slashes, it is interpreted as a -regular expression. In org-mode files, this will create an `org-occur' -sparse tree. In ordinary files, `occur' will be used to list matches. -If the current buffer is in `dired-mode', grep will be used to search -in all files. If AVOID-POS is given, ignore matches near that position. +(defun org-search-radio-target (target) + "Search a radio target matching TARGET in current buffer. +White spaces are not significant." + (let ((re (format "<<<%s>>>" + (mapconcat #'regexp-quote + (split-string target) + "[ \t]+\\(?:\n[ \t]*\\)?"))) + (origin (point))) + (goto-char (point-min)) + (catch :radio-match + (while (re-search-forward re nil t) + (backward-char) + (let ((object (org-element-context))) + (when (eq (org-element-type object) 'radio-target) + (goto-char (org-element-property :begin object)) + (org-show-context 'link-search) + (throw :radio-match nil)))) + (goto-char origin) + (user-error "No match for radio target: %s" target)))) + +(defun org-link-search (s &optional avoid-pos stealth) + "Search for a search string S. + +If S starts with \"#\", it triggers a custom ID search. + +If S is enclosed within parenthesis, it initiates a coderef +search. + +If S is surrounded by forward slashes, it is interpreted as +a regular expression. In Org mode files, this will create an +`org-occur' sparse tree. In ordinary files, `occur' will be used +to list matches. If the current buffer is in `dired-mode', grep +will be used to search in all files. + +When AVOID-POS is given, ignore matches near that position. When optional argument STEALTH is non-nil, do not modify -visibility around point, thus ignoring -`org-show-hierarchy-above', `org-show-following-heading' and -`org-show-siblings' variables." - (let ((case-fold-search t) - (s0 (mapconcat 'identity (org-split-string s "[ \t\r\n]+") " ")) - (markers (concat "\\(?:" (mapconcat (lambda (x) (regexp-quote (car x))) - (append '(("") (" ") ("\t") ("\n")) - org-emphasis-alist) - "\\|") "\\)")) - (pos (point)) - (pre nil) (post nil) - words re0 re1 re2 re3 re4_ re4 re5 re2a re2a_ reall) +visibility around point, thus ignoring `org-show-context-detail' +variable. + +Search is case-insensitive and ignores white spaces. Return type +of matched result, which is either `dedicated' or `fuzzy'." + (unless (org-string-nw-p s) (error "Invalid search string \"%s\"" s)) + (let* ((case-fold-search t) + (origin (point)) + (normalized (replace-regexp-in-string "\n[ \t]*" " " s)) + (starred (eq (string-to-char normalized) ?*)) + (words (split-string (if starred (substring s 1) s))) + (s-multi-re (mapconcat #'regexp-quote words "\\(?:[ \t\n]+\\)")) + (s-single-re (mapconcat #'regexp-quote words "[ \t]+")) + type) (cond - ;; First check if there are any special search functions + ;; Check if there are any special search functions. ((run-hook-with-args-until-success 'org-execute-file-search-functions s)) - ;; Now try the builtin stuff - ((and (equal (string-to-char s0) ?#) - (> (length s0) 1) - (save-excursion - (goto-char (point-min)) - (and - (re-search-forward - (concat "^[ \t]*:CUSTOM_ID:[ \t]+" - (regexp-quote (substring s0 1)) "[ \t]*$") nil t) - (setq type 'dedicated - pos (match-beginning 0)))) - ;; There is an exact target for this - (goto-char pos) - (org-back-to-heading t))) - ((save-excursion + ((eq (string-to-char s) ?#) + ;; Look for a custom ID S if S starts with "#". + (let* ((id (substring normalized 1)) + (match (org-find-property "CUSTOM_ID" id))) + (if match (progn (goto-char match) (setf type 'dedicated)) + (error "No match for custom ID: %s" id)))) + ((string-match "\\`(\\(.*\\))\\'" normalized) + ;; Look for coderef targets if S is enclosed within parenthesis. + (let ((coderef (match-string-no-properties 1 normalized)) + (re (substring s-single-re 1 -1))) (goto-char (point-min)) - (and - (re-search-forward - (concat "<<" (regexp-quote s0) ">>") nil t) - (setq type 'dedicated - pos (match-beginning 0)))) - ;; There is an exact target for this - (goto-char pos)) - ((save-excursion - (goto-char (point-min)) - (and - (re-search-forward - (format "^[ \t]*#\\+NAME: %s" (regexp-quote s0)) nil t) - (setq type 'dedicated pos (match-beginning 0)))) - ;; Found an element with a matching #+name affiliated keyword. - (goto-char pos)) - ((and (string-match "^(\\(.*\\))$" s0) - (save-excursion + (catch :coderef-match + (while (re-search-forward re nil t) + (let ((element (org-element-at-point))) + (when (and (memq (org-element-type element) + '(example-block src-block)) + ;; Build proper regexp according to current + ;; block's label format. + (let ((label-fmt + (regexp-quote + (or (org-element-property :label-fmt element) + org-coderef-label-format)))) + (save-excursion + (beginning-of-line) + (looking-at (format ".*?\\(%s\\)[ \t]*$" + (format label-fmt coderef)))))) + (setq type 'dedicated) + (goto-char (match-beginning 1)) + (throw :coderef-match nil)))) + (goto-char origin) + (error "No match for coderef: %s" coderef)))) + ((string-match "\\`/\\(.*\\)/\\'" normalized) + ;; Look for a regular expression. + (funcall (if (derived-mode-p 'org-mode) #'org-occur #'org-do-occur) + (match-string 1 s))) + ;; From here, we handle fuzzy links. + ;; + ;; Look for targets, only if not in a headline search. + ((and (not starred) + (let ((target (format "<<%s>>" s-multi-re))) + (catch :target-match + (goto-char (point-min)) + (while (re-search-forward target nil t) + (backward-char) + (let ((context (org-element-context))) + (when (eq (org-element-type context) 'target) + (setq type 'dedicated) + (goto-char (org-element-property :begin context)) + (throw :target-match t)))) + nil)))) + ;; Look for elements named after S, only if not in a headline + ;; search. + ((and (not starred) + (let ((name (format "^[ \t]*#\\+NAME: +%s[ \t]*$" s-single-re))) + (catch :name-match + (goto-char (point-min)) + (while (re-search-forward name nil t) + (let ((element (org-element-at-point))) + (when (equal words + (split-string + (org-element-property :name element))) + (setq type 'dedicated) + (beginning-of-line) + (throw :name-match t)))) + nil)))) + ;; Regular text search. Prefer headlines in Org mode buffers. + ;; Ignore COMMENT keyword, TODO keywords, priority cookies, + ;; statistics cookies and tags. + ((and (derived-mode-p 'org-mode) + (let ((title-re + (format "%s.*\\(?:%s[ \t]\\)?.*%s" + org-outline-regexp-bol + org-comment-string + (mapconcat #'regexp-quote words ".+"))) + (cookie-re "\\[[0-9]*\\(?:%\\|/[0-9]*\\)\\]") + (comment-re (eval-when-compile + (format "\\`%s[ \t]+" org-comment-string)))) (goto-char (point-min)) - (and - (re-search-forward - (concat "[^[]" (regexp-quote - (format org-coderef-label-format - (match-string 1 s0)))) - nil t) - (setq type 'dedicated - pos (1+ (match-beginning 0)))))) - ;; There is a coderef target for this - (goto-char pos)) - ((string-match "^/\\(.*\\)/$" s) - ;; A regular expression - (cond - ((derived-mode-p 'org-mode) - (org-occur (match-string 1 s))) - (t (org-do-occur (match-string 1 s))))) - ((and (derived-mode-p 'org-mode) org-link-search-must-match-exact-headline) - (and (equal (string-to-char s) ?*) (setq s (substring s 1))) - (goto-char (point-min)) - (cond - ((let (case-fold-search) - (re-search-forward (format org-complex-heading-regexp-format - (regexp-quote s)) - nil t)) - ;; OK, found a match - (setq type 'dedicated) - (goto-char (match-beginning 0))) - ((and (not org-link-search-inhibit-query) - (eq org-link-search-must-match-exact-headline 'query-to-create) - (y-or-n-p "No match - create this as a new heading? ")) - (goto-char (point-max)) - (or (bolp) (newline)) - (insert "* " s "\n") - (beginning-of-line 0)) - (t - (goto-char pos) - (error "No match")))) - (t - ;; A normal search string - (when (equal (string-to-char s) ?*) - ;; Anchor on headlines, post may include tags. - (setq pre "^\\*+[ \t]+\\(?:\\sw+\\)?[ \t]*" - post (org-re "[ \t]*\\(?:[ \t]+:[[:alnum:]_@#%:+]:[ \t]*\\)?$") - s (substring s 1))) - (remove-text-properties - 0 (length s) - '(face nil mouse-face nil keymap nil fontified nil) s) - ;; Make a series of regular expressions to find a match - (setq words (org-split-string s "[ \n\r\t]+") - - re0 (concat "\\(<<" (regexp-quote s0) ">>\\)") - re2 (concat markers "\\(" (mapconcat 'downcase words "[ \t]+") - "\\)" markers) - re2a_ (concat "\\(" (mapconcat 'downcase words - "[ \t\r\n]+") "\\)[ \t\r\n]") - re2a (concat "[ \t\r\n]" re2a_) - re4_ (concat "\\(" (mapconcat 'downcase words - "[^a-zA-Z_\r\n]+") "\\)[^a-zA-Z_]") - re4 (concat "[^a-zA-Z_]" re4_) - - re1 (concat pre re2 post) - re3 (concat pre (if pre re4_ re4) post) - re5 (concat pre ".*" re4) - re2 (concat pre re2) - re2a (concat pre (if pre re2a_ re2a)) - re4 (concat pre (if pre re4_ re4)) - reall (concat "\\(" re0 "\\)\\|\\(" re1 "\\)\\|\\(" re2 - "\\)\\|\\(" re3 "\\)\\|\\(" re4 "\\)\\|\\(" - re5 "\\)")) - (cond - ((eq type 'org-occur) (org-occur reall)) - ((eq type 'occur) (org-do-occur (downcase reall) 'cleanup)) - (t (goto-char (point-min)) - (setq type 'fuzzy) - (if (or (and (org-search-not-self 1 re0 nil t) - (setq type 'dedicated)) - (org-search-not-self 1 re1 nil t) - (org-search-not-self 1 re2 nil t) - (org-search-not-self 1 re2a nil t) - (org-search-not-self 1 re3 nil t) - (org-search-not-self 1 re4 nil t) - (org-search-not-self 1 re5 nil t)) - (goto-char (match-beginning 1)) - (goto-char pos) - (error "No match")))))) - (and (derived-mode-p 'org-mode) - (not stealth) - (org-show-context 'link-search)) + (catch :found + (while (re-search-forward title-re nil t) + (when (equal words + (split-string + (replace-regexp-in-string + cookie-re "" + (replace-regexp-in-string + comment-re "" (org-get-heading t t t))))) + (throw :found t))) + nil))) + (beginning-of-line) + (setq type 'dedicated)) + ;; Offer to create non-existent headline depending on + ;; `org-link-search-must-match-exact-headline'. + ((and (derived-mode-p 'org-mode) + (not org-link-search-inhibit-query) + (eq org-link-search-must-match-exact-headline 'query-to-create) + (yes-or-no-p "No match - create this as a new heading? ")) + (goto-char (point-max)) + (unless (bolp) (newline)) + (org-insert-heading nil t t) + (insert s "\n") + (beginning-of-line 0)) + ;; Only headlines are looked after. No need to process + ;; further: throw an error. + ((and (derived-mode-p 'org-mode) + (or starred org-link-search-must-match-exact-headline)) + (goto-char origin) + (error "No match for fuzzy expression: %s" normalized)) + ;; Regular text search. + ((catch :fuzzy-match + (goto-char (point-min)) + (while (re-search-forward s-multi-re nil t) + ;; Skip match if it contains AVOID-POS or it is included in + ;; a link with a description but outside the description. + (unless (or (and avoid-pos + (<= (match-beginning 0) avoid-pos) + (> (match-end 0) avoid-pos)) + (and (save-match-data + (org-in-regexp org-bracket-link-regexp)) + (match-beginning 3) + (or (> (match-beginning 3) (point)) + (<= (match-end 3) (point))) + (org-element-lineage + (save-match-data (org-element-context)) + '(link) t))) + (goto-char (match-beginning 0)) + (setq type 'fuzzy) + (throw :fuzzy-match t))) + nil)) + ;; All failed. Throw an error. + (t (goto-char origin) + (error "No match for fuzzy expression: %s" normalized))) + ;; Disclose surroundings of match, if appropriate. + (when (and (derived-mode-p 'org-mode) (not stealth)) + (org-show-context 'link-search)) type)) -(defun org-search-not-self (group &rest args) - "Execute `re-search-forward', but only accept matches that do not -enclose the position of `org-open-link-marker'." - (let ((m org-open-link-marker)) - (catch 'exit - (while (apply #'re-search-forward args) - (unless (get-text-property (match-end group) 'intangible) ; Emacs 21 - (goto-char (match-end group)) - (if (and (or (not (eq (marker-buffer m) (current-buffer))) - (> (match-beginning 0) (marker-position m)) - (< (match-end 0) (marker-position m))) - (save-match-data - (or (not (org-in-regexp - org-bracket-link-analytic-regexp 1)) - (not (match-end 4)) ; no description - (and (<= (match-beginning 4) (point)) - (>= (match-end 4) (point)))))) - (throw 'exit (point)))))))) - (defun org-get-buffer-for-internal-link (buffer) "Return a buffer to be used for displaying the link target of internal links." (cond ((not org-display-internal-link-with-indirect-buffer) buffer) - ((string-match "(Clone)$" (buffer-name buffer)) + ((string-suffix-p "(Clone)" (buffer-name buffer)) (message "Buffer is already a clone, not making another one") ;; we also do not modify visibility in this case buffer) @@ -10953,8 +11054,8 @@ to read." (goto-char (point-min)) (when (re-search-forward "match[a-z]+" nil t) (setq beg (match-end 0)) - (if (re-search-forward "^[ \t]*[0-9]+" nil t) - (setq end (1- (match-beginning 0))))) + (when (re-search-forward "^[ \t]*[0-9]+" nil t) + (setq end (1- (match-beginning 0))))) (and beg end (let ((inhibit-read-only t)) (delete-region beg end))) (goto-char (point-min)) (select-window cwin)))) @@ -10962,13 +11063,13 @@ to read." ;;; The mark ring for links jumps (defvar org-mark-ring nil - "Mark ring for positions before jumps in Org-mode.") + "Mark ring for positions before jumps in Org mode.") (defvar org-mark-ring-last-goto nil "Last position in the mark ring used to go back.") ;; Fill and close the ring (setq org-mark-ring nil org-mark-ring-last-goto nil) ;; in case file is reloaded -(loop for i from 1 to org-mark-ring-length do - (push (make-marker) org-mark-ring)) +(dotimes (_ org-mark-ring-length) + (push (make-marker) org-mark-ring)) (setcdr (nthcdr (1- org-mark-ring-length) org-mark-ring) org-mark-ring) @@ -10982,15 +11083,15 @@ to read." (or buffer (current-buffer))) (message "%s" (substitute-command-keys - "Position saved to mark ring, go back with \\[org-mark-ring-goto]."))) + "Position saved to mark ring, go back with \ +`\\[org-mark-ring-goto]'."))) (defun org-mark-ring-goto (&optional n) "Jump to the previous position in the mark ring. With prefix arg N, jump back that many stored positions. When called several times in succession, walk through the entire ring. -Org-mode commands jumping to a different position in the current file, -or to another Org-mode file, automatically push the old position -onto the ring." +Org mode commands jumping to a different position in the current file, +or to another Org file, automatically push the old position onto the ring." (interactive "p") (let (p m) (if (eq last-command this-command) @@ -10998,25 +11099,19 @@ onto the ring." (setq p org-mark-ring)) (setq org-mark-ring-last-goto p) (setq m (car p)) - (org-pop-to-buffer-same-window (marker-buffer m)) + (pop-to-buffer-same-window (marker-buffer m)) (goto-char m) - (if (or (outline-invisible-p) (org-invisible-p2)) (org-show-context 'mark-goto)))) + (when (or (org-invisible-p) (org-invisible-p2)) (org-show-context 'mark-goto)))) -(defun org-remove-angle-brackets (s) - (if (equal (substring s 0 1) "<") (setq s (substring s 1))) - (if (equal (substring s -1) ">") (setq s (substring s 0 -1))) - s) (defun org-add-angle-brackets (s) - (if (equal (substring s 0 1) "<") nil (setq s (concat "<" s))) - (if (equal (substring s -1) ">") nil (setq s (concat s ">"))) - s) -(defun org-remove-double-quotes (s) - (if (equal (substring s 0 1) "\"") (setq s (substring s 1))) - (if (equal (substring s -1) "\"") (setq s (substring s 0 -1))) + (unless (equal (substring s 0 1) "<") (setq s (concat "<" s))) + (unless (equal (substring s -1) ">") (setq s (concat s ">"))) s) ;;; Following specific links +(defvar org-agenda-buffer-tmp-name) +(defvar org-agenda-start-on-weekday) (defun org-follow-timestamp-link () "Open an agenda view for the time-stamp date/range at point." (cond @@ -11030,7 +11125,7 @@ onto the ring." (format "*Org Agenda(a:%s)" (concat (substring t1 0 10) "--" (substring t2 0 10))))) (org-agenda-list nil tt1 (1+ (- tt2 tt1)))))) - ((org-at-timestamp-p t) + ((org-at-timestamp-p 'lax) (let ((org-agenda-buffer-tmp-name (format "*Org Agenda(a:%s)" (substring (match-string 1) 0 10)))) (org-agenda-list nil (time-to-days (org-time-string-to-time @@ -11071,43 +11166,47 @@ If the file does not exist, an error is thrown." buffer-file-name (substitute-in-file-name (expand-file-name path)))) (file-apps (append org-file-apps (org-default-apps))) - (apps (org-remove-if + (apps (cl-remove-if 'org-file-apps-entry-match-against-dlink-p file-apps)) - (apps-dlink (org-remove-if-not + (apps-dlink (cl-remove-if-not 'org-file-apps-entry-match-against-dlink-p file-apps)) (remp (and (assq 'remote apps) (org-file-remote-p file))) - (dirp (if remp nil (file-directory-p file))) + (dirp (unless remp (file-directory-p file))) (file (if (and dirp org-open-directory-means-index-dot-org) (concat (file-name-as-directory file) "index.org") file)) (a-m-a-p (assq 'auto-mode apps)) (dfile (downcase file)) - ;; reconstruct the original file: link from the PATH, LINE and SEARCH args - (link (cond ((and (eq line nil) - (eq search nil)) - file) - (line - (concat file "::" (number-to-string line))) - (search - (concat file "::" search)))) + ;; Reconstruct the original link from the PATH, LINE and + ;; SEARCH args. + (link (cond (line (concat file "::" (number-to-string line))) + (search (concat file "::" search)) + (t file))) (dlink (downcase link)) - (old-buffer (current-buffer)) - (old-pos (point)) - (old-mode major-mode) - ext cmd link-match-data) - (if (string-match "^.*\\.\\([a-zA-Z0-9]+\\.gz\\)$" dfile) - (setq ext (match-string 1 dfile)) - (if (string-match "^.*\\.\\([a-zA-Z0-9]+\\)$" dfile) - (setq ext (match-string 1 dfile)))) + (ext + (and (string-match "\\`.*?\\.\\([a-zA-Z0-9]+\\(\\.gz\\)?\\)\\'" dfile) + (match-string 1 dfile))) + (save-position-maybe + (let ((old-buffer (current-buffer)) + (old-pos (point)) + (old-mode major-mode)) + (lambda () + (and (derived-mode-p 'org-mode) + (eq old-mode 'org-mode) + (or (not (eq old-buffer (current-buffer))) + (not (eq old-pos (point)))) + (org-mark-ring-push old-pos old-buffer))))) + cmd link-match-data) (cond ((member in-emacs '((16) system)) - (setq cmd (cdr (assoc 'system apps)))) + (setq cmd (cdr (assq 'system apps)))) (in-emacs (setq cmd 'emacs)) (t - (setq cmd (or (and remp (cdr (assoc 'remote apps))) - (and dirp (cdr (assoc 'directory apps))) - ; first, try matching against apps-dlink - ; if we get a match here, store the match data for later + (setq cmd (or (and remp (cdr (assq 'remote apps))) + (and dirp (cdr (assq 'directory apps))) + ;; First, try matching against apps-dlink if we + ;; get a match here, store the match data for + ;; later. (let ((match (assoc-default dlink apps-dlink 'string-match))) (if match @@ -11120,9 +11219,9 @@ If the file does not exist, an error is thrown." (assoc-default dfile (org-apps-regexp-alist apps a-m-a-p) 'string-match) (cdr (assoc ext apps)) - (cdr (assoc t apps)))))) + (cdr (assq t apps)))))) (when (eq cmd 'system) - (setq cmd (cdr (assoc 'system apps)))) + (setq cmd (cdr (assq 'system apps)))) (when (eq cmd 'default) (setq cmd (cdr (assoc t apps)))) (when (eq cmd 'mailcap) @@ -11133,21 +11232,20 @@ If the file does not exist, an error is thrown." (if (stringp command) (setq cmd command) (setq cmd 'emacs)))) - (if (and (not (eq cmd 'emacs)) ; Emacs has no problems with non-ex files - (not (file-exists-p file)) - (not org-open-non-existing-files)) - (user-error "No such file: %s" file)) + (when (and (not (eq cmd 'emacs)) ; Emacs has no problems with non-ex files + (not (file-exists-p file)) + (not org-open-non-existing-files)) + (user-error "No such file: %s" file)) (cond ((and (stringp cmd) (not (string-match "^\\s-*$" cmd))) ;; Remove quotes around the file name - we'll use shell-quote-argument. (while (string-match "['\"]%s['\"]" cmd) (setq cmd (replace-match "%s" t t cmd))) - (while (string-match "%s" cmd) - (setq cmd (replace-match - (save-match-data - (shell-quote-argument - (convert-standard-filename file))) - t t cmd))) + (setq cmd (replace-regexp-in-string + "%s" + (shell-quote-argument (convert-standard-filename file)) + cmd + nil t)) ;; Replace "%1", "%2" etc. in command with group matches from regex (save-match-data @@ -11169,18 +11267,34 @@ If the file does not exist, an error is thrown." (eq cmd 'emacs)) (funcall (cdr (assq 'file org-link-frame-setup)) file) (widen) - (if line (org-goto-line line) - (if search (org-link-search search)))) + (cond (line (org-goto-line line) + (when (derived-mode-p 'org-mode) (org-reveal))) + (search (condition-case err + (org-link-search search) + ;; Save position before error-ing out so user + ;; can easily move back to the original buffer. + (error (funcall save-position-maybe) + (error (nth 1 err))))))) + ((functionp cmd) + (save-match-data + (set-match-data link-match-data) + (condition-case nil + (funcall cmd file link) + ;; FIXME: Remove this check when most default installations + ;; of Emacs have at least Org 9.0. + ((debug wrong-number-of-arguments wrong-type-argument + invalid-function) + (user-error "Please see Org News for version 9.0 about \ +`org-file-apps'--Lisp error: %S" cmd))))) ((consp cmd) - (let ((file (convert-standard-filename file))) - (save-match-data - (set-match-data link-match-data) - (eval cmd)))) + ;; FIXME: Remove this check when most default installations of + ;; Emacs have at least Org 9.0. Heads-up instead of silently + ;; fall back to `org-link-frame-setup' for an old usage of + ;; `org-file-apps' with sexp instead of a function for `cmd'. + (user-error "Please see Org News for version 9.0 about \ +`org-file-apps'--Error: Deprecated usage of %S" cmd)) (t (funcall (cdr (assq 'file org-link-frame-setup)) file))) - (and (derived-mode-p 'org-mode) (eq old-mode 'org-mode) - (or (not (equal old-buffer (current-buffer))) - (not (equal old-pos (point)))) - (org-mark-ring-push old-pos old-buffer)))) + (funcall save-position-maybe))) (defun org-file-apps-entry-match-against-dlink-p (entry) "This function returns non-nil if `entry' uses a regular @@ -11220,16 +11334,15 @@ be opened in Emacs." (append (delq nil (mapcar (lambda (x) - (if (not (stringp (car x))) - nil + (unless (not (stringp (car x))) (if (string-match "\\W" (car x)) x (cons (concat "\\." (car x) "\\'") (cdr x))))) list)) - (if add-auto-mode - (mapcar (lambda (x) (cons (car x) 'emacs)) auto-mode-alist)))) + (when add-auto-mode + (mapcar (lambda (x) (cons (car x) 'emacs)) auto-mode-alist)))) -(defvar ange-ftp-name-format) ; to silence the XEmacs compiler. +(defvar ange-ftp-name-format) (defun org-file-remote-p (file) "Test whether FILE specifies a location on a remote system. Return non-nil if the location is indeed remote. @@ -11262,8 +11375,8 @@ on the system \"/user@host:\"." ((not (listp org-reverse-note-order)) nil) (t (catch 'exit (dolist (entry org-reverse-note-order) - (if (string-match (car entry) buffer-file-name) - (throw 'exit (cdr entry)))))))) + (when (string-match (car entry) buffer-file-name) + (throw 'exit (cdr entry)))))))) (defvar org-refile-target-table nil "The list of refile targets, created by `org-refile'.") @@ -11288,7 +11401,7 @@ on the system \"/user@host:\"." (defun org-refile-cache-clear () "Clear the refile cache and disable all the markers." - (mapc (lambda (m) (move-marker m nil)) org-refile-markers) + (dolist (m org-refile-markers) (move-marker m nil)) (setq org-refile-markers nil) (setq org-refile-cache nil) (message "Refile cache has been cleared")) @@ -11323,17 +11436,23 @@ on the system \"/user@host:\"." org-refile-cache)))) (and set (org-refile-cache-check-set set) set))))) -(defun org-refile-get-targets (&optional default-buffer excluded-entries) +(defvar org-outline-path-cache nil + "Alist between buffer positions and outline paths. +It value is an alist (POSITION . PATH) where POSITION is the +buffer position at the beginning of an entry and PATH is a list +of strings describing the outline path for that entry, in reverse +order.") + +(defun org-refile-get-targets (&optional default-buffer) "Produce a table with refile targets." (let ((case-fold-search nil) ;; otherwise org confuses "TODO" as a kw and "Todo" as a word (entries (or org-refile-targets '((nil . (:level . 1))))) - targets tgs txt re files desc descre fast-path-p level pos0) + targets tgs files desc descre) (message "Getting targets...") (with-current-buffer (or default-buffer (current-buffer)) (dolist (entry entries) (setq files (car entry) desc (cdr entry)) - (setq fast-path-p nil) (cond ((null files) (setq files (list (current-buffer)))) ((eq files 'org-agenda-files) @@ -11342,7 +11461,7 @@ on the system \"/user@host:\"." (setq files (funcall files))) ((and (symbolp files) (boundp files)) (setq files (symbol-value files)))) - (if (stringp files) (setq files (list files))) + (when (stringp files) (setq files (list files))) (cond ((eq (car desc) :tag) (setq descre (concat "^\\*+[ \t]+.*?:" (regexp-quote (cdr desc)) ":"))) @@ -11357,7 +11476,6 @@ on the system \"/user@host:\"." (cdr desc))) "\\}[ \t]"))) ((eq (car desc) :maxlevel) - (setq fast-path-p t) (setq descre (concat "^\\*\\{1," (number-to-string (if org-odd-levels-only (1- (* 2 (cdr desc))) @@ -11365,99 +11483,119 @@ on the system \"/user@host:\"." "\\}[ \t]"))) (t (error "Bad refiling target description %s" desc))) (dolist (f files) - (with-current-buffer - (if (bufferp f) f (org-get-agenda-file-buffer f)) + (with-current-buffer (if (bufferp f) f (org-get-agenda-file-buffer f)) (or (setq tgs (org-refile-cache-get (buffer-file-name) descre)) (progn - (if (bufferp f) (setq f (buffer-file-name - (buffer-base-buffer f)))) + (when (bufferp f) + (setq f (buffer-file-name (buffer-base-buffer f)))) (setq f (and f (expand-file-name f))) - (if (eq org-refile-use-outline-path 'file) - (push (list (file-name-nondirectory f) f nil nil) tgs)) - (save-excursion - (save-restriction - (widen) - (goto-char (point-min)) - (while (re-search-forward descre nil t) - (goto-char (setq pos0 (point-at-bol))) - (catch 'next - (when org-refile-target-verify-function - (save-match-data - (or (funcall org-refile-target-verify-function) - (throw 'next t)))) - (when (and (looking-at org-complex-heading-regexp) - (not (member (match-string 4) excluded-entries)) - (match-string 4)) - (setq level (org-reduced-level - (- (match-end 1) (match-beginning 1))) - txt (org-link-display-format (match-string 4)) - txt (replace-regexp-in-string "\\( *[[0-9]+/?[0-9]*%?]\\)+$" "" txt) - re (format org-complex-heading-regexp-format - (regexp-quote (match-string 4)))) - (when org-refile-use-outline-path - (setq txt (mapconcat - 'org-protect-slash - (append - (if (eq org-refile-use-outline-path - 'file) - (list (file-name-nondirectory - (buffer-file-name - (buffer-base-buffer)))) - (if (eq org-refile-use-outline-path - 'full-file-path) - (list (buffer-file-name - (buffer-base-buffer))))) - (org-get-outline-path fast-path-p - level txt) - (list txt)) - "/"))) - (push (list txt f re (org-refile-marker (point))) - tgs))) - (when (= (point) pos0) - ;; verification function has not moved point - (goto-char (point-at-eol)))))))) + (when (eq org-refile-use-outline-path 'file) + (push (list (file-name-nondirectory f) f nil nil) tgs)) + (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") - (nreverse targets))) - -(defun org-protect-slash (s) - (while (string-match "/" s) - (setq s (replace-match "\\" t t s))) - s) - -(defvar org-olpa (make-vector 20 nil)) - -(defun org-get-outline-path (&optional fastp level heading) - "Return the outline path to the current entry, as a list. - -The parameters FASTP, LEVEL, and HEADING are for use by a scanner -routine which makes outline path derivations for an entire file, -avoiding backtracing. Refile target collection makes use of that." - (if fastp - (progn - (if (> level 19) - (error "Outline path failure, more than 19 levels")) - (loop for i from level upto 19 do - (aset org-olpa i nil)) - (prog1 - (delq nil (append org-olpa nil)) - (aset org-olpa level heading))) - (let (rtn case-fold-search) - (save-excursion - (save-restriction - (widen) - (while (org-up-heading-safe) - (when (looking-at org-complex-heading-regexp) - (push (org-trim - (replace-regexp-in-string - ;; Remove statistical/checkboxes cookies - "\\[[0-9]+%\\]\\|\\[[0-9]+/[0-9]+\\]" "" - (org-match-string-no-properties 4))) - rtn))) - rtn))))) + (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. @@ -11467,38 +11605,28 @@ such as the file name. SEPARATOR is inserted between the different parts of the path, the default is \"/\"." (setq width (or width 79)) - (if prefix (setq width (- width (length prefix)))) - (if (not path) - (or prefix "") - (let* ((nsteps (length path)) - (total-width (+ nsteps (apply '+ (mapcar 'length path)))) - (maxwidth (if (<= total-width width) - 10000 ;; everything fits - ;; we need to shorten the level headings - (/ (- width nsteps) nsteps))) - (org-odd-levels-only nil) - (n 0) - (total (1+ (length prefix)))) - (setq maxwidth (max maxwidth 10)) - (concat prefix - (if prefix (or separator "/")) - (mapconcat - (lambda (h) - (setq n (1+ n)) - (if (and (= n nsteps) (< maxwidth 10000)) - (setq maxwidth (- total-width total))) - (if (< (length h) maxwidth) - (progn (setq total (+ total (length h) 1)) h) - (setq h (substring h 0 (- maxwidth 2)) - total (+ total maxwidth 1)) - (if (string-match "[ \t]+\\'" h) - (setq h (substring h 0 (match-beginning 0)))) - (setq h (concat h ".."))) - (org-add-props h nil 'face - (nth (% (1- n) org-n-level-faces) - org-level-faces)) - h) - path (or separator "/")))))) + (setq path (delq nil path)) + (unless (> width 0) + (user-error "Argument `width' must be positive")) + (setq separator (or separator "/")) + (let* ((org-odd-levels-only nil) + (fpath (concat + prefix (and prefix path separator) + (mapconcat + (lambda (s) (replace-regexp-in-string "[ \t]+\\'" "" s)) + (cl-loop for head in path + for n from 0 + collect (org-add-props + head nil 'face + (nth (% n org-n-level-faces) org-level-faces))) + separator)))) + (when (> (length fpath) width) + (if (< width 7) + ;; It's unlikely that `width' will be this small, but don't + ;; waste characters by adding ".." if it is. + (setq fpath (substring fpath 0 width)) + (setf (substring fpath (- width 2)) ".."))) + fpath)) (defun org-display-outline-path (&optional file current separator just-return-string) "Display the current outline path in the echo area. @@ -11513,10 +11641,10 @@ If JUST-RETURN-STRING is non-nil, return a string, don't display a message." (bfn (buffer-file-name (buffer-base-buffer))) (path (and (derived-mode-p 'org-mode) (org-get-outline-path))) res) - (if current (setq path (append path - (save-excursion - (org-back-to-heading t) - (if (looking-at org-complex-heading-regexp) + (when current (setq path (append path + (save-excursion + (org-back-to-heading t) + (when (looking-at org-complex-heading-regexp) (list (match-string 4))))))) (setq res (org-format-outline-path @@ -11546,25 +11674,27 @@ the *old* location.") (let ((org-refile-keep t)) (funcall 'org-refile nil nil nil "Copy"))) -(defun org-refile (&optional goto default-buffer rfloc msg) +(defun org-refile (&optional arg default-buffer rfloc msg) "Move the entry or entries at point to another heading. + The list of target headings is compiled using the information in `org-refile-targets', which see. -At the target location, the entry is filed as a subitem of the target -heading. Depending on `org-reverse-note-order', the new subitem will -either be the first or the last subitem. +At the target location, the entry is filed as a subitem of the +target heading. Depending on `org-reverse-note-order', the new +subitem will either be the first or the last subitem. -If there is an active region, all entries in that region will be moved. -However, the region must fulfill the requirement that the first heading -is the first one sets the top-level of the moved text - at most siblings -below it are allowed. +If there is an active region, all entries in that region will be +refiled. However, the region must fulfill the requirement that +the first heading sets the top-level of the moved text. -With prefix arg GOTO, the command will only visit the target location +With a `\\[universal-argument]' ARG, the command will only visit the target \ +location and not actually move anything. -With a double prefix arg \\[universal-argument] \\[universal-argument], \ -go to the location where the last refiling operation has put the subtree. +With a prefix `\\[universal-argument] \\[universal-argument]', go to the \ +location where the last +refiling operation has put the subtree. With a numeric prefix argument of `2', refile to the running clock. @@ -11578,26 +11708,23 @@ RFLOC can be a refile location obtained in a different way. MSG is a string to replace \"Refile\" in the default prompt with another verb. E.g. `org-copy' sets this parameter to \"Copy\". -See also `org-refile-use-outline-path' and `org-completion-use-ido'. +See also `org-refile-use-outline-path'. -If you are using target caching (see `org-refile-use-cache'), -you have to clear the target cache in order to find new targets. -This can be done with a 0 prefix (`C-0 C-c C-w') or a triple +If you are using target caching (see `org-refile-use-cache'), you +have to clear the target cache in order to find new targets. +This can be done with a `0' prefix (`C-0 C-c C-w') or a triple prefix argument (`C-u C-u C-u C-c C-w')." - (interactive "P") - (if (member goto '(0 (64))) + (if (member arg '(0 (64))) (org-refile-cache-clear) (let* ((actionmsg (cond (msg msg) - ((equal goto 3) "Refile (and keep)") + ((equal arg 3) "Refile (and keep)") (t "Refile"))) - (cbuf (current-buffer)) (regionp (org-region-active-p)) (region-start (and regionp (region-beginning))) (region-end (and regionp (region-end))) - (filename (buffer-file-name (buffer-base-buffer cbuf))) - (org-refile-keep (if (equal goto 3) t org-refile-keep)) - pos it nbuf file re level reversed) + (org-refile-keep (if (equal arg 3) t org-refile-keep)) + pos it nbuf file level reversed) (setq last-command nil) (when regionp (goto-char region-start) @@ -11610,10 +11737,10 @@ prefix argument (`C-u C-u C-u C-c C-w')." (org-toggle-heading) (setq region-end (+ (- (point-at-eol) s) region-end))))) (user-error "The region is not a (sequence of) subtree(s)"))) - (if (equal goto '(16)) + (if (equal arg '(16)) (org-refile-goto-last-stored) (when (or - (and (equal goto 2) + (and (equal arg 2) org-clock-hd-marker (marker-buffer org-clock-hd-marker) (prog1 (setq it (list (or org-clock-heading "running clock") @@ -11621,44 +11748,47 @@ prefix argument (`C-u C-u C-u C-c C-w')." (marker-buffer org-clock-hd-marker)) "" (marker-position org-clock-hd-marker))) - (setq goto nil))) - (setq it (or rfloc - (let (heading-text) - (save-excursion - (unless (and goto (listp goto)) - (org-back-to-heading t) - (setq heading-text - (nth 4 (org-heading-components)))) - - (org-refile-get-location - (cond ((and goto (listp goto)) "Goto") - (regionp (concat actionmsg " region to")) - (t (concat actionmsg " subtree \"" - heading-text "\" to"))) - default-buffer - (and (not (equal '(4) goto)) - org-refile-allow-creating-parent-nodes) - goto)))))) + (setq arg nil))) + (setq it + (or rfloc + (let (heading-text) + (save-excursion + (unless (and arg (listp arg)) + (org-back-to-heading t) + (setq heading-text + (replace-regexp-in-string + org-bracket-link-regexp + "\\3" + (or (nth 4 (org-heading-components)) + "")))) + (org-refile-get-location + (cond ((and arg (listp arg)) "Goto") + (regionp (concat actionmsg " region to")) + (t (concat actionmsg " subtree \"" + heading-text "\" to"))) + default-buffer + (and (not (equal '(4) arg)) + org-refile-allow-creating-parent-nodes))))))) (setq file (nth 1 it) - re (nth 2 it) pos (nth 3 it)) - (if (and (not goto) - pos - (equal (buffer-file-name) file) - (if regionp - (and (>= pos region-start) - (<= pos region-end)) - (and (>= pos (point)) - (< pos (save-excursion - (org-end-of-subtree t t)))))) - (error "Cannot refile to position inside the tree or region")) - + (when (and (not arg) + pos + (equal (buffer-file-name) file) + (if regionp + (and (>= pos region-start) + (<= pos region-end)) + (and (>= pos (point)) + (< pos (save-excursion + (org-end-of-subtree t t)))))) + (error "Cannot refile to position inside the tree or region")) (setq nbuf (or (find-buffer-visiting file) (find-file-noselect file))) - (if (and goto (not (equal goto 3))) + (if (and arg (not (equal arg 3))) (progn - (org-pop-to-buffer-same-window nbuf) - (goto-char pos) + (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 @@ -11668,50 +11798,47 @@ prefix argument (`C-u C-u C-u C-c C-w')." (with-current-buffer (setq nbuf (or (find-buffer-visiting file) (find-file-noselect file))) (setq reversed (org-notes-order-reversed-p)) - (save-excursion - (save-restriction - (widen) - (if pos - (progn - (goto-char pos) - (looking-at org-outline-regexp) - (setq level (org-get-valid-level (funcall outline-level) 1)) - (goto-char - (if reversed - (or (outline-next-heading) (point-max)) - (or (save-excursion (org-get-next-sibling)) - (org-end-of-subtree t t) - (point-max))))) - (setq level 1) - (if (not reversed) - (goto-char (point-max)) - (goto-char (point-min)) - (or (outline-next-heading) (goto-char (point-max))))) - (if (not (bolp)) (newline)) - (org-paste-subtree level) - (when org-log-refile - (org-add-log-setup 'refile nil nil 'findpos org-log-refile) - (unless (eq org-log-refile 'note) - (save-excursion (org-add-log-note)))) - (and org-auto-align-tags - (let ((org-loop-over-headlines-in-active-region nil)) - (org-set-tags nil t))) - (let ((bookmark-name (plist-get org-bookmark-names-plist - :last-refile))) - (when bookmark-name - (with-demoted-errors - (bookmark-set bookmark-name)))) - ;; If we are refiling for capture, make sure that the - ;; last-capture pointers point here - (when (org-bound-and-true-p org-refile-for-capture) - (let ((bookmark-name (plist-get org-bookmark-names-plist - :last-capture-marker))) - (when bookmark-name - (with-demoted-errors - (bookmark-set bookmark-name)))) - (move-marker org-capture-last-stored-marker (point))) - (if (fboundp 'deactivate-mark) (deactivate-mark)) - (run-hooks 'org-after-refile-insert-hook)))) + (org-with-wide-buffer + (if pos + (progn + (goto-char pos) + (setq level (org-get-valid-level (funcall outline-level) 1)) + (goto-char + (if reversed + (or (outline-next-heading) (point-max)) + (or (save-excursion (org-get-next-sibling)) + (org-end-of-subtree t t) + (point-max))))) + (setq level 1) + (if (not reversed) + (goto-char (point-max)) + (goto-char (point-min)) + (or (outline-next-heading) (goto-char (point-max))))) + (unless (bolp) (newline)) + (org-paste-subtree level nil nil t) + (when org-log-refile + (org-add-log-setup 'refile nil nil org-log-refile) + (unless (eq org-log-refile 'note) + (save-excursion (org-add-log-note)))) + (and org-auto-align-tags + (let ((org-loop-over-headlines-in-active-region nil)) + (org-set-tags nil t))) + (let ((bookmark-name (plist-get org-bookmark-names-plist + :last-refile))) + (when bookmark-name + (with-demoted-errors + (bookmark-set bookmark-name)))) + ;; If we are refiling for capture, make sure that the + ;; last-capture pointers point here + (when (bound-and-true-p org-capture-is-refiling) + (let ((bookmark-name (plist-get org-bookmark-names-plist + :last-capture-marker))) + (when bookmark-name + (with-demoted-errors + (bookmark-set bookmark-name)))) + (move-marker org-capture-last-stored-marker (point))) + (when (fboundp 'deactivate-mark) (deactivate-mark)) + (run-hooks 'org-after-refile-insert-hook))) (unless org-refile-keep (if regionp (delete-region (point) (+ (point) (- region-end region-start))) @@ -11726,7 +11853,7 @@ prefix argument (`C-u C-u C-u C-c C-w')." (defun org-refile-goto-last-stored () "Go to the location where the last refile was stored." (interactive) - (bookmark-jump "org-refile-last-stored") + (bookmark-jump (plist-get org-bookmark-names-plist :last-refile)) (message "This is the location of the last refile")) (defun org-refile--get-location (refloc tbl) @@ -11740,35 +11867,22 @@ Also check `org-refile-target-table'." (list (replace-regexp-in-string "/$" "" refloc) (replace-regexp-in-string "\\([^/]\\)$" "\\1/" refloc)))))) -(defun org-refile-get-location (&optional prompt default-buffer new-nodes - no-exclude) +(defun org-refile-get-location (&optional prompt default-buffer new-nodes) "Prompt the user for a refile location, using PROMPT. PROMPT should not be suffixed with a colon and a space, because this function appends the default value from -`org-refile-history' automatically, if that is not empty. -When NO-EXCLUDE is set, do not exclude headlines in the current subtree, -this is used for the GOTO interface." +`org-refile-history' automatically, if that is not empty." (let ((org-refile-targets org-refile-targets) - (org-refile-use-outline-path org-refile-use-outline-path) - excluded-entries) - (when (and (derived-mode-p 'org-mode) - (not org-refile-use-cache) - (not no-exclude)) - (org-map-tree - (lambda() - (setq excluded-entries - (append excluded-entries (list (org-get-heading t t))))))) - (setq org-refile-target-table - (org-refile-get-targets default-buffer excluded-entries))) + (org-refile-use-outline-path org-refile-use-outline-path)) + (setq org-refile-target-table (org-refile-get-targets default-buffer))) (unless org-refile-target-table (user-error "No refile targets")) (let* ((cbuf (current-buffer)) - (partial-completion-mode nil) (cfn (buffer-file-name (buffer-base-buffer cbuf))) (cfunc (if (and org-refile-use-outline-path org-outline-path-complete-in-steps) - 'org-olpath-completing-read - 'org-icompleting-read)) + #'org-olpath-completing-read + #'completing-read)) (extra (if org-refile-use-outline-path "/" "")) (cbnex (concat (buffer-name) extra)) (filename (and cfn (expand-file-name cfn))) @@ -11803,8 +11917,8 @@ this is used for the GOTO interface." (cons (car pa) (if (assoc (car org-refile-history) tbl) org-refile-history (cdr org-refile-history)))) - (if (equal (car org-refile-history) (nth 1 org-refile-history)) - (pop org-refile-history))) + (when (equal (car org-refile-history) (nth 1 org-refile-history)) + (pop org-refile-history))) pa) (if (string-match "\\`\\(.*\\)/\\([^/]+\\)\\'" answ) (progn @@ -11827,20 +11941,18 @@ this is used for the GOTO interface." (pos (nth 3 refile-pointer)) buffer) (if (and (not (markerp pos)) (not file)) - (user-error "Please save the buffer to a file before refiling") + (user-error "Please indicate a target file in the refile path") (when (org-string-nw-p re) (setq buffer (if (markerp pos) (marker-buffer pos) (or (find-buffer-visiting file) (find-file-noselect file)))) (with-current-buffer buffer - (save-excursion - (save-restriction - (widen) - (goto-char pos) - (beginning-of-line 1) - (unless (org-looking-at-p re) - (user-error "Invalid refile position, please clear the cache with `C-0 C-c C-w' before refiling"))))))))) + (org-with-wide-buffer + (goto-char pos) + (beginning-of-line 1) + (unless (looking-at-p re) + (user-error "Invalid refile position, please clear the cache with `C-0 C-c C-w' before refiling")))))))) (defun org-refile-new-child (parent-target child) "Use refile target PARENT-TARGET to add new CHILD below it." @@ -11851,53 +11963,43 @@ this is used for the GOTO interface." level) (with-current-buffer (or (find-buffer-visiting file) (find-file-noselect file)) - (save-excursion - (save-restriction - (widen) - (if pos - (goto-char pos) - (goto-char (point-max)) - (if (not (bolp)) (newline))) - (when (looking-at org-outline-regexp) - (setq level (funcall outline-level)) - (org-end-of-subtree t t)) - (org-back-over-empty-lines) - (insert "\n" (make-string - (if pos (org-get-valid-level level 1) 1) ?*) - " " child "\n") - (beginning-of-line 0) - (list (concat (car parent-target) "/" child) file "" (point))))))) + (org-with-wide-buffer + (if pos + (goto-char pos) + (goto-char (point-max)) + (unless (bolp) (newline))) + (when (looking-at org-outline-regexp) + (setq level (funcall outline-level)) + (org-end-of-subtree t t)) + (org-back-over-empty-lines) + (insert "\n" (make-string + (if pos (org-get-valid-level level 1) 1) ?*) + " " child "\n") + (beginning-of-line 0) + (list (concat (car parent-target) "/" child) file "" (point)))))) (defun org-olpath-completing-read (prompt collection &rest args) "Read an outline path like a file name." - (let ((thetable collection) - (org-completion-use-ido nil) ; does not work with ido. - (org-completion-use-iswitchb nil)) ; or iswitchb - (apply - 'org-icompleting-read prompt - (lambda (string predicate &optional flag) - (let (rtn r f (l (length string))) - (cond - ((eq flag nil) - ;; try completion - (try-completion string thetable)) - ((eq flag t) - ;; all-completions - (setq rtn (all-completions string thetable predicate)) - (mapcar - (lambda (x) - (setq r (substring x l)) - (if (string-match " ([^)]*)$" x) - (setq f (match-string 0 x)) - (setq f "")) - (if (string-match "/" r) - (concat string (substring r 0 (match-end 0)) f) - x)) - rtn)) - ((eq flag 'lambda) - ;; exact match? - (assoc string thetable))))) - args))) + (let ((thetable collection)) + (apply #'completing-read + prompt + (lambda (string predicate &optional flag) + (cond + ((eq flag nil) (try-completion string thetable)) + ((eq flag t) + (let ((l (length string))) + (mapcar (lambda (x) + (let ((r (substring x l)) + (f (if (string-match " ([^)]*)$" x) + (match-string 0 x) + ""))) + (if (string-match "/" r) + (concat string (substring r 0 (match-end 0)) f) + x))) + (all-completions string thetable predicate)))) + ;; Exact match? + ((eq flag 'lambda) (assoc string thetable)))) + args))) ;;;; Dynamic blocks @@ -11910,19 +12012,12 @@ If not found, stay at current position and return nil." (setq pos (and (re-search-forward (concat "^[ \t]*#\\+\\(?:BEGIN\\|begin\\):[ \t]+" name "\\>") nil t) (match-beginning 0)))) - (if pos (goto-char pos)) + (when pos (goto-char pos)) pos)) -(defconst org-dblock-start-re - "^[ \t]*#\\+\\(?:BEGIN\\|begin\\):[ \t]+\\(\\S-+\\)\\([ \t]+\\(.*\\)\\)?" - "Matches the start line of a dynamic block, with parameters.") - -(defconst org-dblock-end-re "^[ \t]*#\\+\\(?:END\\|end\\)\\([: \t\r\n]\\|$\\)" - "Matches the end of a dynamic block.") - (defun org-create-dblock (plist) "Create a dynamic block section, with parameters taken from PLIST. -PLIST must contain a :name entry which is used as name of the block." +PLIST must contain a :name entry which is used as the name of the block." (when (string-match "\\S-" (buffer-substring (point-at-bol) (point-at-eol))) (end-of-line 1) (newline)) @@ -12042,13 +12137,14 @@ This function can be used in a hook." ;;;; Completion +(declare-function org-export-backend-options "ox" (cl-x) t) (defun org-get-export-keywords () "Return a list of all currently understood export keywords. Export keywords include options, block names, attributes and keywords relative to each registered export back-end." (let (keywords) (dolist (backend - (org-bound-and-true-p org-export--registered-backends) + (bound-and-true-p org-export-registered-backends) (delq nil keywords)) ;; Back-end name (for keywords, like #+LATEX:) (push (upcase (symbol-name (org-export-backend-name backend))) keywords) @@ -12064,27 +12160,25 @@ keywords relative to each registered export back-end." "TITLE:" "TODO:" "TYP_TODO:" "SELECT_TAGS:" "EXCLUDE_TAGS:")) (defcustom org-structure-template-alist - '(("s" "#+BEGIN_SRC ?\n\n#+END_SRC" "<src lang=\"?\">\n\n</src>") - ("e" "#+BEGIN_EXAMPLE\n?\n#+END_EXAMPLE" "<example>\n?\n</example>") - ("q" "#+BEGIN_QUOTE\n?\n#+END_QUOTE" "<quote>\n?\n</quote>") - ("v" "#+BEGIN_VERSE\n?\n#+END_VERSE" "<verse>\n?\n</verse>") - ("V" "#+BEGIN_VERBATIM\n?\n#+END_VERBATIM" "<verbatim>\n?\n</verbatim>") - ("c" "#+BEGIN_CENTER\n?\n#+END_CENTER" "<center>\n?\n</center>") - ("l" "#+BEGIN_LaTeX\n?\n#+END_LaTeX" - "<literal style=\"latex\">\n?\n</literal>") - ("L" "#+LaTeX: " "<literal style=\"latex\">?</literal>") - ("h" "#+BEGIN_HTML\n?\n#+END_HTML" - "<literal style=\"html\">\n?\n</literal>") - ("H" "#+HTML: " "<literal style=\"html\">?</literal>") - ("a" "#+BEGIN_ASCII\n?\n#+END_ASCII" "") - ("A" "#+ASCII: " "") - ("i" "#+INDEX: ?" "#+INDEX: ?") - ("I" "#+INCLUDE: %file ?" - "<include file=%file markup=\"?\">")) + '(("s" "#+BEGIN_SRC ?\n\n#+END_SRC") + ("e" "#+BEGIN_EXAMPLE\n?\n#+END_EXAMPLE") + ("q" "#+BEGIN_QUOTE\n?\n#+END_QUOTE") + ("v" "#+BEGIN_VERSE\n?\n#+END_VERSE") + ("V" "#+BEGIN_VERBATIM\n?\n#+END_VERBATIM") + ("c" "#+BEGIN_CENTER\n?\n#+END_CENTER") + ("C" "#+BEGIN_COMMENT\n?\n#+END_COMMENT") + ("l" "#+BEGIN_EXPORT latex\n?\n#+END_EXPORT") + ("L" "#+LaTeX: ") + ("h" "#+BEGIN_EXPORT html\n?\n#+END_EXPORT") + ("H" "#+HTML: ") + ("a" "#+BEGIN_EXPORT ascii\n?\n#+END_EXPORT") + ("A" "#+ASCII: ") + ("i" "#+INDEX: ?") + ("I" "#+INCLUDE: %file ?")) "Structure completion elements. This is a list of abbreviation keys and values. The value gets inserted if you type `<' followed by the key and then press the completion key, -usually `M-TAB'. %file will be replaced by a file name after prompting +usually `TAB'. %file will be replaced by a file name after prompting for the file using completion. The cursor will be placed at the position of the `?' in the template. There are two templates for each key, the first uses the original Org syntax, @@ -12095,8 +12189,9 @@ variable `org-mtags-prefer-muse-templates'." :type '(repeat (list (string :tag "Key") - (string :tag "Template") - (string :tag "Muse Template")))) + (string :tag "Template"))) + :version "26.1" + :package-version '(Org . "8.3")) (defun org-try-structure-completion () "Try to complete a structure template before point. @@ -12113,29 +12208,28 @@ expands them." (defun org-complete-expand-structure-template (start cell) "Expand a structure template." - (let* ((musep (org-bound-and-true-p org-mtags-prefer-muse-templates)) - (rpl (nth (if musep 2 1) cell)) - (ind "")) + (let ((rpl (nth 1 cell)) + (ind "")) (delete-region start (point)) - (when (string-match "\\`#\\+" rpl) + (when (string-match "\\`[ \t]*#\\+" rpl) (cond ((bolp)) ((not (string-match "\\S-" (buffer-substring (point-at-bol) (point)))) (setq ind (buffer-substring (point-at-bol) (point)))) (t (newline)))) (setq start (point)) - (if (string-match "%file" rpl) - (setq rpl (replace-match - (concat - "\"" - (save-match-data - (abbreviate-file-name (read-file-name "Include file: "))) - "\"") - t t rpl))) + (when (string-match "%file" rpl) + (setq rpl (replace-match + (concat + "\"" + (save-match-data + (abbreviate-file-name (read-file-name "Include file: "))) + "\"") + t t rpl))) (setq rpl (mapconcat 'identity (split-string rpl "\n") (concat "\n" ind))) (insert rpl) - (if (re-search-backward "\\?" start t) (delete-char 1)))) + (when (re-search-backward "\\?" start t) (delete-char 1)))) ;;;; TODO, DEADLINE, Comments @@ -12144,17 +12238,18 @@ expands them." (interactive) (save-excursion (org-back-to-heading) - (let (case-fold-search) - (cond - ((looking-at (format org-heading-keyword-regexp-format - org-comment-string)) - (goto-char (match-end 1)) - (looking-at (concat " +" org-comment-string)) - (replace-match "" t t) - (when (eolp) (insert " "))) - ((looking-at org-outline-regexp) - (goto-char (match-end 0)) - (insert org-comment-string " ")))))) + (let ((case-fold-search nil)) + (looking-at org-complex-heading-regexp)) + (goto-char (or (match-end 3) (match-end 2) (match-end 1))) + (skip-chars-forward " \t") + (unless (memq (char-before) '(?\s ?\t)) (insert " ")) + (if (org-in-commented-heading-p t) + (delete-region (point) + (progn (search-forward " " (line-end-position) 'move) + (skip-chars-forward " \t") + (point))) + (insert org-comment-string) + (unless (eolp) (insert " "))))) (defvar org-last-todo-state-is-todo nil "This is non-nil when the last TODO state change led to a TODO state. @@ -12193,43 +12288,65 @@ nil or a string to be used for the todo mark." ) (interactive "P") (if (eq major-mode 'org-agenda-mode) (apply 'org-agenda-todo-yesterday arg) - (let* ((hour (third (decode-time - (org-current-time)))) + (let* ((org-use-effective-time t) + (hour (nth 2 (decode-time (org-current-time)))) (org-extend-today-until (1+ hour))) (org-todo arg)))) (defvar org-block-entry-blocking "" "First entry preventing the TODO state change.") +(defun org-cancel-repeater () + "Cancel a repeater by setting its numeric value to zero." + (interactive) + (save-excursion + (org-back-to-heading t) + (let ((bound1 (point)) + (bound0 (save-excursion (outline-next-heading) (point)))) + (when (and (re-search-forward + (concat "\\(" org-scheduled-time-regexp "\\)\\|\\(" + org-deadline-time-regexp "\\)\\|\\(" + org-ts-regexp "\\)") + bound0 t) + (re-search-backward "[ \t]+\\(?:[.+]\\)?\\+\\([0-9]+\\)[hdwmy]" + bound1 t)) + (replace-match "0" t nil nil 1))))) + +(defvar org-state) +(defvar org-blocked-by-checkboxes) (defun org-todo (&optional arg) "Change the TODO state of an item. + The state of an item is given by a keyword at the start of the heading, like *** TODO Write paper *** DONE Call mom The different keywords are specified in the variable `org-todo-keywords'. -By default the available states are \"TODO\" and \"DONE\". -So for this example: when the item starts with TODO, it is changed to DONE. +By default the available states are \"TODO\" and \"DONE\". So, for this +example: when the item starts with TODO, it is changed to DONE. When it starts with DONE, the DONE is removed. And when neither TODO nor DONE are present, add TODO at the beginning of the heading. -With \\[universal-argument] prefix arg, use completion to determine the new \ +With `\\[universal-argument]' prefix ARG, use completion to determine the new \ state. -With numeric prefix arg, switch to that state. -With a double \\[universal-argument] prefix, switch to the next set of TODO \ +With numeric prefix ARG, switch to that state. +With a `\\[universal-argument] \\[universal-argument]' prefix, switch to the \ +next set of TODO \ keywords (nextset). -With a triple \\[universal-argument] prefix, circumvent any state blocking. +With a `\\[universal-argument] \\[universal-argument] \\[universal-argument]' \ +prefix, circumvent any state blocking. With a numeric prefix arg of 0, inhibit note taking for the change. - -For calling through lisp, arg is also interpreted in the following way: -`none' -> empty state -\"\" (empty string) -> switch to empty state -`done' -> switch to DONE -`nextset' -> switch to the next set of keywords -`previousset' -> switch to the previous set of keywords -\"WAITING\" -> switch to the specified keyword, but only if it - really is a member of `org-todo-keywords'." +With a numeric prefix arg of -1, cancel repeater to allow marking as DONE. + +When called through ELisp, arg is also interpreted in the following way: +`none' -> empty state +\"\" -> switch to empty state +`done' -> switch to DONE +`nextset' -> switch to the next set of keywords +`previousset' -> switch to the previous set of keywords +\"WAITING\" -> switch to the specified keyword, but only if it + really is a member of `org-todo-keywords'." (interactive "P") (if (and (org-region-active-p) org-loop-over-headlines-in-active-region) (let ((cl (if (eq org-loop-over-headlines-in-active-region 'start-level) @@ -12238,8 +12355,9 @@ For calling through lisp, arg is also interpreted in the following way: (org-map-entries `(org-todo ,arg) org-loop-over-headlines-in-active-region - cl (if (outline-invisible-p) (org-end-of-subtree nil t)))) - (if (equal arg '(16)) (setq arg 'nextset)) + cl (when (org-invisible-p) (org-end-of-subtree nil t)))) + (when (equal arg '(16)) (setq arg 'nextset)) + (when (equal arg -1) (org-cancel-repeater) (setq arg nil)) (let ((org-blocker-hook org-blocker-hook) commentp case-fold-search) @@ -12252,10 +12370,10 @@ For calling through lisp, arg is also interpreted in the following way: (save-excursion (catch 'exit (org-back-to-heading t) - (when (looking-at (concat "^\\*+ " org-comment-string)) + (when (org-in-commented-heading-p t) (org-toggle-comment) (setq commentp t)) - (if (looking-at org-outline-regexp) (goto-char (1- (match-end 0)))) + (when (looking-at org-outline-regexp) (goto-char (1- (match-end 0)))) (or (looking-at (concat " +" org-todo-regexp "\\( +\\|[ \t]*$\\)")) (looking-at "\\(?: *\\|[ \t]*$\\)")) (let* ((match-data (match-data)) @@ -12285,31 +12403,30 @@ For calling through lisp, arg is also interpreted in the following way: (and (not arg) org-use-fast-todo-selection (not (eq org-use-fast-todo-selection 'prefix))))) - ;; Use fast selection + ;; Use fast selection. (org-fast-todo-selection)) ((and (equal arg '(4)) (or (not org-use-fast-todo-selection) (not org-todo-key-trigger))) - ;; Read a state with completion - (org-icompleting-read - "State: " (mapcar 'list org-todo-keywords-1) + ;; Read a state with completion. + (completing-read + "State: " (mapcar #'list org-todo-keywords-1) nil t)) ((eq arg 'right) (if this (if tail (car tail) nil) (car org-todo-keywords-1))) ((eq arg 'left) - (if (equal member org-todo-keywords-1) - nil + (unless (equal member org-todo-keywords-1) (if this (nth (- (length org-todo-keywords-1) (length tail) 2) org-todo-keywords-1) (org-last org-todo-keywords-1)))) ((and (eq org-use-fast-todo-selection t) (equal arg '(4)) - (setq arg nil))) ; hack to fall back to cycling + (setq arg nil))) ;hack to fall back to cycling (arg - ;; user or caller requests a specific state + ;; User or caller requests a specific state. (cond ((equal arg "") nil) ((eq arg 'none) nil) @@ -12327,8 +12444,8 @@ For calling through lisp, arg is also interpreted in the following way: ((nth (1- (prefix-numeric-value arg)) org-todo-keywords-1)))) ((null member) (or head (car org-todo-keywords-1))) - ((equal this final-done-word) nil) ;; -> make empty - ((null tail) nil) ;; -> first entry + ((equal this final-done-word) nil) ;-> make empty + ((null tail) nil) ;-> first entry ((memq interpret '(type priority)) (if (eq this-command last-command) (car tail) @@ -12346,24 +12463,30 @@ For calling through lisp, arg is also interpreted in the following way: :position startpos)) dolog now-done-p) (when org-blocker-hook - (setq org-last-todo-state-is-todo - (not (member this org-done-keywords))) - (unless (save-excursion - (save-match-data - (org-with-wide-buffer - (run-hook-with-args-until-failure - 'org-blocker-hook change-plist)))) - (if (org-called-interactively-p 'interactive) - (user-error "TODO state change from %s to %s blocked (by \"%s\")" - this org-state org-block-entry-blocking) - ;; fail silently - (message "TODO state change from %s to %s blocked (by \"%s\")" - this org-state org-block-entry-blocking) - (throw 'exit nil)))) + (let (org-blocked-by-checkboxes block-reason) + (setq org-last-todo-state-is-todo + (not (member this org-done-keywords))) + (unless (save-excursion + (save-match-data + (org-with-wide-buffer + (run-hook-with-args-until-failure + 'org-blocker-hook change-plist)))) + (setq block-reason (if org-blocked-by-checkboxes + "contained checkboxes" + (format "\"%s\"" org-block-entry-blocking))) + (if (called-interactively-p 'interactive) + (user-error "TODO state change from %s to %s blocked (by %s)" + this org-state block-reason) + ;; Fail silently. + (message "TODO state change from %s to %s blocked (by %s)" + this org-state block-reason) + (throw 'exit nil))))) (store-match-data match-data) (replace-match next t t) - (unless (pos-visible-in-window-p hl-pos) - (message "TODO state changed to %s" (org-trim next))) + (cond ((equal this org-state) + (message "TODO state was already %s" (org-trim next))) + ((not (pos-visible-in-window-p hl-pos)) + (message "TODO state changed to %s" (org-trim next)))) (unless head (setq head (org-get-todo-sequence-head org-state) ass (assoc head org-todo-kwd-alist) @@ -12384,11 +12507,11 @@ For calling through lisp, arg is also interpreted in the following way: (when (and (or org-todo-log-states org-log-done) (not (eq org-inhibit-logging t)) (not (memq arg '(nextset previousset)))) - ;; we need to look at recording a time and note + ;; We need to look at recording a time and note. (setq dolog (or (nth 1 (assoc org-state org-todo-log-states)) (nth 2 (assoc this org-todo-log-states)))) - (if (and (eq dolog 'note) (eq org-inhibit-logging 'note)) - (setq dolog 'time)) + (when (and (eq dolog 'note) (eq org-inhibit-logging 'note)) + (setq dolog 'time)) (when (or (and (not org-state) (not org-closed-keep-when-no-todo)) (and org-state (member org-state org-not-done-keywords) @@ -12397,21 +12520,21 @@ For calling through lisp, arg is also interpreted in the following way: ;; If there was a CLOSED time stamp, get rid of it. (org-add-planning-info nil nil 'closed)) (when (and now-done-p org-log-done) - ;; It is now done, and it was not done before + ;; It is now done, and it was not done before. (org-add-planning-info 'closed (org-current-effective-time)) - (if (and (not dolog) (eq 'note org-log-done)) - (org-add-log-setup 'done org-state this 'findpos 'note))) + (when (and (not dolog) (eq 'note org-log-done)) + (org-add-log-setup 'done org-state this 'note))) (when (and org-state dolog) - ;; This is a non-nil state, and we need to log it - (org-add-log-setup 'state org-state this 'findpos dolog))) - ;; Fixup tag positioning + ;; This is a non-nil state, and we need to log it. + (org-add-log-setup 'state org-state this dolog))) + ;; Fixup tag positioning. (org-todo-trigger-tag-changes org-state) (and org-auto-align-tags (not org-setting-tags) (org-set-tags nil t)) (when org-provide-todo-statistics (org-update-parent-todo-statistics)) (run-hooks 'org-after-todo-state-change-hook) - (if (and arg (not (member org-state org-done-keywords))) - (setq head (org-get-todo-sequence-head org-state))) + (when (and arg (not (member org-state org-done-keywords))) + (setq head (org-get-todo-sequence-head org-state))) (put-text-property (point-at-bol) (point-at-eol) 'org-todo-head head) ;; Do we need to trigger a repeat? (when now-done-p @@ -12421,15 +12544,14 @@ For calling through lisp, arg is also interpreted in the following way: (setq org-agenda-headline-snapshot-before-repeat (org-get-heading)))) (org-auto-repeat-maybe org-state)) - ;; Fixup cursor location if close to the keyword - (if (and (outline-on-heading-p) - (not (bolp)) - (save-excursion (beginning-of-line 1) - (looking-at org-todo-line-regexp)) - (< (point) (+ 2 (or (match-end 2) (match-end 1))))) - (progn - (goto-char (or (match-end 2) (match-end 1))) - (and (looking-at " ") (just-one-space)))) + ;; Fixup cursor location if close to the keyword. + (when (and (outline-on-heading-p) + (not (bolp)) + (save-excursion (beginning-of-line 1) + (looking-at org-todo-line-regexp)) + (< (point) (+ 2 (or (match-end 2) (match-end 1))))) + (goto-char (or (match-end 2) (match-end 1))) + (and (looking-at " ") (just-one-space))) (when org-trigger-hook (save-excursion (run-hook-with-args 'org-trigger-hook change-plist))) @@ -12471,10 +12593,10 @@ changes. Such blocking occurs when: (> child-level this-level)) ;; this todo has children, check whether they are all ;; completed - (if (and (not (org-entry-is-done-p)) - (org-entry-is-todo-p)) - (progn (setq org-block-entry-blocking (org-get-heading)) - (throw 'dont-block nil))) + (when (and (not (org-entry-is-done-p)) + (org-entry-is-todo-p)) + (setq org-block-entry-blocking (org-get-heading)) + (throw 'dont-block nil)) (outline-next-heading) (setq child-level (funcall outline-level)))))) ;; Otherwise, if the task's parent has the :ORDERED: property, and @@ -12482,8 +12604,9 @@ changes. Such blocking occurs when: (save-excursion (org-back-to-heading t) (let* ((pos (point)) - (parent-pos (and (org-up-heading-safe) (point)))) - (if (not parent-pos) (throw 'dont-block t)) ; no parent + (parent-pos (and (org-up-heading-safe) (point))) + (case-fold-search nil)) + (unless parent-pos (throw 'dont-block t)) ; no parent (when (and (org-not-nil (org-entry-get (point) "ORDERED")) (forward-line 1) (re-search-forward org-not-done-heading-regexp pos t)) @@ -12492,11 +12615,11 @@ changes. Such blocking occurs when: ;; Search further up the hierarchy, to see if an ancestor is blocked (while t (goto-char parent-pos) - (if (not (looking-at org-not-done-heading-regexp)) - (throw 'dont-block t)) ; do not block, parent is not a TODO + (unless (looking-at org-not-done-heading-regexp) + (throw 'dont-block t)) ; do not block, parent is not a TODO (setq pos (point)) (setq parent-pos (and (org-up-heading-safe) (point))) - (if (not parent-pos) (throw 'dont-block t)) ; no parent + (unless parent-pos (throw 'dont-block t)) ; no parent (when (and (org-not-nil (org-entry-get (point) "ORDERED")) (forward-line 1) (re-search-forward org-not-done-heading-regexp pos t) @@ -12533,14 +12656,13 @@ See variable `org-track-ordered-property-with-tag'." (org-back-to-heading) (if (org-entry-get nil "ORDERED") (progn - (org-delete-property "ORDERED" "PROPERTIES") + (org-delete-property "ORDERED") (and tag (org-toggle-tag tag 'off)) (message "Subtasks can be completed in arbitrary order")) (org-entry-put nil "ORDERED" "t") (and tag (org-toggle-tag tag 'on)) (message "Subtasks must be completed in sequence"))))) -(defvar org-blocked-by-checkboxes) ; dynamically scoped (defun org-block-todo-from-checkboxes (change-plist) "Block turning an entry into a TODO, using checkboxes. This checks whether the current task should be blocked from state @@ -12564,32 +12686,32 @@ changes because there are unchecked boxes in this entry." (outline-next-heading) (setq end (point)) (goto-char beg) - (if (org-list-search-forward - (concat (org-item-beginning-re) - "\\(?:\\[@\\(?:start:\\)?\\([0-9]+\\|[A-Za-z]\\)\\][ \t]*\\)?" - "\\[[- ]\\]") - end t) - (progn - (if (boundp 'org-blocked-by-checkboxes) - (setq org-blocked-by-checkboxes t)) - (throw 'dont-block nil))))) + (when (org-list-search-forward + (concat (org-item-beginning-re) + "\\(?:\\[@\\(?:start:\\)?\\([0-9]+\\|[A-Za-z]\\)\\][ \t]*\\)?" + "\\[[- ]\\]") + end t) + (when (boundp 'org-blocked-by-checkboxes) + (setq org-blocked-by-checkboxes t)) + (throw 'dont-block nil)))) t))) ; do not block (defun org-entry-blocked-p () - "Is the current entry blocked?" - (org-with-silent-modifications - (if (org-entry-get nil "NOBLOCKING") - nil ;; Never block this entry - (not (run-hook-with-args-until-failure - 'org-blocker-hook - (list :type 'todo-state-change - :position (point) - :from 'todo - :to 'done)))))) + "Non-nil if entry at point is blocked." + (and (not (org-entry-get nil "NOBLOCKING")) + (member (org-entry-get nil "TODO") org-not-done-keywords) + (not (run-hook-with-args-until-failure + 'org-blocker-hook + (list :type 'todo-state-change + :position (point) + :from 'todo + :to 'done))))) (defun org-update-statistics-cookies (all) "Update the statistics cookie, either from TODO or from checkboxes. -This should be called with the cursor in a line with a statistics cookie." +This should be called with the cursor in a line with a statistics +cookie. When called with a \\[universal-argument] prefix, update +all statistics cookies in the buffer." (interactive "P") (if all (progn @@ -12605,7 +12727,7 @@ This should be called with the cursor in a line with a statistics cookie." (setq l1 (org-outline-level)) (setq end (save-excursion (outline-next-heading) - (if (org-at-heading-p) (setq l2 (org-outline-level))) + (when (org-at-heading-p) (setq l2 (org-outline-level))) (point))) (if (and (save-excursion (re-search-forward @@ -12642,7 +12764,7 @@ statistics everywhere." (box-re "\\(\\(\\[[0-9]*%\\]\\)\\|\\(\\[[0-9]*/[0-9]*\\]\\)\\)") level ltoggle l1 new ndel (cnt-all 0) (cnt-done 0) is-percent kwd - checkbox-beg ov ovs ove cookie-present) + checkbox-beg cookie-present) (catch 'exit (save-excursion (beginning-of-line 1) @@ -12677,14 +12799,31 @@ statistics everywhere." (setq kwd (and (or recursive (= l1 ltoggle)) (match-string 2))) (if (or (eq org-provide-todo-statistics 'all-headlines) + (and (eq org-provide-todo-statistics t) + (or (member kwd org-done-keywords))) (and (listp org-provide-todo-statistics) + (stringp (car org-provide-todo-statistics)) (or (member kwd org-provide-todo-statistics) - (member kwd org-done-keywords)))) + (member kwd org-done-keywords))) + (and (listp org-provide-todo-statistics) + (listp (car org-provide-todo-statistics)) + (or (member kwd (car org-provide-todo-statistics)) + (and (member kwd org-done-keywords) + (member kwd (cadr org-provide-todo-statistics)))))) (setq cnt-all (1+ cnt-all)) - (if (eq org-provide-todo-statistics t) - (and kwd (setq cnt-all (1+ cnt-all))))) - (and (member kwd org-done-keywords) - (setq cnt-done (1+ cnt-done))) + (and (eq org-provide-todo-statistics t) + kwd + (setq cnt-all (1+ cnt-all)))) + (when (or (and (member org-provide-todo-statistics '(t all-headlines)) + (member kwd org-done-keywords)) + (and (listp org-provide-todo-statistics) + (listp (car org-provide-todo-statistics)) + (member kwd org-done-keywords) + (member kwd (cadr org-provide-todo-statistics))) + (and (listp org-provide-todo-statistics) + (stringp (car org-provide-todo-statistics)) + (member kwd org-done-keywords))) + (setq cnt-done (1+ cnt-done))) (outline-next-heading))) (setq new (if is-percent @@ -12692,15 +12831,10 @@ statistics everywhere." (max 1 cnt-all))) (format "[%d/%d]" cnt-done cnt-all)) ndel (- (match-end 0) checkbox-beg)) - ;; handle overlays when updating cookie from column view - (when (setq ov (car (overlays-at checkbox-beg))) - (setq ovs (overlay-start ov) ove (overlay-end ov)) - (delete-overlay ov)) (goto-char checkbox-beg) (insert new) (delete-region (point) (+ (point) ndel)) - (when org-auto-align-tags (org-fix-tags-on-the-fly)) - (when ov (move-overlay ov ovs ove))) + (when org-auto-align-tags (org-fix-tags-on-the-fly))) (when cookie-present (run-hook-with-args 'org-after-todo-statistics-hook cnt-done (- cnt-all cnt-done)))))) @@ -12736,9 +12870,9 @@ This hook runs even if there is no statistics cookie present, in which case (when (and (stringp state) (> (length state) 0)) (setq changes (append changes (cdr (assoc state l))))) (when (member state org-not-done-keywords) - (setq changes (append changes (cdr (assoc 'todo l))))) + (setq changes (append changes (cdr (assq 'todo l))))) (when (member state org-done-keywords) - (setq changes (append changes (cdr (assoc 'done l))))) + (setq changes (append changes (cdr (assq 'done l))))) (dolist (c changes) (org-toggle-tag (car c) (if (cdr c) 'on 'off))))) @@ -12748,8 +12882,8 @@ This hook runs even if there is no statistics cookie present, in which case (setq org-log-done nil org-log-repeat nil org-todo-log-states nil) - (dolist (w (org-split-string value)) - (let* (a) + (dolist (w (split-string value)) + (let (a) (cond ((setq a (assoc w org-startup-options)) (and (member (nth 1 a) '(org-log-done org-log-repeat)) @@ -12786,7 +12920,7 @@ Returns the new TODO keyword, or nil if no state change should occur." (expert nil) (fwidth (+ maxlen 3 1 3)) (ncol (/ (- (window-width) 4) fwidth)) - tg cnt c tbl + tg cnt e c tbl groups ingroup) (save-excursion (save-window-excursion @@ -12794,13 +12928,13 @@ Returns the new TODO keyword, or nil if no state change should occur." (set-buffer (get-buffer-create " *Org todo*")) (org-switch-to-buffer-other-window (get-buffer-create " *Org todo*"))) (erase-buffer) - (org-set-local 'org-done-keywords done-keywords) + (setq-local org-done-keywords done-keywords) (setq tbl fulltable cnt 0) - (dolist (e tbl) + (while (setq e (pop tbl)) (cond ((equal e '(:startgroup)) (push '() groups) (setq ingroup t) - (when (not (= cnt 0)) + (unless (= cnt 0) (setq cnt 0) (insert "\n")) (insert "{ ")) @@ -12808,7 +12942,7 @@ Returns the new TODO keyword, or nil if no state change should occur." (setq ingroup nil cnt 0) (insert "}\n")) ((equal e '(:newline)) - (when (not (= cnt 0)) + (unless (= cnt 0) (setq cnt 0) (insert "\n") (setq e (car tbl)) @@ -12817,19 +12951,19 @@ Returns the new TODO keyword, or nil if no state change should occur." (setq tbl (cdr tbl))))) (t (setq tg (car e) c (cdr e)) - (if ingroup (push tg (car groups))) + (when ingroup (push tg (car groups))) (setq tg (org-add-props tg nil 'face (org-get-todo-face tg))) - (if (and (= cnt 0) (not ingroup)) (insert " ")) + (when (and (= cnt 0) (not ingroup)) (insert " ")) (insert "[" c "] " tg (make-string (- fwidth 4 (length tg)) ?\ )) (when (= (setq cnt (1+ cnt)) ncol) (insert "\n") - (if ingroup (insert " ")) + (when ingroup (insert " ")) (setq cnt 0))))) (insert "\n") (goto-char (point-min)) - (if (not expert) (org-fit-window-to-buffer)) + (unless expert (org-fit-window-to-buffer)) (message "[a-z..]:Set [SPC]:clear") (setq c (let ((inhibit-quit t)) (read-char-exclusive))) (cond @@ -12851,12 +12985,19 @@ Returns the new TODO keyword, or nil if no state change should occur." "Return the TODO keyword of the current subtree." (save-excursion (org-back-to-heading t) - (and (looking-at org-todo-line-regexp) + (and (let ((case-fold-search nil)) (looking-at org-todo-line-regexp)) (match-end 2) (match-string 2)))) (defun org-at-date-range-p (&optional inactive-ok) - "Is the cursor inside a date range?" + "Non-nil if point is inside a date range. + +When optional argument INACTIVE-OK is non-nil, also consider +inactive time ranges. + +When this function returns a non-nil value, match data is set +according to `org-tr-regexp-both' or `org-tr-regexp', depending +on INACTIVE-OK." (interactive) (save-excursion (catch 'exit @@ -12873,29 +13014,41 @@ Returns the new TODO keyword, or nil if no state change should occur." (throw 'exit t))) nil))) -(defun org-get-repeat (&optional tagline) - "Check if there is a deadline/schedule with repeater in this entry." +(defun org-get-repeat (&optional timestamp) + "Check if there is a time-stamp with repeater in this entry. + +Return the repeater, as a string, or nil. Also return nil when +this function is called before first heading. + +When optional argument TIMESTAMP is a string, extract the +repeater from there instead." (save-match-data - (save-excursion - (org-back-to-heading t) - (and (re-search-forward (if tagline - (concat tagline "\\s-*" org-repeat-re) - org-repeat-re) - (org-entry-end-position) t) - (match-string-no-properties 1))))) + (cond (timestamp + (and (string-match org-repeat-re timestamp) + (match-string-no-properties 1 timestamp))) + ((org-before-first-heading-p) nil) + (t + (save-excursion + (org-back-to-heading t) + (let ((end (org-entry-end-position))) + (catch :repeat + (while (re-search-forward org-repeat-re end t) + (when (save-match-data (org-at-timestamp-p 'agenda)) + (throw :repeat (match-string-no-properties 1))))))))))) (defvar org-last-changed-timestamp) (defvar org-last-inserted-timestamp) (defvar org-log-post-message) (defvar org-log-note-purpose) -(defvar org-log-note-how) +(defvar org-log-note-how nil) (defvar org-log-note-extra) (defun org-auto-repeat-maybe (done-word) - "Check if the current headline contains a repeated deadline/schedule. + "Check if the current headline contains a repeated time-stamp. + If yes, set TODO state back to what it was and change the base date of repeating deadline/scheduled time stamps to new date. + This function is run automatically after each state change to a DONE state." - ;; last-state is dynamically scoped into this function (let* ((repeat (org-get-repeat)) (aa (assoc org-last-state org-todo-kwd-alist)) (interpret (nth 1 aa)) @@ -12904,80 +13057,122 @@ This function is run automatically after each state change to a DONE state." (msg "Entry repeats: ") (org-log-done nil) (org-todo-log-states nil) - re type n what ts time to-state) - (when repeat - (if (eq org-log-repeat t) (setq org-log-repeat 'state)) - (setq to-state (or (org-entry-get nil "REPEAT_TO_STATE") - org-todo-repeat-to-state)) - (unless (and to-state (member to-state org-todo-keywords-1)) - (setq to-state (if (eq interpret 'type) org-last-state head))) - (org-todo to-state) - (when (or org-log-repeat (org-entry-get nil "CLOCK")) - (org-entry-put nil "LAST_REPEAT" (format-time-string - (org-time-stamp-format t t)))) - (when org-log-repeat - (if (or (memq 'org-add-log-note (default-value 'post-command-hook)) - (memq 'org-add-log-note post-command-hook)) - ;; OK, we are already setup for some record - (if (eq org-log-repeat 'note) - ;; make sure we take a note, not only a time stamp - (setq org-log-note-how 'note)) - ;; Set up for taking a record - (org-add-log-setup 'state (or done-word (car org-done-keywords)) - org-last-state - 'findpos org-log-repeat))) - (org-back-to-heading t) - (org-add-planning-info nil nil 'closed) - (setq re (concat "\\(" org-scheduled-time-regexp "\\)\\|\\(" - org-deadline-time-regexp "\\)\\|\\(" - org-ts-regexp "\\)")) - (while (re-search-forward - re (save-excursion (outline-next-heading) (point)) t) - (setq type (if (match-end 1) org-scheduled-string - (if (match-end 3) org-deadline-string "Plain:")) - ts (match-string (if (match-end 2) 2 (if (match-end 4) 4 0)))) - (when (string-match "\\([.+]\\)?\\(\\+[0-9]+\\)\\([hdwmy]\\)" ts) - (setq n (string-to-number (match-string 2 ts)) - what (match-string 3 ts)) - (if (equal what "w") (setq n (* n 7) what "d")) - (if (and (equal what "h") (not (string-match "[0-9]\\{1,2\\}:[0-9]\\{2\\}" ts))) - (user-error "Cannot repeat in Repeat in %d hour(s) because no hour has been set" n)) - ;; Preparation, see if we need to modify the start date for the change - (when (match-end 1) - (setq time (save-match-data (org-time-string-to-time ts))) - (cond - ((equal (match-string 1 ts) ".") - ;; Shift starting date to today - (org-timestamp-change - (- (org-today) (time-to-days time)) - 'day)) - ((equal (match-string 1 ts) "+") - (let ((nshiftmax 10) (nshift 0)) - (while (or (= nshift 0) - (<= (time-to-days time) - (time-to-days (current-time)))) - (when (= (incf nshift) nshiftmax) - (or (y-or-n-p (message "%d repeater intervals were not enough to shift date past today. Continue? " nshift)) - (error "Abort"))) - (org-timestamp-change n (cdr (assoc what whata))) - (org-at-timestamp-p t) - (setq ts (match-string 1)) - (setq time (save-match-data (org-time-string-to-time ts))))) - (org-timestamp-change (- n) (cdr (assoc what whata))) - ;; rematch, so that we have everything in place for the real shift - (org-at-timestamp-p t) - (setq ts (match-string 1)) - (string-match "\\([.+]\\)?\\(\\+[0-9]+\\)\\([hdwmy]\\)" ts)))) - (save-excursion (org-timestamp-change n (cdr (assoc what whata)) nil t)) - (setq msg (concat msg type " " org-last-changed-timestamp " ")))) - (setq org-log-post-message msg) - (message "%s" msg)))) + (end (copy-marker (org-entry-end-position)))) + (unwind-protect + (when (and repeat (not (zerop (string-to-number (substring repeat 1))))) + (when (eq org-log-repeat t) (setq org-log-repeat 'state)) + (let ((to-state (or (org-entry-get nil "REPEAT_TO_STATE" 'selective) + org-todo-repeat-to-state))) + (org-todo (cond + ((and to-state (member to-state org-todo-keywords-1)) + to-state) + ((eq interpret 'type) org-last-state) + (head) + (t 'none)))) + (org-back-to-heading t) + (org-add-planning-info nil nil 'closed) + ;; When `org-log-repeat' is non-nil or entry contains + ;; a clock, set LAST_REPEAT property. + (when (or org-log-repeat + (catch :clock + (save-excursion + (while (re-search-forward org-clock-line-re end t) + (when (org-at-clock-log-p) (throw :clock t)))))) + (org-entry-put nil "LAST_REPEAT" (format-time-string + (org-time-stamp-format t t) + (current-time)))) + (when org-log-repeat + (if (or (memq 'org-add-log-note (default-value 'post-command-hook)) + (memq 'org-add-log-note post-command-hook)) + ;; We are already setup for some record. + (when (eq org-log-repeat 'note) + ;; Make sure we take a note, not only a time stamp. + (setq org-log-note-how 'note)) + ;; Set up for taking a record. + (org-add-log-setup 'state + (or done-word (car org-done-keywords)) + org-last-state + org-log-repeat))) + (let ((planning-re (regexp-opt + (list org-scheduled-string org-deadline-string)))) + (while (re-search-forward org-ts-regexp end t) + (let* ((ts (match-string 0)) + (planning? (org-at-planning-p)) + (type (if (not planning?) "Plain:" + (save-excursion + (re-search-backward + planning-re (line-beginning-position) t) + (match-string 0))))) + (cond + ;; Ignore fake time-stamps (e.g., within comments). + ((not (org-at-timestamp-p 'agenda))) + ;; Time-stamps without a repeater are usually + ;; skipped. However, a SCHEDULED time-stamp without + ;; one is removed, as they are no longer relevant. + ((not (string-match "\\([.+]\\)?\\(\\+[0-9]+\\)\\([hdwmy]\\)" + ts)) + (when (equal type org-scheduled-string) + (org-remove-timestamp-with-keyword type))) + (t + (let ((n (string-to-number (match-string 2 ts))) + (what (match-string 3 ts))) + (when (equal what "w") (setq n (* n 7) what "d")) + (when (and (equal what "h") + (not (string-match-p "[0-9]\\{1,2\\}:[0-9]\\{2\\}" + ts))) + (user-error + "Cannot repeat in Repeat in %d hour(s) because no hour \ +has been set" + n)) + ;; Preparation, see if we need to modify the start + ;; date for the change. + (when (match-end 1) + (let ((time (save-match-data + (org-time-string-to-time ts)))) + (cond + ((equal (match-string 1 ts) ".") + ;; Shift starting date to today + (org-timestamp-change + (- (org-today) (time-to-days time)) + 'day)) + ((equal (match-string 1 ts) "+") + (let ((nshiftmax 10) + (nshift 0)) + (while (or (= nshift 0) + (not (time-less-p (current-time) time))) + (when (= (cl-incf nshift) nshiftmax) + (or (y-or-n-p + (format "%d repeater intervals were not \ +enough to shift date past today. Continue? " + nshift)) + (user-error "Abort"))) + (org-timestamp-change n (cdr (assoc what whata))) + (org-in-regexp org-ts-regexp3) + (setq ts (match-string 1)) + (setq time + (save-match-data + (org-time-string-to-time ts))))) + (org-timestamp-change (- n) (cdr (assoc what whata))) + ;; Rematch, so that we have everything in place + ;; for the real shift. + (org-in-regexp org-ts-regexp3) + (setq ts (match-string 1)) + (string-match "\\([.+]\\)?\\(\\+[0-9]+\\)\\([hdwmy]\\)" + ts))))) + (save-excursion + (org-timestamp-change n (cdr (assoc what whata)) nil t)) + (setq msg + (concat + msg type " " org-last-changed-timestamp " ")))))))) + (setq org-log-post-message msg) + (message "%s" msg)) + (set-marker end nil)))) (defun org-show-todo-tree (arg) "Make a compact tree which shows all headlines marked with TODO. The tree will show the lines where the regexp matches, and all higher headlines above the match. -With a \\[universal-argument] prefix, prompt for a regexp to match. +With a `\\[universal-argument]' prefix, prompt for a regexp to match. With a numeric prefix N, construct a sparse tree for the Nth element of `org-todo-keywords-1'." (interactive "P") @@ -12985,8 +13180,9 @@ of `org-todo-keywords-1'." (kwd-re (cond ((null arg) org-not-done-regexp) ((equal arg '(4)) - (let ((kwd (org-icompleting-read "Keyword (or KWD1|KWD2|...): " - (mapcar 'list org-todo-keywords-1)))) + (let ((kwd + (completing-read "Keyword (or KWD1|KWD2|...): " + (mapcar #'list org-todo-keywords-1)))) (concat "\\(" (mapconcat 'identity (org-split-string kwd "|") "\\|") "\\)\\>"))) @@ -12997,6 +13193,83 @@ of `org-todo-keywords-1'." (message "%d TODO entries found" (org-occur (concat "^" org-outline-regexp " *" kwd-re ))))) +(defun org--deadline-or-schedule (arg type time) + "Insert DEADLINE or SCHEDULE information in current entry. +TYPE is either `deadline' or `scheduled'. See `org-deadline' or +`org-schedule' for information about ARG and TIME arguments." + (let* ((deadline? (eq type 'deadline)) + (keyword (if deadline? org-deadline-string org-scheduled-string)) + (log (if deadline? org-log-redeadline org-log-reschedule)) + (old-date (org-entry-get nil (if deadline? "DEADLINE" "SCHEDULED"))) + (old-date-time (and old-date (org-time-string-to-time old-date))) + ;; Save repeater cookie from either TIME or current scheduled + ;; time stamp. We are going to insert it back at the end of + ;; the process. + (repeater (or (and (org-string-nw-p time) + ;; We use `org-repeat-re' because we need + ;; to tell the difference between a real + ;; repeater and a time delta, e.g. "+2d". + (string-match org-repeat-re time) + (match-string 1 time)) + (and (org-string-nw-p old-date) + (string-match "\\([.+-]+[0-9]+[hdwmy]\ +\\(?:[/ ][-+]?[0-9]+[hdwmy]\\)?\\)" + old-date) + (match-string 1 old-date))))) + (pcase arg + (`(4) + (when (and old-date log) + (org-add-log-setup (if deadline? 'deldeadline 'delschedule) + nil old-date log)) + (org-remove-timestamp-with-keyword keyword) + (message (if deadline? "Item no longer has a deadline." + "Item is no longer scheduled."))) + (`(16) + (save-excursion + (org-back-to-heading t) + (let ((regexp (if deadline? org-deadline-time-regexp + org-scheduled-time-regexp))) + (if (not (re-search-forward regexp (line-end-position 2) t)) + (user-error (if deadline? "No deadline information to update" + "No scheduled information to update")) + (let* ((rpl0 (match-string 1)) + (rpl (replace-regexp-in-string " -[0-9]+[hdwmy]" "" rpl0)) + (msg (if deadline? "Warn starting from" "Delay until"))) + (replace-match + (concat keyword + " <" rpl + (format " -%dd" + (abs (- (time-to-days + (save-match-data + (org-read-date + nil t nil msg old-date-time))) + (time-to-days old-date-time)))) + ">") t t)))))) + (_ + (org-add-planning-info type time 'closed) + (when (and old-date + log + (not (equal old-date org-last-inserted-timestamp))) + (org-add-log-setup (if deadline? 'redeadline 'reschedule) + org-last-inserted-timestamp + old-date + log)) + (when repeater + (save-excursion + (org-back-to-heading t) + (when (re-search-forward + (concat keyword " " org-last-inserted-timestamp) + (line-end-position 2) + t) + (goto-char (1- (match-end 0))) + (insert " " repeater) + (setq org-last-inserted-timestamp + (concat (substring org-last-inserted-timestamp 0 -1) + " " repeater + (substring org-last-inserted-timestamp -1)))))) + (message (if deadline? "Deadline on %s" "Scheduled to %s") + org-last-inserted-timestamp))))) + (defun org-deadline (arg &optional time) "Insert the \"DEADLINE:\" string with a timestamp to make a deadline. With one universal prefix argument, remove any deadline from the item. @@ -13005,67 +13278,14 @@ With argument TIME, set the deadline at the corresponding date. TIME can either be an Org date like \"2011-07-24\" or a delta like \"+2d\"." (interactive "P") (if (and (org-region-active-p) org-loop-over-headlines-in-active-region) - (let ((cl (if (eq org-loop-over-headlines-in-active-region 'start-level) - 'region-start-level 'region)) - org-loop-over-headlines-in-active-region) - (org-map-entries - `(org-deadline ',arg ,time) - org-loop-over-headlines-in-active-region - cl (if (outline-invisible-p) (org-end-of-subtree nil t)))) - (let* ((old-date (org-entry-get nil "DEADLINE")) - (old-date-time (if old-date (org-time-string-to-time old-date))) - (repeater (and old-date - (string-match - "\\([.+-]+[0-9]+[hdwmy]\\(?:[/ ][-+]?[0-9]+[hdwmy]\\)?\\) ?" - old-date) - (match-string 1 old-date)))) - (cond - ((equal arg '(4)) - (when (and old-date org-log-redeadline) - (org-add-log-setup 'deldeadline nil old-date 'findpos - org-log-redeadline)) - (org-remove-timestamp-with-keyword org-deadline-string) - (message "Item no longer has a deadline.")) - ((equal arg '(16)) - (save-excursion - (org-back-to-heading t) - (if (re-search-forward - org-deadline-time-regexp - (save-excursion (outline-next-heading) (point)) t) - (let* ((rpl0 (match-string 1)) - (rpl (replace-regexp-in-string " -[0-9]+[hdwmy]" "" rpl0))) - (replace-match - (concat org-deadline-string - " <" rpl - (format " -%dd" - (abs - (- (time-to-days - (save-match-data - (org-read-date nil t nil "Warn starting from" old-date-time))) - (time-to-days old-date-time)))) - ">") t t)) - (user-error "No deadline information to update")))) - (t - (org-add-planning-info 'deadline time 'closed) - (when (and old-date org-log-redeadline - (not (equal old-date - (substring org-last-inserted-timestamp 1 -1)))) - (org-add-log-setup 'redeadline nil old-date 'findpos - org-log-redeadline)) - (when repeater - (save-excursion - (org-back-to-heading t) - (when (re-search-forward (concat org-deadline-string " " - org-last-inserted-timestamp) - (save-excursion - (outline-next-heading) (point)) t) - (goto-char (1- (match-end 0))) - (insert " " repeater) - (setq org-last-inserted-timestamp - (concat (substring org-last-inserted-timestamp 0 -1) - " " repeater - (substring org-last-inserted-timestamp -1)))))) - (message "Deadline on %s" org-last-inserted-timestamp)))))) + (org-map-entries + (lambda () (org--deadline-or-schedule arg 'deadline time)) + nil + (if (eq org-loop-over-headlines-in-active-region 'start-level) + 'region-start-level + 'region) + (lambda () (when (org-invisible-p) (org-end-of-subtree nil t)))) + (org--deadline-or-schedule arg 'deadline time))) (defun org-schedule (arg &optional time) "Insert the SCHEDULED: string with a timestamp to schedule a TODO item. @@ -13075,68 +13295,14 @@ With argument TIME, scheduled at the corresponding date. TIME can either be an Org date like \"2011-07-24\" or a delta like \"+2d\"." (interactive "P") (if (and (org-region-active-p) org-loop-over-headlines-in-active-region) - (let ((cl (if (eq org-loop-over-headlines-in-active-region 'start-level) - 'region-start-level 'region)) - org-loop-over-headlines-in-active-region) - (org-map-entries - `(org-schedule ',arg ,time) - org-loop-over-headlines-in-active-region - cl (if (outline-invisible-p) (org-end-of-subtree nil t)))) - (let* ((old-date (org-entry-get nil "SCHEDULED")) - (old-date-time (if old-date (org-time-string-to-time old-date))) - (repeater (and old-date - (string-match - "\\([.+-]+[0-9]+[hdwmy]\\(?:[/ ][-+]?[0-9]+[hdwmy]\\)?\\) ?" - old-date) - (match-string 1 old-date)))) - (cond - ((equal arg '(4)) - (progn - (when (and old-date org-log-reschedule) - (org-add-log-setup 'delschedule nil old-date 'findpos - org-log-reschedule)) - (org-remove-timestamp-with-keyword org-scheduled-string) - (message "Item is no longer scheduled."))) - ((equal arg '(16)) - (save-excursion - (org-back-to-heading t) - (if (re-search-forward - org-scheduled-time-regexp - (save-excursion (outline-next-heading) (point)) t) - (let* ((rpl0 (match-string 1)) - (rpl (replace-regexp-in-string " -[0-9]+[hdwmy]" "" rpl0))) - (replace-match - (concat org-scheduled-string - " <" rpl - (format " -%dd" - (abs - (- (time-to-days - (save-match-data - (org-read-date nil t nil "Delay until" old-date-time))) - (time-to-days old-date-time)))) - ">") t t)) - (user-error "No scheduled information to update")))) - (t - (org-add-planning-info 'scheduled time 'closed) - (when (and old-date org-log-reschedule - (not (equal old-date - (substring org-last-inserted-timestamp 1 -1)))) - (org-add-log-setup 'reschedule nil old-date 'findpos - org-log-reschedule)) - (when repeater - (save-excursion - (org-back-to-heading t) - (when (re-search-forward (concat org-scheduled-string " " - org-last-inserted-timestamp) - (save-excursion - (outline-next-heading) (point)) t) - (goto-char (1- (match-end 0))) - (insert " " repeater) - (setq org-last-inserted-timestamp - (concat (substring org-last-inserted-timestamp 0 -1) - " " repeater - (substring org-last-inserted-timestamp -1)))))) - (message "Scheduled to %s" org-last-inserted-timestamp)))))) + (org-map-entries + (lambda () (org--deadline-or-schedule arg 'scheduled time)) + nil + (if (eq org-loop-over-headlines-in-active-region 'start-level) + 'region-start-level + 'region) + (lambda () (when (org-invisible-p) (org-end-of-subtree nil t)))) + (org--deadline-or-schedule arg 'scheduled time))) (defun org-get-scheduled-time (pom &optional inherit) "Get the scheduled time as a time tuple, of a format suitable @@ -13167,24 +13333,36 @@ nil." (if (and (string-match "\\S-" (buffer-substring (point-at-bol) (point))) (equal (char-before) ?\ )) (backward-delete-char 1) - (if (string-match "^[ \t]*$" (buffer-substring - (point-at-bol) (point-at-eol))) - (delete-region (point-at-bol) - (min (point-max) (1+ (point-at-eol)))))))))) + (when (string-match "^[ \t]*$" (buffer-substring + (point-at-bol) (point-at-eol))) + (delete-region (point-at-bol) + (min (point-max) (1+ (point-at-eol)))))))))) (defvar org-time-was-given) ; dynamically scoped parameter (defvar org-end-time-was-given) ; dynamically scoped parameter -(defun org-add-planning-info (what &optional time &rest remove) - "Insert new timestamp with keyword in the line directly after the headline. -WHAT indicates what kind of time stamp to add. TIME indicates the time to use. -If non is given, the user is prompted for a date. -REMOVE indicates what kind of entries to remove. An old WHAT entry will also -be removed." - (interactive) - (let (org-time-was-given org-end-time-was-given ts - end default-time default-input) +(defun org-at-planning-p () + "Non-nil when point is on a planning info line." + ;; This is as accurate and faster than `org-element-at-point' since + ;; planning info location is fixed in the section. + (org-with-wide-buffer + (beginning-of-line) + (and (looking-at-p org-planning-line-re) + (eq (point) + (ignore-errors + (if (and (featurep 'org-inlinetask) (org-inlinetask-in-task-p)) + (org-back-to-heading t) + (org-with-limited-levels (org-back-to-heading t))) + (line-beginning-position 2)))))) +(defun org-add-planning-info (what &optional time &rest remove) + "Insert new timestamp with keyword in the planning line. +WHAT indicates what kind of time stamp to add. It is a symbol +among `closed', `deadline', `scheduled' and nil. TIME indicates +the time to use. If none is given, the user is prompted for +a date. REMOVE indicates what kind of entries to remove. An old +WHAT entry will also be removed." + (let (org-time-was-given org-end-time-was-given default-time default-input) (catch 'exit (when (and (memq what '(scheduled deadline)) (or (not time) @@ -13193,108 +13371,98 @@ be removed." ;; Try to get a default date/time from existing timestamp (save-excursion (org-back-to-heading t) - (setq end (save-excursion (outline-next-heading) (point))) - (when (re-search-forward (if (eq what 'scheduled) - org-scheduled-time-regexp - org-deadline-time-regexp) - end t) - (setq ts (match-string 1) - default-time - (apply 'encode-time (org-parse-time-string ts)) - default-input (and ts (org-get-compact-tod ts)))))) + (let ((end (save-excursion (outline-next-heading) (point))) ts) + (when (re-search-forward (if (eq what 'scheduled) + org-scheduled-time-regexp + org-deadline-time-regexp) + end t) + (setq ts (match-string 1) + default-time (apply 'encode-time (org-parse-time-string ts)) + default-input (and ts (org-get-compact-tod ts))))))) (when what (setq time (if (stringp time) - ;; This is a string (relative or absolute), set proper date - (apply 'encode-time + ;; This is a string (relative or absolute), set + ;; proper date. + (apply #'encode-time (org-read-date-analyze time default-time (decode-time default-time))) ;; If necessary, get the time from the user (or time (org-read-date nil 'to-time nil nil default-time default-input))))) - (when (and org-insert-labeled-timestamps-at-point - (member what '(scheduled deadline))) - (insert - (if (eq what 'scheduled) org-scheduled-string org-deadline-string) " ") - (org-insert-time-stamp time org-time-was-given - nil nil nil (list org-end-time-was-given)) - (setq what nil)) - (save-excursion - (save-restriction - (let (col list elt ts buffer-invisibility-spec) - (org-back-to-heading t) - (looking-at (concat org-outline-regexp "\\( *\\)[^\r\n]*")) - (goto-char (match-end 1)) - (setq col (current-column)) - (goto-char (match-end 0)) - (if (eobp) (insert "\n") (forward-char 1)) - (when (and (not what) - (not (looking-at - (concat "[ \t]*" - org-keyword-time-not-clock-regexp)))) - ;; Nothing to add, nothing to remove...... :-) - (throw 'exit nil)) - (if (and (not (looking-at org-outline-regexp)) - (looking-at (concat "[^\r\n]*?" org-keyword-time-regexp - "[^\r\n]*")) - (not (equal (match-string 1) org-clock-string))) - (narrow-to-region (match-beginning 0) (match-end 0)) - (insert-before-markers "\n") - (backward-char 1) - (narrow-to-region (point) (point)) - (and org-adapt-indentation (org-indent-to-column col))) - ;; Check if we have to remove something. - (setq list (cons what remove)) - (while list - (setq elt (pop list)) - (when (or (and (eq elt 'scheduled) - (re-search-forward org-scheduled-time-regexp nil t)) - (and (eq elt 'deadline) - (re-search-forward org-deadline-time-regexp nil t)) - (and (eq elt 'closed) - (re-search-forward org-closed-time-regexp nil t))) - (replace-match "") - (if (looking-at "--+<[^>]+>") (replace-match "")))) - (and (looking-at "[ \t]+") (replace-match "")) - (and org-adapt-indentation (bolp) (org-indent-to-column col)) - (when what - (insert - (if (not (or (bolp) (eq (char-before) ?\ ))) " " "") - (cond ((eq what 'scheduled) org-scheduled-string) - ((eq what 'deadline) org-deadline-string) - ((eq what 'closed) org-closed-string)) - " ") - (setq ts (org-insert-time-stamp - time - (or org-time-was-given - (and (eq what 'closed) org-log-done-with-time)) - (eq what 'closed) - nil nil (list org-end-time-was-given))) - (insert - (if (not (or (bolp) (eq (char-before) ?\ ) - (memq (char-after) '(32 10)) - (eobp))) " " "")) - (end-of-line 1)) - (goto-char (point-min)) - (widen) - (if (and (looking-at "[ \t]*\n") - (equal (char-before) ?\n)) - (delete-region (1- (point)) (point-at-eol))) - ts)))))) - -(defvar org-log-note-marker (make-marker)) + (org-with-wide-buffer + (org-back-to-heading t) + (forward-line) + (unless (bolp) (insert "\n")) + (cond ((looking-at-p org-planning-line-re) + ;; Move to current indentation. + (skip-chars-forward " \t") + ;; Check if we have to remove something. + (dolist (type (if what (cons what remove) remove)) + (save-excursion + (when (re-search-forward + (cl-case type + (closed org-closed-time-regexp) + (deadline org-deadline-time-regexp) + (scheduled org-scheduled-time-regexp) + (otherwise + (error "Invalid planning type: %s" type))) + (line-end-position) t) + ;; Delete until next keyword or end of line. + (delete-region + (match-beginning 0) + (if (re-search-forward org-keyword-time-not-clock-regexp + (line-end-position) + t) + (match-beginning 0) + (line-end-position)))))) + ;; If there is nothing more to add and no more keyword + ;; is left, remove the line completely. + (if (and (looking-at-p "[ \t]*$") (not what)) + (delete-region (line-beginning-position) + (line-beginning-position 2)) + ;; If we removed last keyword, do not leave trailing + ;; white space at the end of line. + (let ((p (point))) + (save-excursion + (end-of-line) + (unless (= (skip-chars-backward " \t" p) 0) + (delete-region (point) (line-end-position))))))) + ((not what) (throw 'exit nil)) ; Nothing to do. + (t (insert-before-markers "\n") + (backward-char 1) + (when org-adapt-indentation + (indent-to-column (1+ (org-outline-level)))))) + (when what + ;; Insert planning keyword. + (insert (cl-case what + (closed org-closed-string) + (deadline org-deadline-string) + (scheduled org-scheduled-string) + (otherwise (error "Invalid planning type: %s" what))) + " ") + ;; Insert associated timestamp. + (let ((ts (org-insert-time-stamp + time + (or org-time-was-given + (and (eq what 'closed) org-log-done-with-time)) + (eq what 'closed) + nil nil (list org-end-time-was-given)))) + (unless (eolp) (insert " ")) + ts)))))) + +(defvar org-log-note-marker (make-marker) + "Marker pointing at the entry where the note is to be inserted.") (defvar org-log-note-purpose nil) (defvar org-log-note-state nil) (defvar org-log-note-previous-state nil) -(defvar org-log-note-how nil) (defvar org-log-note-extra nil) (defvar org-log-note-window-configuration nil) (defvar org-log-note-return-to (make-marker)) (defvar org-log-note-effective-time nil "Remembered current time so that dynamically scoped -`org-extend-today-until' affects tha timestamps in state change -log") +`org-extend-today-until' affects timestamps in state change log") (defvar org-log-post-message nil "Message to be displayed after a log note has been stored. @@ -13304,90 +13472,97 @@ The auto-repeater uses this.") "Add a note to the current entry. This is done in the same way as adding a state change note." (interactive) - (org-add-log-setup 'note nil nil 'findpos nil)) + (org-add-log-setup 'note)) -(defvar org-property-end-re) -(defun org-add-log-setup (&optional purpose state prev-state - findpos how extra) +(defun org-log-beginning (&optional create) + "Return expected start of log notes in current entry. +When optional argument CREATE is non-nil, the function creates +a drawer to store notes, if necessary. Returned position ignores +narrowing." + (org-with-wide-buffer + (let ((drawer (org-log-into-drawer))) + (cond + (drawer + (org-end-of-meta-data) + (let ((regexp (concat "^[ \t]*:" (regexp-quote drawer) ":[ \t]*$")) + (end (if (org-at-heading-p) (point) + (save-excursion (outline-next-heading) (point)))) + (case-fold-search t)) + (catch 'exit + ;; Try to find existing drawer. + (while (re-search-forward regexp end t) + (let ((element (org-element-at-point))) + (when (eq (org-element-type element) 'drawer) + (let ((cend (org-element-property :contents-end element))) + (when (and (not org-log-states-order-reversed) cend) + (goto-char cend))) + (throw 'exit nil)))) + ;; No drawer found. Create one, if permitted. + (when create + (unless (bolp) (insert "\n")) + (let ((beg (point))) + (insert ":" drawer ":\n:END:\n") + (org-indent-region beg (point))) + (end-of-line -1))))) + (t + (org-end-of-meta-data org-log-state-notes-insert-after-drawers) + (skip-chars-forward " \t\n") + (beginning-of-line) + (unless org-log-states-order-reversed + (org-skip-over-state-notes) + (skip-chars-backward " \t\n") + (forward-line))))) + (if (bolp) (point) (line-beginning-position 2)))) + +(defun org-add-log-setup (&optional purpose state prev-state how extra) "Set up the post command hook to take a note. If this is about to TODO state change, the new state is expected in STATE. -When FINDPOS is non-nil, find the correct position for the note in -the current entry. If not, assume that it can be inserted at point. HOW is an indicator what kind of note should be created. EXTRA is additional text that will be inserted into the notes buffer." - (let* ((org-log-into-drawer (org-log-into-drawer)) - (drawer (cond ((stringp org-log-into-drawer) - org-log-into-drawer) - (org-log-into-drawer "LOGBOOK")))) - (save-restriction - (save-excursion - (when findpos - (org-back-to-heading t) - (narrow-to-region (point) (save-excursion - (outline-next-heading) (point))) - (looking-at (concat org-outline-regexp "\\( *\\)[^\r\n]*" - "\\(\n[^\r\n]*?" org-keyword-time-not-clock-regexp - "[^\r\n]*\\)?")) - (goto-char (match-end 0)) - (cond - (drawer - (if (re-search-forward (concat "^[ \t]*:" drawer ":[ \t]*$") - nil t) - (progn - (goto-char (match-end 0)) - (or org-log-states-order-reversed - (and (re-search-forward org-property-end-re nil t) - (goto-char (1- (match-beginning 0)))))) - (insert "\n:" drawer ":\n:END:") - (beginning-of-line 0) - (org-indent-line) - (beginning-of-line 2) - (org-indent-line) - (end-of-line 0))) - ((and org-log-state-notes-insert-after-drawers - (save-excursion - (forward-line) (looking-at org-drawer-regexp))) - (forward-line) - (while (looking-at org-drawer-regexp) - (goto-char (match-end 0)) - (re-search-forward org-property-end-re (point-max) t) - (forward-line)) - (forward-line -1))) - (unless org-log-states-order-reversed - (and (= (char-after) ?\n) (forward-char 1)) - (org-skip-over-state-notes) - (skip-chars-backward " \t\n\r"))) - (move-marker org-log-note-marker (point)) - (setq org-log-note-purpose purpose - org-log-note-state state - org-log-note-previous-state prev-state - org-log-note-how how - org-log-note-extra extra - org-log-note-effective-time (org-current-effective-time)) - (add-hook 'post-command-hook 'org-add-log-note 'append))))) + (move-marker org-log-note-marker (point)) + (setq org-log-note-purpose purpose + org-log-note-state state + org-log-note-previous-state prev-state + org-log-note-how how + org-log-note-extra extra + org-log-note-effective-time (org-current-effective-time)) + (add-hook 'post-command-hook 'org-add-log-note 'append)) (defun org-skip-over-state-notes () "Skip past the list of State notes in an entry." - (if (looking-at "\n[ \t]*- State") (forward-char 1)) (when (ignore-errors (goto-char (org-in-item-p))) (let* ((struct (org-list-struct)) - (prevs (org-list-prevs-alist struct))) - (while (looking-at "[ \t]*- State") + (prevs (org-list-prevs-alist struct)) + (regexp + (concat "[ \t]*- +" + (replace-regexp-in-string + " +" " +" + (org-replace-escapes + (regexp-quote (cdr (assq 'state org-log-note-headings))) + `(("%d" . ,org-ts-regexp-inactive) + ("%D" . ,org-ts-regexp) + ("%s" . "\"\\S-+\"") + ("%S" . "\"\\S-+\"") + ("%t" . ,org-ts-regexp-inactive) + ("%T" . ,org-ts-regexp) + ("%u" . ".*?") + ("%U" . ".*?"))))))) + (while (looking-at-p regexp) (goto-char (or (org-list-get-next-item (point) struct prevs) (org-list-get-item-end (point) struct))))))) -(defun org-add-log-note (&optional purpose) - "Pop up a window for taking a note, and add this note later at point." +(defun org-add-log-note (&optional _purpose) + "Pop up a window for taking a note, and add this note later." (remove-hook 'post-command-hook 'org-add-log-note) (setq org-log-note-window-configuration (current-window-configuration)) (delete-other-windows) (move-marker org-log-note-return-to (point)) - (org-pop-to-buffer-same-window (marker-buffer org-log-note-marker)) + (pop-to-buffer-same-window (marker-buffer org-log-note-marker)) (goto-char org-log-note-marker) (org-switch-to-buffer-other-window "*Org Note*") (erase-buffer) (if (memq org-log-note-how '(time state)) - (let (current-prefix-arg) (org-store-log-note)) + (org-store-log-note) (let ((org-inhibit-startup t)) (org-mode)) (insert (format "# Insert note for %s. # Finish with C-c C-c, or cancel with C-c C-k.\n\n" @@ -13411,23 +13586,23 @@ EXTRA is additional text that will be inserted into the notes buffer." ((eq org-log-note-purpose 'note) "this entry") (t (error "This should not happen"))))) - (if org-log-note-extra (insert org-log-note-extra)) - (org-set-local 'org-finish-function 'org-store-log-note) + (when org-log-note-extra (insert org-log-note-extra)) + (setq-local org-finish-function 'org-store-log-note) (run-hooks 'org-log-buffer-setup-hook))) (defvar org-note-abort nil) ; dynamically scoped (defun org-store-log-note () "Finish taking a log note, and insert it to where it belongs." - (let ((txt (buffer-string))) - (kill-buffer (current-buffer)) - (let ((note (cdr (assq org-log-note-purpose org-log-note-headings))) - lines ind bul) + (let ((txt (prog1 (buffer-string) + (kill-buffer))) + (note (cdr (assq org-log-note-purpose org-log-note-headings))) + lines) (while (string-match "\\`# .*\n[ \t\n]*" txt) (setq txt (replace-match "" t t txt))) - (if (string-match "\\s-+\\'" txt) - (setq txt (replace-match "" t t txt))) - (setq lines (org-split-string txt "\n")) - (when (and note (string-match "\\S-" note)) + (when (string-match "\\s-+\\'" txt) + (setq txt (replace-match "" t t txt))) + (setq lines (and (not (equal "" txt)) (org-split-string txt "\n"))) + (when (org-string-nw-p note) (setq note (org-replace-escapes note @@ -13445,74 +13620,83 @@ EXTRA is additional text that will be inserted into the notes buffer." (cons "%D" (format-time-string (org-time-stamp-format nil nil) org-log-note-effective-time)) - (cons "%s" (if org-log-note-state - (concat "\"" org-log-note-state "\"") - "")) - (cons "%S" (if org-log-note-previous-state - (concat "\"" org-log-note-previous-state "\"") - "\"\""))))) - (if lines (setq note (concat note " \\\\"))) + (cons "%s" (cond + ((not org-log-note-state) "") + ((string-match-p org-ts-regexp + org-log-note-state) + (format "\"[%s]\"" + (substring org-log-note-state 1 -1))) + (t (format "\"%s\"" org-log-note-state)))) + (cons "%S" + (cond + ((not org-log-note-previous-state) "") + ((string-match-p org-ts-regexp + org-log-note-previous-state) + (format "\"[%s]\"" + (substring + org-log-note-previous-state 1 -1))) + (t (format "\"%s\"" + org-log-note-previous-state))))))) + (when lines (setq note (concat note " \\\\"))) (push note lines)) - (when (or current-prefix-arg org-note-abort) - (when org-log-into-drawer - (org-remove-empty-drawer-at - (if (stringp org-log-into-drawer) org-log-into-drawer "LOGBOOK") - org-log-note-marker)) - (setq lines nil)) - (when lines + (when (and lines (not org-note-abort)) (with-current-buffer (marker-buffer org-log-note-marker) - (save-excursion - (goto-char org-log-note-marker) - (move-marker org-log-note-marker nil) - (end-of-line 1) - (if (not (bolp)) (let ((inhibit-read-only t)) (insert "\n"))) - (setq ind (save-excursion - (if (ignore-errors (goto-char (org-in-item-p))) - (let ((struct (org-list-struct))) - (org-list-get-ind - (org-list-get-top-point struct) struct)) - (skip-chars-backward " \r\t\n") - (cond - ((and (org-at-heading-p) - org-adapt-indentation) - (1+ (org-current-level))) - ((org-at-heading-p) 0) - (t (org-get-indentation)))))) - (setq bul (org-list-bullet-string "-")) - (org-indent-line-to ind) - (insert bul (pop lines)) - (let ((ind-body (+ (length bul) ind))) - (while lines - (insert "\n") - (org-indent-line-to ind-body) - (insert (pop lines)))) - (message "Note stored") - (org-back-to-heading t) - (org-cycle-hide-drawers 'children)) + (org-with-wide-buffer + ;; Find location for the new note. + (goto-char org-log-note-marker) + (set-marker org-log-note-marker nil) + ;; Note associated to a clock is to be located right after + ;; the clock. Do not move point. + (unless (eq org-log-note-purpose 'clock-out) + (goto-char (org-log-beginning t))) + ;; Make sure point is at the beginning of an empty line. + (cond ((not (bolp)) (let ((inhibit-read-only t)) (insert "\n"))) + ((looking-at "[ \t]*\\S-") (save-excursion (insert "\n")))) + ;; In an existing list, add a new item at the top level. + ;; Otherwise, indent line like a regular one. + (let ((itemp (org-in-item-p))) + (if itemp + (indent-line-to + (let ((struct (save-excursion + (goto-char itemp) (org-list-struct)))) + (org-list-get-ind (org-list-get-top-point struct) struct))) + (org-indent-line))) + (insert (org-list-bullet-string "-") (pop lines)) + (let ((ind (org-list-item-body-column (line-beginning-position)))) + (dolist (line lines) + (insert "\n") + (indent-line-to ind) + (insert line))) + (message "Note stored") + (org-back-to-heading t) + (org-cycle-hide-drawers 'children)) ;; Fix `buffer-undo-list' when `org-store-log-note' is called ;; from within `org-add-log-note' because `buffer-undo-list' ;; is then modified outside of `org-with-remote-undo'. (when (eq this-command 'org-agenda-todo) - (setcdr buffer-undo-list (cddr buffer-undo-list))))))) - ;; Don't add undo information when called from `org-agenda-todo' + (setcdr buffer-undo-list (cddr buffer-undo-list)))))) + ;; Don't add undo information when called from `org-agenda-todo'. (let ((buffer-undo-list (eq this-command 'org-agenda-todo))) (set-window-configuration org-log-note-window-configuration) (with-current-buffer (marker-buffer org-log-note-return-to) (goto-char org-log-note-return-to)) (move-marker org-log-note-return-to nil) - (and org-log-post-message (message "%s" org-log-post-message)))) + (when org-log-post-message (message "%s" org-log-post-message)))) -(defun org-remove-empty-drawer-at (drawer pos) - "Remove an empty drawer DRAWER at position POS. +(defun org-remove-empty-drawer-at (pos) + "Remove an empty drawer at position POS. POS may also be a marker." (with-current-buffer (if (markerp pos) (marker-buffer pos) (current-buffer)) - (save-excursion - (save-restriction - (widen) - (goto-char pos) - (if (org-in-regexp - (concat "^[ \t]*:" drawer ":[ \t]*\n[ \t]*:END:[ \t]*\n?") 2) - (replace-match "")))))) + (org-with-wide-buffer + (goto-char pos) + (let ((drawer (org-element-at-point))) + (when (and (memq (org-element-type drawer) '(drawer property-drawer)) + (not (org-element-property :contents-begin drawer))) + (delete-region (org-element-property :begin drawer) + (progn (goto-char (org-element-property :end drawer)) + (skip-chars-backward " \r\t\n") + (forward-line) + (point)))))))) (defvar org-ts-type nil) (defun org-sparse-tree (&optional arg type) @@ -13533,47 +13717,45 @@ D Show deadlines and scheduled items between a date range." (interactive "P") (setq type (or type org-sparse-tree-default-date-type)) (setq org-ts-type type) - (message "Sparse tree: [/]regexp [t]odo [T]odo-kwd [m]atch [p]roperty - [d]eadlines [b]efore-date [a]fter-date [D]ates range - [c]ycle through date types: %s" - (case type + (message "Sparse tree: [r]egexp [t]odo [T]odo-kwd [m]atch [p]roperty + \[d]eadlines [b]efore-date [a]fter-date [D]ates range + \[c]ycle through date types: %s" + (cl-case type (all "all timestamps") (scheduled "only scheduled") (deadline "only deadline") (active "only active timestamps") (inactive "only inactive timestamps") - (scheduled-or-deadline "scheduled/deadline") (closed "with a closed time-stamp") (otherwise "scheduled/deadline"))) (let ((answer (read-char-exclusive))) - (case answer + (cl-case answer (?c (org-sparse-tree arg - (cadr (memq type '(scheduled-or-deadline all scheduled deadline active - inactive closed))))) - (?d (call-interactively #'org-check-deadlines)) - (?b (call-interactively #'org-check-before-date)) - (?a (call-interactively #'org-check-after-date)) - (?D (call-interactively #'org-check-dates-range)) - (?t (call-interactively #'org-show-todo-tree)) + (cadr + (memq type '(nil all scheduled deadline active inactive closed))))) + (?d (call-interactively 'org-check-deadlines)) + (?b (call-interactively 'org-check-before-date)) + (?a (call-interactively 'org-check-after-date)) + (?D (call-interactively 'org-check-dates-range)) + (?t (call-interactively 'org-show-todo-tree)) (?T (org-show-todo-tree '(4))) - (?m (call-interactively #'org-match-sparse-tree)) + (?m (call-interactively 'org-match-sparse-tree)) ((?p ?P) - (let* ((kwd (org-icompleting-read + (let* ((kwd (completing-read "Property: " (mapcar #'list (org-buffer-property-keys)))) - (value (org-icompleting-read + (value (completing-read "Value: " (mapcar #'list (org-property-values kwd))))) (unless (string-match "\\`{.*}\\'" value) (setq value (concat "\"" value "\""))) (org-match-sparse-tree arg (concat kwd "=" value)))) - ((?r ?R ?/) (call-interactively #'org-occur)) + ((?r ?R ?/) (call-interactively 'org-occur)) (otherwise (user-error "No such sparse tree command \"%c\"" answer))))) -(defvar org-occur-highlights nil +(defvar-local org-occur-highlights nil "List of overlays used for occur matches.") -(make-variable-buffer-local 'org-occur-highlights) -(defvar org-occur-parameters nil +(defvar-local org-occur-parameters nil "Parameters of the active org-occur calls. This is a list, each call to org-occur pushes as cons cell, containing the regular expression and the callback, onto the list. @@ -13583,18 +13765,21 @@ will only contain one set of parameters. When the highlights are removed (for example with `C-c C-c', or with the next edit (depending on `org-remove-highlights-with-change'), this variable is emptied as well.") -(make-variable-buffer-local 'org-occur-parameters) (defun org-occur (regexp &optional keep-previous callback) "Make a compact tree which shows all matches of REGEXP. -The tree will show the lines where the regexp matches, and all higher -headlines above the match. It will also show the heading after the match, -to make sure editing the matching entry is easy. -If KEEP-PREVIOUS is non-nil, highlighting and exposing done by a previous -call to `org-occur' will be kept, to allow stacking of calls to this -command. -If CALLBACK is non-nil, it is a function which is called to confirm -that the match should indeed be shown." + +The tree will show the lines where the regexp matches, and any other context +defined in `org-show-context-detail', which see. + +When optional argument KEEP-PREVIOUS is non-nil, highlighting and exposing +done by a previous call to `org-occur' will be kept, to allow stacking of +calls to this command. + +Optional argument CALLBACK can be a function of no argument. In this case, +it is called with point at the end of the match, match data being set +accordingly. Current match is shown only if the return value is non-nil. +The function must neither move point nor alter narrowing." (interactive "sRegexp: \nP") (when (equal regexp "") (user-error "Regexp cannot be empty")) @@ -13604,32 +13789,35 @@ that the match should indeed be shown." (let ((cnt 0)) (save-excursion (goto-char (point-min)) - (if (or (not keep-previous) ; do not want to keep - (not org-occur-highlights)) ; no previous matches - ;; hide everything - (org-overview)) - (while (re-search-forward regexp nil t) - (when (or (not callback) - (save-match-data (funcall callback))) - (setq cnt (1+ cnt)) - (when org-highlight-sparse-tree-matches - (org-highlight-new-match (match-beginning 0) (match-end 0))) - (org-show-context 'occur-tree)))) + (when (or (not keep-previous) ; do not want to keep + (not org-occur-highlights)) ; no previous matches + ;; hide everything + (org-overview)) + (let ((case-fold-search (if (eq org-occur-case-fold-search 'smart) + (isearch-no-upper-case-p regexp t) + org-occur-case-fold-search))) + (while (re-search-forward regexp nil t) + (when (or (not callback) + (save-match-data (funcall callback))) + (setq cnt (1+ cnt)) + (when org-highlight-sparse-tree-matches + (org-highlight-new-match (match-beginning 0) (match-end 0))) + (org-show-context 'occur-tree))))) (when org-remove-highlights-with-change - (org-add-hook 'before-change-functions 'org-remove-occur-highlights - nil 'local)) + (add-hook 'before-change-functions 'org-remove-occur-highlights + nil 'local)) (unless org-sparse-tree-open-archived-trees (org-hide-archived-subtrees (point-min) (point-max))) (run-hooks 'org-occur-hook) - (if (org-called-interactively-p 'interactive) - (message "%d match(es) for regexp %s" cnt regexp)) + (when (called-interactively-p 'interactive) + (message "%d match(es) for regexp %s" cnt regexp)) cnt)) -(defun org-occur-next-match (&optional n reset) +(defun org-occur-next-match (&optional n _reset) "Function for `next-error-function' to find sparse tree matches. N is the number of matches to move, when negative move backwards. -RESET is entirely ignored - this function always goes back to the -starting point when no match is found." +This function always goes back to the starting point when no +match is found." (let* ((limit (if (< n 0) (point-min) (point-max))) (search-func (if (< n 0) 'previous-single-char-property-change @@ -13641,7 +13829,7 @@ starting point when no match is found." (while (setq p1 (funcall search-func (point) 'org-type)) (when (equal p1 limit) (goto-char pos) - (error "No more matches")) + (user-error "No more matches")) (when (equal (get-char-property p1 'org-type) 'org-occur) (setq n (1- n)) (when (= n 0) @@ -13649,65 +13837,75 @@ starting point when no match is found." (throw 'exit (point)))) (goto-char p1)) (goto-char p1) - (error "No more matches")))) + (user-error "No more matches")))) (defun org-show-context (&optional key) "Make sure point and context are visible. -How much context is shown depends upon the variables -`org-show-hierarchy-above', `org-show-following-heading', -`org-show-entry-below' and `org-show-siblings'." - (let ((heading-p (org-at-heading-p t)) - (hierarchy-p (org-get-alist-option org-show-hierarchy-above key)) - (following-p (org-get-alist-option org-show-following-heading key)) - (entry-p (org-get-alist-option org-show-entry-below key)) - (siblings-p (org-get-alist-option org-show-siblings key))) - ;; Show heading or entry text - (if (and heading-p (not entry-p)) - (org-flag-heading nil) ; only show the heading - (and (or entry-p (outline-invisible-p) (org-invisible-p2)) - (org-show-hidden-entry))) ; show entire entry - (when following-p - ;; Show next sibling, or heading below text - (save-excursion - (and (if heading-p (org-goto-sibling) (outline-next-heading)) - (org-flag-heading nil)))) - (when siblings-p (org-show-siblings)) - (when hierarchy-p - ;; show all higher headings, possibly with siblings - (save-excursion - (while (and (condition-case nil - (progn (org-up-heading-all 1) t) - (error nil)) - (not (bobp))) - (org-flag-heading nil) - (when siblings-p (org-show-siblings))))))) +Optional argument KEY, when non-nil, is a symbol. See +`org-show-context-detail' for allowed values and how much is to +be shown." + (org-show-set-visibility + (cond ((symbolp org-show-context-detail) org-show-context-detail) + ((cdr (assq key org-show-context-detail))) + (t (cdr (assq 'default org-show-context-detail)))))) + +(defun org-show-set-visibility (detail) + "Set visibility around point according to DETAIL. +DETAIL is either nil, `minimal', `local', `ancestors', `lineage', +`tree', `canonical' or t. See `org-show-context-detail' for more +information." + ;; Show current heading and possibly its entry, following headline + ;; or all children. + (if (and (org-at-heading-p) (not (eq detail 'local))) + (org-flag-heading nil) + (org-show-entry) + ;; If point is hidden within a drawer or a block, make sure to + ;; expose it. + (dolist (o (overlays-at (point))) + (when (memq (overlay-get o 'invisible) '(org-hide-block outline)) + (delete-overlay o))) + (unless (org-before-first-heading-p) + (org-with-limited-levels + (cl-case detail + ((tree canonical t) (org-show-children)) + ((nil minimal ancestors)) + (t (save-excursion + (outline-next-heading) + (org-flag-heading nil))))))) + ;; Show all siblings. + (when (eq detail 'lineage) (org-show-siblings)) + ;; Show ancestors, possibly with their children. + (when (memq detail '(ancestors lineage tree canonical t)) + (save-excursion + (while (org-up-heading-safe) + (org-flag-heading nil) + (when (memq detail '(canonical t)) (org-show-entry)) + (when (memq detail '(tree canonical t)) (org-show-children)))))) (defvar org-reveal-start-hook nil "Hook run before revealing a location.") (defun org-reveal (&optional siblings) "Show current entry, hierarchy above it, and the following headline. -This can be used to show a consistent set of context around locations -exposed with `org-show-hierarchy-above' or `org-show-following-heading' -not t for the search context. + +This can be used to show a consistent set of context around +locations exposed with `org-show-context'. With optional argument SIBLINGS, on each level of the hierarchy all siblings are shown. This repairs the tree structure to what it would look like when opened with hierarchical calls to `org-cycle'. -With double optional argument \\[universal-argument] \\[universal-argument], \ -go to the parent and show the -entire tree." + +With a \\[universal-argument] \\[universal-argument] prefix, \ +go to the parent and show the entire tree." (interactive "P") (run-hooks 'org-reveal-start-hook) - (let ((org-show-hierarchy-above t) - (org-show-following-heading t) - (org-show-siblings (if siblings t org-show-siblings))) - (org-show-context nil)) - (when (equal siblings '(16)) - (save-excursion - (when (org-up-heading-safe) - (org-show-subtree) - (run-hook-with-args 'org-cycle-hook 'subtree))))) + (cond ((equal siblings '(4)) (org-show-set-visibility 'canonical)) + ((equal siblings '(16)) + (save-excursion + (when (org-up-heading-safe) + (org-show-subtree) + (run-hook-with-args 'org-cycle-hook 'subtree)))) + (t (org-show-set-visibility 'lineage)))) (defun org-highlight-new-match (beg end) "Highlight from BEG to END and mark the highlight is an occur headline." @@ -13716,13 +13914,13 @@ entire tree." (overlay-put ov 'org-type 'org-occur) (push ov org-occur-highlights))) -(defun org-remove-occur-highlights (&optional beg end noremove) +(defun org-remove-occur-highlights (&optional _beg _end noremove) "Remove the occur highlights from the buffer. BEG and END are ignored. If NOREMOVE is nil, remove this function from the `before-change-functions' in the current buffer." (interactive) (unless org-inhibit-highlight-removal - (mapc 'delete-overlay org-occur-highlights) + (mapc #'delete-overlay org-occur-highlights) (setq org-occur-highlights nil) (setq org-occur-parameters nil) (unless noremove @@ -13746,89 +13944,88 @@ from the `before-change-functions' in the current buffer." (interactive) (org-priority 'down)) -(defun org-priority (&optional action show) +(defun org-priority (&optional action _show) "Change the priority of an item. ACTION can be `set', `up', `down', or a character." (interactive "P") (if (equal action '(4)) (org-show-priority) - (unless org-enable-priority-commands - (user-error "Priority commands are disabled")) - (setq action (or action 'set)) - (let (current new news have remove) - (save-excursion - (org-back-to-heading t) - (if (looking-at org-priority-regexp) + (unless org-enable-priority-commands + (user-error "Priority commands are disabled")) + (setq action (or action 'set)) + (let (current new news have remove) + (save-excursion + (org-back-to-heading t) + (when (looking-at org-priority-regexp) (setq current (string-to-char (match-string 2)) have t)) - (cond - ((eq action 'remove) - (setq remove t new ?\ )) - ((or (eq action 'set) - (if (featurep 'xemacs) (characterp action) (integerp action))) - (if (not (eq action 'set)) - (setq new action) - (message "Priority %c-%c, SPC to remove: " - org-highest-priority org-lowest-priority) - (save-match-data - (setq new (read-char-exclusive)))) - (if (and (= (upcase org-highest-priority) org-highest-priority) - (= (upcase org-lowest-priority) org-lowest-priority)) + (cond + ((eq action 'remove) + (setq remove t new ?\ )) + ((or (eq action 'set) + (integerp action)) + (if (not (eq action 'set)) + (setq new action) + (message "Priority %c-%c, SPC to remove: " + org-highest-priority org-lowest-priority) + (save-match-data + (setq new (read-char-exclusive)))) + (when (and (= (upcase org-highest-priority) org-highest-priority) + (= (upcase org-lowest-priority) org-lowest-priority)) (setq new (upcase new))) - (cond ((equal new ?\ ) (setq remove t)) - ((or (< (upcase new) org-highest-priority) (> (upcase new) org-lowest-priority)) - (user-error "Priority must be between `%c' and `%c'" - org-highest-priority org-lowest-priority)))) - ((eq action 'up) - (setq new (if have - (1- current) ; normal cycling - ;; last priority was empty - (if (eq last-command this-command) - org-lowest-priority ; wrap around empty to lowest - ;; default - (if org-priority-start-cycle-with-default - org-default-priority - (1- org-default-priority)))))) - ((eq action 'down) - (setq new (if have - (1+ current) ; normal cycling - ;; last priority was empty - (if (eq last-command this-command) - org-highest-priority ; wrap around empty to highest - ;; default - (if org-priority-start-cycle-with-default - org-default-priority - (1+ org-default-priority)))))) - (t (user-error "Invalid action"))) - (if (or (< (upcase new) org-highest-priority) - (> (upcase new) org-lowest-priority)) + (cond ((equal new ?\ ) (setq remove t)) + ((or (< (upcase new) org-highest-priority) (> (upcase new) org-lowest-priority)) + (user-error "Priority must be between `%c' and `%c'" + org-highest-priority org-lowest-priority)))) + ((eq action 'up) + (setq new (if have + (1- current) ; normal cycling + ;; last priority was empty + (if (eq last-command this-command) + org-lowest-priority ; wrap around empty to lowest + ;; default + (if org-priority-start-cycle-with-default + org-default-priority + (1- org-default-priority)))))) + ((eq action 'down) + (setq new (if have + (1+ current) ; normal cycling + ;; last priority was empty + (if (eq last-command this-command) + org-highest-priority ; wrap around empty to highest + ;; default + (if org-priority-start-cycle-with-default + org-default-priority + (1+ org-default-priority)))))) + (t (user-error "Invalid action"))) + (when (or (< (upcase new) org-highest-priority) + (> (upcase new) org-lowest-priority)) (if (and (memq action '(up down)) (not have) (not (eq last-command this-command))) - ;; `new' is from default priority + ;; `new' is from default priority (error "The default can not be set, see `org-default-priority' why") - ;; normal cycling: `new' is beyond highest/lowest priority - ;; and is wrapped around to the empty priority + ;; normal cycling: `new' is beyond highest/lowest priority + ;; and is wrapped around to the empty priority (setq remove t))) - (setq news (format "%c" new)) - (if have + (setq news (format "%c" new)) + (if have + (if remove + (replace-match "" t t nil 1) + (replace-match news t t nil 2)) (if remove - (replace-match "" t t nil 1) - (replace-match news t t nil 2)) - (if remove - (user-error "No priority cookie found in line") - (let ((case-fold-search nil)) - (looking-at org-todo-line-regexp)) - (if (match-end 2) - (progn - (goto-char (match-end 2)) - (insert " [#" news "]")) - (goto-char (match-beginning 3)) - (insert "[#" news "] ")))) - (org-preserve-lc (org-set-tags nil 'align))) - (if remove - (message "Priority removed") - (message "Priority of current item set to %s" news))))) + (user-error "No priority cookie found in line") + (let ((case-fold-search nil)) (looking-at org-todo-line-regexp)) + (if (match-end 2) + (progn + (goto-char (match-end 2)) + (insert " [#" news "]")) + (goto-char (match-beginning 3)) + (insert "[#" news "] ")))) + (org-set-tags nil 'align)) + (if remove + (message "Priority removed") + (message "Priority of current item set to %s" news))))) (defun org-show-priority () "Show the priority of the current item. @@ -13863,6 +14060,7 @@ Can be set by the action argument to `org-scan-tags' and `org-map-entries'.") (defvar org-scanner-tags nil "The current tag list while the tags scanner is running.") + (defvar org-trust-scanner-tags nil "Should `org-get-tags-at' use the tags for the scanner. This is for internal dynamical scoping only. @@ -13874,6 +14072,8 @@ obtain a list of properties. Building the tags list for each entry in such a file becomes an N^2 operation - but with this variable set, it scales as N.") +(defvar org--matcher-tags-todo-only nil) + (defun org-scan-tags (action matcher todo-only &optional start-level) "Scan headline tags with inheritance and produce output ACTION. @@ -13882,11 +14082,14 @@ or `agenda' to produce an entry list for an agenda view. It can also be a Lisp form or a function that should be called at each matched headline, in this case the return value is a list of all return values from these calls. -MATCHER is a Lisp form to be evaluated, testing if a given set of tags -qualifies a headline for inclusion. When TODO-ONLY is non-nil, -only lines with a not-done TODO keyword are included in the output. -This should be the same variable that was scoped into -and set by `org-make-tags-matcher' when it constructed MATCHER. +MATCHER is a function accepting three arguments, returning +a non-nil value whenever a given set of tags qualifies a headline +for inclusion. See `org-make-tags-matcher' for more information. +As a special case, it can also be set to t (respectively nil) in +order to match all (respectively none) headline. + +When TODO-ONLY is non-nil, only lines with a TODO keyword are +included in the output. START-LEVEL can be a string with asterisks, reducing the scope to headlines matching this string." @@ -13897,8 +14100,8 @@ headlines matching this string." (concat "\\*\\{" (number-to-string start-level) "\\} ") org-outline-regexp) " *\\(\\<\\(" - (mapconcat 'regexp-quote org-todo-keywords-1 "\\|") - (org-re "\\)\\>\\)? *\\(.*?\\)\\(:[[:alnum:]_@#%:]+:\\)?[ \t]*$"))) + (mapconcat #'regexp-quote org-todo-keywords-1 "\\|") + "\\)\\>\\)? *\\(.*?\\)\\(:[[:alnum:]_@#%:]+:\\)?[ \t]*$")) (props (list 'face 'default 'done-face 'org-agenda-done 'undone-face 'default @@ -13915,8 +14118,9 @@ headlines matching this string." lspos tags tags-list (tags-alist (list (cons 0 org-file-tags))) (llast 0) rtn rtn1 level category i txt - todo marker entry priority) - (when (not (or (member action '(agenda sparse-tree)) (functionp action))) + todo marker entry priority + ts-date ts-date-type ts-date-pair) + (unless (or (member action '(agenda sparse-tree)) (functionp action)) (setq action (list 'lambda nil action))) (save-excursion (goto-char (point-min)) @@ -13927,11 +14131,17 @@ headlines matching this string." (re-search-forward re nil t)) (setq org-map-continue-from nil) (catch :skip - (setq todo (if (match-end 1) (org-match-string-no-properties 2)) - tags (if (match-end 4) (org-match-string-no-properties 4))) + (setq todo + ;; TODO: is the 1-2 difference a bug? + (when (match-end 1) (match-string-no-properties 2)) + tags (when (match-end 4) (match-string-no-properties 4))) (goto-char (setq lspos (match-beginning 0))) (setq level (org-reduced-level (org-outline-level)) category (org-get-category)) + (when (eq action 'agenda) + (setq ts-date-pair (org-agenda-entry-get-agenda-timestamp (point)) + ts-date (car ts-date-pair) + ts-date-type (cdr ts-date-pair))) (setq i llast llast level) ;; remove tag lists from same and sublevels (while (>= i level) @@ -13958,25 +14168,27 @@ headlines matching this string." (when (and tags org-use-tag-inheritance (or (not (eq t org-use-tag-inheritance)) org-tags-exclude-from-inheritance)) - ;; selective inheritance, remove uninherited ones + ;; Selective inheritance, remove uninherited ones. (setcdr (car tags-alist) (org-remove-uninherited-tags (cdar tags-alist)))) (when (and ;; eval matcher only when the todo condition is OK - (and (or (not todo-only) (member todo org-not-done-keywords)) - (let ((case-fold-search t) (org-trust-scanner-tags t)) - (eval matcher))) - - ;; Call the skipper, but return t if it does not skip, - ;; so that the `and' form continues evaluating + (and (or (not todo-only) (member todo org-todo-keywords-1)) + (if (functionp matcher) + (let ((case-fold-search t) (org-trust-scanner-tags t)) + (funcall matcher todo tags-list level)) + matcher)) + + ;; Call the skipper, but return t if it does not + ;; skip, so that the `and' form continues evaluating. (progn (unless (eq action 'sparse-tree) (org-agenda-skip)) t) ;; Check if timestamps are deselecting this entry (or (not todo-only) - (and (member todo org-not-done-keywords) + (and (member todo org-todo-keywords-1) (or (not org-agenda-tags-todo-honor-ignore-options) (not (org-agenda-check-for-timestamp-as-reason-to-ignore-todo-item)))))) @@ -13995,7 +14207,8 @@ headlines matching this string." (if (eq org-tags-match-list-sublevels 'indented) (make-string (1- level) ?.) "") (org-get-heading)) - level category + (make-string level ?\s) + category tags-list) priority (org-get-priority txt)) (goto-char lspos) @@ -14003,7 +14216,9 @@ headlines matching this string." (org-add-props txt props 'org-marker marker 'org-hd-marker marker 'org-category category 'todo-state todo - 'priority priority 'type "tagsmatch") + 'ts-date ts-date + 'priority priority + 'type (concat "tagsmatch" ts-date-type)) (push txt rtn)) ((functionp action) (setq org-map-continue-from nil) @@ -14048,13 +14263,19 @@ headlines matching this string." (defun org-match-sparse-tree (&optional todo-only match) "Create a sparse tree according to tags string MATCH. -MATCH can contain positive and negative selection of tags, like -\"+WORK+URGENT-WITHBOSS\". -If optional argument TODO-ONLY is non-nil, only select lines that are -also TODO lines." + +MATCH is a string with match syntax. It can contain a selection +of tags (\"+work+urgent-boss\"), properties (\"LEVEL>3\"), and +TODO keywords (\"TODO=\\\"WAITING\\\"\") or a combination of +those. See the manual for details. + +If optional argument TODO-ONLY is non-nil, only select lines that +are also TODO tasks." (interactive "P") (org-agenda-prepare-buffers (list (current-buffer))) - (org-scan-tags 'sparse-tree (cdr (org-make-tags-matcher match)) todo-only)) + (let ((org--matcher-tags-todo-only todo-only)) + (org-scan-tags 'sparse-tree (cdr (org-make-tags-matcher match)) + org--matcher-tags-todo-only))) (defalias 'org-tags-sparse-tree 'org-match-sparse-tree) @@ -14062,15 +14283,17 @@ also TODO lines." (defun org-cached-entry-get (pom property) (if (or (eq t org-use-property-inheritance) (and (stringp org-use-property-inheritance) - (string-match org-use-property-inheritance property)) + (let ((case-fold-search t)) + (string-match-p org-use-property-inheritance property))) (and (listp org-use-property-inheritance) - (member property org-use-property-inheritance))) - ;; Caching is not possible, check it directly + (member-ignore-case property org-use-property-inheritance))) + ;; Caching is not possible, check it directly. (org-entry-get pom property 'inherit) - ;; Get all properties, so that we can do complicated checks easily - (cdr (assoc property (or org-cached-props - (setq org-cached-props - (org-entry-properties pom))))))) + ;; Get all properties, so we can do complicated checks easily. + (cdr (assoc-string property + (or org-cached-props + (setq org-cached-props (org-entry-properties pom))) + t)))) (defun org-global-tags-completion-table (&optional files) "Return the list of all tags in all agenda buffer/files. @@ -14079,186 +14302,173 @@ instead of the agenda files." (save-excursion (org-uniquify (delq nil - (apply 'append + (apply #'append (mapcar (lambda (file) (set-buffer (find-file-noselect file)) - (append (org-get-buffer-tags) - (mapcar (lambda (x) (if (stringp (car-safe x)) - (list (car-safe x)) nil)) - org-tag-alist))) - (if (and files (car files)) - files + (mapcar (lambda (x) + (and (stringp (car-safe x)) + (list (car-safe x)))) + (or org-current-tag-alist (org-get-buffer-tags)))) + (if (car-safe files) files (org-agenda-files)))))))) (defun org-make-tags-matcher (match) "Create the TAGS/TODO matcher form for the selection string MATCH. -The variable `todo-only' is scoped dynamically into this function. -It will be set to t if the matcher restricts matching to TODO entries, -otherwise will not be touched. - -Returns a cons of the selection string MATCH and the constructed -lisp form implementing the matcher. The matcher is to be evaluated -at an Org entry, with point on the headline, and returns t if the -entry matches the selection string MATCH. The returned lisp form -references two variables with information about the entry, which -must be bound around the form's evaluation: todo, the TODO keyword -at the entry (or nil of none); and tags-list, the list of all tags -at the entry including inherited ones. Additionally, the category -of the entry (if any) must be specified as the text property -'org-category on the headline. - -See also `org-scan-tags'. -" - (declare (special todo-only)) - (unless (boundp 'todo-only) - (error "`org-make-tags-matcher' expects todo-only to be scoped in")) +Returns a cons of the selection string MATCH and a function +implementing the matcher. + +The matcher is to be called at an Org entry, with point on the +headline, and returns non-nil if the entry matches the selection +string MATCH. It must be called with three arguments: the TODO +keyword at the entry (or nil if none), the list of all tags at +the entry including inherited ones and the reduced level of the +headline. Additionally, the category of the entry, if any, must +be specified as the text property `org-category' on the headline. + +This function sets the variable `org--matcher-tags-todo-only' to +a non-nil value if the matcher restricts matching to TODO +entries, otherwise it is not touched. + +See also `org-scan-tags'." (unless match ;; Get a new match request, with completion against the global - ;; tags table and the local tags in current buffer + ;; tags table and the local tags in current buffer. (let ((org-last-tags-completion-table (org-uniquify (delq nil (append (org-get-buffer-tags) (org-global-tags-completion-table)))))) - (setq match (org-completing-read-no-i - "Match: " 'org-tags-completion-function nil nil nil - 'org-tags-history)))) + (setq match + (completing-read + "Match: " + 'org-tags-completion-function nil nil nil 'org-tags-history)))) - ;; Parse the string and create a lisp form (let ((match0 match) - (re (org-re "^&?\\([-+:]\\)?\\({[^}]+}\\|LEVEL\\([<=>]\\{1,2\\}\\)\\([0-9]+\\)\\|\\(\\(?:[[:alnum:]_]+\\(?:\\\\-\\)*\\)+\\)\\([<>=]\\{1,2\\}\\)\\({[^}]+}\\|\"[^\"]*\"\\|-?[.0-9]+\\(?:[eE][-+]?[0-9]+\\)?\\)\\|[[:alnum:]_@#%]+\\)")) - minus tag mm - tagsmatch todomatch tagsmatcher todomatcher kwd matcher - orterms orlist re-p str-p level-p level-op time-p - prop-p pn pv po gv rest (start 0) (ss 0)) - ;; Expand group tags + (re "^&?\\([-+:]\\)?\\({[^}]+}\\|LEVEL\\([<=>]\\{1,2\\}\\)\\([0-9]+\\)\\|\\(\\(?:[[:alnum:]_]+\\(?:\\\\-\\)*\\)+\\)\\([<>=]\\{1,2\\}\\)\\({[^}]+}\\|\"[^\"]*\"\\|-?[.0-9]+\\(?:[eE][-+]?[0-9]+\\)?\\)\\|[[:alnum:]_@#%]+\\)") + (start 0) + tagsmatch todomatch tagsmatcher todomatcher) + + ;; Expand group tags. (setq match (org-tags-expand match)) ;; Check if there is a TODO part of this match, which would be the - ;; part after a "/". TO make sure that this slash is not part of - ;; a property value to be matched against, we also check that there - ;; is no " after that slash. - ;; First, find the last slash - (while (string-match "/+" match ss) - (setq start (match-beginning 0) ss (match-end 0))) + ;; part after a "/". To make sure that this slash is not part of + ;; a property value to be matched against, we also check that + ;; there is no / after that slash. First, find the last slash. + (let ((s 0)) + (while (string-match "/+" match s) + (setq start (match-beginning 0)) + (setq s (match-end 0)))) (if (and (string-match "/+" match start) - (not (save-match-data (string-match "\"" match start)))) - ;; match contains also a todo-matching request + (not (string-match-p "\"" match start))) + ;; Match contains also a TODO-matching request. (progn - (setq tagsmatch (substring match 0 (match-beginning 0)) - todomatch (substring match (match-end 0))) - (if (string-match "^!" todomatch) - (setq todo-only t todomatch (substring todomatch 1))) - (if (string-match "^\\s-*$" todomatch) - (setq todomatch nil))) - ;; only matching tags - (setq tagsmatch match todomatch nil)) - - ;; Make the tags matcher - (if (or (not tagsmatch) (not (string-match "\\S-" tagsmatch))) - (setq tagsmatcher t) - (setq orterms (org-split-string tagsmatch "|") orlist nil) - (dolist (term orterms) - (while (and (equal (substring term -1) "\\") orterms) - (setq term (concat term "|" (pop orterms)))) ; repair bad split - (while (string-match re term) - (setq rest (substring term (match-end 0)) - minus (and (match-end 1) - (equal (match-string 1 term) "-")) - tag (save-match-data (replace-regexp-in-string - "\\\\-" "-" - (match-string 2 term))) - re-p (equal (string-to-char tag) ?{) - level-p (match-end 4) - prop-p (match-end 5) - mm (cond - (re-p `(org-match-any-p ,(substring tag 1 -1) tags-list)) - (level-p - (setq level-op (org-op-to-function (match-string 3 term))) - `(,level-op level ,(string-to-number - (match-string 4 term)))) - (prop-p - (setq pn (match-string 5 term) - po (match-string 6 term) - pv (match-string 7 term) - re-p (equal (string-to-char pv) ?{) - str-p (equal (string-to-char pv) ?\") - time-p (save-match-data - (string-match "^\"[[<].*[]>]\"$" pv)) - pv (if (or re-p str-p) (substring pv 1 -1) pv)) - (if time-p (setq pv (org-matcher-time pv))) - (setq po (org-op-to-function po (if time-p 'time str-p))) - (cond - ((equal pn "CATEGORY") - (setq gv '(get-text-property (point) 'org-category))) - ((equal pn "TODO") - (setq gv 'todo)) - (t - (setq gv `(org-cached-entry-get nil ,pn)))) - (if re-p - (if (eq po 'org<>) - `(not (string-match ,pv (or ,gv ""))) - `(string-match ,pv (or ,gv ""))) - (if str-p - `(,po (or ,gv "") ,pv) - `(,po (string-to-number (or ,gv "")) - ,(string-to-number pv) )))) - (t `(member ,tag tags-list))) - mm (if minus (list 'not mm) mm) - term rest) - (push mm tagsmatcher)) - (push (if (> (length tagsmatcher) 1) - (cons 'and tagsmatcher) - (car tagsmatcher)) - orlist) - (setq tagsmatcher nil)) - (setq tagsmatcher (if (> (length orlist) 1) (cons 'or orlist) (car orlist))) - (setq tagsmatcher - (list 'progn '(setq org-cached-props nil) tagsmatcher))) - ;; Make the todo matcher - (if (or (not todomatch) (not (string-match "\\S-" todomatch))) - (setq todomatcher t) - (setq orterms (org-split-string todomatch "|") orlist nil) - (dolist (term orterms) - (while (string-match re term) - (setq minus (and (match-end 1) - (equal (match-string 1 term) "-")) - kwd (match-string 2 term) - re-p (equal (string-to-char kwd) ?{) - term (substring term (match-end 0)) - mm (if re-p - `(string-match ,(substring kwd 1 -1) todo) - (list 'equal 'todo kwd)) - mm (if minus (list 'not mm) mm)) - (push mm todomatcher)) - (push (if (> (length todomatcher) 1) - (cons 'and todomatcher) - (car todomatcher)) - orlist) - (setq todomatcher nil)) - (setq todomatcher (if (> (length orlist) 1) - (cons 'or orlist) (car orlist)))) - - ;; Return the string and lisp forms of the matcher - (setq matcher (if todomatcher - (list 'and tagsmatcher todomatcher) - tagsmatcher)) - (when todo-only - (setq matcher (list 'and '(member todo org-not-done-keywords) - matcher))) - (cons match0 matcher))) - -(defun org-tags-expand (match &optional single-as-list downcased) + (setq tagsmatch (substring match 0 (match-beginning 0))) + (setq todomatch (substring match (match-end 0))) + (when (string-prefix-p "!" todomatch) + (setq org--matcher-tags-todo-only t) + (setq todomatch (substring todomatch 1))) + (when (string-match "\\`\\s-*\\'" todomatch) + (setq todomatch nil))) + ;; Only matching tags. + (setq tagsmatch match) + (setq todomatch nil)) + + ;; Make the tags matcher. + (when (org-string-nw-p tagsmatch) + (let ((orlist nil) + (orterms (org-split-string tagsmatch "|")) + term) + (while (setq term (pop orterms)) + (while (and (equal (substring term -1) "\\") orterms) + (setq term (concat term "|" (pop orterms)))) ;repair bad split. + (while (string-match re term) + (let* ((rest (substring term (match-end 0))) + (minus (and (match-end 1) + (equal (match-string 1 term) "-"))) + (tag (save-match-data + (replace-regexp-in-string + "\\\\-" "-" (match-string 2 term)))) + (regexp (eq (string-to-char tag) ?{)) + (levelp (match-end 4)) + (propp (match-end 5)) + (mm + (cond + (regexp `(org-match-any-p ,(substring tag 1 -1) tags-list)) + (levelp + `(,(org-op-to-function (match-string 3 term)) + level + ,(string-to-number (match-string 4 term)))) + (propp + (let* ((gv (pcase (upcase (match-string 5 term)) + ("CATEGORY" + '(get-text-property (point) 'org-category)) + ("TODO" 'todo) + (p `(org-cached-entry-get nil ,p)))) + (pv (match-string 7 term)) + (regexp (eq (string-to-char pv) ?{)) + (strp (eq (string-to-char pv) ?\")) + (timep (string-match-p "^\"[[<].*[]>]\"$" pv)) + (po (org-op-to-function (match-string 6 term) + (if timep 'time strp)))) + (setq pv (if (or regexp strp) (substring pv 1 -1) pv)) + (when timep (setq pv (org-matcher-time pv))) + (cond ((and regexp (eq po 'org<>)) + `(not (string-match ,pv (or ,gv "")))) + (regexp `(string-match ,pv (or ,gv ""))) + (strp `(,po (or ,gv "") ,pv)) + (t + `(,po + (string-to-number (or ,gv "")) + ,(string-to-number pv)))))) + (t `(member ,tag tags-list))))) + (push (if minus `(not ,mm) mm) tagsmatcher) + (setq term rest))) + (push `(and ,@tagsmatcher) orlist) + (setq tagsmatcher nil)) + (setq tagsmatcher `(progn (setq org-cached-props nil) (or ,@orlist))))) + + ;; Make the TODO matcher. + (when (org-string-nw-p todomatch) + (let ((orlist nil)) + (dolist (term (org-split-string todomatch "|")) + (while (string-match re term) + (let* ((minus (and (match-end 1) + (equal (match-string 1 term) "-"))) + (kwd (match-string 2 term)) + (regexp (eq (string-to-char kwd) ?{)) + (mm (if regexp `(string-match ,(substring kwd 1 -1) todo) + `(equal todo ,kwd)))) + (push (if minus `(not ,mm) mm) todomatcher)) + (setq term (substring term (match-end 0)))) + (push (if (> (length todomatcher) 1) + (cons 'and todomatcher) + (car todomatcher)) + orlist) + (setq todomatcher nil)) + (setq todomatcher (cons 'or orlist)))) + + ;; Return the string and function of the matcher. If no + ;; tags-specific or todo-specific matcher exists, match + ;; everything. + (let ((matcher (if (and tagsmatcher todomatcher) + `(and ,tagsmatcher ,todomatcher) + (or tagsmatcher todomatcher t)))) + (when org--matcher-tags-todo-only + (setq matcher `(and (member todo org-not-done-keywords) ,matcher))) + (cons match0 `(lambda (todo tags-list level) ,matcher))))) + +(defun org-tags-expand (match &optional single-as-list downcased tags-already-expanded) "Expand group tags in MATCH. This replaces every group tag in MATCH with a regexp tag search. For example, a group tag \"Work\" defined as { Work : Lab Conf } will be replaced like this: - Work => {\\(?:Work\\|Lab\\|Conf\\)} - +Work => +{\\(?:Work\\|Lab\\|Conf\\)} - -Work => -{\\(?:Work\\|Lab\\|Conf\\)} + Work => {\\<\\(?:Work\\|Lab\\|Conf\\)\\>} + +Work => +{\\<\\(?:Work\\|Lab\\|Conf\\)\\>} + -Work => -{\\<\\(?:Work\\|Lab\\|Conf\\)\\>} Replacing by a regexp preserves the structure of the match. E.g., this expansion @@ -14268,6 +14478,12 @@ E.g., this expansion will match anything tagged with \"Lab\" and \"Home\", or tagged with \"Conf\" and \"Home\" or tagged with \"Work\" and \"home\". +A group tag in MATCH can contain regular expressions of its own. +For example, a group tag \"Proj\" defined as { Proj : {P@.+} } +will be replaced like this: + + Proj => {\\<\\(?:Proj\\)\\>\\|P@.+} + When the optional argument SINGLE-AS-LIST is non-nil, MATCH is assumed to be a single group tag, and the function will return the list of tags in this group. @@ -14276,34 +14492,113 @@ When DOWNCASE is non-nil, expand downcased TAGS." (if org-group-tags (let* ((case-fold-search t) (stable org-mode-syntax-table) - (tal (or org-tag-groups-alist-for-agenda - org-tag-groups-alist)) - (tal (if downcased - (mapcar (lambda(tg) (mapcar 'downcase tg)) tal) tal)) - (tml (mapcar 'car tal)) - (rtnmatch match) rpl) - ;; @ and _ are allowed as word-components in tags + (taggroups (or org-tag-groups-alist-for-agenda org-tag-groups-alist)) + (taggroups (if downcased + (mapcar (lambda (tg) (mapcar #'downcase tg)) + taggroups) + taggroups)) + (taggroups-keys (mapcar #'car taggroups)) + (return-match (if downcased (downcase match) match)) + (count 0) + (work-already-expanded tags-already-expanded) + regexps-in-match tags-in-group regexp-in-group regexp-in-group-escaped) + ;; @ and _ are allowed as word-components in tags. (modify-syntax-entry ?@ "w" stable) (modify-syntax-entry ?_ "w" stable) - (while (and tml + ;; Temporarily replace regexp-expressions in the match-expression. + (while (string-match "{.+?}" return-match) + (cl-incf count) + (push (match-string 0 return-match) regexps-in-match) + (setq return-match (replace-match (format "<%d>" count) t nil return-match))) + (while (and taggroups-keys (with-syntax-table stable (string-match (concat "\\(?1:[+-]?\\)\\(?2:\\<" - (regexp-opt tml) "\\>\\)") - rtnmatch))) - (let* ((dir (match-string 1 rtnmatch)) - (tag (match-string 2 rtnmatch)) + (regexp-opt taggroups-keys) "\\>\\)") + return-match))) + (let* ((dir (match-string 1 return-match)) + (tag (match-string 2 return-match)) (tag (if downcased (downcase tag) tag))) - (setq tml (delete tag tml)) - (when (not (get-text-property 0 'grouptag (match-string 2 rtnmatch))) - (setq rpl (append (org-uniquify rpl) (assoc tag tal))) - (setq rpl (concat dir "{\\<" (regexp-opt rpl) "\\>}")) - (if (stringp rpl) (org-add-props rpl '(grouptag t))) - (setq rtnmatch (replace-match rpl t t rtnmatch))))) + (unless (or (get-text-property 0 'grouptag (match-string 2 return-match)) + (member tag work-already-expanded)) + (setq tags-in-group (assoc tag taggroups)) + (push tag work-already-expanded) + ;; Recursively expand each tag in the group, if the tag hasn't + ;; already been expanded. Restore the match-data after all recursive calls. + (save-match-data + (let (tags-expanded) + (dolist (x (cdr tags-in-group)) + (if (and (member x taggroups-keys) + (not (member x work-already-expanded))) + (setq tags-expanded + (delete-dups + (append + (org-tags-expand x t downcased + work-already-expanded) + tags-expanded))) + (setq tags-expanded + (append (list x) tags-expanded))) + (setq work-already-expanded + (delete-dups + (append tags-expanded + work-already-expanded)))) + (setq tags-in-group + (delete-dups (cons (car tags-in-group) + tags-expanded))))) + ;; Filter tag-regexps from tags. + (setq regexp-in-group-escaped + (delq nil (mapcar (lambda (x) + (if (stringp x) + (and (equal "{" (substring x 0 1)) + (equal "}" (substring x -1)) + x) + x)) + tags-in-group)) + regexp-in-group + (mapcar (lambda (x) + (substring x 1 -1)) + regexp-in-group-escaped) + tags-in-group + (delq nil (mapcar (lambda (x) + (if (stringp x) + (and (not (equal "{" (substring x 0 1))) + (not (equal "}" (substring x -1))) + x) + x)) + tags-in-group))) + ;; If single-as-list, do no more in the while-loop. + (if (not single-as-list) + (progn + (when regexp-in-group + (setq regexp-in-group + (concat "\\|" + (mapconcat 'identity regexp-in-group + "\\|")))) + (setq tags-in-group + (concat dir + "{\\<" + (regexp-opt tags-in-group) + "\\>" + regexp-in-group + "}")) + (when (stringp tags-in-group) + (org-add-props tags-in-group '(grouptag t))) + (setq return-match + (replace-match tags-in-group t t return-match))) + (setq tags-in-group + (append regexp-in-group-escaped tags-in-group)))) + (setq taggroups-keys (delete tag taggroups-keys)))) + ;; Add the regular expressions back into the match-expression again. + (while regexps-in-match + (setq return-match (replace-regexp-in-string (format "<%d>" count) + (pop regexps-in-match) + return-match t t)) + (cl-decf count)) (if single-as-list - (or (reverse rpl) (list rtnmatch)) - rtnmatch)) - (if single-as-list (list (if downcased (downcase match) match)) + (if tags-in-group tags-in-group (list return-match)) + return-match)) + (if single-as-list + (list (if downcased (downcase match) match)) match))) (defun org-op-to-function (op &optional stringp) @@ -14337,7 +14632,7 @@ it as a time string and apply `float-time' to it. If S is nil, just return 0." ((numberp s) s) ((stringp s) (condition-case nil - (float-time (apply 'encode-time (org-parse-time-string s))) + (float-time (apply #'encode-time (org-parse-time-string s))) (error 0.))) (t 0.))) @@ -14371,7 +14666,7 @@ epoch to the beginning of today (00:00)." (defvar org-add-colon-after-tag-completion nil) ;; dynamically scoped param (defvar org-tags-overlay (make-overlay 1 1)) -(org-detach-overlay org-tags-overlay) +(delete-overlay org-tags-overlay) (defun org-get-local-tags-at (&optional pos) "Get a list of tags defined in the current headline." @@ -14405,10 +14700,9 @@ ignore inherited ones." (org-back-to-heading t) (while (not (equal lastpos (point))) (setq lastpos (point)) - (when (looking-at - (org-re "[^\r\n]+?:\\([[:alnum:]_@#%:]+\\):[ \t]*$")) + (when (looking-at ".+?:\\([[:alnum:]_@#%:]+\\):[ \t]*$") (setq ltags (org-split-string - (org-match-string-no-properties 1) ":")) + (match-string-no-properties 1) ":")) (when parent (setq ltags (mapcar 'org-add-prop-inherited ltags))) (setq tags (append @@ -14417,7 +14711,7 @@ ignore inherited ones." ltags) tags))) (or org-use-tag-inheritance (throw 'done t)) - (if local (throw 'done t)) + (when local (throw 'done t)) (or (org-up-heading-safe) (error nil)) (setq parent t))) (error nil))))) @@ -14436,58 +14730,51 @@ ignore inherited ones." (defun org-toggle-tag (tag &optional onoff) "Toggle the tag TAG for the current line. If ONOFF is `on' or `off', don't toggle but set to this state." - (let (res current) - (save-excursion - (org-back-to-heading t) - (if (re-search-forward (org-re "[ \t]:\\([[:alnum:]_@#%:]+\\):[ \t]*$") - (point-at-eol) t) - (progn - (setq current (match-string 1)) - (replace-match "")) - (setq current "")) - (setq current (nreverse (org-split-string current ":"))) - (cond - ((eq onoff 'on) - (setq res t) - (or (member tag current) (push tag current))) - ((eq onoff 'off) - (or (not (member tag current)) (setq current (delete tag current)))) - (t (if (member tag current) - (setq current (delete tag current)) - (setq res t) - (push tag current)))) - (end-of-line 1) + (save-excursion + (org-back-to-heading t) + (let ((current + (when (re-search-forward "[ \t]:\\([[:alnum:]_@#%:]+\\):[ \t]*$" + (line-end-position) t) + (let ((tags (match-string 1))) + ;; Clear current tags. + (replace-match "") + ;; Reverse the tags list so any new tag is appended to + ;; the current list of tags. + (nreverse (org-split-string tags ":"))))) + res) + (pcase onoff + (`off (setq current (delete tag current))) + ((or `on (guard (not (member tag current)))) + (setq res t) + (cl-pushnew tag current :test #'equal)) + (_ (setq current (delete tag current)))) + (end-of-line) (if current (progn - (insert " :" (mapconcat 'identity (nreverse current) ":") ":") + (insert " :" (mapconcat #'identity (nreverse current) ":") ":") (org-set-tags nil t)) (delete-horizontal-space)) - (run-hooks 'org-after-tags-change-hook)) - res)) + (run-hooks 'org-after-tags-change-hook) + res))) -(defun org-align-tags-here (to-col) - ;; Assumes that this is a headline - "Align tags on the current headline to TO-COL." - (let ((pos (point)) (col (current-column)) ncol tags-l p) - (beginning-of-line 1) - (if (and (looking-at (org-re ".*?\\([ \t]+\\)\\(:[[:alnum:]_@#%:]+:\\)[ \t]*$")) - (< pos (match-beginning 2))) - (progn - (setq tags-l (- (match-end 2) (match-beginning 2))) - (goto-char (match-beginning 1)) - (insert " ") - (delete-region (point) (1+ (match-beginning 2))) - (setq ncol (max (current-column) - (1+ col) - (if (> to-col 0) - to-col - (- (abs to-col) tags-l)))) - (setq p (point)) - (insert (make-string (- ncol (current-column)) ?\ )) - (setq ncol (current-column)) - (when indent-tabs-mode (tabify p (point-at-eol))) - (org-move-to-column (min ncol col))) - (goto-char pos)))) +(defun org--align-tags-here (to-col) + "Align tags on the current headline to TO-COL. +Assume point is on a headline." + (let ((pos (point))) + (beginning-of-line) + (if (or (not (looking-at ".*?\\([ \t]+\\)\\(:[[:alnum:]_@#%:]+:\\)[ \t]*$")) + (>= pos (match-beginning 2))) + ;; No tags or point within tags: do not align. + (goto-char pos) + (goto-char (match-beginning 1)) + (let ((shift (max (- (if (>= to-col 0) to-col + (- (abs to-col) (string-width (match-string 2)))) + (current-column)) + 1))) + (replace-match (make-string shift ?\s) nil nil nil 1) + ;; Preserve initial position, if possible. In any case, stop + ;; before tags. + (when (< pos (point)) (goto-char pos)))))) (defun org-set-tags-command (&optional arg just-align) "Call the set-tags command for the current entry." @@ -14517,7 +14804,8 @@ If DATA is nil or the empty string, any tags will be removed." (when data (save-excursion (org-back-to-heading t) - (when (looking-at org-complex-heading-regexp) + (when (let ((case-fold-search nil)) + (looking-at org-complex-heading-regexp)) (if (match-end 5) (progn (goto-char (match-beginning 5)) @@ -14528,11 +14816,11 @@ If DATA is nil or the empty string, any tags will be removed." (insert " " data) (org-set-tags nil 'align))) (beginning-of-line 1) - (if (looking-at ".*?\\([ \t]+\\)$") - (delete-region (match-beginning 1) (match-end 1)))))) + (when (looking-at ".*?\\([ \t]+\\)$") + (delete-region (match-beginning 1) (match-end 1)))))) (defun org-align-all-tags () - "Align the tags i all headings." + "Align the tags in all headings." (interactive) (save-excursion (or (ignore-errors (org-back-to-heading t)) @@ -14549,106 +14837,124 @@ When JUST-ALIGN is non-nil, only align tags." (interactive "P") (if (and (org-region-active-p) org-loop-over-headlines-in-active-region) (let ((cl (if (eq org-loop-over-headlines-in-active-region 'start-level) - 'region-start-level 'region)) - org-loop-over-headlines-in-active-region) - (org-map-entries - ;; We don't use ARG and JUST-ALIGN here because these args - ;; are not useful when looping over headlines. - `(org-set-tags) - org-loop-over-headlines-in-active-region - cl (if (outline-invisible-p) (org-end-of-subtree nil t)))) - (let* ((re org-outline-regexp-bol) - (current (unless arg (org-get-tags-string))) - (col (current-column)) - (org-setting-tags t) - table current-tags inherited-tags ; computed below when needed - tags p0 c0 c1 rpl di tc level) + 'region-start-level + 'region)) + org-loop-over-headlines-in-active-region) + (org-map-entries + ;; We don't use ARG and JUST-ALIGN here because these args + ;; are not useful when looping over headlines. + #'org-set-tags + org-loop-over-headlines-in-active-region + cl + '(when (org-invisible-p) (org-end-of-subtree nil t)))) + (let ((org-setting-tags t)) (if arg - (save-excursion - (goto-char (point-min)) - (let ((buffer-invisibility-spec (org-inhibit-invisibility))) - (while (re-search-forward re nil t) - (org-set-tags nil t) - (end-of-line 1))) - (message "All tags realigned to column %d" org-tags-column)) - (if just-align - (setq tags current) - ;; Get a new set of tags from the user - (save-excursion - (setq table (append org-tag-persistent-alist - (or org-tag-alist (org-get-buffer-tags)) - (and - org-complete-tags-always-offer-all-agenda-tags - (org-global-tags-completion-table - (org-agenda-files)))) - org-last-tags-completion-table table - current-tags (org-split-string current ":") - inherited-tags (nreverse - (nthcdr (length current-tags) - (nreverse (org-get-tags-at)))) - tags - (if (or (eq t org-use-fast-tag-selection) - (and org-use-fast-tag-selection - (delq nil (mapcar 'cdr table)))) - (org-fast-tag-selection - current-tags inherited-tags table - (if org-fast-tag-selection-include-todo - org-todo-key-alist)) - (let ((org-add-colon-after-tag-completion (< 1 (length table)))) - (org-trim - (org-icompleting-read "Tags: " - 'org-tags-completion-function - nil nil current 'org-tags-history)))))) - (while (string-match "[-+&]+" tags) - ;; No boolean logic, just a list - (setq tags (replace-match ":" t t tags)))) - - (setq tags (replace-regexp-in-string "[,]" ":" tags)) - - (if org-tags-sort-function - (setq tags (mapconcat 'identity - (sort (org-split-string - tags (org-re "[^[:alnum:]_@#%]+")) - org-tags-sort-function) ":"))) - - (if (string-match "\\`[\t ]*\\'" tags) - (setq tags "") - (unless (string-match ":$" tags) (setq tags (concat tags ":"))) - (unless (string-match "^:" tags) (setq tags (concat ":" tags)))) - - ;; Insert new tags at the correct column - (beginning-of-line 1) - (setq level (or (and (looking-at org-outline-regexp) - (- (match-end 0) (point) 1)) - 1)) - (cond - ((and (equal current "") (equal tags ""))) - ((re-search-forward - (concat "\\([ \t]*" (regexp-quote current) "\\)[ \t]*$") - (point-at-eol) t) - (if (equal tags "") - (setq rpl "") - (goto-char (match-beginning 0)) - (setq c0 (current-column) - ;; compute offset for the case of org-indent-mode active - di (if (org-bound-and-true-p org-indent-mode) - (* (1- org-indent-indentation-per-level) (1- level)) - 0) - p0 (if (equal (char-before) ?*) (1+ (point)) (point)) - tc (+ org-tags-column (if (> org-tags-column 0) (- di) di)) - c1 (max (1+ c0) (if (> tc 0) tc (- (- tc) (length tags)))) - rpl (concat (make-string (max 0 (- c1 c0)) ?\ ) tags))) - (replace-match rpl t t) - (and (not (featurep 'xemacs)) c0 indent-tabs-mode (tabify p0 (point))) - tags) - (t (error "Tags alignment failed"))) - (org-move-to-column col) - (unless just-align - (run-hooks 'org-after-tags-change-hook)))))) + (save-excursion + (goto-char (point-min)) + (while (re-search-forward org-outline-regexp-bol nil t) + (org-set-tags nil t) + (end-of-line)) + (message "All tags realigned to column %d" org-tags-column)) + (let* ((current (org-get-tags-string)) + (tags + (if just-align current + ;; Get a new set of tags from the user. + (save-excursion + (let* ((seen) + (table + (setq + org-last-tags-completion-table + ;; Uniquify tags in alists, yet preserve + ;; structure (i.e., keywords). + (delq nil + (mapcar + (lambda (pair) + (let ((head (car pair))) + (cond ((symbolp head) pair) + ((member head seen) nil) + (t (push head seen) + pair)))) + (append + (or org-current-tag-alist + (org-get-buffer-tags)) + (and + org-complete-tags-always-offer-all-agenda-tags + (org-global-tags-completion-table + (org-agenda-files)))))))) + (current-tags (org-split-string current ":")) + (inherited-tags + (nreverse (nthcdr (length current-tags) + (nreverse (org-get-tags-at)))))) + (replace-regexp-in-string + "\\([-+&]+\\|,\\)" + ":" + (if (or (eq t org-use-fast-tag-selection) + (and org-use-fast-tag-selection + (delq nil (mapcar #'cdr table)))) + (org-fast-tag-selection + current-tags inherited-tags table + (and org-fast-tag-selection-include-todo + org-todo-key-alist)) + (let ((org-add-colon-after-tag-completion + (< 1 (length table)))) + (org-trim + (completing-read + "Tags: " + #'org-tags-completion-function + nil nil current 'org-tags-history)))))))))) + + (when org-tags-sort-function + (setq tags + (mapconcat + #'identity + (sort (org-split-string tags "[^[:alnum:]_@#%]+") + org-tags-sort-function) + ":"))) + + (if (or (string= ":" tags) + (string= "::" tags)) + (setq tags "")) + (if (not (org-string-nw-p tags)) (setq tags "") + (unless (string-suffix-p ":" tags) (setq tags (concat tags ":"))) + (unless (string-prefix-p ":" tags) (setq tags (concat ":" tags)))) + + ;; Insert new tags at the correct column. + (unless (equal current tags) + (save-excursion + (beginning-of-line) + (let ((case-fold-search nil)) + (looking-at org-complex-heading-regexp)) + ;; Remove current tags, if any. + (when (match-end 5) (replace-match "" nil nil nil 5)) + ;; Insert new tags, if any. Otherwise, remove trailing + ;; white spaces. + (end-of-line) + (if (not (equal tags "")) + ;; When text is being inserted on an invisible + ;; region boundary, it can be inadvertently sucked + ;; into invisibility. + (outline-flag-region (point) (progn (insert " " tags) (point)) nil) + (skip-chars-backward " \t") + (delete-region (point) (line-end-position))))) + ;; Align tags, if any. Fix tags column if `org-indent-mode' + ;; is on. + (unless (equal tags "") + (let* ((level (save-excursion + (beginning-of-line) + (skip-chars-forward "\\*"))) + (offset (if (bound-and-true-p org-indent-mode) + (* (1- org-indent-indentation-per-level) + (1- level)) + 0)) + (tags-column + (+ org-tags-column + (if (> org-tags-column 0) (- offset) offset)))) + (org--align-tags-here tags-column)))) + (unless just-align (run-hooks 'org-after-tags-change-hook)))))) (defun org-change-tag-in-region (beg end tag off) "Add or remove TAG for each entry in the region. -This works in the agenda, and also in an org-mode buffer." +This works in the agenda, and also in an Org buffer." (interactive (list (region-beginning) (region-end) (let ((org-last-tags-completion-table @@ -14657,37 +14963,37 @@ This works in the agenda, and also in an org-mode buffer." (delq nil (append (org-get-buffer-tags) (org-global-tags-completion-table)))) (org-global-tags-completion-table)))) - (org-icompleting-read + (completing-read "Tag: " 'org-tags-completion-function nil nil nil 'org-tags-history)) (progn (message "[s]et or [r]emove? ") (equal (read-char-exclusive) ?r)))) - (if (fboundp 'deactivate-mark) (deactivate-mark)) + (when (fboundp 'deactivate-mark) (deactivate-mark)) (let ((agendap (equal major-mode 'org-agenda-mode)) l1 l2 m buf pos newhead (cnt 0)) (goto-char end) (setq l2 (1- (org-current-line))) (goto-char beg) (setq l1 (org-current-line)) - (loop for l from l1 to l2 do - (org-goto-line l) - (setq m (get-text-property (point) 'org-hd-marker)) - (when (or (and (derived-mode-p 'org-mode) (org-at-heading-p)) - (and agendap m)) - (setq buf (if agendap (marker-buffer m) (current-buffer)) - pos (if agendap m (point))) - (with-current-buffer buf - (save-excursion - (save-restriction - (goto-char pos) - (setq cnt (1+ cnt)) - (org-toggle-tag tag (if off 'off 'on)) - (setq newhead (org-get-heading))))) - (and agendap (org-agenda-change-all-lines newhead m)))) + (cl-loop for l from l1 to l2 do + (org-goto-line l) + (setq m (get-text-property (point) 'org-hd-marker)) + (when (or (and (derived-mode-p 'org-mode) (org-at-heading-p)) + (and agendap m)) + (setq buf (if agendap (marker-buffer m) (current-buffer)) + pos (if agendap m (point))) + (with-current-buffer buf + (save-excursion + (save-restriction + (goto-char pos) + (setq cnt (1+ cnt)) + (org-toggle-tag tag (if off 'off 'on)) + (setq newhead (org-get-heading))))) + (and agendap (org-agenda-change-all-lines newhead m)))) (message "Tag :%s: %s in %d headings" tag (if off "removed" "set") cnt))) -(defun org-tags-completion-function (string predicate &optional flag) +(defun org-tags-completion-function (string _predicate &optional flag) (let (s1 s2 rtn (ctable org-last-tags-completion-table) (confirm (lambda (x) (stringp (car x))))) (if (string-match "^\\(.*[-+:&,|]\\)\\([^-+:&,|]*\\)$" string) @@ -14698,12 +15004,12 @@ This works in the agenda, and also in an org-mode buffer." ((eq flag nil) ;; try completion (setq rtn (try-completion s2 ctable confirm)) - (if (stringp rtn) - (setq rtn - (concat s1 s2 (substring rtn (length s2)) - (if (and org-add-colon-after-tag-completion - (assoc rtn ctable)) - ":" "")))) + (when (stringp rtn) + (setq rtn + (concat s1 s2 (substring rtn (length s2)) + (if (and org-add-colon-after-tag-completion + (assoc rtn ctable)) + ":" "")))) rtn) ((eq flag t) ;; all-completions @@ -14722,8 +15028,8 @@ Also insert END." (defun org-fast-tag-show-exit (flag) (save-excursion (org-goto-line 3) - (if (re-search-forward "[ \t]+Next change exits" (point-at-eol) t) - (replace-match "")) + (when (re-search-forward "[ \t]+Next change exits" (point-at-eol) t) + (replace-match "")) (when flag (end-of-line 1) (org-move-to-column (- (window-width) 19) t) @@ -14732,11 +15038,8 @@ Also insert END." (defun org-set-current-tags-overlay (current prefix) "Add an overlay to CURRENT tag with PREFIX." (let ((s (concat ":" (mapconcat 'identity current ":") ":"))) - (if (featurep 'xemacs) - (org-overlay-display org-tags-overlay (concat prefix s) - 'secondary-selection) - (put-text-property 0 (length s) 'face '(secondary-selection org-tag) s) - (org-overlay-display org-tags-overlay (concat prefix s))))) + (put-text-property 0 (length s) 'face '(secondary-selection org-tag) s) + (org-overlay-display org-tags-overlay (concat prefix s)))) (defvar org-last-tag-selection-key nil) (defun org-fast-tag-selection (current inherited table &optional todo-table) @@ -14759,15 +15062,14 @@ Returns the new tags string, or nil to not change the current settings." (ncol (/ (- (window-width) 4) fwidth)) (i-face 'org-done) (c-face 'org-todo) - tg cnt c char c1 c2 ntable tbl rtn + tg cnt e c char c1 c2 ntable tbl rtn ov-start ov-end ov-prefix (exit-after-next org-fast-tag-selection-single-key) (done-keywords org-done-keywords) - groups ingroup) + groups ingroup intaggroup) (save-excursion (beginning-of-line 1) - (if (looking-at - (org-re ".*[ \t]\\(:[[:alnum:]_@#%:]+:\\)[ \t]*$")) + (if (looking-at ".*[ \t]\\(:[[:alnum:]_@#%:]+:\\)[ \t]*$") (setq ov-start (match-beginning 1) ov-end (match-end 1) ov-prefix "") @@ -14788,32 +15090,41 @@ Returns the new tags string, or nil to not change the current settings." (set-window-buffer (split-window-vertically) (get-buffer-create " *Org tags*")) (org-switch-to-buffer-other-window " *Org tags*")) (erase-buffer) - (org-set-local 'org-done-keywords done-keywords) + (setq-local org-done-keywords done-keywords) (org-fast-tag-insert "Inherited" inherited i-face "\n") (org-fast-tag-insert "Current" current c-face "\n\n") (org-fast-tag-show-exit exit-after-next) (org-set-current-tags-overlay current ov-prefix) (setq tbl fulltable char ?a cnt 0) - (dolist (e tbl) + (while (setq e (pop tbl)) (cond - ((equal (car e) :startgroup) + ((eq (car e) :startgroup) (push '() groups) (setq ingroup t) - (when (not (= cnt 0)) + (unless (zerop cnt) (setq cnt 0) (insert "\n")) (insert (if (cdr e) (format "%s: " (cdr e)) "") "{ ")) - ((equal (car e) :endgroup) + ((eq (car e) :endgroup) (setq ingroup nil cnt 0) (insert "}" (if (cdr e) (format " (%s) " (cdr e)) "") "\n")) + ((eq (car e) :startgrouptag) + (setq intaggroup t) + (unless (zerop cnt) + (setq cnt 0) + (insert "\n")) + (insert "[ ")) + ((eq (car e) :endgrouptag) + (setq intaggroup nil cnt 0) + (insert "]\n")) ((equal e '(:newline)) - (when (not (= cnt 0)) + (unless (zerop cnt) (setq cnt 0) (insert "\n") (setq e (car tbl)) (while (equal (car tbl) '(:newline)) (insert "\n") (setq tbl (cdr tbl))))) - ((equal e '(:grouptags)) nil) + ((equal e '(:grouptags)) (insert " : ")) (t (setq tg (copy-sequence (car e)) c2 nil) (if (cdr e) @@ -14827,31 +15138,31 @@ Returns the new tags string, or nil to not change the current settings." (setq char (1+ char))) (setq c2 c1)) (setq c (or c2 char))) - (if ingroup (push tg (car groups))) + (when ingroup (push tg (car groups))) (setq tg (org-add-props tg nil 'face (cond ((not (assoc tg table)) (org-get-todo-face tg)) ((member tg current) c-face) ((member tg inherited) i-face)))) - (if (equal (caar tbl) :grouptags) - (org-add-props tg nil 'face 'org-tag-group)) - (if (and (= cnt 0) (not ingroup)) (insert " ")) + (when (equal (caar tbl) :grouptags) + (org-add-props tg nil 'face 'org-tag-group)) + (when (and (zerop cnt) (not ingroup) (not intaggroup)) (insert " ")) (insert "[" c "] " tg (make-string (- fwidth 4 (length tg)) ?\ )) (push (cons tg c) ntable) - (when (= (setq cnt (1+ cnt)) ncol) + (when (= (cl-incf cnt) ncol) (insert "\n") - (if ingroup (insert " ")) + (when (or ingroup intaggroup) (insert " ")) (setq cnt 0))))) (setq ntable (nreverse ntable)) (insert "\n") (goto-char (point-min)) - (if (not expert) (org-fit-window-to-buffer)) + (unless expert (org-fit-window-to-buffer)) (setq rtn (catch 'exit (while t - (message "[a-z..]:Toggle [SPC]:clear [RET]:accept [TAB]:free [!] %sgroups%s" + (message "[a-z..]:toggle [SPC]:clear [RET]:accept [TAB]:edit [!] %sgroups%s" (if (not groups) "no " "") (if expert " [C-c]:window" (if exit-after-next " [C-c]:single" " [C-c]:multi"))) (setq c (let ((inhibit-quit t)) (read-char-exclusive))) @@ -14873,53 +15184,51 @@ Returns the new tags string, or nil to not change the current settings." (org-fit-window-to-buffer))) ((or (= c ?\C-g) (and (= c ?q) (not (rassoc c ntable)))) - (org-detach-overlay org-tags-overlay) + (delete-overlay org-tags-overlay) (setq quit-flag t)) ((= c ?\ ) (setq current nil) - (if exit-after-next (setq exit-after-next 'now))) + (when exit-after-next (setq exit-after-next 'now))) ((= c ?\t) (condition-case nil - (setq tg (org-icompleting-read + (setq tg (completing-read "Tag: " (or buffer-tags (with-current-buffer buf - (org-get-buffer-tags))))) + (setq buffer-tags + (org-get-buffer-tags)))))) (quit (setq tg ""))) (when (string-match "\\S-" tg) - (add-to-list 'buffer-tags (list tg)) + (cl-pushnew (list tg) buffer-tags :test #'equal) (if (member tg current) (setq current (delete tg current)) (push tg current))) - (if exit-after-next (setq exit-after-next 'now))) + (when exit-after-next (setq exit-after-next 'now))) ((setq e (rassoc c todo-table) tg (car e)) (with-current-buffer buf (save-excursion (org-todo tg))) - (if exit-after-next (setq exit-after-next 'now))) + (when exit-after-next (setq exit-after-next 'now))) ((setq e (rassoc c ntable) tg (car e)) (if (member tg current) (setq current (delete tg current)) - (loop for g in groups do - (if (member tg g) - (mapc (lambda (x) - (setq current (delete x current))) - g))) + (cl-loop for g in groups do + (when (member tg g) + (dolist (x g) (setq current (delete x current))))) (push tg current)) - (if exit-after-next (setq exit-after-next 'now)))) + (when exit-after-next (setq exit-after-next 'now)))) ;; Create a sorted list (setq current (sort current (lambda (a b) (assoc b (cdr (memq (assoc a ntable) ntable)))))) - (if (eq exit-after-next 'now) (throw 'exit t)) + (when (eq exit-after-next 'now) (throw 'exit t)) (goto-char (point-min)) (beginning-of-line 2) (delete-region (point) (point-at-eol)) (org-fast-tag-insert "Current" current c-face) (org-set-current-tags-overlay current ov-prefix) - (while (re-search-forward - (org-re "\\[.\\] \\([[:alnum:]_@#%]+\\)") nil t) + (while (re-search-forward "\\[.\\] \\([[:alnum:]_@#%]+\\)" nil t) (setq tg (match-string 1)) (add-text-properties (match-beginning 1) (match-end 1) @@ -14929,7 +15238,7 @@ Returns the new tags string, or nil to not change the current settings." ((member tg inherited) i-face) (t (get-text-property (match-beginning 1) 'face)))))) (goto-char (point-min))))) - (org-detach-overlay org-tags-overlay) + (delete-overlay org-tags-overlay) (if rtn (mapconcat 'identity current ":") nil)))) @@ -14940,8 +15249,8 @@ Returns the new tags string, or nil to not change the current settings." (user-error "Not on a heading")) (save-excursion (beginning-of-line 1) - (if (looking-at (org-re ".*[ \t]\\(:[[:alnum:]_@#%:]+:\\)[ \t]*$")) - (org-match-string-no-properties 1) + (if (looking-at ".*[ \t]\\(:[[:alnum:]_@#%:]+:\\)[ \t]*$") + (match-string-no-properties 1) ""))) (defun org-get-tags () @@ -14950,19 +15259,20 @@ Returns the new tags string, or nil to not change the current settings." (defun org-get-buffer-tags () "Get a table of all tags used in the buffer, for completion." - (let (tags) - (save-excursion - (goto-char (point-min)) - (while (re-search-forward - (org-re "[ \t]:\\([[:alnum:]_@#%:]+\\):[ \t\r\n]") nil t) - (when (equal (char-after (point-at-bol 0)) ?*) - (mapc (lambda (x) (add-to-list 'tags x)) - (org-split-string (org-match-string-no-properties 1) ":"))))) - (mapc (lambda (s) (add-to-list 'tags s)) org-file-tags) - (mapcar 'list tags))) + (org-with-wide-buffer + (goto-char (point-min)) + (let ((tag-re (concat org-outline-regexp-bol + "\\(?:.*?[ \t]\\)?:\\([[:alnum:]_@#%:]+\\):[ \t]*$")) + tags) + (while (re-search-forward tag-re nil t) + (dolist (tag (org-split-string (match-string-no-properties 1) ":")) + (push tag tags))) + (mapcar #'list (append org-file-tags (org-uniquify tags)))))) ;;;; The mapping API +(defvar org-agenda-skip-comment-trees) +(defvar org-agenda-skip-function) (defun org-map-entries (func &optional match scope &rest skip) "Call FUNC at each headline selected by MATCH in SCOPE. @@ -15032,13 +15342,12 @@ a *different* entry, you cannot use these techniques." (car (org-delete-all '(comment archive) skip))) (org-tags-match-list-sublevels t) (start-level (eq scope 'region-start-level)) - matcher file res + matcher res org-todo-keywords-for-agenda org-done-keywords-for-agenda org-todo-keyword-alist-for-agenda - org-drawers-for-agenda org-tag-alist-for-agenda - todo-only) + org--matcher-tags-todo-only) (cond ((eq match t) (setq matcher t)) @@ -15071,7 +15380,9 @@ a *different* entry, you cannot use these techniques." (progn (org-agenda-prepare-buffers (and buffer-file-name (list buffer-file-name))) - (setq res (org-scan-tags func matcher todo-only start-level))) + (setq res + (org-scan-tags + func matcher org--matcher-tags-todo-only start-level))) ;; Get the right scope (cond ((and scope (listp scope) (symbolp (car scope))) @@ -15088,22 +15399,21 @@ a *different* entry, you cannot use these techniques." (org-agenda-prepare-buffers scope) (dolist (file scope) (with-current-buffer (org-find-base-buffer-visiting file) - (save-excursion - (save-restriction - (widen) - (goto-char (point-min)) - (setq res (append res (org-scan-tags func matcher todo-only)))))))))) + (org-with-wide-buffer + (goto-char (point-min)) + (setq res + (append + res + (org-scan-tags + func matcher org--matcher-tags-todo-only))))))))) res))) -;;;; Properties - -;;; Setting and retrieving properties +;;; Properties API (defconst org-special-properties - '("TODO" "TAGS" "ALLTAGS" "DEADLINE" "SCHEDULED" "CLOCK" "CLOSED" "PRIORITY" - "TIMESTAMP" "TIMESTAMP_IA" "BLOCKED" "FILE" "CLOCKSUM" "CLOCKSUM_T") - "The special properties valid in Org-mode. - + '("ALLTAGS" "BLOCKED" "CLOCKSUM" "CLOCKSUM_T" "CLOSED" "DEADLINE" "FILE" + "ITEM" "PRIORITY" "SCHEDULED" "TAGS" "TIMESTAMP" "TIMESTAMP_IA" "TODO") + "The special properties valid in Org mode. These are properties that are not defined in the property drawer, but in some other way.") @@ -15112,59 +15422,85 @@ but in some other way.") "LOCATION" "LOGGING" "COLUMNS" "VISIBILITY" "TABLE_EXPORT_FORMAT" "TABLE_EXPORT_FILE" "EXPORT_OPTIONS" "EXPORT_TEXT" "EXPORT_FILE_NAME" - "EXPORT_TITLE" "EXPORT_AUTHOR" "EXPORT_DATE" + "EXPORT_TITLE" "EXPORT_AUTHOR" "EXPORT_DATE" "UNNUMBERED" "ORDERED" "NOBLOCKING" "COOKIE_DATA" "LOG_INTO_DRAWER" "REPEAT_TO_STATE" "CLOCK_MODELINE_TOTAL" "STYLE" "HTML_CONTAINER_CLASS") - "Some properties that are used by Org-mode for various purposes. + "Some properties that are used by Org mode for various purposes. Being in this list makes sure that they are offered for completion.") -(defconst org-property-start-re "^[ \t]*:PROPERTIES:[ \t]*$" - "Regular expression matching the first line of a property drawer.") - -(defconst org-property-end-re "^[ \t]*:END:[ \t]*$" - "Regular expression matching the last line of a property drawer.") - -(defconst org-clock-drawer-start-re "^[ \t]*:CLOCK:[ \t]*$" - "Regular expression matching the first line of a property drawer.") - -(defconst org-clock-drawer-end-re "^[ \t]*:END:[ \t]*$" - "Regular expression matching the first line of a property drawer.") - -(defconst org-property-drawer-re - (concat "\\(" org-property-start-re "\\)[^\000]*\\(" - org-property-end-re "\\)\n?") - "Matches an entire property drawer.") +(defun org--valid-property-p (property) + "Non nil when string PROPERTY is a valid property name." + (not + (or (equal property "") + (string-match-p "\\s-" property)))) + +(defun org--update-property-plist (key val props) + "Associate KEY to VAL in alist PROPS. +Modifications are made by side-effect. Return new alist." + (let* ((appending (string= (substring key -1) "+")) + (key (if appending (substring key 0 -1) key)) + (old (assoc-string key props t))) + (if (not old) (cons (cons key val) props) + (setcdr old (if appending (concat (cdr old) " " val) val)) + props))) + +(defun org-get-property-block (&optional beg force) + "Return the (beg . end) range of the body of the property drawer. +BEG is the beginning of the current subtree, or of the part +before the first headline. If it is not given, it will be found. +If the drawer does not exist, create it if FORCE is non-nil, or +return nil." + (org-with-wide-buffer + (when beg (goto-char beg)) + (unless (org-before-first-heading-p) + (let ((beg (cond (beg) + ((or (not (featurep 'org-inlinetask)) + (org-inlinetask-in-task-p)) + (org-back-to-heading t)) + (t (org-with-limited-levels (org-back-to-heading t)))))) + (forward-line) + (when (looking-at-p org-planning-line-re) (forward-line)) + (cond ((looking-at org-property-drawer-re) + (forward-line) + (cons (point) (progn (goto-char (match-end 0)) + (line-beginning-position)))) + (force + (goto-char beg) + (org-insert-property-drawer) + (let ((pos (save-excursion (search-forward ":END:") + (line-beginning-position)))) + (cons pos pos)))))))) -(defconst org-clock-drawer-re - (concat "\\(" org-clock-drawer-start-re "\\)[^\000]*\\(" - org-property-end-re "\\)\n?") - "Matches an entire clock drawer.") +(defun org-at-property-p () + "Non-nil when point is inside a property drawer. +See `org-property-re' for match data, if applicable." + (save-excursion + (beginning-of-line) + (and (looking-at org-property-re) + (let ((property-drawer (save-match-data (org-get-property-block)))) + (and property-drawer + (>= (point) (car property-drawer)) + (< (point) (cdr property-drawer))))))) (defun org-property-action () "Do an action on properties." (interactive) - (let (c) - (org-at-property-p) - (message "Property Action: [s]et [d]elete [D]elete globally [c]ompute") - (setq c (read-char-exclusive)) - (cond - ((equal c ?s) - (call-interactively 'org-set-property)) - ((equal c ?d) - (call-interactively 'org-delete-property)) - ((equal c ?D) - (call-interactively 'org-delete-property-globally)) - ((equal c ?c) - (call-interactively 'org-compute-property-at-point)) - (t (user-error "No such property action %c" c))))) + (message "Property Action: [s]et [d]elete [D]elete globally [c]ompute") + (let ((c (read-char-exclusive))) + (cl-case c + (?s (call-interactively #'org-set-property)) + (?d (call-interactively #'org-delete-property)) + (?D (call-interactively #'org-delete-property-globally)) + (?c (call-interactively #'org-compute-property-at-point)) + (otherwise (user-error "No such property action %c" c))))) (defun org-inc-effort () "Increment the value of the effort property in the current entry." (interactive) (org-set-effort nil t)) -(defvar org-clock-effort) ;; Defined in org-clock.el -(defvar org-clock-current-task) ;; Defined in org-clock.el +(defvar org-clock-effort) ; Defined in org-clock.el. +(defvar org-clock-current-task) ; Defined in org-clock.el. (defun org-set-effort (&optional value increment) "Set the effort property of the current entry. With numerical prefix arg, use the nth allowed value, 0 stands for the @@ -15172,7 +15508,7 @@ With numerical prefix arg, use the nth allowed value, 0 stands for the When INCREMENT is non-nil, set the property to the next allowed value." (interactive "P") - (if (equal value 0) (setq value 10)) + (when (equal value 0) (setq value 10)) (let* ((completion-ignore-case t) (prop org-effort-property) (cur (org-entry-get nil prop)) @@ -15186,7 +15522,7 @@ When INCREMENT is non-nil, set the property to the next allowed value." (or (car (nth (1- value) allowed)) (car (org-last allowed)))) ((and allowed increment) - (or (caadr (member (list cur) allowed)) + (or (cl-caadr (member (list cur) allowed)) (user-error "Allowed effort values are not set"))) (allowed (message "Select 1-9,0, [RET%s]: %s" @@ -15196,231 +15532,294 @@ When INCREMENT is non-nil, set the property to the next allowed value." (if (equal rpl ?\r) cur (setq rpl (- rpl ?0)) - (if (equal rpl 0) (setq rpl 10)) + (when (equal rpl 0) (setq rpl 10)) (if (and (> rpl 0) (<= rpl (length allowed))) (car (nth (1- rpl) allowed)) (org-completing-read "Effort: " allowed nil)))) (t - (let (org-completion-use-ido org-completion-use-iswitchb) - (org-completing-read - (concat "Effort " (if (and cur (string-match "\\S-" cur)) - (concat "[" cur "]") "") - ": ") - existing nil nil "" nil cur)))))) + (org-completing-read + (concat "Effort" (and cur (string-match "\\S-" cur) + (concat " [" cur "]")) + ": ") + existing nil nil "" nil cur))))) (unless (equal (org-entry-get nil prop) val) (org-entry-put nil prop val)) - (save-excursion - (org-back-to-heading t) - (put-text-property (point-at-bol) (point-at-eol) 'org-effort val)) - (when (string= heading org-clock-current-task) - (setq org-clock-effort (get-text-property (point-at-bol) 'org-effort)) + (org-refresh-property + '((effort . identity) + (effort-minutes . org-duration-to-minutes)) + val) + (when (equal heading (bound-and-true-p org-clock-current-task)) + (setq org-clock-effort (get-text-property (point-at-bol) 'effort)) (org-clock-update-mode-line)) (message "%s is now %s" prop val))) -(defun org-at-property-p () - "Is cursor inside a property drawer?" - (save-excursion - (when (equal 'node-property (car (org-element-at-point))) - (beginning-of-line 1) - (looking-at org-property-re)))) +(defun org-entry-properties (&optional pom which) + "Get all properties of the current entry. + +When POM is a buffer position, get all properties from the entry +there instead. + +This includes the TODO keyword, the tags, time strings for +deadline, scheduled, and clocking, and any additional properties +defined in the entry. -(defun org-get-property-block (&optional beg end force) - "Return the (beg . end) range of the body of the property drawer. -BEG and END are the beginning and end of the current subtree, or of -the part before the first headline. If they are not given, they will -be found. If the drawer does not exist and FORCE is non-nil, create -the drawer." - (catch 'exit - (save-excursion - (let* ((beg (or beg (and (org-before-first-heading-p) (point-min)) - (progn (org-back-to-heading t) (point)))) - (end (or end (and (not (outline-next-heading)) (point-max)) - (point)))) - (goto-char beg) - (if (re-search-forward org-property-start-re end t) - (setq beg (1+ (match-end 0))) - (if force - (save-excursion - (org-insert-property-drawer) - (setq end (progn (outline-next-heading) (point)))) - (throw 'exit nil)) - (goto-char beg) - (if (re-search-forward org-property-start-re end t) - (setq beg (1+ (match-end 0))))) - (if (re-search-forward org-property-end-re end t) - (setq end (match-beginning 0)) - (or force (throw 'exit nil)) - (goto-char beg) - (setq end beg) - (org-indent-line) - (insert ":END:\n")) - (cons beg end))))) - -(defun org-entry-properties (&optional pom which specific) - "Get all properties of the entry at point-or-marker POM. -This includes the TODO keyword, the tags, time strings for deadline, -scheduled, and clocking, and any additional properties defined in the -entry. The return value is an alist, keys may occur multiple times -if the property key was used several times. -POM may also be nil, in which case the current entry is used. If WHICH is nil or `all', get all properties. If WHICH is -`special' or `standard', only get that subclass. If WHICH -is a string only get exactly this property. SPECIFIC can be a string, the -specific property we are interested in. Specifying it can speed -things up because then unnecessary parsing is avoided." - (setq which (or which 'all)) - (org-with-wide-buffer - (org-with-point-at pom - (let ((clockstr (substring org-clock-string 0 -1)) - (excluded '("TODO" "TAGS" "ALLTAGS" "PRIORITY" "BLOCKED")) - (case-fold-search nil) - beg end range props sum-props key key1 value string clocksum clocksumt) - (when (and (derived-mode-p 'org-mode) - (ignore-errors (org-back-to-heading t))) - (setq beg (point)) - (setq sum-props (get-text-property (point) 'org-summaries)) - (setq clocksum (get-text-property (point) :org-clock-minutes) - clocksumt (get-text-property (point) :org-clock-minutes-today)) - (outline-next-heading) - (setq end (point)) - (when (memq which '(all special)) - ;; Get the special properties, like TODO and tags - (goto-char beg) - (when (and (or (not specific) (string= specific "TODO")) - (looking-at org-todo-line-regexp) (match-end 2)) - (push (cons "TODO" (org-match-string-no-properties 2)) props)) - (when (and (or (not specific) (string= specific "PRIORITY")) - (looking-at org-priority-regexp)) - (push (cons "PRIORITY" (org-match-string-no-properties 2)) props)) - (when (or (not specific) (string= specific "FILE")) - (push (cons "FILE" buffer-file-name) props)) - (when (and (or (not specific) (string= specific "TAGS")) - (setq value (org-get-tags-string)) - (string-match "\\S-" value)) - (push (cons "TAGS" value) props)) - (when (and (or (not specific) (string= specific "ALLTAGS")) - (setq value (org-get-tags-at))) - (push (cons "ALLTAGS" (concat ":" (mapconcat 'identity value ":") - ":")) - props)) - (when (or (not specific) (string= specific "BLOCKED")) - (push (cons "BLOCKED" (if (org-entry-blocked-p) "t" "")) props)) - (when (or (not specific) - (member specific - '("SCHEDULED" "DEADLINE" "CLOCK" "CLOSED" - "TIMESTAMP" "TIMESTAMP_IA"))) - (catch 'match - (while (and (re-search-forward org-maybe-keyword-time-regexp end t) - (not (text-property-any 0 (length (match-string 0)) - 'face 'font-lock-comment-face - (match-string 0)))) - (setq key (if (match-end 1) - (substring (org-match-string-no-properties 1) - 0 -1)) - string (if (equal key clockstr) - (org-trim - (buffer-substring-no-properties - (match-beginning 3) (goto-char - (point-at-eol)))) - (substring (org-match-string-no-properties 3) - 1 -1))) - ;; Get the correct property name from the key. This is - ;; necessary if the user has configured time keywords. - (setq key1 (concat key ":")) - (cond - ((not key) - (setq key - (if (= (char-after (match-beginning 3)) ?\[) - "TIMESTAMP_IA" "TIMESTAMP"))) - ((equal key1 org-scheduled-string) (setq key "SCHEDULED")) - ((equal key1 org-deadline-string) (setq key "DEADLINE")) - ((equal key1 org-closed-string) (setq key "CLOSED")) - ((equal key1 org-clock-string) (setq key "CLOCK"))) - (if (and specific (equal key specific) (not (equal key "CLOCK"))) - (progn - (push (cons key string) props) - ;; no need to search further if match is found - (throw 'match t)) - (when (or (equal key "CLOCK") (not (assoc key props))) - (push (cons key string) props))))))) - - (when (memq which '(all standard)) - ;; Get the standard properties, like :PROP: ... - (setq range (org-get-property-block beg end)) - (when range - (goto-char (car range)) - (while (re-search-forward org-property-re - (cdr range) t) - (setq key (org-match-string-no-properties 2) - value (org-trim (or (org-match-string-no-properties 3) ""))) - (unless (member key excluded) - (push (cons key (or value "")) props))))) - (if clocksum - (push (cons "CLOCKSUM" - (org-columns-number-to-string (/ (float clocksum) 60.) - 'add_times)) - props)) - (if clocksumt - (push (cons "CLOCKSUM_T" - (org-columns-number-to-string (/ (float clocksumt) 60.) - 'add_times)) - props)) - (unless (assoc "CATEGORY" props) - (push (cons "CATEGORY" (org-get-category)) props)) - (append sum-props (nreverse props))))))) +`special' or `standard', only get that subclass. If WHICH is +a string, only get that property. + +Return value is an alist. Keys are properties, as upcased +strings." + (org-with-point-at pom + (when (and (derived-mode-p 'org-mode) + (ignore-errors (org-back-to-heading t))) + (catch 'exit + (let* ((beg (point)) + (specific (and (stringp which) (upcase which))) + (which (cond ((not specific) which) + ((member specific org-special-properties) 'special) + (t 'standard))) + props) + ;; Get the special properties, like TODO and TAGS. + (when (memq which '(nil all special)) + (when (or (not specific) (string= specific "CLOCKSUM")) + (let ((clocksum (get-text-property (point) :org-clock-minutes))) + (when clocksum + (push (cons "CLOCKSUM" (org-duration-from-minutes clocksum)) + props))) + (when specific (throw 'exit props))) + (when (or (not specific) (string= specific "CLOCKSUM_T")) + (let ((clocksumt (get-text-property (point) + :org-clock-minutes-today))) + (when clocksumt + (push (cons "CLOCKSUM_T" + (org-duration-from-minutes clocksumt)) + props))) + (when specific (throw 'exit props))) + (when (or (not specific) (string= specific "ITEM")) + (let ((case-fold-search nil)) + (when (looking-at org-complex-heading-regexp) + (push (cons "ITEM" + (let ((title (match-string-no-properties 4))) + (if (org-string-nw-p title) + (org-remove-tabs title) + ""))) + props))) + (when specific (throw 'exit props))) + (when (or (not specific) (string= specific "TODO")) + (let ((case-fold-search nil)) + (when (and (looking-at org-todo-line-regexp) (match-end 2)) + (push (cons "TODO" (match-string-no-properties 2)) props))) + (when specific (throw 'exit props))) + (when (or (not specific) (string= specific "PRIORITY")) + (push (cons "PRIORITY" + (if (looking-at org-priority-regexp) + (match-string-no-properties 2) + (char-to-string org-default-priority))) + props) + (when specific (throw 'exit props))) + (when (or (not specific) (string= specific "FILE")) + (push (cons "FILE" (buffer-file-name (buffer-base-buffer))) + props) + (when specific (throw 'exit props))) + (when (or (not specific) (string= specific "TAGS")) + (let ((value (org-string-nw-p (org-get-tags-string)))) + (when value (push (cons "TAGS" value) props))) + (when specific (throw 'exit props))) + (when (or (not specific) (string= specific "ALLTAGS")) + (let ((value (org-get-tags-at))) + (when value + (push (cons "ALLTAGS" + (format ":%s:" (mapconcat #'identity value ":"))) + props))) + (when specific (throw 'exit props))) + (when (or (not specific) (string= specific "BLOCKED")) + (push (cons "BLOCKED" (if (org-entry-blocked-p) "t" "")) props) + (when specific (throw 'exit props))) + (when (or (not specific) + (member specific '("CLOSED" "DEADLINE" "SCHEDULED"))) + (forward-line) + (when (looking-at-p org-planning-line-re) + (end-of-line) + (let ((bol (line-beginning-position)) + ;; Backward compatibility: time keywords used to + ;; be configurable (before 8.3). Make sure we + ;; get the correct keyword. + (key-assoc `(("CLOSED" . ,org-closed-string) + ("DEADLINE" . ,org-deadline-string) + ("SCHEDULED" . ,org-scheduled-string)))) + (dolist (pair (if specific (list (assoc specific key-assoc)) + key-assoc)) + (save-excursion + (when (search-backward (cdr pair) bol t) + (goto-char (match-end 0)) + (skip-chars-forward " \t") + (and (looking-at org-ts-regexp-both) + (push (cons (car pair) + (match-string-no-properties 0)) + props))))))) + (when specific (throw 'exit props))) + (when (or (not specific) + (member specific '("TIMESTAMP" "TIMESTAMP_IA"))) + (let ((find-ts + (lambda (end ts) + ;; Fix next time-stamp before END. TS is the + ;; list of time-stamps found so far. + (let ((ts ts) + (regexp (cond + ((string= specific "TIMESTAMP") + org-ts-regexp) + ((string= specific "TIMESTAMP_IA") + org-ts-regexp-inactive) + ((assoc "TIMESTAMP_IA" ts) + org-ts-regexp) + ((assoc "TIMESTAMP" ts) + org-ts-regexp-inactive) + (t org-ts-regexp-both)))) + (catch 'next + (while (re-search-forward regexp end t) + (backward-char) + (let ((object (org-element-context))) + ;; Accept to match timestamps in node + ;; properties, too. + (when (memq (org-element-type object) + '(node-property timestamp)) + (let ((type + (org-element-property :type object))) + (cond + ((and (memq type '(active active-range)) + (not (equal specific "TIMESTAMP_IA"))) + (unless (assoc "TIMESTAMP" ts) + (push (cons "TIMESTAMP" + (org-element-property + :raw-value object)) + ts) + (when specific (throw 'exit ts)))) + ((and (memq type '(inactive inactive-range)) + (not (string= specific "TIMESTAMP"))) + (unless (assoc "TIMESTAMP_IA" ts) + (push (cons "TIMESTAMP_IA" + (org-element-property + :raw-value object)) + ts) + (when specific (throw 'exit ts)))))) + ;; Both timestamp types are found, + ;; move to next part. + (when (= (length ts) 2) (throw 'next ts))))) + ts))))) + (goto-char beg) + ;; First look for timestamps within headline. + (let ((ts (funcall find-ts (line-end-position) nil))) + (if (= (length ts) 2) (setq props (nconc ts props)) + ;; Then find timestamps in the section, skipping + ;; planning line. + (let ((end (save-excursion (outline-next-heading)))) + (forward-line) + (when (looking-at-p org-planning-line-re) (forward-line)) + (setq props (nconc (funcall find-ts end ts) props)))))))) + ;; Get the standard properties, like :PROP:. + (when (memq which '(nil all standard)) + ;; If we are looking after a specific property, delegate + ;; to `org-entry-get', which is faster. However, make an + ;; exception for "CATEGORY", since it can be also set + ;; through keywords (i.e. #+CATEGORY). + (if (and specific (not (equal specific "CATEGORY"))) + (let ((value (org-entry-get beg specific nil t))) + (throw 'exit (and value (list (cons specific value))))) + (let ((range (org-get-property-block beg))) + (when range + (let ((end (cdr range)) seen-base) + (goto-char (car range)) + ;; Unlike to `org--update-property-plist', we + ;; handle the case where base values is found + ;; after its extension. We also forbid standard + ;; properties to be named as special properties. + (while (re-search-forward org-property-re end t) + (let* ((key (upcase (match-string-no-properties 2))) + (extendp (string-match-p "\\+\\'" key)) + (key-base (if extendp (substring key 0 -1) key)) + (value (match-string-no-properties 3))) + (cond + ((member-ignore-case key-base org-special-properties)) + (extendp + (setq props + (org--update-property-plist key value props))) + ((member key seen-base)) + (t (push key seen-base) + (let ((p (assoc-string key props t))) + (if p (setcdr p (concat value " " (cdr p))) + (push (cons key value) props)))))))))))) + (unless (assoc "CATEGORY" props) + (push (cons "CATEGORY" (org-get-category beg)) props) + (when (string= specific "CATEGORY") (throw 'exit props))) + ;; Return value. + props))))) + +(defun org--property-local-values (property literal-nil) + "Return value for PROPERTY in current entry. +Value is a list whose car is the base value for PROPERTY and cdr +a list of accumulated values. Return nil if neither is found in +the entry. Also return nil when PROPERTY is set to \"nil\", +unless LITERAL-NIL is non-nil." + (let ((range (org-get-property-block))) + (when range + (goto-char (car range)) + (let* ((case-fold-search t) + (end (cdr range)) + (value + ;; Base value. + (save-excursion + (let ((v (and (re-search-forward + (org-re-property property nil t) end t) + (match-string-no-properties 3)))) + (list (if literal-nil v (org-not-nil v))))))) + ;; Find additional values. + (let* ((property+ (org-re-property (concat property "+") nil t))) + (while (re-search-forward property+ end t) + (push (match-string-no-properties 3) value))) + ;; Return final values. + (and (not (equal value '(nil))) (nreverse value)))))) + +(defun org--property-global-value (property literal-nil) + "Return value for PROPERTY in current buffer. +Return value is a string. Return nil if property is not set +globally. Also return nil when PROPERTY is set to \"nil\", +unless LITERAL-NIL is non-nil." + (let ((global + (cdr (or (assoc-string property org-file-properties t) + (assoc-string property org-global-properties t) + (assoc-string property org-global-properties-fixed t))))) + (if literal-nil global (org-not-nil global)))) (defun org-entry-get (pom property &optional inherit literal-nil) "Get value of PROPERTY for entry or content at point-or-marker POM. -If INHERIT is non-nil and the entry does not have the property, -then also check higher levels of the hierarchy. -If INHERIT is the symbol `selective', use inheritance only if the setting -in `org-use-property-inheritance' selects PROPERTY for inheritance. -If the property is present but empty, the return value is the empty string. -If the property is not present at all, nil is returned. - -Return the value as a string. -If LITERAL-NIL is set, return the string value \"nil\" as a string, -do not interpret it as the list atom nil. This is used for inheritance -when a \"nil\" value can supersede a non-nil value higher up the hierarchy." +If INHERIT is non-nil and the entry does not have the property, +then also check higher levels of the hierarchy. If INHERIT is +the symbol `selective', use inheritance only if the setting in +`org-use-property-inheritance' selects PROPERTY for inheritance. + +If the property is present but empty, the return value is the +empty string. If the property is not present at all, nil is +returned. In any other case, return the value as a string. +Search is case-insensitive. + +If LITERAL-NIL is set, return the string value \"nil\" as +a string, do not interpret it as the list atom nil. This is used +for inheritance when a \"nil\" value can supersede a non-nil +value higher up the hierarchy." (org-with-point-at pom - (if (and inherit (if (eq inherit 'selective) - (org-property-inherit-p property) - t)) - (org-entry-get-with-inheritance property literal-nil) - (if (member property org-special-properties) - ;; We need a special property. Use `org-entry-properties' - ;; to retrieve it, but specify the wanted property - (cdr (assoc property (org-entry-properties nil 'special property))) - (org-with-wide-buffer - (let ((range (org-get-property-block))) - (when (and range (not (eq (car range) (cdr range))) - (save-excursion - (goto-char (car range)) - (re-search-forward - (concat (org-re-property property) "\\|" - (org-re-property (concat property "+"))) - (cdr range) t))) - (let* ((props - (list (or (assoc property org-file-properties) - (assoc property org-global-properties) - (assoc property org-global-properties-fixed)))) - (ap (lambda (key) - (when (re-search-forward - (org-re-property key) (cdr range) t) - (setq props - (org-update-property-plist - key - (if (match-end 3) - (org-match-string-no-properties 3) "") - props))))) - val) - (goto-char (car range)) - (funcall ap property) - (goto-char (car range)) - (while (funcall ap (concat property "+"))) - (setq val (cdr (assoc property props))) - (when val (if literal-nil val (org-not-nil val))))))))))) + (cond + ((member-ignore-case property (cons "CATEGORY" org-special-properties)) + ;; We need a special property. Use `org-entry-properties' to + ;; retrieve it, but specify the wanted property. + (cdr (assoc-string property (org-entry-properties nil property)))) + ((and inherit + (or (not (eq inherit 'selective)) (org-property-inherit-p property))) + (org-entry-get-with-inheritance property literal-nil)) + (t + (let* ((local (org--property-local-values property literal-nil)) + (value (and local (mapconcat #'identity (delq nil local) " ")))) + (if literal-nil value (org-not-nil value))))))) (defun org-property-or-variable-value (var &optional inherit) "Check if there is a property fixing the value of VAR. @@ -15430,70 +15829,67 @@ If yes, return this value. If not, return the current value of the variable." (read prop) (symbol-value var)))) -(defun org-entry-delete (pom property &optional delete-empty-drawer) - "Delete the property PROPERTY from entry at point-or-marker POM. -When optional argument DELETE-EMPTY-DRAWER is a string, it defines -an empty drawer to delete." +(defun org-entry-delete (pom property) + "Delete PROPERTY from entry at point-or-marker POM. +Accumulated properties, i.e. PROPERTY+, are also removed. Return +non-nil when a property was removed." (org-with-point-at pom - (if (member property org-special-properties) - nil ; cannot delete these properties. - (let ((range (org-get-property-block))) - (if (and range - (goto-char (car range)) - (re-search-forward - (org-re-property property nil t) - (cdr range) t)) - (progn - (delete-region (match-beginning 0) (1+ (point-at-eol))) - (and delete-empty-drawer - (org-remove-empty-drawer-at - delete-empty-drawer (car range))) - t) - nil))))) + (pcase (org-get-property-block) + (`(,begin . ,origin) + (let* ((end (copy-marker origin)) + (re (org-re-property + (concat (regexp-quote property) "\\+?") t t))) + (goto-char begin) + (while (re-search-forward re end t) + (delete-region (match-beginning 0) (line-beginning-position 2))) + ;; If drawer is empty, remove it altogether. + (when (= begin end) + (delete-region (line-beginning-position 0) + (line-beginning-position 2))) + ;; Return non-nil if some property was removed. + (prog1 (/= end origin) (set-marker end nil)))) + (_ nil)))) ;; Multi-values properties are properties that contain multiple values ;; These values are assumed to be single words, separated by whitespace. (defun org-entry-add-to-multivalued-property (pom property value) "Add VALUE to the words in the PROPERTY in entry at point-or-marker POM." (let* ((old (org-entry-get pom property)) - (values (and old (org-split-string old "[ \t]")))) + (values (and old (split-string old)))) (setq value (org-entry-protect-space value)) (unless (member value values) (setq values (append values (list value))) - (org-entry-put pom property - (mapconcat 'identity values " "))))) + (org-entry-put pom property (mapconcat #'identity values " "))))) (defun org-entry-remove-from-multivalued-property (pom property value) "Remove VALUE from words in the PROPERTY in entry at point-or-marker POM." (let* ((old (org-entry-get pom property)) - (values (and old (org-split-string old "[ \t]")))) + (values (and old (split-string old)))) (setq value (org-entry-protect-space value)) (when (member value values) (setq values (delete value values)) - (org-entry-put pom property - (mapconcat 'identity values " "))))) + (org-entry-put pom property (mapconcat #'identity values " "))))) (defun org-entry-member-in-multivalued-property (pom property value) "Is VALUE one of the words in the PROPERTY in entry at point-or-marker POM?" (let* ((old (org-entry-get pom property)) - (values (and old (org-split-string old "[ \t]")))) + (values (and old (split-string old)))) (setq value (org-entry-protect-space value)) (member value values))) (defun org-entry-get-multivalued-property (pom property) "Return a list of values in a multivalued property." (let* ((value (org-entry-get pom property)) - (values (and value (org-split-string value "[ \t]")))) - (mapcar 'org-entry-restore-space values))) + (values (and value (split-string value)))) + (mapcar #'org-entry-restore-space values))) (defun org-entry-put-multivalued-property (pom property &rest values) "Set multivalued PROPERTY at point-or-marker POM to VALUES. VALUES should be a list of strings. Spaces will be protected." - (org-entry-put pom property - (mapconcat 'org-entry-protect-space values " ")) + (org-entry-put pom property (mapconcat #'org-entry-protect-space values " ")) (let* ((value (org-entry-get pom property)) - (values (and value (org-split-string value "[ \t]")))) - (mapcar 'org-entry-restore-space values))) + (values (and value (split-string value)))) + (mapcar #'org-entry-restore-space values))) (defun org-entry-protect-space (s) "Protect spaces and newline in string S." @@ -15526,24 +15922,29 @@ If the value found is \"nil\", return nil to show that the property should be considered as undefined (this is the meaning of nil here). However, if LITERAL-NIL is set, return the string value \"nil\" instead." (move-marker org-entry-property-inherited-from nil) - (let (tmp) - (save-excursion - (save-restriction - (widen) - (catch 'ex - (while t - (when (setq tmp (org-entry-get nil property nil literal-nil)) - (or (ignore-errors (org-back-to-heading t)) - (goto-char (point-min))) - (move-marker org-entry-property-inherited-from (point)) - (throw 'ex tmp)) - (or (ignore-errors (org-up-heading-safe)) - (throw 'ex nil)))))) - (setq tmp (or tmp - (cdr (assoc property org-file-properties)) - (cdr (assoc property org-global-properties)) - (cdr (assoc property org-global-properties-fixed)))) - (if literal-nil tmp (org-not-nil tmp)))) + (org-with-wide-buffer + (let (value) + (catch 'exit + (while t + (let ((v (org--property-local-values property literal-nil))) + (when v + (setq value + (concat (mapconcat #'identity (delq nil v) " ") + (and value " ") + value))) + (cond + ((car v) + (org-back-to-heading t) + (move-marker org-entry-property-inherited-from (point)) + (throw 'exit nil)) + ((org-up-heading-safe)) + (t + (let ((global (org--property-global-value property literal-nil))) + (cond ((not global)) + (value (setq value (concat global " " value))) + (t (setq value global)))) + (throw 'exit nil)))))) + (if literal-nil value (org-not-nil value))))) (defvar org-property-changed-functions nil "Hook called when the value of a property has changed. @@ -15552,177 +15953,190 @@ and the new value.") (defun org-entry-put (pom property value) "Set PROPERTY to VALUE for entry at point-or-marker POM. -If the value is nil, it is converted to the empty string. -If it is not a string, an error is raised." + +If the value is nil, it is converted to the empty string. If it +is not a string, an error is raised. Also raise an error on +invalid property names. + +PROPERTY can be any regular property (see +`org-special-properties'). It can also be \"TODO\", +\"PRIORITY\", \"SCHEDULED\" and \"DEADLINE\". + +For the last two properties, VALUE may have any of the special +values \"earlier\" and \"later\". The function then increases or +decreases scheduled or deadline date by one day." (cond ((null value) (setq value "")) - ((not (stringp value)) - (error "Properties values should be strings."))) + ((not (stringp value)) (error "Properties values should be strings")) + ((not (org--valid-property-p property)) + (user-error "Invalid property name: \"%s\"" property))) (org-with-point-at pom - (org-back-to-heading t) - (let ((beg (point)) (end (save-excursion (outline-next-heading) (point))) - range) + (if (or (not (featurep 'org-inlinetask)) (org-inlinetask-in-task-p)) + (org-back-to-heading t) + (org-with-limited-levels (org-back-to-heading t))) + (let ((beg (point))) (cond ((equal property "TODO") - (when (and (string-match "\\S-" value) - (not (member value org-todo-keywords-1))) - (user-error "\"%s\" is not a valid TODO state" value)) - (if (or (not value) - (not (string-match "\\S-" value))) - (setq value 'none)) + (cond ((not (org-string-nw-p value)) (setq value 'none)) + ((not (member value org-todo-keywords-1)) + (user-error "\"%s\" is not a valid TODO state" value))) (org-todo value) (org-set-tags nil 'align)) ((equal property "PRIORITY") - (org-priority (if (and value (string-match "\\S-" value)) - (string-to-char value) ?\ )) + (org-priority (if (org-string-nw-p value) (string-to-char value) ?\s)) (org-set-tags nil 'align)) - ((equal property "CLOCKSUM") - (if (not (re-search-forward - (concat org-clock-string ".*\\]--\\(\\[[^]]+\\]\\)") nil t)) - (error "Cannot find a clock log") - (goto-char (- (match-end 1) 2)) - (cond - ((eq value 'earlier) (org-timestamp-down)) - ((eq value 'later) (org-timestamp-up))) - (org-clock-sum-current-item))) ((equal property "SCHEDULED") - (if (re-search-forward org-scheduled-time-regexp end t) - (cond - ((eq value 'earlier) (org-timestamp-change -1 'day)) - ((eq value 'later) (org-timestamp-change 1 'day)) - (t (call-interactively 'org-schedule))) - (call-interactively 'org-schedule))) + (forward-line) + (if (and (looking-at-p org-planning-line-re) + (re-search-forward + org-scheduled-time-regexp (line-end-position) t)) + (cond ((string= value "earlier") (org-timestamp-change -1 'day)) + ((string= value "later") (org-timestamp-change 1 'day)) + ((string= value "") (org-schedule '(4))) + (t (org-schedule nil value))) + (if (member value '("earlier" "later" "")) + (call-interactively #'org-schedule) + (org-schedule nil value)))) ((equal property "DEADLINE") - (if (re-search-forward org-deadline-time-regexp end t) - (cond - ((eq value 'earlier) (org-timestamp-change -1 'day)) - ((eq value 'later) (org-timestamp-change 1 'day)) - (t (call-interactively 'org-deadline))) - (call-interactively 'org-deadline))) + (forward-line) + (if (and (looking-at-p org-planning-line-re) + (re-search-forward + org-deadline-time-regexp (line-end-position) t)) + (cond ((string= value "earlier") (org-timestamp-change -1 'day)) + ((string= value "later") (org-timestamp-change 1 'day)) + ((string= value "") (org-deadline '(4))) + (t (org-deadline nil value))) + (if (member value '("earlier" "later" "")) + (call-interactively #'org-deadline) + (org-deadline nil value)))) ((member property org-special-properties) - (error "The %s property can not yet be set with `org-entry-put'" - property)) - (t ; a non-special property - (let ((buffer-invisibility-spec (org-inhibit-invisibility))) ; Emacs 21 - (setq range (org-get-property-block beg end 'force)) + (error "The %s property cannot be set with `org-entry-put'" property)) + (t + (let* ((range (org-get-property-block beg 'force)) + (end (cdr range)) + (case-fold-search t)) (goto-char (car range)) - (if (re-search-forward - (org-re-property property nil t) (cdr range) t) - (progn - (delete-region (match-beginning 0) (match-end 0)) - (goto-char (match-beginning 0))) - (goto-char (cdr range)) + (if (re-search-forward (org-re-property property nil t) end t) + (progn (delete-region (match-beginning 0) (match-end 0)) + (goto-char (match-beginning 0))) + (goto-char end) (insert "\n") - (backward-char 1) - (org-indent-line)) + (backward-char)) (insert ":" property ":") - (and value (insert " " value)) + (when value (insert " " value)) (org-indent-line))))) (run-hook-with-args 'org-property-changed-functions property value))) -(defun org-buffer-property-keys (&optional include-specials include-defaults include-columns) +(defun org-buffer-property-keys + (&optional specials defaults columns ignore-malformed) "Get all property keys in the current buffer. -With INCLUDE-SPECIALS, also list the special properties that reflect things -like tags and TODO state. -With INCLUDE-DEFAULTS, also include properties that has special meaning -internally: ARCHIVE, CATEGORY, SUMMARY, DESCRIPTION, LOCATION, and LOGGING -and others. -With INCLUDE-COLUMNS, also include property names given in COLUMN -formats in the current buffer." - (let (rtn range cfmt s p) - (save-excursion - (save-restriction - (widen) - (goto-char (point-min)) - (while (re-search-forward org-property-start-re nil t) - (setq range (org-get-property-block)) - (goto-char (car range)) - (while (re-search-forward org-property-re - (cdr range) t) - (add-to-list 'rtn (org-match-string-no-properties 2))) - (outline-next-heading)))) - (when include-specials - (setq rtn (append org-special-properties rtn))) +When SPECIALS is non-nil, also list the special properties that +reflect things like tags and TODO state. - (when include-defaults - (mapc (lambda (x) (add-to-list 'rtn x)) org-default-properties) - (add-to-list 'rtn org-effort-property)) +When DEFAULTS is non-nil, also include properties that has +special meaning internally: ARCHIVE, CATEGORY, SUMMARY, +DESCRIPTION, LOCATION, and LOGGING and others. - (when include-columns - (save-excursion - (save-restriction - (widen) - (goto-char (point-min)) - (while (re-search-forward - "^\\(#\\+COLUMNS:\\|[ \t]*:COLUMNS:\\)[ \t]*\\(.*\\)" - nil t) - (setq cfmt (match-string 2) s 0) - (while (string-match (org-re "%[0-9]*\\([-[:alnum:]_]+\\)") - cfmt s) - (setq s (match-end 0) - p (match-string 1 cfmt)) - (unless (or (equal p "ITEM") - (member p org-special-properties)) - (add-to-list 'rtn (match-string 1 cfmt)))))))) - - (sort rtn (lambda (a b) (string< (upcase a) (upcase b)))))) +When COLUMNS in non-nil, also include property names given in +COLUMN formats in the current buffer. + +When IGNORE-MALFORMED is non-nil, malformed drawer repair will not be +automatically performed, such drawers will be silently ignored." + (let ((case-fold-search t) + (props (append + (and specials org-special-properties) + (and defaults (cons org-effort-property org-default-properties)) + nil))) + (org-with-wide-buffer + (goto-char (point-min)) + (while (re-search-forward org-property-start-re nil t) + (let ((range (org-get-property-block))) + (catch 'skip + (unless range + (when (and (not ignore-malformed) + (not (org-before-first-heading-p)) + (y-or-n-p (format "Malformed drawer at %d, repair?" + (line-beginning-position)))) + (org-get-property-block nil t)) + (throw 'skip nil)) + (goto-char (car range)) + (let ((begin (car range)) + (end (cdr range))) + ;; Make sure that found property block is not located + ;; before current point, as it would generate an infloop. + ;; It can happen, for example, in the following + ;; situation: + ;; + ;; * Headline + ;; :PROPERTIES: + ;; ... + ;; :END: + ;; *************** Inlinetask + ;; #+BEGIN_EXAMPLE + ;; :PROPERTIES: + ;; #+END_EXAMPLE + ;; + (if (< begin (point)) (throw 'skip nil) (goto-char begin)) + (while (< (point) end) + (let ((p (progn (looking-at org-property-re) + (match-string-no-properties 2)))) + ;; Only add true property name, not extension symbol. + (push (if (not (string-match-p "\\+\\'" p)) p + (substring p 0 -1)) + props)) + (forward-line)))) + (outline-next-heading))) + (when columns + (goto-char (point-min)) + (while (re-search-forward "^[ \t]*\\(?:#\\+\\|:\\)COLUMNS:" nil t) + (let ((element (org-element-at-point))) + (when (memq (org-element-type element) '(keyword node-property)) + (let ((value (org-element-property :value element)) + (start 0)) + (while (string-match "%[0-9]*\\([[:alnum:]_-]+\\)\\(([^)]+)\\)?\ +\\(?:{[^}]+}\\)?" + value start) + (setq start (match-end 0)) + (let ((p (match-string-no-properties 1 value))) + (unless (member-ignore-case p org-special-properties) + (push p props)))))))))) + (sort (delete-dups props) (lambda (a b) (string< (upcase a) (upcase b)))))) (defun org-property-values (key) - "Return a list of all values of property KEY in the current buffer." - (save-excursion - (save-restriction - (widen) - (goto-char (point-min)) - (let ((re (org-re-property key)) - values) - (while (re-search-forward re nil t) - (add-to-list 'values (org-trim (match-string 3)))) - (delete "" values))))) + "List all non-nil values of property KEY in current buffer." + (org-with-wide-buffer + (goto-char (point-min)) + (let ((case-fold-search t) + (re (org-re-property key)) + values) + (while (re-search-forward re nil t) + (push (org-entry-get (point) key) values)) + (delete-dups values)))) (defun org-insert-property-drawer () "Insert a property drawer into the current entry." - (org-back-to-heading t) - (looking-at org-outline-regexp) - (let ((indent (if org-adapt-indentation - (- (match-end 0) (match-beginning 0)) - 0)) - (beg (point)) - (re (concat "^[ \t]*" org-keyword-time-regexp)) - end hiddenp) - (outline-next-heading) - (setq end (point)) - (goto-char beg) - (while (re-search-forward re end t)) - (setq hiddenp (outline-invisible-p)) - (end-of-line 1) - (and (equal (char-after) ?\n) (forward-char 1)) - (while (looking-at "^[ \t]*\\(:CLOCK:\\|:LOGBOOK:\\|CLOCK:\\|:END:\\)") - (if (member (match-string 1) '("CLOCK:" ":END:")) - ;; just skip this line - (beginning-of-line 2) - ;; Drawer start, find the end - (re-search-forward "^\\*+ \\|^[ \t]*:END:" nil t) - (beginning-of-line 1))) - (org-skip-over-state-notes) - (skip-chars-backward " \t\n\r") - (if (and (eq (char-before) ?*) (not (eq (char-after) ?\n))) - (forward-char 1)) - (goto-char (point-at-eol)) - (let ((inhibit-read-only t)) (insert "\n:PROPERTIES:\n:END:")) - (beginning-of-line 0) - (org-indent-to-column indent) - (beginning-of-line 2) - (org-indent-to-column indent) - (beginning-of-line 0) - (if hiddenp - (save-excursion - (org-back-to-heading t) - (hide-entry)) - (org-flag-drawer t)))) + (org-with-wide-buffer + (if (or (not (featurep 'org-inlinetask)) (org-inlinetask-in-task-p)) + (org-back-to-heading t) + (org-with-limited-levels (org-back-to-heading t))) + (forward-line) + (when (looking-at-p org-planning-line-re) (forward-line)) + (unless (looking-at-p org-property-drawer-re) + ;; Make sure we start editing a line from current entry, not from + ;; next one. It prevents extending text properties or overlays + ;; belonging to the latter. + (when (bolp) (backward-char)) + (let ((begin (1+ (point))) + (inhibit-read-only t)) + (insert "\n:PROPERTIES:\n:END:") + (when (eobp) (insert "\n")) + (org-indent-region begin (point)))))) (defun org-insert-drawer (&optional arg drawer) "Insert a drawer at point. +When optional argument ARG is non-nil, insert a property drawer. + Optional argument DRAWER, when non-nil, is a string representing drawer's name. Otherwise, the user is prompted for a name. @@ -15731,23 +16145,14 @@ instead. Point is left between drawer's boundaries." (interactive "P") - (let* ((logbook (if (stringp org-log-into-drawer) org-log-into-drawer - "LOGBOOK")) - ;; SYSTEM-DRAWERS is a list of drawer names that are used - ;; internally by Org. They are meant to be inserted - ;; automatically. - (system-drawers `("CLOCK" ,logbook "PROPERTIES")) - ;; Remove system drawers from list. Note: For some reason, - ;; `org-completing-read' ignores the predicate while - ;; `completing-read' handles it fine. - (drawer (if arg "PROPERTIES" - (or drawer - (completing-read - "Drawer: " org-drawers - (lambda (d) (not (member d system-drawers)))))))) + (let* ((drawer (if arg "PROPERTIES" + (or drawer (read-from-minibuffer "Drawer: "))))) (cond ;; With C-u, fall back on `org-insert-property-drawer' (arg (org-insert-property-drawer)) + ;; Check validity of suggested drawer's name. + ((not (string-match-p org-drawer-regexp (format ":%s:" drawer))) + (user-error "Invalid drawer name")) ;; With an active region, insert a drawer at point. ((not (org-region-active-p)) (progn @@ -15813,38 +16218,25 @@ This is computed according to `org-property-set-functions-alist'." (funcall set-function prompt allowed nil (not (get-text-property 0 'org-unrestricted (caar allowed)))) - (let (org-completion-use-ido org-completion-use-iswitchb) - (funcall set-function prompt - (mapcar 'list (org-property-values property)) - nil nil "" nil cur))))) + (funcall set-function prompt + (mapcar 'list (org-property-values property)) + nil nil "" nil cur)))) (org-trim val))) (defvar org-last-set-property nil) (defvar org-last-set-property-value nil) (defun org-read-property-name () "Read a property name." - (let* ((completion-ignore-case t) - (keys (org-buffer-property-keys nil t t)) - (default-prop (or (save-excursion - (save-match-data - (beginning-of-line) - (and (looking-at "^\\s-*:\\([^:\n]+\\):") - (null (string= (match-string 1) "END")) - (match-string 1)))) - org-last-set-property)) - (property (org-icompleting-read - (concat "Property" - (if default-prop (concat " [" default-prop "]") "") - ": ") - (mapcar 'list keys) - nil nil nil nil - default-prop))) - (if (member property keys) - property - (or (cdr (assoc (downcase property) - (mapcar (lambda (x) (cons (downcase x) x)) - keys))) - property)))) + (let ((completion-ignore-case t) + (default-prop (or (and (org-at-property-p) + (match-string-no-properties 2)) + org-last-set-property))) + (org-completing-read + (concat "Property" + (if default-prop (concat " [" default-prop "]") "") + ": ") + (mapcar #'list (org-buffer-property-keys nil t t)) + nil nil nil nil default-prop))) (defun org-set-property-and-value (use-last) "Allow to set [PROPERTY]: [value] direction from prompt. @@ -15865,26 +16257,52 @@ When use-default, don't even ask, just use the last (defun org-set-property (property value) "In the current entry, set PROPERTY to VALUE. + When called interactively, this will prompt for a property name, offering completion on existing and default properties. And then it will prompt for a value, offering completion either on allowed values (via an inherited xxx_ALL property) or on existing values in other instances of this property -in the current file." +in the current file. + +Throw an error when trying to set a property with an invalid name." (interactive (list nil nil)) - (let* ((property (or property (org-read-property-name))) - (value (or value (org-read-property-value property))) - (fn (cdr (assoc property org-properties-postprocess-alist)))) - (setq org-last-set-property property) - (setq org-last-set-property-value (concat property ": " value)) - ;; Possibly postprocess the inserted value: - (when fn (setq value (funcall fn value))) - (unless (equal (org-entry-get nil property) value) - (org-entry-put nil property value)))) - -(defun org-delete-property (property &optional delete-empty-drawer) - "In the current entry, delete PROPERTY. -When optional argument DELETE-EMPTY-DRAWER is a string, it defines -an empty drawer to delete." + (let ((property (or property (org-read-property-name)))) + ;; `org-entry-put' also makes the following check, but this one + ;; avoids polluting `org-last-set-property' and + ;; `org-last-set-property-value' needlessly. + (unless (org--valid-property-p property) + (user-error "Invalid property name: \"%s\"" property)) + (let ((value (or value (org-read-property-value property))) + (fn (cdr (assoc-string property org-properties-postprocess-alist t)))) + (setq org-last-set-property property) + (setq org-last-set-property-value (concat property ": " value)) + ;; Possibly postprocess the inserted value: + (when fn (setq value (funcall fn value))) + (unless (equal (org-entry-get nil property) value) + (org-entry-put nil property value))))) + +(defun org-find-property (property &optional value) + "Find first entry in buffer that sets PROPERTY. + +When optional argument VALUE is non-nil, only consider an entry +if it contains PROPERTY set to this value. If PROPERTY should be +explicitly set to nil, use string \"nil\" for VALUE. + +Return position where the entry begins, or nil if there is no +such entry. If narrowing is in effect, only search the visible +part of the buffer." + (save-excursion + (goto-char (point-min)) + (let ((case-fold-search t) + (re (org-re-property property nil (not value) value))) + (catch 'exit + (while (re-search-forward re nil t) + (when (if value (org-at-property-p) + (org-entry-get (point) property nil t)) + (throw 'exit (progn (org-back-to-heading t) (point))))))))) + +(defun org-delete-property (property) + "In the current entry, delete PROPERTY." (interactive (let* ((completion-ignore-case t) (cat (org-entry-get (point) "CATEGORY")) @@ -15892,33 +16310,30 @@ an empty drawer to delete." (props (if cat props0 (delete `("CATEGORY" . ,(org-get-category)) props0))) (prop (if (< 1 (length props)) - (org-icompleting-read "Property: " props nil t) + (completing-read "Property: " props nil t) (caar props)))) (list prop))) (if (not property) (message "No property to delete in this entry") - (org-entry-delete nil property delete-empty-drawer) + (org-entry-delete nil property) (message "Property \"%s\" deleted" property))) (defun org-delete-property-globally (property) - "Remove PROPERTY globally, from all entries." + "Remove PROPERTY globally, from all entries. +This function ignores narrowing, if any." (interactive (let* ((completion-ignore-case t) - (prop (org-icompleting-read + (prop (completing-read "Globally remove property: " - (mapcar 'list (org-buffer-property-keys))))) + (mapcar #'list (org-buffer-property-keys))))) (list prop))) - (save-excursion - (save-restriction - (widen) - (goto-char (point-min)) - (let ((cnt 0)) - (while (re-search-forward - (org-re-property property) - nil t) - (setq cnt (1+ cnt)) - (delete-region (match-beginning 0) (1+ (point-at-eol)))) - (message "Property \"%s\" removed from %d entries" property cnt))))) + (org-with-wide-buffer + (goto-char (point-min)) + (let ((count 0) + (re (org-re-property (concat (regexp-quote property) "\\+?") t t))) + (while (re-search-forward re nil t) + (when (org-entry-delete (point) property) (cl-incf count))) + (message "Property \"%s\" removed from %d entries" property count)))) (defvar org-columns-current-fmt-compiled) ; defined in org-colview.el @@ -15929,9 +16344,9 @@ then applies it to the property in the column format's scope." (interactive) (unless (org-at-property-p) (user-error "Not at a property")) - (let ((prop (org-match-string-no-properties 2))) + (let ((prop (match-string-no-properties 2))) (org-columns-get-format-and-top-level) - (unless (nth 3 (assoc prop org-columns-current-fmt-compiled)) + (unless (nth 3 (assoc-string prop org-columns-current-fmt-compiled t)) (user-error "No operator defined for property %s" prop)) (org-columns-compute prop))) @@ -15958,6 +16373,7 @@ completion." (while (>= n org-highest-priority) (push (char-to-string n) vals) (setq n (1- n))))) + ((equal property "CATEGORY")) ((member property org-special-properties)) ((setq vals (run-hook-with-args-until-success 'org-property-allowed-value-functions property))) @@ -15976,7 +16392,7 @@ completion." (org-add-props (car vals) '(org-unrestricted t))) (if table (mapcar 'list vals) vals))) -(defun org-property-previous-allowed-value (&optional previous) +(defun org-property-previous-allowed-value (&optional _previous) "Switch to the next allowed value for this property." (interactive) (org-property-next-allowed-value t)) @@ -15996,21 +16412,22 @@ completion." nval) (unless allowed (user-error "Allowed values for this property have not been defined")) - (if previous (setq allowed (reverse allowed))) - (if (member value allowed) - (setq nval (car (cdr (member value allowed))))) + (when previous (setq allowed (reverse allowed))) + (when (member value allowed) + (setq nval (car (cdr (member value allowed))))) (setq nval (or nval (car allowed))) - (if (equal nval value) - (user-error "Only one allowed value for this property")) + (when (equal nval value) + (user-error "Only one allowed value for this property")) (org-at-property-p) (replace-match (concat " :" key ": " nval) t t) (org-indent-line) (beginning-of-line 1) (skip-chars-forward " \t") (when (equal prop org-effort-property) - (save-excursion - (org-back-to-heading t) - (put-text-property (point-at-bol) (point-at-eol) 'org-effort nval)) + (org-refresh-property + '((effort . identity) + (effort-minutes . org-duration-to-minutes)) + nval) (when (string= org-clock-current-task heading) (setq org-clock-effort nval) (org-clock-update-mode-line))) @@ -16035,31 +16452,30 @@ only headings." (level 1) (lmin 1) (lmax 1) - limit re end found pos heading cnt flevel) + end found flevel) (unless buffer (error "File not found :%s" file)) (with-current-buffer buffer - (save-excursion - (save-restriction - (widen) - (setq limit (point-max)) - (goto-char (point-min)) - (dolist (heading path) - (setq re (format org-complex-heading-regexp-format - (regexp-quote heading))) - (setq cnt 0 pos (point)) - (while (re-search-forward re end t) - (setq level (- (match-end 1) (match-beginning 1))) - (if (and (>= level lmin) (<= level lmax)) - (setq found (match-beginning 0) flevel level cnt (1+ cnt)))) - (when (= cnt 0) (error "Heading not found on level %d: %s" - lmax heading)) - (when (> cnt 1) (error "Heading not unique on level %d: %s" - lmax heading)) - (goto-char found) - (setq lmin (1+ flevel) lmax (+ lmin (if org-odd-levels-only 1 0))) - (setq end (save-excursion (org-end-of-subtree t t)))) - (when (org-at-heading-p) - (point-marker))))))) + (unless (derived-mode-p 'org-mode) + (error "Buffer %s needs to be in Org mode" buffer)) + (org-with-wide-buffer + (goto-char (point-min)) + (dolist (heading path) + (let ((re (format org-complex-heading-regexp-format + (regexp-quote heading))) + (cnt 0)) + (while (re-search-forward re end t) + (setq level (- (match-end 1) (match-beginning 1))) + (when (and (>= level lmin) (<= level lmax)) + (setq found (match-beginning 0) flevel level cnt (1+ cnt)))) + (when (= cnt 0) + (error "Heading not found on level %d: %s" lmax heading)) + (when (> cnt 1) + (error "Heading not unique on level %d: %s" lmax heading)) + (goto-char found) + (setq lmin (1+ flevel) lmax (+ lmin (if org-odd-levels-only 1 0))) + (setq end (save-excursion (org-end-of-subtree t t))))) + (when (org-at-heading-p) + (point-marker)))))) (defun org-find-exact-headline-in-buffer (heading &optional buffer pos-only) "Find node HEADING in BUFFER. @@ -16069,24 +16485,22 @@ If POS-ONLY is set, return just the position instead of a marker. The heading text must match exact, but it may have a TODO keyword, a priority cookie and tags in the standard locations." (with-current-buffer (or buffer (current-buffer)) - (save-excursion - (save-restriction - (widen) - (goto-char (point-min)) - (let (case-fold-search) - (if (re-search-forward - (format org-complex-heading-regexp-format - (regexp-quote heading)) nil t) - (if pos-only - (match-beginning 0) - (move-marker (make-marker) (match-beginning 0))))))))) + (org-with-wide-buffer + (goto-char (point-min)) + (let (case-fold-search) + (when (re-search-forward + (format org-complex-heading-regexp-format + (regexp-quote heading)) nil t) + (if pos-only + (match-beginning 0) + (move-marker (make-marker) (match-beginning 0)))))))) (defun org-find-exact-heading-in-directory (heading &optional dir) "Find Org node headline HEADING in all .org files in directory DIR. When the target headline is found, return a marker to this location." (let ((files (directory-files (or dir default-directory) - nil "\\`[^.#].*\\.org\\'")) - file visiting m buffer) + t "\\`[^.#].*\\.org\\'")) + visiting m buffer) (catch 'found (dolist (file files) (message "trying %s" file) @@ -16105,40 +16519,29 @@ Return the position where this entry starts, or nil if there is no such entry." (interactive "sID: ") (let ((id (cond ((stringp ident) ident) - ((symbol-name ident) (symbol-name ident)) + ((symbolp ident) (symbol-name ident)) ((numberp ident) (number-to-string ident)) - (t (error "IDENT %s must be a string, symbol or number" ident)))) - (case-fold-search nil)) - (save-excursion - (save-restriction - (widen) - (goto-char (point-min)) - (when (re-search-forward - (concat "^[ \t]*:ID:[ \t]+" (regexp-quote id) "[ \t]*$") - nil t) - (org-back-to-heading t) - (point)))))) + (t (error "IDENT %s must be a string, symbol or number" ident))))) + (org-with-wide-buffer (org-find-property "ID" id)))) ;;;; Timestamps (defvar org-last-changed-timestamp nil) (defvar org-last-inserted-timestamp nil "The last time stamp inserted with `org-insert-time-stamp'.") -(defvar org-ts-what) ; dynamically scoped parameter (defun org-time-stamp (arg &optional inactive) "Prompt for a date/time and insert a time stamp. + If the user specifies a time like HH:MM or if this command is called with at least one prefix argument, the time stamp contains -the date and the time. Otherwise, only the date is be included. +the date and the time. Otherwise, only the date is included. -All parts of a date not specified by the user is filled in from -the current date/time. So if you just press return without -typing anything, the time stamp will represent the current -date/time. +All parts of a date not specified by the user are filled in from +the timestamp at point, if any, or the current date/time +otherwise. -If there is already a timestamp at the cursor, it will be -modified. +If there is already a timestamp at the cursor, it is replaced. With two universal prefix arguments, insert an active timestamp with the current time without prompting the user. @@ -16146,57 +16549,56 @@ with the current time without prompting the user. When called from lisp, the timestamp is inactive if INACTIVE is non-nil." (interactive "P") - (let* ((ts nil) - (default-time - ;; Default time is either today, or, when entering a range, - ;; the range start. - (if (or (and (org-at-timestamp-p t) (setq ts (match-string 0))) - (save-excursion - (re-search-backward - (concat org-ts-regexp "--?-?\\=") ; 1-3 minuses - (- (point) 20) t))) - (apply 'encode-time (org-parse-time-string (match-string 1))) - (current-time))) - (default-input (and ts (org-get-compact-tod ts))) - (repeater (save-excursion - (save-match-data - (beginning-of-line) - (when (re-search-forward - "\\([.+-]+[0-9]+[hdwmy] ?\\)+" ;;\\(?:[/ ][-+]?[0-9]+[hdwmy]\\)?\\) ?" - (save-excursion (progn (end-of-line) (point))) t) - (match-string 0))))) - org-time-was-given org-end-time-was-given time) + (let* ((ts (cond + ((org-at-date-range-p t) + (match-string (if (< (point) (- (match-beginning 2) 2)) 1 2))) + ((org-at-timestamp-p 'lax) (match-string 0)))) + ;; Default time is either the timestamp at point or today. + ;; When entering a range, only the range start is considered. + (default-time (if (not ts) (current-time) + (apply #'encode-time (org-parse-time-string ts)))) + (default-input (and ts (org-get-compact-tod ts))) + (repeater (and ts + (string-match "\\([.+-]+[0-9]+[hdwmy] ?\\)+" ts) + (match-string 0 ts))) + org-time-was-given + org-end-time-was-given + (time + (and (if (equal arg '(16)) (current-time) + ;; Preserve `this-command' and `last-command'. + (let ((this-command this-command) + (last-command last-command)) + (org-read-date + arg 'totime nil nil default-time default-input + inactive)))))) (cond - ((and (org-at-timestamp-p t) - (memq last-command '(org-time-stamp org-time-stamp-inactive)) - (memq this-command '(org-time-stamp org-time-stamp-inactive))) + ((and ts + (memq last-command '(org-time-stamp org-time-stamp-inactive)) + (memq this-command '(org-time-stamp org-time-stamp-inactive))) (insert "--") - (setq time (let ((this-command this-command)) - (org-read-date arg 'totime nil nil - default-time default-input inactive))) (org-insert-time-stamp time (or org-time-was-given arg) inactive)) - ((org-at-timestamp-p t) - (setq time (let ((this-command this-command)) - (org-read-date arg 'totime nil nil default-time default-input inactive))) - (when (org-at-timestamp-p t) ; just to get the match data - ; (setq inactive (eq (char-after (match-beginning 0)) ?\[)) - (replace-match "") + (ts + ;; Make sure we're on a timestamp. When in the middle of a date + ;; range, move arbitrarily to range end. + (unless (org-at-timestamp-p 'lax) + (skip-chars-forward "-") + (org-at-timestamp-p 'lax)) + (replace-match "") + (setq org-last-changed-timestamp + (org-insert-time-stamp + time (or org-time-was-given arg) + inactive nil nil (list org-end-time-was-given))) + (when repeater + (backward-char) + (insert " " repeater) (setq org-last-changed-timestamp - (org-insert-time-stamp - time (or org-time-was-given arg) - inactive nil nil (list org-end-time-was-given))) - (when repeater (goto-char (1- (point))) (insert " " repeater) - (setq org-last-changed-timestamp - (concat (substring org-last-inserted-timestamp 0 -1) - " " repeater ">")))) + (concat (substring org-last-inserted-timestamp 0 -1) + " " repeater ">"))) (message "Timestamp updated")) - ((equal arg '(16)) - (org-insert-time-stamp (current-time) t inactive)) - (t - (setq time (let ((this-command this-command)) - (org-read-date arg 'totime nil nil default-time default-input inactive))) - (org-insert-time-stamp time (or org-time-was-given arg) inactive - nil nil (list org-end-time-was-given)))))) + ((equal arg '(16)) (org-insert-time-stamp time t inactive)) + (t (org-insert-time-stamp + time (or org-time-was-given arg) inactive nil nil + (list org-end-time-was-given)))))) ;; FIXME: can we use this for something else, like computing time differences? (defun org-get-compact-tod (s) @@ -16211,7 +16613,7 @@ non-nil." (if (not t2) t1 (setq dh (- h2 h1) dm (- m2 m1)) - (if (< dm 0) (setq dm (+ dm 60) dh (1- dh))) + (when (< dm 0) (setq dm (+ dm 60) dh (1- dh))) (concat t1 "+" (number-to-string dh) (and (/= 0 dm) (format ":%02d" dm))))))) @@ -16226,7 +16628,7 @@ So these are more for recording a certain time/date." (defvar org-date-ovl (make-overlay 1 1)) (overlay-put org-date-ovl 'face 'org-date-selected) -(org-detach-overlay org-date-ovl) +(delete-overlay org-date-ovl) (defvar org-ans1) ; dynamically scoped parameter (defvar org-ans2) ; dynamically scoped parameter @@ -16243,13 +16645,14 @@ So these are more for recording a certain time/date." (defvar org-read-date-inactive) (defvar org-read-date-minibuffer-local-map - (let* ((org-replace-disputed-keys nil) - (map (make-sparse-keymap))) + (let* ((map (make-sparse-keymap))) (set-keymap-parent map minibuffer-local-map) (org-defkey map (kbd ".") (lambda () (interactive) ;; Are we at the beginning of the prompt? - (if (looking-back "^[^:]+: ") + (if (looking-back "^[^:]+: " + (let ((inhibit-field-text-motion t)) + (line-beginning-position))) (org-eval-in-calendar '(calendar-goto-today)) (insert ".")))) (org-defkey map (kbd "C-.") @@ -16316,7 +16719,8 @@ So these are more for recording a certain time/date." (defvar org-defdecode) (defvar org-with-time) -(defun org-read-date (&optional org-with-time to-time from-string prompt +(defvar calendar-setup) ; Dynamically scoped. +(defun org-read-date (&optional with-time to-time from-string prompt default-time default-input inactive) "Read a date, possibly a time, and make things smooth for the user. The prompt will suggest to enter an ISO date, but you can also enter anything @@ -16360,8 +16764,8 @@ If you don't like the calendar, turn it off with With optional argument TO-TIME, the date will immediately be converted to an internal time. -With an optional argument ORG-WITH-TIME, the prompt will suggest to -also insert a time. Note that when ORG-WITH-TIME is not set, you can +With an optional argument WITH-TIME, the prompt will suggest to +also insert a time. Note that when WITH-TIME is not set, you can still enter a time, and this function will inform the calling routine about this change. The calling routine may then choose to change the format used to insert the time stamp into the buffer to include the time. @@ -16370,75 +16774,90 @@ the user. PROMPT can overwrite the default prompt. DEFAULT-TIME is the time/date that is used for everything that is not specified by the user." (require 'parse-time) - (let* ((org-time-stamp-rounding-minutes - (if (equal org-with-time '(16)) '(0 0) org-time-stamp-rounding-minutes)) + (let* ((org-with-time with-time) + (org-time-stamp-rounding-minutes + (if (equal org-with-time '(16)) + '(0 0) + org-time-stamp-rounding-minutes)) (org-dcst org-display-custom-times) (ct (org-current-time)) (org-def (or org-overriding-default-time default-time ct)) (org-defdecode (decode-time org-def)) - (dummy (progn - (when (< (nth 2 org-defdecode) org-extend-today-until) - (setcar (nthcdr 2 org-defdecode) -1) - (setcar (nthcdr 1 org-defdecode) 59) - (setq org-def (apply 'encode-time org-defdecode) - org-defdecode (decode-time org-def))))) - (mouse-autoselect-window nil) ; Don't let the mouse jump - (calendar-frame-setup nil) - (calendar-setup nil) + (cur-frame (selected-frame)) + (mouse-autoselect-window nil) ; Don't let the mouse jump + (calendar-setup + (and (eq calendar-setup 'calendar-only) 'calendar-only)) (calendar-move-hook nil) (calendar-view-diary-initially-flag nil) (calendar-view-holidays-initially-flag nil) - (timestr (format-time-string - (if org-with-time "%Y-%m-%d %H:%M" "%Y-%m-%d") org-def)) - (prompt (concat (if prompt (concat prompt " ") "") - (format "Date+time [%s]: " timestr))) - ans (org-ans0 "") org-ans1 org-ans2 final) - - (cond - (from-string (setq ans from-string)) - (org-read-date-popup-calendar - (save-excursion - (save-window-excursion - (calendar) - (org-eval-in-calendar '(setq cursor-type nil) t) - (unwind-protect - (progn - (calendar-forward-day (- (time-to-days org-def) - (calendar-absolute-from-gregorian - (calendar-current-date)))) - (org-eval-in-calendar nil t) - (let* ((old-map (current-local-map)) - (map (copy-keymap calendar-mode-map)) - (minibuffer-local-map - (copy-keymap org-read-date-minibuffer-local-map))) - (org-defkey map (kbd "RET") 'org-calendar-select) - (org-defkey map [mouse-1] 'org-calendar-select-mouse) - (org-defkey map [mouse-2] 'org-calendar-select-mouse) - (unwind-protect - (progn - (use-local-map map) - (setq org-read-date-inactive inactive) - (add-hook 'post-command-hook 'org-read-date-display) - (setq org-ans0 (read-string prompt default-input - 'org-read-date-history nil)) - ;; org-ans0: from prompt - ;; org-ans1: from mouse click - ;; org-ans2: from calendar motion - (setq ans (concat org-ans0 " " (or org-ans1 org-ans2)))) - (remove-hook 'post-command-hook 'org-read-date-display) - (use-local-map old-map) - (when org-read-date-overlay - (delete-overlay org-read-date-overlay) - (setq org-read-date-overlay nil))))) - (bury-buffer "*Calendar*"))))) - - (t ; Naked prompt only - (unwind-protect - (setq ans (read-string prompt default-input - 'org-read-date-history timestr)) - (when org-read-date-overlay - (delete-overlay org-read-date-overlay) - (setq org-read-date-overlay nil))))) + ans (org-ans0 "") org-ans1 org-ans2 final cal-frame) + ;; Rationalize `org-def' and `org-defdecode', if required. + (when (< (nth 2 org-defdecode) org-extend-today-until) + (setf (nth 2 org-defdecode) -1) + (setf (nth 1 org-defdecode) 59) + (setq org-def (apply #'encode-time org-defdecode)) + (setq org-defdecode (decode-time org-def))) + (let* ((timestr (format-time-string + (if org-with-time "%Y-%m-%d %H:%M" "%Y-%m-%d") + org-def)) + (prompt (concat (if prompt (concat prompt " ") "") + (format "Date+time [%s]: " timestr)))) + (cond + (from-string (setq ans from-string)) + (org-read-date-popup-calendar + (save-excursion + (save-window-excursion + (calendar) + (when (eq calendar-setup 'calendar-only) + (setq cal-frame + (window-frame (get-buffer-window "*Calendar*" 'visible))) + (select-frame cal-frame)) + (org-eval-in-calendar '(setq cursor-type nil) t) + (unwind-protect + (progn + (calendar-forward-day (- (time-to-days org-def) + (calendar-absolute-from-gregorian + (calendar-current-date)))) + (org-eval-in-calendar nil t) + (let* ((old-map (current-local-map)) + (map (copy-keymap calendar-mode-map)) + (minibuffer-local-map + (copy-keymap org-read-date-minibuffer-local-map))) + (org-defkey map (kbd "RET") 'org-calendar-select) + (org-defkey map [mouse-1] 'org-calendar-select-mouse) + (org-defkey map [mouse-2] 'org-calendar-select-mouse) + (unwind-protect + (progn + (use-local-map map) + (setq org-read-date-inactive inactive) + (add-hook 'post-command-hook 'org-read-date-display) + (setq org-ans0 + (read-string prompt + default-input + 'org-read-date-history + nil)) + ;; org-ans0: from prompt + ;; org-ans1: from mouse click + ;; org-ans2: from calendar motion + (setq ans + (concat org-ans0 " " (or org-ans1 org-ans2)))) + (remove-hook 'post-command-hook 'org-read-date-display) + (use-local-map old-map) + (when org-read-date-overlay + (delete-overlay org-read-date-overlay) + (setq org-read-date-overlay nil))))) + (bury-buffer "*Calendar*") + (when cal-frame + (delete-frame cal-frame) + (select-frame-set-input-focus cur-frame)))))) + + (t ; Naked prompt only + (unwind-protect + (setq ans (read-string prompt default-input + 'org-read-date-history timestr)) + (when org-read-date-overlay + (delete-overlay org-read-date-overlay) + (setq org-read-date-overlay nil)))))) (setq final (org-read-date-analyze ans org-def org-defdecode)) @@ -16499,13 +16918,18 @@ user." (make-overlay (1- (point-at-eol)) (point-at-eol))) (org-overlay-display org-read-date-overlay txt 'secondary-selection))))) -(defun org-read-date-analyze (ans org-def org-defdecode) +(defun org-read-date-analyze (ans def defdecode) "Analyze the combined answer of the date prompt." ;; FIXME: cleanup and comment - (let ((nowdecode (decode-time)) + ;; Pass `current-time' result to `decode-time' (instead of calling + ;; without arguments) so that only `current-time' has to be + ;; overridden in tests. + (let ((org-def def) + (org-defdecode defdecode) + (nowdecode (decode-time (current-time))) delta deltan deltaw deltadef year month day hour minute second wday pm h2 m2 tl wday1 - iso-year iso-weekday iso-week iso-year iso-date futurep kill-year) + iso-year iso-weekday iso-week iso-date futurep kill-year) (setq org-read-date-analyze-futurep nil org-read-date-analyze-forced-year nil) (when (string-match "\\`[ \t]*\\.[ \t]*\\'" ans) @@ -16521,11 +16945,11 @@ user." ;; info and postpone interpreting it until the rest of the parsing ;; is done. (when (string-match "\\<\\(?:\\([0-9]+\\)-\\)?[wW]\\([0-9]\\{1,2\\}\\)\\(?:-\\([0-6]\\)\\)?\\([ \t]\\|$\\)" ans) - (setq iso-year (if (match-end 1) - (org-small-year-to-year - (string-to-number (match-string 1 ans)))) - iso-weekday (if (match-end 3) - (string-to-number (match-string 3 ans))) + (setq iso-year (when (match-end 1) + (org-small-year-to-year + (string-to-number (match-string 1 ans)))) + iso-weekday (when (match-end 3) + (string-to-number (match-string 3 ans))) iso-week (string-to-number (match-string 2 ans))) (setq ans (replace-match "" t t ans))) @@ -16538,7 +16962,7 @@ user." (string-to-number (format-time-string "%Y")))) month (string-to-number (match-string 3 ans)) day (string-to-number (match-string 4 ans))) - (if (< year 100) (setq year (+ 2000 year))) + (setq year (org-small-year-to-year year)) (setq ans (replace-match (format "%04d-%02d-%02d\\5" year month day) t nil ans))) @@ -16562,26 +16986,26 @@ user." (string-to-number (format-time-string "%Y")))) month (string-to-number (match-string 1 ans)) day (string-to-number (match-string 2 ans))) - (if (< year 100) (setq year (+ 2000 year))) + (setq year (org-small-year-to-year year)) (setq ans (replace-match (format "%04d-%02d-%02d\\5" year month day) t nil ans))) ;; Help matching am/pm times, because `parse-time-string' does not do that. ;; If there is a time with am/pm, and *no* time without it, we convert ;; so that matching will be successful. - (loop for i from 1 to 2 do ; twice, for end time as well - (when (and (not (string-match "\\(\\`\\|[^+]\\)[012]?[0-9]:[0-9][0-9]\\([ \t\n]\\|$\\)" ans)) - (string-match "\\([012]?[0-9]\\)\\(:\\([0-5][0-9]\\)\\)?\\(am\\|AM\\|pm\\|PM\\)\\>" ans)) - (setq hour (string-to-number (match-string 1 ans)) - minute (if (match-end 3) - (string-to-number (match-string 3 ans)) - 0) - pm (equal ?p - (string-to-char (downcase (match-string 4 ans))))) - (if (and (= hour 12) (not pm)) - (setq hour 0) - (if (and pm (< hour 12)) (setq hour (+ 12 hour)))) - (setq ans (replace-match (format "%02d:%02d" hour minute) - t t ans)))) + (cl-loop for i from 1 to 2 do ; twice, for end time as well + (when (and (not (string-match "\\(\\`\\|[^+]\\)[012]?[0-9]:[0-9][0-9]\\([ \t\n]\\|$\\)" ans)) + (string-match "\\([012]?[0-9]\\)\\(:\\([0-5][0-9]\\)\\)?\\(am\\|AM\\|pm\\|PM\\)\\>" ans)) + (setq hour (string-to-number (match-string 1 ans)) + minute (if (match-end 3) + (string-to-number (match-string 3 ans)) + 0) + pm (equal ?p + (string-to-char (downcase (match-string 4 ans))))) + (if (and (= hour 12) (not pm)) + (setq hour 0) + (when (and pm (< hour 12)) (setq hour (+ 12 hour)))) + (setq ans (replace-match (format "%02d:%02d" hour minute) + t t ans)))) ;; Check if a time range is given as a duration (when (string-match "\\([012]?[0-9]\\):\\([0-6][0-9]\\)\\+\\([012]?[0-9]\\)\\(:\\([0-5][0-9]\\)\\)?" ans) @@ -16590,7 +17014,7 @@ user." minute (string-to-number (match-string 2 ans)) m2 (+ minute (if (match-end 5) (string-to-number (match-string 5 ans))0))) - (if (>= m2 60) (setq h2 (1+ h2) m2 (- m2 60))) + (when (>= m2 60) (setq h2 (1+ h2) m2 (- m2 60))) (setq ans (replace-match (format "%02d:%02d-%02d:%02d" hour minute h2 m2) t t ans))) @@ -16605,16 +17029,35 @@ user." (setq tl (parse-time-string ans) day (or (nth 3 tl) (nth 3 org-defdecode)) - month (or (nth 4 tl) - (if (and org-read-date-prefer-future - (nth 3 tl) (< (nth 3 tl) (nth 3 nowdecode))) - (prog1 (1+ (nth 4 nowdecode)) (setq futurep t)) - (nth 4 org-defdecode))) - year (or (and (not kill-year) (nth 5 tl)) - (if (and org-read-date-prefer-future - (nth 4 tl) (< (nth 4 tl) (nth 4 nowdecode))) - (prog1 (1+ (nth 5 nowdecode)) (setq futurep t)) - (nth 5 org-defdecode))) + month + (cond ((nth 4 tl)) + ((not org-read-date-prefer-future) (nth 4 org-defdecode)) + ;; Day was specified. Make sure DAY+MONTH + ;; combination happens in the future. + ((nth 3 tl) + (setq futurep t) + (if (< day (nth 3 nowdecode)) (1+ (nth 4 nowdecode)) + (nth 4 nowdecode))) + (t (nth 4 org-defdecode))) + year + (cond ((and (not kill-year) (nth 5 tl))) + ((not org-read-date-prefer-future) (nth 5 org-defdecode)) + ;; Month was guessed in the future and is at least + ;; equal to NOWDECODE's. Fix year accordingly. + (futurep + (if (or (> month (nth 4 nowdecode)) + (>= day (nth 3 nowdecode))) + (nth 5 nowdecode) + (1+ (nth 5 nowdecode)))) + ;; Month was specified. Make sure MONTH+YEAR + ;; combination happens in the future. + ((nth 4 tl) + (setq futurep t) + (cond ((> month (nth 4 nowdecode)) (nth 5 nowdecode)) + ((< month (nth 4 nowdecode)) (1+ (nth 5 nowdecode))) + ((< day (nth 3 nowdecode)) (1+ (nth 5 nowdecode))) + (t (nth 5 nowdecode)))) + (t (nth 5 org-defdecode))) hour (or (nth 2 tl) (nth 2 org-defdecode)) minute (or (nth 1 tl) (nth 1 org-defdecode)) second (or (nth 0 tl) 0) @@ -16643,7 +17086,7 @@ user." day (or iso-weekday wday 1) wday nil ; to make sure that the trigger below does not match iso-date (calendar-gregorian-from-absolute - (calendar-absolute-from-iso + (calendar-iso-to-absolute (list iso-week day year)))) ; FIXME: Should we also push ISO weeks into the future? ; (when (and org-read-date-prefer-future @@ -16652,7 +17095,7 @@ user." ; (time-to-days (current-time)))) ; (setq year (1+ year) ; iso-date (calendar-gregorian-from-absolute - ; (calendar-absolute-from-iso + ; (calendar-iso-to-absolute ; (list iso-week day year))))) (setq month (car iso-date) year (nth 2 iso-date) @@ -16660,7 +17103,10 @@ user." (deltan (setq futurep nil) (unless deltadef - (let ((now (decode-time))) + ;; Pass `current-time' result to `decode-time' (instead of + ;; calling without arguments) so that only `current-time' has + ;; to be overridden in tests. + (let ((now (decode-time (current-time)))) (setq day (nth 3 now) month (nth 4 now) year (nth 5 now)))) (cond ((member deltaw '("d" "")) (setq day (+ day deltan))) ((equal deltaw "w") (setq day (+ day (* 7 deltan)))) @@ -16672,17 +17118,17 @@ user." (setq wday1 (nth 6 (decode-time (encode-time 0 0 0 day month year)))) (unless (equal wday wday1) (setq day (+ day (% (- wday wday1 -7) 7)))))) - (if (and (boundp 'org-time-was-given) - (nth 2 tl)) - (setq org-time-was-given t)) - (if (< year 100) (setq year (+ 2000 year))) + (when (and (boundp 'org-time-was-given) + (nth 2 tl)) + (setq org-time-was-given t)) + (when (< year 100) (setq year (+ 2000 year))) ;; Check of the date is representable (if org-read-date-force-compatible-dates (progn - (if (< year 1970) - (setq year 1970 org-read-date-analyze-forced-year t)) - (if (> year 2037) - (setq year 2037 org-read-date-analyze-forced-year t))) + (when (< year 1970) + (setq year 1970 org-read-date-analyze-forced-year t)) + (when (> year 2037) + (setq year 2037 org-read-date-analyze-forced-year t))) (condition-case nil (ignore (encode-time second minute hour day month year)) (error @@ -16722,12 +17168,11 @@ DEF-FLAG is t when a double ++ or -- indicates shift relative to (if wday1 (progn (setq delta (mod (+ 7 (- wday1 wday)) 7)) - (if (= delta 0) (setq delta 7)) - (if (= dir ?-) - (progn - (setq delta (- delta 7)) - (if (= delta 0) (setq delta -7)))) - (if (> n 1) (setq delta (+ delta (* (1- n) (if (= dir ?-) -7 7))))) + (when (= delta 0) (setq delta 7)) + (when (= dir ?-) + (setq delta (- delta 7)) + (when (= delta 0) (setq delta -7))) + (when (> n 1) (setq delta (+ delta (* (1- n) (if (= dir ?-) -7 7))))) (list delta "d" rel)) (list (* n (if (= dir ?-) -1 1)) what rel))))) @@ -16736,23 +17181,14 @@ DEF-FLAG is t when a double ++ or -- indicates shift relative to The internal representation needed by the calendar is (month day year). This is a wrapper to handle the brain-dead convention in calendar that user function argument order change dependent on argument order." - (if (boundp 'calendar-date-style) - (cond - ((eq calendar-date-style 'american) - (list arg1 arg2 arg3)) - ((eq calendar-date-style 'european) - (list arg2 arg1 arg3)) - ((eq calendar-date-style 'iso) - (list arg2 arg3 arg1))) - (org-no-warnings ;; european-calendar-style is obsolete as of version 23.1 - (if (org-bound-and-true-p european-calendar-style) - (list arg2 arg1 arg3) - (list arg1 arg2 arg3))))) + (pcase calendar-date-style + (`american (list arg1 arg2 arg3)) + (`european (list arg2 arg1 arg3)) + (`iso (list arg2 arg3 arg1)))) (defun org-eval-in-calendar (form &optional keepdate) "Eval FORM in the calendar window and return to current window. -When KEEPDATE is non-nil, update `org-ans2' from the cursor date, -otherwise stick to the current value of `org-ans2'." +Unless KEEPDATE is non-nil, update `org-ans2' to the cursor date." (let ((sf (selected-frame)) (sw (selected-window))) (select-window (get-buffer-window "*Calendar*" t)) @@ -16763,7 +17199,7 @@ otherwise stick to the current value of `org-ans2'." (setq org-ans2 (format-time-string "%Y-%m-%d" time)))) (move-overlay org-date-ovl (1- (point)) (1+ (point)) (current-buffer)) (select-window sw) - (org-select-frame-set-input-focus sf))) + (select-frame-set-input-focus sf))) (defun org-calendar-select () "Return to `org-read-date' with the date currently selected. @@ -16773,10 +17209,11 @@ This is used by `org-read-date' in a temporary keymap for the calendar buffer." (let* ((date (calendar-cursor-to-date)) (time (encode-time 0 0 0 (nth 1 date) (nth 0 date) (nth 2 date)))) (setq org-ans1 (format-time-string "%Y-%m-%d" time))) - (if (active-minibuffer-window) (exit-minibuffer)))) + (when (active-minibuffer-window) (exit-minibuffer)))) (defun org-insert-time-stamp (time &optional with-hm inactive pre post extra) "Insert a date stamp for the date given by the internal TIME. +See `format-time-string' for the format of TIME. WITH-HM means use the stamp format that includes the time of the day. INACTIVE means use square brackets instead of angular ones, so that the stamp will not contribute to the agenda. @@ -16785,7 +17222,7 @@ stamp. The command returns the inserted time stamp." (let ((fmt (funcall (if with-hm 'cdr 'car) org-time-stamp-formats)) stamp) - (if inactive (setq fmt (concat "[" (substring fmt 1 -1) "]"))) + (when inactive (setq fmt (concat "[" (substring fmt 1 -1) "]"))) (insert-before-markers (or pre "")) (when (listp extra) (setq extra (car extra)) @@ -16808,14 +17245,12 @@ The command returns the inserted time stamp." (unless org-display-custom-times (let ((p (point-min)) (bmp (buffer-modified-p))) (while (setq p (next-single-property-change p 'display)) - (if (and (get-text-property p 'display) - (eq (get-text-property p 'face) 'org-date)) - (remove-text-properties - p (setq p (next-single-property-change p 'display)) - '(display t)))) + (when (and (get-text-property p 'display) + (eq (get-text-property p 'face) 'org-date)) + (remove-text-properties + p (setq p (next-single-property-change p 'display)) + '(display t)))) (set-buffer-modified-p bmp))) - (if (featurep 'xemacs) - (remove-text-properties (point-min) (point-max) '(end-glyph t))) (org-restart-font-lock) (setq org-table-may-need-update t) (if org-display-custom-times @@ -16825,56 +17260,20 @@ The command returns the inserted time stamp." (defun org-display-custom-time (beg end) "Overlay modified time stamp format over timestamp between BEG and END." (let* ((ts (buffer-substring beg end)) - t1 w1 with-hm tf time str w2 (off 0)) + t1 with-hm tf time str (off 0)) (save-match-data (setq t1 (org-parse-time-string ts t)) - (if (string-match "\\(-[0-9]+:[0-9]+\\)?\\( [.+]?\\+[0-9]+[hdwmy]\\(/[0-9]+[hdwmy]\\)?\\)?\\'" ts) - (setq off (- (match-end 0) (match-beginning 0))))) + (when (string-match "\\(-[0-9]+:[0-9]+\\)?\\( [.+]?\\+[0-9]+[hdwmy]\\(/[0-9]+[hdwmy]\\)?\\)?\\'" ts) + (setq off (- (match-end 0) (match-beginning 0))))) (setq end (- end off)) - (setq w1 (- end beg) - with-hm (and (nth 1 t1) (nth 2 t1)) + (setq with-hm (and (nth 1 t1) (nth 2 t1)) tf (funcall (if with-hm 'cdr 'car) org-time-stamp-custom-formats) time (org-fix-decoded-time t1) str (org-add-props (format-time-string (substring tf 1 -1) (apply 'encode-time time)) - nil 'mouse-face 'highlight) - w2 (length str)) - (if (not (= w2 w1)) - (add-text-properties (1+ beg) (+ 2 beg) - (list 'org-dwidth t 'org-dwidth-n (- w1 w2)))) - (if (featurep 'xemacs) - (progn - (put-text-property beg end 'invisible t) - (put-text-property beg end 'end-glyph (make-glyph str))) - (put-text-property beg end 'display str)))) - -(defun org-translate-time (string) - "Translate all timestamps in STRING to custom format. -But do this only if the variable `org-display-custom-times' is set." - (when org-display-custom-times - (save-match-data - (let* ((start 0) - (re org-ts-regexp-both) - t1 with-hm inactive tf time str beg end) - (while (setq start (string-match re string start)) - (setq beg (match-beginning 0) - end (match-end 0) - t1 (save-match-data - (org-parse-time-string (substring string beg end) t)) - with-hm (and (nth 1 t1) (nth 2 t1)) - inactive (equal (substring string beg (1+ beg)) "[") - tf (funcall (if with-hm 'cdr 'car) - org-time-stamp-custom-formats) - time (org-fix-decoded-time t1) - str (format-time-string - (concat - (if inactive "[" "<") (substring tf 1 -1) - (if inactive "]" ">")) - (apply 'encode-time time)) - string (replace-match str t t string) - start (+ start (length str))))))) - string) + nil 'mouse-face 'highlight)) + (put-text-property beg end 'display str))) (defun org-fix-decoded-time (time) "Set 0 instead of nil for the first 6 elements of time. @@ -16882,19 +17281,17 @@ Don't touch the rest." (let ((n 0)) (mapcar (lambda (x) (if (< (setq n (1+ n)) 7) (or x 0) x)) time))) -(define-obsolete-function-alias 'org-days-to-time 'org-time-stamp-to-now "24.4") - (defun org-time-stamp-to-now (timestamp-string &optional seconds) "Difference between TIMESTAMP-STRING and now in days. If SECONDS is non-nil, return the difference in seconds." - (let ((fdiff (if seconds 'float-time 'time-to-days))) + (let ((fdiff (if seconds #'float-time #'time-to-days))) (- (funcall fdiff (org-time-string-to-time timestamp-string)) (funcall fdiff (current-time))))) -(defun org-deadline-close (timestamp-string &optional ndays) +(defun org-deadline-close-p (timestamp-string &optional ndays) "Is the time in TIMESTAMP-STRING close to the current date?" (setq ndays (or ndays (org-get-wdays timestamp-string))) - (and (< (org-time-stamp-to-now timestamp-string) ndays) + (and (<= (org-time-stamp-to-now timestamp-string) ndays) (not (org-entry-is-done-p)))) (defun org-get-wdays (ts &optional delay zero-delay) @@ -16930,14 +17327,15 @@ This is used by `org-read-date' in a temporary keymap for the calendar buffer." (let* ((date (calendar-cursor-to-date)) (time (encode-time 0 0 0 (nth 1 date) (nth 0 date) (nth 2 date)))) (setq org-ans1 (format-time-string "%Y-%m-%d" time))) - (if (active-minibuffer-window) (exit-minibuffer)))) + (when (active-minibuffer-window) (exit-minibuffer)))) (defun org-check-deadlines (ndays) "Check if there are any deadlines due or past due. A deadline is considered due if it happens within `org-deadline-warning-days' days from today's date. If the deadline appears in an entry marked DONE, -it is not shown. The prefix arg NDAYS can be used to test that many -days. If the prefix is a raw \\[universal-argument] prefix, all deadlines are shown." +it is not shown. A numeric prefix argument NDAYS can be used to test that +many days. If the prefix is a raw `\\[universal-argument]', all deadlines \ +are shown." (interactive "P") (let* ((org-warn-days (cond @@ -16947,8 +17345,7 @@ days. If the prefix is a raw \\[universal-argument] prefix, all deadlines are s (case-fold-search nil) (regexp (concat "\\<" org-deadline-string " *<\\([^>]+\\)>")) (callback - (lambda () (org-deadline-close (match-string 1) org-warn-days)))) - + (lambda () (org-deadline-close-p (match-string 1) org-warn-days)))) (message "%d deadlines past-due or due within %d days" (org-occur regexp nil callback) org-warn-days))) @@ -16966,39 +17363,61 @@ Allowed values for TYPE are: When TYPE is nil, fall back on returning a regexp that matches both scheduled and deadline timestamps." - (cond ((eq type 'all) "\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\(?: +[^]+0-9>\r\n -]+\\)?\\(?: +[0-9]\\{1,2\\}:[0-9]\\{2\\}\\)?\\)") - ((eq type 'active) org-ts-regexp) - ((eq type 'inactive) "\\[\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} ?[^\r\n>]*?\\)\\]") - ((eq type 'scheduled) (concat "\\<" org-scheduled-string " *<\\([^>]+\\)>")) - ((eq type 'deadline) (concat "\\<" org-deadline-string " *<\\([^>]+\\)>")) - ((eq type 'closed) (concat org-closed-string " \\[\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} ?[^\r\n>]*?\\)\\]")) - ((eq type 'scheduled-or-deadline) - (concat "\\<\\(?:" org-deadline-string "\\|" org-scheduled-string "\\) *<\\([^>]+\\)>")))) - -(defun org-check-before-date (date) - "Check if there are deadlines or scheduled entries before DATE." + (cl-case type + (all org-ts-regexp-both) + (active org-ts-regexp) + (inactive org-ts-regexp-inactive) + (scheduled org-scheduled-time-regexp) + (deadline org-deadline-time-regexp) + (closed org-closed-time-regexp) + (otherwise + (concat "\\<" + (regexp-opt (list org-deadline-string org-scheduled-string)) + " *<\\([^>]+\\)>")))) + +(defun org-check-before-date (d) + "Check if there are deadlines or scheduled entries before date D." (interactive (list (org-read-date))) - (let ((case-fold-search nil) - (regexp (org-re-timestamp org-ts-type)) - (callback - (lambda () (time-less-p - (org-time-string-to-time (match-string 1)) - (org-time-string-to-time date))))) + (let* ((case-fold-search nil) + (regexp (org-re-timestamp org-ts-type)) + (ts-type org-ts-type) + (callback + (lambda () + (let ((match (match-string 1))) + (and (if (memq ts-type '(active inactive all)) + (eq (org-element-type (save-excursion + (backward-char) + (org-element-context))) + 'timestamp) + (org-at-planning-p)) + (time-less-p + (org-time-string-to-time match) + (org-time-string-to-time d))))))) (message "%d entries before %s" - (org-occur regexp nil callback) date))) + (org-occur regexp nil callback) + d))) -(defun org-check-after-date (date) - "Check if there are deadlines or scheduled entries after DATE." +(defun org-check-after-date (d) + "Check if there are deadlines or scheduled entries after date D." (interactive (list (org-read-date))) - (let ((case-fold-search nil) - (regexp (org-re-timestamp org-ts-type)) - (callback - (lambda () (not - (time-less-p - (org-time-string-to-time (match-string 1)) - (org-time-string-to-time date)))))) + (let* ((case-fold-search nil) + (regexp (org-re-timestamp org-ts-type)) + (ts-type org-ts-type) + (callback + (lambda () + (let ((match (match-string 1))) + (and (if (memq ts-type '(active inactive all)) + (eq (org-element-type (save-excursion + (backward-char) + (org-element-context))) + 'timestamp) + (org-at-planning-p)) + (not (time-less-p + (org-time-string-to-time match) + (org-time-string-to-time d)))))))) (message "%d entries after %s" - (org-occur regexp nil callback) date))) + (org-occur regexp nil callback) + d))) (defun org-check-dates-range (start-date end-date) "Check for deadlines/scheduled entries between START-DATE and END-DATE." @@ -17007,15 +17426,22 @@ both scheduled and deadline timestamps." (let ((case-fold-search nil) (regexp (org-re-timestamp org-ts-type)) (callback - (lambda () - (let ((match (match-string 1))) - (and - (not (time-less-p - (org-time-string-to-time match) - (org-time-string-to-time start-date))) - (time-less-p - (org-time-string-to-time match) - (org-time-string-to-time end-date))))))) + (let ((type org-ts-type)) + (lambda () + (let ((match (match-string 1))) + (and + (if (memq type '(active inactive all)) + (eq (org-element-type (save-excursion + (backward-char) + (org-element-context))) + 'timestamp) + (org-at-planning-p)) + (not (time-less-p + (org-time-string-to-time match) + (org-time-string-to-time start-date))) + (time-less-p + (org-time-string-to-time match) + (org-time-string-to-time end-date)))))))) (message "%d entries between %s and %s" (org-occur regexp nil callback) start-date end-date))) @@ -17034,8 +17460,8 @@ days in order to avoid rounding problems." (unless (org-at-date-range-p t) (goto-char (point-at-bol)) (re-search-forward org-tr-regexp-both (point-at-eol) t)) - (if (not (org-at-date-range-p t)) - (user-error "Not at a time-stamp range, and none found in current line"))) + (unless (org-at-date-range-p t) + (user-error "Not at a time-stamp range, and none found in current line"))) (let* ((ts1 (match-string 1)) (ts2 (match-string 2)) (havetime (or (> (length ts1) 15) (> (length ts2) 15))) @@ -17073,65 +17499,75 @@ days in order to avoid rounding problems." (setq align t) (and (looking-at " *|") (goto-char (match-end 0)))) (goto-char match-end)) - (if (looking-at - "\\( *-? *[0-9]+y\\)?\\( *[0-9]+d\\)? *[0-9][0-9]:[0-9][0-9]") - (replace-match "")) - (if negative (insert " -")) + (when (looking-at + "\\( *-? *[0-9]+y\\)?\\( *[0-9]+d\\)? *[0-9][0-9]:[0-9][0-9]") + (replace-match "")) + (when negative (insert " -")) (if (> y 0) (insert " " (format (if havetime fy fy1) y d h m)) (if (> d 0) (insert " " (format (if havetime fd fd1) d h m)) (insert " " (format fh h m)))) - (if align (org-table-align)) + (when align (org-table-align)) (message "Time difference inserted"))))) (defun org-make-tdiff-string (y d h m) (let ((fmt "") (l nil)) - (if (> y 0) (setq fmt (concat fmt "%d year" (if (> y 1) "s" "") " ") - l (push y l))) - (if (> d 0) (setq fmt (concat fmt "%d day" (if (> d 1) "s" "") " ") - l (push d l))) - (if (> h 0) (setq fmt (concat fmt "%d hour" (if (> h 1) "s" "") " ") - l (push h l))) - (if (> m 0) (setq fmt (concat fmt "%d minute" (if (> m 1) "s" "") " ") - l (push m l))) + (when (> y 0) + (setq fmt (concat fmt "%d year" (if (> y 1) "s" "") " ")) + (push y l)) + (when (> d 0) + (setq fmt (concat fmt "%d day" (if (> d 1) "s" "") " ")) + (push d l)) + (when (> h 0) + (setq fmt (concat fmt "%d hour" (if (> h 1) "s" "") " ")) + (push h l)) + (when (> m 0) + (setq fmt (concat fmt "%d minute" (if (> m 1) "s" "") " ")) + (push m l)) (apply 'format fmt (nreverse l)))) -(defun org-time-string-to-time (s &optional buffer pos) - "Convert a timestamp string into internal time." - (condition-case errdata - (apply 'encode-time (org-parse-time-string s)) - (error (error "Bad timestamp `%s'%s\nError was: %s" - s (if (not (and buffer pos)) - "" - (format-message " at %d in buffer `%s'" pos buffer)) - (cdr errdata))))) +(defun org-time-string-to-time (s) + "Convert timestamp string S into internal time." + (apply #'encode-time (org-parse-time-string s))) (defun org-time-string-to-seconds (s) - "Convert a timestamp string to a number of seconds." + "Convert a timestamp string S into a number of seconds." (float-time (org-time-string-to-time s))) -(defun org-time-string-to-absolute (s &optional daynr prefer show-all buffer pos) - "Convert a time stamp to an absolute day number. -If there is a specifier for a cyclic time stamp, get the closest -date to DAYNR. -PREFER and SHOW-ALL are passed through to `org-closest-date'. -The variable `date' is bound by the calendar when this is called." +(org-define-error 'org-diary-sexp-no-match "Unable to match diary sexp") + +(defun org-time-string-to-absolute (s &optional daynr prefer buffer pos) + "Convert time stamp S to an absolute day number. + +If DAYNR in non-nil, and there is a specifier for a cyclic time +stamp, get the closest date to DAYNR. If PREFER is +`past' (respectively `future') return a date past (respectively +after) or equal to DAYNR. + +POS is the location of time stamp S, as a buffer position in +BUFFER. + +Diary sexp timestamps are matched against DAYNR, when non-nil. +If matching fails or DAYNR is nil, `org-diary-sexp-no-match' is +signaled." (cond - ((and daynr (string-match "\\`%%\\((.*)\\)" s)) - (if (org-diary-sexp-entry (match-string 1 s) "" date) + ((string-match "\\`%%\\((.*)\\)" s) + ;; Sexp timestamp: try to match DAYNR, if available, since we're + ;; only able to match individual dates. If it fails, raise an + ;; error. + (if (and daynr + (org-diary-sexp-entry + (match-string 1 s) "" (calendar-gregorian-from-absolute daynr))) daynr - (+ daynr 1000))) - ((and daynr (string-match "\\+[0-9]+[hdwmy]" s)) - (org-closest-date s (if (and (boundp 'daynr) (integerp daynr)) daynr - (time-to-days (current-time))) (match-string 0 s) - prefer show-all)) + (signal 'org-diary-sexp-no-match (list s)))) + (daynr (org-closest-date s daynr prefer)) (t (time-to-days (condition-case errdata - (apply 'encode-time (org-parse-time-string s)) + (apply #'encode-time (org-parse-time-string s)) (error (error "Bad timestamp `%s'%s\nError was: %s" - s (if (not (and buffer pos)) - "" - (format-message " at %d in buffer `%s'" pos buffer)) + s + (if (not (and buffer pos)) "" + (format-message " at %d in buffer `%s'" pos buffer)) (cdr errdata)))))))) (defun org-days-to-iso-week (days) @@ -17141,43 +17577,46 @@ The variable `date' is bound by the calendar when this is called." (defun org-small-year-to-year (year) "Convert 2-digit years into 4-digit years. -38-99 are mapped into 1938-1999. 1-37 are mapped into 2001-2037. -The year 2000 cannot be abbreviated. Any year larger than 99 -is returned unchanged." - (if (< year 38) - (setq year (+ 2000 year)) - (if (< year 100) - (setq year (+ 1900 year)))) - year) +YEAR is expanded into one of the 30 next years, if possible, or +into a past one. Any year larger than 99 is returned unchanged." + (if (>= year 100) year + (let* ((current (string-to-number (format-time-string "%Y" (current-time)))) + (century (/ current 100)) + (offset (- year (% current 100)))) + (cond ((> offset 30) (+ (* (1- century) 100) year)) + ((> offset -70) (+ (* century 100) year)) + (t (+ (* (1+ century) 100) year)))))) (defun org-time-from-absolute (d) "Return the time corresponding to date D. D may be an absolute day number, or a calendar-type list (month day year)." - (if (numberp d) (setq d (calendar-gregorian-from-absolute d))) + (when (numberp d) (setq d (calendar-gregorian-from-absolute d))) (encode-time 0 0 0 (nth 1 d) (car d) (nth 2 d))) +(defvar org-agenda-current-date) (defun org-calendar-holiday () - "List of holidays, for Diary display in Org-mode." + "List of holidays, for Diary display in Org mode." (require 'holidays) - (let ((hl (funcall - (if (fboundp 'calendar-check-holidays) - 'calendar-check-holidays 'check-calendar-holidays) date))) - (if hl (mapconcat 'identity hl "; ")))) + (let ((hl (calendar-check-holidays org-agenda-current-date))) + (and hl (mapconcat #'identity hl "; ")))) -(defun org-diary-sexp-entry (sexp entry date) - "Process a SEXP diary ENTRY for DATE." +(defun org-diary-sexp-entry (sexp entry d) + "Process a SEXP diary ENTRY for date D." (require 'diary-lib) - (let ((result (if calendar-debug-sexp - (let ((stack-trace-on-error t)) - (eval (car (read-from-string sexp)))) - (condition-case nil - (eval (car (read-from-string sexp))) - (error - (beep) - (message "Bad sexp at line %d in %s: %s" - (org-current-line) - (buffer-file-name) sexp) - (sleep-for 2)))))) + ;; `org-anniversary' and alike expect ENTRY and DATE to be bound + ;; dynamically. + (let* ((sexp `(let ((entry ,entry) + (date ',d)) + ,(car (read-from-string sexp)))) + (result (if calendar-debug-sexp (eval sexp) + (condition-case nil + (eval sexp) + (error + (beep) + (message "Bad sexp at line %d in %s: %s" + (org-current-line) + (buffer-file-name) sexp) + (sleep-for 2)))))) (cond ((stringp result) (split-string result "; ")) ((and (consp result) (not (consp (cdr result))) @@ -17189,9 +17628,7 @@ D may be an absolute day number, or a calendar-type list (month day year)." (defun org-diary-to-ical-string (frombuf) "Get iCalendar entries from diary entries in buffer FROMBUF. This uses the icalendar.el library." - (let* ((tmpdir (if (featurep 'xemacs) - (temp-directory) - temporary-file-directory)) + (let* ((tmpdir temporary-file-directory) (tmpfile (make-temp-name (expand-file-name "orgics" tmpdir))) buf rtn b e) @@ -17200,125 +17637,142 @@ This uses the icalendar.el library." (setq buf (find-buffer-visiting tmpfile)) (set-buffer buf) (goto-char (point-min)) - (if (re-search-forward "^BEGIN:VEVENT" nil t) - (setq b (match-beginning 0))) + (when (re-search-forward "^BEGIN:VEVENT" nil t) + (setq b (match-beginning 0))) (goto-char (point-max)) - (if (re-search-backward "^END:VEVENT" nil t) - (setq e (match-end 0))) + (when (re-search-backward "^END:VEVENT" nil t) + (setq e (match-end 0))) (setq rtn (if (and b e) (concat (buffer-substring b e) "\n") ""))) (kill-buffer buf) (delete-file tmpfile) rtn)) -(defun org-closest-date (start current change prefer show-all) - "Find the date closest to CURRENT that is consistent with START and CHANGE. -When PREFER is `past', return a date that is either CURRENT or past. -When PREFER is `future', return a date that is either CURRENT or future. -When SHOW-ALL is nil, only return the current occurrence of a time stamp." - ;; Make the proper lists from the dates - (catch 'exit - (let ((a1 '(("h" . hour) - ("d" . day) - ("w" . week) - ("m" . month) - ("y" . year))) - (shour (nth 2 (org-parse-time-string start))) - dn dw sday cday n1 n2 n0 - d m y y1 y2 date1 date2 nmonths nm ny m2) - - (setq start (org-date-to-gregorian start) - current (org-date-to-gregorian - (if show-all - current - (time-to-days (current-time)))) - sday (calendar-absolute-from-gregorian start) - cday (calendar-absolute-from-gregorian current)) - - (if (<= cday sday) (throw 'exit sday)) - - (if (string-match "\\(\\+[0-9]+\\)\\([hdwmy]\\)" change) - (setq dn (string-to-number (match-string 1 change)) - dw (cdr (assoc (match-string 2 change) a1))) - (user-error "Invalid change specifier: %s" change)) - (if (eq dw 'week) (setq dw 'day dn (* 7 dn))) - (cond - ((eq dw 'hour) - (let ((missing-hours - (mod (+ (- (* 24 (- cday sday)) shour) org-extend-today-until) - dn))) - (setq n1 (if (zerop missing-hours) cday - (- cday (1+ (floor (/ missing-hours 24))))) - n2 (+ cday (floor (/ (- dn missing-hours) 24)))))) - ((eq dw 'day) - (setq n1 (+ sday (* dn (floor (/ (- cday sday) dn)))) - n2 (+ n1 dn))) - ((eq dw 'year) - (setq d (nth 1 start) m (car start) y1 (nth 2 start) y2 (nth 2 current)) - (setq y1 (+ (* (floor (/ (- y2 y1) dn)) dn) y1)) - (setq date1 (list m d y1) - n1 (calendar-absolute-from-gregorian date1) - date2 (list m d (+ y1 (* (if (< n1 cday) 1 -1) dn))) - n2 (calendar-absolute-from-gregorian date2))) - ((eq dw 'month) - ;; approx number of month between the two dates - (setq nmonths (floor (/ (- cday sday) 30.436875))) - ;; How often does dn fit in there? - (setq d (nth 1 start) m (car start) y (nth 2 start) - nm (* dn (max 0 (1- (floor (/ nmonths dn))))) - m (+ m nm) - ny (floor (/ m 12)) - y (+ y ny) - m (- m (* ny 12))) - (while (> m 12) (setq m (- m 12) y (1+ y))) - (setq n1 (calendar-absolute-from-gregorian (list m d y))) - (setq m2 (+ m dn) y2 y) - (if (> m2 12) (setq y2 (1+ y2) m2 (- m2 12))) - (setq n2 (calendar-absolute-from-gregorian (list m2 d y2))) - (while (<= n2 cday) - (setq n1 n2 m m2 y y2) - (setq m2 (+ m dn) y2 y) - (if (> m2 12) (setq y2 (1+ y2) m2 (- m2 12))) - (setq n2 (calendar-absolute-from-gregorian (list m2 d y2)))))) - ;; Make sure n1 is the earlier date - (setq n0 n1 n1 (min n1 n2) n2 (max n0 n2)) - (if show-all - (cond - ((eq prefer 'past) (if (= cday n2) n2 n1)) - ((eq prefer 'future) (if (= cday n1) n1 n2)) - (t (if (> (abs (- cday n1)) (abs (- cday n2))) n2 n1))) - (cond - ((eq prefer 'past) (if (= cday n2) n2 n1)) - ((eq prefer 'future) (if (= cday n1) n1 n2)) - (t (if (= cday n1) n1 n2))))))) - -(defun org-date-to-gregorian (date) - "Turn any specification of DATE into a Gregorian date for the calendar." - (cond ((integerp date) (calendar-gregorian-from-absolute date)) - ((and (listp date) (= (length date) 3)) date) - ((stringp date) - (setq date (org-parse-time-string date)) - (list (nth 4 date) (nth 3 date) (nth 5 date))) - ((listp date) - (list (nth 4 date) (nth 3 date) (nth 5 date))))) +(defun org-closest-date (start current prefer) + "Return closest date to CURRENT starting from START. + +CURRENT and START are both time stamps. + +When PREFER is `past', return a date that is either CURRENT or +past. When PREFER is `future', return a date that is either +CURRENT or future. + +Only time stamps with a repeater are modified. Any other time +stamp stay unchanged. In any case, return value is an absolute +day number." + (if (not (string-match "\\+\\([0-9]+\\)\\([hdwmy]\\)" start)) + ;; No repeater. Do not shift time stamp. + (time-to-days (apply #'encode-time (org-parse-time-string start))) + (let ((value (string-to-number (match-string 1 start))) + (type (match-string 2 start))) + (if (= 0 value) + ;; Repeater with a 0-value is considered as void. + (time-to-days (apply #'encode-time (org-parse-time-string start))) + (let* ((base (org-date-to-gregorian start)) + (target (org-date-to-gregorian current)) + (sday (calendar-absolute-from-gregorian base)) + (cday (calendar-absolute-from-gregorian target)) + n1 n2) + ;; If START is already past CURRENT, just return START. + (if (<= cday sday) sday + ;; Compute closest date before (N1) and closest date past + ;; (N2) CURRENT. + (pcase type + ("h" + (let ((missing-hours + (mod (+ (- (* 24 (- cday sday)) + (nth 2 (org-parse-time-string start))) + org-extend-today-until) + value))) + (setf n1 (if (= missing-hours 0) cday + (- cday (1+ (/ missing-hours 24))))) + (setf n2 (+ cday (/ (- value missing-hours) 24))))) + ((or "d" "w") + (let ((value (if (equal type "w") (* 7 value) value))) + (setf n1 (+ sday (* value (/ (- cday sday) value)))) + (setf n2 (+ n1 value)))) + ("m" + (let* ((add-months + (lambda (d n) + ;; Add N months to gregorian date D, i.e., + ;; a list (MONTH DAY YEAR). Return a valid + ;; gregorian date. + (let ((m (+ (nth 0 d) n))) + (list (mod m 12) + (nth 1 d) + (+ (/ m 12) (nth 2 d)))))) + (months ; Complete months to TARGET. + (* (/ (+ (* 12 (- (nth 2 target) (nth 2 base))) + (- (nth 0 target) (nth 0 base)) + ;; If START's day is greater than + ;; TARGET's, remove incomplete month. + (if (> (nth 1 target) (nth 1 base)) 0 -1)) + value) + value)) + (before (funcall add-months base months))) + (setf n1 (calendar-absolute-from-gregorian before)) + (setf n2 + (calendar-absolute-from-gregorian + (funcall add-months before value))))) + (_ + (let* ((d (nth 1 base)) + (m (nth 0 base)) + (y (nth 2 base)) + (years ; Complete years to TARGET. + (* (/ (- (nth 2 target) + y + ;; If START's month and day are + ;; greater than TARGET's, remove + ;; incomplete year. + (if (or (> (nth 0 target) m) + (and (= (nth 0 target) m) + (> (nth 1 target) d))) + 0 + 1)) + value) + value)) + (before (list m d (+ y years)))) + (setf n1 (calendar-absolute-from-gregorian before)) + (setf n2 (calendar-absolute-from-gregorian + (list m d (+ (nth 2 before) value))))))) + ;; Handle PREFER parameter, if any. + (cond + ((eq prefer 'past) (if (= cday n2) n2 n1)) + ((eq prefer 'future) (if (= cday n1) n1 n2)) + (t (if (> (abs (- cday n1)) (abs (- cday n2))) n2 n1))))))))) + +(defun org-date-to-gregorian (d) + "Turn any specification of date D into a Gregorian date for the calendar." + (cond ((integerp d) (calendar-gregorian-from-absolute d)) + ((and (listp d) (= (length d) 3)) d) + ((stringp d) + (let ((d (org-parse-time-string d))) + (list (nth 4 d) (nth 3 d) (nth 5 d)))) + ((listp d) (list (nth 4 d) (nth 3 d) (nth 5 d))))) (defun org-parse-time-string (s &optional nodefault) - "Parse the standard Org-mode time string. + "Parse the standard Org time string. + This should be a lot faster than the normal `parse-time-string'. -If time is not given, defaults to 0:00. However, with optional NODEFAULT, -hour and minute fields will be nil if not given." + +If time is not given, defaults to 0:00. However, with optional +NODEFAULT, hour and minute fields will be nil if not given." (cond ((string-match org-ts-regexp0 s) (list 0 - (if (or (match-beginning 8) (not nodefault)) - (string-to-number (or (match-string 8 s) "0"))) - (if (or (match-beginning 7) (not nodefault)) - (string-to-number (or (match-string 7 s) "0"))) + (when (or (match-beginning 8) (not nodefault)) + (string-to-number (or (match-string 8 s) "0"))) + (when (or (match-beginning 7) (not nodefault)) + (string-to-number (or (match-string 7 s) "0"))) (string-to-number (match-string 4 s)) (string-to-number (match-string 3 s)) (string-to-number (match-string 2 s)) nil nil nil)) ((string-match "^<[^>]+>$" s) + ;; FIXME: `decode-time' needs to be called with ZONE as its + ;; second argument. However, this requires at least Emacs + ;; 25.1. We can do it when we switch to this version as our + ;; minimal requirement. (decode-time (seconds-to-time (org-matcher-time s)))) - (t (error "Not a standard Org-mode time string: %s" s)))) + (t (error "Not a standard Org time string: %s" s)))) (defun org-timestamp-up (&optional arg) "Increase the date item at the cursor by one. @@ -17340,7 +17794,7 @@ With prefix ARG, change by that many units." "Increase the date in the time stamp by one day. With prefix ARG, change that many days." (interactive "p") - (if (and (not (org-at-timestamp-p t)) + (if (and (not (org-at-timestamp-p 'lax)) (org-at-heading-p)) (org-todo 'up) (org-timestamp-change (prefix-numeric-value arg) 'day 'updown))) @@ -17349,47 +17803,89 @@ With prefix ARG, change that many days." "Decrease the date in the time stamp by one day. With prefix ARG, change that many days." (interactive "p") - (if (and (not (org-at-timestamp-p t)) + (if (and (not (org-at-timestamp-p 'lax)) (org-at-heading-p)) (org-todo 'down) (org-timestamp-change (- (prefix-numeric-value arg)) 'day) 'updown)) -(defun org-at-timestamp-p (&optional inactive-ok) - "Determine if the cursor is in or at a timestamp." - (interactive) - (let* ((tsr (if inactive-ok org-ts-regexp3 org-ts-regexp2)) +(defun org-at-timestamp-p (&optional extended) + "Non-nil if point is inside a timestamp. + +By default, the function only consider syntactically valid active +timestamps. However, the caller may have a broader definition +for timestamps. As a consequence, optional argument EXTENDED can +be set to the following values + + `inactive' + + Include also syntactically valid inactive timestamps. + + `agenda' + + Include timestamps allowed in Agenda, i.e., those in + properties drawers, planning lines and clock lines. + + `lax' + + Ignore context. The function matches any part of the + document looking like a timestamp. This includes comments, + example blocks... + +For backward-compatibility with Org 9.0, every other non-nil +value is equivalent to `inactive'. + +When at a timestamp, return the position of the point as a symbol +among `bracket', `after', `year', `month', `hour', `minute', +`day' or a number of character from the last know part of the +time stamp. + +When matching, the match groups are the following: + group 1: year + group 2: month + group 3: day number + group 4: day name + group 5: hours, if any + group 6: minutes, if any" + (let* ((regexp (if extended org-ts-regexp3 org-ts-regexp2)) (pos (point)) - (ans (or (looking-at tsr) - (save-excursion - (skip-chars-backward "^[<\n\r\t") - (if (> (point) (point-min)) (backward-char 1)) - (and (looking-at tsr) - (> (- (match-end 0) pos) -1)))))) - (and ans - (boundp 'org-ts-what) - (setq org-ts-what - (cond - ((= pos (match-beginning 0)) 'bracket) - ;; Point is considered to be "on the bracket" whether - ;; it's really on it or right after it. - ((= pos (1- (match-end 0))) 'bracket) - ((= pos (match-end 0)) 'after) - ((org-pos-in-match-range pos 2) 'year) - ((org-pos-in-match-range pos 3) 'month) - ((org-pos-in-match-range pos 7) 'hour) - ((org-pos-in-match-range pos 8) 'minute) - ((or (org-pos-in-match-range pos 4) - (org-pos-in-match-range pos 5)) 'day) - ((and (> pos (or (match-end 8) (match-end 5))) - (< pos (match-end 0))) - (- pos (or (match-end 8) (match-end 5)))) - (t 'day)))) - ans)) + (match? + (let ((boundaries (org-in-regexp regexp))) + (save-match-data + (cond ((null boundaries) nil) + ((eq extended 'lax) t) + (t + (or (and (eq extended 'agenda) + (or (org-at-planning-p) + (org-at-property-p) + (and (bound-and-true-p + org-agenda-include-inactive-timestamps) + (org-at-clock-log-p)))) + (eq 'timestamp + (save-excursion + (when (= pos (cdr boundaries)) (forward-char -1)) + (org-element-type (org-element-context))))))))))) + (cond + ((not match?) nil) + ((= pos (match-beginning 0)) 'bracket) + ;; Distinguish location right before the closing bracket from + ;; right after it. + ((= pos (1- (match-end 0))) 'bracket) + ((= pos (match-end 0)) 'after) + ((org-pos-in-match-range pos 2) 'year) + ((org-pos-in-match-range pos 3) 'month) + ((org-pos-in-match-range pos 7) 'hour) + ((org-pos-in-match-range pos 8) 'minute) + ((or (org-pos-in-match-range pos 4) + (org-pos-in-match-range pos 5)) 'day) + ((and (> pos (or (match-end 8) (match-end 5))) + (< pos (match-end 0))) + (- pos (or (match-end 8) (match-end 5)))) + (t 'day)))) (defun org-toggle-timestamp-type () "Toggle the type (<active> or [inactive]) of a time stamp." (interactive) - (when (org-at-timestamp-p t) + (when (org-at-timestamp-p 'lax) (let ((beg (match-beginning 0)) (end (match-end 0)) (map '((?\[ . "<") (?\] . ">") (?< . "[") (?> . "]")))) (save-excursion @@ -17400,11 +17896,10 @@ With prefix ARG, change that many days." (message "Timestamp is now %sactive" (if (equal (char-after beg) ?<) "" "in"))))) -(defun org-at-clock-log-p nil - "Is the cursor on the clock log line?" - (save-excursion - (move-beginning-of-line 1) - (looking-at "^[ \t]*CLOCK:"))) +(defun org-at-clock-log-p () + "Non-nil if point is on a clock log line." + (and (org-match-line org-clock-line-re) + (eq (org-element-type (save-match-data (org-element-at-point))) 'clock))) (defvar org-clock-history) ; defined in org-clock.el (defvar org-clock-adjust-closest nil) ; defined in org-clock.el @@ -17414,26 +17909,26 @@ The date will be changed by N times WHAT. WHAT can be `day', `month', `year', `minute', `second'. If WHAT is not given, the cursor position in the timestamp determines what will be changed. When SUPPRESS-TMP-DELAY is non-nil, suppress delays like \"--2d\"." - (let ((origin (point)) origin-cat + (let ((origin (point)) + (timestamp? (org-at-timestamp-p 'lax)) + origin-cat with-hm inactive (dm (max (nth 1 org-time-stamp-rounding-minutes) 1)) - org-ts-what extra rem ts time time0 fixnext clrgx) - (if (not (org-at-timestamp-p t)) - (user-error "Not at a timestamp")) - (if (and (not what) (eq org-ts-what 'bracket)) + (unless timestamp? (user-error "Not at a timestamp")) + (if (and (not what) (eq timestamp? 'bracket)) (org-toggle-timestamp-type) ;; Point isn't on brackets. Remember the part of the time-stamp ;; the point was in. Indeed, size of time-stamps may change, ;; but point must be kept in the same category nonetheless. - (setq origin-cat org-ts-what) - (if (and (not what) (not (eq org-ts-what 'day)) - org-display-custom-times - (get-text-property (point) 'display) - (not (get-text-property (1- (point)) 'display))) - (setq org-ts-what 'day)) - (setq org-ts-what (or what org-ts-what) + (setq origin-cat timestamp?) + (when (and (not what) (not (eq timestamp? 'day)) + org-display-custom-times + (get-text-property (point) 'display) + (not (get-text-property (1- (point)) 'display))) + (setq timestamp? 'day)) + (setq timestamp? (or what timestamp?) inactive (= (char-after (match-beginning 0)) ?\[) ts (match-string 0)) (replace-match "") @@ -17441,44 +17936,46 @@ When SUPPRESS-TMP-DELAY is non-nil, suppress delays like \"--2d\"." "\\(\\(-[012][0-9]:[0-5][0-9]\\)?\\( +[.+]?-?[-+][0-9]+[hdwmy]\\(/[0-9]+[hdwmy]\\)?\\)*\\)[]>]" ts) (setq extra (match-string 1 ts)) - (if suppress-tmp-delay - (setq extra (replace-regexp-in-string " --[0-9]+[hdwmy]" "" extra)))) - (if (string-match "^.\\{10\\}.*?[0-9]+:[0-9][0-9]" ts) - (setq with-hm t)) + (when suppress-tmp-delay + (setq extra (replace-regexp-in-string " --[0-9]+[hdwmy]" "" extra)))) + (when (string-match "^.\\{10\\}.*?[0-9]+:[0-9][0-9]" ts) + (setq with-hm t)) (setq time0 (org-parse-time-string ts)) (when (and updown - (eq org-ts-what 'minute) + (eq timestamp? 'minute) (not current-prefix-arg)) ;; This looks like s-up and s-down. Change by one rounding step. (setq n (* dm (cond ((> n 0) 1) ((< n 0) -1) (t 0)))) - (when (not (= 0 (setq rem (% (nth 1 time0) dm)))) + (unless (= 0 (setq rem (% (nth 1 time0) dm))) (setcar (cdr time0) (+ (nth 1 time0) (if (> n 0) (- rem) (- dm rem)))))) (setq time - (encode-time (or (car time0) 0) - (+ (if (eq org-ts-what 'minute) n 0) (nth 1 time0)) - (+ (if (eq org-ts-what 'hour) n 0) (nth 2 time0)) - (+ (if (eq org-ts-what 'day) n 0) (nth 3 time0)) - (+ (if (eq org-ts-what 'month) n 0) (nth 4 time0)) - (+ (if (eq org-ts-what 'year) n 0) (nth 5 time0)))) - (when (and (member org-ts-what '(hour minute)) + (apply #'encode-time + (or (car time0) 0) + (+ (if (eq timestamp? 'minute) n 0) (nth 1 time0)) + (+ (if (eq timestamp? 'hour) n 0) (nth 2 time0)) + (+ (if (eq timestamp? 'day) n 0) (nth 3 time0)) + (+ (if (eq timestamp? 'month) n 0) (nth 4 time0)) + (+ (if (eq timestamp? 'year) n 0) (nth 5 time0)) + (nthcdr 6 time0))) + (when (and (memq timestamp? '(hour minute)) extra (string-match "-\\([012][0-9]\\):\\([0-5][0-9]\\)" extra)) (setq extra (org-modify-ts-extra extra - (if (eq org-ts-what 'hour) 2 5) + (if (eq timestamp? 'hour) 2 5) n dm))) - (when (integerp org-ts-what) - (setq extra (org-modify-ts-extra extra org-ts-what n dm))) - (if (eq what 'calendar) - (let ((cal-date (org-get-date-from-calendar))) - (setcar (nthcdr 4 time0) (nth 0 cal-date)) ; month - (setcar (nthcdr 3 time0) (nth 1 cal-date)) ; day - (setcar (nthcdr 5 time0) (nth 2 cal-date)) ; year - (setcar time0 (or (car time0) 0)) - (setcar (nthcdr 1 time0) (or (nth 1 time0) 0)) - (setcar (nthcdr 2 time0) (or (nth 2 time0) 0)) - (setq time (apply 'encode-time time0)))) + (when (integerp timestamp?) + (setq extra (org-modify-ts-extra extra timestamp? n dm))) + (when (eq what 'calendar) + (let ((cal-date (org-get-date-from-calendar))) + (setcar (nthcdr 4 time0) (nth 0 cal-date)) ; month + (setcar (nthcdr 3 time0) (nth 1 cal-date)) ; day + (setcar (nthcdr 5 time0) (nth 2 cal-date)) ; year + (setcar time0 (or (car time0) 0)) + (setcar (nthcdr 1 time0) (or (nth 1 time0) 0)) + (setcar (nthcdr 2 time0) (or (nth 2 time0) 0)) + (setq time (apply 'encode-time time0)))) ;; Insert the new time-stamp, and ensure point stays in the same ;; category as before (i.e. not after the last position in that ;; category). @@ -17489,17 +17986,21 @@ When SUPPRESS-TMP-DELAY is non-nil, suppress delays like \"--2d\"." (goto-char pos)) (save-match-data (looking-at org-ts-regexp3) - (goto-char (cond - ;; `day' category ends before `hour' if any, or at - ;; the end of the day name. - ((eq origin-cat 'day) - (min (or (match-beginning 7) (1- (match-end 5))) origin)) - ((eq origin-cat 'hour) (min (match-end 7) origin)) - ((eq origin-cat 'minute) (min (1- (match-end 8)) origin)) - ((integerp origin-cat) (min (1- (match-end 0)) origin)) - ;; `year' and `month' have both fixed size: point - ;; couldn't have moved into another part. - (t origin)))) + (goto-char + (pcase origin-cat + ;; `day' category ends before `hour' if any, or at the end + ;; of the day name. + (`day (min (or (match-beginning 7) (1- (match-end 5))) origin)) + (`hour (min (match-end 7) origin)) + (`minute (min (1- (match-end 8)) origin)) + ((pred integerp) (min (1- (match-end 0)) origin)) + ;; Point was right after the time-stamp. However, the + ;; time-stamp length might have changed, so refer to + ;; (match-end 0) instead. + (`after (match-end 0)) + ;; `year' and `month' have both fixed size: point couldn't + ;; have moved into another part. + (_ origin)))) ;; Update clock if on a CLOCK line. (org-clock-update-time-maybe) ;; Maybe adjust the closest clock in `org-clock-history' @@ -17508,11 +18009,12 @@ When SUPPRESS-TMP-DELAY is non-nil, suppress delays like \"--2d\"." (< 1 (length (delq nil (mapcar 'marker-position org-clock-history)))))) (message "No clock to adjust") - (cond ((save-excursion ; fix previous clock? + (cond ((save-excursion ; fix previous clock? (re-search-backward org-ts-regexp0 nil t) - (org-looking-back (concat org-clock-string " \\["))) + (looking-back (concat org-clock-string " \\[") + (line-beginning-position))) (setq fixnext 1 clrgx (concat org-ts-regexp0 "\\] =>.*$"))) - ((save-excursion ; fix next clock? + ((save-excursion ; fix next clock? (re-search-backward org-ts-regexp0 nil t) (looking-at (concat org-ts-regexp0 "\\] =>"))) (setq fixnext -1 clrgx (concat org-clock-string " \\[" org-ts-regexp0)))) @@ -17521,8 +18023,8 @@ When SUPPRESS-TMP-DELAY is non-nil, suppress delays like \"--2d\"." (let* ((p (save-excursion (org-back-to-heading t))) (cl (mapcar (lambda(c) (abs (- (marker-position c) p))) org-clock-history)) (clfixnth - (+ fixnext (- (length cl) (or (length (member (apply #'min cl) cl)) 100)))) - (clfixpos (if (> 0 clfixnth) nil (nth clfixnth org-clock-history)))) + (+ fixnext (- (length cl) (or (length (member (apply 'min cl) cl)) 100)))) + (clfixpos (unless (> 0 clfixnth) (nth clfixnth org-clock-history)))) (if (not clfixpos) (message "No clock to adjust") (save-excursion @@ -17531,15 +18033,15 @@ When SUPPRESS-TMP-DELAY is non-nil, suppress delays like \"--2d\"." (when (re-search-forward clrgx nil t) (goto-char (match-beginning 1)) (let (org-clock-adjust-closest) - (org-timestamp-change n org-ts-what updown)) + (org-timestamp-change n timestamp? updown)) (message "Clock adjusted in %s for heading: %s" (file-name-nondirectory (buffer-file-name)) (org-get-heading t t))))))))) ;; Try to recenter the calendar window, if any. - (if (and org-calendar-follow-timestamp-change - (get-buffer-window "*Calendar*" t) - (memq org-ts-what '(day month year))) - (org-recenter-calendar (time-to-days time)))))) + (when (and org-calendar-follow-timestamp-change + (get-buffer-window "*Calendar*" t) + (memq timestamp? '(day month year))) + (org-recenter-calendar (time-to-days time)))))) (defun org-modify-ts-extra (s pos n dm) "Change the different parts of the lead-time and repeat fields in timestamp." @@ -17553,13 +18055,13 @@ When SUPPRESS-TMP-DELAY is non-nil, suppress delays like \"--2d\"." h (string-to-number (match-string 2 s))) (if (org-pos-in-match-range pos 2) (setq h (+ h n)) - (setq n (* dm (org-no-warnings (signum n)))) - (when (not (= 0 (setq rem (% m dm)))) + (setq n (* dm (with-no-warnings (signum n)))) + (unless (= 0 (setq rem (% m dm))) (setq m (+ m (if (> n 0) (- rem) (- dm rem))))) (setq m (+ m n))) - (if (< m 0) (setq m (+ m 60) h (1- h))) - (if (> m 59) (setq m (- m 60) h (1+ h))) - (setq h (min 24 (max 0 h))) + (when (< m 0) (setq m (+ m 60) h (1- h))) + (when (> m 59) (setq m (- m 60) h (1+ h))) + (setq h (mod h 24)) (setq ng 1 new (format "-%02d:%02d" h m))) ((org-pos-in-match-range pos 6) (setq ng 6 new (car (rassoc (+ n (cdr (assoc (match-string 6 s) idx))) idx)))) @@ -17578,35 +18080,32 @@ When SUPPRESS-TMP-DELAY is non-nil, suppress delays like \"--2d\"." (substring s (match-end ng)))))) s)) -(defun org-recenter-calendar (date) - "If the calendar is visible, recenter it to DATE." +(defun org-recenter-calendar (d) + "If the calendar is visible, recenter it to date D." (let ((cwin (get-buffer-window "*Calendar*" t))) (when cwin (let ((calendar-move-hook nil)) (with-selected-window cwin - (calendar-goto-date (if (listp date) date - (calendar-gregorian-from-absolute date)))))))) + (calendar-goto-date + (if (listp d) d (calendar-gregorian-from-absolute d)))))))) (defun org-goto-calendar (&optional arg) "Go to the Emacs calendar at the current date. If there is a time stamp in the current line, go to that date. A prefix ARG can be used to force the current date." (interactive "P") - (let ((tsr org-ts-regexp) diff - (calendar-move-hook nil) + (let ((calendar-move-hook nil) (calendar-view-holidays-initially-flag nil) - (calendar-view-diary-initially-flag nil)) - (if (or (org-at-timestamp-p) - (save-excursion - (beginning-of-line 1) - (looking-at (concat ".*" tsr)))) - (let ((d1 (time-to-days (current-time))) - (d2 (time-to-days - (org-time-string-to-time (match-string 1))))) - (setq diff (- d2 d1)))) + (calendar-view-diary-initially-flag nil) + diff) + (when (or (org-at-timestamp-p 'lax) + (org-match-line (concat ".*" org-ts-regexp))) + (let ((d1 (time-to-days (current-time))) + (d2 (time-to-days (org-time-string-to-time (match-string 1))))) + (setq diff (- d2 d1)))) (calendar) (calendar-goto-today) - (if (and diff (not arg)) (calendar-forward-day diff)))) + (when (and diff (not arg)) (calendar-forward-day diff)))) (defun org-get-date-from-calendar () "Return a list (month day year) of date at point in calendar." @@ -17618,14 +18117,15 @@ A prefix ARG can be used to force the current date." "Insert time stamp corresponding to cursor date in *Calendar* buffer. If there is already a time stamp at the cursor position, update it." (interactive) - (if (org-at-timestamp-p t) + (if (org-at-timestamp-p 'lax) (org-timestamp-change 0 'calendar) (let ((cal-date (org-get-date-from-calendar))) (org-insert-time-stamp (encode-time 0 0 0 (nth 1 cal-date) (car cal-date) (nth 2 cal-date)))))) (defcustom org-effort-durations - `(("h" . 60) + `(("min" . 1) + ("h" . 60) ("d" . ,(* 60 8)) ("w" . ,(* 60 8 5)) ("m" . ,(* 60 8 5 4)) @@ -17641,121 +18141,11 @@ minutes. For example, if the value of this variable is ((\"hours\" . 60)), then an effort string \"2hours\" is equivalent to 120 minutes." :group 'org-agenda - :version "24.1" + :version "26.1" + :package-version '(Org . "8.3") :type '(alist :key-type (string :tag "Modifier") :value-type (number :tag "Minutes"))) -(defun org-minutes-to-clocksum-string (m) - "Format number of minutes as a clocksum string. -The format is determined by `org-time-clocksum-format', -`org-time-clocksum-use-fractional' and -`org-time-clocksum-fractional-format' and -`org-time-clocksum-use-effort-durations'." - (let ((clocksum "") - (m (round m)) ; Don't allow fractions of minutes - h d w mo y fmt n) - (setq h (if org-time-clocksum-use-effort-durations - (cdr (assoc "h" org-effort-durations)) 60) - d (if org-time-clocksum-use-effort-durations - (/ (cdr (assoc "d" org-effort-durations)) h) 24) - w (if org-time-clocksum-use-effort-durations - (/ (cdr (assoc "w" org-effort-durations)) (* d h)) 7) - mo (if org-time-clocksum-use-effort-durations - (/ (cdr (assoc "m" org-effort-durations)) (* d h)) 30) - y (if org-time-clocksum-use-effort-durations - (/ (cdr (assoc "y" org-effort-durations)) (* d h)) 365)) - ;; fractional format - (if org-time-clocksum-use-fractional - (cond - ;; single format string - ((stringp org-time-clocksum-fractional-format) - (format org-time-clocksum-fractional-format (/ m (float h)))) - ;; choice of fractional formats for different time units - ((and (setq fmt (plist-get org-time-clocksum-fractional-format :years)) - (> (/ (truncate m) (* y d h)) 0)) - (format fmt (/ m (* y d (float h))))) - ((and (setq fmt (plist-get org-time-clocksum-fractional-format :months)) - (> (/ (truncate m) (* mo d h)) 0)) - (format fmt (/ m (* mo d (float h))))) - ((and (setq fmt (plist-get org-time-clocksum-fractional-format :weeks)) - (> (/ (truncate m) (* w d h)) 0)) - (format fmt (/ m (* w d (float h))))) - ((and (setq fmt (plist-get org-time-clocksum-fractional-format :days)) - (> (/ (truncate m) (* d h)) 0)) - (format fmt (/ m (* d (float h))))) - ((and (setq fmt (plist-get org-time-clocksum-fractional-format :hours)) - (> (/ (truncate m) h) 0)) - (format fmt (/ m (float h)))) - ((setq fmt (plist-get org-time-clocksum-fractional-format :minutes)) - (format fmt m)) - ;; fall back to smallest time unit with a format - ((setq fmt (plist-get org-time-clocksum-fractional-format :hours)) - (format fmt (/ m (float h)))) - ((setq fmt (plist-get org-time-clocksum-fractional-format :days)) - (format fmt (/ m (* d (float h))))) - ((setq fmt (plist-get org-time-clocksum-fractional-format :weeks)) - (format fmt (/ m (* w d (float h))))) - ((setq fmt (plist-get org-time-clocksum-fractional-format :months)) - (format fmt (/ m (* mo d (float h))))) - ((setq fmt (plist-get org-time-clocksum-fractional-format :years)) - (format fmt (/ m (* y d (float h)))))) - ;; standard (non-fractional) format, with single format string - (if (stringp org-time-clocksum-format) - (format org-time-clocksum-format (setq n (/ m h)) (- m (* h n))) - ;; separate formats components - (and (setq fmt (plist-get org-time-clocksum-format :years)) - (or (> (setq n (/ (truncate m) (* y d h))) 0) - (plist-get org-time-clocksum-format :require-years)) - (setq clocksum (concat clocksum (format fmt n)) - m (- m (* n y d h)))) - (and (setq fmt (plist-get org-time-clocksum-format :months)) - (or (> (setq n (/ (truncate m) (* mo d h))) 0) - (plist-get org-time-clocksum-format :require-months)) - (setq clocksum (concat clocksum (format fmt n)) - m (- m (* n mo d h)))) - (and (setq fmt (plist-get org-time-clocksum-format :weeks)) - (or (> (setq n (/ (truncate m) (* w d h))) 0) - (plist-get org-time-clocksum-format :require-weeks)) - (setq clocksum (concat clocksum (format fmt n)) - m (- m (* n w d h)))) - (and (setq fmt (plist-get org-time-clocksum-format :days)) - (or (> (setq n (/ (truncate m) (* d h))) 0) - (plist-get org-time-clocksum-format :require-days)) - (setq clocksum (concat clocksum (format fmt n)) - m (- m (* n d h)))) - (and (setq fmt (plist-get org-time-clocksum-format :hours)) - (or (> (setq n (/ (truncate m) h)) 0) - (plist-get org-time-clocksum-format :require-hours)) - (setq clocksum (concat clocksum (format fmt n)) - m (- m (* n h)))) - (and (setq fmt (plist-get org-time-clocksum-format :minutes)) - (or (> m 0) (plist-get org-time-clocksum-format :require-minutes)) - (setq clocksum (concat clocksum (format fmt m)))) - ;; return formatted time duration - clocksum)))) - -(defalias 'org-minutes-to-hh:mm-string 'org-minutes-to-clocksum-string) -(make-obsolete 'org-minutes-to-hh:mm-string 'org-minutes-to-clocksum-string - "Org mode version 8.0") - -(defun org-hours-to-clocksum-string (n) - (org-minutes-to-clocksum-string (* n 60))) - -(defun org-hh:mm-string-to-minutes (s) - "Convert a string H:MM to a number of minutes. -If the string is just a number, interpret it as minutes. -In fact, the first hh:mm or number in the string will be taken, -there can be extra stuff in the string. -If no number is found, the return value is 0." - (cond - ((integerp s) s) - ((string-match "\\([0-9]+\\):\\([0-9]+\\)" s) - (+ (* (string-to-number (match-string 1 s)) 60) - (string-to-number (match-string 2 s)))) - ((string-match "\\([0-9]+\\)" s) - (string-to-number (match-string 1 s))) - (t 0))) - (defcustom org-image-actual-width t "Should we use the actual width of images when inlining them? @@ -17793,53 +18183,35 @@ tables are not re-aligned, etc." :version "24.3" :group 'org-agenda) -(defcustom org-agenda-ignore-drawer-properties nil +(defcustom org-agenda-ignore-properties nil "Avoid updating text properties when building the agenda. -Properties are used to prepare buffers for effort estimates, appointments, -and subtree-local categories. -If you don't use these in the agenda, you can add them to this list and -agenda building will be a bit faster. +Properties are used to prepare buffers for effort estimates, +appointments, statistics and subtree-local categories. +If you don't use these in the agenda, you can add them to this +list and agenda building will be a bit faster. The value is a list, with zero or more of the symbols `effort', `appt', -or `category'." +`stats' or `category'." :type '(set :greedy t (const effort) (const appt) + (const stats) (const category)) - :version "24.3" + :version "26.1" + :package-version '(Org . "8.3") :group 'org-agenda) -(defun org-duration-string-to-minutes (s &optional output-to-string) - "Convert a duration string S to minutes. - -A bare number is interpreted as minutes, modifiers can be set by -customizing `org-effort-durations' (which see). - -Entries containing a colon are interpreted as H:MM by -`org-hh:mm-string-to-minutes'." - (let ((result 0) - (re (concat "\\([0-9.]+\\) *\\(" - (regexp-opt (mapcar 'car org-effort-durations)) - "\\)"))) - (while (string-match re s) - (incf result (* (cdr (assoc (match-string 2 s) org-effort-durations)) - (string-to-number (match-string 1 s)))) - (setq s (replace-match "" nil t s))) - (setq result (floor result)) - (incf result (org-hh:mm-string-to-minutes s)) - (if output-to-string (number-to-string result) result))) - ;;;; Files (defun org-save-all-org-buffers () - "Save all Org-mode buffers without user confirmation." + "Save all Org buffers without user confirmation." (interactive) - (message "Saving all Org-mode buffers...") + (message "Saving all Org buffers...") (save-some-buffers t (lambda () (derived-mode-p 'org-mode))) (when (featurep 'org-id) (org-id-locations-save)) - (message "Saving all Org-mode buffers... done")) + (message "Saving all Org buffers... done")) (defun org-revert-all-org-buffers () - "Revert all Org-mode buffers. + "Revert all Org buffers. Prompt for confirmation when there are unsaved changes. Be sure you know what you are doing before letting this function overwrite your changes. @@ -17856,13 +18228,11 @@ changes from another. I believe the procedure must be like this: (user-error "Abort")) (save-excursion (save-window-excursion - (mapc - (lambda (b) - (when (and (with-current-buffer b (derived-mode-p 'org-mode)) - (with-current-buffer b buffer-file-name)) - (org-pop-to-buffer-same-window b) - (revert-buffer t 'no-confirm))) - (buffer-list)) + (dolist (b (buffer-list)) + (when (and (with-current-buffer b (derived-mode-p 'org-mode)) + (with-current-buffer b buffer-file-name)) + (pop-to-buffer-same-window b) + (revert-buffer t 'no-confirm))) (when (and (featurep 'org-id) org-id-track-globally) (org-id-locations-load))))) @@ -17871,29 +18241,19 @@ changes from another. I believe the procedure must be like this: ;;;###autoload (defun org-switchb (&optional arg) "Switch between Org buffers. -With one prefix argument, restrict available buffers to files. -With two prefix arguments, restrict available buffers to agenda files. -Defaults to `iswitchb' for buffer name completion. -Set `org-completion-use-ido' to make it use ido instead." +With `\\[universal-argument]' prefix, restrict available buffers to files. + +With `\\[universal-argument] \\[universal-argument]' \ +prefix, restrict available buffers to agenda files." (interactive "P") - (let ((blist (cond ((equal arg '(4)) (org-buffer-list 'files)) - ((equal arg '(16)) (org-buffer-list 'agenda)) - (t (org-buffer-list)))) - (org-completion-use-iswitchb org-completion-use-iswitchb) - (org-completion-use-ido org-completion-use-ido)) - (unless (or org-completion-use-ido org-completion-use-iswitchb) - (setq org-completion-use-iswitchb t)) - (org-pop-to-buffer-same-window - (org-icompleting-read "Org buffer: " - (mapcar 'list (mapcar 'buffer-name blist)) - nil t)))) - -;;; Define some older names previously used for this functionality -;;;###autoload -(defalias 'org-ido-switchb 'org-switchb) -;;;###autoload -(defalias 'org-iswitchb 'org-switchb) + (let ((blist (org-buffer-list + (cond ((equal arg '(4)) 'files) + ((equal arg '(16)) 'agenda))))) + (pop-to-buffer-same-window + (completing-read "Org buffer: " + (mapcar #'list (mapcar #'buffer-name blist)) + nil t)))) (defun org-buffer-list (&optional predicate exclude-tmp) "Return a list of Org buffers. @@ -17968,8 +18328,10 @@ used by the agenda files. If ARCHIVE is `ifmode', do this only if "Return non-nil, if FILE is an agenda file. If FILE is omitted, use the file associated with the current buffer." - (member (or file (buffer-file-name)) - (org-agenda-files t))) + (let ((fname (or file (buffer-file-name)))) + (and fname + (member (file-truename fname) + (mapcar #'file-truename (org-agenda-files t)))))) (defun org-edit-agenda-file-list () "Edit the list of agenda files. @@ -17981,15 +18343,15 @@ the buffer and restores the previous window configuration." (if (stringp org-agenda-files) (let ((cw (current-window-configuration))) (find-file org-agenda-files) - (org-set-local 'org-window-configuration cw) - (org-add-hook 'after-save-hook - (lambda () - (set-window-configuration - (prog1 org-window-configuration - (kill-buffer (current-buffer)))) - (org-install-agenda-files-menu) - (message "New agenda file list installed")) - nil 'local) + (setq-local org-window-configuration cw) + (add-hook 'after-save-hook + (lambda () + (set-window-configuration + (prog1 org-window-configuration + (kill-buffer (current-buffer)))) + (org-install-agenda-files-menu) + (message "New agenda file list installed")) + nil 'local) (message "%s" (substitute-command-keys "Edit list and finish with \\[save-buffer]"))) (customize-variable 'org-agenda-files))) @@ -18039,19 +18401,16 @@ un-expanded file names." If the current buffer visits an agenda file, find the next one in the list. If the current buffer does not, find the first agenda file." (interactive) - (let* ((fs (org-agenda-files t)) - (files (append fs (list (car fs)))) - (tcf (if buffer-file-name (file-truename buffer-file-name))) + (let* ((fs (or (org-agenda-files t) + (user-error "No agenda files"))) + (files (copy-sequence fs)) + (tcf (and buffer-file-name (file-truename buffer-file-name))) file) - (unless files (user-error "No agenda files")) - (catch 'exit - (dolist (file files) - (if (equal (file-truename file) tcf) - (when (car files) - (find-file (car files)) - (throw 'exit t)))) - (find-file (car fs))) - (if (buffer-base-buffer) (org-pop-to-buffer-same-window (buffer-base-buffer))))) + (when tcf + (while (and (setq file (pop files)) + (not (equal (file-truename file) tcf))))) + (find-file (car (or files fs))) + (when (buffer-base-buffer) (pop-to-buffer-same-window (buffer-base-buffer))))) (defun org-agenda-file-to-front (&optional to-end) "Move/add the current file to the top of the agenda file list. @@ -18069,7 +18428,7 @@ end of the list." x had) (setq x (assoc ctf file-alist) had x) - (if (not x) (setq x (cons ctf (abbreviate-file-name buffer-file-name)))) + (unless x (setq x (cons ctf (abbreviate-file-name buffer-file-name)))) (if to-end (setq file-alist (append (delq x file-alist) (list x))) (setq file-alist (cons x (delq x file-alist)))) @@ -18090,15 +18449,15 @@ Optional argument FILE means use this file instead of the current." (afile (abbreviate-file-name file)) (files (delq nil (mapcar (lambda (x) - (if (equal true-file - (file-truename x)) - nil x)) + (unless (equal true-file + (file-truename x)) + x)) (org-agenda-files t))))) (if (not (= (length files) (length (org-agenda-files t)))) (progn (org-store-new-agenda-file-list files) (org-install-agenda-files-menu) - (message "Removed file: %s" afile)) + (message "Removed from Org Agenda list: %s" afile)) (message "File was not in list: %s (not removed)" afile)))) (defun org-file-menu-entry (file) @@ -18106,7 +18465,7 @@ Optional argument FILE means use this file instead of the current." (defun org-check-agenda-file (file) "Make sure FILE exists. If not, ask user what to do." - (when (not (file-exists-p file)) + (unless (file-exists-p file) (message "Non-existent agenda file %s. [R]emove from list or [A]bort?" (abbreviate-file-name file)) (let ((r (downcase (read-char-exclusive)))) @@ -18114,17 +18473,18 @@ Optional argument FILE means use this file instead of the current." ((equal r ?r) (org-remove-file file) (throw 'nextfile t)) - (t (error "Abort")))))) + (t (user-error "Abort")))))) (defun org-get-agenda-file-buffer (file) - "Get a buffer visiting FILE. If the buffer needs to be created, add -it to the list of buffers which might be released later." + "Get an agenda buffer visiting FILE. +If the buffer needs to be created, add it to the list of buffers +which might be released later." (let ((buf (org-find-base-buffer-visiting file))) (if buf buf ; just return it ;; Make a new buffer and remember it (setq buf (find-file-noselect file)) - (if buf (push buf org-agenda-new-buffers)) + (when buf (push buf org-agenda-new-buffers)) buf))) (defun org-release-buffers (blist) @@ -18149,7 +18509,7 @@ When a buffer is unmodified, it is just killed. When modified, it is saved (inhibit-read-only t) (org-inhibit-startup org-agenda-inhibit-startup) (rea (concat ":" org-archive-tag ":")) - file re pos) + re pos) (setq org-tag-alist-for-agenda nil org-tag-groups-alist-for-agenda nil) (save-excursion @@ -18161,20 +18521,15 @@ When a buffer is unmodified, it is just killed. When modified, it is saved (org-check-agenda-file file) (set-buffer (org-get-agenda-file-buffer file))) (widen) - (org-set-regexps-and-options-for-tags) + (org-set-regexps-and-options 'tags-only) (setq pos (point)) - (goto-char (point-min)) - (let ((case-fold-search t)) - (when (search-forward "#+setupfile" nil t) - ;; Don't set all regexps and options systematically as - ;; this is only run for setting agenda tags from setup - ;; file - (org-set-regexps-and-options))) - (or (memq 'category org-agenda-ignore-drawer-properties) + (or (memq 'category org-agenda-ignore-properties) (org-refresh-category-properties)) - (or (memq 'effort org-agenda-ignore-drawer-properties) - (org-refresh-properties org-effort-property 'org-effort)) - (or (memq 'appt org-agenda-ignore-drawer-properties) + (or (memq 'stats org-agenda-ignore-properties) + (org-refresh-stats-properties)) + (or (memq 'effort org-agenda-ignore-properties) + (org-refresh-effort-properties)) + (or (memq 'appt org-agenda-ignore-properties) (org-refresh-properties "APPT_WARNTIME" 'org-appt-warntime)) (setq org-todo-keywords-for-agenda (append org-todo-keywords-for-agenda org-todo-keywords-1)) @@ -18182,31 +18537,32 @@ When a buffer is unmodified, it is just killed. When modified, it is saved (append org-done-keywords-for-agenda org-done-keywords)) (setq org-todo-keyword-alist-for-agenda (append org-todo-keyword-alist-for-agenda org-todo-key-alist)) - (setq org-drawers-for-agenda - (append org-drawers-for-agenda org-drawers)) (setq org-tag-alist-for-agenda (org-uniquify (append org-tag-alist-for-agenda - org-tag-alist - org-tag-persistent-alist))) - (if org-group-tags - (setq org-tag-groups-alist-for-agenda - (org-uniquify-alist - (append org-tag-groups-alist-for-agenda org-tag-groups-alist)))) + org-current-tag-alist))) + ;; Merge current file's tag groups into global + ;; `org-tag-groups-alist-for-agenda'. + (when org-group-tags + (dolist (alist org-tag-groups-alist) + (let ((old (assoc (car alist) org-tag-groups-alist-for-agenda))) + (if old + (setcdr old (org-uniquify (append (cdr old) (cdr alist)))) + (push alist org-tag-groups-alist-for-agenda))))) (org-with-silent-modifications (save-excursion (remove-text-properties (point-min) (point-max) pall) (when org-agenda-skip-archived-trees (goto-char (point-min)) (while (re-search-forward rea nil t) - (if (org-at-heading-p t) - (add-text-properties (point-at-bol) (org-end-of-subtree t) pa)))) + (when (org-at-heading-p t) + (add-text-properties (point-at-bol) (org-end-of-subtree t) pa)))) (goto-char (point-min)) - (setq re (format org-heading-keyword-regexp-format - org-comment-string)) + (setq re (format "^\\*+ .*\\<%s\\>" org-comment-string)) (while (re-search-forward re nil t) - (add-text-properties - (match-beginning 0) (org-end-of-subtree t) pc)))) + (when (save-match-data (org-in-commented-heading-p t)) + (add-text-properties + (match-beginning 0) (org-end-of-subtree t) pc))))) (goto-char pos))))) (setq org-todo-keywords-for-agenda (org-uniquify org-todo-keywords-for-agenda)) @@ -18223,7 +18579,7 @@ When a buffer is unmodified, it is just killed. When modified, it is saved (org-defkey org-cdlatex-mode-map "^" 'org-cdlatex-underscore-caret) (org-defkey org-cdlatex-mode-map "`" 'cdlatex-math-symbol) (org-defkey org-cdlatex-mode-map "'" 'org-cdlatex-math-modify) -(org-defkey org-cdlatex-mode-map "\C-c{" 'cdlatex-environment) +(org-defkey org-cdlatex-mode-map "\C-c{" 'org-cdlatex-environment-indent) (defvar org-cdlatex-texmathp-advice-is-done nil "Flag remembering if we have applied the advice to texmathp already.") @@ -18231,7 +18587,7 @@ When a buffer is unmodified, it is just killed. When modified, it is saved (define-minor-mode org-cdlatex-mode "Toggle the minor `org-cdlatex-mode'. This mode supports entering LaTeX environment and math in LaTeX fragments -in Org-mode. +in Org mode. \\{org-cdlatex-mode-map}" nil " OCDL" nil (when org-cdlatex-mode @@ -18241,11 +18597,11 @@ in Org-mode. (unless org-cdlatex-texmathp-advice-is-done (setq org-cdlatex-texmathp-advice-is-done t) (defadvice texmathp (around org-math-always-on activate) - "Always return t in org-mode buffers. + "Always return t in Org buffers. This is because we want to insert math symbols without dollars even outside -the LaTeX math segments. If Orgmode thinks that point is actually inside -an embedded LaTeX fragment, let texmathp do its job. -\\[org-cdlatex-mode-map]" +the LaTeX math segments. If Org mode thinks that point is actually inside +an embedded LaTeX fragment, let `texmathp' do its job. +`\\[org-cdlatex-mode-map]'" (interactive) (let (p) (cond @@ -18257,8 +18613,8 @@ an embedded LaTeX fragment, let texmathp do its job. (let ((p (org-inside-LaTeX-fragment-p))) (if (and p (member (car p) (plist-get org-format-latex-options :matchers))) (setq ad-return-value t - texmathp-why '("Org-mode embedded math" . 0)) - (if p ad-do-it))))))))) + texmathp-why '("Org mode embedded math" . 0)) + (when p ad-do-it))))))))) (defun turn-on-org-cdlatex () "Unconditionally turn on `org-cdlatex-mode'." @@ -18283,7 +18639,7 @@ It makes sense to do so if `org-cdlatex-mode' is active and if the cursor is (cdlatex-tab) t) ((org-inside-LaTeX-fragment-p) (cdlatex-tab) t)))) -(defun org-cdlatex-underscore-caret (&optional arg) +(defun org-cdlatex-underscore-caret (&optional _arg) "Execute `cdlatex-sub-superscript' in LaTeX fragments. Revert to the normal definition outside of these fragments." (interactive "P") @@ -18292,7 +18648,7 @@ Revert to the normal definition outside of these fragments." (let (org-cdlatex-mode) (call-interactively (key-binding (vector last-input-event)))))) -(defun org-cdlatex-math-modify (&optional arg) +(defun org-cdlatex-math-modify (&optional _arg) "Execute `cdlatex-math-modify' in LaTeX fragments. Revert to the normal definition outside of these fragments." (interactive "P") @@ -18301,21 +18657,66 @@ Revert to the normal definition outside of these fragments." (let (org-cdlatex-mode) (call-interactively (key-binding (vector last-input-event)))))) +(defun org-cdlatex-environment-indent (&optional environment item) + "Execute `cdlatex-environment' and indent the inserted environment. + +ENVIRONMENT and ITEM are passed to `cdlatex-environment'. + +The inserted environment is indented to current indentation +unless point is at the beginning of the line, in which the +environment remains unintended." + (interactive) + ;; cdlatex-environment always return nil. Therefore, capture output + ;; first and determine if an environment was selected. + (let* ((beg (point-marker)) + (end (copy-marker (point) t)) + (inserted (progn + (ignore-errors (cdlatex-environment environment item)) + (< beg end))) + ;; Figure out how many lines to move forward after the + ;; environment has been inserted. + (lines (when inserted + (save-excursion + (- (cl-loop while (< beg (point)) + with x = 0 + do (forward-line -1) + (cl-incf x) + finally return x) + (if (progn (goto-char beg) + (and (progn (skip-chars-forward " \t") (eolp)) + (progn (skip-chars-backward " \t") (bolp)))) + 1 0))))) + (env (org-trim (delete-and-extract-region beg end)))) + (when inserted + ;; Get indentation of next line unless at column 0. + (let ((ind (if (bolp) 0 + (save-excursion + (org-return-indent) + (prog1 (org-get-indentation) + (when (progn (skip-chars-forward " \t") (eolp)) + (delete-region beg (point))))))) + (bol (progn (skip-chars-backward " \t") (bolp)))) + ;; Insert a newline before environment unless at column zero + ;; to "escape" the current line. Insert a newline if + ;; something is one the same line as \end{ENVIRONMENT}. + (insert + (concat (unless bol "\n") env + (when (and (skip-chars-forward " \t") (not (eolp))) "\n"))) + (unless (zerop ind) + (save-excursion + (goto-char beg) + (while (< (point) end) + (unless (eolp) (indent-line-to ind)) + (forward-line)))) + (goto-char beg) + (forward-line lines) + (indent-line-to ind))) + (set-marker beg nil) + (set-marker end nil))) ;;;; LaTeX fragments -(defvar org-latex-regexps - '(("begin" "^[ \t]*\\(\\\\begin{\\([a-zA-Z0-9\\*]+\\)[^\000]+?\\\\end{\\2}\\)" 1 t) - ;; ("$" "\\([ (]\\|^\\)\\(\\(\\([$]\\)\\([^ \r\n,.$].*?\\(\n.*?\\)\\{0,5\\}[^ \r\n,.$]\\)\\4\\)\\)\\([ .,?;:'\")]\\|$\\)" 2 nil) - ;; \000 in the following regex is needed for org-inside-LaTeX-fragment-p - ("$1" "\\([^$]\\|^\\)\\(\\$[^ \r\n,;.$]\\$\\)\\([- .,?;:'\")\000]\\|$\\)" 2 nil) - ("$" "\\([^$]\\|^\\)\\(\\(\\$\\([^ \r\n,;.$][^$\n\r]*?\\(\n[^$\n\r]*?\\)\\{0,2\\}[^ \r\n,.$]\\)\\$\\)\\)\\([- .,?;:'\")\000]\\|$\\)" 2 nil) - ("\\(" "\\\\([^\000]*?\\\\)" 0 nil) - ("\\[" "\\\\\\[[^\000]*?\\\\\\]" 0 nil) - ("$$" "\\$\\$[^\000]*?\\$\\$" 0 nil)) - "Regular expressions for matching embedded LaTeX.") - (defun org-inside-LaTeX-fragment-p () "Test if point is inside a LaTeX fragment. I.e. after a \\begin, \\(, \\[, $, or $$, without the corresponding closing @@ -18335,9 +18736,7 @@ looks only before point, not after." (catch 'exit (let ((pos (point)) (dodollar (member "$" (plist-get org-format-latex-options :matchers))) - (lim (progn - (re-search-backward (concat "^\\(" paragraph-start "\\)") nil t) - (point))) + (lim (save-excursion (org-backward-paragraph) (point))) dd-on str (start 0) m re) (goto-char pos) (when dodollar @@ -18358,7 +18757,7 @@ looks only before point, not after." (while (re-search-backward "\\$\\$" lim t) (setq dd-on (not dd-on))) (goto-char pos) - (if dd-on (cons "$$" m)))))) + (when dd-on (cons "$$" m)))))) (defun org-inside-latex-macro-p () "Is point inside a LaTeX macro or its arguments?" @@ -18366,179 +18765,226 @@ looks only before point, not after." (org-in-regexp "\\\\[a-zA-Z]+\\*?\\(\\(\\[[^][\n{}]*\\]\\)\\|\\({[^{}\n]*}\\)\\)*"))) -(defvar org-latex-fragment-image-overlays nil - "List of overlays carrying the images of latex fragments.") -(make-variable-buffer-local 'org-latex-fragment-image-overlays) +(defun org--format-latex-make-overlay (beg end image &optional imagetype) + "Build an overlay between BEG and END using IMAGE file. +Argument IMAGETYPE is the extension of the displayed image, +as a string. It defaults to \"png\"." + (let ((ov (make-overlay beg end)) + (imagetype (or (intern imagetype) 'png))) + (overlay-put ov 'org-overlay-type 'org-latex-overlay) + (overlay-put ov 'evaporate t) + (overlay-put ov + 'modification-hooks + (list (lambda (o _flag _beg _end &optional _l) + (delete-overlay o)))) + (overlay-put ov + 'display + (list 'image :type imagetype :file image :ascent 'center)))) + +(defun org--list-latex-overlays (&optional beg end) + "List all Org LaTeX overlays in current buffer. +Limit to overlays between BEG and END when those are provided." + (cl-remove-if-not + (lambda (o) (eq (overlay-get o 'org-overlay-type) 'org-latex-overlay)) + (overlays-in (or beg (point-min)) (or end (point-max))))) + +(defun org-remove-latex-fragment-image-overlays (&optional beg end) + "Remove all overlays with LaTeX fragment images in current buffer. +When optional arguments BEG and END are non-nil, remove all +overlays between them instead. Return a non-nil value when some +overlays were removed, nil otherwise." + (let ((overlays (org--list-latex-overlays beg end))) + (mapc #'delete-overlay overlays) + overlays)) + +(defun org-toggle-latex-fragment (&optional arg) + "Preview the LaTeX fragment at point, or all locally or globally. -(defun org-remove-latex-fragment-image-overlays () - "Remove all overlays with LaTeX fragment images in current buffer." - (mapc 'delete-overlay org-latex-fragment-image-overlays) - (setq org-latex-fragment-image-overlays nil)) +If the cursor is on a LaTeX fragment, create the image and overlay +it over the source code, if there is none. Remove it otherwise. +If there is no fragment at point, display all fragments in the +current section. -(defun org-preview-latex-fragment (&optional subtree) - "Preview the LaTeX fragment at point, or all locally or globally. -If the cursor is in a LaTeX fragment, create the image and overlay -it over the source code. If there is no fragment at point, display -all fragments in the current text, from one headline to the next. With -prefix SUBTREE, display all fragments in the current subtree. With a -double prefix arg \\[universal-argument] \\[universal-argument], or when \ -the cursor is before the first headline, -display all fragments in the buffer. -The images can be removed again with \\[org-ctrl-c-ctrl-c]." +With prefix ARG, preview or clear image for all fragments in the +current subtree or in the whole buffer when used before the first +headline. With a prefix ARG `\\[universal-argument] \ +\\[universal-argument]' preview or clear images +for all fragments in the buffer." (interactive "P") - (unless buffer-file-name - (user-error "Can't preview LaTeX fragment in a non-file buffer")) (when (display-graphic-p) - (org-remove-latex-fragment-image-overlays) - (save-excursion - (save-restriction - (let (beg end at msg) + (catch 'exit + (save-excursion + (let (beg end msg) (cond - ((or (equal subtree '(16)) - (not (save-excursion - (re-search-backward org-outline-regexp-bol nil t)))) - (setq beg (point-min) end (point-max) - msg "Creating images for buffer...%s")) - ((equal subtree '(4)) - (org-back-to-heading) - (setq beg (point) end (org-end-of-subtree t) - msg "Creating images for subtree...%s")) + ((or (equal arg '(16)) + (and (equal arg '(4)) + (org-with-limited-levels (org-before-first-heading-p)))) + (if (org-remove-latex-fragment-image-overlays) + (progn (message "LaTeX fragments images removed from buffer") + (throw 'exit nil)) + (setq msg "Creating images for buffer..."))) + ((equal arg '(4)) + (org-with-limited-levels (org-back-to-heading t)) + (setq beg (point)) + (setq end (progn (org-end-of-subtree t) (point))) + (if (org-remove-latex-fragment-image-overlays beg end) + (progn + (message "LaTeX fragment images removed from subtree") + (throw 'exit nil)) + (setq msg "Creating images for subtree..."))) + ((let ((datum (org-element-context))) + (when (memq (org-element-type datum) + '(latex-environment latex-fragment)) + (setq beg (org-element-property :begin datum)) + (setq end (org-element-property :end datum)) + (if (org-remove-latex-fragment-image-overlays beg end) + (progn (message "LaTeX fragment image removed") + (throw 'exit nil)) + (setq msg "Creating image..."))))) (t - (if (setq at (org-inside-LaTeX-fragment-p)) - (goto-char (max (point-min) (- (cdr at) 2))) - (org-back-to-heading)) - (setq beg (point) end (progn (outline-next-heading) (point)) - msg (if at "Creating image...%s" - "Creating images for entry...%s")))) - (message msg "") - (narrow-to-region beg end) - (goto-char beg) - (org-format-latex - (concat org-latex-preview-ltxpng-directory (file-name-sans-extension - (file-name-nondirectory - buffer-file-name))) - default-directory 'overlays msg at 'forbuffer - org-latex-create-formula-image-program) - (message msg "done. Use `C-c C-c' to remove images.")))))) - -(defun org-format-latex (prefix &optional dir overlays msg at - forbuffer processing-type) - "Replace LaTeX fragments with links to an image, and produce images. + (org-with-limited-levels + (setq beg (if (org-at-heading-p) (line-beginning-position) + (outline-previous-heading) + (point))) + (setq end (progn (outline-next-heading) (point))) + (if (org-remove-latex-fragment-image-overlays beg end) + (progn + (message "LaTeX fragment images removed from section") + (throw 'exit nil)) + (setq msg "Creating images for section..."))))) + (let ((file (buffer-file-name (buffer-base-buffer)))) + (org-format-latex + (concat org-preview-latex-image-directory "org-ltximg") + beg end + ;; Emacs cannot overlay images from remote hosts. Create + ;; it in `temporary-file-directory' instead. + (if (or (not file) (file-remote-p file)) + temporary-file-directory + default-directory) + 'overlays msg 'forbuffer org-preview-latex-default-process)) + (message (concat msg "done"))))))) + +(defun org-format-latex + (prefix &optional beg end dir overlays msg forbuffer processing-type) + "Replace LaTeX fragments with links to an image. + +The function takes care of creating the replacement image. + +Only consider fragments between BEG and END when those are +provided. + +When optional argument OVERLAYS is non-nil, display the image on +top of the fragment instead of replacing it. + +PROCESSING-TYPE is the conversion method to use, as a symbol. + Some of the options can be changed using the variable -`org-format-latex-options'." - (if (and overlays (fboundp 'clear-image-cache)) (clear-image-cache)) - (let* ((prefixnodir (file-name-nondirectory prefix)) - (absprefix (expand-file-name prefix dir)) - (todir (file-name-directory absprefix)) - (opt org-format-latex-options) - (optnew org-format-latex-options) - (matchers (plist-get opt :matchers)) - (re-list org-latex-regexps) - (cnt 0) txt hash link beg end re checkdir - string - m n block-type block linkfile movefile ov) - ;; Check the different regular expressions - (dolist (e re-list) - (setq m (car e) re (nth 1 e) n (nth 2 e) block-type (nth 3 e) - block (if block-type "\n\n" "")) - (when (member m matchers) - (goto-char (point-min)) - (while (re-search-forward re nil t) - (when (and (or (not at) (equal (cdr at) (match-beginning n))) - (or (not overlays) - (not (eq (get-char-property (match-beginning n) - 'org-overlay-type) - 'org-latex-overlay)))) - (cond - ((eq processing-type 'verbatim)) - ((eq processing-type 'mathjax) - ;; Prepare for MathJax processing. - (setq string (match-string n)) - (when (member m '("$" "$1")) - (save-excursion - (delete-region (match-beginning n) (match-end n)) - (goto-char (match-beginning n)) - (insert (concat "\\(" (substring string 1 -1) "\\)"))))) - ((or (eq processing-type 'dvipng) - (eq processing-type 'imagemagick)) - ;; Process to an image. - (setq txt (match-string n) - beg (match-beginning n) end (match-end n) - cnt (1+ cnt)) - (let ((face (face-at-point)) - (fg (plist-get opt :foreground)) - (bg (plist-get opt :background)) - ;; Ensure full list is printed. - print-length print-level) - (when forbuffer - ;; Get the colors from the face at point. +`org-format-latex-options', which see." + (when (and overlays (fboundp 'clear-image-cache)) (clear-image-cache)) + (unless (eq processing-type 'verbatim) + (let* ((math-regexp "\\$\\|\\\\[([]\\|^[ \t]*\\\\begin{[A-Za-z0-9*]+}") + (cnt 0) + checkdir-flag) + (goto-char (or beg (point-min))) + ;; Optimize overlay creation: (info "(elisp) Managing Overlays"). + (when (and overlays (memq processing-type '(dvipng imagemagick))) + (overlay-recenter (or end (point-max)))) + (while (re-search-forward math-regexp end t) + (unless (and overlays + (eq (get-char-property (point) 'org-overlay-type) + 'org-latex-overlay)) + (let* ((context (org-element-context)) + (type (org-element-type context))) + (when (memq type '(latex-environment latex-fragment)) + (let ((block-type (eq type 'latex-environment)) + (value (org-element-property :value context)) + (beg (org-element-property :begin context)) + (end (save-excursion + (goto-char (org-element-property :end context)) + (skip-chars-backward " \r\t\n") + (point)))) + (cond + ((eq processing-type 'mathjax) + ;; Prepare for MathJax processing. + (if (not (string-match "\\`\\$\\$?" value)) + (goto-char end) + (delete-region beg end) + (if (string= (match-string 0 value) "$$") + (insert "\\[" (substring value 2 -2) "\\]") + (insert "\\(" (substring value 1 -1) "\\)")))) + ((assq processing-type org-preview-latex-process-alist) + ;; Process to an image. + (cl-incf cnt) (goto-char beg) - (when (eq fg 'auto) - (setq fg (face-attribute face :foreground nil 'default))) - (when (eq bg 'auto) - (setq bg (face-attribute face :background nil 'default))) - (setq optnew (copy-sequence opt)) - (plist-put optnew :foreground fg) - (plist-put optnew :background bg)) - (setq hash (sha1 (prin1-to-string - (list org-format-latex-header - org-latex-default-packages-alist - org-latex-packages-alist - org-format-latex-options - forbuffer txt fg bg))) - linkfile (format "%s_%s.png" prefix hash) - movefile (format "%s_%s.png" absprefix hash))) - (setq link (concat block "[[file:" linkfile "]]" block)) - (if msg (message msg cnt)) - (goto-char beg) - (unless checkdir ; Ensure the directory exists. - (setq checkdir t) - (or (file-directory-p todir) (make-directory todir t))) - (unless (file-exists-p movefile) - (org-create-formula-image - txt movefile optnew forbuffer processing-type)) - (if overlays - (progn - (mapc (lambda (o) - (if (eq (overlay-get o 'org-overlay-type) - 'org-latex-overlay) - (delete-overlay o))) - (overlays-in beg end)) - (setq ov (make-overlay beg end)) - (overlay-put ov 'org-overlay-type 'org-latex-overlay) - (if (featurep 'xemacs) + (let* ((processing-info + (cdr (assq processing-type org-preview-latex-process-alist))) + (face (face-at-point)) + ;; Get the colors from the face at point. + (fg + (let ((color (plist-get org-format-latex-options + :foreground))) + (if (and forbuffer (eq color 'auto)) + (face-attribute face :foreground nil 'default) + color))) + (bg + (let ((color (plist-get org-format-latex-options + :background))) + (if (and forbuffer (eq color 'auto)) + (face-attribute face :background nil 'default) + color))) + (hash (sha1 (prin1-to-string + (list org-format-latex-header + org-latex-default-packages-alist + org-latex-packages-alist + org-format-latex-options + forbuffer value fg bg)))) + (imagetype (or (plist-get processing-info :image-output-type) "png")) + (absprefix (expand-file-name prefix dir)) + (linkfile (format "%s_%s.%s" prefix hash imagetype)) + (movefile (format "%s_%s.%s" absprefix hash imagetype)) + (sep (and block-type "\n\n")) + (link (concat sep "[[file:" linkfile "]]" sep)) + (options + (org-combine-plists + org-format-latex-options + `(:foreground ,fg :background ,bg)))) + (when msg (message msg cnt)) + (unless checkdir-flag ; Ensure the directory exists. + (setq checkdir-flag t) + (let ((todir (file-name-directory absprefix))) + (unless (file-directory-p todir) + (make-directory todir t)))) + (unless (file-exists-p movefile) + (org-create-formula-image + value movefile options forbuffer processing-type)) + (if overlays (progn - (overlay-put ov 'invisible t) - (overlay-put - ov 'end-glyph - (make-glyph (vector 'png :file movefile)))) - (overlay-put - ov 'display - (list 'image :type 'png :file movefile :ascent 'center))) - (push ov org-latex-fragment-image-overlays) - (goto-char end)) - (delete-region beg end) - (insert (org-add-props link - (list 'org-latex-src - (replace-regexp-in-string - "\"" "" txt) - 'org-latex-src-embed-type - (if block-type 'paragraph 'character)))))) - ((eq processing-type 'mathml) - ;; Process to MathML - (unless (save-match-data (org-format-latex-mathml-available-p)) - (user-error "LaTeX to MathML converter not configured")) - (setq txt (match-string n) - beg (match-beginning n) end (match-end n) - cnt (1+ cnt)) - (if msg (message msg cnt)) - (goto-char beg) - (delete-region beg end) - (insert (org-format-latex-as-mathml - txt block-type prefix dir))) - (t - (error "Unknown conversion type %s for LaTeX fragments" - processing-type))))))))) + (dolist (o (overlays-in beg end)) + (when (eq (overlay-get o 'org-overlay-type) + 'org-latex-overlay) + (delete-overlay o))) + (org--format-latex-make-overlay beg end movefile imagetype) + (goto-char end)) + (delete-region beg end) + (insert + (org-add-props link + (list 'org-latex-src + (replace-regexp-in-string "\"" "" value) + 'org-latex-src-embed-type + (if block-type 'paragraph 'character))))))) + ((eq processing-type 'mathml) + ;; Process to MathML. + (unless (org-format-latex-mathml-available-p) + (user-error "LaTeX to MathML converter not configured")) + (cl-incf cnt) + (when msg (message msg cnt)) + (goto-char beg) + (delete-region beg end) + (insert (org-format-latex-as-mathml + value block-type prefix dir))) + (t + (error "Unknown conversion process %s for LaTeX fragments" + processing-type))))))))))) (defun org-create-math-formula (latex-frag &optional mathml-file) "Convert LATEX-FRAG to MathML and store it in MATHML-FILE. @@ -18553,20 +18999,25 @@ inspection." (buffer-substring-no-properties (region-beginning) (region-end))))) (read-string "LaTeX Fragment: " frag nil frag)))) - (unless latex-frag (error "Invalid LaTeX fragment")) - (let* ((tmp-in-file (file-relative-name - (make-temp-name (expand-file-name "ltxmathml-in")))) - (ignore (write-region latex-frag nil tmp-in-file)) + (unless latex-frag (user-error "Invalid LaTeX fragment")) + (let* ((tmp-in-file + (let ((file (file-relative-name + (make-temp-name (expand-file-name "ltxmathml-in"))))) + (write-region latex-frag nil file) + file)) (tmp-out-file (file-relative-name (make-temp-name (expand-file-name "ltxmathml-out")))) (cmd (format-spec org-latex-to-mathml-convert-command - `((?j . ,(shell-quote-argument - (expand-file-name org-latex-to-mathml-jar-file))) + `((?j . ,(and org-latex-to-mathml-jar-file + (shell-quote-argument + (expand-file-name + org-latex-to-mathml-jar-file)))) (?I . ,(shell-quote-argument tmp-in-file)) + (?i . ,latex-frag) (?o . ,(shell-quote-argument tmp-out-file))))) mathml shell-command-output) - (when (org-called-interactively-p 'any) + (when (called-interactively-p 'any) (unless (org-format-latex-mathml-available-p) (user-error "LaTeX to MathML converter not configured"))) (message "Running %s" cmd) @@ -18576,11 +19027,10 @@ inspection." (with-current-buffer (find-file-noselect tmp-out-file t) (goto-char (point-min)) (when (re-search-forward - (concat - (regexp-quote - "<math xmlns=\"http://www.w3.org/1998/Math/MathML\">") - "\\(.\\|\n\\)*" - (regexp-quote "</math>")) nil t) + (format "<math[^>]*?%s[^>]*?>\\(.\\|\n\\)*</math>" + (regexp-quote + "xmlns=\"http://www.w3.org/1998/Math/MathML\"")) + nil t) (prog1 (match-string 0) (kill-buffer)))))) (cond (mathml @@ -18588,7 +19038,7 @@ inspection." (concat "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" mathml)) (when mathml-file (write-region mathml nil mathml-file)) - (when (org-called-interactively-p 'any) + (when (called-interactively-p 'any) (message mathml))) ((message "LaTeX to MathML conversion failed") (message shell-command-output))) @@ -18627,186 +19077,117 @@ inspection." ;; Failed conversion. Return the LaTeX fragment verbatim latex-frag))) -(defun org-create-formula-image (string tofile options buffer &optional type) - "Create an image from LaTeX source using dvipng or convert. -This function calls either `org-create-formula-image-with-dvipng' -or `org-create-formula-image-with-imagemagick' depending on the -value of `org-latex-create-formula-image-program' or on the value -of the optional TYPE variable. - -Note: ultimately these two function should be combined as they -share a good deal of logic." - (org-check-external-command - "latex" "needed to convert LaTeX fragments to images") - (funcall - (case (or type org-latex-create-formula-image-program) - ('dvipng - (org-check-external-command - "dvipng" "needed to convert LaTeX fragments to images") - #'org-create-formula-image-with-dvipng) - ('imagemagick - (org-check-external-command - "convert" "you need to install imagemagick") - #'org-create-formula-image-with-imagemagick) - (t (error - "Invalid value of `org-latex-create-formula-image-program'"))) - string tofile options buffer)) - -(declare-function org-export-get-backend "ox" (name)) -(declare-function org-export--get-global-options "ox" (&optional backend)) -(declare-function org-export--get-inbuffer-options "ox" (&optional backend)) -(declare-function org-latex-guess-inputenc "ox-latex" (header)) -(declare-function org-latex-guess-babel-language "ox-latex" (header info)) -(defun org-create-formula--latex-header () - "Return LaTeX header appropriate for previewing a LaTeX snippet." - (let ((info (org-combine-plists (org-export--get-global-options - (org-export-get-backend 'latex)) - (org-export--get-inbuffer-options - (org-export-get-backend 'latex))))) - (org-latex-guess-babel-language - (org-latex-guess-inputenc - (org-splice-latex-header - org-format-latex-header - org-latex-default-packages-alist - org-latex-packages-alist t - (plist-get info :latex-header))) - info))) - -;; This function borrows from Ganesh Swami's latex2png.el -(defun org-create-formula-image-with-dvipng (string tofile options buffer) - "This calls dvipng." - (require 'ox-latex) - (let* ((tmpdir (if (featurep 'xemacs) - (temp-directory) - temporary-file-directory)) +(defun org--get-display-dpi () + "Get the DPI of the display. +The function assumes that the display has the same pixel width in +the horizontal and vertical directions." + (if (display-graphic-p) + (round (/ (display-pixel-height) + (/ (display-mm-height) 25.4))) + (error "Attempt to calculate the dpi of a non-graphic display"))) + +(defun org-create-formula-image + (string tofile options buffer &optional processing-type) + "Create an image from LaTeX source using external processes. + +The LaTeX STRING is saved to a temporary LaTeX file, then +converted to an image file by process PROCESSING-TYPE defined in +`org-preview-latex-process-alist'. A nil value defaults to +`org-preview-latex-default-process'. + +The generated image file is eventually moved to TOFILE. + +The OPTIONS argument controls the size, foreground color and +background color of the generated image. + +When BUFFER non-nil, this function is used for LaTeX previewing. +Otherwise, it is used to deal with LaTeX snippets showed in +a HTML file." + (let* ((processing-type (or processing-type + org-preview-latex-default-process)) + (processing-info + (cdr (assq processing-type org-preview-latex-process-alist))) + (programs (plist-get processing-info :programs)) + (error-message (or (plist-get processing-info :message) "")) + (use-xcolor (plist-get processing-info :use-xcolor)) + (image-input-type (plist-get processing-info :image-input-type)) + (image-output-type (plist-get processing-info :image-output-type)) + (post-clean (or (plist-get processing-info :post-clean) + '(".dvi" ".xdv" ".pdf" ".tex" ".aux" ".log" + ".svg" ".png" ".jpg" ".jpeg" ".out"))) + (latex-header + (or (plist-get processing-info :latex-header) + (org-latex-make-preamble + (org-export-get-environment (org-export-get-backend 'latex)) + org-format-latex-header + 'snippet))) + (latex-compiler (plist-get processing-info :latex-compiler)) + (image-converter (plist-get processing-info :image-converter)) + (tmpdir temporary-file-directory) (texfilebase (make-temp-name (expand-file-name "orgtex" tmpdir))) (texfile (concat texfilebase ".tex")) - (dvifile (concat texfilebase ".dvi")) - (pngfile (concat texfilebase ".png")) - (fnh (if (featurep 'xemacs) - (font-height (face-font 'default)) - (face-attribute 'default :height nil))) - (scale (or (plist-get options (if buffer :scale :html-scale)) 1.0)) - (dpi (number-to-string (* scale (floor (* 0.9 (if buffer fnh 140.)))))) + (image-size-adjust (or (plist-get processing-info :image-size-adjust) + '(1.0 . 1.0))) + (scale (* (if buffer (car image-size-adjust) (cdr image-size-adjust)) + (or (plist-get options (if buffer :scale :html-scale)) 1.0))) + (dpi (* scale (if buffer (org--get-display-dpi) 140.0))) (fg (or (plist-get options (if buffer :foreground :html-foreground)) "Black")) (bg (or (plist-get options (if buffer :background :html-background)) - "Transparent"))) - (if (eq fg 'default) (setq fg (org-dvipng-color :foreground)) - (unless (string= fg "Transparent") (setq fg (org-dvipng-color-format fg)))) - (if (eq bg 'default) (setq bg (org-dvipng-color :background)) - (unless (string= bg "Transparent") (setq bg (org-dvipng-color-format bg)))) - (let ((latex-header (org-create-formula--latex-header))) + "Transparent")) + (log-buf (get-buffer-create "*Org Preview LaTeX Output*")) + (resize-mini-windows nil)) ;Fix Emacs flicker when creating image. + (dolist (program programs) + (org-check-external-command program error-message)) + (if use-xcolor + (progn (if (eq fg 'default) + (setq fg (org-latex-color :foreground)) + (setq fg (org-latex-color-format fg))) + (if (eq bg 'default) + (setq bg (org-latex-color :background)) + (setq bg (org-latex-color-format + (if (string= bg "Transparent") "white" bg)))) + (with-temp-file texfile + (insert latex-header) + (insert "\n\\begin{document}\n" + "\\definecolor{fg}{rgb}{" fg "}\n" + "\\definecolor{bg}{rgb}{" bg "}\n" + "\n\\pagecolor{bg}\n" + "\n{\\color{fg}\n" + string + "\n}\n" + "\n\\end{document}\n"))) + (if (eq fg 'default) + (setq fg (org-dvipng-color :foreground)) + (unless (string= fg "Transparent") + (setq fg (org-dvipng-color-format fg)))) + (if (eq bg 'default) + (setq bg (org-dvipng-color :background)) + (unless (string= bg "Transparent") + (setq bg (org-dvipng-color-format bg)))) (with-temp-file texfile (insert latex-header) (insert "\n\\begin{document}\n" string "\n\\end{document}\n"))) - (let ((dir default-directory)) - (condition-case nil - (progn - (cd tmpdir) - (call-process "latex" nil nil nil texfile)) - (error nil)) - (cd dir)) - (if (not (file-exists-p dvifile)) - (progn (message "Failed to create dvi file from %s" texfile) nil) - (condition-case nil - (if (featurep 'xemacs) - (call-process "dvipng" nil nil nil - "-fg" fg "-bg" bg - "-T" "tight" - "-o" pngfile - dvifile) - (call-process "dvipng" nil nil nil - "-fg" fg "-bg" bg - "-D" dpi - ;;"-x" scale "-y" scale - "-T" "tight" - "-o" pngfile - dvifile)) - (error nil)) - (if (not (file-exists-p pngfile)) - (if org-format-latex-signal-error - (error "Failed to create png file from %s" texfile) - (message "Failed to create png file from %s" texfile) - nil) - ;; Use the requested file name and clean up - (copy-file pngfile tofile 'replace) - (loop for e in '(".dvi" ".tex" ".aux" ".log" ".png" ".out") do - (if (file-exists-p (concat texfilebase e)) - (delete-file (concat texfilebase e)))) - pngfile)))) - -(declare-function org-latex-compile "ox-latex" (texfile &optional snippet)) -(defun org-create-formula-image-with-imagemagick (string tofile options buffer) - "This calls convert, which is included into imagemagick." - (require 'ox-latex) - (let* ((tmpdir (if (featurep 'xemacs) - (temp-directory) - temporary-file-directory)) - (texfilebase (make-temp-name - (expand-file-name "orgtex" tmpdir))) - (texfile (concat texfilebase ".tex")) - (pdffile (concat texfilebase ".pdf")) - (pngfile (concat texfilebase ".png")) - (fnh (if (featurep 'xemacs) - (font-height (face-font 'default)) - (face-attribute 'default :height nil))) - (scale (or (plist-get options (if buffer :scale :html-scale)) 1.0)) - (dpi (number-to-string (* scale (floor (if buffer fnh 120.))))) - (fg (or (plist-get options (if buffer :foreground :html-foreground)) - "black")) - (bg (or (plist-get options (if buffer :background :html-background)) - "white"))) - (if (eq fg 'default) (setq fg (org-latex-color :foreground)) - (setq fg (org-latex-color-format fg))) - (if (eq bg 'default) (setq bg (org-latex-color :background)) - (setq bg (org-latex-color-format - (if (string= bg "Transparent") "white" bg)))) - (let ((latex-header (org-create-formula--latex-header))) - (with-temp-file texfile - (insert latex-header) - (insert "\n\\begin{document}\n" - "\\definecolor{fg}{rgb}{" fg "}\n" - "\\definecolor{bg}{rgb}{" bg "}\n" - "\n\\pagecolor{bg}\n" - "\n{\\color{fg}\n" - string - "\n}\n" - "\n\\end{document}\n"))) - (org-latex-compile texfile t) - (if (not (file-exists-p pdffile)) - (progn (message "Failed to create pdf file from %s" texfile) nil) - (condition-case nil - (if (featurep 'xemacs) - (call-process "convert" nil nil nil - "-density" "96" - "-trim" - "-antialias" - pdffile - "-quality" "100" - ;; "-sharpen" "0x1.0" - pngfile) - (call-process "convert" nil nil nil - "-density" dpi - "-trim" - "-antialias" - pdffile - "-quality" "100" - ;; "-sharpen" "0x1.0" - pngfile)) - (error nil)) - (if (not (file-exists-p pngfile)) - (if org-format-latex-signal-error - (error "Failed to create png file from %s" texfile) - (message "Failed to create png file from %s" texfile) - nil) - ;; Use the requested file name and clean up - (copy-file pngfile tofile 'replace) - (loop for e in '(".pdf" ".tex" ".aux" ".log" ".png") do - (if (file-exists-p (concat texfilebase e)) - (delete-file (concat texfilebase e)))) - pngfile)))) + + (let* ((err-msg (format "Please adjust `%s' part of \ +`org-preview-latex-process-alist'." + processing-type)) + (image-input-file + (org-compile-file + texfile latex-compiler image-input-type err-msg log-buf)) + (image-output-file + (org-compile-file + image-input-file image-converter image-output-type err-msg log-buf + `((?F . ,(shell-quote-argument fg)) + (?B . ,(shell-quote-argument bg)) + (?D . ,(shell-quote-argument (format "%s" dpi))) + (?S . ,(shell-quote-argument (format "%s" (/ dpi 140.0)))))))) + (copy-file image-output-file tofile 'replace) + (dolist (e post-clean) + (when (file-exists-p (concat texfilebase e)) + (delete-file (concat texfilebase e)))) + image-output-file))) (defun org-splice-latex-header (tpl def-pkg pkg snippets-p &optional extra) "Fill a LaTeX header template TPL. @@ -18830,22 +19211,22 @@ SNIPPETS-P indicates if this is run to create snippet images for HTML." (setq rpl (if (or (match-end 1) (not def-pkg)) "" (org-latex-packages-to-string def-pkg snippets-p t)) tpl (replace-match rpl t t tpl)) - (if def-pkg (setq end (org-latex-packages-to-string def-pkg snippets-p)))) + (when def-pkg (setq end (org-latex-packages-to-string def-pkg snippets-p)))) (if (string-match "\\[\\(NO-\\)?PACKAGES\\][ \t]*\n?" tpl) (setq rpl (if (or (match-end 1) (not pkg)) "" (org-latex-packages-to-string pkg snippets-p t)) tpl (replace-match rpl t t tpl)) - (if pkg (setq end - (concat end "\n" - (org-latex-packages-to-string pkg snippets-p))))) + (when pkg (setq end + (concat end "\n" + (org-latex-packages-to-string pkg snippets-p))))) (if (string-match "\\[\\(NO-\\)?EXTRA\\][ \t]*\n?" tpl) (setq rpl (if (or (match-end 1) (not extra)) "" (concat extra "\n")) tpl (replace-match rpl t t tpl)) - (if (and extra (string-match "\\S-" extra)) - (setq end (concat end "\n" extra)))) + (when (and extra (string-match "\\S-" extra)) + (setq end (concat end "\n" extra)))) (if (string-match "\\S-" end) (concat tpl "\n" end) @@ -18869,35 +19250,21 @@ SNIPPETS-P indicates if this is run to create snippet images for HTML." (defun org-dvipng-color (attr) "Return a RGB color specification for dvipng." - (apply 'format "rgb %s %s %s" - (mapcar 'org-normalize-color - (if (featurep 'xemacs) - (color-rgb-components - (face-property 'default - (cond ((eq attr :foreground) 'foreground) - ((eq attr :background) 'background)))) - (color-values (face-attribute 'default attr nil)))))) + (org-dvipng-color-format (face-attribute 'default attr nil))) (defun org-dvipng-color-format (color-name) "Convert COLOR-NAME to a RGB color value for dvipng." - (apply 'format "rgb %s %s %s" + (apply #'format "rgb %s %s %s" (mapcar 'org-normalize-color - (color-values color-name)))) + (color-values color-name)))) (defun org-latex-color (attr) "Return a RGB color for the LaTeX color package." - (apply 'format "%s,%s,%s" - (mapcar 'org-normalize-color - (if (featurep 'xemacs) - (color-rgb-components - (face-property 'default - (cond ((eq attr :foreground) 'foreground) - ((eq attr :background) 'background)))) - (color-values (face-attribute 'default attr nil)))))) + (org-latex-color-format (face-attribute 'default attr nil))) (defun org-latex-color-format (color-name) "Convert COLOR-NAME to a RGB color value." - (apply 'format "%s,%s,%s" + (apply #'format "%s,%s,%s" (mapcar 'org-normalize-color (color-values color-name)))) @@ -18909,8 +19276,7 @@ SNIPPETS-P indicates if this is run to create snippet images for HTML." ;; Image display -(defvar org-inline-image-overlays nil) -(make-variable-buffer-local 'org-inline-image-overlays) +(defvar-local org-inline-image-overlays nil) (defun org-toggle-inline-images (&optional include-linked) "Toggle the display of inline images. @@ -18919,13 +19285,14 @@ INCLUDE-LINKED is passed to `org-display-inline-images'." (if org-inline-image-overlays (progn (org-remove-inline-images) - (message "Inline image display turned off")) + (when (called-interactively-p 'interactive) + (message "Inline image display turned off"))) (org-display-inline-images include-linked) - (if (and (org-called-interactively-p) - org-inline-image-overlays) - (message "%d images displayed inline" - (length org-inline-image-overlays)) - (message "No images to display inline")))) + (when (called-interactively-p 'interactive) + (message (if org-inline-image-overlays + (format "%d images displayed inline" + (length org-inline-image-overlays)) + "No images to display inline"))))) (defun org-redisplay-inline-images () "Refresh the display of inline images." @@ -18937,68 +19304,115 @@ INCLUDE-LINKED is passed to `org-display-inline-images'." (defun org-display-inline-images (&optional include-linked refresh beg end) "Display inline images. -Normally only links without a description part are inlined, because this -is how it will work for export. When INCLUDE-LINKED is set, also links -with a description part will be inlined. This can be nice for a quick -look at those images, but it does not reflect what exported files will look -like. -When REFRESH is set, refresh existing images between BEG and END. -This will create new image displays only if necessary. -BEG and END default to the buffer boundaries." + +An inline image is a link which follows either of these +conventions: + + 1. Its path is a file with an extension matching return value + from `image-file-name-regexp' and it has no contents. + + 2. Its description consists in a single link of the previous + type. + +When optional argument INCLUDE-LINKED is non-nil, also links with +a text description part will be inlined. This can be nice for +a quick look at those images, but it does not reflect what +exported files will look like. + +When optional argument REFRESH is non-nil, refresh existing +images between BEG and END. This will create new image displays +only if necessary. BEG and END default to the buffer +boundaries." (interactive "P") (when (display-graphic-p) (unless refresh (org-remove-inline-images) - (if (fboundp 'clear-image-cache) (clear-image-cache))) - (save-excursion - (save-restriction - (widen) - (setq beg (or beg (point-min)) end (or end (point-max))) - (goto-char beg) - (let ((re (concat "\\[\\[\\(\\(file:\\)\\|\\([./~]\\)\\)\\([^]\n]+?" - (substring (org-image-file-name-regexp) 0 -2) - "\\)\\]" (if include-linked "" "\\]"))) - (case-fold-search t) - old file ov img type attrwidth width) - (while (re-search-forward re end t) - (setq old (get-char-property-and-overlay (match-beginning 1) - 'org-image-overlay) - file (expand-file-name - (concat (or (match-string 3) "") (match-string 4)))) - (when (image-type-available-p 'imagemagick) - (setq attrwidth (if (or (listp org-image-actual-width) - (null org-image-actual-width)) - (save-excursion - (save-match-data - (when (re-search-backward - "#\\+attr.*:width[ \t]+\\([^ ]+\\)" - (save-excursion - (re-search-backward "^[ \t]*$\\|\\`" nil t)) t) - (string-to-number (match-string 1)))))) - width (cond ((eq org-image-actual-width t) nil) - ((null org-image-actual-width) attrwidth) - ((numberp org-image-actual-width) - org-image-actual-width) - ((listp org-image-actual-width) - (or attrwidth (car org-image-actual-width)))) - type (if width 'imagemagick))) - (when (file-exists-p file) - (if (and (car-safe old) refresh) - (image-refresh (overlay-get (cdr old) 'display)) - (setq img (save-match-data (create-image file type nil :width width))) - (when img - (setq ov (make-overlay (match-beginning 0) (match-end 0))) - (overlay-put ov 'display img) - (overlay-put ov 'face 'default) - (overlay-put ov 'org-image-overlay t) - (overlay-put ov 'modification-hooks - (list 'org-display-inline-remove-overlay)) - (push ov org-inline-image-overlays)))))))))) - -(define-obsolete-function-alias - 'org-display-inline-modification-hook 'org-display-inline-remove-overlay "24.3") - -(defun org-display-inline-remove-overlay (ov after beg end &optional len) + (when (fboundp 'clear-image-cache) (clear-image-cache))) + (org-with-wide-buffer + (goto-char (or beg (point-min))) + (let* ((case-fold-search t) + (file-extension-re (image-file-name-regexp)) + (link-abbrevs (mapcar #'car + (append org-link-abbrev-alist-local + org-link-abbrev-alist))) + ;; Check absolute, relative file names and explicit + ;; "file:" links. Also check link abbreviations since + ;; some might expand to "file" links. + (file-types-re (format "[][]\\[\\(?:file\\|[./~]%s\\)" + (if (not link-abbrevs) "" + (format "\\|\\(?:%s:\\)" + (regexp-opt link-abbrevs)))))) + (while (re-search-forward file-types-re end t) + (let ((link (save-match-data (org-element-context)))) + ;; Check if we're at an inline image, i.e., an image file + ;; link without a description (unless INCLUDE-LINKED is + ;; non-nil). + (when (and (equal "file" (org-element-property :type link)) + (or include-linked + (null (org-element-contents link))) + (string-match-p file-extension-re + (org-element-property :path link))) + (let ((file (expand-file-name + (org-link-unescape + (org-element-property :path link))))) + (when (file-exists-p file) + (let ((width + ;; Apply `org-image-actual-width' specifications. + (cond + ((not (image-type-available-p 'imagemagick)) nil) + ((eq org-image-actual-width t) nil) + ((listp org-image-actual-width) + (or + ;; First try to find a width among + ;; attributes associated to the paragraph + ;; containing link. + (let ((paragraph + (let ((e link)) + (while (and (setq e (org-element-property + :parent e)) + (not (eq (org-element-type e) + 'paragraph)))) + e))) + (when paragraph + (save-excursion + (goto-char (org-element-property :begin paragraph)) + (when + (re-search-forward + "^[ \t]*#\\+attr_.*?: +.*?:width +\\(\\S-+\\)" + (org-element-property + :post-affiliated paragraph) + t) + (string-to-number (match-string 1)))))) + ;; Otherwise, fall-back to provided number. + (car org-image-actual-width))) + ((numberp org-image-actual-width) + org-image-actual-width))) + (old (get-char-property-and-overlay + (org-element-property :begin link) + 'org-image-overlay))) + (if (and (car-safe old) refresh) + (image-refresh (overlay-get (cdr old) 'display)) + (let ((image (create-image file + (and width 'imagemagick) + nil + :width width))) + (when image + (let ((ov (make-overlay + (org-element-property :begin link) + (progn + (goto-char + (org-element-property :end link)) + (skip-chars-backward " \t") + (point))))) + (overlay-put ov 'display image) + (overlay-put ov 'face 'default) + (overlay-put ov 'org-image-overlay t) + (overlay-put + ov 'modification-hooks + (list 'org-display-inline-remove-overlay)) + (push ov org-inline-image-overlays))))))))))))))) + +(defun org-display-inline-remove-overlay (ov after _beg _end &optional _len) "Remove inline-display overlay if a corresponding region is modified." (let ((inhibit-modification-hooks t)) (when (and ov after) @@ -19008,52 +19422,62 @@ BEG and END default to the buffer boundaries." (defun org-remove-inline-images () "Remove inline display of images." (interactive) - (mapc 'delete-overlay org-inline-image-overlays) + (mapc #'delete-overlay org-inline-image-overlays) (setq org-inline-image-overlays nil)) ;;;; Key bindings +(defun org-remap (map &rest commands) + "In MAP, remap the functions given in COMMANDS. +COMMANDS is a list of alternating OLDDEF NEWDEF command names." + (let (new old) + (while commands + (setq old (pop commands) new (pop commands)) + (org-defkey map (vector 'remap old) new)))) + ;; Outline functions from `outline-mode-prefix-map' ;; that can be remapped in Org: (define-key org-mode-map [remap outline-mark-subtree] 'org-mark-subtree) -(define-key org-mode-map [remap show-subtree] 'org-show-subtree) +(define-key org-mode-map [remap outline-show-subtree] 'org-show-subtree) (define-key org-mode-map [remap outline-forward-same-level] 'org-forward-heading-same-level) (define-key org-mode-map [remap outline-backward-same-level] 'org-backward-heading-same-level) -(define-key org-mode-map [remap show-branches] +(define-key org-mode-map [remap outline-show-branches] 'org-kill-note-or-show-branches) (define-key org-mode-map [remap outline-promote] 'org-promote-subtree) (define-key org-mode-map [remap outline-demote] 'org-demote-subtree) (define-key org-mode-map [remap outline-insert-heading] 'org-ctrl-c-ret) +(define-key org-mode-map [remap outline-next-visible-heading] + 'org-next-visible-heading) +(define-key org-mode-map [remap outline-previous-visible-heading] + 'org-previous-visible-heading) +(define-key org-mode-map [remap show-children] 'org-show-children) ;; Outline functions from `outline-mode-prefix-map' that can not ;; be remapped in Org: -;; + ;; - the column "key binding" shows whether the Outline function is still ;; available in Org mode on the same key that it has been bound to in ;; Outline mode: ;; - "overridden": key used for a different functionality in Org mode ;; - else: key still bound to the same Outline function in Org mode -;; -;; | Outline function | key binding | Org replacement | -;; |------------------------------------+-------------+-----------------------| -;; | `outline-next-visible-heading' | `C-c C-n' | still same function | -;; | `outline-previous-visible-heading' | `C-c C-p' | still same function | -;; | `outline-up-heading' | `C-c C-u' | still same function | -;; | `outline-move-subtree-up' | overridden | better: org-shiftup | -;; | `outline-move-subtree-down' | overridden | better: org-shiftdown | -;; | `show-entry' | overridden | no replacement | -;; | `show-children' | `C-c C-i' | visibility cycling | -;; | `show-branches' | `C-c C-k' | still same function | -;; | `show-subtree' | overridden | visibility cycling | -;; | `show-all' | overridden | no replacement | -;; | `hide-subtree' | overridden | visibility cycling | -;; | `hide-body' | overridden | no replacement | -;; | `hide-entry' | overridden | visibility cycling | -;; | `hide-leaves' | overridden | no replacement | -;; | `hide-sublevels' | overridden | no replacement | -;; | `hide-other' | overridden | no replacement | + +;; | Outline function | key binding | Org replacement | +;; |------------------------------------+-------------+--------------------------| +;; | `outline-up-heading' | `C-c C-u' | still same function | +;; | `outline-move-subtree-up' | overridden | better: org-shiftup | +;; | `outline-move-subtree-down' | overridden | better: org-shiftdown | +;; | `show-entry' | overridden | no replacement | +;; | `show-branches' | `C-c C-k' | still same function | +;; | `show-subtree' | overridden | visibility cycling | +;; | `show-all' | overridden | no replacement | +;; | `hide-subtree' | overridden | visibility cycling | +;; | `hide-body' | overridden | no replacement | +;; | `hide-entry' | overridden | visibility cycling | +;; | `hide-leaves' | overridden | no replacement | +;; | `hide-sublevels' | overridden | no replacement | +;; | `hide-other' | overridden | no replacement | ;; Make `C-c C-x' a prefix key (org-defkey org-mode-map "\C-c\C-x" (make-sparse-keymap)) @@ -19063,15 +19487,15 @@ BEG and END default to the buffer boundaries." (org-defkey org-mode-map [(tab)] 'org-cycle) (org-defkey org-mode-map [(control tab)] 'org-force-cycle-archived) (org-defkey org-mode-map "\M-\t" #'pcomplete) + ;; The following line is necessary under Suse GNU/Linux -(unless (featurep 'xemacs) - (org-defkey org-mode-map [S-iso-lefttab] 'org-shifttab)) +(org-defkey org-mode-map [S-iso-lefttab] 'org-shifttab) (org-defkey org-mode-map [(shift tab)] 'org-shifttab) (define-key org-mode-map [backtab] 'org-shifttab) (org-defkey org-mode-map [(shift return)] 'org-table-copy-down) (org-defkey org-mode-map [(meta shift return)] 'org-insert-todo-heading) -(org-defkey org-mode-map [(meta return)] 'org-meta-return) +(org-defkey org-mode-map (kbd "M-RET") #'org-meta-return) ;; Cursor keys with modifiers (org-defkey org-mode-map [(meta left)] 'org-metaleft) @@ -19079,6 +19503,8 @@ BEG and END default to the buffer boundaries." (org-defkey org-mode-map [(meta up)] 'org-metaup) (org-defkey org-mode-map [(meta down)] 'org-metadown) +(org-defkey org-mode-map [(control meta shift right)] 'org-increase-number-at-point) +(org-defkey org-mode-map [(control meta shift left)] 'org-decrease-number-at-point) (org-defkey org-mode-map [(meta shift left)] 'org-shiftmetaleft) (org-defkey org-mode-map [(meta shift right)] 'org-shiftmetaright) (org-defkey org-mode-map [(meta shift up)] 'org-shiftmetaup) @@ -19096,17 +19522,14 @@ BEG and END default to the buffer boundaries." ;; Babel keys (define-key org-mode-map org-babel-key-prefix org-babel-map) -(mapc (lambda (pair) - (define-key org-babel-map (car pair) (cdr pair))) - org-babel-key-bindings) +(dolist (pair org-babel-key-bindings) + (define-key org-babel-map (car pair) (cdr pair))) ;;; Extra keys for tty access. ;; We only set them when really needed because otherwise the ;; menus don't show the simple keys -(when (or org-use-extra-keys - (featurep 'xemacs) ;; because XEmacs supports multi-device stuff - (not window-system)) +(when (or org-use-extra-keys (not window-system)) (org-defkey org-mode-map "\C-c\C-xc" 'org-table-copy-down) (org-defkey org-mode-map "\C-c\C-xM" 'org-insert-todo-heading) (org-defkey org-mode-map "\C-c\C-xm" 'org-meta-return) @@ -19137,8 +19560,13 @@ BEG and END default to the buffer boundaries." (org-defkey org-mode-map [?\e (shift down)] 'org-shiftmetadown)) ;; All the other keys +(org-remap org-mode-map + 'self-insert-command 'org-self-insert-command + 'delete-char 'org-delete-char + 'delete-backward-char 'org-delete-backward-char) +(org-defkey org-mode-map "|" 'org-force-self-insert) -(org-defkey org-mode-map "\C-c\C-a" 'show-all) ; in case allout messed up. +(org-defkey org-mode-map "\C-c\C-a" 'outline-show-all) ; in case allout messed up. (org-defkey org-mode-map "\C-c\C-r" 'org-reveal) (if (boundp 'narrow-map) (org-defkey narrow-map "s" 'org-narrow-to-subtree) @@ -19177,7 +19605,6 @@ BEG and END default to the buffer boundaries." (org-defkey org-mode-map "\C-c/" 'org-sparse-tree) ; Minor-mode reserved (org-defkey org-mode-map "\C-c\\" 'org-match-sparse-tree) ; Minor-mode res. (org-defkey org-mode-map "\C-c\C-m" 'org-ctrl-c-ret) -(org-defkey org-mode-map "\M-\C-m" 'org-insert-heading) (org-defkey org-mode-map "\C-c\C-xc" 'org-clone-subtree-with-time-shift) (org-defkey org-mode-map "\C-c\C-xv" 'org-copy-visible) (org-defkey org-mode-map [(control return)] 'org-insert-heading-respect-content) @@ -19185,6 +19612,7 @@ BEG and END default to the buffer boundaries." (org-defkey org-mode-map "\C-c\C-x\C-n" 'org-next-link) (org-defkey org-mode-map "\C-c\C-x\C-p" 'org-previous-link) (org-defkey org-mode-map "\C-c\C-l" 'org-insert-link) +(org-defkey org-mode-map "\C-c\M-l" 'org-insert-last-stored-link) (org-defkey org-mode-map "\C-c\C-\M-l" 'org-insert-all-links) (org-defkey org-mode-map "\C-c\C-o" 'org-open-at-point) (org-defkey org-mode-map "\C-c%" 'org-mark-ring-push) @@ -19209,8 +19637,10 @@ BEG and END default to the buffer boundaries." (org-defkey org-mode-map "\C-c\C-k" 'org-kill-note-or-show-branches) (org-defkey org-mode-map "\C-c#" 'org-update-statistics-cookies) (org-defkey org-mode-map [remap open-line] 'org-open-line) +(org-defkey org-mode-map [remap comment-dwim] 'org-comment-dwim) (org-defkey org-mode-map [remap forward-paragraph] 'org-forward-paragraph) (org-defkey org-mode-map [remap backward-paragraph] 'org-backward-paragraph) +(org-defkey org-mode-map "\M-^" 'org-delete-indentation) (org-defkey org-mode-map "\C-m" 'org-return) (org-defkey org-mode-map "\C-j" 'org-return-indent) (org-defkey org-mode-map "\C-c?" 'org-table-field-info) @@ -19219,6 +19649,8 @@ BEG and END default to the buffer boundaries." (org-defkey org-mode-map "\C-c=" 'org-table-eval-formula) (org-defkey org-mode-map "\C-c'" 'org-edit-special) (org-defkey org-mode-map "\C-c`" 'org-table-edit-field) +(org-defkey org-mode-map "\C-c\"a" 'orgtbl-ascii-plot) +(org-defkey org-mode-map "\C-c\"g" 'org-plot/gnuplot) (org-defkey org-mode-map "\C-c|" 'org-table-create-or-convert-from-region) (org-defkey org-mode-map [(control ?#)] 'org-table-rotate-recalc-marks) (org-defkey org-mode-map "\C-c~" 'org-table-create-with-table.el) @@ -19226,7 +19658,7 @@ BEG and END default to the buffer boundaries." (org-defkey org-mode-map "\C-c}" 'org-table-toggle-coordinate-overlays) (org-defkey org-mode-map "\C-c{" 'org-table-toggle-formula-debugger) (org-defkey org-mode-map "\C-c\C-e" 'org-export-dispatch) -(org-defkey org-mode-map "\C-c:" 'org-toggle-fixed-width-section) +(org-defkey org-mode-map "\C-c:" 'org-toggle-fixed-width) (org-defkey org-mode-map "\C-c\C-x\C-f" 'org-emphasize) (org-defkey org-mode-map "\C-c\C-xf" 'org-footnote-action) (org-defkey org-mode-map "\C-c\C-x\C-mg" 'org-mobile-pull) @@ -19250,7 +19682,7 @@ BEG and END default to the buffer boundaries." (org-defkey org-mode-map "\C-c\C-x\C-d" 'org-clock-display) (org-defkey org-mode-map "\C-c\C-x\C-r" 'org-clock-report) (org-defkey org-mode-map "\C-c\C-x\C-u" 'org-dblock-update) -(org-defkey org-mode-map "\C-c\C-x\C-l" 'org-preview-latex-fragment) +(org-defkey org-mode-map "\C-c\C-x\C-l" 'org-toggle-latex-fragment) (org-defkey org-mode-map "\C-c\C-x\C-v" 'org-toggle-inline-images) (org-defkey org-mode-map "\C-c\C-x\C-\M-v" 'org-redisplay-inline-images) (org-defkey org-mode-map "\C-c\C-x\\" 'org-toggle-pretty-entities) @@ -19260,9 +19692,8 @@ BEG and END default to the buffer boundaries." (org-defkey org-mode-map "\C-c\C-xe" 'org-set-effort) (org-defkey org-mode-map "\C-c\C-xE" 'org-inc-effort) (org-defkey org-mode-map "\C-c\C-xo" 'org-toggle-ordered-property) -(org-defkey org-mode-map "\C-c\C-xi" 'org-insert-columns-dblock) +(org-defkey org-mode-map "\C-c\C-xi" 'org-columns-insert-dblock) (org-defkey org-mode-map [(control ?c) (control ?x) ?\;] 'org-timer-set-timer) -(org-defkey org-mode-map [(control ?c) (control ?x) ?\:] 'org-timer-cancel-timer) (org-defkey org-mode-map "\C-c\C-x." 'org-timer) (org-defkey org-mode-map "\C-c\C-x-" 'org-timer-item) @@ -19280,15 +19711,11 @@ BEG and END default to the buffer boundaries." (define-key org-mode-map "\C-c\C-x[" 'org-reftex-citation) -(when (featurep 'xemacs) - (org-defkey org-mode-map 'button3 'popup-mode-menu)) - - (defconst org-speed-commands-default '( ("Outline Navigation") - ("n" . (org-speed-move-safe 'outline-next-visible-heading)) - ("p" . (org-speed-move-safe 'outline-previous-visible-heading)) + ("n" . (org-speed-move-safe 'org-next-visible-heading)) + ("p" . (org-speed-move-safe 'org-previous-visible-heading)) ("f" . (org-speed-move-safe 'org-forward-heading-same-level)) ("b" . (org-speed-move-safe 'org-backward-heading-same-level)) ("F" . org-next-block) @@ -19303,8 +19730,8 @@ BEG and END default to the buffer boundaries." ("s" . org-narrow-to-subtree) ("=" . org-columns) ("Outline Structure Editing") - ("U" . org-shiftmetaup) - ("D" . org-shiftmetadown) + ("U" . org-metaup) + ("D" . org-metadown) ("r" . org-metaright) ("l" . org-metaleft) ("R" . org-shiftmetaright) @@ -19364,10 +19791,10 @@ BEG and END default to the buffer boundaries." (user-error "Speed commands are not activated, customize `org-use-speed-commands'") (with-output-to-temp-buffer "*Help*" (princ "User-defined Speed commands\n===========================\n") - (mapc 'org-print-speed-command org-speed-commands-user) + (mapc #'org-print-speed-command org-speed-commands-user) (princ "\n") (princ "Built-in Speed commands\n=======================\n") - (mapc 'org-print-speed-command org-speed-commands-default)) + (mapc #'org-print-speed-command org-speed-commands-default)) (with-current-buffer "*Help*" (setq truncate-lines t)))) @@ -19386,9 +19813,6 @@ If not, return to the original position and throw an error." (defvar org-table-auto-blank-field) ; defined in org-table.el (defvar org-speed-command nil) -(define-obsolete-function-alias - 'org-speed-command-default-hook 'org-speed-command-activate "24.3") - (defun org-speed-command-activate (keys) "Hook for activating single-letter speed commands. `org-speed-commands-default' specifies a minimal command set. @@ -19399,16 +19823,13 @@ Use `org-speed-commands-user' for further customization." (cdr (assoc keys (append org-speed-commands-user org-speed-commands-default))))) -(define-obsolete-function-alias - 'org-babel-speed-command-hook 'org-babel-speed-command-activate "24.3") - (defun org-babel-speed-command-activate (keys) "Hook for activating single-letter code block commands." (when (and (bolp) (looking-at org-babel-src-block-regexp)) (cdr (assoc keys org-babel-key-bindings)))) (defcustom org-speed-command-hook - '(org-speed-command-default-hook org-babel-speed-command-hook) + '(org-speed-command-activate org-babel-speed-command-activate) "Hook for activating speed commands at strategic locations. Hook functions are called in sequence until a valid handler is found. @@ -19434,9 +19855,11 @@ overwritten, and the table is not marked as requiring realignment." (org-check-before-invisible-edit 'insert) (cond ((and org-use-speed-commands - (setq org-speed-command - (run-hook-with-args-until-success - 'org-speed-command-hook (this-command-keys)))) + (let ((kv (this-command-keys-vector))) + (setq org-speed-command + (run-hook-with-args-until-success + 'org-speed-command-hook + (make-string 1 (aref kv (1- (length kv)))))))) (cond ((commandp org-speed-command) (setq this-command org-speed-command) @@ -19448,94 +19871,116 @@ overwritten, and the table is not marked as requiring realignment." (t (let (org-use-speed-commands) (call-interactively 'org-self-insert-command))))) ((and - (org-table-p) + (org-at-table-p) + (eq N 1) + (not (org-region-active-p)) (progn - ;; check if we blank the field, and if that triggers align + ;; Check if we blank the field, and if that triggers align. (and (featurep 'org-table) org-table-auto-blank-field - (member last-command - '(org-cycle org-return org-shifttab org-ctrl-c-ctrl-c yas/expand)) - (if (or (equal (char-after) ?\ ) (looking-at "[^|\n]* |")) - ;; got extra space, this field does not determine column width + (memq last-command + '(org-cycle org-return org-shifttab org-ctrl-c-ctrl-c)) + (if (or (eq (char-after) ?\s) (looking-at "[^|\n]* |")) + ;; Got extra space, this field does not determine + ;; column width. (let (org-table-may-need-update) (org-table-blank-field)) - ;; no extra space, this field may determine column width + ;; No extra space, this field may determine column + ;; width. (org-table-blank-field))) t) - (eq N 1) - (looking-at "[^|\n]* |")) - (let (org-table-may-need-update) - (goto-char (1- (match-end 0))) - (backward-delete-char 1) - (goto-char (match-beginning 0)) - (self-insert-command N))) + (looking-at "[^|\n]* \\( \\)|")) + ;; There is room for insertion without re-aligning the table. + (delete-region (match-beginning 1) (match-end 1)) + (self-insert-command N)) (t (setq org-table-may-need-update t) (self-insert-command N) (org-fix-tags-on-the-fly) - (if org-self-insert-cluster-for-undo - (if (not (eq last-command 'org-self-insert-command)) + (when org-self-insert-cluster-for-undo + (if (not (eq last-command 'org-self-insert-command)) + (setq org-self-insert-command-undo-counter 1) + (if (>= org-self-insert-command-undo-counter 20) (setq org-self-insert-command-undo-counter 1) - (if (>= org-self-insert-command-undo-counter 20) - (setq org-self-insert-command-undo-counter 1) - (and (> org-self-insert-command-undo-counter 0) - buffer-undo-list (listp buffer-undo-list) - (not (cadr buffer-undo-list)) ; remove nil entry - (setcdr buffer-undo-list (cddr buffer-undo-list))) - (setq org-self-insert-command-undo-counter - (1+ org-self-insert-command-undo-counter)))))))) + (and (> org-self-insert-command-undo-counter 0) + buffer-undo-list (listp buffer-undo-list) + (not (cadr buffer-undo-list)) ; remove nil entry + (setcdr buffer-undo-list (cddr buffer-undo-list))) + (setq org-self-insert-command-undo-counter + (1+ org-self-insert-command-undo-counter)))))))) (defun org-check-before-invisible-edit (kind) "Check is editing if kind KIND would be dangerous with invisible text around. The detailed reaction depends on the user option `org-catch-invisible-edits'." ;; First, try to get out of here as quickly as possible, to reduce overhead - (if (and org-catch-invisible-edits - (or (not (boundp 'visible-mode)) (not visible-mode)) - (or (get-char-property (point) 'invisible) - (get-char-property (max (point-min) (1- (point))) 'invisible))) - ;; OK, we need to take a closer look - (let* ((invisible-at-point (get-char-property (point) 'invisible)) - (invisible-before-point (if (bobp) nil (get-char-property - (1- (point)) 'invisible))) - (border-and-ok-direction - (or - ;; Check if we are acting predictably before invisible text - (and invisible-at-point (not invisible-before-point) - (memq kind '(insert delete-backward))) - ;; Check if we are acting predictably after invisible text - ;; This works not well, and I have turned it off. It seems - ;; better to always show and stop after invisible text. - ;; (and (not invisible-at-point) invisible-before-point - ;; (memq kind '(insert delete))) - ))) - (when (or (memq invisible-at-point '(outline org-hide-block t)) - (memq invisible-before-point '(outline org-hide-block t))) - (if (eq org-catch-invisible-edits 'error) - (user-error "Editing in invisible areas is prohibited, make them visible first")) - (if (and org-custom-properties-overlays - (y-or-n-p "Display invisible properties in this buffer? ")) - (org-toggle-custom-properties-visibility) - ;; Make the area visible - (save-excursion - (if invisible-before-point - (goto-char (previous-single-char-property-change - (point) 'invisible))) - (show-subtree)) - (cond - ((eq org-catch-invisible-edits 'show) - ;; That's it, we do the edit after showing - (message - "Unfolding invisible region around point before editing") - (sit-for 1)) - ((and (eq org-catch-invisible-edits 'smart) - border-and-ok-direction) - (message "Unfolding invisible region around point before editing")) - (t - ;; Don't do the edit, make the user repeat it in full visibility - (user-error "Edit in invisible region aborted, repeat to confirm with text visible")))))))) + (when (and org-catch-invisible-edits + (or (not (boundp 'visible-mode)) (not visible-mode)) + (or (get-char-property (point) 'invisible) + (get-char-property (max (point-min) (1- (point))) 'invisible))) + ;; OK, we need to take a closer look. Do not consider + ;; invisibility obtained through text properties (e.g., link + ;; fontification), as it cannot be toggled. + (let* ((invisible-at-point + (pcase (get-char-property-and-overlay (point) 'invisible) + (`(,_ . ,(and (pred overlayp) o)) o))) + ;; Assume that point cannot land in the middle of an + ;; overlay, or between two overlays. + (invisible-before-point + (and (not invisible-at-point) + (not (bobp)) + (pcase (get-char-property-and-overlay (1- (point)) 'invisible) + (`(,_ . ,(and (pred overlayp) o)) o)))) + (border-and-ok-direction + (or + ;; Check if we are acting predictably before invisible + ;; text. + (and invisible-at-point + (memq kind '(insert delete-backward))) + ;; Check if we are acting predictably after invisible text + ;; This works not well, and I have turned it off. It seems + ;; better to always show and stop after invisible text. + ;; (and (not invisible-at-point) invisible-before-point + ;; (memq kind '(insert delete))) + ))) + (when (or invisible-at-point invisible-before-point) + (when (eq org-catch-invisible-edits 'error) + (user-error "Editing in invisible areas is prohibited, make them visible first")) + (if (and org-custom-properties-overlays + (y-or-n-p "Display invisible properties in this buffer? ")) + (org-toggle-custom-properties-visibility) + ;; Make the area visible + (save-excursion + (when invisible-before-point + (goto-char + (previous-single-char-property-change (point) 'invisible))) + ;; Remove whatever overlay is currently making yet-to-be + ;; edited text invisible. Also remove nested invisibility + ;; related overlays. + (delete-overlay (or invisible-at-point invisible-before-point)) + (let ((origin (if invisible-at-point (point) (1- (point))))) + (while (pcase (get-char-property-and-overlay origin 'invisible) + (`(,_ . ,(and (pred overlayp) o)) + (delete-overlay o) + t))))) + (cond + ((eq org-catch-invisible-edits 'show) + ;; That's it, we do the edit after showing + (message + "Unfolding invisible region around point before editing") + (sit-for 1)) + ((and (eq org-catch-invisible-edits 'smart) + border-and-ok-direction) + (message "Unfolding invisible region around point before editing")) + (t + ;; Don't do the edit, make the user repeat it in full visibility + (user-error "Edit in invisible region aborted, repeat to confirm with text visible")))))))) (defun org-fix-tags-on-the-fly () - (when (and (equal (char-after (point-at-bol)) ?*) + "Align tags in headline at point. +Unlike to `org-set-tags', it ignores region and sorting." + (when (and (eq (char-after (line-beginning-position)) ?*) ;short-circuit (org-at-heading-p)) - (org-align-tags-here org-tags-column))) + (let ((org-ignore-region t) + (org-tags-sort-function nil)) + (org-set-tags nil t)))) (defun org-delete-backward-char (N) "Like `delete-backward-char', insert whitespace at field end in tables. @@ -19546,22 +19991,22 @@ because, in this case the deletion might narrow the column." (interactive "p") (save-match-data (org-check-before-invisible-edit 'delete-backward) - (if (and (org-table-p) + (if (and (org-at-table-p) (eq N 1) + (not (org-region-active-p)) (string-match "|" (buffer-substring (point-at-bol) (point))) (looking-at ".*?|")) (let ((pos (point)) (noalign (looking-at "[^|\n\r]* |")) (c org-table-may-need-update)) (backward-delete-char N) - (if (not overwrite-mode) - (progn - (skip-chars-forward "^|") - (insert " ") - (goto-char (1- pos)))) + (unless overwrite-mode + (skip-chars-forward "^|") + (insert " ") + (goto-char (1- pos))) ;; noalign: if there were two spaces at the end, this field ;; does not determine the width of the column. - (if noalign (setq org-table-may-need-update c))) + (when noalign (setq org-table-may-need-update c))) (backward-delete-char N) (org-fix-tags-on-the-fly)))) @@ -19574,7 +20019,7 @@ because, in this case the deletion might narrow the column." (interactive "p") (save-match-data (org-check-before-invisible-edit 'delete) - (if (and (org-table-p) + (if (and (org-at-table-p) (not (bolp)) (not (= (char-after) ?|)) (eq N 1)) @@ -19587,12 +20032,12 @@ because, in this case the deletion might narrow the column." (goto-char pos) ;; noalign: if there were two spaces at the end, this field ;; does not determine the width of the column. - (if noalign (setq org-table-may-need-update c))) + (when noalign (setq org-table-may-need-update c))) (delete-char N)) (delete-char N) (org-fix-tags-on-the-fly)))) -;; Make `delete-selection-mode' work with org-mode and orgtbl-mode +;; Make `delete-selection-mode' work with Org mode and Orgtbl mode (put 'org-self-insert-command 'delete-selection (lambda () (not (run-hook-with-args-until-success @@ -19611,20 +20056,10 @@ because, in this case the deletion might narrow the column." (put 'org-delete-char 'flyspell-delayed t) (put 'org-delete-backward-char 'flyspell-delayed t) -;; Make pabbrev-mode expand after org-mode commands +;; Make pabbrev-mode expand after Org mode commands (put 'org-self-insert-command 'pabbrev-expand-after-command t) (put 'orgtbl-self-insert-command 'pabbrev-expand-after-command t) -(defun org-remap (map &rest commands) - "In MAP, remap the functions given in COMMANDS. -COMMANDS is a list of alternating OLDDEF NEWDEF command names." - (let (new old) - (while commands - (setq old (pop commands) new (pop commands)) - (if (fboundp 'command-remapping) - (org-defkey map (vector 'remap old) new) - (substitute-key-definition old new map global-map))))) - (defun org-transpose-words () "Transpose words for Org. This uses the `org-mode-transpose-word-syntax-table' syntax @@ -19635,15 +20070,6 @@ word constituents." (call-interactively 'transpose-words))) (org-remap org-mode-map 'transpose-words 'org-transpose-words) -(when (eq org-enable-table-editor 'optimized) - ;; If the user wants maximum table support, we need to hijack - ;; some standard editing functions - (org-remap org-mode-map - 'self-insert-command 'org-self-insert-command - 'delete-char 'org-delete-char - 'delete-backward-char 'org-delete-backward-char) - (org-defkey org-mode-map "|" 'org-force-self-insert)) - (defvar org-ctrl-c-ctrl-c-hook nil "Hook for functions attaching themselves to `C-c C-c'. @@ -19765,7 +20191,7 @@ See `org-ctrl-c-ctrl-c-hook' for more information.") (defun org-shiftselect-error () "Throw an error because Shift-Cursor command was applied in wrong context." (if (and (boundp 'shift-select-mode) shift-select-mode) - (user-error "To use shift-selection with Org-mode, customize `org-support-shift-select'") + (user-error "To use shift-selection with Org mode, customize `org-support-shift-select'") (user-error "This command works only in special context like headlines or timestamps"))) (defun org-call-for-shift-select (cmd) @@ -19820,32 +20246,30 @@ individual commands for more information." (call-interactively 'org-indent-item-tree)) (t (org-modifier-cursor-error)))) -(defun org-shiftmetaup (&optional arg) - "Move subtree up or kill table row. -Calls `org-move-subtree-up' or `org-table-kill-row' or -`org-move-item-up' or `org-timestamp-up', depending on context. -See the individual commands for more information." +(defun org-shiftmetaup (&optional _arg) + "Drag the line at point up. +In a table, kill the current row. +On a clock timestamp, update the value of the timestamp like `S-<up>' +but also adjust the previous clocked item in the clock history. +Everywhere else, drag the line at point up." (interactive "P") (cond ((run-hook-with-args-until-success 'org-shiftmetaup-hook)) ((org-at-table-p) (call-interactively 'org-table-kill-row)) - ((org-at-heading-p) (call-interactively 'org-move-subtree-up)) - ((org-at-item-p) (call-interactively 'org-move-item-up)) ((org-at-clock-log-p) (let ((org-clock-adjust-closest t)) (call-interactively 'org-timestamp-up))) (t (call-interactively 'org-drag-line-backward)))) -(defun org-shiftmetadown (&optional arg) - "Move subtree down or insert table row. -Calls `org-move-subtree-down' or `org-table-insert-row' or -`org-move-item-down' or `org-timestamp-up', depending on context. -See the individual commands for more information." +(defun org-shiftmetadown (&optional _arg) + "Drag the line at point down. +In a table, insert an empty row at the current line. +On a clock timestamp, update the value of the timestamp like `S-<down>' +but also adjust the previous clocked item in the clock history. +Everywhere else, drag the line at point down." (interactive "P") (cond ((run-hook-with-args-until-success 'org-shiftmetadown-hook)) ((org-at-table-p) (call-interactively 'org-table-insert-row)) - ((org-at-heading-p) (call-interactively 'org-move-subtree-down)) - ((org-at-item-p) (call-interactively 'org-move-item-down)) ((org-at-clock-log-p) (let ((org-clock-adjust-closest t)) (call-interactively 'org-timestamp-down))) (t (call-interactively 'org-drag-line-forward)))) @@ -19854,11 +20278,16 @@ See the individual commands for more information." (user-error "Hidden subtree, open with TAB or use subtree command M-S-<left>/<right>")) -(defun org-metaleft (&optional arg) - "Promote heading or move table column to left. -Calls `org-do-promote' or `org-table-move-column', depending on context. -With no specific context, calls the Emacs default `backward-word'. -See the individual commands for more information." +(defun org-metaleft (&optional _arg) + "Promote heading, list item at point or move table column left. + +Calls `org-do-promote', `org-outdent-item' or `org-table-move-column', +depending on context. With no specific context, calls the Emacs +default `backward-word'. See the individual commands for more +information. + +This function runs the hook `org-metaleft-hook' as a first step, +and returns at first non-nil value." (interactive "P") (cond ((run-hook-with-args-until-success 'org-metaleft-hook)) @@ -19883,11 +20312,18 @@ See the individual commands for more information." (call-interactively 'org-outdent-item)) (t (call-interactively 'backward-word)))) -(defun org-metaright (&optional arg) - "Demote a subtree, a list item or move table column to right. +(defun org-metaright (&optional _arg) + "Demote heading, list item at point or move table column right. + In front of a drawer or a block keyword, indent it correctly. + +Calls `org-do-demote', `org-indent-item', `org-table-move-column', +`org-indent-drawer' or `org-indent-block' depending on context. With no specific context, calls the Emacs default `forward-word'. -See the individual commands for more information." +See the individual commands for more information. + +This function runs the hook `org-metaright-hook' as a first step, +and returns at first non-nil value." (interactive "P") (cond ((run-hook-with-args-until-success 'org-metaright-hook)) @@ -19937,11 +20373,11 @@ this function returns t, nil otherwise." (goto-char (point-at-eol)) (setq end (max end (point))) (while (re-search-forward re end t) - (if (get-char-property (match-beginning 0) 'invisible) - (throw 'exit t)))) + (when (get-char-property (match-beginning 0) 'invisible) + (throw 'exit t)))) nil)))) -(defun org-metaup (&optional arg) +(defun org-metaup (&optional _arg) "Move subtree up or move table row up. Calls `org-move-subtree-up' or `org-table-move-row' or `org-move-item-up', depending on context. See the individual commands @@ -19963,7 +20399,7 @@ for more information." ((org-at-item-p) (call-interactively 'org-move-item-up)) (t (org-drag-element-backward)))) -(defun org-metadown (&optional arg) +(defun org-metadown (&optional _arg) "Move subtree down or move table row down. Calls `org-move-subtree-down' or `org-table-move-row' or `org-move-item-down', depending on context. See the individual @@ -19994,7 +20430,7 @@ depending on context. See the individual commands for more information." ((run-hook-with-args-until-success 'org-shiftup-hook)) ((and org-support-shift-select (org-region-active-p)) (org-call-for-shift-select 'previous-line)) - ((org-at-timestamp-p t) + ((org-at-timestamp-p 'lax) (call-interactively (if org-edit-timestamp-down-means-later 'org-timestamp-down 'org-timestamp-up))) ((and (not (eq org-support-shift-select 'always)) @@ -20018,7 +20454,7 @@ depending on context. See the individual commands for more information." ((run-hook-with-args-until-success 'org-shiftdown-hook)) ((and org-support-shift-select (org-region-active-p)) (org-call-for-shift-select 'next-line)) - ((org-at-timestamp-p t) + ((org-at-timestamp-p 'lax) (call-interactively (if org-edit-timestamp-down-means-later 'org-timestamp-up 'org-timestamp-down))) ((and (not (eq org-support-shift-select 'always)) @@ -20047,7 +20483,7 @@ Depending on context, this does one of the following: ((run-hook-with-args-until-success 'org-shiftright-hook)) ((and org-support-shift-select (org-region-active-p)) (org-call-for-shift-select 'forward-char)) - ((org-at-timestamp-p t) (call-interactively 'org-timestamp-up-day)) + ((org-at-timestamp-p 'lax) (call-interactively 'org-timestamp-up-day)) ((and (not (eq org-support-shift-select 'always)) (org-at-heading-p)) (let ((org-inhibit-logging @@ -20083,7 +20519,7 @@ Depending on context, this does one of the following: ((run-hook-with-args-until-success 'org-shiftleft-hook)) ((and org-support-shift-select (org-region-active-p)) (org-call-for-shift-select 'backward-char)) - ((org-at-timestamp-p t) (call-interactively 'org-timestamp-down-day)) + ((org-at-timestamp-p 'lax) (call-interactively 'org-timestamp-down-day)) ((and (not (eq org-support-shift-select 'always)) (org-at-heading-p)) (let ((org-inhibit-logging @@ -20135,7 +20571,7 @@ Depending on context, this does one of the following: "Change timestamps synchronously up in CLOCK log lines. Optional argument N tells to change by that many units." (interactive "P") - (if (and (org-at-clock-log-p) (org-at-timestamp-p t)) + (if (and (org-at-clock-log-p) (org-at-timestamp-p 'lax)) (let (org-support-shift-select) (org-clock-timestamps-up n)) (user-error "Not at a clock log"))) @@ -20144,11 +20580,37 @@ Optional argument N tells to change by that many units." "Change timestamps synchronously down in CLOCK log lines. Optional argument N tells to change by that many units." (interactive "P") - (if (and (org-at-clock-log-p) (org-at-timestamp-p t)) + (if (and (org-at-clock-log-p) (org-at-timestamp-p 'lax)) (let (org-support-shift-select) (org-clock-timestamps-down n)) (user-error "Not at a clock log"))) +(defun org-increase-number-at-point (&optional inc) + "Increment the number at point. +With an optional prefix numeric argument INC, increment using +this numeric value." + (interactive "p") + (if (not (number-at-point)) + (user-error "Not on a number") + (unless inc (setq inc 1)) + (let ((pos (point)) + (beg (skip-chars-backward "-+^/*0-9eE.")) + (end (skip-chars-forward "-+^/*0-9eE^.")) nap) + (setq nap (buffer-substring-no-properties + (+ pos beg) (+ pos beg end))) + (delete-region (+ pos beg) (+ pos beg end)) + (insert (calc-eval (concat (number-to-string inc) "+" nap)))) + (when (org-at-table-p) + (org-table-align) + (org-table-end-of-field 1)))) + +(defun org-decrease-number-at-point (&optional inc) + "Decrement the number at point. +With an optional prefix numeric argument INC, decrement using +this numeric value." + (interactive "p") + (org-increase-number-at-point (- (or inc 1)))) + (defun org-ctrl-c-ret () "Call `org-table-hline-and-move' or `org-insert-heading' dep. on context." (interactive) @@ -20170,32 +20632,30 @@ Optional argument N tells to change by that many units." (defun org-copy-visible (beg end) "Copy the visible parts of the region." (interactive "r") - (let (snippets s) - (save-excursion - (save-restriction - (narrow-to-region beg end) - (setq s (goto-char (point-min))) - (while (not (= (point) (point-max))) - (goto-char (org-find-invisible)) - (push (buffer-substring s (point)) snippets) - (setq s (goto-char (org-find-visible)))))) - (kill-new (apply 'concat (nreverse snippets))))) + (let ((result "")) + (while (/= beg end) + (when (get-char-property beg 'invisible) + (setq beg (next-single-char-property-change beg 'invisible nil end))) + (let ((next (next-single-char-property-change beg 'invisible nil end))) + (setq result (concat result (buffer-substring beg next))) + (setq beg next))) + (kill-new result))) (defun org-copy-special () "Copy region in table or copy current subtree. -Calls `org-table-copy' or `org-copy-subtree', depending on context. -See the individual commands for more information." +Calls `org-table-copy-region' or `org-copy-subtree', depending on +context. See the individual commands for more information." (interactive) (call-interactively - (if (org-at-table-p) 'org-table-copy-region 'org-copy-subtree))) + (if (org-at-table-p) #'org-table-copy-region #'org-copy-subtree))) (defun org-cut-special () "Cut region in table or cut current subtree. -Calls `org-table-copy' or `org-cut-subtree', depending on context. -See the individual commands for more information." +Calls `org-table-cut-region' or `org-cut-subtree', depending on +context. See the individual commands for more information." (interactive) (call-interactively - (if (org-at-table-p) 'org-table-cut-region 'org-cut-subtree))) + (if (org-at-table-p) #'org-table-cut-region #'org-cut-subtree))) (defun org-paste-special (arg) "Paste rectangular region into table, or past subtree relative to level. @@ -20206,57 +20666,69 @@ See the individual commands for more information." (org-table-paste-rectangle) (org-paste-subtree arg))) -(defsubst org-in-fixed-width-region-p () - "Is point in a fixed-width region?" - (save-match-data - (eq 'fixed-width (org-element-type (org-element-at-point))))) - (defun org-edit-special (&optional arg) "Call a special editor for the element at point. When at a table, call the formula editor with `org-table-edit-formulas'. When in a source code block, call `org-edit-src-code'. When in a fixed-width region, call `org-edit-fixed-width-region'. +When in an export block, call `org-edit-export-block'. +When in a LaTeX environment, call `org-edit-latex-environment'. When at an #+INCLUDE keyword, visit the included file. +When at a footnote reference, call `org-edit-footnote-reference' On a link, call `ffap' to visit the link at point. Otherwise, return a user error." (interactive "P") (let ((element (org-element-at-point))) - (assert (not buffer-read-only) nil - "Buffer is read-only: %s" (buffer-name)) - (case (org-element-type element) - (src-block + (barf-if-buffer-read-only) + (pcase (org-element-type element) + (`src-block (if (not arg) (org-edit-src-code) - (let* ((info (org-babel-get-src-block-info)) - (lang (nth 0 info)) - (params (nth 2 info)) - (session (cdr (assq :session params)))) - (if (not session) (org-edit-src-code) - ;; At a src-block with a session and function called with - ;; an ARG: switch to the buffer related to the inferior - ;; process. - (switch-to-buffer + (let* ((info (org-babel-get-src-block-info)) + (lang (nth 0 info)) + (params (nth 2 info)) + (session (cdr (assq :session params)))) + (if (not session) (org-edit-src-code) + ;; At a src-block with a session and function called with + ;; an ARG: switch to the buffer related to the inferior + ;; process. + (switch-to-buffer (funcall (intern (concat "org-babel-prep-session:" lang)) session params)))))) - (keyword + (`keyword (if (member (org-element-property :key element) '("INCLUDE" "SETUPFILE")) - (find-file - (org-remove-double-quotes - (car (org-split-string (org-element-property :value element))))) + (org-open-link-from-string + (format "[[%s]]" + (expand-file-name + (let ((value (org-element-property :value element))) + (cond ((org-file-url-p value) + (user-error "The file is specified as a URL, cannot be edited")) + ((not (org-string-nw-p value)) + (user-error "No file to edit")) + ((string-match "\\`\"\\(.*?\\)\"" value) + (match-string 1 value)) + ((string-match "\\`[^ \t\"]\\S-*" value) + (match-string 0 value)) + (t (user-error "No valid file specified"))))))) (user-error "No special environment to edit here"))) - (table + (`table (if (eq (org-element-property :type element) 'table.el) - (org-edit-src-code) + (org-edit-table.el) (call-interactively 'org-table-edit-formulas))) ;; Only Org tables contain `table-row' type elements. - (table-row (call-interactively 'org-table-edit-formulas)) - ((example-block export-block) (org-edit-src-code)) - (fixed-width (org-edit-fixed-width-region)) - (otherwise - ;; No notable element at point. Though, we may be at a link, - ;; which is an object. Thus, scan deeper. - (if (eq (org-element-type (org-element-context element)) 'link) - (call-interactively 'ffap) - (user-error "No special environment to edit here")))))) + (`table-row (call-interactively 'org-table-edit-formulas)) + (`example-block (org-edit-src-code)) + (`export-block (org-edit-export-block)) + (`fixed-width (org-edit-fixed-width-region)) + (`latex-environment (org-edit-latex-environment)) + (_ + ;; No notable element at point. Though, we may be at a link or + ;; a footnote reference, which are objects. Thus, scan deeper. + (let ((context (org-element-context element))) + (pcase (org-element-type context) + (`footnote-reference (org-edit-footnote-reference)) + (`inline-src-block (org-edit-inline-src-code)) + (`link (call-interactively #'ffap)) + (_ (user-error "No special environment to edit here")))))))) (defvar org-table-coordinate-overlays) ; defined in org-table.el (defun org-ctrl-c-ctrl-c (&optional arg) @@ -20305,240 +20777,319 @@ This command does many different things, depending on context: inhibited by setting `org-babel-no-eval-on-ctrl-c-ctrl-c'." (interactive "P") (cond - ((or (and (boundp 'org-clock-overlays) org-clock-overlays) - org-occur-highlights - org-latex-fragment-image-overlays) - (and (boundp 'org-clock-overlays) (org-clock-remove-overlays)) + ((or (bound-and-true-p org-clock-overlays) org-occur-highlights) + (when (boundp 'org-clock-overlays) (org-clock-remove-overlays)) (org-remove-occur-highlights) - (org-remove-latex-fragment-image-overlays) (message "Temporary highlights/overlays removed from current buffer")) - ((and (local-variable-p 'org-finish-function (current-buffer)) + ((and (local-variable-p 'org-finish-function) (fboundp org-finish-function)) (funcall org-finish-function)) + ((org-babel-hash-at-point)) ((run-hook-with-args-until-success 'org-ctrl-c-ctrl-c-hook)) (t - (let* ((context (org-element-context)) (type (org-element-type context))) - ;; Test if point is within a blank line. - (if (save-excursion (beginning-of-line) (looking-at "[ \t]*$")) - (or (run-hook-with-args-until-success 'org-ctrl-c-ctrl-c-final-hook) - (user-error "C-c C-c can do nothing useful at this location")) - (case type - ;; When at a link, act according to the parent instead. - (link (setq context (org-element-property :parent context)) - (setq type (org-element-type context))) - ;; Unsupported object types: refer to the first supported - ;; element or object containing it. - ((bold code entity export-snippet inline-babel-call inline-src-block - italic latex-fragment line-break macro strike-through subscript - superscript underline verbatim) - (while (and (setq context (org-element-property :parent context)) - (not (memq (setq type (org-element-type context)) - '(radio-target paragraph verse-block - table-cell))))))) - ;; For convenience: at the first line of a paragraph on the - ;; same line as an item, apply function on that item instead. - (when (eq type 'paragraph) - (let ((parent (org-element-property :parent context))) - (when (and (eq (org-element-type parent) 'item) - (= (point-at-bol) (org-element-property :begin parent))) - (setq context parent type 'item)))) - ;; Act according to type of element or object at point. - (case type - (clock (org-clock-update-time-maybe)) - (dynamic-block - (save-excursion - (goto-char (org-element-property :post-affiliated context)) - (org-update-dblock))) - (footnote-definition + (let* ((context + (org-element-lineage + (org-element-context) + ;; Limit to supported contexts. + '(babel-call clock dynamic-block footnote-definition + footnote-reference inline-babel-call inline-src-block + inlinetask item keyword node-property paragraph + plain-list planning property-drawer radio-target + src-block statistics-cookie table table-cell table-row + timestamp) + t)) + (type (org-element-type context))) + ;; For convenience: at the first line of a paragraph on the same + ;; line as an item, apply function on that item instead. + (when (eq type 'paragraph) + (let ((parent (org-element-property :parent context))) + (when (and (eq (org-element-type parent) 'item) + (= (line-beginning-position) + (org-element-property :begin parent))) + (setq context parent) + (setq type 'item)))) + ;; Act according to type of element or object at point. + ;; + ;; Do nothing on a blank line, except if it is contained in + ;; a src block. Hence, we first check if point is in such + ;; a block and then if it is at a blank line. + (pcase type + ((or `inline-src-block `src-block) + (unless org-babel-no-eval-on-ctrl-c-ctrl-c + (org-babel-eval-wipe-error-buffer) + (org-babel-execute-src-block + current-prefix-arg (org-babel-get-src-block-info nil context)))) + ((guard (org-match-line "[ \t]*$")) + (or (run-hook-with-args-until-success 'org-ctrl-c-ctrl-c-final-hook) + (user-error + (substitute-command-keys + "`\\[org-ctrl-c-ctrl-c]' can do nothing useful here")))) + ((or `babel-call `inline-babel-call) + (let ((info (org-babel-lob-get-info context))) + (when info (org-babel-execute-src-block nil info)))) + (`clock (org-clock-update-time-maybe)) + (`dynamic-block + (save-excursion (goto-char (org-element-property :post-affiliated context)) - (call-interactively 'org-footnote-action)) - (footnote-reference (call-interactively 'org-footnote-action)) - ((headline inlinetask) - (save-excursion (goto-char (org-element-property :begin context)) - (call-interactively 'org-set-tags))) - (item - ;; At an item: a double C-u set checkbox to "[-]" - ;; unconditionally, whereas a single one will toggle its - ;; presence. Without a universal argument, if the item - ;; has a checkbox, toggle it. Otherwise repair the list. - (let* ((box (org-element-property :checkbox context)) - (struct (org-element-property :structure context)) - (old-struct (copy-tree struct)) - (parents (org-list-parents-alist struct)) - (prevs (org-list-prevs-alist struct)) - (orderedp (org-not-nil (org-entry-get nil "ORDERED")))) - (org-list-set-checkbox - (org-element-property :begin context) struct - (cond ((equal arg '(16)) "[-]") - ((and (not box) (equal arg '(4))) "[ ]") - ((or (not box) (equal arg '(4))) nil) - ((eq box 'on) "[ ]") - (t "[X]"))) - ;; Mimic `org-list-write-struct' but with grabbing - ;; a return value from `org-list-struct-fix-box'. - (org-list-struct-fix-ind struct parents 2) - (org-list-struct-fix-item-end struct) - (org-list-struct-fix-bul struct prevs) - (org-list-struct-fix-ind struct parents) - (let ((block-item - (org-list-struct-fix-box struct parents prevs orderedp))) - (if (and box (equal struct old-struct)) - (if (equal arg '(16)) - (message "Checkboxes already reset") - (user-error "Cannot toggle this checkbox: %s" - (if (eq box 'on) - "all subitems checked" - "unchecked subitems"))) - (org-list-struct-apply-struct struct old-struct) - (org-update-checkbox-count-maybe)) - (when block-item - (message "Checkboxes were removed due to empty box at line %d" - (org-current-line block-item)))))) - (keyword - (let ((org-inhibit-startup-visibility-stuff t) - (org-startup-align-all-tables nil)) - (when (boundp 'org-table-coordinate-overlays) - (mapc 'delete-overlay org-table-coordinate-overlays) - (setq org-table-coordinate-overlays nil)) - (org-save-outline-visibility 'use-markers (org-mode-restart))) - (message "Local setup has been refreshed")) - (plain-list - ;; At a plain list, with a double C-u argument, set - ;; checkboxes of each item to "[-]", whereas a single one - ;; will toggle their presence according to the state of the - ;; first item in the list. Without an argument, repair the - ;; list. - (let* ((begin (org-element-property :contents-begin context)) - (beginm (move-marker (make-marker) begin)) - (struct (org-element-property :structure context)) - (old-struct (copy-tree struct)) - (first-box (save-excursion - (goto-char begin) - (looking-at org-list-full-item-re) - (match-string-no-properties 3))) - (new-box (cond ((equal arg '(16)) "[-]") - ((equal arg '(4)) (unless first-box "[ ]")) - ((equal first-box "[X]") "[ ]") - (t "[X]")))) - (cond - (arg - (mapc (lambda (pos) (org-list-set-checkbox pos struct new-box)) - (org-list-get-all-items - begin struct (org-list-prevs-alist struct)))) - ((and first-box (eq (point) begin)) - ;; For convenience, when point is at bol on the first - ;; item of the list and no argument is provided, simply - ;; toggle checkbox of that item, if any. - (org-list-set-checkbox begin struct new-box))) - (org-list-write-struct - struct (org-list-parents-alist struct) old-struct) - (org-update-checkbox-count-maybe) - (save-excursion (goto-char beginm) (org-list-send-list 'maybe)))) - ((property-drawer node-property) - (call-interactively 'org-property-action)) - ((radio-target target) - (call-interactively 'org-update-radio-target-regexp)) - (statistics-cookie - (call-interactively 'org-update-statistics-cookies)) - ((table table-cell table-row) - ;; At a table, recalculate every field and align it. Also - ;; send the table if necessary. If the table has - ;; a `table.el' type, just give up. At a table row or - ;; cell, maybe recalculate line but always align table. - (if (eq (org-element-property :type context) 'table.el) - (message "%s" "Use C-c ' to edit table.el tables") - (let ((org-enable-table-editor t)) - (if (or (eq type 'table) - ;; Check if point is at a TBLFM line. - (and (eq type 'table-row) - (= (point) (org-element-property :end context)))) - (save-excursion - (if (org-at-TBLFM-p) - (progn (require 'org-table) - (org-table-calc-current-TBLFM)) - (goto-char (org-element-property :contents-begin context)) - (org-call-with-arg 'org-table-recalculate (or arg t)) - (orgtbl-send-table 'maybe))) - (org-table-maybe-eval-formula) - (cond (arg (call-interactively 'org-table-recalculate)) - ((org-table-maybe-recalculate-line)) - (t (org-table-align))))))) - (timestamp (org-timestamp-change 0 'day)) - (otherwise - (or (run-hook-with-args-until-success 'org-ctrl-c-ctrl-c-final-hook) - (user-error - "C-c C-c can do nothing useful at this location"))))))))) + (org-update-dblock))) + (`footnote-definition + (goto-char (org-element-property :post-affiliated context)) + (call-interactively 'org-footnote-action)) + (`footnote-reference (call-interactively #'org-footnote-action)) + ((or `headline `inlinetask) + (save-excursion (goto-char (org-element-property :begin context)) + (call-interactively #'org-set-tags))) + (`item + ;; At an item: `C-u C-u' sets checkbox to "[-]" + ;; unconditionally, whereas `C-u' will toggle its presence. + ;; Without a universal argument, if the item has a checkbox, + ;; toggle it. Otherwise repair the list. + (let* ((box (org-element-property :checkbox context)) + (struct (org-element-property :structure context)) + (old-struct (copy-tree struct)) + (parents (org-list-parents-alist struct)) + (prevs (org-list-prevs-alist struct)) + (orderedp (org-not-nil (org-entry-get nil "ORDERED")))) + (org-list-set-checkbox + (org-element-property :begin context) struct + (cond ((equal arg '(16)) "[-]") + ((and (not box) (equal arg '(4))) "[ ]") + ((or (not box) (equal arg '(4))) nil) + ((eq box 'on) "[ ]") + (t "[X]"))) + ;; Mimic `org-list-write-struct' but with grabbing a return + ;; value from `org-list-struct-fix-box'. + (org-list-struct-fix-ind struct parents 2) + (org-list-struct-fix-item-end struct) + (org-list-struct-fix-bul struct prevs) + (org-list-struct-fix-ind struct parents) + (let ((block-item + (org-list-struct-fix-box struct parents prevs orderedp))) + (if (and box (equal struct old-struct)) + (if (equal arg '(16)) + (message "Checkboxes already reset") + (user-error "Cannot toggle this checkbox: %s" + (if (eq box 'on) + "all subitems checked" + "unchecked subitems"))) + (org-list-struct-apply-struct struct old-struct) + (org-update-checkbox-count-maybe)) + (when block-item + (message "Checkboxes were removed due to empty box at line %d" + (org-current-line block-item)))))) + (`keyword + (let ((org-inhibit-startup-visibility-stuff t) + (org-startup-align-all-tables nil)) + (when (boundp 'org-table-coordinate-overlays) + (mapc #'delete-overlay org-table-coordinate-overlays) + (setq org-table-coordinate-overlays nil)) + (org-save-outline-visibility 'use-markers (org-mode-restart))) + (message "Local setup has been refreshed")) + (`plain-list + ;; At a plain list, with a double C-u argument, set + ;; checkboxes of each item to "[-]", whereas a single one + ;; will toggle their presence according to the state of the + ;; first item in the list. Without an argument, repair the + ;; list. + (let* ((begin (org-element-property :contents-begin context)) + (beginm (move-marker (make-marker) begin)) + (struct (org-element-property :structure context)) + (old-struct (copy-tree struct)) + (first-box (save-excursion + (goto-char begin) + (looking-at org-list-full-item-re) + (match-string-no-properties 3))) + (new-box (cond ((equal arg '(16)) "[-]") + ((equal arg '(4)) (unless first-box "[ ]")) + ((equal first-box "[X]") "[ ]") + (t "[X]")))) + (cond + (arg + (dolist (pos + (org-list-get-all-items + begin struct (org-list-prevs-alist struct))) + (org-list-set-checkbox pos struct new-box))) + ((and first-box (eq (point) begin)) + ;; For convenience, when point is at bol on the first + ;; item of the list and no argument is provided, simply + ;; toggle checkbox of that item, if any. + (org-list-set-checkbox begin struct new-box))) + (org-list-write-struct + struct (org-list-parents-alist struct) old-struct) + (org-update-checkbox-count-maybe) + (save-excursion (goto-char beginm) (org-list-send-list 'maybe)))) + ((or `property-drawer `node-property) + (call-interactively #'org-property-action)) + (`radio-target + (call-interactively #'org-update-radio-target-regexp)) + (`statistics-cookie + (call-interactively #'org-update-statistics-cookies)) + ((or `table `table-cell `table-row) + ;; At a table, recalculate every field and align it. Also + ;; send the table if necessary. If the table has + ;; a `table.el' type, just give up. At a table row or cell, + ;; maybe recalculate line but always align table. + (if (eq (org-element-property :type context) 'table.el) + (message "%s" (substitute-command-keys "\\<org-mode-map>\ +Use `\\[org-edit-special]' to edit table.el tables")) + (if (or (eq type 'table) + ;; Check if point is at a TBLFM line. + (and (eq type 'table-row) + (= (point) (org-element-property :end context)))) + (save-excursion + (if (org-at-TBLFM-p) + (progn (require 'org-table) + (org-table-calc-current-TBLFM)) + (goto-char (org-element-property :contents-begin context)) + (org-call-with-arg 'org-table-recalculate (or arg t)) + (orgtbl-send-table 'maybe))) + (org-table-maybe-eval-formula) + (cond (arg (call-interactively #'org-table-recalculate)) + ((org-table-maybe-recalculate-line)) + (t (org-table-align)))))) + ((or `timestamp (and `planning (guard (org-at-timestamp-p 'lax)))) + (org-timestamp-change 0 'day)) + ((and `nil (guard (org-at-heading-p))) + ;; When point is on an unsupported object type, we can miss + ;; the fact that it also is at a heading. Handle it here. + (call-interactively #'org-set-tags)) + ((guard + (run-hook-with-args-until-success 'org-ctrl-c-ctrl-c-final-hook))) + (_ + (user-error + (substitute-command-keys + "`\\[org-ctrl-c-ctrl-c]' can do nothing useful here")))))))) (defun org-mode-restart () (interactive) - (let ((indent-status (org-bound-and-true-p org-indent-mode))) + (let ((indent-status (bound-and-true-p org-indent-mode))) (funcall major-mode) (hack-local-variables) - (when (and indent-status (not (org-bound-and-true-p org-indent-mode))) - (org-indent-mode -1))) + (when (and indent-status (not (bound-and-true-p org-indent-mode))) + (org-indent-mode -1)) + (org-reset-file-cache)) (message "%s restarted" major-mode)) (defun org-kill-note-or-show-branches () - "If this is a Note buffer, abort storing the note. Else call `show-branches'." + "Abort storing current note, or call `outline-show-branches'." (interactive) (if (not org-finish-function) (progn - (hide-subtree) - (call-interactively 'show-branches)) + (outline-hide-subtree) + (call-interactively 'outline-show-branches)) (let ((org-note-abort t)) (funcall org-finish-function)))) +(defun org-delete-indentation (&optional arg) + "Join current line to previous and fix whitespace at join. + +If previous line is a headline add to headline title. Otherwise +the function calls `delete-indentation'. + +With a non-nil optional argument, join it to the following one." + (interactive "*P") + (if (save-excursion + (beginning-of-line (if arg 1 0)) + (let ((case-fold-search nil)) + (looking-at org-complex-heading-regexp))) + ;; At headline. + (let ((tags-column (when (match-beginning 5) + (save-excursion (goto-char (match-beginning 5)) + (current-column)))) + (string (concat " " (progn (when arg (forward-line 1)) + (org-trim (delete-and-extract-region + (line-beginning-position) + (line-end-position))))))) + (unless (bobp) (delete-region (point) (1- (point)))) + (goto-char (or (match-end 4) + (match-beginning 5) + (match-end 0))) + (skip-chars-backward " \t") + (save-excursion (insert string)) + ;; Adjust alignment of tags. + (cond + ((not tags-column)) ;no tags + (org-auto-align-tags (org-set-tags nil t)) + (t (org--align-tags-here tags-column)))) ;preserve tags column + (delete-indentation arg))) + (defun org-open-line (n) "Insert a new row in tables, call `open-line' elsewhere. -If `org-special-ctrl-o' is nil, just call `open-line' everywhere." +If `org-special-ctrl-o' is nil, just call `open-line' everywhere. +As a special case, when a document starts with a table, allow to +call `open-line' on the very first character." (interactive "*p") - (cond - ((not org-special-ctrl-o) - (open-line n)) - ((org-at-table-p) - (org-table-insert-row)) - (t - (open-line n)))) + (if (and org-special-ctrl-o (/= (point) 1) (org-at-table-p)) + (org-table-insert-row) + (open-line n))) (defun org-return (&optional indent) "Goto next table row or insert a newline. + Calls `org-table-next-row' or `newline', depending on context. -See the individual commands for more information." + +When optional INDENT argument is non-nil, call +`newline-and-indent' instead of `newline'. + +When `org-return-follows-link' is non-nil and point is on +a timestamp or a link, call `org-open-at-point'. However, it +will not happen if point is in a table or on a \"dead\" +object (e.g., within a comment). In these case, you need to use +`org-open-at-point' directly." (interactive) - (let (org-ts-what) + (let ((context (if org-return-follows-link (org-element-context) + (org-element-at-point)))) (cond - ((or (bobp) (org-in-src-block-p)) - (if indent (newline-and-indent) (newline))) - ((org-at-table-p) - (org-table-justify-field-maybe) - (call-interactively 'org-table-next-row)) - ;; when `newline-and-indent' is called within a list, make sure - ;; text moved stays inside the item. - ((and (org-in-item-p) indent) - (if (and (org-at-item-p) (>= (point) (match-end 0))) - (progn - (save-match-data (newline)) - (org-indent-line-to (length (match-string 0)))) - (let ((ind (org-get-indentation))) - (newline) - (if (org-looking-back org-list-end-re) - (org-indent-line) - (org-indent-line-to ind))))) - ((and org-return-follows-link - (org-at-timestamp-p t) - (not (eq org-ts-what 'after))) - (org-follow-timestamp-link)) + ;; In a table, call `org-table-next-row'. However, before first + ;; column or after last one, split the table. + ((or (and (eq (org-element-type context) 'table) + (>= (point) (org-element-property :contents-begin context)) + (< (point) (org-element-property :contents-end context))) + (org-element-lineage context '(table-row table-cell) t)) + (if (or (looking-at-p "[ \t]*$") + (save-excursion (skip-chars-backward " \t") (bolp))) + (insert "\n") + (org-table-justify-field-maybe) + (call-interactively #'org-table-next-row))) + ;; On a link or a timestamp, call `org-open-at-point' if + ;; `org-return-follows-link' allows it. Tolerate fuzzy + ;; locations, e.g., in a comment, as `org-open-at-point'. ((and org-return-follows-link - (let ((tprop (get-text-property (point) 'face))) - (or (eq tprop 'org-link) - (and (listp tprop) (memq 'org-link tprop))))) - (call-interactively 'org-open-at-point)) - ((and (org-at-heading-p) - (looking-at - (org-re "\\([ \t]+\\(:[[:alnum:]_@#%:]+:\\)\\)[ \t]*$"))) - (org-show-entry) - (end-of-line 1) - (newline)) + (or (org-in-regexp org-ts-regexp-both nil t) + (org-in-regexp org-tsr-regexp-both nil t) + (org-in-regexp org-any-link-re nil t))) + (call-interactively #'org-open-at-point)) + ;; Insert newline in heading, but preserve tags. + ((and (not (bolp)) + (save-excursion (beginning-of-line) + (let ((case-fold-search nil)) + (looking-at org-complex-heading-regexp)))) + ;; At headline. Split line. However, if point is on keyword, + ;; priority cookie or tags, do not break any of them: add + ;; a newline after the headline instead. + (let ((tags-column (and (match-beginning 5) + (save-excursion (goto-char (match-beginning 5)) + (current-column)))) + (string + (when (and (match-end 4) (org-point-in-group (point) 4)) + (delete-and-extract-region (point) (match-end 4))))) + ;; Adjust tag alignment. + (cond + ((not (and tags-column string))) + (org-auto-align-tags (org-set-tags nil t)) + (t (org--align-tags-here tags-column))) ;preserve tags column + (end-of-line) + (org-show-entry) + (if indent (newline-and-indent) (newline)) + (when string (save-excursion (insert (org-trim string)))))) + ;; In a list, make sure indenting keeps trailing text within. + ((and indent + (not (eolp)) + (org-element-lineage context '(item))) + (let ((trailing-data + (delete-and-extract-region (point) (line-end-position)))) + (newline-and-indent) + (save-excursion (insert trailing-data)))) (t (if indent (newline-and-indent) (newline)))))) (defun org-return-indent () @@ -20576,141 +21127,11 @@ Calls `org-table-insert-hline', `org-toggle-item', or (t (call-interactively 'org-toggle-item)))) -(defun org-toggle-item (arg) - "Convert headings or normal lines to items, items to normal lines. -If there is no active region, only the current line is considered. - -If the first non blank line in the region is a headline, convert -all headlines to items, shifting text accordingly. - -If it is an item, convert all items to normal lines. - -If it is normal text, change region into a list of items. -With a prefix argument ARG, change the region in a single item." - (interactive "P") - (let ((shift-text - (function - ;; Shift text in current section to IND, from point to END. - ;; The function leaves point to END line. - (lambda (ind end) - (let ((min-i 1000) (end (copy-marker end))) - ;; First determine the minimum indentation (MIN-I) of - ;; the text. - (save-excursion - (catch 'exit - (while (< (point) end) - (let ((i (org-get-indentation))) - (cond - ;; Skip blank lines and inline tasks. - ((looking-at "^[ \t]*$")) - ((looking-at org-outline-regexp-bol)) - ;; We can't find less than 0 indentation. - ((zerop i) (throw 'exit (setq min-i 0))) - ((< i min-i) (setq min-i i)))) - (forward-line)))) - ;; Then indent each line so that a line indented to - ;; MIN-I becomes indented to IND. Ignore blank lines - ;; and inline tasks in the process. - (let ((delta (- ind min-i))) - (while (< (point) end) - (unless (or (looking-at "^[ \t]*$") - (looking-at org-outline-regexp-bol)) - (org-indent-line-to (+ (org-get-indentation) delta))) - (forward-line))))))) - (skip-blanks - (function - ;; Return beginning of first non-blank line, starting from - ;; line at POS. - (lambda (pos) - (save-excursion - (goto-char pos) - (skip-chars-forward " \r\t\n") - (point-at-bol))))) - beg end) - ;; Determine boundaries of changes. - (if (org-region-active-p) - (setq beg (funcall skip-blanks (region-beginning)) - end (copy-marker (region-end))) - (setq beg (funcall skip-blanks (point-at-bol)) - end (copy-marker (point-at-eol)))) - ;; Depending on the starting line, choose an action on the text - ;; between BEG and END. - (org-with-limited-levels - (save-excursion - (goto-char beg) - (cond - ;; Case 1. Start at an item: de-itemize. Note that it only - ;; happens when a region is active: `org-ctrl-c-minus' - ;; would call `org-cycle-list-bullet' otherwise. - ((org-at-item-p) - (while (< (point) end) - (when (org-at-item-p) - (skip-chars-forward " \t") - (delete-region (point) (match-end 0))) - (forward-line))) - ;; Case 2. Start at an heading: convert to items. - ((org-at-heading-p) - (let* ((bul (org-list-bullet-string "-")) - (bul-len (length bul)) - ;; Indentation of the first heading. It should be - ;; relative to the indentation of its parent, if any. - (start-ind (save-excursion - (cond - ((not org-adapt-indentation) 0) - ((not (outline-previous-heading)) 0) - (t (length (match-string 0)))))) - ;; Level of first heading. Further headings will be - ;; compared to it to determine hierarchy in the list. - (ref-level (org-reduced-level (org-outline-level)))) - (while (< (point) end) - (let* ((level (org-reduced-level (org-outline-level))) - (delta (max 0 (- level ref-level)))) - ;; If current headline is less indented than the first - ;; one, set it as reference, in order to preserve - ;; subtrees. - (when (< level ref-level) (setq ref-level level)) - (replace-match bul t t) - (org-indent-line-to (+ start-ind (* delta bul-len))) - ;; Ensure all text down to END (or SECTION-END) belongs - ;; to the newly created item. - (let ((section-end (save-excursion - (or (outline-next-heading) (point))))) - (forward-line) - (funcall shift-text - (+ start-ind (* (1+ delta) bul-len)) - (min end section-end))))))) - ;; Case 3. Normal line with ARG: make the first line of region - ;; an item, and shift indentation of others lines to - ;; set them as item's body. - (arg (let* ((bul (org-list-bullet-string "-")) - (bul-len (length bul)) - (ref-ind (org-get-indentation))) - (skip-chars-forward " \t") - (insert bul) - (forward-line) - (while (< (point) end) - ;; Ensure that lines less indented than first one - ;; still get included in item body. - (funcall shift-text - (+ ref-ind bul-len) - (min end (save-excursion (or (outline-next-heading) - (point))))) - (forward-line)))) - ;; Case 4. Normal line without ARG: turn each non-item line - ;; into an item. - (t - (while (< (point) end) - (unless (or (org-at-heading-p) (org-at-item-p)) - (if (looking-at "\\([ \t]*\\)\\(\\S-\\)") - (replace-match - (concat "\\1" (org-list-bullet-string "-") "\\2")))) - (forward-line)))))))) - (defun org-toggle-heading (&optional nstars) "Convert headings to normal text, or items or text to headings. If there is no active region, only convert the current line. -With a \\[universal-argument] prefix, convert the whole list at +With a `\\[universal-argument]' prefix, convert the whole list at point into heading. In a region: @@ -20746,7 +21167,7 @@ number of stars to add." ;; do not consider the last line to be in the region. (when (and current-prefix-arg (org-at-item-p)) - (if (listp current-prefix-arg) (setq current-prefix-arg 1)) + (when (listp current-prefix-arg) (setq current-prefix-arg 1)) (org-mark-element)) (if (org-region-active-p) @@ -20771,31 +21192,17 @@ number of stars to add." ;; Case 2. Started at an item: change items into headlines. ;; One star will be added by `org-list-to-subtree'. ((org-at-item-p) - (let* ((stars (make-string - ;; subtract the star that will be added again by - ;; `org-list-to-subtree' - (if (numberp nstars) (1- nstars) - (or (org-current-level) 0)) - ?*)) - (add-stars - (cond (nstars "") ; stars from prefix only - ((equal stars "") "") ; before first heading - (org-odd-levels-only "*") ; inside heading, odd - (t "")))) ; inside heading, oddeven - (while (< (point) end) - (when (org-at-item-p) - ;; Pay attention to cases when region ends before list. - (let* ((struct (org-list-struct)) - (list-end (min (org-list-get-bottom-point struct) (1+ end)))) - (save-restriction - (narrow-to-region (point) list-end) - (insert - (org-list-to-subtree - (org-list-parse-list t) - `(:istart (concat ',stars ',add-stars (funcall get-stars depth)) - :icount (concat ',stars ',add-stars (funcall get-stars depth))))))) - (setq toggled t)) - (forward-line)))) + (while (< (point) end) + (when (org-at-item-p) + ;; Pay attention to cases when region ends before list. + (let* ((struct (org-list-struct)) + (list-end + (min (org-list-get-bottom-point struct) (1+ end)))) + (save-restriction + (narrow-to-region (point) list-end) + (insert (org-list-to-subtree (org-list-to-lisp t)) "\n"))) + (setq toggled t)) + (forward-line))) ;; Case 3. Started at normal text: make every line an heading, ;; skipping headlines and items. (t (let* ((stars @@ -20807,7 +21214,7 @@ number of stars to add." (org-odd-levels-only "**") ; inside heading, odd (t "*"))) ; inside heading, oddeven (rpl (concat stars add-stars " ")) - (lend (if (listp nstars) (save-excursion (end-of-line) (point))))) + (lend (when (listp nstars) (save-excursion (end-of-line) (point))))) (while (< (point) (if (equal nstars '(4)) lend end)) (when (and (not (or (org-at-heading-p) (org-at-item-p) (org-at-comment-p))) (looking-at "\\([ \t]*\\)\\(\\S-\\)")) @@ -20815,24 +21222,18 @@ number of stars to add." (forward-line))))))) (unless toggled (message "Cannot toggle heading from here")))) -(defun org-meta-return (&optional _arg) +(defun org-meta-return (&optional arg) "Insert a new heading or wrap a region in a table. -Calls `org-insert-heading' or `org-table-wrap-region', depending -on context. See the individual commands for more information." - (interactive) +Calls `org-insert-heading', `org-insert-item' or +`org-table-wrap-region', depending on context. When called with +an argument, unconditionally call `org-insert-heading'." + (interactive "P") (org-check-before-invisible-edit 'insert) (or (run-hook-with-args-until-success 'org-metareturn-hook) - (let* ((element (org-element-at-point)) - (type (org-element-type element))) - (when (eq type 'table-row) - (setq element (org-element-property :parent element)) - (setq type 'table)) - (if (and (eq type 'table) - (eq (org-element-property :type element) 'org) - (>= (point) (org-element-property :contents-begin element)) - (< (point) (org-element-property :contents-end element))) - (call-interactively 'org-table-wrap-region) - (call-interactively 'org-insert-heading))))) + (call-interactively (cond (arg #'org-insert-heading) + ((org-at-table-p) #'org-table-wrap-region) + ((org-in-item-p) #'org-insert-item) + (t #'org-insert-heading))))) ;;; Menu entries @@ -20841,7 +21242,7 @@ on context. See the individual commands for more information." (and (not (org-before-first-heading-p)) (not (org-at-table-p)))) -;; Define the Org-mode menus +;; Define the Org mode menus (easy-menu-define org-tbl-menu org-mode-map "Tbl menu" '("Tbl" ["Align" org-ctrl-c-ctrl-c :active (org-at-table-p)] @@ -20888,19 +21289,22 @@ on context. See the individual commands for more information." ["Which Column?" org-table-current-column (org-at-table-p)]) ["Debug Formulas" org-table-toggle-formula-debugger - :style toggle :selected (org-bound-and-true-p org-table-formula-debug)] + :style toggle :selected (bound-and-true-p org-table-formula-debug)] ["Show Col/Row Numbers" org-table-toggle-coordinate-overlays :style toggle - :selected (org-bound-and-true-p org-table-overlay-coordinates)] + :selected (bound-and-true-p org-table-overlay-coordinates)] "--" - ["Create" org-table-create (and (not (org-at-table-p)) - org-enable-table-editor)] + ["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])) + ["Create/Convert from/to table.el" org-table-create-with-table.el t] + "--" + ("Plot" + ["Ascii plot" orgtbl-ascii-plot :active (org-at-table-p) :keys "C-c \" a"] + ["Gnuplot" org-plot/gnuplot :active (org-at-table-p) :keys "C-c \" g"]))) (easy-menu-define org-org-menu org-mode-map "Org menu" '("Org" @@ -20909,7 +21313,7 @@ on context. See the individual commands for more information." ["Cycle Global Visibility" org-shifttab :active (not (org-at-table-p))] ["Sparse Tree..." org-sparse-tree t] ["Reveal Context" org-reveal t] - ["Show All" show-all t] + ["Show All" outline-show-all t] "--" ["Subtree to indirect buffer" org-tree-to-indirect-buffer t]) "--" @@ -20925,8 +21329,8 @@ on context. See the individual commands for more information." ("Edit Structure" ["Refile Subtree" org-refile (org-in-subtree-not-table-p)] "--" - ["Move Subtree Up" org-shiftmetaup (org-in-subtree-not-table-p)] - ["Move Subtree Down" org-shiftmetadown (org-in-subtree-not-table-p)] + ["Move Subtree Up" org-metaup (org-at-heading-p)] + ["Move Subtree Down" org-metadown (org-at-heading-p)] "--" ["Copy Subtree" org-copy-special (org-in-subtree-not-table-p)] ["Cut Subtree" org-cut-special (org-in-subtree-not-table-p)] @@ -20987,7 +21391,7 @@ on context. See the individual commands for more information." ["Next keyword set" org-shiftcontrolright (and (> (length org-todo-sets) 1) (org-at-heading-p))] ["Previous keyword set" org-shiftcontrolright (and (> (length org-todo-sets) 1) (org-at-heading-p))]) ["Show TODO Tree" org-show-todo-tree :active t :keys "C-c / t"] - ["Global TODO list" org-todo-list :active t :keys "C-c a t"] + ["Global TODO list" org-todo-list :active t :keys "\\[org-agenda] t"] "--" ["Enforce dependencies" (customize-variable 'org-enforce-todo-dependencies) :selected org-enforce-todo-dependencies :style toggle :active t] @@ -21012,15 +21416,15 @@ on context. See the individual commands for more information." "--" ["Set property" org-set-property (not (org-before-first-heading-p))] ["Column view of properties" org-columns t] - ["Insert Column View DBlock" org-insert-columns-dblock t]) + ["Insert Column View DBlock" org-columns-insert-dblock t]) ("Dates and Scheduling" ["Timestamp" org-time-stamp (not (org-before-first-heading-p))] ["Timestamp (inactive)" org-time-stamp-inactive (not (org-before-first-heading-p))] ("Change Date" - ["1 Day Later" org-shiftright (org-at-timestamp-p)] - ["1 Day Earlier" org-shiftleft (org-at-timestamp-p)] - ["1 ... Later" org-shiftup (org-at-timestamp-p)] - ["1 ... Earlier" org-shiftdown (org-at-timestamp-p)]) + ["1 Day Later" org-shiftright (org-at-timestamp-p 'lax)] + ["1 Day Earlier" org-shiftleft (org-at-timestamp-p 'lax)] + ["1 ... Later" org-shiftup (org-at-timestamp-p 'lax)] + ["1 ... Earlier" org-shiftdown (org-at-timestamp-p 'lax)]) ["Compute Time Range" org-evaluate-time-range t] ["Schedule Item" org-schedule (not (org-before-first-heading-p))] ["Deadline" org-deadline (not (org-before-first-heading-p))] @@ -21062,25 +21466,22 @@ on context. See the individual commands for more information." ("Special views current file" ["TODO Tree" org-show-todo-tree t] ["Check Deadlines" org-check-deadlines t] - ["Timeline" org-timeline t] ["Tags/Property tree" org-match-sparse-tree t]) "--" ["Export/Publish..." org-export-dispatch t] ("LaTeX" - ["Org CDLaTeX mode" org-cdlatex-mode :style toggle - :selected org-cdlatex-mode] + ["Org CDLaTeX mode" org-cdlatex-mode :active (require 'cdlatex nil t) + :style toggle :selected org-cdlatex-mode] ["Insert Environment" cdlatex-environment (fboundp 'cdlatex-environment)] ["Insert math symbol" cdlatex-math-symbol (fboundp 'cdlatex-math-symbol)] ["Modify math symbol" org-cdlatex-math-modify (org-inside-LaTeX-fragment-p)] - ["Insert citation" org-reftex-citation t] - "--" - ["Template for BEAMER" (org-beamer-insert-options-template) t]) + ["Insert citation" org-reftex-citation t]) "--" ("MobileOrg" ["Push Files and Views" org-mobile-push t] ["Get Captured and Flagged" org-mobile-pull t] - ["Find FLAGGED Tasks" (org-agenda nil "?") :active t :keys "C-c a ?"] + ["Find FLAGGED Tasks" (org-agenda nil "?") :active t :keys "\\[org-agenda] ?"] "--" ["Setup" (progn (require 'org-mobile) (customize-group 'org-mobile)) t]) "--" @@ -21101,20 +21502,20 @@ on context. See the individual commands for more information." )) (defun org-info (&optional node) - "Read documentation for Org-mode in the info system. + "Read documentation for Org in the info system. With optional NODE, go directly to that node." (interactive) (info (format "(org)%s" (or node "")))) ;;;###autoload (defun org-submit-bug-report () - "Submit a bug report on Org-mode via mail. + "Submit a bug report on Org via mail. Don't hesitate to report any problems or inaccurate documentation. If you don't have setup sending mail from (X)Emacs, please copy the output buffer into your mail program, as it gives us important -information about your Org-mode version and configuration." +information about your Org version and configuration." (interactive) (require 'reporter) (defvar reporter-prompt-for-summary-p) @@ -21126,12 +21527,12 @@ information about your Org-mode version and configuration." (org-version nil 'full) (let (list) (save-window-excursion - (org-pop-to-buffer-same-window (get-buffer-create "*Warn about privacy*")) + (pop-to-buffer-same-window (get-buffer-create "*Warn about privacy*")) (delete-other-windows) (erase-buffer) - (insert "You are about to submit a bug report to the Org-mode mailing list. + (insert "You are about to submit a bug report to the Org mailing list. -We would like to add your full Org-mode and Outline configuration to the +We would like to add your full Org and Outline configuration to the bug report. This greatly simplifies the work of the maintainer and other experts on the mailing list. @@ -21141,7 +21542,7 @@ appear in the form of file names, tags, todo states, or search strings. If you answer yes to the prompt, you might want to check and remove such private information before sending the email.") (add-text-properties (point-min) (point-max) '(face org-warning)) - (when (yes-or-no-p "Include your Org-mode configuration ") + (when (yes-or-no-p "Include your Org configuration ") (mapatoms (lambda (v) (and (boundp v) @@ -21160,11 +21561,11 @@ what in fact did happen. You don't know how to make a good report? See http://orgmode.org/manual/Feedback.html#Feedback -Your bug report will be posted to the Org-mode mailing list. +Your bug report will be posted to the Org mailing list. ------------------------------------------------------------------------") (save-excursion - (if (re-search-backward "^\\(Subject: \\)Org-mode version \\(.*?\\);[ \t]*\\(.*\\)" nil t) - (replace-match "\\1Bug: \\3 [\\2]"))))) + (when (re-search-backward "^\\(Subject: \\)Org mode version \\(.*?\\);[ \t]*\\(.*\\)" nil t) + (replace-match "\\1Bug: \\3 [\\2]"))))) (defun org-install-agenda-files-menu () @@ -21172,7 +21573,7 @@ Your bug report will be posted to the Org-mode mailing list. (save-excursion (while bl (set-buffer (pop bl)) - (if (derived-mode-p 'org-mode) (setq bl nil))) + (when (derived-mode-p 'org-mode) (setq bl nil))) (when (derived-mode-p 'org-mode) (easy-menu-change '("Org") "File List for Agenda" @@ -21184,13 +21585,15 @@ Your bug report will be posted to the Org-mode mailing list. ["Cycle through agenda files" org-cycle-agenda-files t] ["Occur in all agenda files" org-occur-in-agenda-files t] "--") - (mapcar 'org-file-menu-entry (org-agenda-files t)))))))) + (mapcar 'org-file-menu-entry + ;; Prevent initialization from failing. + (ignore-errors (org-agenda-files t))))))))) ;;;; Documentation (defun org-require-autoloaded-modules () (interactive) - (mapc 'require + (mapc #'require '(org-agenda org-archive org-attach org-clock org-colview org-id org-table org-timer))) @@ -21203,13 +21606,8 @@ With prefix arg UNCOMPILED, load the uncompiled versions." (let* ((org-dir (org-find-library-dir "org")) (contrib-dir (or (org-find-library-dir "org-contribdir") org-dir)) (feature-re "^\\(org\\|ob\\|ox\\)\\(-.*\\)?") - (remove-re (mapconcat 'identity - (mapcar (lambda (f) (concat "^" f "$")) - (list (if (featurep 'xemacs) - "org-colview" - "org-colview-xemacs") - "org" "org-loaddefs" "org-version")) - "\\|")) + (remove-re (format "\\`%s\\'" + (regexp-opt '("org" "org-loaddefs" "org-version")))) (feats (delete-dups (mapcar 'file-name-sans-extension (mapcar 'file-name-nondirectory @@ -21241,9 +21639,9 @@ With prefix arg UNCOMPILED, load the uncompiled versions." 't) f)) lfeat))) - (if load-uncore - (message "The following feature%s found in load-path, please check if that's correct:\n%s" - (if (> (length load-uncore) 1) "s were" " was") load-uncore)) + (when load-uncore + (message "The following feature%s found in load-path, please check if that's correct:\n%s" + (if (> (length load-uncore) 1) "s were" " was") load-uncore)) (if load-misses (message "Some error occurred while reloading Org feature%s\n%s\nPlease check *Messages*!\n%s" (if (> (length load-misses) 1) "s" "") load-misses (org-version nil 'full)) @@ -21258,7 +21656,7 @@ With prefix arg UNCOMPILED, load the uncompiled versions." (customize-browse 'org)) (defun org-create-customize-menu () - "Create a full customization menu for Org-mode, insert it into the menu." + "Create a full customization menu for Org mode, insert it into the menu." (interactive) (org-load-modules-maybe) (org-require-autoloaded-modules) @@ -21281,9 +21679,9 @@ With prefix arg UNCOMPILED, load the uncompiled versions." ;;; Generally useful functions -(defun org-get-at-bol (property) - "Get text property PROPERTY at beginning of line." - (get-text-property (point-at-bol) property)) +(defun org-get-at-eol (property n) + "Get text property PROPERTY at the end of line less N characters." + (get-text-property (- (point-at-eol) n) property)) (defun org-find-text-property-in-string (prop s) "Return the first non-nil value of property PROP in string S." @@ -21291,19 +21689,9 @@ With prefix arg UNCOMPILED, load the uncompiled versions." (get-text-property (or (next-single-property-change 0 prop s) 0) prop s))) -(defun org-display-warning (message) ;; Copied from Emacs-Muse +(defun org-display-warning (message) "Display the given MESSAGE as a warning." - (if (fboundp 'display-warning) - (display-warning 'org message - (if (featurep 'xemacs) 'warning :warning)) - (let ((buf (get-buffer-create "*Org warnings*"))) - (with-current-buffer buf - (goto-char (point-max)) - (insert "Warning (Org): " message) - (unless (bolp) - (newline))) - (display-buffer buf) - (sit-for 0)))) + (display-warning 'org message :warning)) (defun org-eval (form) "Eval FORM and return result." @@ -21322,32 +21710,41 @@ With prefix arg UNCOMPILED, load the uncompiled versions." (>= (match-end 0) pos) start)))) -(defun org-in-commented-line () - "Is point in a line starting with `#'?" - (equal (char-after (point-at-bol)) ?#)) - -(defun org-in-indented-comment-line () - "Is point in a line starting with `#' after some white space?" - (save-excursion - (save-match-data - (goto-char (point-at-bol)) - (looking-at "[ \t]*#")))) - (defun org-in-verbatim-emphasis () (save-match-data - (and (org-in-regexp org-emph-re 2) + (and (org-in-regexp org-verbatim-re 2) (>= (point) (match-beginning 3)) - (<= (point) (match-end 4)) - (member (match-string 3) '("=" "~"))))) + (<= (point) (match-end 4))))) + +(defun org-overlay-display (ovl text &optional face evap) + "Make overlay OVL display TEXT with face FACE." + (overlay-put ovl 'display text) + (if face (overlay-put ovl 'face face)) + (if evap (overlay-put ovl 'evaporate t))) + +(defun org-overlay-before-string (ovl text &optional face evap) + "Make overlay OVL display TEXT with face FACE." + (if face (org-add-props text nil 'face face)) + (overlay-put ovl 'before-string text) + (if evap (overlay-put ovl 'evaporate t))) + +(defun org-find-overlays (prop &optional pos delete) + "Find all overlays specifying PROP at POS or point. +If DELETE is non-nil, delete all those overlays." + (let (found) + (dolist (ov (overlays-at (or pos (point))) found) + (cond ((not (overlay-get ov prop))) + (delete (delete-overlay ov)) + (t (push ov found)))))) (defun org-goto-marker-or-bmk (marker &optional bookmark) "Go to MARKER, widen if necessary. When marker is not live, try BOOKMARK." (if (and marker (marker-buffer marker) (buffer-live-p (marker-buffer marker))) (progn - (org-pop-to-buffer-same-window (marker-buffer marker)) - (if (or (> marker (point-max)) (< marker (point-min))) - (widen)) + (pop-to-buffer-same-window (marker-buffer marker)) + (when (or (> marker (point-max)) (< marker (point-min))) + (widen)) (goto-char marker) (org-show-context 'org-goto)) (if bookmark @@ -21365,32 +21762,8 @@ With prefix arg UNCOMPILED, load the uncompiled versions." (interactive "p") (self-insert-command N)) -(defun org-string-width (s) - "Compute width of string, ignoring invisible characters. -This ignores character with invisibility property `org-link', and also -characters with property `org-cwidth', because these will become invisible -upon the next fontification round." - (let (b l) - (when (or (eq t buffer-invisibility-spec) - (assq 'org-link buffer-invisibility-spec)) - (while (setq b (text-property-any 0 (length s) - 'invisible 'org-link s)) - (setq s (concat (substring s 0 b) - (substring s (or (next-single-property-change - b 'invisible s) - (length s))))))) - (while (setq b (text-property-any 0 (length s) 'org-cwidth t s)) - (setq s (concat (substring s 0 b) - (substring s (or (next-single-property-change - b 'org-cwidth s) - (length s)))))) - (setq l (string-width s) b -1) - (while (setq b (text-property-any (1+ b) (length s) 'org-dwidth t s)) - (setq l (- l (get-text-property b 'org-dwidth-n s)))) - l)) - (defun org-shorten-string (s maxlength) - "Shorten string S so tht it is no longer than MAXLENGTH characters. + "Shorten string S so that it is no longer than MAXLENGTH characters. If the string is shorter or has length MAXLENGTH, just return the original string. If it is longer, the functions finds a space in the string, breaks this string off at that locations and adds three dots @@ -21410,8 +21783,8 @@ if necessary." "Get the indentation of the current line, interpreting tabs. When LINE is given, assume it represents a line and compute its indentation." (if line - (if (string-match "^ *" (org-remove-tabs line)) - (match-end 0)) + (when (string-match "^ *" (org-remove-tabs line)) + (match-end 0)) (save-excursion (beginning-of-line 1) (skip-chars-forward " \t") @@ -21448,35 +21821,45 @@ leave it alone. If it is larger than ind, set it to the target." (let* ((l (org-remove-tabs line)) (i (org-get-indentation l)) (i1 (car ind)) (i2 (cdr ind))) - (if (>= i i2) (setq l (substring line i2))) + (when (>= i i2) (setq l (substring line i2))) (if (> i1 0) (concat (make-string i1 ?\ ) l) l))) (defun org-remove-indentation (code &optional n) - "Remove the maximum common indentation from the lines in CODE. -N may optionally be the number of spaces to remove." + "Remove maximum common indentation in string CODE and return it. +N may optionally be the number of columns to remove. Return CODE +as-is if removal failed." (with-temp-buffer (insert code) - (org-do-remove-indentation n) - (buffer-string))) + (if (org-do-remove-indentation n) (buffer-string) code))) (defun org-do-remove-indentation (&optional n) - "Remove the maximum common indentation from the buffer." - (untabify (point-min) (point-max)) - (let ((min 10000) re) - (if n - (setq min n) - (goto-char (point-min)) - (while (re-search-forward "^ *[^ \n]" nil t) - (setq min (min min (1- (- (match-end 0) (match-beginning 0))))))) - (unless (or (= min 0) (= min 10000)) - (setq re (format "^ \\{%d\\}" min)) - (goto-char (point-min)) - (while (re-search-forward re nil t) - (replace-match "") - (end-of-line 1)) - min))) + "Remove the maximum common indentation from the buffer. +When optional argument N is a positive integer, remove exactly +that much characters from indentation, if possible. Return nil +if it fails." + (catch :exit + (goto-char (point-min)) + ;; Find maximum common indentation, if not specified. + (let ((n (or n + (let ((min-ind (point-max))) + (save-excursion + (while (re-search-forward "^[ \t]*\\S-" nil t) + (let ((ind (1- (current-column)))) + (if (zerop ind) (throw :exit nil) + (setq min-ind (min min-ind ind)))))) + min-ind)))) + (if (zerop n) (throw :exit nil) + ;; Remove exactly N indentation, but give up if not possible. + (while (not (eobp)) + (let ((ind (progn (skip-chars-forward " \t") (current-column)))) + (cond ((eolp) (delete-region (line-beginning-position) (point))) + ((< ind n) (throw :exit nil)) + (t (indent-line-to (- ind n)))) + (forward-line))) + ;; Signal success. + t)))) (defun org-fill-template (template alist) "Find each %key of ALIST in TEMPLATE and replace it." @@ -21496,12 +21879,6 @@ N may optionally be the number of spaces to remove." (or (buffer-base-buffer buffer) buffer))) -(defun org-trim (s) - "Remove whitespace at beginning and end of string." - (if (string-match "\\`[ \t\n\r]+" s) (setq s (replace-match "" t t s))) - (if (string-match "[ \t\n\r]+\\'" s) (setq s (replace-match "" t t s))) - s) - (defun org-wrap (string &optional width lines) "Wrap string to either a number of lines, or a width in characters. If WIDTH is non-nil, the string is wrapped to that width, however many lines @@ -21510,7 +21887,7 @@ wrapped to the length of that word. IF WIDTH is nil and LINES is non-nil, the string is forced into at most that many lines, whatever width that takes. The return value is a list of lines, without newlines at the end." - (let* ((words (org-split-string string "[ \t\n]+")) + (let* ((words (split-string string)) (maxword (apply 'max (mapcar 'org-string-width words))) w ll) (cond (width @@ -21537,34 +21914,6 @@ The return value is a list of lines, without newlines at the end." (setq lines (push line lines))) (nreverse lines))) -(defun org-split-string (string &optional separators) - "Splits STRING into substrings at SEPARATORS. -No empty strings are returned if there are matches at the beginning -and end of string." - (let ((rexp (or separators "[ \f\t\n\r\v]+")) - (start 0) - notfirst - (list nil)) - (while (and (string-match rexp string - (if (and notfirst - (= start (match-beginning 0)) - (< start (length string))) - (1+ start) start)) - (< (match-beginning 0) (length string))) - (setq notfirst t) - (or (eq (match-beginning 0) 0) - (and (eq (match-beginning 0) (match-end 0)) - (eq (match-beginning 0) start)) - (setq list - (cons (substring string start (match-beginning 0)) - list))) - (setq start (match-end 0))) - (or (eq start (length string)) - (setq list - (cons (substring string start) - list))) - (nreverse list))) - (defun org-quote-vert (s) "Replace \"|\" with \"\\vert\"." (while (string-match "|" s) @@ -21579,10 +21928,8 @@ and end of string." "Whether point is in a code source block. When INSIDE is non-nil, don't consider we are within a src block when point is at #+BEGIN_SRC or #+END_SRC." - (let ((case-fold-search t) ov) - (or (and (setq ov (overlays-at (point))) - (memq 'org-block-background - (overlay-properties (car ov)))) + (let ((case-fold-search t)) + (or (and (eq (get-char-property (point) 'src-block) t)) (and (not inside) (save-match-data (save-excursion @@ -21604,13 +21951,13 @@ contexts are: :item on the first line of a plain list item :item-bullet on the bullet/number of a plain list item :checkbox on the checkbox in a plain list item -:table in an org-mode table +:table in an Org table :table-special on a special filed in a table :table-table in a table.el table :clocktable in a clocktable :src-block in a source block :link on a hyperlink -:keyword on a keyword: SCHEDULED, DEADLINE, CLOSE, COMMENT, QUOTE. +:keyword on a keyword: SCHEDULED, DEADLINE, CLOSE, COMMENT. :target on a <<target>> :radio-target on a <<<radio-target>>> :latex-fragment on a LaTeX fragment @@ -21635,8 +21982,8 @@ and :keyword." (push (org-point-in-group p 4 :tags) clist)) (goto-char p) (skip-chars-backward "^[\n\r \t") (or (bobp) (backward-char 1)) - (if (looking-at "\\[#[A-Z0-9]\\]") - (push (org-point-in-group p 0 :priority) clist))) + (when (looking-at "\\[#[A-Z0-9]\\]") + (push (org-point-in-group p 0 :priority) clist))) ((org-at-item-p) (push (org-point-in-group p 2 :item-bullet) clist) @@ -21648,10 +21995,10 @@ and :keyword." ((org-at-table-p) (push (list :table (org-table-begin) (org-table-end)) clist) - (if (memq 'org-formula faces) - (push (list :table-special - (previous-single-property-change p 'face) - (next-single-property-change p 'face)) clist))) + (when (memq 'org-formula faces) + (push (list :table-special + (previous-single-property-change p 'face) + (next-single-property-change p 'face)) clist))) ((org-at-table-p 'any) (push (list :table-table) clist))) (goto-char p) @@ -21660,16 +22007,16 @@ and :keyword." ;; New the "medium" contexts: clocktables, source blocks (cond ((org-in-clocktable-p) (push (list :clocktable - (and (or (looking-at "#\\+BEGIN: clocktable") - (search-backward "#+BEGIN: clocktable" nil t)) - (match-beginning 0)) - (and (re-search-forward "#\\+END:?" nil t) + (and (or (looking-at "[ \t]*\\(#\\+BEGIN: clocktable\\)") + (re-search-backward "[ \t]*\\(#+BEGIN: clocktable\\)" nil t)) + (match-beginning 1)) + (and (re-search-forward "[ \t]*#\\+END:?" nil t) (match-end 0))) clist)) ((org-in-src-block-p) (push (list :src-block - (and (or (looking-at "#\\+BEGIN_SRC") - (search-backward "#+BEGIN_SRC" nil t)) - (match-beginning 0)) + (and (or (looking-at "[ \t]*\\(#\\+BEGIN_SRC\\)") + (re-search-backward "[ \t]*\\(#+BEGIN_SRC\\)" nil t)) + (match-beginning 1)) (and (search-forward "#+END_SRC" nil t) (match-beginning 0))) clist)))) (goto-char p) @@ -21689,14 +22036,14 @@ and :keyword." ((org-at-target-p) (push (org-point-in-group p 0 :target) clist) (goto-char (1- (match-beginning 0))) - (if (looking-at org-radio-target-regexp) - (push (org-point-in-group p 0 :radio-target) clist)) + (when (looking-at org-radio-target-regexp) + (push (org-point-in-group p 0 :radio-target) clist)) (goto-char p)) - ((setq o (car (delq nil - (mapcar - (lambda (x) - (if (memq x org-latex-fragment-image-overlays) x)) - (overlays-at (point)))))) + ((setq o (cl-some + (lambda (o) + (and (eq (overlay-get o 'org-overlay-type) 'org-latex-overlay) + o)) + (overlays-at (point)))) (push (list :latex-fragment (overlay-start o) (overlay-end o)) clist) (push (list :latex-preview @@ -21708,35 +22055,27 @@ and :keyword." (setq clist (nreverse (delq nil clist))) clist)) -;; FIXME: Compare with at-regexp-p Do we need both? -(defun org-in-regexp (re &optional nlines visually) - "Check if point is inside a match of regexp. -Normally only the current line is checked, but you can include NLINES extra -lines both before and after point into the search. -If VISUALLY is set, require that the cursor is not after the match but -really on, so that the block visually is on the match." - (catch 'exit +(defun org-in-regexp (regexp &optional nlines visually) + "Check if point is inside a match of REGEXP. + +Normally only the current line is checked, but you can include +NLINES extra lines around point into the search. If VISUALLY is +set, require that the cursor is not after the match but really +on, so that the block visually is on the match. + +Return nil or a cons cell (BEG . END) where BEG and END are, +respectively, the positions at the beginning and the end of the +match." + (catch :exit (let ((pos (point)) - (eol (point-at-eol (+ 1 (or nlines 0)))) - (inc (if visually 1 0))) + (eol (line-end-position (if nlines (1+ nlines) 1)))) (save-excursion (beginning-of-line (- 1 (or nlines 0))) - (while (re-search-forward re eol t) - (if (and (<= (match-beginning 0) pos) - (>= (+ inc (match-end 0)) pos)) - (throw 'exit (cons (match-beginning 0) (match-end 0))))))))) - -(defun org-at-regexp-p (regexp) - "Is point inside a match of REGEXP in the current line?" - (catch 'exit - (save-excursion - (let ((pos (point)) (end (point-at-eol))) - (beginning-of-line 1) - (while (re-search-forward regexp end t) - (if (and (<= (match-beginning 0) pos) - (>= (match-end 0) pos)) - (throw 'exit t))) - nil)))) + (while (and (re-search-forward regexp eol t) + (<= (match-beginning 0) pos)) + (let ((end (match-end 0))) + (when (or (> end pos) (and (= end pos) (not visually))) + (throw :exit (cons (match-beginning 0) (match-end 0)))))))))) (defun org-between-regexps-p (start-re end-re &optional lim-up lim-down) "Non-nil when point is between matches of START-RE and END-RE. @@ -21757,7 +22096,7 @@ position before START-RE (resp. after END-RE)." (save-excursion ;; Point is on a block when on START-RE or if START-RE can be ;; found before it... - (and (or (org-at-regexp-p start-re) + (and (or (org-in-regexp start-re) (re-search-backward start-re limit-up t)) (setq beg (match-beginning 0)) ;; ... and END-RE after it... @@ -21783,27 +22122,15 @@ block from point." (let ((case-fold-search t) (lim-up (save-excursion (outline-previous-heading))) (lim-down (save-excursion (outline-next-heading)))) - (mapc (lambda (name) - (let ((n (regexp-quote name))) - (when (org-between-regexps-p - (concat "^[ \t]*#\\+begin_" n) - (concat "^[ \t]*#\\+end_" n) - lim-up lim-down) - (throw 'exit n)))) - names)) + (dolist (name names) + (let ((n (regexp-quote name))) + (when (org-between-regexps-p + (concat "^[ \t]*#\\+begin_" n) + (concat "^[ \t]*#\\+end_" n) + lim-up lim-down) + (throw 'exit n))))) nil))) -(defun org-in-drawer-p () - "Is point within a drawer?" - (save-match-data - (let ((case-fold-search t) - (lim-up (save-excursion (outline-previous-heading))) - (lim-down (save-excursion (outline-next-heading)))) - (org-between-regexps-p - (concat "^[ \t]*:" (regexp-opt org-drawers) ":") - "^[ \t]*:end:.*$" - lim-up lim-down)))) - (defun org-occur-in-agenda-files (regexp &optional _nlines) "Call `multi-occur' with buffers for all agenda files." (interactive "sOrg-files matching: ") @@ -21815,40 +22142,21 @@ block from point." (setq files (org-add-archive-files files))) (dolist (f extra) (unless (member (file-truename f) tnames) - (unless (member f files) (setq files (append files (list f)))) - (setq tnames (append tnames (list (file-truename f)))))) + (unless (member f files) (setq files (append files (list f)))) + (setq tnames (append tnames (list (file-truename f)))))) (multi-occur (mapcar (lambda (x) (with-current-buffer - ;; FIXME: Why not just (find-file-noselect x)? - ;; Is it to avoid the "revert buffer" prompt? + ;; FIXME: Why not just (find-file-noselect x)? + ;; Is it to avoid the "revert buffer" prompt? (or (get-file-buffer x) (find-file-noselect x)) (widen) (current-buffer))) files) regexp))) -(if (boundp 'occur-mode-find-occurrence-hook) - ;; Emacs 23 - (add-hook 'occur-mode-find-occurrence-hook - (lambda () - (when (derived-mode-p 'org-mode) - (org-reveal)))) - ;; Emacs 22 - (defadvice occur-mode-goto-occurrence - (after org-occur-reveal activate) - (and (derived-mode-p 'org-mode) (org-reveal))) - (defadvice occur-mode-goto-occurrence-other-window - (after org-occur-reveal activate) - (and (derived-mode-p 'org-mode) (org-reveal))) - (defadvice occur-mode-display-occurrence - (after org-occur-reveal activate) - (when (derived-mode-p 'org-mode) - (let ((pos (occur-mode-find-occurrence))) - (with-current-buffer (marker-buffer pos) - (save-excursion - (goto-char pos) - (org-reveal))))))) +(add-hook 'occur-mode-find-occurrence-hook + (lambda () (when (derived-mode-p 'org-mode) (org-reveal)))) (defun org-occur-link-in-agenda-files () "Create a link and search for it in the agendas. @@ -21878,81 +22186,27 @@ merge (a 1) and (a 3) into (a 1 3). The function returns the new ALIST." (let (rtn) - (mapc - (lambda (e) - (let (n) - (if (not (assoc (car e) rtn)) - (push e rtn) - (setq n (cons (car e) (append (cdr (assoc (car e) rtn)) (cdr e)))) - (setq rtn (assq-delete-all (car e) rtn)) - (push n rtn)))) - alist) - rtn)) + (dolist (e alist rtn) + (let (n) + (if (not (assoc (car e) rtn)) + (push e rtn) + (setq n (cons (car e) (append (cdr (assoc (car e) rtn)) (cdr e)))) + (setq rtn (assq-delete-all (car e) rtn)) + (push n rtn)))))) (defun org-delete-all (elts list) - "Remove all elements in ELTS from LIST." + "Remove all elements in ELTS from LIST. +Comparison is done with `equal'. It is a destructive operation +that may remove elements by altering the list structure." (while elts (setq list (delete (pop elts) list))) list) -(defun org-count (cl-item cl-seq) - "Count the number of occurrences of ITEM in SEQ. -Taken from `count' in cl-seq.el with all keyword arguments removed." - (let ((cl-end (length cl-seq)) (cl-start 0) (cl-count 0) cl-x) - (when (consp cl-seq) (setq cl-seq (nthcdr cl-start cl-seq))) - (while (< cl-start cl-end) - (setq cl-x (if (consp cl-seq) (pop cl-seq) (aref cl-seq cl-start))) - (if (equal cl-item cl-x) (setq cl-count (1+ cl-count))) - (setq cl-start (1+ cl-start))) - cl-count)) - -(defun org-remove-if (predicate seq) - "Remove everything from SEQ that fulfills PREDICATE." - (let (res e) - (while seq - (setq e (pop seq)) - (if (not (funcall predicate e)) (push e res))) - (nreverse res))) - -(defun org-remove-if-not (predicate seq) - "Remove everything from SEQ that does not fulfill PREDICATE." - (let (res e) - (while seq - (setq e (pop seq)) - (if (funcall predicate e) (push e res))) - (nreverse res))) - -(defun org-reduce (cl-func cl-seq &rest cl-keys) - "Reduce two-argument FUNCTION across SEQ. -Taken from `reduce' in cl-seq.el with all keyword arguments but -\":initial-value\" removed." - (let ((cl-accum (cond ((memq :initial-value cl-keys) - (cadr (memq :initial-value cl-keys))) - (cl-seq (pop cl-seq)) - (t (funcall cl-func))))) - (while cl-seq - (setq cl-accum (funcall cl-func cl-accum (pop cl-seq)))) - cl-accum)) - -(defun org-every (pred seq) - "Return true if PREDICATE is true of every element of SEQ. -Adapted from `every' in cl.el." - (catch 'org-every - (mapc (lambda (e) (unless (funcall pred e) (throw 'org-every nil))) seq) - t)) - -(defun org-some (pred seq) - "Return true if PREDICATE is true of any element of SEQ. -Adapted from `some' in cl.el." - (catch 'org-some - (mapc (lambda (e) (when (funcall pred e) (throw 'org-some t))) seq) - nil)) - (defun org-back-over-empty-lines () "Move backwards over whitespace, to the beginning of the first empty line. Returns the number of empty lines passed." (let ((pos (point))) - (if (cdr (assoc 'heading org-blank-before-new-entry)) + (if (cdr (assq 'heading org-blank-before-new-entry)) (skip-chars-backward " \t\n\r") (unless (eobp) (forward-line -1))) @@ -22005,7 +22259,7 @@ so values can contain further %-escapes if they are define later in TABLE." (let ((tbl (copy-alist table)) (case-fold-search nil) (pchg 0) - e re rpl) + re rpl) (dolist (e tbl) (setq re (concat "%-?[0-9.]*" (substring (car e) 1))) (when (and (cdr e) (string-match re (cdr e))) @@ -22023,16 +22277,6 @@ so values can contain further %-escapes if they are define later in TABLE." (setq string (replace-match sref t t string))))) string)) -(defun org-sublist (list start end) - "Return a section of LIST, from START to END. -Counting starts at 1." - (let (rtn (c start)) - (setq list (nthcdr (1- start) list)) - (while (and list (<= c end)) - (push (pop list) rtn) - (setq c (1+ c))) - (nreverse rtn))) - (defun org-find-base-buffer-visiting (file) "Like `find-buffer-visiting' but always return the base buffer and not an indirect buffer." @@ -22042,26 +22286,12 @@ not an indirect buffer." (or (buffer-base-buffer buf) buf) nil))) -(defun org-image-file-name-regexp (&optional extensions) - "Return regexp matching the file names of images. -If EXTENSIONS is given, only match these." - (if (and (not extensions) (fboundp 'image-file-name-regexp)) - (image-file-name-regexp) - (let ((image-file-name-extensions - (or extensions - '("png" "jpeg" "jpg" "gif" "tiff" "tif" - "xbm" "xpm" "pbm" "pgm" "ppm")))) - (concat "\\." - (regexp-opt (nconc (mapcar 'upcase - image-file-name-extensions) - image-file-name-extensions) - t) - "\\'")))) - -(defun org-file-image-p (file &optional extensions) +;;; TODO: Only called once, from ox-odt which should probably use +;;; org-export-inline-image-p or something. +(defun org-file-image-p (file) "Return non-nil if FILE is an image." (save-match-data - (string-match (org-image-file-name-regexp extensions) file))) + (string-match (image-file-name-regexp) file))) (defun org-get-cursor-date (&optional with-time) "Return the date at cursor in as a time. @@ -22085,10 +22315,10 @@ the agenda) or the current time of the day." (nth 1 date) (nth 0 date) (nth 2 date)))) ((eq major-mode 'org-agenda-mode) (setq day (get-text-property (point) 'day)) - (if day - (setq date (calendar-gregorian-from-absolute day) - defd (encode-time 0 (or mod 0) (or hod 0) - (nth 1 date) (nth 0 date) (nth 2 date)))))) + (when day + (setq date (calendar-gregorian-from-absolute day) + defd (encode-time 0 (or mod 0) (or hod 0) + (nth 1 date) (nth 0 date) (nth 2 date)))))) (or defd (current-time)))) (defun org-mark-subtree (&optional up) @@ -22101,177 +22331,441 @@ hierarchy of headlines by UP levels before marking the subtree." (cond ((org-at-heading-p) (beginning-of-line)) ((org-before-first-heading-p) (user-error "Not in a subtree")) (t (outline-previous-visible-heading 1)))) - (when up (while (and (> up 0) (org-up-heading-safe)) (decf up))) - (if (org-called-interactively-p 'any) + (when up (while (and (> up 0) (org-up-heading-safe)) (cl-decf up))) + (if (called-interactively-p 'any) (call-interactively 'org-mark-element) (org-mark-element))) +(defun org-file-newer-than-p (file time) + "Non-nil if FILE is newer than TIME. +FILE is a filename, as a string, TIME is a list of integers, as +returned by, e.g., `current-time'." + (and (file-exists-p file) + ;; Only compare times up to whole seconds as some file-systems + ;; (e.g. HFS+) do not retain any finer granularity. As + ;; a consequence, make sure we return non-nil when the two + ;; times are equal. + (not (time-less-p (cl-subseq (nth 5 (file-attributes file)) 0 2) + (cl-subseq time 0 2))))) + +(defun org-compile-file (source process ext &optional err-msg log-buf spec) + "Compile a SOURCE file using PROCESS. + +PROCESS is either a function or a list of shell commands, as +strings. EXT is a file extension, without the leading dot, as +a string. It is used to check if the process actually succeeded. + +PROCESS must create a file with the same base name and directory +as SOURCE, but ending with EXT. The function then returns its +filename. Otherwise, it raises an error. The error message can +then be refined by providing string ERR-MSG, which is appended to +the standard message. + +If PROCESS is a function, it is called with a single argument: +the SOURCE file. + +If it is a list of commands, each of them is called using +`shell-command'. By default, in each command, %b, %f, %F, %o and +%O are replaced with, respectively, SOURCE base name, name, full +name, directory and absolute output file name. It is possible, +however, to use more place-holders by specifying them in optional +argument SPEC, as an alist following the pattern + + (CHARACTER . REPLACEMENT-STRING). + +When PROCESS is a list of commands, optional argument LOG-BUF can +be set to a buffer or a buffer name. `shell-command' then uses +it for output." + (let* ((base-name (file-name-base source)) + (full-name (file-truename source)) + (out-dir (or (file-name-directory source) "./")) + (output (expand-file-name (concat base-name "." ext) out-dir)) + (time (current-time)) + (err-msg (if (stringp err-msg) (concat ". " err-msg) ""))) + (save-window-excursion + (pcase process + ((pred functionp) (funcall process (shell-quote-argument source))) + ((pred consp) + (let ((log-buf (and log-buf (get-buffer-create log-buf))) + (spec (append spec + `((?b . ,(shell-quote-argument base-name)) + (?f . ,(shell-quote-argument source)) + (?F . ,(shell-quote-argument full-name)) + (?o . ,(shell-quote-argument out-dir)) + (?O . ,(shell-quote-argument output)))))) + (dolist (command process) + (shell-command (format-spec command spec) log-buf)) + (when log-buf (with-current-buffer log-buf (compilation-mode))))) + (_ (error "No valid command to process %S%s" source err-msg)))) + ;; Check for process failure. Output file is expected to be + ;; located in the same directory as SOURCE. + (unless (org-file-newer-than-p output time) + (error (format "File %S wasn't produced%s" output err-msg))) + output)) ;;; Indentation +(defvar org-element-greater-elements) +(defun org--get-expected-indentation (element contentsp) + "Expected indentation column for current line, according to ELEMENT. +ELEMENT is an element containing point. CONTENTSP is non-nil +when indentation is to be computed according to contents of +ELEMENT." + (let ((type (org-element-type element)) + (start (org-element-property :begin element)) + (post-affiliated (org-element-property :post-affiliated element))) + (org-with-wide-buffer + (cond + (contentsp + (cl-case type + ((diary-sexp footnote-definition) 0) + ((headline inlinetask nil) + (if (not org-adapt-indentation) 0 + (let ((level (org-current-level))) + (if level (1+ level) 0)))) + ((item plain-list) (org-list-item-body-column post-affiliated)) + (t + (goto-char start) + (org-get-indentation)))) + ((memq type '(headline inlinetask nil)) + (if (org-match-line "[ \t]*$") + (org--get-expected-indentation element t) + 0)) + ((memq type '(diary-sexp footnote-definition)) 0) + ;; First paragraph of a footnote definition or an item. + ;; Indent like parent. + ((< (line-beginning-position) start) + (org--get-expected-indentation + (org-element-property :parent element) t)) + ;; At first line: indent according to previous sibling, if any, + ;; ignoring footnote definitions and inline tasks, or parent's + ;; contents. + ((= (line-beginning-position) start) + (catch 'exit + (while t + (if (= (point-min) start) (throw 'exit 0) + (goto-char (1- start)) + (let* ((previous (org-element-at-point)) + (parent previous)) + (while (and parent (<= (org-element-property :end parent) start)) + (setq previous parent + parent (org-element-property :parent parent))) + (cond + ((not previous) (throw 'exit 0)) + ((> (org-element-property :end previous) start) + (throw 'exit (org--get-expected-indentation previous t))) + ((memq (org-element-type previous) + '(footnote-definition inlinetask)) + (setq start (org-element-property :begin previous))) + (t (goto-char (org-element-property :begin previous)) + (throw 'exit + (if (bolp) (org-get-indentation) + ;; At first paragraph in an item or + ;; a footnote definition. + (org--get-expected-indentation + (org-element-property :parent previous) t)))))))))) + ;; Otherwise, move to the first non-blank line above. + (t + (beginning-of-line) + (let ((pos (point))) + (skip-chars-backward " \r\t\n") + (cond + ;; Two blank lines end a footnote definition or a plain + ;; list. When we indent an empty line after them, the + ;; containing list or footnote definition is over, so it + ;; qualifies as a previous sibling. Therefore, we indent + ;; like its first line. + ((and (memq type '(footnote-definition plain-list)) + (> (count-lines (point) pos) 2)) + (goto-char start) + (org-get-indentation)) + ;; Line above is the first one of a paragraph at the + ;; beginning of an item or a footnote definition. Indent + ;; like parent. + ((< (line-beginning-position) start) + (org--get-expected-indentation + (org-element-property :parent element) t)) + ;; Line above is the beginning of an element, i.e., point + ;; was originally on the blank lines between element's start + ;; and contents. + ((= (line-beginning-position) post-affiliated) + (org--get-expected-indentation element t)) + ;; POS is after contents in a greater element. Indent like + ;; the beginning of the element. + ((and (memq type org-element-greater-elements) + (let ((cend (org-element-property :contents-end element))) + (and cend (<= cend pos)))) + ;; As a special case, if point is at the end of a footnote + ;; definition or an item, indent like the very last element + ;; within. If that last element is an item, indent like + ;; its contents. + (if (memq type '(footnote-definition item plain-list)) + (let ((last (org-element-at-point))) + (goto-char pos) + (org--get-expected-indentation + last (eq (org-element-type last) 'item))) + (goto-char start) + (org-get-indentation))) + ;; In any other case, indent like the current line. + (t (org-get-indentation))))))))) + +(defun org--align-node-property () + "Align node property at point. +Alignment is done according to `org-property-format', which see." + (when (save-excursion + (beginning-of-line) + (looking-at org-property-re)) + (replace-match + (concat (match-string 4) + (org-trim + (format org-property-format (match-string 1) (match-string 3)))) + t t))) + (defun org-indent-line () - "Indent line depending on context." + "Indent line depending on context. + +Indentation is done according to the following rules: + + - Footnote definitions, diary sexps, headlines and inline tasks + have to start at column 0. + + - On the very first line of an element, consider, in order, the + next rules until one matches: + + 1. If there's a sibling element before, ignoring footnote + definitions and inline tasks, indent like its first line. + + 2. If element has a parent, indent like its contents. More + precisely, if parent is an item, indent after the + description part, if any, or the bullet (see + `org-list-description-max-indent'). Else, indent like + parent's first line. + + 3. Otherwise, indent relatively to current level, if + `org-adapt-indentation' is non-nil, or to left margin. + + - On a blank line at the end of an element, indent according to + the type of the element. More precisely + + 1. If element is a plain list, an item, or a footnote + definition, indent like the very last element within. + + 2. If element is a paragraph, indent like its last non blank + line. + + 3. Otherwise, indent like its very first line. + + - In the code part of a source block, use language major mode + to indent current line if `org-src-tab-acts-natively' is + non-nil. If it is nil, do nothing. + + - Otherwise, indent like the first non-blank line above. + +The function doesn't indent an item as it could break the whole +list structure. Instead, use \\<org-mode-map>`\\[org-shiftmetaleft]' or \ +`\\[org-shiftmetaright]'. + +Also align node properties according to `org-property-format'." (interactive) - (let* ((pos (point)) - (itemp (org-at-item-p)) - (case-fold-search t) - (org-drawer-regexp (or org-drawer-regexp "\000")) - (inline-task-p (and (featurep 'org-inlinetask) - (org-inlinetask-in-task-p))) - (inline-re (and inline-task-p - (org-inlinetask-outline-regexp))) - column) - (if (and orgstruct-is-++ (eq pos (point))) - (let ((indent-line-function (cadadr (assoc 'indent-line-function org-fb-vars)))) - (indent-according-to-mode)) - (beginning-of-line 1) - (cond - ;; Headings - ((looking-at org-outline-regexp) (setq column 0)) - ;; Footnote definition - ((looking-at org-footnote-definition-re) (setq column 0)) - ;; Literal examples - ((looking-at "[ \t]*:\\( \\|$\\)") - (setq column (org-get-indentation))) ; do nothing - ;; Lists - ((ignore-errors (goto-char (org-in-item-p))) - (setq column (if itemp - (org-get-indentation) - (org-list-item-body-column (point)))) - (goto-char pos)) - ;; Drawers - ((and (looking-at "[ \t]*:END:") - (save-excursion (re-search-backward org-drawer-regexp nil t))) - (save-excursion - (goto-char (1- (match-beginning 1))) - (setq column (current-column)))) - ;; Special blocks - ((and (looking-at "[ \t]*#\\+end_\\([a-z]+\\)") - (save-excursion - (re-search-backward - (concat "^[ \t]*#\\+begin_" (downcase (match-string 1))) nil t))) - (setq column (org-get-indentation (match-string 0)))) - ((and (not (looking-at "[ \t]*#\\+begin_")) - (org-between-regexps-p "^[ \t]*#\\+begin_" "[ \t]*#\\+end_")) - (save-excursion - (re-search-backward "^[ \t]*#\\+begin_\\([a-z]+\\)" nil t)) - (setq column - (cond ((equal (downcase (match-string 1)) "src") - ;; src blocks: let `org-edit-src-exit' handle them - (org-get-indentation)) - ((equal (downcase (match-string 1)) "example") - (max (org-get-indentation) - (org-get-indentation (match-string 0)))) - (t - (org-get-indentation (match-string 0)))))) - ;; This line has nothing special, look at the previous relevant - ;; line to compute indentation - (t - (beginning-of-line 0) - (while (and (not (bobp)) - (not (looking-at org-table-line-regexp)) - (not (looking-at org-drawer-regexp)) - ;; When point started in an inline task, do not move - ;; above task starting line. - (not (and inline-task-p (looking-at inline-re))) - ;; Skip drawers, blocks, empty lines, verbatim, - ;; comments, tables, footnotes definitions, lists, - ;; inline tasks. - (or (and (looking-at "[ \t]*:END:") - (re-search-backward org-drawer-regexp nil t)) - (and (looking-at "[ \t]*#\\+end_") - (re-search-backward "[ \t]*#\\+begin_"nil t)) - (looking-at "[ \t]*[\n:#|]") - (looking-at org-footnote-definition-re) - (and (not inline-task-p) - (featurep 'org-inlinetask) - (org-inlinetask-in-task-p) - (or (org-inlinetask-goto-beginning) t)))) - (beginning-of-line 0)) - (cond - ;; There was a list item above. - ((ignore-errors (goto-char (org-in-item-p))) - (goto-char (org-list-get-top-point (org-list-struct))) - (setq column (org-get-indentation))) - ;; There was an heading above. - ((looking-at "\\*+[ \t]+") - (if (not org-adapt-indentation) - (setq column 0) - (goto-char (match-end 0)) - (setq column (current-column)))) - ;; A drawer had started and is unfinished - ((looking-at org-drawer-regexp) - (goto-char (1- (match-beginning 1))) - (setq column (current-column))) - ;; Else, nothing noticeable found: get indentation and go on. - (t (setq column (org-get-indentation)))))) - ;; Now apply indentation and move cursor accordingly - (goto-char pos) - (if (<= (current-column) (current-indentation)) - (org-indent-line-to column) - (save-excursion (org-indent-line-to column))) - ;; Special polishing for properties, see `org-property-format' - (setq column (current-column)) - (beginning-of-line 1) - (if (looking-at org-property-re) - (replace-match (concat (match-string 4) - (format org-property-format - (match-string 1) (match-string 3))) - t t)) - (org-move-to-column column)))) + (cond + (orgstruct-is-++ + (let ((indent-line-function + (cl-cadadr (assq 'indent-line-function org-fb-vars)))) + (indent-according-to-mode))) + ((org-at-heading-p) 'noindent) + (t + (let* ((element (save-excursion (beginning-of-line) (org-element-at-point))) + (type (org-element-type element))) + (cond ((and (memq type '(plain-list item)) + (= (line-beginning-position) + (org-element-property :post-affiliated element))) + 'noindent) + ((and (eq type 'latex-environment) + (>= (point) (org-element-property :post-affiliated element)) + (< (point) (org-with-wide-buffer + (goto-char (org-element-property :end element)) + (skip-chars-backward " \r\t\n") + (line-beginning-position 2)))) + 'noindent) + ((and (eq type 'src-block) + org-src-tab-acts-natively + (> (line-beginning-position) + (org-element-property :post-affiliated element)) + (< (line-beginning-position) + (org-with-wide-buffer + (goto-char (org-element-property :end element)) + (skip-chars-backward " \r\t\n") + (line-beginning-position)))) + (org-babel-do-key-sequence-in-edit-buffer (kbd "TAB"))) + (t + (let ((column (org--get-expected-indentation element nil))) + ;; Preserve current column. + (if (<= (current-column) (current-indentation)) + (indent-line-to column) + (save-excursion (indent-line-to column)))) + ;; Align node property. Also preserve current column. + (when (eq type 'node-property) + (let ((column (current-column))) + (org--align-node-property) + (org-move-to-column column))))))))) + +(defun org-indent-region (start end) + "Indent each non-blank line in the region. +Called from a program, START and END specify the region to +indent. The function will not indent contents of example blocks, +verse blocks and export blocks as leading white spaces are +assumed to be significant there." + (interactive "r") + (save-excursion + (goto-char start) + (skip-chars-forward " \r\t\n") + (unless (eobp) (beginning-of-line)) + (let ((indent-to + (lambda (ind pos) + ;; Set IND as indentation for all lines between point and + ;; POS. Blank lines are ignored. Leave point after POS + ;; once done. + (let ((limit (copy-marker pos))) + (while (< (point) limit) + (unless (looking-at-p "[ \t]*$") (indent-line-to ind)) + (forward-line)) + (set-marker limit nil)))) + (end (copy-marker end))) + (while (< (point) end) + (if (or (looking-at-p " \r\t\n") (org-at-heading-p)) (forward-line) + (let* ((element (org-element-at-point)) + (type (org-element-type element)) + (element-end (copy-marker (org-element-property :end element))) + (ind (org--get-expected-indentation element nil))) + (cond + ;; Element indented as a single block. Example blocks + ;; preserving indentation are a special case since the + ;; "contents" must not be indented whereas the block + ;; boundaries can. + ((or (memq type '(export-block latex-environment)) + (and (eq type 'example-block) + (not + (or org-src-preserve-indentation + (org-element-property :preserve-indent element))))) + (let ((offset (- ind (org-get-indentation)))) + (unless (zerop offset) + (indent-rigidly (org-element-property :begin element) + (org-element-property :end element) + offset))) + (goto-char element-end)) + ;; Elements indented line wise. Be sure to exclude + ;; example blocks (preserving indentation) and source + ;; blocks from this category as they are treated + ;; specially later. + ((or (memq type '(paragraph table table-row)) + (not (or (org-element-property :contents-begin element) + (memq type '(example-block src-block))))) + (when (eq type 'node-property) + (org--align-node-property) + (beginning-of-line)) + (funcall indent-to ind (min element-end end))) + ;; Elements consisting of three parts: before the + ;; contents, the contents, and after the contents. The + ;; contents are treated specially, according to the + ;; element type, or not indented at all. Other parts are + ;; indented as a single block. + (t + (let* ((post (copy-marker + (org-element-property :post-affiliated element))) + (cbeg + (copy-marker + (cond + ((not (org-element-property :contents-begin element)) + ;; Fake contents for source blocks. + (org-with-wide-buffer + (goto-char post) + (line-beginning-position 2))) + ((memq type '(footnote-definition item plain-list)) + ;; Contents in these elements could start on + ;; the same line as the beginning of the + ;; element. Make sure we start indenting + ;; from the second line. + (org-with-wide-buffer + (goto-char post) + (end-of-line) + (skip-chars-forward " \r\t\n") + (if (eobp) (point) (line-beginning-position)))) + (t (org-element-property :contents-begin element))))) + (cend (copy-marker + (or (org-element-property :contents-end element) + ;; Fake contents for source blocks. + (org-with-wide-buffer + (goto-char element-end) + (skip-chars-backward " \r\t\n") + (line-beginning-position))) + t))) + ;; Do not change items indentation individually as it + ;; might break the list as a whole. On the other + ;; hand, when at a plain list, indent it as a whole. + (cond ((eq type 'plain-list) + (let ((offset (- ind (org-get-indentation)))) + (unless (zerop offset) + (indent-rigidly (org-element-property :begin element) + (org-element-property :end element) + offset)) + (goto-char cbeg))) + ((eq type 'item) (goto-char cbeg)) + (t (funcall indent-to ind (min cbeg end)))) + (when (< (point) end) + (cl-case type + ((example-block verse-block)) + (src-block + ;; In a source block, indent source code + ;; according to language major mode, but only if + ;; `org-src-tab-acts-natively' is non-nil. + (when (and (< (point) end) org-src-tab-acts-natively) + (ignore-errors + (org-babel-do-in-edit-buffer + (indent-region (point-min) (point-max)))))) + (t (org-indent-region (point) (min cend end)))) + (goto-char (min cend end)) + (when (< (point) end) + (funcall indent-to ind (min element-end end)))) + (set-marker post nil) + (set-marker cbeg nil) + (set-marker cend nil)))) + (set-marker element-end nil)))) + (set-marker end nil)))) (defun org-indent-drawer () "Indent the drawer at point." (interactive) - (let ((p (point)) - (e (and (save-excursion (re-search-forward ":END:" nil t)) - (match-end 0))) - (folded - (save-excursion - (end-of-line) - (when (overlays-at (point)) - (member 'invisible (overlay-properties - (car (overlays-at (point))))))))) - (when folded (org-cycle)) - (indent-for-tab-command) - (while (and (move-beginning-of-line 2) (< (point) e)) - (indent-for-tab-command)) - (goto-char p) - (when folded (org-cycle))) + (unless (save-excursion + (beginning-of-line) + (looking-at-p org-drawer-regexp)) + (user-error "Not at a drawer")) + (let ((element (org-element-at-point))) + (unless (memq (org-element-type element) '(drawer property-drawer)) + (user-error "Not at a drawer")) + (org-with-wide-buffer + (org-indent-region (org-element-property :begin element) + (org-element-property :end element)))) (message "Drawer at point indented")) (defun org-indent-block () "Indent the block at point." (interactive) - (let ((p (point)) - (case-fold-search t) - (e (and (save-excursion (re-search-forward "#\\+end_?\\(?:[a-z]+\\)?" nil t)) - (match-end 0))) - (folded - (save-excursion - (end-of-line) - (when (overlays-at (point)) - (member 'invisible (overlay-properties - (car (overlays-at (point))))))))) - (when folded (org-cycle)) - (indent-for-tab-command) - (while (and (move-beginning-of-line 2) (< (point) e)) - (indent-for-tab-command)) - (goto-char p) - (when folded (org-cycle))) + (unless (save-excursion + (beginning-of-line) + (let ((case-fold-search t)) + (looking-at-p "[ \t]*#\\+\\(begin\\|end\\)_"))) + (user-error "Not at a block")) + (let ((element (org-element-at-point))) + (unless (memq (org-element-type element) + '(comment-block center-block dynamic-block example-block + export-block quote-block special-block + src-block verse-block)) + (user-error "Not at a block")) + (org-with-wide-buffer + (org-indent-region (org-element-property :begin element) + (org-element-property :end element)))) (message "Block at point indented")) -(defun org-indent-region (start end) - "Indent region." - (interactive "r") - (save-excursion - (let ((line-end (org-current-line end))) - (goto-char start) - (while (< (org-current-line) line-end) - (cond ((org-in-src-block-p t) (org-src-native-tab-command-maybe)) - (t (call-interactively 'org-indent-line))) - (move-beginning-of-line 2))))) - ;;; Filling @@ -22289,25 +22783,25 @@ hierarchy of headlines by UP levels before marking the subtree." ;; `org-setup-filling' installs filling and auto-filling related ;; variables during `org-mode' initialization. -(defvar org-element-paragraph-separate) ; org-element.el (defun org-setup-filling () (require 'org-element) ;; Prevent auto-fill from inserting unwanted new items. (when (boundp 'fill-nobreak-predicate) - (org-set-local - 'fill-nobreak-predicate + (setq-local + fill-nobreak-predicate (org-uniquify (append fill-nobreak-predicate '(org-fill-line-break-nobreak-p + org-fill-n-macro-as-item-nobreak-p org-fill-paragraph-with-timestamp-nobreak-p))))) (let ((paragraph-ending (substring org-element-paragraph-separate 1))) - (org-set-local 'paragraph-start paragraph-ending) - (org-set-local 'paragraph-separate paragraph-ending)) - (org-set-local 'fill-paragraph-function 'org-fill-paragraph) - (org-set-local 'auto-fill-inhibit-regexp nil) - (org-set-local 'adaptive-fill-function 'org-adaptive-fill-function) - (org-set-local 'normal-auto-fill-function 'org-auto-fill-function) - (org-set-local 'comment-line-break-function 'org-comment-line-break-function)) + (setq-local paragraph-start paragraph-ending) + (setq-local paragraph-separate paragraph-ending)) + (setq-local fill-paragraph-function 'org-fill-paragraph) + (setq-local auto-fill-inhibit-regexp nil) + (setq-local adaptive-fill-function 'org-adaptive-fill-function) + (setq-local normal-auto-fill-function 'org-auto-fill-function) + (setq-local comment-line-break-function 'org-comment-line-break-function)) (defun org-fill-line-break-nobreak-p () "Non-nil when a new line at point would create an Org line break." @@ -22318,9 +22812,15 @@ hierarchy of headlines by UP levels before marking the subtree." (defun org-fill-paragraph-with-timestamp-nobreak-p () "Non-nil when a new line at point would split a timestamp." - (and (org-at-timestamp-p t) + (and (org-at-timestamp-p 'lax) (not (looking-at org-ts-regexp-both)))) +(defun org-fill-n-macro-as-item-nobreak-p () + "Non-nil when a new line at point would create a new list." + ;; During export, a "n" macro followed by a dot or a closing + ;; parenthesis can end up being parsed as a new list item. + (looking-at-p "[ \t]*{{{n\\(?:([^\n)]*)\\)?}}}[.)]\\(?:$\\| \\)")) + (declare-function message-in-body-p "message" ()) (defvar orgtbl-line-start-regexp) ; From org-table.el (defun org-adaptive-fill-function () @@ -22332,73 +22832,69 @@ matches in paragraphs or comments, use it." (when (derived-mode-p 'message-mode) (save-excursion (beginning-of-line) - (cond ((or (not (message-in-body-p)) - (looking-at orgtbl-line-start-regexp)) - (throw 'exit nil)) + (cond ((not (message-in-body-p)) (throw 'exit nil)) + ((looking-at-p org-table-line-regexp) (throw 'exit nil)) ((looking-at message-cite-prefix-regexp) (throw 'exit (match-string-no-properties 0))) ((looking-at org-outline-regexp) - (throw 'exit (make-string (length (match-string 0)) ? )))))) + (throw 'exit (make-string (length (match-string 0)) ?\s)))))) (org-with-wide-buffer - (let* ((p (line-beginning-position)) - (element (save-excursion - (beginning-of-line) - (or (ignore-errors (org-element-at-point)) - (user-error "An element cannot be parsed line %d" - (line-number-at-pos (point)))))) - (type (org-element-type element)) - (post-affiliated (org-element-property :post-affiliated element))) - (unless (and post-affiliated (< p post-affiliated)) - (case type - (comment - (save-excursion - (beginning-of-line) - (looking-at "[ \t]*") - (concat (match-string 0) "# "))) - (footnote-definition "") - ((item plain-list) - (make-string (org-list-item-body-column - (or post-affiliated - (org-element-property :begin element))) - ? )) - (paragraph - ;; Fill prefix is usually the same as the current line, - ;; unless the paragraph is at the beginning of an item. - (let ((parent (org-element-property :parent element))) + (unless (org-at-heading-p) + (let* ((p (line-beginning-position)) + (element (save-excursion + (beginning-of-line) + (org-element-at-point))) + (type (org-element-type element)) + (post-affiliated (org-element-property :post-affiliated element))) + (unless (< p post-affiliated) + (cl-case type + (comment (save-excursion (beginning-of-line) - (cond ((eq (org-element-type parent) 'item) - (make-string (org-list-item-body-column - (org-element-property :begin parent)) - ? )) - ((and adaptive-fill-regexp - ;; Locally disable - ;; `adaptive-fill-function' to let - ;; `fill-context-prefix' handle - ;; `adaptive-fill-regexp' variable. - (let (adaptive-fill-function) - (fill-context-prefix - post-affiliated - (org-element-property :end element))))) - ((looking-at "[ \t]+") (match-string 0)) - (t ""))))) - (comment-block - ;; Only fill contents if P is within block boundaries. - (let* ((cbeg (save-excursion (goto-char post-affiliated) - (forward-line) - (point))) - (cend (save-excursion - (goto-char (org-element-property :end element)) - (skip-chars-backward " \r\t\n") - (line-beginning-position)))) - (when (and (>= p cbeg) (< p cend)) - (if (save-excursion (beginning-of-line) (looking-at "[ \t]+")) - (match-string 0) - "")))))))))) + (looking-at "[ \t]*") + (concat (match-string 0) "# "))) + (footnote-definition "") + ((item plain-list) + (make-string (org-list-item-body-column post-affiliated) ?\s)) + (paragraph + ;; Fill prefix is usually the same as the current line, + ;; unless the paragraph is at the beginning of an item. + (let ((parent (org-element-property :parent element))) + (save-excursion + (beginning-of-line) + (cond ((eq (org-element-type parent) 'item) + (make-string (org-list-item-body-column + (org-element-property :begin parent)) + ?\s)) + ((and adaptive-fill-regexp + ;; Locally disable + ;; `adaptive-fill-function' to let + ;; `fill-context-prefix' handle + ;; `adaptive-fill-regexp' variable. + (let (adaptive-fill-function) + (fill-context-prefix + post-affiliated + (org-element-property :end element))))) + ((looking-at "[ \t]+") (match-string 0)) + (t ""))))) + (comment-block + ;; Only fill contents if P is within block boundaries. + (let* ((cbeg (save-excursion (goto-char post-affiliated) + (forward-line) + (point))) + (cend (save-excursion + (goto-char (org-element-property :end element)) + (skip-chars-backward " \r\t\n") + (line-beginning-position)))) + (when (and (>= p cbeg) (< p cend)) + (if (save-excursion (beginning-of-line) (looking-at "[ \t]+")) + (match-string 0) + ""))))))))))) (declare-function message-goto-body "message" ()) (defvar message-cite-prefix-regexp) ; From message.el -(defun org-fill-paragraph (&optional justify) + +(defun org-fill-element (&optional justify) "Fill element at point, when applicable. This function only applies to comment blocks, comments, example @@ -22413,133 +22909,160 @@ width for filling. For convenience, when point is at a plain list, an item or a footnote definition, try to fill the first paragraph within." - (interactive) - (if (and (derived-mode-p 'message-mode) - (or (not (message-in-body-p)) - (save-excursion (move-beginning-of-line 1) - (looking-at message-cite-prefix-regexp)))) - ;; First ensure filling is correct in message-mode. - (let ((fill-paragraph-function - (cadadr (assoc 'fill-paragraph-function org-fb-vars))) - (fill-prefix (cadadr (assoc 'fill-prefix org-fb-vars))) - (paragraph-start (cadadr (assoc 'paragraph-start org-fb-vars))) - (paragraph-separate - (cadadr (assoc 'paragraph-separate org-fb-vars)))) - (fill-paragraph nil)) - (with-syntax-table org-mode-transpose-word-syntax-table - ;; Move to end of line in order to get the first paragraph - ;; within a plain list or a footnote definition. - (let ((element (save-excursion - (end-of-line) - (or (ignore-errors (org-element-at-point)) - (user-error "An element cannot be parsed line %d" - (line-number-at-pos (point))))))) - ;; First check if point is in a blank line at the beginning of - ;; the buffer. In that case, ignore filling. - (case (org-element-type element) - ;; Use major mode filling function is src blocks. - (src-block (org-babel-do-key-sequence-in-edit-buffer (kbd "M-q"))) - ;; Align Org tables, leave table.el tables as-is. - (table-row (org-table-align) t) - (table - (when (eq (org-element-property :type element) 'org) + (with-syntax-table org-mode-transpose-word-syntax-table + ;; Move to end of line in order to get the first paragraph within + ;; a plain list or a footnote definition. + (let ((element (save-excursion (end-of-line) (org-element-at-point)))) + ;; First check if point is in a blank line at the beginning of + ;; the buffer. In that case, ignore filling. + (cl-case (org-element-type element) + ;; Use major mode filling function is src blocks. + (src-block (org-babel-do-key-sequence-in-edit-buffer (kbd "M-q"))) + ;; Align Org tables, leave table.el tables as-is. + (table-row (org-table-align) t) + (table + (when (eq (org-element-property :type element) 'org) + (save-excursion + (goto-char (org-element-property :post-affiliated element)) + (org-table-align))) + t) + (paragraph + ;; Paragraphs may contain `line-break' type objects. + (let ((beg (max (point-min) + (org-element-property :contents-begin element))) + (end (min (point-max) + (org-element-property :contents-end element)))) + ;; Do nothing if point is at an affiliated keyword. + (if (< (line-end-position) beg) t + (when (derived-mode-p 'message-mode) + ;; In `message-mode', do not fill following citation + ;; in current paragraph nor text before message body. + (let ((body-start (save-excursion (message-goto-body)))) + (when body-start (setq beg (max body-start beg)))) + (when (save-excursion + (re-search-forward + (concat "^" message-cite-prefix-regexp) end t)) + (setq end (match-beginning 0)))) + ;; Fill paragraph, taking line breaks into account. (save-excursion - (goto-char (org-element-property :post-affiliated element)) - (org-table-align))) - t) - (paragraph - ;; Paragraphs may contain `line-break' type objects. - (let ((beg (max (point-min) - (org-element-property :contents-begin element))) - (end (min (point-max) - (org-element-property :contents-end element)))) - ;; Do nothing if point is at an affiliated keyword. - (if (< (line-end-position) beg) t - (when (derived-mode-p 'message-mode) - ;; In `message-mode', do not fill following citation - ;; in current paragraph nor text before message body. - (let ((body-start (save-excursion (message-goto-body)))) - (when body-start (setq beg (max body-start beg)))) - (when (save-excursion - (re-search-forward - (concat "^" message-cite-prefix-regexp) end t)) - (setq end (match-beginning 0)))) - ;; Fill paragraph, taking line breaks into account. - ;; For that, slice the paragraph using line breaks as - ;; separators, and fill the parts in reverse order to - ;; avoid messing with markers. - (save-excursion - (goto-char end) - (mapc - (lambda (pos) - (fill-region-as-paragraph pos (point) justify) - (goto-char pos)) - ;; Find the list of ending positions for line breaks - ;; in the current paragraph. Add paragraph - ;; beginning to include first slice. - (nreverse - (cons beg - (org-element-map - (org-element--parse-objects - beg end nil (org-element-restriction 'paragraph)) - 'line-break - (lambda (lb) (org-element-property :end lb))))))) - t))) - ;; Contents of `comment-block' type elements should be - ;; filled as plain text, but only if point is within block - ;; markers. - (comment-block - (let* ((case-fold-search t) - (beg (save-excursion - (goto-char (org-element-property :begin element)) - (re-search-forward "^[ \t]*#\\+begin_comment" nil t) - (forward-line) - (point))) - (end (save-excursion - (goto-char (org-element-property :end element)) - (re-search-backward "^[ \t]*#\\+end_comment" nil t) - (line-beginning-position)))) - (if (or (< (point) beg) (> (point) end)) t - (fill-region-as-paragraph - (save-excursion (end-of-line) - (re-search-backward "^[ \t]*$" beg 'move) - (line-beginning-position)) - (save-excursion (beginning-of-line) - (re-search-forward "^[ \t]*$" end 'move) - (line-beginning-position)) - justify)))) - ;; Fill comments. - (comment - (let ((begin (org-element-property :post-affiliated element)) - (end (org-element-property :end element))) - (when (and (>= (point) begin) (<= (point) end)) - (let ((begin (save-excursion - (end-of-line) - (if (re-search-backward "^[ \t]*#[ \t]*$" begin t) - (progn (forward-line) (point)) - begin))) - (end (save-excursion + (goto-char beg) + (let ((cuts (list beg))) + (while (re-search-forward "\\\\\\\\[ \t]*\n" end t) + (when (eq 'line-break + (org-element-type + (save-excursion (backward-char) + (org-element-context)))) + (push (point) cuts))) + (dolist (c (delq end cuts)) + (fill-region-as-paragraph c end justify) + (setq end c)))) + t))) + ;; Contents of `comment-block' type elements should be + ;; filled as plain text, but only if point is within block + ;; markers. + (comment-block + (let* ((case-fold-search t) + (beg (save-excursion + (goto-char (org-element-property :begin element)) + (re-search-forward "^[ \t]*#\\+begin_comment" nil t) + (forward-line) + (point))) + (end (save-excursion + (goto-char (org-element-property :end element)) + (re-search-backward "^[ \t]*#\\+end_comment" nil t) + (line-beginning-position)))) + (if (or (< (point) beg) (> (point) end)) t + (fill-region-as-paragraph + (save-excursion (end-of-line) + (re-search-backward "^[ \t]*$" beg 'move) + (line-beginning-position)) + (save-excursion (beginning-of-line) + (re-search-forward "^[ \t]*$" end 'move) + (line-beginning-position)) + justify)))) + ;; Fill comments. + (comment + (let ((begin (org-element-property :post-affiliated element)) + (end (org-element-property :end element))) + (when (and (>= (point) begin) (<= (point) end)) + (let ((begin (save-excursion (end-of-line) - (if (re-search-forward "^[ \t]*#[ \t]*$" end 'move) - (1- (line-beginning-position)) - (skip-chars-backward " \r\t\n") - (line-end-position))))) - ;; Do not fill comments when at a blank line. - (when (> end begin) - (let ((fill-prefix - (save-excursion - (beginning-of-line) - (looking-at "[ \t]*#") - (let ((comment-prefix (match-string 0))) - (goto-char (match-end 0)) - (if (looking-at adaptive-fill-regexp) - (concat comment-prefix (match-string 0)) - (concat comment-prefix " ")))))) - (save-excursion - (fill-region-as-paragraph begin end justify)))))) - t)) - ;; Ignore every other element. - (otherwise t)))))) + (if (re-search-backward "^[ \t]*#[ \t]*$" begin t) + (progn (forward-line) (point)) + begin))) + (end (save-excursion + (end-of-line) + (if (re-search-forward "^[ \t]*#[ \t]*$" end 'move) + (1- (line-beginning-position)) + (skip-chars-backward " \r\t\n") + (line-end-position))))) + ;; Do not fill comments when at a blank line. + (when (> end begin) + (let ((fill-prefix + (save-excursion + (beginning-of-line) + (looking-at "[ \t]*#") + (let ((comment-prefix (match-string 0))) + (goto-char (match-end 0)) + (if (looking-at adaptive-fill-regexp) + (concat comment-prefix (match-string 0)) + (concat comment-prefix " ")))))) + (save-excursion + (fill-region-as-paragraph begin end justify)))))) + t)) + ;; Ignore every other element. + (otherwise t))))) + +(defun org-fill-paragraph (&optional justify region) + "Fill element at point, when applicable. + +This function only applies to comment blocks, comments, example +blocks and paragraphs. Also, as a special case, re-align table +when point is at one. + +For convenience, when point is at a plain list, an item or +a footnote definition, try to fill the first paragraph within. + +If JUSTIFY is non-nil (interactively, with prefix argument), +justify as well. If `sentence-end-double-space' is non-nil, then +period followed by one space does not end a sentence, so don't +break a line there. The variable `fill-column' controls the +width for filling. + +The REGION argument is non-nil if called interactively; in that +case, if Transient Mark mode is enabled and the mark is active, +fill each of the elements in the active region, instead of just +filling the current element." + (interactive (progn + (barf-if-buffer-read-only) + (list (if current-prefix-arg 'full) t))) + (cond + ((and (derived-mode-p 'message-mode) + (or (not (message-in-body-p)) + (save-excursion (move-beginning-of-line 1) + (looking-at message-cite-prefix-regexp)))) + ;; First ensure filling is correct in message-mode. + (let ((fill-paragraph-function + (cl-cadadr (assq 'fill-paragraph-function org-fb-vars))) + (fill-prefix (cl-cadadr (assq 'fill-prefix org-fb-vars))) + (paragraph-start (cl-cadadr (assq 'paragraph-start org-fb-vars))) + (paragraph-separate + (cl-cadadr (assq 'paragraph-separate org-fb-vars)))) + (fill-paragraph nil))) + ((and region transient-mark-mode mark-active + (not (eq (region-beginning) (region-end)))) + (let ((origin (point-marker)) + (start (region-beginning))) + (unwind-protect + (progn + (goto-char (region-end)) + (while (> (point) start) + (org-backward-paragraph) + (org-fill-element justify))) + (goto-char origin) + (set-marker origin nil)))) + (t (org-fill-element justify)))) +(org-remap org-mode-map 'fill-paragraph 'org-fill-paragraph) (defun org-auto-fill-function () "Auto-fill function." @@ -22564,11 +23087,135 @@ non-nil." (insert-before-markers-and-inherit fill-prefix)) +;;; Fixed Width Areas + +(defun org-toggle-fixed-width () + "Toggle fixed-width markup. + +Add or remove fixed-width markup on current line, whenever it +makes sense. Return an error otherwise. + +If a region is active and if it contains only fixed-width areas +or blank lines, remove all fixed-width markup in it. If the +region contains anything else, convert all non-fixed-width lines +to fixed-width ones. + +Blank lines at the end of the region are ignored unless the +region only contains such lines." + (interactive) + (if (not (org-region-active-p)) + ;; No region: + ;; + ;; Remove fixed width marker only in a fixed-with element. + ;; + ;; Add fixed width maker in paragraphs, in blank lines after + ;; elements or at the beginning of a headline or an inlinetask, + ;; and before any one-line elements (e.g., a clock). + (progn + (beginning-of-line) + (let* ((element (org-element-at-point)) + (type (org-element-type element))) + (cond + ((and (eq type 'fixed-width) + (looking-at "[ \t]*\\(:\\(?: \\|$\\)\\)")) + (replace-match + "" nil nil nil (if (= (line-end-position) (match-end 0)) 0 1))) + ((and (memq type '(babel-call clock comment diary-sexp headline + horizontal-rule keyword paragraph + planning)) + (<= (org-element-property :post-affiliated element) (point))) + (skip-chars-forward " \t") + (insert ": ")) + ((and (looking-at-p "[ \t]*$") + (or (eq type 'inlinetask) + (save-excursion + (skip-chars-forward " \r\t\n") + (<= (org-element-property :end element) (point))))) + (delete-region (point) (line-end-position)) + (org-indent-line) + (insert ": ")) + (t (user-error "Cannot insert a fixed-width line here"))))) + ;; Region active. + (let* ((begin (save-excursion + (goto-char (region-beginning)) + (line-beginning-position))) + (end (copy-marker + (save-excursion + (goto-char (region-end)) + (unless (eolp) (beginning-of-line)) + (if (save-excursion (re-search-backward "\\S-" begin t)) + (progn (skip-chars-backward " \r\t\n") (point)) + (point))))) + (all-fixed-width-p + (catch 'not-all-p + (save-excursion + (goto-char begin) + (skip-chars-forward " \r\t\n") + (when (eobp) (throw 'not-all-p nil)) + (while (< (point) end) + (let ((element (org-element-at-point))) + (if (eq (org-element-type element) 'fixed-width) + (goto-char (org-element-property :end element)) + (throw 'not-all-p nil)))) + t)))) + (if all-fixed-width-p + (save-excursion + (goto-char begin) + (while (< (point) end) + (when (looking-at "[ \t]*\\(:\\(?: \\|$\\)\\)") + (replace-match + "" nil nil nil + (if (= (line-end-position) (match-end 0)) 0 1))) + (forward-line))) + (let ((min-ind (point-max))) + ;; Find minimum indentation across all lines. + (save-excursion + (goto-char begin) + (if (not (save-excursion (re-search-forward "\\S-" end t))) + (setq min-ind 0) + (catch 'zerop + (while (< (point) end) + (unless (looking-at-p "[ \t]*$") + (let ((ind (org-get-indentation))) + (setq min-ind (min min-ind ind)) + (when (zerop ind) (throw 'zerop t)))) + (forward-line))))) + ;; Loop over all lines and add fixed-width markup everywhere + ;; but in fixed-width lines. + (save-excursion + (goto-char begin) + (while (< (point) end) + (cond + ((org-at-heading-p) + (insert ": ") + (forward-line) + (while (and (< (point) end) (looking-at-p "[ \t]*$")) + (insert ":") + (forward-line))) + ((looking-at-p "[ \t]*:\\( \\|$\\)") + (let* ((element (org-element-at-point)) + (element-end (org-element-property :end element))) + (if (eq (org-element-type element) 'fixed-width) + (progn (goto-char element-end) + (skip-chars-backward " \r\t\n") + (forward-line)) + (let ((limit (min end element-end))) + (while (< (point) limit) + (org-move-to-column min-ind t) + (insert ": ") + (forward-line)))))) + (t + (org-move-to-column min-ind t) + (insert ": ") + (forward-line))))))) + (set-marker end nil)))) + + ;;; Comments ;; Org comments syntax is quite complex. It requires the entire line ;; to be just a comment. Also, even with the right syntax at the -;; beginning of line, some some elements (i.e. verse-block or +;; beginning of line, some elements (e.g., verse-block or ;; example-block) don't accept comments. Usual Emacs comment commands ;; cannot cope with those requirements. Therefore, Org replaces them. @@ -22584,87 +23231,139 @@ non-nil." (defun org-setup-comments-handling () (interactive) - (org-set-local 'comment-use-syntax nil) - (org-set-local 'comment-start "# ") - (org-set-local 'comment-start-skip "^\\s-*#\\(?: \\|$\\)") - (org-set-local 'comment-insert-comment-function 'org-insert-comment) - (org-set-local 'comment-region-function 'org-comment-or-uncomment-region) - (org-set-local 'uncomment-region-function 'org-comment-or-uncomment-region)) + (setq-local comment-use-syntax nil) + (setq-local comment-start "# ") + (setq-local comment-start-skip "^\\s-*#\\(?: \\|$\\)") + (setq-local comment-insert-comment-function 'org-insert-comment) + (setq-local comment-region-function 'org-comment-or-uncomment-region) + (setq-local uncomment-region-function 'org-comment-or-uncomment-region)) (defun org-insert-comment () "Insert an empty comment above current line. -If the line is empty, insert comment at its beginning." - (beginning-of-line) - (if (looking-at "\\s-*$") (replace-match "") (open-line 1)) - (org-indent-line) - (insert "# ")) +If the line is empty, insert comment at its beginning. When +point is within a source block, comment according to the related +major mode." + (if (let ((element (org-element-at-point))) + (and (eq (org-element-type element) 'src-block) + (< (save-excursion + (goto-char (org-element-property :post-affiliated element)) + (line-end-position)) + (point)) + (> (save-excursion + (goto-char (org-element-property :end element)) + (skip-chars-backward " \r\t\n") + (line-beginning-position)) + (point)))) + (org-babel-do-in-edit-buffer (call-interactively 'comment-dwim)) + (beginning-of-line) + (if (looking-at "\\s-*$") (delete-region (point) (point-at-eol)) + (open-line 1)) + (org-indent-line) + (insert "# "))) (defvar comment-empty-lines) ; From newcomment.el. (defun org-comment-or-uncomment-region (beg end &rest _) "Comment or uncomment each non-blank line in the region. Uncomment each non-blank line between BEG and END if it only -contains commented lines. Otherwise, comment them." - (save-restriction - ;; Restrict region - (narrow-to-region (save-excursion (goto-char beg) - (skip-chars-forward " \r\t\n" end) - (line-beginning-position)) - (save-excursion (goto-char end) - (skip-chars-backward " \r\t\n" beg) - (line-end-position))) - (let ((uncommentp - ;; UNCOMMENTP is non-nil when every non blank line between - ;; BEG and END is a comment. - (save-excursion - (goto-char (point-min)) - (while (and (not (eobp)) - (let ((element (org-element-at-point))) - (and (eq (org-element-type element) 'comment) - (goto-char (min (point-max) - (org-element-property - :end element))))))) - (eobp)))) - (if uncommentp - ;; Only blank lines and comments in region: uncomment it. - (save-excursion - (goto-char (point-min)) - (while (not (eobp)) - (when (looking-at "[ \t]*\\(#\\(?: \\|$\\)\\)") - (replace-match "" nil nil nil 1)) - (forward-line))) - ;; Comment each line in region. - (let ((min-indent (point-max))) - ;; First find the minimum indentation across all lines. - (save-excursion - (goto-char (point-min)) - (while (and (not (eobp)) (not (zerop min-indent))) - (unless (looking-at "[ \t]*$") - (setq min-indent (min min-indent (current-indentation)))) - (forward-line))) - ;; Then loop over all lines. - (save-excursion - (goto-char (point-min)) - (while (not (eobp)) - (unless (and (not comment-empty-lines) (looking-at "[ \t]*$")) - ;; Don't get fooled by invisible text (e.g. link path) - ;; when moving to column MIN-INDENT. - (let ((buffer-invisibility-spec nil)) - (org-move-to-column min-indent t)) - (insert comment-start)) - (forward-line)))))))) +contains commented lines. Otherwise, comment them. If region is +strictly within a source block, use appropriate comment syntax." + (if (let ((element (org-element-at-point))) + (and (eq (org-element-type element) 'src-block) + (< (save-excursion + (goto-char (org-element-property :post-affiliated element)) + (line-end-position)) + beg) + (>= (save-excursion + (goto-char (org-element-property :end element)) + (skip-chars-backward " \r\t\n") + (line-beginning-position)) + end))) + ;; Translate region boundaries for the Org buffer to the source + ;; buffer. + (let ((offset (- end beg))) + (save-excursion + (goto-char beg) + (org-babel-do-in-edit-buffer + (comment-or-uncomment-region (point) (+ offset (point)))))) + (save-restriction + ;; Restrict region + (narrow-to-region (save-excursion (goto-char beg) + (skip-chars-forward " \r\t\n" end) + (line-beginning-position)) + (save-excursion (goto-char end) + (skip-chars-backward " \r\t\n" beg) + (line-end-position))) + (let ((uncommentp + ;; UNCOMMENTP is non-nil when every non blank line between + ;; BEG and END is a comment. + (save-excursion + (goto-char (point-min)) + (while (and (not (eobp)) + (let ((element (org-element-at-point))) + (and (eq (org-element-type element) 'comment) + (goto-char (min (point-max) + (org-element-property + :end element))))))) + (eobp)))) + (if uncommentp + ;; Only blank lines and comments in region: uncomment it. + (save-excursion + (goto-char (point-min)) + (while (not (eobp)) + (when (looking-at "[ \t]*\\(#\\(?: \\|$\\)\\)") + (replace-match "" nil nil nil 1)) + (forward-line))) + ;; Comment each line in region. + (let ((min-indent (point-max))) + ;; First find the minimum indentation across all lines. + (save-excursion + (goto-char (point-min)) + (while (and (not (eobp)) (not (zerop min-indent))) + (unless (looking-at "[ \t]*$") + (setq min-indent (min min-indent (current-indentation)))) + (forward-line))) + ;; Then loop over all lines. + (save-excursion + (goto-char (point-min)) + (while (not (eobp)) + (unless (and (not comment-empty-lines) (looking-at "[ \t]*$")) + ;; Don't get fooled by invisible text (e.g. link path) + ;; when moving to column MIN-INDENT. + (let ((buffer-invisibility-spec nil)) + (org-move-to-column min-indent t)) + (insert comment-start)) + (forward-line))))))))) + +(defun org-comment-dwim (_arg) + "Call `comment-dwim' within a source edit buffer if needed." + (interactive "*P") + (if (org-in-src-block-p) + (org-babel-do-in-edit-buffer (call-interactively 'comment-dwim)) + (call-interactively 'comment-dwim))) -;;; Planning +;;; Timestamps API ;; This section contains tools to operate on timestamp objects, as ;; returned by, e.g. `org-element-context'. +(defun org-timestamp--to-internal-time (timestamp &optional end) + "Encode TIMESTAMP object into Emacs internal time. +Use end of date range or time range when END is non-nil." + (apply #'encode-time + (cons 0 + (mapcar + (lambda (prop) (or (org-element-property prop timestamp) 0)) + (if end '(:minute-end :hour-end :day-end :month-end :year-end) + '(:minute-start :hour-start :day-start :month-start + :year-start)))))) + (defun org-timestamp-has-time-p (timestamp) "Non-nil when TIMESTAMP has a time specified." (org-element-property :hour-start timestamp)) -(defun org-timestamp-format (timestamp format &optional end zone) - "Format a TIMESTAMP element into a string. +(defun org-timestamp-format (timestamp format &optional end utc) + "Format a TIMESTAMP object into a string. FORMAT is a format specifier to be passed to `format-time-string'. @@ -22672,33 +23371,22 @@ FORMAT is a format specifier to be passed to When optional argument END is non-nil, use end of date-range or time-range, if possible. -The optional ZONE is omitted or nil for Emacs local time, t for -Universal Time, `wall' for system wall clock time, or a string as -in the TZ environment variable. It can also be a list (as from -`current-time-zone') or an integer (as from `decode-time') -applied without consideration for daylight saving time." +When optional argument UTC is non-nil, time will be expressed as +Universal Time." (format-time-string - format - (apply 'encode-time - (cons 0 - (mapcar - (lambda (prop) (or (org-element-property prop timestamp) 0)) - (if end '(:minute-end :hour-end :day-end :month-end :year-end) - '(:minute-start :hour-start :day-start :month-start - :year-start))))) - zone)) + format (org-timestamp--to-internal-time timestamp end) + (and utc t))) (defun org-timestamp-split-range (timestamp &optional end) - "Extract a timestamp object from a date or time range. + "Extract a TIMESTAMP object from a date or time range. -TIMESTAMP is a timestamp object. END, when non-nil, means extract -the end of the range. Otherwise, extract its start. +END, when non-nil, means extract the end of the range. +Otherwise, extract its start. -Return a new timestamp object sharing the same parent as -TIMESTAMP." +Return a new timestamp object." (let ((type (org-element-property :type timestamp))) (if (memq type '(active inactive diary)) timestamp - (let ((split-ts (list 'timestamp (copy-sequence (nth 1 timestamp))))) + (let ((split-ts (org-element-copy timestamp))) ;; Set new type. (org-element-put-property split-ts :type (if (eq type 'active-range) 'active 'inactive)) @@ -22712,91 +23400,43 @@ TIMESTAMP." (dolist (p-cell p-alist) (org-element-put-property split-ts - (funcall (if end 'car 'cdr) p-cell) + (funcall (if end #'car #'cdr) p-cell) (org-element-property - (funcall (if end 'cdr 'car) p-cell) split-ts))) + (funcall (if end #'cdr #'car) p-cell) split-ts))) ;; Eventually refresh `:raw-value'. (org-element-put-property split-ts :raw-value nil) (org-element-put-property split-ts :raw-value (org-element-interpret-data split-ts))))))) (defun org-timestamp-translate (timestamp &optional boundary) - "Apply `org-translate-time' on a TIMESTAMP object. + "Translate TIMESTAMP object to custom format. + +Format string is defined in `org-time-stamp-custom-formats', +which see. + When optional argument BOUNDARY is non-nil, it is either the symbol `start' or `end'. In this case, only translate the starting or ending part of TIMESTAMP if it is a date or time -range. Otherwise, translate both parts." - (if (and (not boundary) - (memq (org-element-property :type timestamp) - '(active-range inactive-range))) - (concat - (org-translate-time - (org-element-property :raw-value - (org-timestamp-split-range timestamp))) - "--" - (org-translate-time - (org-element-property :raw-value - (org-timestamp-split-range timestamp t)))) - (org-translate-time - (org-element-property - :raw-value - (if (not boundary) timestamp - (org-timestamp-split-range timestamp (eq boundary 'end))))))) +range. Otherwise, translate both parts. +Return timestamp as-is if `org-display-custom-times' is nil or if +it has a `diary' type." + (let ((type (org-element-property :type timestamp))) + (if (or (not org-display-custom-times) (eq type 'diary)) + (org-element-interpret-data timestamp) + (let ((fmt (funcall (if (org-timestamp-has-time-p timestamp) #'cdr #'car) + org-time-stamp-custom-formats))) + (if (and (not boundary) (memq type '(active-range inactive-range))) + (concat (org-timestamp-format timestamp fmt) + "--" + (org-timestamp-format timestamp fmt t)) + (org-timestamp-format timestamp fmt (eq boundary 'end))))))) -;;; Other stuff. -(defun org-toggle-fixed-width-section (arg) - "Toggle the fixed-width export. -If there is no active region, the QUOTE keyword at the current headline is -inserted or removed. When present, it causes the text between this headline -and the next to be exported as fixed-width text, and unmodified. -If there is an active region, this command adds or removes a colon as the -first character of this line. If the first character of a line is a colon, -this line is also exported in fixed-width font." - (interactive "P") - (let* ((cc 0) - (regionp (org-region-active-p)) - (beg (if regionp (region-beginning) (point))) - (end (if regionp (region-end))) - (nlines (or arg (if (and beg end) (count-lines beg end) 1))) - (case-fold-search nil) - (re "[ \t]*\\(:\\(?: \\|$\\)\\)") - off) - (if regionp - (save-excursion - (goto-char beg) - (setq cc (current-column)) - (beginning-of-line 1) - (setq off (looking-at re)) - (while (> nlines 0) - (setq nlines (1- nlines)) - (beginning-of-line 1) - (cond - (arg - (org-move-to-column cc t) - (insert ": \n") - (forward-line -1)) - ((and off (looking-at re)) - (replace-match "" t t nil 1)) - ((not off) (org-move-to-column cc t) (insert ": "))) - (forward-line 1))) - (save-excursion - (org-back-to-heading) - (cond - ((looking-at (format org-heading-keyword-regexp-format - org-quote-string)) - (goto-char (match-end 1)) - (looking-at (concat " +" org-quote-string)) - (replace-match "" t t) - (when (eolp) (insert " "))) - ((looking-at org-outline-regexp) - (goto-char (match-end 0)) - (insert org-quote-string " "))))))) +;;; Other stuff. (defvar reftex-docstruct-symbol) -(defvar reftex-cite-format) (defvar org--rds) (defun org-reftex-citation () @@ -22814,131 +23454,137 @@ Export of such citations to both LaTeX and HTML is handled by the contributed package ox-bibtex by Taru Karttunen." (interactive) (let ((reftex-docstruct-symbol 'org--rds) - (reftex-cite-format "\\cite{%l}") org--rds bib) - (save-excursion - (save-restriction - (widen) - (let ((case-fold-search t) - (re "^#\\+bibliography:[ \t]+\\([^ \t\n]+\\)")) - (if (not (save-excursion - (or (re-search-forward re nil t) - (re-search-backward re nil t)))) - (error "No bibliography defined in file") - (setq bib (concat (match-string 1) ".bib") - org--rds (list (list 'bib bib))))))) + (org-with-wide-buffer + (let ((case-fold-search t) + (re "^[ \t]*#\\+BIBLIOGRAPHY:[ \t]+\\([^ \t\n]+\\)")) + (if (not (save-excursion + (or (re-search-forward re nil t) + (re-search-backward re nil t)))) + (user-error "No bibliography defined in file") + (setq bib (concat (match-string 1) ".bib") + org--rds (list (list 'bib bib)))))) (call-interactively 'reftex-citation))) ;;;; Functions extending outline functionality -(defun org-beginning-of-line (&optional arg) - "Go to the beginning of the current line. If that is invisible, continue -to a visible line beginning. This makes the function of C-a more intuitive. -If this is a headline, and `org-special-ctrl-a/e' is set, ignore tags on the -first attempt, and only move to after the tags when the cursor is already -beyond the end of the headline." - (interactive "P") - (let ((pos (point)) - (special (if (consp org-special-ctrl-a/e) - (car org-special-ctrl-a/e) - org-special-ctrl-a/e)) - deactivate-mark refpos) - (if (org-bound-and-true-p visual-line-mode) - (beginning-of-visual-line 1) - (beginning-of-line 1)) - (if (and arg (fboundp 'move-beginning-of-line)) - (call-interactively 'move-beginning-of-line) - (if (bobp) - nil - (backward-char 1) - (if (org-truely-invisible-p) - (while (and (not (bobp)) (org-truely-invisible-p)) - (backward-char 1) - (beginning-of-line 1)) - (forward-char 1)))) - (when special - (cond - ((and (looking-at org-complex-heading-regexp) - (= (char-after (match-end 1)) ?\ )) - (setq refpos (min (1+ (or (match-end 3) (match-end 2) (match-end 1))) - (point-at-eol))) - (goto-char - (if (eq special t) - (cond ((> pos refpos) refpos) - ((= pos (point)) refpos) - (t (point))) - (cond ((> pos (point)) (point)) - ((not (eq last-command this-command)) (point)) - (t refpos))))) - ((org-at-item-p) - ;; Being at an item and not looking at an the item means point - ;; was previously moved to beginning of a visual line, which - ;; doesn't contain the item. Therefore, do nothing special, - ;; just stay here. - (when (looking-at org-list-full-item-re) - ;; Set special position at first white space character after - ;; bullet, and check-box, if any. - (let ((after-bullet - (let ((box (match-end 3))) - (if (not box) (match-end 1) - (let ((after (char-after box))) - (if (and after (= after ? )) (1+ box) box)))))) - ;; Special case: Move point to special position when - ;; currently after it or at beginning of line. - (if (eq special t) - (when (or (> pos after-bullet) (= (point) pos)) - (goto-char after-bullet)) - ;; Reversed case: Move point to special position when - ;; point was already at beginning of line and command is - ;; repeated. - (when (and (= (point) pos) (eq last-command this-command)) - (goto-char after-bullet)))))))) - (org-no-warnings - (and (featurep 'xemacs) (setq zmacs-region-stays t)))) - (setq disable-point-adjustment - (or (not (invisible-p (point))) - (not (invisible-p (max (point-min) (1- (point)))))))) - -(defun org-end-of-line (&optional arg) - "Go to the end of the line. +(defun org-beginning-of-line (&optional n) + "Go to the beginning of the current visible line. + If this is a headline, and `org-special-ctrl-a/e' is set, ignore tags on the first attempt, and only move to after the tags when -the cursor is already beyond the end of the headline." - (interactive "P") - (let ((special (if (consp org-special-ctrl-a/e) (cdr org-special-ctrl-a/e) - org-special-ctrl-a/e)) - (move-fun (cond ((org-bound-and-true-p visual-line-mode) - 'end-of-visual-line) - ((fboundp 'move-end-of-line) 'move-end-of-line) - (t 'end-of-line))) +the cursor is already beyond the end of the headline. + +With argument N not nil or 1, move forward N - 1 lines first." + (interactive "^p") + (let ((origin (point)) + (special (pcase org-special-ctrl-a/e + (`(,C-a . ,_) C-a) (_ org-special-ctrl-a/e))) deactivate-mark) - (if (or (not special) arg) (call-interactively move-fun) - (let* ((element (save-excursion (beginning-of-line) - (org-element-at-point))) - (type (org-element-type element))) - (cond - ((memq type '(headline inlinetask)) - (let ((pos (point))) - (beginning-of-line 1) - (if (looking-at (org-re ".*?\\(?:\\([ \t]*\\)\\(:[[:alnum:]_@#%:]+:\\)?[ \t]*\\)?$")) - (if (eq special t) - (if (or (< pos (match-beginning 1)) (= pos (match-end 0))) - (goto-char (match-beginning 1)) - (goto-char (match-end 0))) - (if (or (< pos (match-end 0)) - (not (eq this-command last-command))) - (goto-char (match-end 0)) - (goto-char (match-beginning 1)))) - (call-interactively move-fun)))) - ((org-element-property :hiddenp element) - ;; If element is hidden, `move-end-of-line' would put point - ;; after it. Use `end-of-line' to stay on current line. - (call-interactively 'end-of-line)) - (t (call-interactively move-fun))))) - (org-no-warnings (and (featurep 'xemacs) (setq zmacs-region-stays t)))) - (setq disable-point-adjustment - (or (not (invisible-p (point))) - (not (invisible-p (max (point-min) (1- (point)))))))) + ;; First move to a visible line. + (if (bound-and-true-p visual-line-mode) + (beginning-of-visual-line n) + (move-beginning-of-line n) + ;; `move-beginning-of-line' may leave point after invisible + ;; characters if line starts with such of these (e.g., with + ;; a link at column 0). Really move to the beginning of the + ;; current visible line. + (beginning-of-line)) + (cond + ;; No special behavior. Point is already at the beginning of + ;; a line, logical or visual. + ((not special)) + ;; `beginning-of-visual-line' left point before logical beginning + ;; of line: point is at the beginning of a visual line. Bail + ;; out. + ((and (bound-and-true-p visual-line-mode) (not (bolp)))) + ((let ((case-fold-search nil)) (looking-at org-complex-heading-regexp)) + ;; At a headline, special position is before the title, but + ;; after any TODO keyword or priority cookie. + (let ((refpos (min (1+ (or (match-end 3) (match-end 2) (match-end 1))) + (line-end-position))) + (bol (point))) + (if (eq special 'reversed) + (when (and (= origin bol) (eq last-command this-command)) + (goto-char refpos)) + (when (or (> origin refpos) (= origin bol)) + (goto-char refpos))))) + ((and (looking-at org-list-full-item-re) + (memq (org-element-type (save-match-data (org-element-at-point))) + '(item plain-list))) + ;; Set special position at first white space character after + ;; bullet, and check-box, if any. + (let ((after-bullet + (let ((box (match-end 3))) + (cond ((not box) (match-end 1)) + ((eq (char-after box) ?\s) (1+ box)) + (t box))))) + (if (eq special 'reversed) + (when (and (= (point) origin) (eq last-command this-command)) + (goto-char after-bullet)) + (when (or (> origin after-bullet) (= (point) origin)) + (goto-char after-bullet))))) + ;; No special context. Point is already at beginning of line. + (t nil)))) + +(defun org-end-of-line (&optional n) + "Go to the end of the line, but before ellipsis, if any. + +If this is a headline, and `org-special-ctrl-a/e' is set, ignore +tags on the first attempt, and only move to after the tags when +the cursor is already beyond the end of the headline. + +With argument N not nil or 1, move forward N - 1 lines first." + (interactive "^p") + (let ((origin (point)) + (special (pcase org-special-ctrl-a/e + (`(,_ . ,C-e) C-e) (_ org-special-ctrl-a/e))) + deactivate-mark) + ;; First move to a visible line. + (if (bound-and-true-p visual-line-mode) + (beginning-of-visual-line n) + (move-beginning-of-line n)) + (cond + ;; At a headline, with tags. + ((and special + (save-excursion + (beginning-of-line) + (let ((case-fold-search nil)) + (looking-at org-complex-heading-regexp))) + (match-end 5)) + (let ((tags (save-excursion + (goto-char (match-beginning 5)) + (skip-chars-backward " \t") + (point))) + (visual-end (and (bound-and-true-p visual-line-mode) + (save-excursion + (end-of-visual-line) + (point))))) + ;; If `end-of-visual-line' brings us before end of line or + ;; even tags, i.e., the headline spans over multiple visual + ;; lines, move there. + (cond ((and visual-end + (< visual-end tags) + (<= origin visual-end)) + (goto-char visual-end)) + ((eq special 'reversed) + (if (and (= origin (line-end-position)) + (eq this-command last-command)) + (goto-char tags) + (end-of-line))) + (t + (if (or (< origin tags) (= origin (line-end-position))) + (goto-char tags) + (end-of-line)))))) + ((bound-and-true-p visual-line-mode) + (let ((bol (line-beginning-position))) + (end-of-visual-line) + ;; If `end-of-visual-line' gets us past the ellipsis at the + ;; end of a line, backtrack and use `end-of-line' instead. + (when (/= bol (line-beginning-position)) + (goto-char bol) + (end-of-line)))) + (t (end-of-line))))) (define-key org-mode-map "\C-a" 'org-beginning-of-line) (define-key org-mode-map "\C-e" 'org-end-of-line) @@ -22948,18 +23594,50 @@ the cursor is already beyond the end of the headline." This will call `backward-sentence' or `org-table-beginning-of-field', depending on context." (interactive) - (cond - ((org-at-table-p) (call-interactively 'org-table-beginning-of-field)) - (t (call-interactively 'backward-sentence)))) + (let* ((element (org-element-at-point)) + (contents-begin (org-element-property :contents-begin element)) + (table (org-element-lineage element '(table) t))) + (if (and table + (> (point) contents-begin) + (<= (point) (org-element-property :contents-end table))) + (call-interactively #'org-table-beginning-of-field) + (save-restriction + (when (and contents-begin + (< (point-min) contents-begin) + (> (point) contents-begin)) + (narrow-to-region contents-begin + (org-element-property :contents-end element))) + (call-interactively #'backward-sentence))))) (defun org-forward-sentence (&optional _arg) "Go to end of sentence, or end of table field. This will call `forward-sentence' or `org-table-end-of-field', depending on context." (interactive) - (cond - ((org-at-table-p) (call-interactively 'org-table-end-of-field)) - (t (call-interactively 'forward-sentence)))) + (if (and (org-at-heading-p) + (save-restriction (skip-chars-forward " \t") (not (eolp)))) + (save-restriction + (narrow-to-region (line-beginning-position) (line-end-position)) + (call-interactively #'forward-sentence)) + (let* ((element (org-element-at-point)) + (contents-end (org-element-property :contents-end element)) + (table (org-element-lineage element '(table) t))) + (if (and table + (>= (point) (org-element-property :contents-begin table)) + (< (point) contents-end)) + (call-interactively #'org-table-end-of-field) + (save-restriction + (when (and contents-end + (> (point-max) contents-end) + ;; Skip blank lines between elements. + (< (org-element-property :end element) + (save-excursion (goto-char contents-end) + (skip-chars-forward " \r\t\n")))) + (narrow-to-region (org-element-property :contents-begin element) + contents-end)) + ;; End of heading is considered as the end of a sentence. + (let ((sentence-end (concat (sentence-end) "\\|^\\*+ .*$"))) + (call-interactively #'forward-sentence))))))) (define-key org-mode-map "\M-a" 'org-backward-sentence) (define-key org-mode-map "\M-e" 'org-forward-sentence) @@ -22971,14 +23649,14 @@ depending on context." ((or (not org-special-ctrl-k) (bolp) (not (org-at-heading-p))) - (if (and (get-char-property (min (point-max) (point-at-eol)) 'invisible) - org-ctrl-k-protect-subtree) - (if (or (eq org-ctrl-k-protect-subtree 'error) - (not (y-or-n-p "Kill hidden subtree along with headline? "))) - (user-error "C-k aborted as it would kill a hidden subtree"))) + (when (and (get-char-property (min (point-max) (point-at-eol)) 'invisible) + org-ctrl-k-protect-subtree + (or (eq org-ctrl-k-protect-subtree 'error) + (not (y-or-n-p "Kill hidden subtree along with headline? ")))) + (user-error "C-k aborted as it would kill a hidden subtree")) (call-interactively - (if (org-bound-and-true-p visual-line-mode) 'kill-visual-line 'kill-line))) - ((looking-at (org-re ".*?\\S-\\([ \t]+\\(:[[:alnum:]_@#%:]+:\\)\\)[ \t]*$")) + (if (bound-and-true-p visual-line-mode) 'kill-visual-line 'kill-line))) + ((looking-at ".*?\\S-\\([ \t]+\\(:[[:alnum:]_@#%:]+:\\)\\)[ \t]*$") (kill-region (point) (match-beginning 1)) (org-set-tags nil t)) (t (kill-region (point) (point-at-eol))))) @@ -22991,24 +23669,25 @@ This command will look at the current kill and check if is a single subtree, or a series of subtrees[1]. If it passes the test, and if the cursor is at the beginning of a line or after the stars of a currently empty headline, then the yank is handled specially. How exactly depends -on the value of the following variables, both set by default. +on the value of the following variables. -org-yank-folded-subtrees - When set, the subtree(s) will be folded after insertion, but only - if doing so would now swallow text after the yanked text. +`org-yank-folded-subtrees' + By default, this variable is non-nil, which results in + subtree(s) being folded after insertion, except if doing so + would swallow text after the yanked text. -org-yank-adjusted-subtrees - When set, the subtree will be promoted or demoted in order to - fit into the local outline tree structure, which means that the level - will be adjusted so that it becomes the smaller one of the two - *visible* surrounding headings. +`org-yank-adjusted-subtrees' + When non-nil (the default value is nil), the subtree will be + promoted or demoted in order to fit into the local outline tree + structure, which means that the level will be adjusted so that it + becomes the smaller one of the two *visible* surrounding headings. Any prefix to this command will cause `yank' to be called directly with -no special treatment. In particular, a simple \\[universal-argument] prefix \ +no special treatment. In particular, a simple `\\[universal-argument]' prefix \ will just plainly yank the text as it is. -[1] The test checks if the first non-white line is a heading +\[1] The test checks if the first non-white line is a heading and if there are no other headings with fewer stars." (interactive "P") (org-yank-generic 'yank arg)) @@ -23051,7 +23730,7 @@ interactive command with similar behavior." (or (looking-at org-outline-regexp) (re-search-forward org-outline-regexp-bol end t)) (while (and (< (point) end) (looking-at org-outline-regexp)) - (hide-subtree) + (outline-hide-subtree) (org-cycle-show-empty-lines 'folded) (condition-case nil (outline-forward-same-level 1) @@ -23082,11 +23761,9 @@ interactive command with similar behavior." (setq level (org-outline-level))) (goto-char end) (skip-chars-forward " \t\r\n\v\f") - (if (or (eobp) - (and (bolp) (looking-at org-outline-regexp) - (<= (org-outline-level) level))) - nil ; Nothing would be swallowed - t))))) ; something would swallow + (not (or (eobp) + (and (bolp) (looking-at-p org-outline-regexp) + (<= (org-outline-level) level)))))))) (define-key org-mode-map "\C-y" 'org-yank) @@ -23094,17 +23771,18 @@ interactive command with similar behavior." "Check if point is at a character currently not visible. This version does not only check the character property, but also `visible-mode'." - ;; Early versions of noutline don't have `outline-invisible-p'. - (if (org-bound-and-true-p visible-mode) - nil - (outline-invisible-p))) + (unless (bound-and-true-p visible-mode) + (org-invisible-p))) (defun org-invisible-p2 () - "Check if point is at a character currently not visible." + "Check if point is at a character currently not visible. + +If the point is at EOL (and not at the beginning of a buffer too), +move it back by one char before doing this check." (save-excursion - (if (and (eolp) (not (bobp))) (backward-char 1)) - ;; Early versions of noutline don't have `outline-invisible-p'. - (outline-invisible-p))) + (when (and (eolp) (not (bobp))) + (backward-char 1)) + (org-invisible-p))) (defun org-back-to-heading (&optional invisible-ok) "Call `outline-back-to-heading', but provide a better error message." @@ -23121,14 +23799,28 @@ This version does not only check the character property, but also (defun org-at-heading-p (&optional ignored) (outline-on-heading-p t)) -;; Compatibility alias with Org versions < 7.8.03 -(defalias 'org-on-heading-p 'org-at-heading-p) + +(defun org-in-commented-heading-p (&optional no-inheritance) + "Non-nil if point is under a commented heading. +This function also checks ancestors of the current headline, +unless optional argument NO-INHERITANCE is non-nil." + (cond + ((org-before-first-heading-p) nil) + ((let ((headline (nth 4 (org-heading-components)))) + (and headline + (let ((case-fold-search nil)) + (string-match-p (concat "^" org-comment-string "\\(?: \\|$\\)") + headline))))) + (no-inheritance nil) + (t + (save-excursion (and (org-up-heading-safe) (org-in-commented-heading-p)))))) (defun org-at-comment-p nil - "Is cursor in a line starting with a # character?" + "Is cursor in a commented line?" (save-excursion - (beginning-of-line) - (looking-at "^#"))) + (save-match-data + (beginning-of-line) + (looking-at "^[ \t]*# ")))) (defun org-at-drawer-p nil "Is cursor at a drawer keyword?" @@ -23146,13 +23838,13 @@ This version does not only check the character property, but also "If point is at the end of an empty headline, return t, else nil. If the heading only contains a TODO keyword, it is still still considered empty." - (and (looking-at "[ \t]*$") - (when org-todo-line-regexp + (let ((case-fold-search nil)) + (and (looking-at "[ \t]*$") + org-todo-line-regexp (save-excursion - (beginning-of-line 1) - (let ((case-fold-search nil)) - (looking-at org-todo-line-regexp) - (string= (match-string 3) "")))))) + (beginning-of-line) + (looking-at org-todo-line-regexp) + (string= (match-string 3) ""))))) (defun org-at-heading-or-item-p () (or (org-at-heading-p) (org-at-item-p))) @@ -23167,9 +23859,7 @@ empty." "Move to the heading line of which the present line is a subheading. This function considers both visible and invisible heading lines. With argument, move up ARG levels." - (if (fboundp 'outline-up-heading-all) - (outline-up-heading-all arg) ; emacs 21 version of outline.el - (outline-up-heading arg t))) ; emacs 22 version of outline.el + (outline-up-heading arg t)) (defun org-up-heading-safe () "Move to the heading line of which the present line is a subheading. @@ -23179,14 +23869,11 @@ headline found, or nil if no higher level is found. Also, this function will be a lot faster than `outline-up-heading', because it relies on stars being the outline starters. This can really make a significant difference in outlines with very many siblings." - (let (start-level re) - (org-back-to-heading t) - (setq start-level (funcall outline-level)) - (if (equal start-level 1) - nil - (setq re (concat "^\\*\\{1," (number-to-string (1- start-level)) "\\} ")) - (if (re-search-backward re nil t) - (funcall outline-level))))) + (when (ignore-errors (org-back-to-heading t)) + (let ((level-up (1- (funcall outline-level)))) + (and (> level-up 0) + (re-search-backward (format "^\\*\\{1,%d\\} " level-up) nil t) + (funcall outline-level))))) (defun org-first-sibling-p () "Is this heading the first child of its parents?" @@ -23211,7 +23898,7 @@ move point." (pos (point)) (re org-outline-regexp-bol) level l) - (when (condition-case nil (org-back-to-heading t) (error nil)) + (when (ignore-errors (org-back-to-heading t)) (setq level (funcall outline-level)) (catch 'exit (or previous (forward-char 1)) @@ -23235,7 +23922,7 @@ move point." Return t when a child was found. Otherwise don't move point and return nil." (let (level (pos (point)) (re org-outline-regexp-bol)) - (when (condition-case nil (org-back-to-heading t) (error nil)) + (when (ignore-errors (org-back-to-heading t)) (setq level (outline-level)) (forward-char 1) (if (and (re-search-forward re nil t) (> (outline-level) level)) @@ -23271,8 +23958,7 @@ This is like outline-next-sibling, but invisible headings are ok." (outline-next-heading) (while (and (not (eobp)) (> (funcall outline-level) level)) (outline-next-heading)) - (if (or (eobp) (< (funcall outline-level) level)) - nil + (unless (or (eobp) (< (funcall outline-level) level)) (point)))) (defun org-get-last-sibling () @@ -23285,8 +23971,7 @@ If there is no such heading, return nil." (while (and (> (funcall outline-level) level) (not (bobp))) (outline-previous-heading)) - (if (< (funcall outline-level) level) - nil + (unless (< (funcall outline-level) level) (point))))) (defun org-end-of-subtree (&optional invisible-ok to-heading) @@ -23302,7 +23987,7 @@ If there is no such heading, return nil." (let ((first t) (level (funcall outline-level))) (if (and (derived-mode-p 'org-mode) (< level 1000)) - ;; A true heading (not a plain list item), in Org-mode + ;; A true heading (not a plain list item), in Org ;; This means we can easily find the end by looking ;; only for the right number of stars. Using a regexp to do ;; this is so much faster than using a Lisp loop. @@ -23315,33 +24000,36 @@ If there is no such heading, return nil." (setq first nil) (outline-next-heading))) (unless to-heading - (if (memq (preceding-char) '(?\n ?\^M)) - (progn - ;; Go to end of line before heading - (forward-char -1) - (if (memq (preceding-char) '(?\n ?\^M)) - ;; leave blank line before heading - (forward-char -1)))))) + (when (memq (preceding-char) '(?\n ?\^M)) + ;; Go to end of line before heading + (forward-char -1) + (when (memq (preceding-char) '(?\n ?\^M)) + ;; leave blank line before heading + (forward-char -1))))) (point)) -(defun org-end-of-meta-data-and-drawers () - "Jump to the first text after meta data and drawers in the current entry. -This will move over empty lines, lines with planning time stamps, -clocking lines, and drawers." +(defun org-end-of-meta-data (&optional full) + "Skip planning line and properties drawer in current entry. +When optional argument FULL is non-nil, also skip empty lines, +clocking lines and regular drawers at the beginning of the +entry." (org-back-to-heading t) - (let ((end (save-excursion (outline-next-heading) (point))) - (re (concat "\\(" org-drawer-regexp "\\)" - "\\|" "[ \t]*" org-keyword-time-regexp))) - (forward-line 1) - (while (re-search-forward re end t) - (if (not (match-end 1)) - ;; empty or planning line - (forward-line 1) - ;; a drawer, find the end - (re-search-forward "^[ \t]*:END:" end 'move) - (forward-line 1))) - (and (re-search-forward "[^\n]" nil t) (backward-char 1)) - (point))) + (forward-line) + (when (looking-at-p org-planning-line-re) (forward-line)) + (when (looking-at org-property-drawer-re) + (goto-char (match-end 0)) + (forward-line)) + (when (and full (not (org-at-heading-p))) + (catch 'exit + (let ((end (save-excursion (outline-next-heading) (point))) + (re (concat "[ \t]*$" "\\|" org-clock-line-re))) + (while (not (eobp)) + (cond ((looking-at-p org-drawer-regexp) + (if (re-search-forward "^[ \t]*:END:[ \t]*$" end t) + (forward-line) + (throw 'exit t))) + ((looking-at-p re) (forward-line)) + (t (throw 'exit t)))))))) (defun org-forward-heading-same-level (arg &optional invisible-ok) "Move forward to the ARG'th subheading at same level as this one. @@ -23349,32 +24037,27 @@ Stop at the first and last subheadings of a superior heading. Normally this only looks at visible headings, but when INVISIBLE-OK is non-nil it will also look at invisible ones." (interactive "p") - (if (not (ignore-errors (org-back-to-heading invisible-ok))) - (if (and arg (< arg 0)) - (goto-char (point-min)) - (outline-next-heading)) - (org-at-heading-p) - (let ((level (- (match-end 0) (match-beginning 0) 1)) - (f (if (and arg (< arg 0)) - 're-search-backward - 're-search-forward)) - (count (if arg (abs arg) 1)) - (result (point))) - (while (and (prog1 (> count 0) - (forward-char (if (and arg (< arg 0)) -1 1))) - (funcall f org-outline-regexp-bol nil 'move)) - (let ((l (- (match-end 0) (match-beginning 0) 1))) - (cond ((< l level) (setq count 0)) - ((and (= l level) - (or invisible-ok - (progn - (goto-char (line-beginning-position)) - (not (outline-invisible-p))))) - (setq count (1- count)) - (when (eq l level) - (setq result (point))))))) - (goto-char result)) - (beginning-of-line 1))) + (let ((backward? (and arg (< arg 0)))) + (if (org-before-first-heading-p) + (if backward? (goto-char (point-min)) (outline-next-heading)) + (org-back-to-heading invisible-ok) + (unless backward? (end-of-line)) ;do not match current headline + (let ((level (- (match-end 0) (match-beginning 0) 1)) + (f (if backward? #'re-search-backward #'re-search-forward)) + (count (if arg (abs arg) 1)) + (result (point))) + (while (and (> count 0) + (funcall f org-outline-regexp-bol nil 'move)) + (let ((l (- (match-end 0) (match-beginning 0) 1))) + (cond ((< l level) (setq count 0)) + ((and (= l level) + (or invisible-ok + (not (org-invisible-p + (line-beginning-position))))) + (cl-decf count) + (when (= l level) (setq result (point))))))) + (goto-char result)) + (beginning-of-line)))) (defun org-backward-heading-same-level (arg &optional invisible-ok) "Move backward to the ARG'th subheading at same level as this one. @@ -23382,20 +24065,64 @@ Stop at the first and last subheadings of a superior heading." (interactive "p") (org-forward-heading-same-level (if arg (- arg) -1) invisible-ok)) +(defun org-next-visible-heading (arg) + "Move to the next visible heading. + +This function wraps `outline-next-visible-heading' with +`org-with-limited-levels' in order to skip over inline tasks and +respect customization of `org-odd-levels-only'." + (interactive "p") + (org-with-limited-levels + (outline-next-visible-heading arg))) + +(defun org-previous-visible-heading (arg) + "Move to the previous visible heading. + +This function wraps `outline-previous-visible-heading' with +`org-with-limited-levels' in order to skip over inline tasks and +respect customization of `org-odd-levels-only'." + (interactive "p") + (org-with-limited-levels + (outline-previous-visible-heading arg))) + (defun org-next-block (arg &optional backward block-regexp) "Jump to the next block. -With a prefix argument ARG, jump forward ARG many source blocks. + +With a prefix argument ARG, jump forward ARG many blocks. + When BACKWARD is non-nil, jump to the previous block. -When BLOCK-REGEXP is non-nil, use this regexp to find blocks." + +When BLOCK-REGEXP is non-nil, use this regexp to find blocks. +Match data is set according to this regexp when the function +returns. + +Return point at beginning of the opening line of found block. +Throw an error if no block is found." (interactive "p") - (let ((re (or block-regexp org-block-regexp)) - (re-search-fn (or (and backward 're-search-backward) - 're-search-forward))) - (if (looking-at re) (forward-char 1)) - (condition-case nil - (funcall re-search-fn re nil nil arg) - (error (error "No %s code blocks" (if backward "previous" "further" )))) - (goto-char (match-beginning 0)) (org-show-context))) + (let ((re (or block-regexp "^[ \t]*#\\+BEGIN")) + (case-fold-search t) + (search-fn (if backward #'re-search-backward #'re-search-forward)) + (count (or arg 1)) + (origin (point)) + last-element) + (if backward (beginning-of-line) (end-of-line)) + (while (and (> count 0) (funcall search-fn re nil t)) + (let ((element (save-excursion + (goto-char (match-beginning 0)) + (save-match-data (org-element-at-point))))) + (when (and (memq (org-element-type element) + '(center-block comment-block dynamic-block + example-block export-block quote-block + special-block src-block verse-block)) + (<= (match-beginning 0) + (org-element-property :post-affiliated element))) + (setq last-element element) + (cl-decf count)))) + (if (= count 0) + (prog1 (goto-char (org-element-property :post-affiliated last-element)) + (save-match-data (org-show-context))) + (goto-char origin) + (user-error "No %s code blocks" (if backward "previous" "further"))))) (defun org-previous-block (arg &optional block-regexp) "Jump to the previous block. @@ -23418,74 +24145,74 @@ item, etc. It also provides some special moves for convenience: - On a table or a property drawer, jump after it. - On a verse or source block, stop after blank lines." (interactive) - (when (eobp) (user-error "Cannot move further down")) - (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. - ((and post-affiliated (< (point) post-affiliated)) - (goto-char post-affiliated)) - ;; At a table row, move to the end of the table. Similarly, - ;; at a node property, move to the end of the property - ;; 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. - ((outline-invisible-p (line-end-position)) - (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. - (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))))) + (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. @@ -23498,57 +24225,62 @@ convenience: - On an affiliated keyword, jump to the first one. - On a table or a property drawer, move to its beginning. - - On a verse or source block, stop before blank lines." + - On comment, example, export, src and verse blocks, stop + before blank lines." (interactive) - (when (bobp) (user-error "Cannot move further up")) - (let* ((deactivate-mark nil) - (element (org-element-at-point)) - (type (org-element-type element)) - (contents-begin (org-element-property :contents-begin element)) - (contents-end (org-element-property :contents-end element)) - (post-affiliated (org-element-property :post-affiliated element)) - (begin (org-element-property :begin element))) - (cond - ((not element) (goto-char (point-min))) - ((= (point) begin) - (backward-char) - (org-backward-paragraph)) - ((and post-affiliated (<= (point) post-affiliated)) (goto-char begin)) - ((memq type '(node-property table-row)) - (goto-char (org-element-property - :post-affiliated (org-element-property :parent element)))) - ((memq type '(property-drawer table)) (goto-char begin)) - ((memq type '(src-block verse-block)) - (when (eq type 'src-block) - (setq contents-begin - (save-excursion (goto-char begin) (forward-line) (point)))) - (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)))))) - ((not contents-begin) (goto-char (or post-affiliated begin))) - ((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. - ((>= (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 (outline-invisible-p (point)) (beginning-of-visual-line)))) + (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))))) (defun org-forward-element () "Move forward by one element. @@ -23587,18 +24319,21 @@ Move to the previous element at the same level, when possible." (progn (goto-char origin) (user-error "Cannot move further up")))))) (t - (let* ((trail (org-element-at-point 'keep-trail)) - (elem (car trail)) - (prev-elem (nth 1 trail)) + (let* ((elem (org-element-at-point)) (beg (org-element-property :begin elem))) (cond ;; Move to beginning of current element if point isn't ;; there already. ((null beg) (message "No element at point")) ((/= (point) beg) (goto-char beg)) - (prev-elem (goto-char (org-element-property :begin prev-elem))) - ((org-before-first-heading-p) (goto-char (point-min))) - (t (org-back-to-heading))))))) + (t (goto-char beg) + (skip-chars-backward " \r\t\n") + (unless (bobp) + (let ((prev (org-element-at-point))) + (goto-char (org-element-property :begin prev)) + (while (and (setq prev (org-element-property :parent prev)) + (<= (org-element-property :end prev) beg)) + (goto-char (org-element-property :begin prev))))))))))) (defun org-up-element () "Move to upper element." @@ -23612,7 +24347,6 @@ Move to the previous element at the same level, when possible." (user-error "No surrounding element") (org-with-limited-levels (org-back-to-heading))))))) -(defvar org-element-greater-elements) (defun org-down-element () "Move to inner element." (interactive) @@ -23623,7 +24357,7 @@ Move to the previous element at the same level, when possible." (forward-char)) ((memq (org-element-type element) org-element-greater-elements) ;; If contents are hidden, first disclose them. - (when (org-element-property :hiddenp element) (org-cycle)) + (when (org-invisible-p (line-end-position)) (org-cycle)) (goto-char (or (org-element-property :contents-begin element) (user-error "No content for this element")))) (t (user-error "No inner element"))))) @@ -23631,24 +24365,41 @@ Move to the previous element at the same level, when possible." (defun org-drag-element-backward () "Move backward element at point." (interactive) - (if (org-with-limited-levels (org-at-heading-p)) (org-move-subtree-up) - (let* ((trail (org-element-at-point 'keep-trail)) - (elem (car trail)) - (prev-elem (nth 1 trail))) - ;; Error out if no previous element or previous element is - ;; a parent of the current one. - (if (or (not prev-elem) (org-element-nested-p elem prev-elem)) - (user-error "Cannot drag element backward") - (let ((pos (point))) - (org-element-swap-A-B prev-elem elem) - (goto-char (+ (org-element-property :begin prev-elem) - (- pos (org-element-property :begin elem))))))))) + (let ((elem (or (org-element-at-point) + (user-error "No element at point")))) + (if (eq (org-element-type elem) 'headline) + ;; Preserve point when moving a whole tree, even if point was + ;; on blank lines below the headline. + (let ((offset (skip-chars-backward " \t\n"))) + (unwind-protect (org-move-subtree-up) + (forward-char (- offset)))) + (let ((prev-elem + (save-excursion + (goto-char (org-element-property :begin elem)) + (skip-chars-backward " \r\t\n") + (unless (bobp) + (let* ((beg (org-element-property :begin elem)) + (prev (org-element-at-point)) + (up prev)) + (while (and (setq up (org-element-property :parent up)) + (<= (org-element-property :end up) beg)) + (setq prev up)) + prev))))) + ;; Error out if no previous element or previous element is + ;; a parent of the current one. + (if (or (not prev-elem) (org-element-nested-p elem prev-elem)) + (user-error "Cannot drag element backward") + (let ((pos (point))) + (org-element-swap-A-B prev-elem elem) + (goto-char (+ (org-element-property :begin prev-elem) + (- pos (org-element-property :begin elem)))))))))) (defun org-drag-element-forward () "Move forward element at point." (interactive) (let* ((pos (point)) - (elem (org-element-at-point))) + (elem (or (org-element-at-point) + (user-error "No element at point")))) (when (= (point-max) (org-element-property :end elem)) (user-error "Cannot drag element forward")) (goto-char (org-element-property :end elem)) @@ -23681,7 +24432,7 @@ Move to the previous element at the same level, when possible." (defun org-drag-line-forward (arg) "Drag the line at point ARG lines forward." (interactive "p") - (dotimes (n (abs arg)) + (dotimes (_ (abs arg)) (let ((c (current-column))) (if (< 0 arg) (progn @@ -23705,7 +24456,7 @@ mode) if the mark is active, it marks the next element after the ones already marked." (interactive) (let (deactivate-mark) - (if (and (org-called-interactively-p 'any) + (if (and (called-interactively-p 'any) (or (and (eq last-command this-command) (mark t)) (and transient-mark-mode mark-active))) (set-mark @@ -23751,13 +24502,10 @@ modified." (interactive) (unless (eq major-mode 'org-mode) (user-error "Cannot un-indent a buffer not in Org mode")) - (let* ((parse-tree (org-element-parse-buffer 'greater-element)) - unindent-tree ; For byte-compiler. - (unindent-tree - (function - (lambda (contents) - (mapc - (lambda (element) + (letrec ((parse-tree (org-element-parse-buffer 'greater-element)) + (unindent-tree + (lambda (contents) + (dolist (element (reverse contents)) (if (memq (org-element-type element) '(headline section)) (funcall unindent-tree (org-element-contents element)) (save-excursion @@ -23765,10 +24513,49 @@ modified." (narrow-to-region (org-element-property :begin element) (org-element-property :end element)) - (org-do-remove-indentation))))) - (reverse contents)))))) + (org-do-remove-indentation)))))))) (funcall unindent-tree (org-element-contents parse-tree)))) +(defun org-show-children (&optional level) + "Show all direct subheadings of this heading. +Prefix arg LEVEL is how many levels below the current level +should be shown. Default is enough to cause the following +heading to appear." + (interactive "p") + ;; If `orgstruct-mode' is active, use the slower version. + (if orgstruct-mode (call-interactively #'outline-show-children) + (save-excursion + (org-back-to-heading t) + (let* ((current-level (funcall outline-level)) + (max-level (org-get-valid-level + current-level + (if level (prefix-numeric-value level) 1))) + (end (save-excursion (org-end-of-subtree t t))) + (regexp-fmt "^\\*\\{%d,%s\\}\\(?: \\|$\\)") + (past-first-child nil) + ;; Make sure to skip inlinetasks. + (re (format regexp-fmt + current-level + (cond + ((not (featurep 'org-inlinetask)) "") + (org-odd-levels-only (- (* 2 org-inlinetask-min-level) + 3)) + (t (1- org-inlinetask-min-level)))))) + ;; Display parent heading. + (outline-flag-region (line-end-position 0) (line-end-position) nil) + (forward-line) + ;; Display children. First child may be deeper than expected + ;; MAX-LEVEL. Since we want to display it anyway, adjust + ;; MAX-LEVEL accordingly. + (while (re-search-forward re end t) + (unless past-first-child + (setq re (format regexp-fmt + current-level + (max (funcall outline-level) max-level))) + (setq past-first-child t)) + (outline-flag-region + (line-end-position 0) (line-end-position) nil)))))) + (defun org-show-subtree () "Show everything after this heading at deeper levels." (interactive) @@ -23783,58 +24570,33 @@ modified." Show the heading too, if it is currently invisible." (interactive) (save-excursion - (condition-case nil - (progn - (org-back-to-heading t) - (outline-flag-region - (max (point-min) (1- (point))) - (save-excursion - (if (re-search-forward - (concat "[\r\n]\\(" org-outline-regexp "\\)") nil t) - (match-beginning 1) - (point-max))) - nil) - (org-cycle-hide-drawers 'children)) - (error nil)))) + (ignore-errors + (org-back-to-heading t) + (outline-flag-region + (max (point-min) (1- (point))) + (save-excursion + (if (re-search-forward + (concat "[\r\n]\\(" org-outline-regexp "\\)") nil t) + (match-beginning 1) + (point-max))) + nil) + (org-cycle-hide-drawers 'children)))) (defun org-make-options-regexp (kwds &optional extra) - "Make a regular expression for keyword lines." - (concat - "^#\\+\\(" - (mapconcat 'regexp-quote kwds "\\|") - (if extra (concat "\\|" extra)) - "\\):[ \t]*\\(.*\\)")) - -;; Make isearch reveal the necessary context -(defun org-isearch-end () - "Reveal context after isearch exits." - (when isearch-success ; only if search was successful - (if (featurep 'xemacs) - ;; Under XEmacs, the hook is run in the correct place, - ;; we directly show the context. - (org-show-context 'isearch) - ;; In Emacs the hook runs *before* restoring the overlays. - ;; So we have to use a one-time post-command-hook to do this. - ;; (Emacs 22 has a special variable, see function `org-mode') - (unless (and (boundp 'isearch-mode-end-hook-quit) - isearch-mode-end-hook-quit) - ;; Only when the isearch was not quitted. - (org-add-hook 'post-command-hook 'org-isearch-post-command - 'append 'local))))) - -(defun org-isearch-post-command () - "Remove self from hook, and show context." - (remove-hook 'post-command-hook 'org-isearch-post-command 'local) - (org-show-context 'isearch)) - + "Make a regular expression for keyword lines. +KWDS is a list of keywords, as strings. Optional argument EXTRA, +when non-nil, is a regexp matching keywords names." + (concat "^[ \t]*#\\+\\(" + (regexp-opt kwds) + (and extra (concat (and kwds "\\|") extra)) + "\\):[ \t]*\\(.*\\)")) ;;;; Integration with and fixes for other packages ;;; Imenu support -(defvar org-imenu-markers nil +(defvar-local org-imenu-markers nil "All markers currently used by Imenu.") -(make-variable-buffer-local 'org-imenu-markers) (defun org-imenu-new-marker (&optional pos) "Return a new marker for use by Imenu, and remember the marker." @@ -23845,50 +24607,48 @@ Show the heading too, if it is currently invisible." (defun org-imenu-get-tree () "Produce the index for Imenu." - (mapc (lambda (x) (move-marker x nil)) org-imenu-markers) + (dolist (x org-imenu-markers) (move-marker x nil)) (setq org-imenu-markers nil) - (let* ((n org-imenu-depth) + (let* ((case-fold-search nil) + (n org-imenu-depth) (re (concat "^" (org-get-limited-outline-regexp))) (subs (make-vector (1+ n) nil)) (last-level 0) m level head0 head) - (save-excursion - (save-restriction - (widen) - (goto-char (point-max)) - (while (re-search-backward re nil t) - (setq level (org-reduced-level (funcall outline-level))) - (when (and (<= level n) - (looking-at org-complex-heading-regexp) - (setq head0 (org-match-string-no-properties 4))) - (setq head (org-link-display-format head0) - m (org-imenu-new-marker)) - (org-add-props head nil 'org-imenu-marker m 'org-imenu t) - (if (>= level last-level) - (push (cons head m) (aref subs level)) - (push (cons head (aref subs (1+ level))) (aref subs level)) - (loop for i from (1+ level) to n do (aset subs i nil))) - (setq last-level level))))) + (org-with-wide-buffer + (goto-char (point-max)) + (while (re-search-backward re nil t) + (setq level (org-reduced-level (funcall outline-level))) + (when (and (<= level n) + (looking-at org-complex-heading-regexp) + (setq head0 (match-string-no-properties 4))) + (setq head (org-link-display-format head0) + m (org-imenu-new-marker)) + (org-add-props head nil 'org-imenu-marker m 'org-imenu t) + (if (>= level last-level) + (push (cons head m) (aref subs level)) + (push (cons head (aref subs (1+ level))) (aref subs level)) + (cl-loop for i from (1+ level) to n do (aset subs i nil))) + (setq last-level level)))) (aref subs 1))) (eval-after-load "imenu" '(progn (add-hook 'imenu-after-jump-hook (lambda () - (if (derived-mode-p 'org-mode) - (org-show-context 'org-goto)))))) + (when (derived-mode-p 'org-mode) + (org-show-context 'org-goto)))))) -(defun org-link-display-format (link) - "Replace a link with its the description. +(defun org-link-display-format (s) + "Replace links in string S with their description. If there is no description, use the link target." (save-match-data - (if (string-match org-bracket-link-analytic-regexp link) - (replace-match (if (match-end 5) - (match-string 5 link) - (concat (match-string 1 link) - (match-string 3 link))) - nil t link) - link))) + (replace-regexp-in-string + org-bracket-link-analytic-regexp + (lambda (m) + (if (match-end 5) (match-string 5 m) + (concat (match-string 1 m) (match-string 3 m)))) + s nil t))) (defun org-toggle-link-display () "Toggle the literal or descriptive display of links." @@ -23909,11 +24669,11 @@ If there is no description, use the link target." 'face 'org-agenda-restriction-lock) (overlay-put org-speedbar-restriction-lock-overlay 'help-echo "Agendas are currently limited to this item.") -(org-detach-overlay org-speedbar-restriction-lock-overlay) +(delete-overlay org-speedbar-restriction-lock-overlay) (defun org-speedbar-set-agenda-restriction () "Restrict future agenda commands to the location at point in speedbar. -To get rid of the restriction, use \\[org-agenda-remove-restriction-lock]." +To get rid of the restriction, use `\\[org-agenda-remove-restriction-lock]'." (interactive) (require 'org-agenda) (let (p m tp np dir txt) @@ -23937,9 +24697,9 @@ To get rid of the restriction, use \\[org-agenda-remove-restriction-lock]." (let ((default-directory dir)) (expand-file-name txt))) (unless (derived-mode-p 'org-mode) - (user-error "Cannot restrict to non-Org-mode file")) + (user-error "Cannot restrict to non-Org mode file")) (org-agenda-set-restriction-lock 'file))) - (t (user-error "Don't know how to restrict Org-mode's agenda"))) + (t (user-error "Don't know how to restrict Org mode agenda"))) (move-overlay org-speedbar-restriction-lock-overlay (point-at-bol) (point-at-eol)) (setq current-prefix-arg nil) @@ -23959,34 +24719,98 @@ To get rid of the restriction, use \\[org-agenda-remove-restriction-lock]." ;;; Fixes and Hacks for problems with other packages -;; Make flyspell not check words in links, to not mess up our keymap -(defvar org-element-affiliated-keywords) ; From org-element.el -(defvar org-element-block-name-alist) ; From org-element.el +(defun org--flyspell-object-check-p (element) + "Non-nil when Flyspell can check object at point. +ELEMENT is the element at point." + (let ((object (save-excursion + (when (looking-at-p "\\>") (backward-char)) + (org-element-context element)))) + (cl-case (org-element-type object) + ;; Prevent checks in links due to keybinding conflict with + ;; Flyspell. + ((code entity export-snippet inline-babel-call + inline-src-block line-break latex-fragment link macro + statistics-cookie target timestamp verbatim) + nil) + (footnote-reference + ;; Only in inline footnotes, within the definition. + (and (eq (org-element-property :type object) 'inline) + (< (save-excursion + (goto-char (org-element-property :begin object)) + (search-forward ":" nil t 2)) + (point)))) + (otherwise t)))) + (defun org-mode-flyspell-verify () - "Don't let flyspell put overlays at active buttons, or on - {todo,all-time,additional-option-like}-keywords." - (require 'org-element) ; For `org-element-affiliated-keywords' - (let ((pos (max (1- (point)) (point-min))) - (word (thing-at-point 'word))) - (and (not (get-text-property pos 'keymap)) - (not (get-text-property pos 'org-no-flyspell)) - (not (member word org-todo-keywords-1)) - (not (member word org-all-time-keywords)) - (not (member word org-options-keywords)) - (not (member word (mapcar 'car org-startup-options))) - (not (member-ignore-case word org-element-affiliated-keywords)) - (not (member-ignore-case word (org-get-export-keywords))) - (not (member-ignore-case - word (mapcar 'car org-element-block-name-alist))) - (not (member-ignore-case word '("BEGIN" "END" "ATTR"))) - (not (org-in-src-block-p))))) + "Function used for `flyspell-generic-check-word-predicate'." + (if (org-at-heading-p) + ;; At a headline or an inlinetask, check title only. This is + ;; faster than relying on `org-element-at-point'. + (and (save-excursion (beginning-of-line) + (and (let ((case-fold-search t)) + (not (looking-at-p "\\*+ END[ \t]*$"))) + (let ((case-fold-search nil)) + (looking-at org-complex-heading-regexp)))) + (match-beginning 4) + (>= (point) (match-beginning 4)) + (or (not (match-beginning 5)) + (< (point) (match-beginning 5)))) + (let* ((element (org-element-at-point)) + (post-affiliated (org-element-property :post-affiliated element))) + (cond + ;; Ignore checks in all affiliated keywords but captions. + ((< (point) post-affiliated) + (and (save-excursion + (beginning-of-line) + (let ((case-fold-search t)) (looking-at "[ \t]*#\\+CAPTION:"))) + (> (point) (match-end 0)) + (org--flyspell-object-check-p element))) + ;; Ignore checks in LOGBOOK (or equivalent) drawer. + ((let ((log (org-log-into-drawer))) + (and log + (let ((drawer (org-element-lineage element '(drawer)))) + (and drawer + (eq (compare-strings + log nil nil + (org-element-property :drawer-name drawer) nil nil t) + t))))) + nil) + (t + (cl-case (org-element-type element) + ((comment quote-section) t) + (comment-block + ;; Allow checks between block markers, not on them. + (and (> (line-beginning-position) post-affiliated) + (save-excursion + (end-of-line) + (skip-chars-forward " \r\t\n") + (< (point) (org-element-property :end element))))) + ;; Arbitrary list of keywords where checks are meaningful. + ;; Make sure point is on the value part of the element. + (keyword + (and (member (org-element-property :key element) + '("DESCRIPTION" "TITLE")) + (save-excursion + (search-backward ":" (line-beginning-position) t)))) + ;; Check is globally allowed in paragraphs verse blocks and + ;; table rows (after affiliated keywords) but some objects + ;; must not be affected. + ((paragraph table-row verse-block) + (let ((cbeg (org-element-property :contents-begin element)) + (cend (org-element-property :contents-end element))) + (and cbeg (>= (point) cbeg) (< (point) cend) + (org--flyspell-object-check-p element)))))))))) +(put 'org-mode 'flyspell-mode-predicate 'org-mode-flyspell-verify) (defun org-remove-flyspell-overlays-in (beg end) "Remove flyspell overlays in region." - (and (org-bound-and-true-p flyspell-mode) + (and (bound-and-true-p flyspell-mode) (fboundp 'flyspell-delete-region-overlays) - (flyspell-delete-region-overlays beg end)) - (add-text-properties beg end '(org-no-flyspell t))) + (flyspell-delete-region-overlays beg end))) + +(defvar flyspell-delayed-commands) +(eval-after-load "flyspell" + '(add-to-list 'flyspell-delayed-commands 'org-self-insert-command)) ;; Make `bookmark-jump' shows the jump location if it was hidden. (eval-after-load "bookmark" @@ -24008,17 +24832,38 @@ To get rid of the restriction, use \\[org-agenda-remove-restriction-lock]." (eval-after-load "ecb" '(defadvice ecb-method-clicked (after esf/org-show-context activate) "Make hierarchy visible when jumping into location from ECB tree buffer." - (if (derived-mode-p 'org-mode) - (org-show-context)))) + (when (derived-mode-p 'org-mode) + (org-show-context)))) (defun org-bookmark-jump-unhide () "Unhide the current position, to show the bookmark location." (and (derived-mode-p 'org-mode) - (or (outline-invisible-p) + (or (org-invisible-p) (save-excursion (goto-char (max (point-min) (1- (point)))) - (outline-invisible-p))) + (org-invisible-p))) (org-show-context 'bookmark-jump))) +(defun org-mark-jump-unhide () + "Make the point visible with `org-show-context' after jumping to the mark." + (when (and (derived-mode-p 'org-mode) + (org-invisible-p)) + (org-show-context 'mark-goto))) + +(eval-after-load "simple" + '(defadvice pop-to-mark-command (after org-make-visible activate) + "Make the point visible with `org-show-context'." + (org-mark-jump-unhide))) + +(eval-after-load "simple" + '(defadvice exchange-point-and-mark (after org-make-visible activate) + "Make the point visible with `org-show-context'." + (org-mark-jump-unhide))) + +(eval-after-load "simple" + '(defadvice pop-global-mark (after org-make-visible activate) + "Make the point visible with `org-show-context'." + (org-mark-jump-unhide))) + ;; Make session.el ignore our circular variable (defvar session-globals-exclude) (eval-after-load "session" diff --git a/lisp/org/ox-ascii.el b/lisp/org/ox-ascii.el index 6ba70d700b2..e83eb197a82 100644 --- a/lisp/org/ox-ascii.el +++ b/lisp/org/ox-ascii.el @@ -1,4 +1,4 @@ -;;; ox-ascii.el --- ASCII Back-End for Org Export Engine +;;; ox-ascii.el --- ASCII Back-End for Org Export Engine -*- lexical-binding: t; -*- ;; Copyright (C) 2012-2017 Free Software Foundation, Inc. @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -27,9 +27,9 @@ ;;; Code: -(eval-when-compile (require 'cl)) (require 'ox) (require 'ox-publish) +(require 'cl-lib) (declare-function aa2u "ext:ascii-art-to-unicode" ()) @@ -49,8 +49,6 @@ (center-block . org-ascii-center-block) (clock . org-ascii-clock) (code . org-ascii-code) - (comment . (lambda (&rest args) "")) - (comment-block . (lambda (&rest args) "")) (drawer . org-ascii-drawer) (dynamic-block . org-ascii-dynamic-block) (entity . org-ascii-entity) @@ -71,12 +69,13 @@ (latex-fragment . org-ascii-latex-fragment) (line-break . org-ascii-line-break) (link . org-ascii-link) + (node-property . org-ascii-node-property) (paragraph . org-ascii-paragraph) (plain-list . org-ascii-plain-list) (plain-text . org-ascii-plain-text) (planning . org-ascii-planning) + (property-drawer . org-ascii-property-drawer) (quote-block . org-ascii-quote-block) - (quote-section . org-ascii-quote-section) (radio-target . org-ascii-radio-target) (section . org-ascii-section) (special-block . org-ascii-special-block) @@ -94,7 +93,6 @@ (underline . org-ascii-underline) (verbatim . org-ascii-verbatim) (verse-block . org-ascii-verse-block)) - :export-block "ASCII" :menu-entry '(?t "Export to Plain Text" ((?A "As ASCII buffer" @@ -119,7 +117,30 @@ (:filter-parse-tree org-ascii-filter-paragraph-spacing org-ascii-filter-comment-spacing) (:filter-section . org-ascii-filter-headline-blank-lines)) - :options-alist '((:ascii-charset nil nil org-ascii-charset))) + :options-alist + '((:subtitle "SUBTITLE" nil nil parse) + (:ascii-bullets nil nil org-ascii-bullets) + (:ascii-caption-above nil nil org-ascii-caption-above) + (:ascii-charset nil nil org-ascii-charset) + (:ascii-global-margin nil nil org-ascii-global-margin) + (:ascii-format-drawer-function nil nil org-ascii-format-drawer-function) + (:ascii-format-inlinetask-function + nil nil org-ascii-format-inlinetask-function) + (:ascii-headline-spacing nil nil org-ascii-headline-spacing) + (:ascii-indented-line-width nil nil org-ascii-indented-line-width) + (:ascii-inlinetask-width nil nil org-ascii-inlinetask-width) + (:ascii-inner-margin nil nil org-ascii-inner-margin) + (:ascii-links-to-notes nil nil org-ascii-links-to-notes) + (:ascii-list-margin nil nil org-ascii-list-margin) + (:ascii-paragraph-spacing nil nil org-ascii-paragraph-spacing) + (:ascii-quote-margin nil nil org-ascii-quote-margin) + (:ascii-table-keep-all-vertical-lines + nil nil org-ascii-table-keep-all-vertical-lines) + (:ascii-table-use-ascii-art nil nil org-ascii-table-use-ascii-art) + (:ascii-table-widen-columns nil nil org-ascii-table-widen-columns) + (:ascii-text-width nil nil org-ascii-text-width) + (:ascii-underline nil nil org-ascii-underline) + (:ascii-verbatim-format nil nil org-ascii-verbatim-format))) @@ -156,12 +177,22 @@ Inner margin is applied between each headline." (defcustom org-ascii-quote-margin 6 "Width of margin used for quoting text, in characters. -This margin is applied on both sides of the text." +This margin is applied on both sides of the text. It is also +applied on the left side of contents in descriptive lists." :group 'org-export-ascii :version "24.4" :package-version '(Org . "8.0") :type 'integer) +(defcustom org-ascii-list-margin 0 + "Width of margin used for plain lists, in characters. +This margin applies to top level list only, not to its +sub-lists." + :group 'org-export-ascii + :version "26.1" + :package-version '(Org . "8.3") + :type 'integer) + (defcustom org-ascii-inlinetask-width 30 "Width of inline tasks, in number of characters. This number ignores any margin." @@ -311,13 +342,10 @@ Org mode, i.e. with \"=>\" as ellipsis." :type 'boolean) (defcustom org-ascii-table-use-ascii-art nil - "Non-nil means table.el tables are turned into ascii-art. - + "Non-nil means \"table.el\" tables are turned into ASCII art. It only makes sense when export charset is `utf-8'. It is nil by -default since it requires ascii-art-to-unicode.el package. You -can download it here: - - http://gnuvola.org/software/j/aa2u/ascii-art-to-unicode.el." +default since it requires \"ascii-art-to-unicode.el\" package, +available through, e.g., GNU ELPA." :group 'org-export-ascii :version "24.4" :package-version '(Org . "8.0") @@ -339,7 +367,7 @@ Otherwise, place it right after it." :type 'string) (defcustom org-ascii-format-drawer-function - (lambda (name contents width) contents) + (lambda (_name contents _width) contents) "Function called to format a drawer in ASCII. The function must accept three parameters: @@ -374,7 +402,7 @@ The function must accept nine parameters: The function should return either the string to be exported or nil to ignore the inline task." :group 'org-export-ascii - :version "24.4" + :version "26.1" :package-version '(Org . "8.3") :type 'function) @@ -384,14 +412,18 @@ nil to ignore the inline task." ;; Internal functions fall into three categories. -;; The first one is about text formatting. The core function is -;; `org-ascii--current-text-width', which determines the current -;; text width allowed to a given element. In other words, it helps -;; keeping each line width within maximum text width defined in -;; `org-ascii-text-width'. Once this information is known, -;; `org-ascii--fill-string', `org-ascii--justify-string', -;; `org-ascii--box-string' and `org-ascii--indent-string' can -;; operate on a given output string. +;; The first one is about text formatting. The core functions are +;; `org-ascii--current-text-width' and +;; `org-ascii--current-justification', which determine, respectively, +;; the current text width allowed to a given element and its expected +;; justification. Once this information is known, +;; `org-ascii--fill-string', `org-ascii--justify-lines', +;; `org-ascii--justify-element' `org-ascii--box-string' and +;; `org-ascii--indent-string' can operate on a given output string. +;; In particular, justification happens at the regular (i.e., +;; non-greater) element level, which means that when the exporting +;; process reaches a container (e.g., a center block) content are +;; already justified. ;; The second category contains functions handling elements listings, ;; triggered by "#+TOC:" keyword. As such, `org-ascii--build-toc' @@ -420,7 +452,8 @@ a communication channel. Optional argument JUSTIFY can specify any type of justification among `left', `center', `right' or `full'. A nil value is equivalent to `left'. For a justification that doesn't also fill -string, see `org-ascii--justify-string'. +string, see `org-ascii--justify-lines' and +`org-ascii--justify-block'. Return nil if S isn't a string." (when (stringp s) @@ -435,8 +468,8 @@ Return nil if S isn't a string." (fill-region (point-min) (point-max) justify)) (buffer-string))))) -(defun org-ascii--justify-string (s text-width how) - "Justify string S. +(defun org-ascii--justify-lines (s text-width how) + "Justify all lines in string S. TEXT-WIDTH is an integer specifying maximum length of a line. HOW determines the type of justification: it can be `left', `right', `full' or `center'." @@ -452,6 +485,48 @@ HOW determines the type of justification: it can be `left', (forward-line))) (buffer-string))) +(defun org-ascii--justify-element (contents element info) + "Justify CONTENTS of ELEMENT. +INFO is a plist used as a communication channel. Justification +is done according to the type of element. More accurately, +paragraphs are filled and other elements are justified as blocks, +that is according to the widest non blank line in CONTENTS." + (if (not (org-string-nw-p contents)) contents + (let ((text-width (org-ascii--current-text-width element info)) + (how (org-ascii--current-justification element))) + (cond + ((eq (org-element-type element) 'paragraph) + ;; Paragraphs are treated specially as they need to be filled. + (org-ascii--fill-string contents text-width info how)) + ((eq how 'left) contents) + (t (with-temp-buffer + (insert contents) + (goto-char (point-min)) + (catch 'exit + (let ((max-width 0)) + ;; Compute maximum width. Bail out if it is greater + ;; than page width, since no justification is + ;; possible. + (save-excursion + (while (not (eobp)) + (unless (looking-at-p "[ \t]*$") + (end-of-line) + (let ((column (current-column))) + (cond + ((>= column text-width) (throw 'exit contents)) + ((> column max-width) (setq max-width column))))) + (forward-line))) + ;; Justify every line according to TEXT-WIDTH and + ;; MAX-WIDTH. + (let ((offset (/ (- text-width max-width) + (if (eq how 'right) 1 2)))) + (if (zerop offset) (throw 'exit contents) + (while (not (eobp)) + (unless (looking-at-p "[ \t]*$") + (indent-to-column offset)) + (forward-line))))) + (buffer-string)))))))) + (defun org-ascii--indent-string (s width) "Indent string S by WIDTH white spaces. Empty lines are not indented." @@ -472,71 +547,89 @@ INFO is a plist used as a communication channel." (defun org-ascii--current-text-width (element info) "Return maximum text width for ELEMENT's contents. INFO is a plist used as a communication channel." - (case (org-element-type element) + (pcase (org-element-type element) ;; Elements with an absolute width: `headline' and `inlinetask'. - (inlinetask org-ascii-inlinetask-width) - (headline - (- org-ascii-text-width - (let ((low-level-rank (org-export-low-level-p element info))) - (if low-level-rank (* low-level-rank 2) org-ascii-global-margin)))) + (`inlinetask (plist-get info :ascii-inlinetask-width)) + (`headline + (- (plist-get info :ascii-text-width) + (let ((low-level-rank (org-export-low-level-p element info))) + (if low-level-rank (* low-level-rank 2) + (plist-get info :ascii-global-margin))))) ;; Elements with a relative width: store maximum text width in ;; TOTAL-WIDTH. - (otherwise - (let* ((genealogy (cons element (org-export-get-genealogy element))) - ;; Total width is determined by the presence, or not, of an - ;; inline task among ELEMENT parents. - (total-width - (if (loop for parent in genealogy - thereis (eq (org-element-type parent) 'inlinetask)) - org-ascii-inlinetask-width - ;; No inlinetask: Remove global margin from text width. - (- org-ascii-text-width - org-ascii-global-margin - (let ((parent (org-export-get-parent-headline element))) - ;; Inner margin doesn't apply to text before first - ;; headline. - (if (not parent) 0 - (let ((low-level-rank - (org-export-low-level-p parent info))) - ;; Inner margin doesn't apply to contents of - ;; low level headlines, since they've got their - ;; own indentation mechanism. - (if low-level-rank (* low-level-rank 2) - org-ascii-inner-margin)))))))) + (_ + (let* ((genealogy (org-element-lineage element nil t)) + ;; Total width is determined by the presence, or not, of an + ;; inline task among ELEMENT parents. + (total-width + (if (cl-some (lambda (parent) + (eq (org-element-type parent) 'inlinetask)) + genealogy) + (plist-get info :ascii-inlinetask-width) + ;; No inlinetask: Remove global margin from text width. + (- (plist-get info :ascii-text-width) + (plist-get info :ascii-global-margin) + (let ((parent (org-export-get-parent-headline element))) + ;; Inner margin doesn't apply to text before first + ;; headline. + (if (not parent) 0 + (let ((low-level-rank + (org-export-low-level-p parent info))) + ;; Inner margin doesn't apply to contents of + ;; low level headlines, since they've got their + ;; own indentation mechanism. + (if low-level-rank (* low-level-rank 2) + (plist-get info :ascii-inner-margin))))))))) (- total-width - ;; Each `quote-block', `quote-section' and `verse-block' above - ;; narrows text width by twice the standard margin size. - (+ (* (loop for parent in genealogy - when (memq (org-element-type parent) - '(quote-block quote-section verse-block)) - count parent) - 2 org-ascii-quote-margin) - ;; Text width within a plain-list is restricted by - ;; indentation of current item. If that's the case, - ;; compute it with the help of `:structure' property from - ;; parent item, if any. - (let ((parent-item - (if (eq (org-element-type element) 'item) element - (loop for parent in genealogy - when (eq (org-element-type parent) 'item) - return parent)))) - (if (not parent-item) 0 - ;; Compute indentation offset of the current item, - ;; that is the sum of the difference between its - ;; indentation and the indentation of the top item in - ;; the list and current item bullet's length. Also - ;; remove checkbox length, and tag length (for - ;; description lists) or bullet length. - (let ((struct (org-element-property :structure parent-item)) - (beg-item (org-element-property :begin parent-item))) - (+ (- (org-list-get-ind beg-item struct) - (org-list-get-ind - (org-list-get-top-point struct) struct)) - (string-width (or (org-ascii--checkbox parent-item info) - "")) - (string-width - (or (org-list-get-tag beg-item struct) - (org-list-get-bullet beg-item struct))))))))))))) + ;; Each `quote-block' and `verse-block' above narrows text + ;; width by twice the standard margin size. + (+ (* (cl-count-if (lambda (parent) + (memq (org-element-type parent) + '(quote-block verse-block))) + genealogy) + 2 + (plist-get info :ascii-quote-margin)) + ;; Apply list margin once per "top-level" plain-list + ;; containing current line + (* (cl-count-if + (lambda (e) + (and (eq (org-element-type e) 'plain-list) + (not (eq (org-element-type (org-export-get-parent e)) + 'item)))) + genealogy) + (plist-get info :ascii-list-margin)) + ;; Compute indentation offset due to current list. It is + ;; `org-ascii-quote-margin' per descriptive item in the + ;; genealogy, bullet's length otherwise. + (let ((indentation 0)) + (dolist (e genealogy) + (cond + ((not (eq 'item (org-element-type e)))) + ((eq (org-element-property :type (org-export-get-parent e)) + 'descriptive) + (cl-incf indentation org-ascii-quote-margin)) + (t + (cl-incf indentation + (+ (string-width + (or (org-ascii--checkbox e info) "")) + (string-width + (org-element-property :bullet e))))))) + indentation))))))) + +(defun org-ascii--current-justification (element) + "Return expected justification for ELEMENT's contents. +Return value is a symbol among `left', `center', `right' and +`full'." + (let (justification) + (while (and (not justification) + (setq element (org-element-property :parent element))) + (pcase (org-element-type element) + (`center-block (setq justification 'center)) + (`special-block + (let ((name (org-element-property :type element))) + (cond ((string= name "JUSTIFYRIGHT") (setq justification 'right)) + ((string= name "JUSTIFYLEFT") (setq justification 'left))))))) + (or justification 'left))) (defun org-ascii--build-title (element info text-width &optional underline notags toc) @@ -601,14 +694,14 @@ possible. It doesn't apply to `inlinetask' elements." (let ((under-char (nth (1- (org-export-get-relative-level element info)) (cdr (assq (plist-get info :ascii-charset) - org-ascii-underline))))) + (plist-get info :ascii-underline)))))) (and under-char (concat "\n" (make-string (/ (string-width first-part) (char-width under-char)) under-char)))))))) -(defun org-ascii--has-caption-p (element info) +(defun org-ascii--has-caption-p (element _info) "Non-nil when ELEMENT has a caption affiliated keyword. INFO is a plist used as a communication channel. This function is meant to be used as a predicate for `org-export-get-ordinal'." @@ -630,9 +723,9 @@ caption keyword." (org-export-get-ordinal element info nil 'org-ascii--has-caption-p)) (title-fmt (org-ascii--translate - (case (org-element-type element) - (table "Table %d:") - (src-block "Listing %d:")) + (pcase (org-element-type element) + (`table "Table %d:") + (`src-block "Listing %d:")) info))) (org-ascii--fill-string (concat (format title-fmt reference) @@ -640,7 +733,7 @@ caption keyword." (org-export-data caption info)) (org-ascii--current-text-width element info) info))))) -(defun org-ascii--build-toc (info &optional n keyword) +(defun org-ascii--build-toc (info &optional n keyword local) "Return a table of contents. INFO is a plist used as a communication channel. @@ -649,28 +742,34 @@ Optional argument N, when non-nil, is an integer specifying the depth of the table. Optional argument KEYWORD specifies the TOC keyword, if any, from -which the table of contents generation has been initiated." - (let ((title (org-ascii--translate "Table of Contents" info))) - (concat - title "\n" - (make-string (string-width title) - (if (eq (plist-get info :ascii-charset) 'utf-8) ?─ ?_)) - "\n\n" - (let ((text-width - (if keyword (org-ascii--current-text-width keyword info) - (- org-ascii-text-width org-ascii-global-margin)))) - (mapconcat - (lambda (headline) - (let* ((level (org-export-get-relative-level headline info)) - (indent (* (1- level) 3))) - (concat - (unless (zerop indent) (concat (make-string (1- indent) ?.) " ")) - (org-ascii--build-title - headline info (- text-width indent) nil - (or (not (plist-get info :with-tags)) - (eq (plist-get info :with-tags) 'not-in-toc)) - 'toc)))) - (org-export-collect-headlines info n) "\n"))))) +which the table of contents generation has been initiated. + +When optional argument LOCAL is non-nil, build a table of +contents according to the current headline." + (concat + (unless local + (let ((title (org-ascii--translate "Table of Contents" info))) + (concat title "\n" + (make-string + (string-width title) + (if (eq (plist-get info :ascii-charset) 'utf-8) ?─ ?_)) + "\n\n"))) + (let ((text-width + (if keyword (org-ascii--current-text-width keyword info) + (- (plist-get info :ascii-text-width) + (plist-get info :ascii-global-margin))))) + (mapconcat + (lambda (headline) + (let* ((level (org-export-get-relative-level headline info)) + (indent (* (1- level) 3))) + (concat + (unless (zerop indent) (concat (make-string (1- indent) ?.) " ")) + (org-ascii--build-title + headline info (- text-width indent) nil + (or (not (plist-get info :with-tags)) + (eq (plist-get info :with-tags) 'not-in-toc)) + 'toc)))) + (org-export-collect-headlines info n (and local keyword)) "\n")))) (defun org-ascii--list-listings (keyword info) "Return a list of listings. @@ -685,7 +784,8 @@ generation. INFO is a plist used as a communication channel." "\n\n" (let ((text-width (if keyword (org-ascii--current-text-width keyword info) - (- org-ascii-text-width org-ascii-global-margin))) + (- (plist-get info :ascii-text-width) + (plist-get info :ascii-global-margin)))) ;; Use a counter instead of retrieving ordinal of each ;; src-block. (count 0)) @@ -696,7 +796,7 @@ generation. INFO is a plist used as a communication channel." ;; filling (like contents of a description list item). (let* ((initial-text (format (org-ascii--translate "Listing %d:" info) - (incf count))) + (cl-incf count))) (initial-width (string-width initial-text))) (concat initial-text " " @@ -724,7 +824,8 @@ generation. INFO is a plist used as a communication channel." "\n\n" (let ((text-width (if keyword (org-ascii--current-text-width keyword info) - (- org-ascii-text-width org-ascii-global-margin))) + (- (plist-get info :ascii-text-width) + (plist-get info :ascii-global-margin)))) ;; Use a counter instead of retrieving ordinal of each ;; src-block. (count 0)) @@ -735,7 +836,7 @@ generation. INFO is a plist used as a communication channel." ;; filling (like contents of a description list item). (let* ((initial-text (format (org-ascii--translate "Table %d:" info) - (incf count))) + (cl-incf count))) (initial-width (string-width initial-text))) (concat initial-text " " @@ -756,69 +857,106 @@ ELEMENT is either a headline element or a section element. INFO is a plist used as a communication channel." (let* (seen (unique-link-p - (function - ;; Return LINK if it wasn't referenced so far, or nil. - ;; Update SEEN links along the way. - (lambda (link) - (let ((footprint - ;; Normalize description in footprints. - (cons (org-element-property :raw-link link) - (let ((contents (org-element-contents link))) - (and contents - (replace-regexp-in-string - "[ \r\t\n]+" " " - (org-trim - (org-element-interpret-data contents)))))))) - ;; Ignore LINK if it hasn't been translated already. - ;; It can happen if it is located in an affiliated - ;; keyword that was ignored. - (when (and (org-string-nw-p - (gethash link (plist-get info :exported-data))) - (not (member footprint seen))) - (push footprint seen) link))))) - ;; If at a section, find parent headline, if any, in order to - ;; count links that might be in the title. - (headline - (if (eq (org-element-type element) 'headline) element - (or (org-export-get-parent-headline element) element)))) - ;; Get all links in HEADLINE. - (org-element-map headline 'link - (lambda (l) (funcall unique-link-p l)) info nil nil t))) + ;; Return LINK if it wasn't referenced so far, or nil. + ;; Update SEEN links along the way. + (lambda (link) + (let ((footprint + ;; Normalize description in footprints. + (cons (org-element-property :raw-link link) + (let ((contents (org-element-contents link))) + (and contents + (replace-regexp-in-string + "[ \r\t\n]+" " " + (org-trim + (org-element-interpret-data contents)))))))) + ;; Ignore LINK if it hasn't been translated already. It + ;; can happen if it is located in an affiliated keyword + ;; that was ignored. + (when (and (org-string-nw-p + (gethash link (plist-get info :exported-data))) + (not (member footprint seen))) + (push footprint seen) link))))) + (org-element-map (if (eq (org-element-type element) 'section) + element + ;; In a headline, only retrieve links in title + ;; and relative section, not in children. + (list (org-element-property :title element) + (car (org-element-contents element)))) + 'link unique-link-p info nil 'headline t))) + +(defun org-ascii--describe-datum (datum info) + "Describe DATUM object or element. +If DATUM is a string, consider it to be a file name, per +`org-export-resolve-id-link'. INFO is the communication channel, +as a plist." + (pcase (org-element-type datum) + (`plain-text (format "See file %s" datum)) ;External file + (`headline + (format (org-ascii--translate "See section %s" info) + (if (org-export-numbered-headline-p datum info) + (mapconcat #'number-to-string + (org-export-get-headline-number datum info) + ".") + (org-export-data (org-element-property :title datum) info)))) + (_ + (let ((number (org-export-get-ordinal + datum info nil #'org-ascii--has-caption-p)) + ;; If destination is a target, make sure we can name the + ;; container it refers to. + (enumerable + (org-element-lineage datum + '(headline paragraph src-block table) t))) + (pcase (org-element-type enumerable) + (`headline + (format (org-ascii--translate "See section %s" info) + (if (org-export-numbered-headline-p enumerable info) + (mapconcat #'number-to-string number ".") + (org-export-data + (org-element-property :title enumerable) info)))) + ((guard (not number)) + (org-ascii--translate "Unknown reference" info)) + (`paragraph + (format (org-ascii--translate "See figure %s" info) number)) + (`src-block + (format (org-ascii--translate "See listing %s" info) number)) + (`table + (format (org-ascii--translate "See table %s" info) number)) + (_ (org-ascii--translate "Unknown reference" info))))))) (defun org-ascii--describe-links (links width info) "Return a string describing a list of links. - LINKS is a list of link type objects, as returned by `org-ascii--unique-links'. WIDTH is the text width allowed for the output string. INFO is a plist used as a communication channel." (mapconcat (lambda (link) - (let ((type (org-element-property :type link)) - (anchor (let ((desc (org-element-contents link))) - (if desc (org-export-data desc info) - (org-element-property :raw-link link))))) + (let* ((type (org-element-property :type link)) + (description (org-element-contents link)) + (anchor (org-export-data + (or description (org-element-property :raw-link link)) + info))) (cond - ;; Coderefs, radio links and fuzzy links are ignored. - ((member type '("coderef" "radio" "fuzzy")) nil) - ;; Id and custom-id links: Headlines refer to their numbering. - ((member type '("custom-id" "id")) - (let ((dest (org-export-resolve-id-link link info))) - (concat - (org-ascii--fill-string - (format - "[%s] %s" - anchor - (if (not dest) (org-ascii--translate "Unknown reference" info) - (format - (org-ascii--translate "See section %s" info) - (mapconcat 'number-to-string - (org-export-get-headline-number dest info) ".")))) - width info) "\n\n"))) + ((member type '("coderef" "radio")) nil) + ((member type '("custom-id" "fuzzy" "id")) + ;; Only links with a description need an entry. Other are + ;; already handled in `org-ascii-link'. + (when description + (let ((dest (if (equal type "fuzzy") + (org-export-resolve-fuzzy-link link info) + (org-export-resolve-id-link link info)))) + (concat + (org-ascii--fill-string + (format "[%s] %s" anchor (org-ascii--describe-datum dest info)) + width info) + "\n\n")))) ;; Do not add a link that cannot be resolved and doesn't have ;; any description: destination is already visible in the ;; paragraph. ((not (org-element-contents link)) nil) + ;; Do not add a link already handled by custom export + ;; functions. + ((org-export-custom-protocol-maybe link anchor 'ascii) nil) (t (concat (org-ascii--fill-string @@ -831,10 +969,10 @@ channel." "Return checkbox string for ITEM or nil. INFO is a plist used as a communication channel." (let ((utf8p (eq (plist-get info :ascii-charset) 'utf-8))) - (case (org-element-property :checkbox item) - (on (if utf8p "☑ " "[X] ")) - (off (if utf8p "☐ " "[ ] ")) - (trans (if utf8p "☒ " "[-] "))))) + (pcase (org-element-property :checkbox item) + (`on (if utf8p "☑ " "[X] ")) + (`off (if utf8p "☐ " "[ ] ")) + (`trans (if utf8p "☒ " "[-] "))))) @@ -843,11 +981,15 @@ INFO is a plist used as a communication channel." (defun org-ascii-template--document-title (info) "Return document title, as a string. INFO is a plist used as a communication channel." - (let* ((text-width org-ascii-text-width) + (let* ((text-width (plist-get info :ascii-text-width)) ;; Links in the title will not be resolved later, so we make ;; sure their path is located right after them. - (org-ascii-links-to-notes nil) - (title (org-export-data (plist-get info :title) info)) + (info (org-combine-plists info '(:ascii-links-to-notes nil))) + (with-title (plist-get info :with-title)) + (title (org-export-data + (when with-title (plist-get info :title)) info)) + (subtitle (org-export-data + (when with-title (plist-get info :subtitle)) info)) (author (and (plist-get info :with-author) (let ((auth (plist-get info :author))) (and auth (org-export-data auth info))))) @@ -878,7 +1020,7 @@ INFO is a plist used as a communication channel." date "\n\n\n")) ((org-string-nw-p date) (concat - (org-ascii--justify-string date text-width 'right) + (org-ascii--justify-lines date text-width 'right) "\n\n\n")) ((and (org-string-nw-p author) (org-string-nw-p email)) (concat author "\n" email "\n\n\n")) @@ -890,8 +1032,14 @@ INFO is a plist used as a communication channel." (let* ((utf8p (eq (plist-get info :ascii-charset) 'utf-8)) ;; Format TITLE. It may be filled if it is too wide, ;; that is wider than the two thirds of the total width. - (title-len (min (length title) (/ (* 2 text-width) 3))) + (title-len (min (apply #'max + (mapcar #'length + (org-split-string + (concat title "\n" subtitle) "\n"))) + (/ (* 2 text-width) 3))) (formatted-title (org-ascii--fill-string title title-len info)) + (formatted-subtitle (when (org-string-nw-p subtitle) + (org-ascii--fill-string subtitle title-len info))) (line (make-string (min (+ (max title-len @@ -899,17 +1047,16 @@ INFO is a plist used as a communication channel." (string-width (or email ""))) 2) text-width) (if utf8p ?━ ?_)))) - (org-ascii--justify-string + (org-ascii--justify-lines (concat line "\n" (unless utf8p "\n") (upcase formatted-title) + (and formatted-subtitle (concat "\n" formatted-subtitle)) (cond ((and (org-string-nw-p author) (org-string-nw-p email)) - (concat (if utf8p "\n\n\n" "\n\n") author "\n" email)) - ((org-string-nw-p author) - (concat (if utf8p "\n\n\n" "\n\n") author)) - ((org-string-nw-p email) - (concat (if utf8p "\n\n\n" "\n\n") email))) + (concat "\n\n" author "\n" email)) + ((org-string-nw-p author) (concat "\n\n" author)) + ((org-string-nw-p email) (concat "\n\n" email))) "\n" line (when (org-string-nw-p date) (concat "\n\n\n" date)) "\n\n\n") text-width 'center))))) @@ -919,81 +1066,83 @@ INFO is a plist used as a communication channel." CONTENTS is the transcoded contents string. INFO is a plist holding export options." (org-element-normalize-string - (org-ascii--indent-string - (concat - ;; 1. Document's body. - contents - ;; 2. Footnote definitions. - (let ((definitions (org-export-collect-footnote-definitions - (plist-get info :parse-tree) info)) - ;; Insert full links right inside the footnote definition - ;; as they have no chance to be inserted later. - (org-ascii-links-to-notes nil)) - (when definitions - (concat - "\n\n\n" - (let ((title (org-ascii--translate "Footnotes" info))) - (concat - title "\n" - (make-string - (string-width title) - (if (eq (plist-get info :ascii-charset) 'utf-8) ?─ ?_)))) - "\n\n" - (let ((text-width (- org-ascii-text-width org-ascii-global-margin))) - (mapconcat - (lambda (ref) - (let ((id (format "[%s] " (car ref)))) - ;; Distinguish between inline definitions and - ;; full-fledged definitions. - (org-trim - (let ((def (nth 2 ref))) - (if (eq (org-element-type def) 'org-data) - ;; Full-fledged definition: footnote ID is - ;; inserted inside the first parsed paragraph - ;; (FIRST), if any, to be sure filling will - ;; take it into consideration. - (let ((first (car (org-element-contents def)))) - (if (not (eq (org-element-type first) 'paragraph)) - (concat id "\n" (org-export-data def info)) - (push id (nthcdr 2 first)) - (org-export-data def info))) - ;; Fill paragraph once footnote ID is inserted - ;; in order to have a correct length for first - ;; line. - (org-ascii--fill-string - (concat id (org-export-data def info)) - text-width info)))))) - definitions "\n\n")))))) - org-ascii-global-margin))) + (let ((global-margin (plist-get info :ascii-global-margin))) + (org-ascii--indent-string + (concat + ;; 1. Document's body. + contents + ;; 2. Footnote definitions. + (let ((definitions (org-export-collect-footnote-definitions info)) + ;; Insert full links right inside the footnote definition + ;; as they have no chance to be inserted later. + (info (org-combine-plists info '(:ascii-links-to-notes nil)))) + (when definitions + (concat + "\n\n\n" + (let ((title (org-ascii--translate "Footnotes" info))) + (concat + title "\n" + (make-string + (string-width title) + (if (eq (plist-get info :ascii-charset) 'utf-8) ?─ ?_)))) + "\n\n" + (let ((text-width (- (plist-get info :ascii-text-width) + global-margin))) + (mapconcat + (lambda (ref) + (let ((id (format "[%s] " (car ref)))) + ;; Distinguish between inline definitions and + ;; full-fledged definitions. + (org-trim + (let ((def (nth 2 ref))) + (if (org-element-map def org-element-all-elements + #'identity info 'first-match) + ;; Full-fledged definition: footnote ID is + ;; inserted inside the first parsed + ;; paragraph (FIRST), if any, to be sure + ;; filling will take it into consideration. + (let ((first (car (org-element-contents def)))) + (if (not (eq (org-element-type first) 'paragraph)) + (concat id "\n" (org-export-data def info)) + (push id (nthcdr 2 first)) + (org-export-data def info))) + ;; Fill paragraph once footnote ID is inserted + ;; in order to have a correct length for first + ;; line. + (org-ascii--fill-string + (concat id (org-export-data def info)) + text-width info)))))) + definitions "\n\n")))))) + global-margin)))) (defun org-ascii-template (contents info) "Return complete document string after ASCII conversion. CONTENTS is the transcoded contents string. INFO is a plist holding export options." - (concat - ;; 1. Build title block. - (org-ascii--indent-string - (concat (org-ascii-template--document-title info) - ;; 2. Table of contents. - (let ((depth (plist-get info :with-toc))) - (when depth - (concat - (org-ascii--build-toc info (and (wholenump depth) depth)) - "\n\n\n")))) - org-ascii-global-margin) - ;; 3. Document's body. - contents - ;; 4. Creator. Ignore `comment' value as there are no comments in - ;; ASCII. Justify it to the bottom right. - (org-ascii--indent-string - (let ((creator-info (plist-get info :with-creator)) - (text-width (- org-ascii-text-width org-ascii-global-margin))) - (unless (or (not creator-info) (eq creator-info 'comment)) - (concat - "\n\n\n" - (org-ascii--fill-string - (plist-get info :creator) text-width info 'right)))) - org-ascii-global-margin))) + (let ((global-margin (plist-get info :ascii-global-margin))) + (concat + ;; Build title block. + (org-ascii--indent-string + (concat (org-ascii-template--document-title info) + ;; 2. Table of contents. + (let ((depth (plist-get info :with-toc))) + (when depth + (concat + (org-ascii--build-toc info (and (wholenump depth) depth)) + "\n\n\n")))) + global-margin) + ;; Document's body. + contents + ;; Creator. Justify it to the bottom right. + (and (plist-get info :with-creator) + (org-ascii--indent-string + (let ((text-width + (- (plist-get info :ascii-text-width) global-margin))) + (concat + "\n\n\n" + (org-ascii--fill-string + (plist-get info :creator) text-width info 'right))) + global-margin))))) (defun org-ascii--translate (s info) "Translate string S according to specified language and charset. @@ -1007,7 +1156,7 @@ INFO is a plist used as a communication channel." ;;;; Bold -(defun org-ascii-bold (bold contents info) +(defun org-ascii-bold (_bold contents _info) "Transcode BOLD from Org to ASCII. CONTENTS is the text with bold markup. INFO is a plist holding contextual information." @@ -1016,39 +1165,41 @@ contextual information." ;;;; Center Block -(defun org-ascii-center-block (center-block contents info) +(defun org-ascii-center-block (_center-block contents _info) "Transcode a CENTER-BLOCK element from Org to ASCII. CONTENTS holds the contents of the block. INFO is a plist holding contextual information." - (org-ascii--justify-string - contents (org-ascii--current-text-width center-block info) 'center)) + ;; Center has already been taken care of at a lower level, so + ;; there's nothing left to do. + contents) ;;;; Clock -(defun org-ascii-clock (clock contents info) +(defun org-ascii-clock (clock _contents info) "Transcode a CLOCK object from Org to ASCII. CONTENTS is nil. INFO is a plist holding contextual information." - (concat org-clock-string " " - (org-translate-time - (org-element-property :raw-value - (org-element-property :value clock))) - (let ((time (org-element-property :duration clock))) - (and time - (concat " => " - (apply 'format - "%2s:%02s" - (org-split-string time ":"))))))) + (org-ascii--justify-element + (concat org-clock-string " " + (org-timestamp-translate (org-element-property :value clock)) + (let ((time (org-element-property :duration clock))) + (and time + (concat " => " + (apply 'format + "%2s:%02s" + (org-split-string time ":")))))) + clock info)) ;;;; Code -(defun org-ascii-code (code contents info) +(defun org-ascii-code (code _contents info) "Return a CODE object from Org to ASCII. CONTENTS is nil. INFO is a plist holding contextual information." - (format org-ascii-verbatim-format (org-element-property :value code))) + (format (plist-get info :ascii-verbatim-format) + (org-element-property :value code))) ;;;; Drawer @@ -1059,12 +1210,13 @@ CONTENTS holds the contents of the block. INFO is a plist holding contextual information." (let ((name (org-element-property :drawer-name drawer)) (width (org-ascii--current-text-width drawer info))) - (funcall org-ascii-format-drawer-function name contents width))) + (funcall (plist-get info :ascii-format-drawer-function) + name contents width))) ;;;; Dynamic Block -(defun org-ascii-dynamic-block (dynamic-block contents info) +(defun org-ascii-dynamic-block (_dynamic-block contents _info) "Transcode a DYNAMIC-BLOCK element from Org to ASCII. CONTENTS holds the contents of the block. INFO is a plist holding contextual information." @@ -1073,7 +1225,7 @@ holding contextual information." ;;;; Entity -(defun org-ascii-entity (entity contents info) +(defun org-ascii-entity (entity _contents info) "Transcode an ENTITY object from Org to ASCII. CONTENTS are the definition itself. INFO is a plist holding contextual information." @@ -1084,16 +1236,18 @@ contextual information." ;;;; Example Block -(defun org-ascii-example-block (example-block contents info) +(defun org-ascii-example-block (example-block _contents info) "Transcode a EXAMPLE-BLOCK element from Org to ASCII. CONTENTS is nil. INFO is a plist holding contextual information." - (org-ascii--box-string - (org-export-format-code-default example-block info) info)) + (org-ascii--justify-element + (org-ascii--box-string + (org-export-format-code-default example-block info) info) + example-block info)) ;;;; Export Snippet -(defun org-ascii-export-snippet (export-snippet contents info) +(defun org-ascii-export-snippet (export-snippet _contents _info) "Transcode a EXPORT-SNIPPET object from Org to ASCII. CONTENTS is nil. INFO is a plist holding contextual information." (when (eq (org-export-snippet-backend export-snippet) 'ascii) @@ -1102,21 +1256,24 @@ CONTENTS is nil. INFO is a plist holding contextual information." ;;;; Export Block -(defun org-ascii-export-block (export-block contents info) +(defun org-ascii-export-block (export-block _contents info) "Transcode a EXPORT-BLOCK element from Org to ASCII. CONTENTS is nil. INFO is a plist holding contextual information." (when (string= (org-element-property :type export-block) "ASCII") - (org-remove-indentation (org-element-property :value export-block)))) + (org-ascii--justify-element + (org-element-property :value export-block) export-block info))) ;;;; Fixed Width -(defun org-ascii-fixed-width (fixed-width contents info) +(defun org-ascii-fixed-width (fixed-width _contents info) "Transcode a FIXED-WIDTH element from Org to ASCII. CONTENTS is nil. INFO is a plist holding contextual information." - (org-ascii--box-string - (org-remove-indentation - (org-element-property :value fixed-width)) info)) + (org-ascii--justify-element + (org-ascii--box-string + (org-remove-indentation + (org-element-property :value fixed-width)) info) + fixed-width info)) ;;;; Footnote Definition @@ -1127,7 +1284,7 @@ CONTENTS is nil. INFO is a plist holding contextual information." ;;;; Footnote Reference -(defun org-ascii-footnote-reference (footnote-reference contents info) +(defun org-ascii-footnote-reference (footnote-reference _contents info) "Transcode a FOOTNOTE-REFERENCE element from Org to ASCII. CONTENTS is nil. INFO is a plist holding contextual information." (format "[%s]" (org-export-get-footnote-number footnote-reference info))) @@ -1142,57 +1299,62 @@ holding contextual information." ;; Don't export footnote section, which will be handled at the end ;; of the template. (unless (org-element-property :footnote-section-p headline) - (let* ((low-level-rank (org-export-low-level-p headline info)) + (let* ((low-level (org-export-low-level-p headline info)) (width (org-ascii--current-text-width headline info)) + ;; Export title early so that any link in it can be + ;; exported and seen in `org-ascii--unique-links'. + (title (org-ascii--build-title headline info width (not low-level))) ;; Blank lines between headline and its contents. ;; `org-ascii-headline-spacing', when set, overwrites ;; original buffer's spacing. (pre-blanks - (make-string - (if org-ascii-headline-spacing (car org-ascii-headline-spacing) - (org-element-property :pre-blank headline)) ?\n)) - ;; Even if HEADLINE has no section, there might be some - ;; links in its title that we shouldn't forget to describe. - (links - (unless (or (eq (caar (org-element-contents headline)) 'section)) - (let ((title (org-element-property :title headline))) - (when (consp title) - (org-ascii--describe-links - (org-ascii--unique-links title info) width info)))))) + (make-string (or (car (plist-get info :ascii-headline-spacing)) + (org-element-property :pre-blank headline) + 0) + ?\n)) + (links (and (plist-get info :ascii-links-to-notes) + (org-ascii--describe-links + (org-ascii--unique-links headline info) width info))) + ;; Re-build contents, inserting section links at the right + ;; place. The cost is low since build results are cached. + (body + (if (not (org-string-nw-p links)) contents + (let* ((contents (org-element-contents headline)) + (section (let ((first (car contents))) + (and (eq (org-element-type first) 'section) + first)))) + (concat (and section + (concat (org-element-normalize-string + (org-export-data section info)) + "\n\n")) + links + (mapconcat (lambda (e) (org-export-data e info)) + (if section (cdr contents) contents) + "")))))) ;; Deep subtree: export it as a list item. - (if low-level-rank - (concat - ;; Bullet. - (let ((bullets (cdr (assq (plist-get info :ascii-charset) - org-ascii-bullets)))) - (char-to-string - (nth (mod (1- low-level-rank) (length bullets)) bullets))) - " " - ;; Title. - (org-ascii--build-title headline info width) "\n" - ;; Contents, indented by length of bullet. - pre-blanks - (org-ascii--indent-string - (concat contents - (when (org-string-nw-p links) (concat "\n\n" links))) - 2)) + (if low-level + (let* ((bullets (cdr (assq (plist-get info :ascii-charset) + (plist-get info :ascii-bullets)))) + (bullet + (format "%c " + (nth (mod (1- low-level) (length bullets)) bullets)))) + (concat bullet title "\n" pre-blanks + ;; Contents, indented by length of bullet. + (org-ascii--indent-string body (length bullet)))) ;; Else: Standard headline. - (concat - (org-ascii--build-title headline info width 'underline) - "\n" pre-blanks - (concat (when (org-string-nw-p links) links) contents)))))) + (concat title "\n" pre-blanks body))))) ;;;; Horizontal Rule -(defun org-ascii-horizontal-rule (horizontal-rule contents info) +(defun org-ascii-horizontal-rule (horizontal-rule _contents info) "Transcode an HORIZONTAL-RULE object from Org to ASCII. CONTENTS is nil. INFO is a plist holding contextual information." (let ((text-width (org-ascii--current-text-width horizontal-rule info)) (spec-width (org-export-read-attribute :attr_ascii horizontal-rule :width))) - (org-ascii--justify-string + (org-ascii--justify-lines (make-string (if (and spec-width (string-match "^[0-9]+$" spec-width)) (string-to-number spec-width) text-width) @@ -1202,23 +1364,23 @@ information." ;;;; Inline Src Block -(defun org-ascii-inline-src-block (inline-src-block contents info) +(defun org-ascii-inline-src-block (inline-src-block _contents info) "Transcode an INLINE-SRC-BLOCK element from Org to ASCII. CONTENTS holds the contents of the item. INFO is a plist holding contextual information." - (format org-ascii-verbatim-format + (format (plist-get info :ascii-verbatim-format) (org-element-property :value inline-src-block))) ;;;; Inlinetask (defun org-ascii-format-inlinetask-default - (todo type priority name tags contents width inlinetask info) + (_todo _type _priority _name _tags contents width inlinetask info) "Format an inline task element for ASCII export. See `org-ascii-format-inlinetask-function' for a description of the parameters." (let* ((utf8p (eq (plist-get info :ascii-charset) 'utf-8)) - (width (or width org-ascii-inlinetask-width))) + (width (or width (plist-get info :ascii-inlinetask-width)))) (org-ascii--indent-string (concat ;; Top line, with an additional blank line if not in UTF-8. @@ -1236,9 +1398,9 @@ of the parameters." ;; Bottom line. (make-string width (if utf8p ?━ ?_))) ;; Flush the inlinetask to the right. - (- org-ascii-text-width org-ascii-global-margin + (- (plist-get info :ascii-text-width) (plist-get info :ascii-global-margin) (if (not (org-export-get-parent-headline inlinetask)) 0 - org-ascii-inner-margin) + (plist-get info :ascii-inner-margin)) (org-ascii--current-text-width inlinetask info))))) (defun org-ascii-inlinetask (inlinetask contents info) @@ -1246,7 +1408,7 @@ of the parameters." CONTENTS holds the contents of the block. INFO is a plist holding contextual information." (let ((width (org-ascii--current-text-width inlinetask info))) - (funcall org-ascii-format-inlinetask-function + (funcall (plist-get info :ascii-format-inlinetask-function) ;; todo. (and (plist-get info :with-todo-keywords) (let ((todo (org-element-property @@ -1268,7 +1430,7 @@ holding contextual information." ;;;; Italic -(defun org-ascii-italic (italic contents info) +(defun org-ascii-italic (_italic contents _info) "Transcode italic from Org to ASCII. CONTENTS is the text with italic markup. INFO is a plist holding contextual information." @@ -1287,82 +1449,99 @@ contextual information." (bullet ;; First parent of ITEM is always the plain-list. Get ;; `:type' property from it. - (org-list-bullet-string - (case list-type - (descriptive - (concat checkbox - (org-export-data (org-element-property :tag item) info) - ": ")) - (ordered - ;; Return correct number for ITEM, paying attention to - ;; counters. - (let* ((struct (org-element-property :structure item)) - (bul (org-element-property :bullet item)) - (num (number-to-string - (car (last (org-list-get-item-number - (org-element-property :begin item) - struct - (org-list-prevs-alist struct) - (org-list-parents-alist struct))))))) - (replace-regexp-in-string "[0-9]+" num bul))) - (t (let ((bul (org-element-property :bullet item))) - ;; Change bullets into more visible form if UTF-8 is active. - (if (not utf8p) bul + (pcase list-type + (`descriptive + (concat checkbox + (org-export-data (org-element-property :tag item) + info))) + (`ordered + ;; Return correct number for ITEM, paying attention to + ;; counters. + (let* ((struct (org-element-property :structure item)) + (bul (org-list-bullet-string + (org-element-property :bullet item))) + (num (number-to-string + (car (last (org-list-get-item-number + (org-element-property :begin item) + struct + (org-list-prevs-alist struct) + (org-list-parents-alist struct))))))) + (replace-regexp-in-string "[0-9]+" num bul))) + (_ (let ((bul (org-list-bullet-string + (org-element-property :bullet item)))) + ;; Change bullets into more visible form if UTF-8 is active. + (if (not utf8p) bul + (replace-regexp-in-string + "-" "•" (replace-regexp-in-string - "-" "•" - (replace-regexp-in-string - "+" "⁃" - (replace-regexp-in-string "*" "‣" bul)))))))))) + "+" "⁃" + (replace-regexp-in-string "*" "‣" bul)))))))) + (indentation (if (eq list-type 'descriptive) org-ascii-quote-margin + (string-width bullet)))) (concat bullet - (unless (eq list-type 'descriptive) checkbox) + checkbox ;; Contents: Pay attention to indentation. Note: check-boxes are ;; already taken care of at the paragraph level so they don't ;; interfere with indentation. - (let ((contents (org-ascii--indent-string contents (string-width bullet)))) - (if (eq (org-element-type (car (org-element-contents item))) 'paragraph) + (let ((contents (org-ascii--indent-string contents indentation))) + ;; Determine if contents should follow the bullet or start + ;; a new line. Do the former when the first contributing + ;; element to contents is a paragraph. In descriptive lists + ;; however, contents always start a new line. + (if (and (not (eq list-type 'descriptive)) + (org-string-nw-p contents) + (eq 'paragraph + (org-element-type + (cl-some (lambda (e) + (and (org-string-nw-p (org-export-data e info)) + e)) + (org-element-contents item))))) (org-trim contents) (concat "\n" contents)))))) ;;;; Keyword -(defun org-ascii-keyword (keyword contents info) +(defun org-ascii-keyword (keyword _contents info) "Transcode a KEYWORD element from Org to ASCII. CONTENTS is nil. INFO is a plist holding contextual information." (let ((key (org-element-property :key keyword)) (value (org-element-property :value keyword))) (cond - ((string= key "ASCII") value) + ((string= key "ASCII") (org-ascii--justify-element value keyword info)) ((string= key "TOC") - (let ((value (downcase value))) - (cond - ((string-match "\\<headlines\\>" value) - (let ((depth (or (and (string-match "[0-9]+" value) - (string-to-number (match-string 0 value))) - (plist-get info :with-toc)))) - (org-ascii--build-toc - info (and (wholenump depth) depth) keyword))) - ((string= "tables" value) - (org-ascii--list-tables keyword info)) - ((string= "listings" value) - (org-ascii--list-listings keyword info)))))))) + (org-ascii--justify-element + (let ((case-fold-search t)) + (cond + ((string-match-p "\\<headlines\\>" value) + (let ((depth (and (string-match "\\<[0-9]+\\>" value) + (string-to-number (match-string 0 value)))) + (localp (string-match-p "\\<local\\>" value))) + (org-ascii--build-toc info depth keyword localp))) + ((string-match-p "\\<tables\\>" value) + (org-ascii--list-tables keyword info)) + ((string-match-p "\\<listings\\>" value) + (org-ascii--list-listings keyword info)))) + keyword info))))) ;;;; Latex Environment -(defun org-ascii-latex-environment (latex-environment contents info) +(defun org-ascii-latex-environment (latex-environment _contents info) "Transcode a LATEX-ENVIRONMENT element from Org to ASCII. CONTENTS is nil. INFO is a plist holding contextual information." (when (plist-get info :with-latex) - (org-remove-indentation (org-element-property :value latex-environment)))) + (org-ascii--justify-element + (org-remove-indentation (org-element-property :value latex-environment)) + latex-environment info))) ;;;; Latex Fragment -(defun org-ascii-latex-fragment (latex-fragment contents info) +(defun org-ascii-latex-fragment (latex-fragment _contents info) "Transcode a LATEX-FRAGMENT object from Org to ASCII. CONTENTS is nil. INFO is a plist holding contextual information." @@ -1372,7 +1551,7 @@ information." ;;;; Line Break -(defun org-ascii-line-break (line-break contents info) +(defun org-ascii-line-break (_line-break _contents _info) "Transcode a LINE-BREAK object from Org to ASCII. CONTENTS is nil. INFO is a plist holding contextual information." hard-newline) @@ -1385,9 +1564,9 @@ CONTENTS is nil. INFO is a plist holding contextual DESC is the description part of the link, or the empty string. INFO is a plist holding contextual information." - (let ((raw-link (org-element-property :raw-link link)) - (type (org-element-property :type link))) + (let ((type (org-element-property :type link))) (cond + ((org-export-custom-protocol-maybe link desc 'ascii)) ((string= type "coderef") (let ((ref (org-element-property :path link))) (format (org-export-get-coderef-format ref desc) @@ -1395,23 +1574,51 @@ INFO is a plist holding contextual information." ;; Do not apply a special syntax on radio links. Though, use ;; transcoded target's contents as output. ((string= type "radio") desc) - ;; Do not apply a special syntax on fuzzy links pointing to - ;; targets. - ((string= type "fuzzy") - (let ((destination (org-export-resolve-fuzzy-link link info))) - (if (org-string-nw-p desc) desc - (when destination - (let ((number - (org-export-get-ordinal - destination info nil 'org-ascii--has-caption-p))) - (when number - (if (atom number) (number-to-string number) - (mapconcat 'number-to-string number ".")))))))) + ((member type '("custom-id" "fuzzy" "id")) + (let ((destination (if (string= type "fuzzy") + (org-export-resolve-fuzzy-link link info) + (org-export-resolve-id-link link info)))) + (pcase (org-element-type destination) + ((guard desc) + (if (plist-get info :ascii-links-to-notes) + (format "[%s]" desc) + (concat desc + (format " (%s)" + (org-ascii--describe-datum destination info))))) + ;; External file. + (`plain-text destination) + (`headline + (if (org-export-numbered-headline-p destination info) + (mapconcat #'number-to-string + (org-export-get-headline-number destination info) + ".") + (org-export-data (org-element-property :title destination) info))) + ;; Handle enumerable elements and targets within them. + ((and (let number (org-export-get-ordinal + destination info nil #'org-ascii--has-caption-p)) + (guard number)) + (if (atom number) (number-to-string number) + (mapconcat #'number-to-string number "."))) + ;; Don't know what to do. Signal it. + (_ "???")))) (t - (if (not (org-string-nw-p desc)) (format "[%s]" raw-link) - (concat - (format "[%s]" desc) - (unless org-ascii-links-to-notes (format " (%s)" raw-link)))))))) + (let ((raw-link (org-element-property :raw-link link))) + (if (not (org-string-nw-p desc)) (format "[%s]" raw-link) + (concat (format "[%s]" desc) + (and (not (plist-get info :ascii-links-to-notes)) + (format " (%s)" raw-link))))))))) + + +;;;; Node Properties + +(defun org-ascii-node-property (node-property _contents _info) + "Transcode a NODE-PROPERTY element from Org to ASCII. +CONTENTS is nil. INFO is a plist holding contextual +information." + (format "%s:%s" + (org-element-property :key node-property) + (let ((value (org-element-property :value node-property))) + (if value (concat " " value) "")))) ;;;; Paragraph @@ -1420,16 +1627,17 @@ INFO is a plist holding contextual information." "Transcode a PARAGRAPH element from Org to ASCII. CONTENTS is the contents of the paragraph, as a string. INFO is the plist used as a communication channel." - (org-ascii--fill-string - (if (not (wholenump org-ascii-indented-line-width)) contents - (concat - ;; Do not indent first paragraph in a section. - (unless (and (not (org-export-get-previous-element paragraph info)) - (eq (org-element-type (org-export-get-parent paragraph)) - 'section)) - (make-string org-ascii-indented-line-width ?\s)) - (replace-regexp-in-string "\\`[ \t]+" "" contents))) - (org-ascii--current-text-width paragraph info) info)) + (org-ascii--justify-element + (let ((indented-line-width (plist-get info :ascii-indented-line-width))) + (if (not (wholenump indented-line-width)) contents + (concat + ;; Do not indent first paragraph in a section. + (unless (and (not (org-export-get-previous-element paragraph info)) + (eq (org-element-type (org-export-get-parent paragraph)) + 'section)) + (make-string indented-line-width ?\s)) + (replace-regexp-in-string "\\`[ \t]+" "" contents)))) + paragraph info)) ;;;; Plain List @@ -1438,7 +1646,11 @@ the plist used as a communication channel." "Transcode a PLAIN-LIST element from Org to ASCII. CONTENTS is the contents of the list. INFO is a plist holding contextual information." - contents) + (let ((margin (plist-get info :ascii-list-margin))) + (if (or (< margin 1) + (eq (org-element-type (org-export-get-parent plain-list)) 'item)) + contents + (org-ascii--indent-string contents margin)))) ;;;; Plain Text @@ -1462,62 +1674,52 @@ INFO is a plist used as a communication channel." ;;;; Planning -(defun org-ascii-planning (planning contents info) +(defun org-ascii-planning (planning _contents info) "Transcode a PLANNING element from Org to ASCII. CONTENTS is nil. INFO is a plist used as a communication channel." - (mapconcat - 'identity - (delq nil - (list (let ((closed (org-element-property :closed planning))) - (when closed - (concat org-closed-string " " - (org-translate-time - (org-element-property :raw-value closed))))) - (let ((deadline (org-element-property :deadline planning))) - (when deadline - (concat org-deadline-string " " - (org-translate-time - (org-element-property :raw-value deadline))))) - (let ((scheduled (org-element-property :scheduled planning))) - (when scheduled - (concat org-scheduled-string " " - (org-translate-time - (org-element-property :raw-value scheduled))))))) - " ")) + (org-ascii--justify-element + (mapconcat + #'identity + (delq nil + (list (let ((closed (org-element-property :closed planning))) + (when closed + (concat org-closed-string " " + (org-timestamp-translate closed)))) + (let ((deadline (org-element-property :deadline planning))) + (when deadline + (concat org-deadline-string " " + (org-timestamp-translate deadline)))) + (let ((scheduled (org-element-property :scheduled planning))) + (when scheduled + (concat org-scheduled-string " " + (org-timestamp-translate scheduled)))))) + " ") + planning info)) + + +;;;; Property Drawer + +(defun org-ascii-property-drawer (property-drawer contents info) + "Transcode a PROPERTY-DRAWER element from Org to ASCII. +CONTENTS holds the contents of the drawer. INFO is a plist +holding contextual information." + (and (org-string-nw-p contents) + (org-ascii--justify-element contents property-drawer info))) ;;;; Quote Block -(defun org-ascii-quote-block (quote-block contents info) +(defun org-ascii-quote-block (_quote-block contents info) "Transcode a QUOTE-BLOCK element from Org to ASCII. CONTENTS holds the contents of the block. INFO is a plist holding contextual information." - (org-ascii--indent-string contents org-ascii-quote-margin)) - - -;;;; Quote Section - -(defun org-ascii-quote-section (quote-section contents info) - "Transcode a QUOTE-SECTION element from Org to ASCII. -CONTENTS is nil. INFO is a plist holding contextual information." - (let ((width (org-ascii--current-text-width quote-section info)) - (value - (org-export-data - (org-remove-indentation (org-element-property :value quote-section)) - info))) - (org-ascii--indent-string - value - (+ org-ascii-quote-margin - ;; Don't apply inner margin if parent headline is low level. - (let ((headline (org-export-get-parent-headline quote-section))) - (if (org-export-low-level-p headline info) 0 - org-ascii-inner-margin)))))) + (org-ascii--indent-string contents (plist-get info :ascii-quote-margin))) ;;;; Radio Target -(defun org-ascii-radio-target (radio-target contents info) +(defun org-ascii-radio-target (_radio-target contents _info) "Transcode a RADIO-TARGET object from Org to ASCII. CONTENTS is the contents of the target. INFO is a plist holding contextual information." @@ -1530,50 +1732,56 @@ contextual information." "Transcode a SECTION element from Org to ASCII. CONTENTS is the contents of the section. INFO is a plist holding contextual information." - (org-ascii--indent-string - (concat - contents - (when org-ascii-links-to-notes - ;; Add list of links at the end of SECTION. - (let ((links (org-ascii--describe-links - (org-ascii--unique-links section info) - (org-ascii--current-text-width section info) info))) - ;; Separate list of links and section contents. - (when (org-string-nw-p links) (concat "\n\n" links))))) - ;; Do not apply inner margin if parent headline is low level. - (let ((headline (org-export-get-parent-headline section))) - (if (or (not headline) (org-export-low-level-p headline info)) 0 - org-ascii-inner-margin)))) + (let ((links + (and (plist-get info :ascii-links-to-notes) + ;; Take care of links in first section of the document. + (not (org-element-lineage section '(headline))) + (org-ascii--describe-links + (org-ascii--unique-links section info) + (org-ascii--current-text-width section info) + info)))) + (org-ascii--indent-string + (if (not (org-string-nw-p links)) contents + (concat (org-element-normalize-string contents) "\n\n" links)) + ;; Do not apply inner margin if parent headline is low level. + (let ((headline (org-export-get-parent-headline section))) + (if (or (not headline) (org-export-low-level-p headline info)) 0 + (plist-get info :ascii-inner-margin)))))) ;;;; Special Block -(defun org-ascii-special-block (special-block contents info) +(defun org-ascii-special-block (_special-block contents _info) "Transcode a SPECIAL-BLOCK element from Org to ASCII. CONTENTS holds the contents of the block. INFO is a plist holding contextual information." + ;; "JUSTIFYLEFT" and "JUSTIFYRIGHT" have already been taken care of + ;; at a lower level. There is no other special block type to + ;; handle. contents) ;;;; Src Block -(defun org-ascii-src-block (src-block contents info) +(defun org-ascii-src-block (src-block _contents info) "Transcode a SRC-BLOCK element from Org to ASCII. CONTENTS holds the contents of the item. INFO is a plist holding contextual information." (let ((caption (org-ascii--build-caption src-block info)) + (caption-above-p (plist-get info :ascii-caption-above)) (code (org-export-format-code-default src-block info))) (if (equal code "") "" - (concat - (when (and caption org-ascii-caption-above) (concat caption "\n")) - (org-ascii--box-string code info) - (when (and caption (not org-ascii-caption-above)) - (concat "\n" caption)))))) + (org-ascii--justify-element + (concat + (and caption caption-above-p (concat caption "\n")) + (org-ascii--box-string code info) + (and caption (not caption-above-p) (concat "\n" caption))) + src-block info)))) ;;;; Statistics Cookie -(defun org-ascii-statistics-cookie (statistics-cookie contents info) +(defun org-ascii-statistics-cookie (statistics-cookie _contents _info) "Transcode a STATISTICS-COOKIE object from Org to ASCII. CONTENTS is nil. INFO is a plist holding contextual information." (org-element-property :value statistics-cookie)) @@ -1581,7 +1789,7 @@ CONTENTS is nil. INFO is a plist holding contextual information." ;;;; Subscript -(defun org-ascii-subscript (subscript contents info) +(defun org-ascii-subscript (subscript contents _info) "Transcode a SUBSCRIPT object from Org to ASCII. CONTENTS is the contents of the object. INFO is a plist holding contextual information." @@ -1592,7 +1800,7 @@ contextual information." ;;;; Superscript -(defun org-ascii-superscript (superscript contents info) +(defun org-ascii-superscript (superscript contents _info) "Transcode a SUPERSCRIPT object from Org to ASCII. CONTENTS is the contents of the object. INFO is a plist holding contextual information." @@ -1603,7 +1811,7 @@ contextual information." ;;;; Strike-through -(defun org-ascii-strike-through (strike-through contents info) +(defun org-ascii-strike-through (_strike-through contents _info) "Transcode STRIKE-THROUGH from Org to ASCII. CONTENTS is text with strike-through markup. INFO is a plist holding contextual information." @@ -1616,26 +1824,29 @@ holding contextual information." "Transcode a TABLE element from Org to ASCII. CONTENTS is the contents of the table. INFO is a plist holding contextual information." - (let ((caption (org-ascii--build-caption table info))) - (concat - ;; Possibly add a caption string above. - (when (and caption org-ascii-caption-above) (concat caption "\n")) - ;; Insert table. Note: "table.el" tables are left unmodified. - (cond ((eq (org-element-property :type table) 'org) contents) - ((and org-ascii-table-use-ascii-art - (eq (plist-get info :ascii-charset) 'utf-8) - (require 'ascii-art-to-unicode nil t)) - (with-temp-buffer - (insert (org-remove-indentation - (org-element-property :value table))) - (goto-char (point-min)) - (aa2u) - (goto-char (point-max)) - (skip-chars-backward " \r\t\n") - (buffer-substring (point-min) (point)))) - (t (org-remove-indentation (org-element-property :value table)))) - ;; Possible add a caption string below. - (and (not org-ascii-caption-above) caption)))) + (let ((caption (org-ascii--build-caption table info)) + (caption-above-p (plist-get info :ascii-caption-above))) + (org-ascii--justify-element + (concat + ;; Possibly add a caption string above. + (and caption caption-above-p (concat caption "\n")) + ;; Insert table. Note: "table.el" tables are left unmodified. + (cond ((eq (org-element-property :type table) 'org) contents) + ((and (plist-get info :ascii-table-use-ascii-art) + (eq (plist-get info :ascii-charset) 'utf-8) + (require 'ascii-art-to-unicode nil t)) + (with-temp-buffer + (insert (org-remove-indentation + (org-element-property :value table))) + (goto-char (point-min)) + (aa2u) + (goto-char (point-max)) + (skip-chars-backward " \r\t\n") + (buffer-substring (point-min) (point)))) + (t (org-remove-indentation (org-element-property :value table)))) + ;; Possible add a caption string below. + (and (not caption-above-p) caption)) + table info))) ;;;; Table Cell @@ -1661,12 +1872,13 @@ are ignored." (plist-put info :ascii-table-cell-width-cache (make-hash-table :test 'equal))) :ascii-table-cell-width-cache))) - (key (cons table col))) + (key (cons table col)) + (widenp (plist-get info :ascii-table-widen-columns))) (or (gethash key cache) (puthash key (let ((cookie-width (org-export-table-cell-width table-cell info))) - (or (and (not org-ascii-table-widen-columns) cookie-width) + (or (and (not widenp) cookie-width) (let ((contents-width (let ((max-width 0)) (org-element-map table 'table-row @@ -1681,8 +1893,7 @@ are ignored." info) max-width))) (cond ((not cookie-width) contents-width) - (org-ascii-table-widen-columns - (max cookie-width contents-width)) + (widenp (max cookie-width contents-width)) (t cookie-width))))) cache)))) @@ -1696,14 +1907,14 @@ a communication channel." ;; each cell in the column. (let ((width (org-ascii--table-cell-width table-cell info))) ;; When contents are too large, truncate them. - (unless (or org-ascii-table-widen-columns + (unless (or (plist-get info :ascii-table-widen-columns) (<= (string-width (or contents "")) width)) (setq contents (concat (substring contents 0 (- width 2)) "=>"))) ;; Align contents correctly within the cell. (let* ((indent-tabs-mode nil) (data (when contents - (org-ascii--justify-string + (org-ascii--justify-lines contents width (org-export-table-cell-alignment table-cell info))))) (setq contents @@ -1770,7 +1981,7 @@ a communication channel." ;;;; Timestamp -(defun org-ascii-timestamp (timestamp contents info) +(defun org-ascii-timestamp (timestamp _contents info) "Transcode a TIMESTAMP object from Org to ASCII. CONTENTS is nil. INFO is a plist holding contextual information." (org-ascii-plain-text (org-timestamp-translate timestamp) info)) @@ -1778,7 +1989,7 @@ CONTENTS is nil. INFO is a plist holding contextual information." ;;;; Underline -(defun org-ascii-underline (underline contents info) +(defun org-ascii-underline (_underline contents _info) "Transcode UNDERLINE from Org to ASCII. CONTENTS is the text with underline markup. INFO is a plist holding contextual information." @@ -1787,10 +1998,10 @@ holding contextual information." ;;;; Verbatim -(defun org-ascii-verbatim (verbatim contents info) +(defun org-ascii-verbatim (verbatim _contents info) "Return a VERBATIM object from Org to ASCII. CONTENTS is nil. INFO is a plist holding contextual information." - (format org-ascii-verbatim-format + (format (plist-get info :ascii-verbatim-format) (org-element-property :value verbatim))) @@ -1800,48 +2011,48 @@ CONTENTS is nil. INFO is a plist holding contextual information." "Transcode a VERSE-BLOCK element from Org to ASCII. CONTENTS is verse block contents. INFO is a plist holding contextual information." - (let ((verse-width (org-ascii--current-text-width verse-block info))) - (org-ascii--indent-string - (org-ascii--justify-string contents verse-width 'left) - org-ascii-quote-margin))) + (org-ascii--indent-string + (org-ascii--justify-element contents verse-block info) + (plist-get info :ascii-quote-margin))) ;;; Filters -(defun org-ascii-filter-headline-blank-lines (headline back-end info) +(defun org-ascii-filter-headline-blank-lines (headline _backend info) "Filter controlling number of blank lines after a headline. -HEADLINE is a string representing a transcoded headline. -BACK-END is symbol specifying back-end used for export. INFO is -plist containing the communication channel. +HEADLINE is a string representing a transcoded headline. BACKEND +is symbol specifying back-end used for export. INFO is plist +containing the communication channel. This function only applies to `ascii' back-end. See `org-ascii-headline-spacing' for information." - (if (not org-ascii-headline-spacing) headline - (let ((blanks (make-string (1+ (cdr org-ascii-headline-spacing)) ?\n))) - (replace-regexp-in-string "\n\\(?:\n[ \t]*\\)*\\'" blanks headline)))) + (let ((headline-spacing (plist-get info :ascii-headline-spacing))) + (if (not headline-spacing) headline + (let ((blanks (make-string (1+ (cdr headline-spacing)) ?\n))) + (replace-regexp-in-string "\n\\(?:\n[ \t]*\\)*\\'" blanks headline))))) -(defun org-ascii-filter-paragraph-spacing (tree back-end info) +(defun org-ascii-filter-paragraph-spacing (tree _backend info) "Filter controlling number of blank lines between paragraphs. -TREE is the parse tree. BACK-END is the symbol specifying +TREE is the parse tree. BACKEND is the symbol specifying back-end used for export. INFO is a plist used as a communication channel. See `org-ascii-paragraph-spacing' for information." - (when (wholenump org-ascii-paragraph-spacing) - (org-element-map tree 'paragraph - (lambda (p) - (when (eq (org-element-type (org-export-get-next-element p info)) - 'paragraph) - (org-element-put-property - p :post-blank org-ascii-paragraph-spacing))))) + (let ((paragraph-spacing (plist-get info :ascii-paragraph-spacing))) + (when (wholenump paragraph-spacing) + (org-element-map tree 'paragraph + (lambda (p) + (when (eq (org-element-type (org-export-get-next-element p info)) + 'paragraph) + (org-element-put-property p :post-blank paragraph-spacing)))))) tree) -(defun org-ascii-filter-comment-spacing (tree backend info) +(defun org-ascii-filter-comment-spacing (tree _backend info) "Filter removing blank lines between comments. -TREE is the parse tree. BACK-END is the symbol specifying +TREE is the parse tree. BACKEND is the symbol specifying back-end used for export. INFO is a plist used as a communication channel." (org-element-map tree '(comment comment-block) diff --git a/lisp/org/ox-beamer.el b/lisp/org/ox-beamer.el index a8d48b67189..5750d6dab03 100644 --- a/lisp/org/ox-beamer.el +++ b/lisp/org/ox-beamer.el @@ -1,4 +1,4 @@ -;;; ox-beamer.el --- Beamer Back-End for Org Export Engine +;;; ox-beamer.el --- Beamer Back-End for Org Export Engine -*- lexical-binding: t; -*- ;; Copyright (C) 2007-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -29,7 +29,7 @@ ;;; Code: -(eval-when-compile (require 'cl)) +(require 'cl-lib) (require 'ox-latex) ;; Install a default set-up for Beamer export. @@ -105,7 +105,9 @@ key Selection key for `org-beamer-select-environment' open The opening template for the environment, with the following escapes %a the action/overlay specification %A the default action/overlay specification - %o the options argument of the template + %R the raw BEAMER_act value + %o the options argument, with square brackets + %O the raw BEAMER_opt value %h the headline text %r the raw headline text (i.e. without any processing) %H if there is headline text, that raw text in {} braces @@ -133,6 +135,15 @@ You might want to put e.g. \"allowframebreaks=0.9\" here." :type '(string :tag "Outline frame options")) +(defcustom org-beamer-subtitle-format "\\subtitle{%s}" + "Format string used for transcoded subtitle. +The format string should have at most one \"%s\"-expression, +which is replaced with the subtitle." + :group 'org-export-beamer + :version "26.1" + :package-version '(Org . "8.3") + :type '(string :tag "Format string")) + ;;; Internal Variables @@ -191,19 +202,14 @@ TYPE is a symbol among the following: `defaction' Return ARGUMENT within both square and angular brackets. `option' Return ARGUMENT within square brackets." (if (not (string-match "\\S-" argument)) "" - (case type - (action (if (string-match "\\`<.*>\\'" argument) argument - (format "<%s>" argument))) - (defaction (cond - ((string-match "\\`\\[<.*>\\]\\'" argument) argument) - ((string-match "\\`<.*>\\'" argument) - (format "[%s]" argument)) - ((string-match "\\`\\[\\(.*\\)\\]\\'" argument) - (format "[<%s>]" (match-string 1 argument))) - (t (format "[<%s>]" argument)))) - (option (if (string-match "\\`\\[.*\\]\\'" argument) argument - (format "[%s]" argument))) - (otherwise argument)))) + (cl-case type + (action (format "<%s>" (org-unbracket-string "<" ">" argument))) + (defaction + (format "[<%s>]" + (org-unbracket-string "<" ">" (org-unbracket-string "[" "]" argument)))) + (option (format "[%s]" (org-unbracket-string "[" "]" argument))) + (otherwise (error "Invalid `type' argument to `org-beamer--normalize-argument': %s" + type))))) (defun org-beamer--element-has-overlay-p (element) "Non-nil when ELEMENT has an overlay specified. @@ -213,14 +219,14 @@ Return overlay specification, as a string, or nil." (let ((first-object (car (org-element-contents element)))) (when (eq (org-element-type first-object) 'export-snippet) (let ((value (org-element-property :value first-object))) - (and (string-match "\\`<.*>\\'" value) value))))) + (and (string-prefix-p "<" value) (string-suffix-p ">" value) + value))))) ;;; Define Back-End (org-export-define-derived-backend 'beamer 'latex - :export-block "BEAMER" :menu-entry '(?l 1 ((?B "As LaTeX buffer (Beamer)" org-beamer-export-as-latex) @@ -231,15 +237,20 @@ Return overlay specification, as a string, or nil." (if a (org-beamer-export-to-pdf t s v b) (org-open-file (org-beamer-export-to-pdf nil s v b))))))) :options-alist - '((:beamer-theme "BEAMER_THEME" nil org-beamer-theme) + '((:headline-levels nil "H" org-beamer-frame-level) + (:latex-class "LATEX_CLASS" nil "beamer" t) + (:beamer-subtitle-format nil nil org-beamer-subtitle-format) + (:beamer-column-view-format "COLUMNS" nil org-beamer-column-view-format) + (:beamer-theme "BEAMER_THEME" nil org-beamer-theme) (:beamer-color-theme "BEAMER_COLOR_THEME" nil nil t) (:beamer-font-theme "BEAMER_FONT_THEME" nil nil t) (:beamer-inner-theme "BEAMER_INNER_THEME" nil nil t) (:beamer-outer-theme "BEAMER_OUTER_THEME" nil nil t) - (:beamer-header-extra "BEAMER_HEADER" nil nil newline) - ;; Modify existing properties. - (:headline-levels nil "H" org-beamer-frame-level) - (:latex-class "LATEX_CLASS" nil "beamer" t)) + (:beamer-header "BEAMER_HEADER" nil nil newline) + (:beamer-environments-extra nil nil org-beamer-environments-extra) + (:beamer-frame-default-options nil nil org-beamer-frame-default-options) + (:beamer-outline-frame-options nil nil org-beamer-outline-frame-options) + (:beamer-outline-frame-title nil nil org-beamer-outline-frame-title)) :translate-alist '((bold . org-beamer-bold) (export-block . org-beamer-export-block) (export-snippet . org-beamer-export-snippet) @@ -249,7 +260,6 @@ Return overlay specification, as a string, or nil." (link . org-beamer-link) (plain-list . org-beamer-plain-list) (radio-target . org-beamer-radio-target) - (target . org-beamer-target) (template . org-beamer-template))) @@ -258,7 +268,7 @@ Return overlay specification, as a string, or nil." ;;;; Bold -(defun org-beamer-bold (bold contents info) +(defun org-beamer-bold (bold contents _info) "Transcode BLOCK object into Beamer code. CONTENTS is the text being bold. INFO is a plist used as a communication channel." @@ -269,7 +279,7 @@ a communication channel." ;;;; Export Block -(defun org-beamer-export-block (export-block contents info) +(defun org-beamer-export-block (export-block _contents _info) "Transcode an EXPORT-BLOCK element into Beamer code. CONTENTS is nil. INFO is a plist used as a communication channel." @@ -279,7 +289,7 @@ channel." ;;;; Export Snippet -(defun org-beamer-export-snippet (export-snippet contents info) +(defun org-beamer-export-snippet (export-snippet _contents info) "Transcode an EXPORT-SNIPPET object into Beamer code. CONTENTS is nil. INFO is a plist used as a communication channel." @@ -315,16 +325,21 @@ channel." INFO is a plist used as a communication channel. The value is either the label specified in \"BEAMER_opt\" -property, or a fallback value built from headline's number. This -function assumes HEADLINE will be treated as a frame." - (let ((opt (org-element-property :BEAMER_OPT headline))) - (if (and (org-string-nw-p opt) - (string-match "\\(?:^\\|,\\)label=\\(.*?\\)\\(?:$\\|,\\)" opt)) - (match-string 1 opt) - (format "sec-%s" - (mapconcat 'number-to-string - (org-export-get-headline-number headline info) - "-"))))) +property, the custom ID, if there is one and +`:latex-prefer-user-labels' property has a non nil value, or +a unique internal label. This function assumes HEADLINE will be +treated as a frame." + (cond + ((let ((opt (org-element-property :BEAMER_OPT headline))) + (and (stringp opt) + (string-match "\\(?:^\\|,\\)label=\\(.*?\\)\\(?:$\\|,\\)" opt) + (let ((label (match-string 1 opt))) + (if (string-match-p "\\`{.*}\\'" label) + (substring label 1 -1) + label))))) + ((and (plist-get info :latex-prefer-user-labels) + (org-element-property :CUSTOM_ID headline))) + (t (format "sec:%s" (org-export-get-reference headline info))))) (defun org-beamer--frame-level (headline info) "Return frame level in subtree containing HEADLINE. @@ -333,12 +348,10 @@ INFO is a plist used as a communication channel." ;; 1. Look for "frame" environment in parents, starting from the ;; farthest. (catch 'exit - (mapc (lambda (parent) - (let ((env (org-element-property :BEAMER_ENV parent))) - (when (and env (member-ignore-case env '("frame" "fullframe"))) - (throw 'exit (org-export-get-relative-level parent info))))) - (nreverse (org-export-get-genealogy headline))) - nil) + (dolist (parent (nreverse (org-element-lineage headline))) + (let ((env (org-element-property :BEAMER_ENV parent))) + (when (and env (member-ignore-case env '("frame" "fullframe"))) + (throw 'exit (org-export-get-relative-level parent info)))))) ;; 2. Look for "frame" environment in HEADLINE. (let ((env (org-element-property :BEAMER_ENV headline))) (and env (member-ignore-case env '("frame" "fullframe")) @@ -410,24 +423,35 @@ used as a communication channel." ;; Options, if any. (let* ((beamer-opt (org-element-property :BEAMER_OPT headline)) (options - ;; Collect options from default value and headline's - ;; properties. Also add a label for links. - (append - (org-split-string org-beamer-frame-default-options ",") - (and beamer-opt - (org-split-string - ;; Remove square brackets if user provided - ;; them. - (and (string-match "^\\[?\\(.*\\)\\]?$" beamer-opt) - (match-string 1 beamer-opt)) - ",")) - ;; Provide an automatic label for the frame - ;; unless the user specified one. - (unless (and beamer-opt - (string-match "\\(^\\|,\\)label=" beamer-opt)) - (list - (format "label=%s" - (org-beamer--get-label headline info))))))) + ;; Collect nonempty options from default value and + ;; headline's properties. Also add a label for + ;; links. + (cl-remove-if-not 'org-string-nw-p + (append + (org-split-string + (plist-get info :beamer-frame-default-options) ",") + (and beamer-opt + (org-split-string + ;; Remove square brackets if user provided + ;; them. + (and (string-match "^\\[?\\(.*\\)\\]?$" beamer-opt) + (match-string 1 beamer-opt)) + ",")) + ;; Provide an automatic label for the frame + ;; unless the user specified one. Also refrain + ;; from labeling `allowframebreaks' frames; this + ;; is not allowed by beamer. + (unless (and beamer-opt + (or (string-match "\\(^\\|,\\)label=" beamer-opt) + (string-match "allowframebreaks" beamer-opt))) + (list + (let ((label (org-beamer--get-label headline info))) + ;; Labels containing colons need to be + ;; wrapped within braces. + (format (if (string-match-p ":" label) + "label={%s}" + "label=%s") + label)))))))) ;; Change options list into a string. (org-beamer--normalize-argument (mapconcat @@ -475,14 +499,15 @@ used as a communication channel." (env-format (cond ((member environment '("column" "columns")) nil) ((assoc environment - (append org-beamer-environments-extra + (append (plist-get info :beamer-environments-extra) org-beamer-environments-default))) (t (user-error "Wrong block type at a headline named \"%s\"" raw-title)))) (title (org-export-data (org-element-property :title headline) info)) - (options (let ((options (org-element-property :BEAMER_OPT headline))) - (if (not options) "" - (org-beamer--normalize-argument options 'option)))) + (raw-options (org-element-property :BEAMER_OPT headline)) + (options (if raw-options + (org-beamer--normalize-argument raw-options 'option) + "")) ;; Start a "columns" environment when explicitly requested or ;; when there is no previous headline or the previous ;; headline do not have a BEAMER_column property. @@ -521,7 +546,7 @@ used as a communication channel." ;; One can specify placement for column only when ;; HEADLINE stands for a column on its own. (if (equal environment "column") options "") - (format "%s\\textwidth" column-width))) + (format "%s\\columnwidth" column-width))) ;; Block's opening string. (when (nth 2 env-format) (concat @@ -534,15 +559,19 @@ used as a communication channel." ;; overlay specification and the default one is nil. (let ((action (org-element-property :BEAMER_ACT headline))) (cond - ((not action) (list (cons "a" "") (cons "A" ""))) - ((string-match "\\`\\[.*\\]\\'" action) + ((not action) (list (cons "a" "") (cons "A" "") (cons "R" ""))) + ((and (string-prefix-p "[" action) + (string-suffix-p "]" action)) (list (cons "A" (org-beamer--normalize-argument action 'defaction)) - (cons "a" ""))) + (cons "a" "") + (cons "R" action))) (t (list (cons "a" (org-beamer--normalize-argument action 'action)) - (cons "A" ""))))) + (cons "A" "") + (cons "R" action))))) (list (cons "o" options) + (cons "O" (or raw-options "")) (cons "h" title) (cons "r" raw-title) (cons "H" (if (equal raw-title "") "" @@ -578,28 +607,27 @@ as a communication channel." (when overlay (org-beamer--normalize-argument overlay - (if (string-match "^\\[.*\\]$" overlay) 'defaction + (if (string-match "\\`\\[.*\\]\\'" overlay) 'defaction 'action)))) ;; Options. (let ((options (org-element-property :BEAMER_OPT headline))) (when options (org-beamer--normalize-argument options 'option))) ;; Resolve reference provided by "BEAMER_ref" - ;; property. This is done by building a minimal fake - ;; link and calling the appropriate resolve function, - ;; depending on the reference syntax. - (let* ((type - (progn - (string-match "^\\(id:\\|#\\|\\*\\)?\\(.*\\)" ref) - (cond - ((or (not (match-string 1 ref)) - (equal (match-string 1 ref) "*")) 'fuzzy) - ((equal (match-string 1 ref) "id:") 'id) - (t 'custom-id)))) - (link (list 'link (list :path (match-string 2 ref)))) - (target (if (eq type 'fuzzy) - (org-export-resolve-fuzzy-link link info) - (org-export-resolve-id-link link info)))) + ;; property. This is done by building a minimal + ;; fake link and calling the appropriate resolve + ;; function, depending on the reference syntax. + (let ((target + (if (string-match "\\`\\(id:\\|#\\)" ref) + (org-export-resolve-id-link + `(link (:path ,(substring ref (match-end 0)))) + info) + (org-export-resolve-fuzzy-link + `(link (:path + ;; Look for headlines only. + ,(if (eq (string-to-char ref) ?*) ref + (concat "*" ref)))) + info)))) ;; Now use user-defined label provided in TARGET ;; headline, or fallback to standard one. (format "{%s}" (org-beamer--get-label target info))))))) @@ -640,15 +668,27 @@ as a communication channel." "Transcode an ITEM element into Beamer code. CONTENTS holds the contents of the item. INFO is a plist holding contextual information." - (let ((action (let ((first-element (car (org-element-contents item)))) - (and (eq (org-element-type first-element) 'paragraph) - (org-beamer--element-has-overlay-p first-element)))) - (output (org-export-with-backend 'latex item contents info))) - (if (or (not action) (not (string-match "\\\\item" output))) output - ;; If the item starts with a paragraph and that paragraph starts - ;; with an export snippet specifying an overlay, insert it after - ;; \item command. - (replace-match (concat "\\\\item" action) nil nil output)))) + (org-export-with-backend + ;; Delegate item export to `latex'. However, we use `beamer' + ;; transcoders for objects in the description tag. + (org-export-create-backend + :parent 'beamer + :transcoders + (list + (cons + 'item + (lambda (item _c _i) + (let ((action + (let ((first (car (org-element-contents item)))) + (and (eq (org-element-type first) 'paragraph) + (org-beamer--element-has-overlay-p first)))) + (output (org-latex-item item contents info))) + (if (not (and action (string-match "\\\\item" output))) output + ;; If the item starts with a paragraph and that paragraph + ;; starts with an export snippet specifying an overlay, + ;; append it to the \item command. + (replace-match (concat "\\\\item" action) nil nil output))))))) + item contents info)) ;;;; Keyword @@ -681,46 +721,16 @@ channel." "Transcode a LINK object into Beamer code. CONTENTS is the description part of the link. INFO is a plist used as a communication channel." - (let ((type (org-element-property :type link)) - (path (org-element-property :path link))) - ;; Use \hyperlink command for all internal links. - (cond - ((equal type "radio") - (let ((destination (org-export-resolve-radio-link link info))) - (if (not destination) contents - (format "\\hyperlink%s{%s}{%s}" - (or (org-beamer--element-has-overlay-p link) "") - (org-export-solidify-link-text - (org-element-property :value destination)) - contents)))) - ((and (member type '("custom-id" "fuzzy" "id")) - (let ((destination (if (string= type "fuzzy") - (org-export-resolve-fuzzy-link link info) - (org-export-resolve-id-link link info)))) - (case (org-element-type destination) - (headline - (let ((label - (format "sec-%s" - (mapconcat - 'number-to-string - (org-export-get-headline-number - destination info) - "-")))) - (if (and (plist-get info :section-numbers) (not contents)) - (format "\\ref{%s}" label) - (format "\\hyperlink%s{%s}{%s}" - (or (org-beamer--element-has-overlay-p link) "") - label - contents)))) - (target - (let ((path (org-export-solidify-link-text path))) - (if (not contents) (format "\\ref{%s}" path) - (format "\\hyperlink%s{%s}{%s}" - (or (org-beamer--element-has-overlay-p link) "") - path - contents)))))))) - ;; Otherwise, use `latex' back-end. - (t (org-export-with-backend 'latex link contents info))))) + (or (org-export-custom-protocol-maybe link contents 'beamer) + ;; Fall-back to LaTeX export. However, prefer "\hyperlink" over + ;; "\hyperref" since the former handles overlay specifications. + (let ((latex-link (org-export-with-backend 'latex link contents info))) + (if (string-match "\\`\\\\hyperref\\[\\(.*?\\)\\]" latex-link) + (replace-match + (format "\\\\hyperlink%s{\\1}" + (or (org-beamer--element-has-overlay-p link) "")) + nil nil latex-link) + latex-link)))) ;;;; Plain List @@ -755,7 +765,8 @@ contextual information." 'option) ;; Eventually insert contents and close environment. contents - latex-type)))) + latex-type) + info))) ;;;; Radio Target @@ -766,21 +777,10 @@ TEXT is the text of the target. INFO is a plist holding contextual information." (format "\\hypertarget%s{%s}{%s}" (or (org-beamer--element-has-overlay-p radio-target) "") - (org-export-solidify-link-text - (org-element-property :value radio-target)) + (org-export-get-reference radio-target info) text)) -;;;; Target - -(defun org-beamer-target (target contents info) - "Transcode a TARGET object into Beamer code. -CONTENTS is nil. INFO is a plist holding contextual -information." - (format "\\hypertarget{%s}{}" - (org-export-solidify-link-text (org-element-property :value target)))) - - ;;;; Template ;; ;; Template used is similar to the one used in `latex' back-end, @@ -790,37 +790,17 @@ information." "Return complete document string after Beamer conversion. CONTENTS is the transcoded contents string. INFO is a plist holding export options." - (let ((title (org-export-data (plist-get info :title) info))) + (let ((title (org-export-data (plist-get info :title) info)) + (subtitle (org-export-data (plist-get info :subtitle) info))) (concat - ;; 1. Time-stamp. + ;; Time-stamp. (and (plist-get info :time-stamp-file) (format-time-string "%% Created %Y-%m-%d %a %H:%M\n")) - ;; 2. Document class and packages. - (let* ((class (plist-get info :latex-class)) - (class-options (plist-get info :latex-class-options)) - (header (nth 1 (assoc class org-latex-classes))) - (document-class-string - (and (stringp header) - (if (not class-options) header - (replace-regexp-in-string - "^[ \t]*\\\\documentclass\\(\\(\\[[^]]*\\]\\)?\\)" - class-options header t nil 1))))) - (if (not document-class-string) - (user-error "Unknown LaTeX class `%s'" class) - (org-latex-guess-babel-language - (org-latex-guess-inputenc - (org-element-normalize-string - (org-splice-latex-header - document-class-string - org-latex-default-packages-alist - org-latex-packages-alist nil - (concat (org-element-normalize-string - (plist-get info :latex-header)) - (org-element-normalize-string - (plist-get info :latex-header-extra)) - (plist-get info :beamer-header-extra))))) - info))) - ;; 3. Insert themes. + ;; LaTeX compiler + (org-latex--insert-compiler info) + ;; Document class and packages. + (org-latex-make-preamble info) + ;; Insert themes. (let ((format-theme (function (lambda (prop command) @@ -840,11 +820,11 @@ holding export options." (:beamer-inner-theme "\\useinnertheme") (:beamer-outer-theme "\\useoutertheme")) "")) - ;; 4. Possibly limit depth for headline numbering. + ;; Possibly limit depth for headline numbering. (let ((sec-num (plist-get info :section-numbers))) (when (integerp sec-num) (format "\\setcounter{secnumdepth}{%d}\n" sec-num))) - ;; 5. Author. + ;; Author. (let ((author (and (plist-get info :with-author) (let ((auth (plist-get info :author))) (and auth (org-export-data auth info))))) @@ -852,52 +832,52 @@ holding export options." (org-export-data (plist-get info :email) info)))) (cond ((and author email (not (string= "" email))) (format "\\author{%s\\thanks{%s}}\n" author email)) - (author (format "\\author{%s}\n" author)) - (t "\\author{}\n"))) - ;; 6. Date. + ((or author email) (format "\\author{%s}\n" (or author email))))) + ;; Date. (let ((date (and (plist-get info :with-date) (org-export-get-date info)))) (format "\\date{%s}\n" (org-export-data date info))) - ;; 7. Title + ;; Title (format "\\title{%s}\n" title) - ;; 8. Hyperref options. - (when (plist-get info :latex-hyperref-p) - (format "\\hypersetup{\n pdfkeywords={%s},\n pdfsubject={%s},\n pdfcreator={%s}}\n" - (or (plist-get info :keywords) "") - (or (plist-get info :description) "") - (if (not (plist-get info :with-creator)) "" - (plist-get info :creator)))) - ;; 9. Document start. + (when (org-string-nw-p subtitle) + (concat (format (plist-get info :beamer-subtitle-format) subtitle) "\n")) + ;; Beamer-header + (let ((beamer-header (plist-get info :beamer-header))) + (when beamer-header + (format "%s\n" (plist-get info :beamer-header)))) + ;; 9. Hyperref options. + (let ((template (plist-get info :latex-hyperref-template))) + (and (stringp template) + (format-spec template (org-latex--format-spec info)))) + ;; Document start. "\\begin{document}\n\n" - ;; 10. Title command. + ;; Title command. (org-element-normalize-string - (cond ((string= "" title) nil) + (cond ((not (plist-get info :with-title)) nil) + ((string= "" title) nil) ((not (stringp org-latex-title-command)) nil) ((string-match "\\(?:[^%]\\|^\\)%s" org-latex-title-command) (format org-latex-title-command title)) (t org-latex-title-command))) - ;; 11. Table of contents. + ;; Table of contents. (let ((depth (plist-get info :with-toc))) (when depth (concat (format "\\begin{frame}%s{%s}\n" (org-beamer--normalize-argument - org-beamer-outline-frame-options 'option) - org-beamer-outline-frame-title) + (plist-get info :beamer-outline-frame-options) 'option) + (plist-get info :beamer-outline-frame-title)) (when (wholenump depth) (format "\\setcounter{tocdepth}{%d}\n" depth)) "\\tableofcontents\n" "\\end{frame}\n\n"))) - ;; 12. Document's body. + ;; Document's body. contents - ;; 13. Creator. - (let ((creator-info (plist-get info :with-creator))) - (cond - ((not creator-info) "") - ((eq creator-info 'comment) - (format "%% %s\n" (plist-get info :creator))) - (t (concat (plist-get info :creator) "\n")))) - ;; 14. Document end. + ;; Creator. + (if (plist-get info :with-creator) + (concat (plist-get info :creator) "\n") + "") + ;; Document end. "\\end{document}"))) @@ -933,7 +913,7 @@ value." (save-excursion (org-back-to-heading t) ;; Filter out Beamer-related tags and install environment tag. - (let ((tags (org-remove-if (lambda (x) (string-match "^B_" x)) + (let ((tags (cl-remove-if (lambda (x) (string-match "^B_" x)) (org-get-tags))) (env-tag (and (org-string-nw-p value) (concat "B_" value)))) (org-set-tags-to (if env-tag (cons env-tag tags) tags)) @@ -955,9 +935,9 @@ value." org-beamer-environments-default))) ((and (equal property "BEAMER_col") (not (org-entry-get nil (concat property "_ALL") 'inherit))) - ;; If no allowed values for BEAMER_col have been defined, - ;; supply some - (org-split-string org-beamer-column-widths " ")))) + ;; If no allowed values for BEAMER_col have been defined, supply + ;; some. + (split-string org-beamer-column-widths " ")))) (add-hook 'org-property-allowed-value-functions 'org-beamer-allowed-property-values) @@ -1085,7 +1065,7 @@ aid, but the tag does not have any semantic meaning." (let* ((envs (append org-beamer-environments-special org-beamer-environments-extra org-beamer-environments-default)) - (org-tag-alist + (org-current-tag-alist (append '((:startgroup)) (mapcar (lambda (e) (cons (concat "B_" (car e)) (string-to-char (nth 1 e)))) @@ -1121,30 +1101,6 @@ aid, but the tag does not have any semantic meaning." (t (org-entry-delete nil "BEAMER_env")))))) ;;;###autoload -(defun org-beamer-insert-options-template (&optional kind) - "Insert a settings template, to make sure users do this right." - (interactive (progn - (message "Current [s]ubtree or [g]lobal?") - (if (eq (read-char-exclusive) ?g) (list 'global) - (list 'subtree)))) - (if (eq kind 'subtree) - (progn - (org-back-to-heading t) - (org-reveal) - (org-entry-put nil "EXPORT_LaTeX_CLASS" "beamer") - (org-entry-put nil "EXPORT_LaTeX_CLASS_OPTIONS" "[presentation]") - (org-entry-put nil "EXPORT_FILE_NAME" "presentation.pdf") - (when org-beamer-column-view-format - (org-entry-put nil "COLUMNS" org-beamer-column-view-format)) - (org-entry-put nil "BEAMER_col_ALL" org-beamer-column-widths)) - (insert "#+LaTeX_CLASS: beamer\n") - (insert "#+LaTeX_CLASS_OPTIONS: [presentation]\n") - (when org-beamer-theme (insert "#+BEAMER_THEME: " org-beamer-theme "\n")) - (when org-beamer-column-view-format - (insert "#+COLUMNS: " org-beamer-column-view-format "\n")) - (insert "#+PROPERTY: BEAMER_col_ALL " org-beamer-column-widths "\n"))) - -;;;###autoload (defun org-beamer-publish-to-latex (plist filename pub-dir) "Publish an Org file to a Beamer presentation (LaTeX). @@ -1168,9 +1124,13 @@ Return output file name." ;; working directory and then moved to publishing directory. (org-publish-attachment plist - (org-latex-compile - (org-publish-org-to - 'beamer filename ".tex" plist (file-name-directory filename))) + ;; Default directory could be anywhere when this function is + ;; called. We ensure it is set to source file directory during + ;; compilation so as to not break links to external documents. + (let ((default-directory (file-name-directory filename))) + (org-latex-compile + (org-publish-org-to + 'beamer filename ".tex" plist (file-name-directory filename)))) pub-dir)) diff --git a/lisp/org/ox-html.el b/lisp/org/ox-html.el index 86ca3a6bb28..bf08de10af7 100644 --- a/lisp/org/ox-html.el +++ b/lisp/org/ox-html.el @@ -1,4 +1,4 @@ -;;; ox-html.el --- HTML Back-End for Org Export Engine +;;; ox-html.el --- HTML Back-End for Org Export Engine -*- lexical-binding: t; -*- ;; Copyright (C) 2011-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -30,20 +30,24 @@ ;;; Dependencies +(require 'cl-lib) +(require 'format-spec) (require 'ox) (require 'ox-publish) -(require 'format-spec) -(eval-when-compile (require 'cl) (require 'table nil 'noerror)) +(require 'table) ;;; Function Declarations (declare-function org-id-find-id-file "org-id" (id)) (declare-function htmlize-region "ext:htmlize" (beg end)) -(declare-function org-pop-to-buffer-same-window - "org-compat" (&optional buffer-or-name norecord label)) (declare-function mm-url-decode-entities "mm-url" ()) +(defvar htmlize-css-name-prefix) +(defvar htmlize-output-type) +(defvar htmlize-output-type) +(defvar htmlize-css-name-prefix) + ;;; Define Back-End (org-export-define-backend 'html @@ -72,13 +76,13 @@ (latex-fragment . org-html-latex-fragment) (line-break . org-html-line-break) (link . org-html-link) + (node-property . org-html-node-property) (paragraph . org-html-paragraph) (plain-list . org-html-plain-list) (plain-text . org-html-plain-text) (planning . org-html-planning) (property-drawer . org-html-property-drawer) (quote-block . org-html-quote-block) - (quote-section . org-html-quote-section) (radio-target . org-html-radio-target) (section . org-html-section) (special-block . org-html-special-block) @@ -96,8 +100,8 @@ (underline . org-html-underline) (verbatim . org-html-verbatim) (verse-block . org-html-verse-block)) - :export-block "HTML" :filters-alist '((:filter-options . org-html-infojs-install-script) + (:filter-parse-tree . org-html-image-link-filter) (:filter-final-output . org-html-final-function)) :menu-entry '(?h "Export to HTML" @@ -108,10 +112,10 @@ (if a (org-html-export-to-html t s v b) (org-open-file (org-html-export-to-html nil s v b))))))) :options-alist - '((:html-extension nil nil org-html-extension) - (:html-link-org-as-html nil nil org-html-link-org-files-as-html) - (:html-doctype "HTML_DOCTYPE" nil org-html-doctype) + '((:html-doctype "HTML_DOCTYPE" nil org-html-doctype) (:html-container "HTML_CONTAINER" nil org-html-container-element) + (:description "DESCRIPTION" nil nil newline) + (:keywords "KEYWORDS" nil nil space) (:html-html5-fancy nil "html5-fancy" org-html-html5-fancy) (:html-link-use-abs-url nil "html-link-use-abs-url" org-html-link-use-abs-url) (:html-link-home "HTML_LINK_HOME" nil org-html-link-home) @@ -121,12 +125,56 @@ (:html-preamble nil "html-preamble" org-html-preamble) (:html-head "HTML_HEAD" nil org-html-head newline) (:html-head-extra "HTML_HEAD_EXTRA" nil org-html-head-extra newline) - (:html-head-include-default-style nil "html-style" org-html-head-include-default-style) + (:subtitle "SUBTITLE" nil nil parse) + (:html-head-include-default-style + nil "html-style" org-html-head-include-default-style) (:html-head-include-scripts nil "html-scripts" org-html-head-include-scripts) + (:html-allow-name-attribute-in-anchors + nil nil org-html-allow-name-attribute-in-anchors) + (:html-divs nil nil org-html-divs) + (:html-checkbox-type nil nil org-html-checkbox-type) + (:html-extension nil nil org-html-extension) + (:html-footnote-format nil nil org-html-footnote-format) + (:html-footnote-separator nil nil org-html-footnote-separator) + (:html-footnotes-section nil nil org-html-footnotes-section) + (:html-format-drawer-function nil nil org-html-format-drawer-function) + (:html-format-headline-function nil nil org-html-format-headline-function) + (:html-format-inlinetask-function + nil nil org-html-format-inlinetask-function) + (:html-home/up-format nil nil org-html-home/up-format) + (:html-indent nil nil org-html-indent) + (:html-infojs-options nil nil org-html-infojs-options) + (:html-infojs-template nil nil org-html-infojs-template) + (:html-inline-image-rules nil nil org-html-inline-image-rules) + (:html-link-org-files-as-html nil nil org-html-link-org-files-as-html) + (:html-mathjax-options nil nil org-html-mathjax-options) + (:html-mathjax-template nil nil org-html-mathjax-template) + (:html-metadata-timestamp-format nil nil org-html-metadata-timestamp-format) + (:html-postamble-format nil nil org-html-postamble-format) + (:html-preamble-format nil nil org-html-preamble-format) + (:html-table-align-individual-fields + nil nil org-html-table-align-individual-fields) + (:html-table-caption-above nil nil org-html-table-caption-above) + (:html-table-data-tags nil nil org-html-table-data-tags) + (:html-table-header-tags nil nil org-html-table-header-tags) + (:html-table-use-header-tags-for-first-column + nil nil org-html-table-use-header-tags-for-first-column) + (:html-tag-class-prefix nil nil org-html-tag-class-prefix) + (:html-text-markup-alist nil nil org-html-text-markup-alist) + (:html-todo-kwd-class-prefix nil nil org-html-todo-kwd-class-prefix) + (:html-toplevel-hlevel nil nil org-html-toplevel-hlevel) + (:html-use-infojs nil nil org-html-use-infojs) + (:html-validation-link nil nil org-html-validation-link) + (:html-viewport nil nil org-html-viewport) + (:html-inline-images nil nil org-html-inline-images) (:html-table-attributes nil nil org-html-table-default-attributes) - (:html-table-row-tags nil nil org-html-table-row-tags) + (:html-table-row-open-tag nil nil org-html-table-row-open-tag) + (:html-table-row-close-tag nil nil org-html-table-row-close-tag) (:html-xml-declaration nil nil org-html-xml-declaration) - (:html-inline-images nil nil org-html-inline-images) + (:html-klipsify-src nil nil org-html-klipsify-src) + (:html-klipse-css nil nil org-html-klipse-css) + (:html-klipse-js nil nil org-html-klipse-js) + (:html-klipse-selection-script nil nil org-html-klipse-selection-script) (:infojs-opt "INFOJS_OPT" nil nil) ;; Redefine regular options. (:creator "CREATOR" nil org-html-creator-string) @@ -186,7 +234,7 @@ property on the headline itself.") @licstart The following is the entire license notice for the JavaScript code in this tag. -Copyright (C) 2012-2013 Free Software Foundation, Inc. +Copyright (C) 2012-2017 Free Software Foundation, Inc. The JavaScript code in this tag is free software: you can redistribute it and/or modify it under the terms of the GNU @@ -232,16 +280,22 @@ for the JavaScript code in this tag. (defconst org-html-style-default "<style type=\"text/css\"> <!--/*--><![CDATA[/*><!--*/ - .title { text-align: center; } + .title { text-align: center; + margin-bottom: .2em; } + .subtitle { text-align: center; + font-size: medium; + font-weight: bold; + margin-top:0; } .todo { font-family: monospace; color: red; } - .done { color: green; } + .done { font-family: monospace; color: green; } + .priority { font-family: monospace; color: orange; } .tag { background-color: #eee; font-family: monospace; padding: 2px; font-size: 80%; font-weight: normal; } .timestamp { color: #bebebe; } .timestamp-kwd { color: #5f9ea0; } - .right { margin-left: auto; margin-right: 0px; text-align: right; } - .left { margin-left: 0px; margin-right: auto; text-align: left; } - .center { margin-left: auto; margin-right: auto; text-align: center; } + .org-right { margin-left: auto; margin-right: 0px; text-align: right; } + .org-left { margin-left: 0px; margin-right: auto; text-align: left; } + .org-center { margin-left: auto; margin-right: auto; text-align: center; } .underline { text-decoration: underline; } #postamble p, #preamble p { font-size: 90%; margin: .2em; } p.verse { margin-left: 3%; } @@ -268,27 +322,111 @@ for the JavaScript code in this tag. border: 1px solid black; } pre.src:hover:before { display: inline;} - pre.src-sh:before { content: 'sh'; } - pre.src-bash:before { content: 'sh'; } + /* Languages per Org manual */ + pre.src-asymptote:before { content: 'Asymptote'; } + pre.src-awk:before { content: 'Awk'; } + pre.src-C:before { content: 'C'; } + /* pre.src-C++ doesn't work in CSS */ + pre.src-clojure:before { content: 'Clojure'; } + pre.src-css:before { content: 'CSS'; } + pre.src-D:before { content: 'D'; } + pre.src-ditaa:before { content: 'ditaa'; } + pre.src-dot:before { content: 'Graphviz'; } + pre.src-calc:before { content: 'Emacs Calc'; } pre.src-emacs-lisp:before { content: 'Emacs Lisp'; } - pre.src-R:before { content: 'R'; } - pre.src-perl:before { content: 'Perl'; } - pre.src-java:before { content: 'Java'; } - pre.src-sql:before { content: 'SQL'; } + pre.src-fortran:before { content: 'Fortran'; } + pre.src-gnuplot:before { content: 'gnuplot'; } + pre.src-haskell:before { content: 'Haskell'; } + pre.src-hledger:before { content: 'hledger'; } + pre.src-java:before { content: 'Java'; } + pre.src-js:before { content: 'Javascript'; } + pre.src-latex:before { content: 'LaTeX'; } + pre.src-ledger:before { content: 'Ledger'; } + pre.src-lisp:before { content: 'Lisp'; } + pre.src-lilypond:before { content: 'Lilypond'; } + pre.src-lua:before { content: 'Lua'; } + pre.src-matlab:before { content: 'MATLAB'; } + pre.src-mscgen:before { content: 'Mscgen'; } + pre.src-ocaml:before { content: 'Objective Caml'; } + pre.src-octave:before { content: 'Octave'; } + pre.src-org:before { content: 'Org mode'; } + pre.src-oz:before { content: 'OZ'; } + pre.src-plantuml:before { content: 'Plantuml'; } + pre.src-processing:before { content: 'Processing.js'; } + pre.src-python:before { content: 'Python'; } + pre.src-R:before { content: 'R'; } + pre.src-ruby:before { content: 'Ruby'; } + pre.src-sass:before { content: 'Sass'; } + pre.src-scheme:before { content: 'Scheme'; } + pre.src-screen:before { content: 'Gnu Screen'; } + pre.src-sed:before { content: 'Sed'; } + pre.src-sh:before { content: 'shell'; } + pre.src-sql:before { content: 'SQL'; } + pre.src-sqlite:before { content: 'SQLite'; } + /* additional languages in org.el's org-babel-load-languages alist */ + pre.src-forth:before { content: 'Forth'; } + pre.src-io:before { content: 'IO'; } + pre.src-J:before { content: 'J'; } + pre.src-makefile:before { content: 'Makefile'; } + pre.src-maxima:before { content: 'Maxima'; } + pre.src-perl:before { content: 'Perl'; } + pre.src-picolisp:before { content: 'Pico Lisp'; } + pre.src-scala:before { content: 'Scala'; } + pre.src-shell:before { content: 'Shell Script'; } + pre.src-ebnf2ps:before { content: 'ebfn2ps'; } + /* additional language identifiers per \"defun org-babel-execute\" + in ob-*.el */ + pre.src-cpp:before { content: 'C++'; } + pre.src-abc:before { content: 'ABC'; } + pre.src-coq:before { content: 'Coq'; } + pre.src-groovy:before { content: 'Groovy'; } + /* additional language identifiers from org-babel-shell-names in + ob-shell.el: ob-shell is the only babel language using a lambda to put + the execution function name together. */ + pre.src-bash:before { content: 'bash'; } + pre.src-csh:before { content: 'csh'; } + pre.src-ash:before { content: 'ash'; } + pre.src-dash:before { content: 'dash'; } + pre.src-ksh:before { content: 'ksh'; } + pre.src-mksh:before { content: 'mksh'; } + pre.src-posh:before { content: 'posh'; } + /* Additional Emacs modes also supported by the LaTeX listings package */ + pre.src-ada:before { content: 'Ada'; } + pre.src-asm:before { content: 'Assembler'; } + pre.src-caml:before { content: 'Caml'; } + pre.src-delphi:before { content: 'Delphi'; } + pre.src-html:before { content: 'HTML'; } + pre.src-idl:before { content: 'IDL'; } + pre.src-mercury:before { content: 'Mercury'; } + pre.src-metapost:before { content: 'MetaPost'; } + pre.src-modula-2:before { content: 'Modula-2'; } + pre.src-pascal:before { content: 'Pascal'; } + pre.src-ps:before { content: 'PostScript'; } + pre.src-prolog:before { content: 'Prolog'; } + pre.src-simula:before { content: 'Simula'; } + pre.src-tcl:before { content: 'tcl'; } + pre.src-tex:before { content: 'TeX'; } + pre.src-plain-tex:before { content: 'Plain TeX'; } + pre.src-verilog:before { content: 'Verilog'; } + pre.src-vhdl:before { content: 'VHDL'; } + pre.src-xml:before { content: 'XML'; } + pre.src-nxml:before { content: 'XML'; } + /* add a generic configuration mode; LaTeX export needs an additional + (add-to-list 'org-latex-listings-langs '(conf \" \")) in .emacs */ + pre.src-conf:before { content: 'Configuration File'; } table { border-collapse:collapse; } caption.t-above { caption-side: top; } caption.t-bottom { caption-side: bottom; } td, th { vertical-align:top; } - th.right { text-align: center; } - th.left { text-align: center; } - th.center { text-align: center; } - td.right { text-align: right; } - td.left { text-align: left; } - td.center { text-align: center; } + th.org-right { text-align: center; } + th.org-left { text-align: center; } + th.org-center { text-align: center; } + td.org-right { text-align: right; } + td.org-left { text-align: left; } + td.org-center { text-align: center; } dt { font-weight: bold; } - .footpara:nth-child(2) { display: inline; } - .footpara { display: block; } + .footpara { display: inline; } .footdef { margin-bottom: 1em; } .figure { padding: 1em; } .figure p { text-align: center; } @@ -308,6 +446,7 @@ for the JavaScript code in this tag. { font-size: 10px; font-weight: bold; white-space: nowrap; } .org-info-js_search-highlight { background-color: #ffff00; color: #000000; font-weight: bold; } + .org-svg { width: 90%; } /*]]>*/--> </style>" "The default style specification for exported HTML files. @@ -385,7 +524,7 @@ means to use the maximum value consistent with other options." * @licstart The following is the entire license notice for the * JavaScript code in %SCRIPT_PATH. * - * Copyright (C) 2012-2013 Free Software Foundation, Inc. + * Copyright (C) 2012-2017 Free Software Foundation, Inc. * * * The JavaScript code in this tag is free software: you can @@ -414,7 +553,7 @@ means to use the maximum value consistent with other options." @licstart The following is the entire license notice for the JavaScript code in this tag. -Copyright (C) 2012-2013 Free Software Foundation, Inc. +Copyright (C) 2012-2017 Free Software Foundation, Inc. The JavaScript code in this tag is free software: you can redistribute it and/or modify it under the terms of the GNU @@ -447,23 +586,24 @@ Option settings will replace the %MANAGER-OPTIONS cookie." :package-version '(Org . "8.0") :type 'string) -(defun org-html-infojs-install-script (exp-plist backend) +(defun org-html-infojs-install-script (exp-plist _backend) "Install script in export options when appropriate. EXP-PLIST is a plist containing export options. BACKEND is the export back-end currently used." (unless (or (memq 'body-only (plist-get exp-plist :export-options)) - (not org-html-use-infojs) - (and (eq org-html-use-infojs 'when-configured) - (or (not (plist-get exp-plist :infojs-opt)) - (string= "" (plist-get exp-plist :infojs-opt)) - (string-match "\\<view:nil\\>" - (plist-get exp-plist :infojs-opt))))) - (let* ((template org-html-infojs-template) + (not (plist-get exp-plist :html-use-infojs)) + (and (eq (plist-get exp-plist :html-use-infojs) 'when-configured) + (let ((opt (plist-get exp-plist :infojs-opt))) + (or (not opt) + (string= "" opt) + (string-match "\\<view:nil\\>" opt))))) + (let* ((template (plist-get exp-plist :html-infojs-template)) (ptoc (plist-get exp-plist :with-toc)) (hlevels (plist-get exp-plist :headline-levels)) (sdepth hlevels) (tdepth (if (integerp ptoc) (min ptoc hlevels) hlevels)) (options (plist-get exp-plist :infojs-opt)) + (infojs-opt (plist-get exp-plist :html-infojs-options)) (table org-html-infojs-opts-table) style) (dolist (entry table) @@ -472,7 +612,7 @@ export back-end currently used." ;; Compute default values for script option OPT from ;; `org-html-infojs-options' variable. (default - (let ((default (cdr (assq opt org-html-infojs-options)))) + (let ((default (cdr (assq opt infojs-opt)))) (if (and (symbolp default) (not (memq default '(t nil)))) (plist-get exp-plist default) default))) @@ -483,21 +623,21 @@ export back-end currently used." options)) (match-string 1 options) default))) - (case opt - (path (setq template - (replace-regexp-in-string - "%SCRIPT_PATH" val template t t))) - (sdepth (when (integerp (read val)) - (setq sdepth (min (read val) sdepth)))) - (tdepth (when (integerp (read val)) - (setq tdepth (min (read val) tdepth)))) - (otherwise (setq val - (cond - ((or (eq val t) (equal val "t")) "1") - ((or (eq val nil) (equal val "nil")) "0") - ((stringp val) val) - (t (format "%s" val)))) - (push (cons var val) style))))) + (pcase opt + (`path (setq template + (replace-regexp-in-string + "%SCRIPT_PATH" val template t t))) + (`sdepth (when (integerp (read val)) + (setq sdepth (min (read val) sdepth)))) + (`tdepth (when (integerp (read val)) + (setq tdepth (min (read val) tdepth)))) + (_ (setq val + (cond + ((or (eq val t) (equal val "t")) "1") + ((or (eq val nil) (equal val "nil")) "0") + ((stringp val) val) + (t (format "%s" val)))) + (push (cons var val) style))))) ;; Now we set the depth of the *generated* TOC to SDEPTH, ;; because the toc will actually determine the splitting. How ;; much of the toc will actually be displayed is governed by the @@ -509,9 +649,9 @@ export back-end currently used." (push (cons "TOC_DEPTH" tdepth) style) ;; Build style string. (setq style (mapconcat - (lambda (x) (format "org_html_manager.set(\"%s\", \"%s\");" - (car x) - (cdr x))) + (lambda (x) + (format "org_html_manager.set(\"%s\", \"%s\");" + (car x) (cdr x))) style "\n")) (when (and style (> (length style) 0)) (and (string-match "%MANAGER_OPTIONS" template) @@ -561,17 +701,9 @@ Warning: non-nil may break indentation of source code blocks." :package-version '(Org . "8.0") :type 'boolean) -(defcustom org-html-use-unicode-chars nil - "Non-nil means to use unicode characters instead of HTML entities." - :group 'org-export-html - :version "24.4" - :package-version '(Org . "8.0") - :type 'boolean) - ;;;; Drawers -(defcustom org-html-format-drawer-function - (lambda (name contents) contents) +(defcustom org-html-format-drawer-function (lambda (_name contents) contents) "Function called to format a drawer in HTML code. The function must accept two parameters: @@ -628,28 +760,30 @@ document title." :group 'org-export-html :type 'integer) -(defcustom org-html-format-headline-function 'ignore +(defcustom org-html-format-headline-function + 'org-html-format-headline-default-function "Function to format headline text. -This function will be called with 5 arguments: +This function will be called with six arguments: TODO the todo keyword (string or nil). TODO-TYPE the type of todo (symbol: `todo', `done', nil) PRIORITY the priority of the headline (integer or nil) TEXT the main headline text (string). TAGS the tags (string or nil). +INFO the export options (plist). The function result will be used in the section format string." :group 'org-export-html - :version "24.4" - :package-version '(Org . "8.0") + :version "26.1" + :package-version '(Org . "8.3") :type 'function) ;;;; HTML-specific -(defcustom org-html-allow-name-attribute-in-anchors t +(defcustom org-html-allow-name-attribute-in-anchors nil "When nil, do not set \"name\" attribute in anchors. -By default, anchors are formatted with both \"id\" and \"name\" -attributes, when appropriate." +By default, when appropriate, anchors are formatted with \"id\" +but without \"name\" attribute." :group 'org-export-html :version "24.4" :package-version '(Org . "8.0") @@ -657,21 +791,23 @@ attributes, when appropriate." ;;;; Inlinetasks -(defcustom org-html-format-inlinetask-function 'ignore +(defcustom org-html-format-inlinetask-function + 'org-html-format-inlinetask-default-function "Function called to format an inlinetask in HTML code. -The function must accept six parameters: +The function must accept seven parameters: TODO the todo keyword, as a string TODO-TYPE the todo type, a symbol among `todo', `done' and nil. PRIORITY the inlinetask priority, as a string NAME the inlinetask name, as a string. TAGS the inlinetask tags, as a list of strings. CONTENTS the contents of the inlinetask, as a string. + INFO the export options, as a plist The function should return the string to be exported." :group 'org-export-html - :version "24.4" - :package-version '(Org . "8.0") + :version "26.1" + :package-version '(Org . "8.3") :type 'function) ;;;; LaTeX @@ -685,24 +821,20 @@ fragments. This option can also be set with the +OPTIONS line, e.g. \"tex:mathjax\". Allowed values are: -nil Ignore math snippets. -`verbatim' Keep everything in verbatim -`dvipng' Process the LaTeX fragments to images. This will also - include processing of non-math environments. -`imagemagick' Convert the LaTeX fragments to pdf files and use - imagemagick to convert pdf files to png files. -`mathjax' Do MathJax preprocessing and arrange for MathJax.js to - be loaded. -t Synonym for `mathjax'." + nil Ignore math snippets. + `verbatim' Keep everything in verbatim + `mathjax', t Do MathJax preprocessing and arrange for MathJax.js to + be loaded. + SYMBOL Any symbol defined in `org-preview-latex-process-alist', + e.g., `dvipng'." :group 'org-export-html :version "24.4" :package-version '(Org . "8.0") :type '(choice (const :tag "Do not process math in any way" nil) - (const :tag "Use dvipng to make images" dvipng) - (const :tag "Use imagemagick to make images" imagemagick) + (const :tag "Leave math verbatim" verbatim) (const :tag "Use MathJax to display math" mathjax) - (const :tag "Leave math verbatim" verbatim))) + (symbol :tag "Convert to image to display math" :value dvipng))) ;;;; Links :: Generic @@ -710,11 +842,11 @@ t Synonym for `mathjax'." "Non-nil means make file links to `file.org' point to `file.html'. When `org-mode' is exporting an `org-mode' file to HTML, links to non-html files are directly put into a href tag in HTML. -However, links to other Org-mode files (recognized by the -extension `.org') should become links to the corresponding html +However, links to other Org files (recognized by the extension +\".org\") should become links to the corresponding HTML file, assuming that the linked `org-mode' file will also be converted to HTML. -When nil, the links still point to the plain `.org' file." +When nil, the links still point to the plain \".org\" file." :group 'org-export-html :type 'boolean) @@ -745,22 +877,20 @@ link's path." ;;;; Plain Text -(defcustom org-html-protect-char-alist +(defvar org-html-protect-char-alist '(("&" . "&") ("<" . "<") (">" . ">")) - "Alist of characters to be converted by `org-html-protect'." - :group 'org-export-html - :type '(repeat (cons (string :tag "Character") - (string :tag "HTML equivalent")))) + "Alist of characters to be converted by `org-html-encode-plain-text'.") ;;;; Src Block (defcustom org-html-htmlize-output-type 'inline-css "Output type to be used by htmlize when formatting code snippets. -Choices are `css', to export the CSS selectors only, or `inline-css', to -export the CSS attribute values inline in the HTML. We use as default -`inline-css', in order to make the resulting HTML self-containing. +Choices are `css' to export the CSS selectors only,`inline-css' +to export the CSS attribute values inline in the HTML or `nil' to +export plain text. We use as default `inline-css', in order to +make the resulting HTML self-containing. However, this will fail when using Emacs in batch mode for export, because then no rich font definitions are in place. It will also not be good if @@ -771,9 +901,9 @@ a style file to define the look of these classes. To get a start for your css file, start Emacs session and make sure that all the faces you are interested in are defined, for example by loading files in all modes you want. Then, use the command -\\[org-html-htmlize-generate-css] to extract class definitions." +`\\[org-html-htmlize-generate-css]' to extract class definitions." :group 'org-export-html - :type '(choice (const css) (const inline-css))) + :type '(choice (const css) (const inline-css) (const nil))) (defcustom org-html-htmlize-font-prefix "org-" "The prefix for CSS class names for htmlize font specifications." @@ -796,7 +926,7 @@ When exporting to HTML5, these values will be disregarded." :value-type (string :tag "Value"))) (defcustom org-html-table-header-tags '("<th scope=\"%s\"%s>" . "</th>") - "The opening tag for table header fields. + "The opening and ending tags for table header fields. This is customizable so that alignment options can be specified. The first %s will be filled with the scope of the field, either row or col. The second %s will be replaced by a style entry to align the field. @@ -806,7 +936,7 @@ See also the variable `org-html-table-align-individual-fields'." :type '(cons (string :tag "Opening tag") (string :tag "Closing tag"))) (defcustom org-html-table-data-tags '("<td%s>" . "</td>") - "The opening tag for table data fields. + "The opening and ending tags for table data fields. This is customizable so that alignment options can be specified. The first %s will be filled with the scope of the field, either row or col. The second %s will be replaced by a style entry to align the field. @@ -814,43 +944,50 @@ See also the variable `org-html-table-align-individual-fields'." :group 'org-export-html :type '(cons (string :tag "Opening tag") (string :tag "Closing tag"))) -(defcustom org-html-table-row-tags '("<tr>" . "</tr>") - "The opening and ending tags for table rows. +(defcustom org-html-table-row-open-tag "<tr>" + "The opening tag for table rows. This is customizable so that alignment options can be specified. -Instead of strings, these can be Lisp forms that will be +Instead of strings, these can be a Lisp function that will be evaluated for each row in order to construct the table row tags. -During evaluation, these variables will be dynamically bound so that -you can reuse them: +The function will be called with these arguments: - `row-number': row number (0 is the first row) - `rowgroup-number': group number of current row - `start-rowgroup-p': non-nil means the row starts a group - `end-rowgroup-p': non-nil means the row ends a group - `top-row-p': non-nil means this is the top row - `bottom-row-p': non-nil means this is the bottom row + `number': row number (0 is the first row) + `group-number': group number of current row + `start-group?': non-nil means the row starts a group + `end-group?': non-nil means the row ends a group + `top?': non-nil means this is the top row + `bottom?': non-nil means this is the bottom row For example: -\(setq org-html-table-row-tags - (cons \\='(cond (top-row-p \"<tr class=\\\"tr-top\\\">\") - (bottom-row-p \"<tr class=\\\"tr-bottom\\\">\") - (t (if (= (mod row-number 2) 1) - \"<tr class=\\\"tr-odd\\\">\" - \"<tr class=\\\"tr-even\\\">\"))) - \"</tr>\")) + (setq org-html-table-row-open-tag + (lambda (number group-number start-group? end-group-p top? bottom?) + (cond (top? \"<tr class=\\\"tr-top\\\">\") + (bottom? \"<tr class=\\\"tr-bottom\\\">\") + (t (if (= (mod number 2) 1) + \"<tr class=\\\"tr-odd\\\">\" + \"<tr class=\\\"tr-even\\\">\"))))) will use the \"tr-top\" and \"tr-bottom\" classes for the top row and the bottom row, and otherwise alternate between \"tr-odd\" and \"tr-even\" for odd and even rows." :group 'org-export-html - :type '(cons - (choice :tag "Opening tag" - (string :tag "Specify") - (sexp)) - (choice :tag "Closing tag" - (string :tag "Specify") - (sexp)))) + :type '(choice :tag "Opening tag" + (string :tag "Specify") + (function))) + +(defcustom org-html-table-row-close-tag "</tr>" + "The closing tag for table rows. +This is customizable so that alignment options can be specified. +Instead of strings, this can be a Lisp function that will be +evaluated for each row in order to construct the table row tags. + +See documentation of `org-html-table-row-open-tag'." + :group 'org-export-html + :type '(choice :tag "Closing tag" + (string :tag "Specify") + (function))) (defcustom org-html-table-align-individual-fields t "Non-nil means attach style attributes for alignment to each table field. @@ -921,7 +1058,10 @@ publishing, with :html-doctype." :group 'org-export-html :version "24.4" :package-version '(Org . "8.0") - :type 'string) + :type (append + '(choice) + (mapcar (lambda (x) `(const ,(car x))) org-html-doctype-alist) + '((string :tag "Custom doctype" )))) (defcustom org-html-html5-fancy nil "Non-nil means using new HTML5 elements. @@ -954,7 +1094,7 @@ org-info.js for your website." (content "div" "content") (postamble "div" "postamble")) "Alist of the three section elements for HTML export. -The car of each entry is one of 'preamble, 'content or 'postamble. +The car of each entry is one of `preamble', `content' or `postamble'. The cdrs of each entry are the ELEMENT_TYPE and ID for each section of the exported document. @@ -973,6 +1113,41 @@ org-info.js for your website." (list :tag "Postamble" (const :format "" postamble) (string :tag " id") (string :tag "element")))) +(defconst org-html-checkbox-types + '((unicode . + ((on . "☑") (off . "☐") (trans . "☐"))) + (ascii . + ((on . "<code>[X]</code>") + (off . "<code>[ ]</code>") + (trans . "<code>[-]</code>"))) + (html . + ((on . "<input type='checkbox' checked='checked' />") + (off . "<input type='checkbox' />") + (trans . "<input type='checkbox' />")))) + "Alist of checkbox types. +The cdr of each entry is an alist list three checkbox types for +HTML export: `on', `off' and `trans'. + +The choices are: + `unicode' Unicode characters (HTML entities) + `ascii' ASCII characters + `html' HTML checkboxes + +Note that only the ascii characters implement tri-state +checkboxes. The other two use the `off' checkbox for `trans'.") + +(defcustom org-html-checkbox-type 'ascii + "The type of checkboxes to use for HTML export. +See `org-html-checkbox-types' for for the values used for each +option." + :group 'org-export-html + :version "24.4" + :package-version '(Org . "8.0") + :type '(choice + (const :tag "ASCII characters" ascii) + (const :tag "Unicode characters" unicode) + (const :tag "HTML checkboxes" html))) + (defcustom org-html-metadata-timestamp-format "%Y-%m-%d %a %H:%M" "Format used for timestamps in preamble, postamble and metadata. See `format-time-string' for more information on its components." @@ -984,82 +1159,107 @@ See `format-time-string' for more information on its components." ;;;; Template :: Mathjax (defcustom org-html-mathjax-options - '((path "http://orgmode.org/mathjax/MathJax.js") + '((path "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-AMS_HTML" ) (scale "100") (align "center") - (indent "2em") - (mathml nil)) + (font "TeX") + (linebreaks "false") + (autonumber "AMS") + (indent "0em") + (multlinewidth "85%") + (tagindent ".8em") + (tagside "right")) "Options for MathJax setup. -path The path where to find MathJax -scale Scaling for the HTML-CSS backend, usually between 100 and 133 -align How to align display math: left, center, or right -indent If align is not center, how far from the left/right side? -mathml Should a MathML player be used if available? - This is faster and reduces bandwidth use, but currently - sometimes has lower spacing quality. Therefore, the default is - nil. When browsers get better, this switch can be flipped. +Alist of the following elements. All values are strings. + +path The path to MathJax. +scale Scaling with HTML-CSS, MathML and SVG output engines. +align How to align display math: left, center, or right. +font The font to use with HTML-CSS and SVG output. As of MathJax 2.5 + the following values are understood: \"TeX\", \"STIX-Web\", + \"Asana-Math\", \"Neo-Euler\", \"Gyre-Pagella\", + \"Gyre-Termes\", and \"Latin-Modern\". +linebreaks Let MathJax perform automatic linebreaks. Valid values + are \"true\" and \"false\". +indent If align is not center, how far from the left/right side? + Valid values are \"left\" and \"right\" +multlinewidth The width of the multline environment. +autonumber How to number equations. Valid values are \"None\", + \"all\" and \"AMS Math\". +tagindent The amount tags are indented. +tagside Which side to show tags/labels on. Valid values are + \"left\" and \"right\" You can also customize this for each buffer, using something like -#+MATHJAX: scale:\"133\" align:\"right\" mathml:t path:\"/MathJax/\"" +#+HTML_MATHJAX: align: left indent: 5em tagside: left font: Neo-Euler + +For further information about MathJax options, see the MathJax documentation: + + http://docs.mathjax.org/" :group 'org-export-html + :package-version '(Org . "8.3") :type '(list :greedy t - (list :tag "path (the path from where to load MathJax.js)" - (const :format " " path) (string)) - (list :tag "scale (scaling for the displayed math)" - (const :format " " scale) (string)) - (list :tag "align (alignment of displayed equations)" - (const :format " " align) (string)) - (list :tag "indent (indentation with left or right alignment)" - (const :format " " indent) (string)) - (list :tag "mathml (should MathML display be used is possible)" - (const :format " " mathml) (boolean)))) + (list :tag "path (the path from where to load MathJax.js)" + (const :format " " path) (string)) + (list :tag "scale (scaling for the displayed math)" + (const :format " " scale) (string)) + (list :tag "align (alignment of displayed equations)" + (const :format " " align) (string)) + (list :tag "font (used to display math)" + (const :format " " font) + (choice (const "TeX") + (const "STIX-Web") + (const "Asana-Math") + (const "Neo-Euler") + (const "Gyre-Pagella") + (const "Gyre-Termes") + (const "Latin-Modern"))) + (list :tag "linebreaks (automatic line-breaking)" + (const :format " " linebreaks) + (choice (const "true") + (const "false"))) + (list :tag "autonumber (when should equations be numbered)" + (const :format " " autonumber) + (choice (const "AMS") + (const "None") + (const "All"))) + (list :tag "indent (indentation with left or right alignment)" + (const :format " " indent) (string)) + (list :tag "multlinewidth (width to use for the multline environment)" + (const :format " " multlinewidth) (string)) + (list :tag "tagindent (the indentation of tags from left or right)" + (const :format " " tagindent) (string)) + (list :tag "tagside (location of tags)" + (const :format " " tagside) + (choice (const "left") + (const "right"))))) (defcustom org-html-mathjax-template - "<script type=\"text/javascript\" src=\"%PATH\"></script> -<script type=\"text/javascript\"> -<!--/*--><![CDATA[/*><!--*/ + "<script type=\"text/x-mathjax-config\"> MathJax.Hub.Config({ - // Only one of the two following lines, depending on user settings - // First allows browser-native MathML display, second forces HTML/CSS - :MMLYES: config: [\"MMLorHTML.js\"], jax: [\"input/TeX\"], - :MMLNO: jax: [\"input/TeX\", \"output/HTML-CSS\"], - extensions: [\"tex2jax.js\",\"TeX/AMSmath.js\",\"TeX/AMSsymbols.js\", - \"TeX/noUndefined.js\"], - tex2jax: { - inlineMath: [ [\"\\\\(\",\"\\\\)\"] ], - displayMath: [ ['$$','$$'], [\"\\\\[\",\"\\\\]\"], [\"\\\\begin{displaymath}\",\"\\\\end{displaymath}\"] ], - skipTags: [\"script\",\"noscript\",\"style\",\"textarea\",\"pre\",\"code\"], - ignoreClass: \"tex2jax_ignore\", - processEscapes: false, - processEnvironments: true, - preview: \"TeX\" - }, - showProcessingMessages: true, displayAlign: \"%ALIGN\", displayIndent: \"%INDENT\", - \"HTML-CSS\": { - scale: %SCALE, - availableFonts: [\"STIX\",\"TeX\"], - preferredFont: \"TeX\", - webFont: \"TeX\", - imageFont: \"TeX\", - showMathMenu: true, - }, - MMLorHTML: { - prefer: { - MSIE: \"MML\", - Firefox: \"MML\", - Opera: \"HTML\", - other: \"HTML\" + \"HTML-CSS\": { scale: %SCALE, + linebreaks: { automatic: \"%LINEBREAKS\" }, + webFont: \"%FONT\" + }, + SVG: {scale: %SCALE, + linebreaks: { automatic: \"%LINEBREAKS\" }, + font: \"%FONT\"}, + NativeMML: {scale: %SCALE}, + TeX: { equationNumbers: {autoNumber: \"%AUTONUMBER\"}, + MultLineWidth: \"%MULTLINEWIDTH\", + TagSide: \"%TAGSIDE\", + TagIndent: \"%TAGINDENT\" } - } - }); -/*]]>*///--> -</script>" - "The MathJax setup for XHTML files." +}); +</script> +<script type=\"text/javascript\" + src=\"%PATH\"></script>" + "The MathJax template. See also `org-html-mathjax-options'." :group 'org-export-html :type 'string) @@ -1068,7 +1268,7 @@ You can also customize this for each buffer, using something like (defcustom org-html-postamble 'auto "Non-nil means insert a postamble in HTML export. -When set to 'auto, check against the +When set to `auto', check against the `org-export-with-author/email/creator/date' variables to set the content of the postamble. When set to a string, use this string as the postamble. When t, insert a string as defined by the @@ -1101,6 +1301,7 @@ The second element of each list is a format string to format the postamble itself. This format string can contain these elements: %t stands for the title. + %s stands for the subtitle. %a stands for the author's name. %e stands for the author's email. %d stands for the date. @@ -1123,7 +1324,7 @@ like that: \"%%\"." :type 'string) (defcustom org-html-creator-string - (format "<a href=\"http://www.gnu.org/software/emacs/\">Emacs</a> %s (<a href=\"http://orgmode.org\">Org</a> mode %s)" + (format "<a href=\"https://www.gnu.org/software/emacs/\">Emacs</a> %s (<a href=\"http://orgmode.org\">Org</a> mode %s)" emacs-version (if (fboundp 'org-version) (org-version) "unknown version")) "Information about the creator of the HTML document. @@ -1165,6 +1366,7 @@ The second element of each list is a format string to format the preamble itself. This format string can contain these elements: %t stands for the title. + %s stands for the subtitle. %a stands for the author's name. %e stands for the author's email. %d stands for the date. @@ -1216,8 +1418,6 @@ ignored." ;;;; Template :: Scripts -(define-obsolete-variable-alias - 'org-html-style-include-scripts 'org-html-head-include-scripts "24.4") (defcustom org-html-head-include-scripts t "Non-nil means include the JavaScript snippets in exported HTML files. The actual script is defined in `org-html-scripts' and should @@ -1229,8 +1429,6 @@ not be modified." ;;;; Template :: Styles -(define-obsolete-variable-alias - 'org-html-style-include-default 'org-html-head-include-default-style "24.4") (defcustom org-html-head-include-default-style t "Non-nil means include the default style in exported HTML files. The actual style is defined in `org-html-style-default' and @@ -1243,7 +1441,6 @@ style information." ;;;###autoload (put 'org-html-head-include-default-style 'safe-local-variable 'booleanp) -(define-obsolete-variable-alias 'org-html-style 'org-html-head "24.4") (defcustom org-html-head "" "Org-wide head definitions for exported HTML files. @@ -1293,6 +1490,88 @@ or for publication projects using the :html-head-extra property." ;;;###autoload (put 'org-html-head-extra 'safe-local-variable 'stringp) +;;;; Template :: Viewport + +(defcustom org-html-viewport '((width "device-width") + (initial-scale "1") + (minimum-scale "") + (maximum-scale "") + (user-scalable "")) + "Viewport options for mobile-optimized sites. + +The following values are recognized + +width Size of the viewport. +initial-scale Zoom level when the page is first loaded. +minimum-scale Minimum allowed zoom level. +maximum-scale Maximum allowed zoom level. +user-scalable Whether zoom can be changed. + +The viewport meta tag is inserted if this variable is non-nil. + +See the following site for a reference: +https://developer.mozilla.org/en-US/docs/Mozilla/Mobile/Viewport_meta_tag" + :group 'org-export-html + :version "26.1" + :package-version '(Org . "8.3") + :type '(choice (const :tag "Disable" nil) + (list :tag "Enable" + (list :tag "Width of viewport" + (const :format " " width) + (choice (const :tag "unset" "") + (string))) + (list :tag "Initial scale" + (const :format " " initial-scale) + (choice (const :tag "unset" "") + (string))) + (list :tag "Minimum scale/zoom" + (const :format " " minimum-scale) + (choice (const :tag "unset" "") + (string))) + (list :tag "Maximum scale/zoom" + (const :format " " maximum-scale) + (choice (const :tag "unset" "") + (string))) + (list :tag "User scalable/zoomable" + (const :format " " user-scalable) + (choice (const :tag "unset" "") + (const "true") + (const "false")))))) + +;; Handle source code blocks with Klipse + +(defcustom org-html-klipsify-src nil + "When non-nil, source code blocks are editable in exported presentation." + :group 'org-export-html + :package-version '(Org . "9.1") + :type 'boolean) + +(defcustom org-html-klipse-css + "https://storage.googleapis.com/app.klipse.tech/css/codemirror.css" + "Location of the codemirror CSS file for use with klipse." + :group 'org-export-html + :package-version '(Org . "9.1") + :type 'string) + +(defcustom org-html-klipse-js + "https://storage.googleapis.com/app.klipse.tech/plugin_prod/js/klipse_plugin.min.js" + "Location of the klipse javascript file." + :group 'org-export-html + :type 'string) + +(defcustom org-html-klipse-selection-script + "window.klipse_settings = {selector_eval_html: '.src-html', + selector_eval_js: '.src-js', + selector_eval_python_client: '.src-python', + selector_eval_scheme: '.src-scheme', + selector: '.src-clojure', + selector_eval_ruby: '.src-ruby'};" + "Javascript snippet to activate klipse." + :group 'org-export-html + :package-version '(Org . "9.1") + :type 'string) + + ;;;; Todos (defcustom org-html-todo-kwd-class-prefix "" @@ -1304,7 +1583,7 @@ CSS classes, then this prefix can be very useful." :group 'org-export-html :type 'string) - + ;;; Internal Functions (defun org-html-xhtml-p (info) @@ -1315,22 +1594,33 @@ CSS classes, then this prefix can be very useful." (let ((dt (downcase (plist-get info :html-doctype)))) (member dt '("html5" "xhtml5" "<!doctype html>")))) +(defun org-html--html5-fancy-p (info) + "Non-nil when exporting to HTML5 with fancy elements. +INFO is the current state of the export process, as a plist." + (and (plist-get info :html-html5-fancy) + (org-html-html5-p info))) + (defun org-html-close-tag (tag attr info) - (concat "<" tag " " attr + "Return close-tag for string TAG. +ATTR specifies additional attributes. INFO is a property list +containing current export state." + (concat "<" tag + (org-string-nw-p (concat " " attr)) (if (org-html-xhtml-p info) " />" ">"))) (defun org-html-doctype (info) - "Return correct html doctype tag from `org-html-doctype-alist', -or the literal value of :html-doctype from INFO if :html-doctype -is not found in the alist. -INFO is a plist used as a communication channel." + "Return correct HTML doctype tag. +INFO is a plist used as a communication channel. Doctype tag is +extracted from `org-html-doctype-alist', or the literal value +of :html-doctype from INFO if :html-doctype is not found in the +alist." (let ((dt (plist-get info :html-doctype))) (or (cdr (assoc dt org-html-doctype-alist)) dt))) (defun org-html--make-attribute-string (attributes) "Return a list of attributes, as a string. -ATTRIBUTES is a plist where values are either strings or nil. An -attributes with a nil value will be omitted from the result." +ATTRIBUTES is a plist where values are either strings or nil. An +attribute with a nil value will be omitted from the result." (let (output) (dolist (item attributes (mapconcat 'identity (nreverse output) " ")) (cond ((null item) (pop output)) @@ -1345,15 +1635,13 @@ attributes with a nil value will be omitted from the result." INFO is a plist used as a communication channel. When optional arguments CAPTION and LABEL are given, use them for caption and \"id\" attribute." - (let ((html5-fancy (and (org-html-html5-p info) - (plist-get info :html-html5-fancy)))) - (format (if html5-fancy "\n<figure%s>%s%s\n</figure>" - "\n<div%s class=\"figure\">%s%s\n</div>") + (let ((html5-fancy (org-html--html5-fancy-p info))) + (format (if html5-fancy "\n<figure%s>\n%s%s\n</figure>" + "\n<div%s class=\"figure\">\n%s%s\n</div>") ;; ID. - (if (not (org-string-nw-p label)) "" - (format " id=\"%s\"" (org-export-solidify-link-text label))) + (if (org-string-nw-p label) (format " id=\"%s\"" label) "") ;; Contents. - (format "\n<p>%s</p>" contents) + (if html5-fancy contents (format "<p>%s</p>" contents)) ;; Caption. (if (not (org-string-nw-p caption)) "" (format (if html5-fancy "\n<figcaption>%s</figcaption>" @@ -1366,17 +1654,42 @@ SOURCE is a string specifying the location of the image. ATTRIBUTES is a plist, as returned by `org-export-read-attribute'. INFO is a plist used as a communication channel." - (org-html-close-tag - "img" - (org-html--make-attribute-string - (org-combine-plists - (list :src source - :alt (if (string-match-p "^ltxpng/" source) - (org-html-encode-plain-text - (org-find-text-property-in-string 'org-latex-src source)) - (file-name-nondirectory source))) - attributes)) - info)) + (if (string= "svg" (file-name-extension source)) + (org-html--svg-image source attributes info) + (org-html-close-tag + "img" + (org-html--make-attribute-string + (org-combine-plists + (list :src source + :alt (if (string-match-p "^ltxpng/" source) + (org-html-encode-plain-text + (org-find-text-property-in-string 'org-latex-src source)) + (file-name-nondirectory source))) + attributes)) + info))) + +(defun org-html--svg-image (source attributes info) + "Return \"object\" embedding svg file SOURCE with given ATTRIBUTES. +INFO is a plist used as a communication channel. + +The special attribute \"fallback\" can be used to specify a +fallback image file to use if the object embedding is not +supported. CSS class \"org-svg\" is assigned as the class of the +object unless a different class is specified with an attribute." + (let ((fallback (plist-get attributes :fallback)) + (attrs (org-html--make-attribute-string + (org-combine-plists + ;; Remove fallback attribute, which is not meant to + ;; appear directly in the attributes string, and + ;; provide a default class if none is set. + '(:class "org-svg") attributes '(:fallback nil))))) + (format "<object type=\"image/svg+xml\" data=\"%s\" %s>\n%s</object>" + source + attrs + (if fallback + (org-html-close-tag + "img" (format "src=\"%s\" %s" fallback attrs) info) + "Sorry, your browser does not support SVG.")))) (defun org-html--textarea-block (element) "Transcode ELEMENT into a textarea block. @@ -1388,7 +1701,7 @@ ELEMENT is either a src block or an example block." (or (plist-get attr :height) (org-count-lines code)) code))) -(defun org-html--has-caption-p (element &optional info) +(defun org-html--has-caption-p (element &optional _info) "Non-nil when ELEMENT has a caption affiliated keyword. INFO is a plist used as a communication channel. This function is meant to be used as a predicate for `org-export-get-ordinal' or @@ -1423,7 +1736,8 @@ If you then set `org-html-htmlize-output-type' to `css', calls to the function `org-html-htmlize-region-for-paste' will produce code that uses these same face definitions." (interactive) - (require 'htmlize) + (or (require 'htmlize nil t) + (error "Please install htmlize from https://github.com/hniksic/emacs-htmlize")) (and (get-buffer "*html*") (kill-buffer "*html*")) (with-temp-buffer (let ((fl (face-list)) @@ -1435,7 +1749,7 @@ produce code that uses these same face definitions." (when (and (symbolp f) (or (not i) (not (listp i)))) (insert (org-add-props (copy-sequence "1") nil 'face f)))) (htmlize-region (point-min) (point-max)))) - (org-pop-to-buffer-same-window "*html*") + (pop-to-buffer-same-window "*html*") (goto-char (point-min)) (if (re-search-forward "<style" nil t) (delete-region (point-min) (match-beginning 0))) @@ -1447,59 +1761,44 @@ produce code that uses these same face definitions." (defun org-html--make-string (n string) "Build a string by concatenating N times STRING." - (let (out) (dotimes (i n out) (setq out (concat string out))))) + (let (out) (dotimes (_ n out) (setq out (concat string out))))) (defun org-html-fix-class-name (kwd) ; audit callers of this function "Turn todo keyword KWD into a valid class name. Replaces invalid characters with \"_\"." - (save-match-data - (while (string-match "[^a-zA-Z0-9_]" kwd) - (setq kwd (replace-match "_" t t kwd)))) - kwd) - -(defun org-html-format-footnote-reference (n def refcnt) - "Format footnote reference N with definition DEF into HTML." - (let ((extra (if (= refcnt 1) "" (format ".%d" refcnt)))) - (format org-html-footnote-format - (let* ((id (format "fnr.%s%s" n extra)) - (href (format " href=\"#fn.%s\"" n)) - (attributes (concat " class=\"footref\"" href))) - (org-html--anchor id n attributes))))) - -(defun org-html-format-footnotes-section (section-name definitions) - "Format footnotes section SECTION-NAME." - (if (not definitions) "" - (format org-html-footnotes-section section-name definitions))) - -(defun org-html-format-footnote-definition (fn) - "Format the footnote definition FN." - (let ((n (car fn)) (def (cdr fn))) - (format - "<div class=\"footdef\">%s %s</div>\n" - (format org-html-footnote-format - (let* ((id (format "fn.%s" n)) - (href (format " href=\"#fnr.%s\"" n)) - (attributes (concat " class=\"footnum\"" href))) - (org-html--anchor id n attributes))) - def))) + (replace-regexp-in-string "[^a-zA-Z0-9_]" "_" kwd nil t)) (defun org-html-footnote-section (info) "Format the footnote section. INFO is a plist used as a communication channel." - (let* ((fn-alist (org-export-collect-footnote-definitions - (plist-get info :parse-tree) info)) + (let* ((fn-alist (org-export-collect-footnote-definitions info)) (fn-alist - (loop for (n type raw) in fn-alist collect - (cons n (if (eq (org-element-type raw) 'org-data) - (org-trim (org-export-data raw info)) - (format "<p>%s</p>" - (org-trim (org-export-data raw info)))))))) + (cl-loop for (n _type raw) in fn-alist collect + (cons n (if (eq (org-element-type raw) 'org-data) + (org-trim (org-export-data raw info)) + (format "<div class=\"footpara\">%s</div>" + (org-trim (org-export-data raw info)))))))) (when fn-alist - (org-html-format-footnotes-section + (format + (plist-get info :html-footnotes-section) (org-html--translate "Footnotes" info) (format "\n%s\n" - (mapconcat 'org-html-format-footnote-definition fn-alist "\n")))))) + (mapconcat + (lambda (fn) + (let ((n (car fn)) (def (cdr fn))) + (format + "<div class=\"footdef\">%s %s</div>\n" + (format + (plist-get info :html-footnote-format) + (org-html--anchor + (format "fn.%d" n) + n + (format " class=\"footnum\" href=\"#fnr.%d\"" n) + info)) + def))) + fn-alist + "\n")))))) ;;; Template @@ -1507,59 +1806,77 @@ INFO is a plist used as a communication channel." (defun org-html--build-meta-info (info) "Return meta tags for exported document. INFO is a plist used as a communication channel." - (let ((protect-string - (lambda (str) - (replace-regexp-in-string - "\"" """ (org-html-encode-plain-text str)))) - (title (org-export-data (plist-get info :title) info)) - (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)))))) - (description (plist-get info :description)) - (keywords (plist-get info :keywords)) - (charset (or (and org-html-coding-system - (fboundp 'coding-system-get) - (coding-system-get org-html-coding-system - 'mime-charset)) - "iso-8859-1"))) + (let* ((protect-string + (lambda (str) + (replace-regexp-in-string + "\"" """ (org-html-encode-plain-text str)))) + (title (org-export-data (plist-get info :title) info)) + ;; Set title to an invisible character instead of leaving it + ;; empty, which is invalid. + (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)))))) + (description (plist-get info :description)) + (keywords (plist-get info :keywords)) + (charset (or (and org-html-coding-system + (fboundp 'coding-system-get) + (coding-system-get org-html-coding-system + 'mime-charset)) + "iso-8859-1"))) (concat - (format "<title>%s</title>\n" title) (when (plist-get info :time-stamp-file) (format-time-string - (concat "<!-- " org-html-metadata-timestamp-format " -->\n"))) + (concat "<!-- " + (plist-get info :html-metadata-timestamp-format) + " -->\n"))) (format (if (org-html-html5-p info) - (org-html-close-tag "meta" " charset=\"%s\"" info) + (org-html-close-tag "meta" "charset=\"%s\"" info) (org-html-close-tag - "meta" " http-equiv=\"Content-Type\" content=\"text/html;charset=%s\"" + "meta" "http-equiv=\"Content-Type\" content=\"text/html;charset=%s\"" info)) charset) "\n" - (org-html-close-tag "meta" " name=\"generator\" content=\"Org-mode\"" info) + (let ((viewport-options + (cl-remove-if-not (lambda (cell) (org-string-nw-p (cadr cell))) + (plist-get info :html-viewport)))) + (and viewport-options + (concat + (org-html-close-tag + "meta" + (format "name=\"viewport\" content=\"%s\"" + (mapconcat + (lambda (elm) (format "%s=%s" (car elm) (cadr elm))) + viewport-options ", ")) + info) + "\n"))) + (format "<title>%s</title>\n" title) + (org-html-close-tag "meta" "name=\"generator\" content=\"Org mode\"" info) "\n" (and (org-string-nw-p author) (concat (org-html-close-tag "meta" - (format " name=\"author\" content=\"%s\"" + (format "name=\"author\" content=\"%s\"" (funcall protect-string author)) info) "\n")) (and (org-string-nw-p description) (concat (org-html-close-tag "meta" - (format " name=\"description\" content=\"%s\"\n" + (format "name=\"description\" content=\"%s\"\n" (funcall protect-string description)) info) "\n")) (and (org-string-nw-p keywords) (concat (org-html-close-tag "meta" - (format " name=\"keywords\" content=\"%s\"" + (format "name=\"keywords\" content=\"%s\"" (funcall protect-string keywords)) info) "\n"))))) @@ -1576,7 +1893,7 @@ INFO is a plist used as a communication channel." (when (and (plist-get info :html-htmlized-css-url) (eq org-html-htmlize-output-type 'css)) (org-html-close-tag "link" - (format " rel=\"stylesheet\" href=\"%s\" type=\"text/css\"" + (format "rel=\"stylesheet\" href=\"%s\" type=\"text/css\"" (plist-get info :html-htmlized-css-url)) info)) (when (plist-get info :html-head-include-scripts) org-html-scripts)))) @@ -1586,56 +1903,44 @@ INFO is a plist used as a communication channel." INFO is a plist used as a communication channel." (when (and (memq (plist-get info :with-latex) '(mathjax t)) (org-element-map (plist-get info :parse-tree) - '(latex-fragment latex-environment) 'identity info t)) - (let ((template org-html-mathjax-template) - (options org-html-mathjax-options) - (in-buffer (or (plist-get info :html-mathjax) "")) - name val (yes " ") (no "// ") x) - (mapc - (lambda (e) - (setq name (car e) val (nth 1 e)) - (if (string-match (concat "\\<" (symbol-name name) ":") in-buffer) - (setq val (car (read-from-string - (substring in-buffer (match-end 0)))))) - (if (not (stringp val)) (setq val (format "%s" val))) - (if (string-match (concat "%" (upcase (symbol-name name))) template) - (setq template (replace-match val t t template)))) - options) - (setq val (nth 1 (assq 'mathml options))) - (if (string-match (concat "\\<mathml:") in-buffer) - (setq val (car (read-from-string - (substring in-buffer (match-end 0)))))) - ;; Exchange prefixes depending on mathml setting. - (if (not val) (setq x yes yes no no x)) - ;; Replace cookies to turn on or off the config/jax lines. - (if (string-match ":MMLYES:" template) - (setq template (replace-match yes t t template))) - (if (string-match ":MMLNO:" template) - (setq template (replace-match no t t template))) - ;; Return the modified template. - (org-element-normalize-string template)))) + '(latex-fragment latex-environment) #'identity info t nil t)) + (let ((template (plist-get info :html-mathjax-template)) + (options (plist-get info :html-mathjax-options)) + (in-buffer (or (plist-get info :html-mathjax) ""))) + (dolist (e options (org-element-normalize-string template)) + (let ((name (car e)) + (val (nth 1 e))) + (when (string-match (concat "\\<" (symbol-name name) ":") in-buffer) + (setq val + (car (read-from-string (substring in-buffer (match-end 0)))))) + (unless (stringp val) (setq val (format "%s" val))) + (while (string-match (concat "%" (upcase (symbol-name name))) + template) + (setq template (replace-match val t t template)))))))) (defun org-html-format-spec (info) - "Return format specification for elements that can be -used in the preamble or postamble." - `((?t . ,(org-export-data (plist-get info :title) info)) - (?d . ,(org-export-data (org-export-get-date info) info)) - (?T . ,(format-time-string org-html-metadata-timestamp-format)) - (?a . ,(org-export-data (plist-get info :author) info)) - (?e . ,(mapconcat - (lambda (e) - (format "<a href=\"mailto:%s\">%s</a>" e e)) - (split-string (plist-get info :email) ",+ *") - ", ")) - (?c . ,(plist-get info :creator)) - (?C . ,(let ((file (plist-get info :input-file))) - (format-time-string org-html-metadata-timestamp-format - (if file (nth 5 (file-attributes file)))))) - (?v . ,(or org-html-validation-link "")))) + "Return format specification for preamble and postamble. +INFO is a plist used as a communication channel." + (let ((timestamp-format (plist-get info :html-metadata-timestamp-format))) + `((?t . ,(org-export-data (plist-get info :title) info)) + (?s . ,(org-export-data (plist-get info :subtitle) info)) + (?d . ,(org-export-data (org-export-get-date info timestamp-format) + info)) + (?T . ,(format-time-string timestamp-format)) + (?a . ,(org-export-data (plist-get info :author) info)) + (?e . ,(mapconcat + (lambda (e) (format "<a href=\"mailto:%s\">%s</a>" e e)) + (split-string (plist-get info :email) ",+ *") + ", ")) + (?c . ,(plist-get info :creator)) + (?C . ,(let ((file (plist-get info :input-file))) + (format-time-string timestamp-format + (and file (nth 5 (file-attributes file)))))) + (?v . ,(or (plist-get info :html-validation-link) ""))))) (defun org-html--build-pre/postamble (type info) "Return document preamble or postamble as a string, or nil. -TYPE is either 'preamble or 'postamble, INFO is a plist used as a +TYPE is either `preamble' or `postamble', INFO is a plist used as a communication channel." (let ((section (plist-get info (intern (format ":html-%s" type)))) (spec (org-html-format-spec info))) @@ -1649,7 +1954,6 @@ communication channel." (author (cdr (assq ?a spec))) (email (cdr (assq ?e spec))) (creator (cdr (assq ?c spec))) - (timestamp (cdr (assq ?T spec))) (validation-link (cdr (assq ?v spec)))) (concat (when (and (plist-get info :with-date) @@ -1671,30 +1975,34 @@ communication channel." (format "<p class=\"date\">%s: %s</p>\n" (org-html--translate "Created" info) - (format-time-string org-html-metadata-timestamp-format))) + (format-time-string + (plist-get info :html-metadata-timestamp-format)))) (when (plist-get info :with-creator) (format "<p class=\"creator\">%s</p>\n" creator)) (format "<p class=\"validation\">%s</p>\n" validation-link)))) (t (format-spec - (or (cadr (assoc + (or (cadr (assoc-string (plist-get info :language) (eval (intern - (format "org-html-%s-format" type))))) + (format "org-html-%s-format" type))) + t)) (cadr - (assoc + (assoc-string "en" (eval - (intern (format "org-html-%s-format" type)))))) + (intern (format "org-html-%s-format" type))) + t))) spec)))))) - (when (org-string-nw-p section-contents) - (concat - (format "<%s id=\"%s\" class=\"%s\">\n" - (nth 1 (assq type org-html-divs)) - (nth 2 (assq type org-html-divs)) - org-html--pre/postamble-class) - (org-element-normalize-string section-contents) - (format "</%s>\n" (nth 1 (assq type org-html-divs))))))))) + (let ((div (assq type (plist-get info :html-divs)))) + (when (org-string-nw-p section-contents) + (concat + (format "<%s id=\"%s\" class=\"%s\">\n" + (nth 1 div) + (nth 2 div) + org-html--pre/postamble-class) + (org-element-normalize-string section-contents) + (format "</%s>\n" (nth 1 div))))))))) (defun org-html-inner-template (contents info) "Return body of document string after HTML conversion. @@ -1715,27 +2023,28 @@ CONTENTS is the transcoded contents string. INFO is a plist holding export options." (concat (when (and (not (org-html-html5-p info)) (org-html-xhtml-p info)) - (let ((decl (or (and (stringp org-html-xml-declaration) - org-html-xml-declaration) - (cdr (assoc (plist-get info :html-extension) - org-html-xml-declaration)) - (cdr (assoc "html" org-html-xml-declaration)) - - ""))) - (when (not (or (eq nil decl) (string= "" decl))) + (let* ((xml-declaration (plist-get info :html-xml-declaration)) + (decl (or (and (stringp xml-declaration) xml-declaration) + (cdr (assoc (plist-get info :html-extension) + xml-declaration)) + (cdr (assoc "html" xml-declaration)) + ""))) + (when (not (or (not decl) (string= "" decl))) (format "%s\n" (format decl - (or (and org-html-coding-system - (fboundp 'coding-system-get) - (coding-system-get org-html-coding-system 'mime-charset)) - "iso-8859-1")))))) + (or (and org-html-coding-system + (fboundp 'coding-system-get) + (coding-system-get org-html-coding-system 'mime-charset)) + "iso-8859-1")))))) (org-html-doctype info) "\n" (concat "<html" - (when (org-html-xhtml-p info) - (format - " xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"%s\" xml:lang=\"%s\"" - (plist-get info :language) (plist-get info :language))) + (cond ((org-html-xhtml-p info) + (format + " xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"%s\" xml:lang=\"%s\"" + (plist-get info :language) (plist-get info :language))) + ((org-html-html5-p info) + (format " lang=\"%s\"" (plist-get info :language)))) ">\n") "<head>\n" (org-html--build-meta-info info) @@ -1746,23 +2055,45 @@ holding export options." (let ((link-up (org-trim (plist-get info :html-link-up))) (link-home (org-trim (plist-get info :html-link-home)))) (unless (and (string= link-up "") (string= link-home "")) - (format org-html-home/up-format + (format (plist-get info :html-home/up-format) (or link-up link-home) (or link-home link-up)))) ;; Preamble. (org-html--build-pre/postamble 'preamble info) ;; Document contents. - (format "<%s id=\"%s\">\n" - (nth 1 (assq 'content org-html-divs)) - (nth 2 (assq 'content org-html-divs))) + (let ((div (assq 'content (plist-get info :html-divs)))) + (format "<%s id=\"%s\">\n" (nth 1 div) (nth 2 div))) ;; Document title. - (let ((title (plist-get info :title))) - (format "<h1 class=\"title\">%s</h1>\n" (org-export-data (or title "") info))) + (when (plist-get info :with-title) + (let ((title (and (plist-get info :with-title) + (plist-get info :title))) + (subtitle (plist-get info :subtitle)) + (html5-fancy (org-html--html5-fancy-p info))) + (when title + (format + (if html5-fancy + "<header>\n<h1 class=\"title\">%s</h1>\n%s</header>" + "<h1 class=\"title\">%s%s</h1>\n") + (org-export-data title info) + (if subtitle + (format + (if html5-fancy + "<p class=\"subtitle\">%s</p>\n" + (concat "\n" (org-html-close-tag "br" nil info) "\n" + "<span class=\"subtitle\">%s</span>\n")) + (org-export-data subtitle info)) + ""))))) contents - (format "</%s>\n" - (nth 1 (assq 'content org-html-divs))) + (format "</%s>\n" (nth 1 (assq 'content (plist-get info :html-divs)))) ;; Postamble. (org-html--build-pre/postamble 'postamble info) + ;; Possibly use the Klipse library live code blocks. + (if (plist-get info :html-klipsify-src) + (concat "<script>" (plist-get info :html-klipse-selection-script) + "</script><script src=\"" + org-html-klipse-js + "\"></script><link rel=\"stylesheet\" type=\"text/css\" href=\"" + org-html-klipse-css "\"/>")) ;; Closing document. "</body>\n</html>")) @@ -1773,9 +2104,9 @@ INFO is a plist used as a communication channel." ;;;; Anchor -(defun org-html--anchor (&optional id desc attributes) +(defun org-html--anchor (id desc attributes info) "Format a HTML anchor." - (let* ((name (and org-html-allow-name-attribute-in-anchors id)) + (let* ((name (and (plist-get info :html-allow-name-attribute-in-anchors) id)) (attributes (concat (and id (format " id=\"%s\"" id)) (and name (format " name=\"%s\"" name)) attributes))) @@ -1783,43 +2114,38 @@ INFO is a plist used as a communication channel." ;;;; Todo -(defun org-html--todo (todo) +(defun org-html--todo (todo info) "Format TODO keywords into HTML." (when todo (format "<span class=\"%s %s%s\">%s</span>" (if (member todo org-done-keywords) "done" "todo") - org-html-todo-kwd-class-prefix (org-html-fix-class-name todo) + (or (plist-get info :html-todo-kwd-class-prefix) "") + (org-html-fix-class-name todo) todo))) +;;;; Priority + +(defun org-html--priority (priority _info) + "Format a priority into HTML. +PRIORITY is the character code of the priority or nil. INFO is +a plist containing export options." + (and priority (format "<span class=\"priority\">[%c]</span>" priority))) + ;;;; Tags -(defun org-html--tags (tags) - "Format TAGS into HTML." +(defun org-html--tags (tags info) + "Format TAGS into HTML. +INFO is a plist containing export options." (when tags (format "<span class=\"tag\">%s</span>" (mapconcat (lambda (tag) (format "<span class=\"%s\">%s</span>" - (concat org-html-tag-class-prefix + (concat (plist-get info :html-tag-class-prefix) (org-html-fix-class-name tag)) tag)) tags " ")))) -;;;; Headline - -(defun* org-html-format-headline - (todo todo-type priority text tags - &key level section-number headline-label &allow-other-keys) - "Format a headline in HTML." - (let ((section-number - (when section-number - (format "<span class=\"section-number-%d\">%s</span> " - level section-number))) - (todo (org-html--todo todo)) - (tags (org-html--tags tags))) - (concat section-number todo (and todo " ") text - (and tags "   ") tags))) - ;;;; Src Code (defun org-html-fontify-code (code lang) @@ -1828,15 +2154,17 @@ CODE is a string representing the source code to colorize. LANG is the language used for CODE, as a string, or nil." (when code (cond - ;; Case 1: No lang. Possibly an example block. - ((not lang) - ;; Simple transcoding. - (org-html-encode-plain-text code)) - ;; Case 2: No htmlize or an inferior version of htmlize - ((not (and (require 'htmlize nil t) (fboundp 'htmlize-region-for-paste))) + ;; No language. Possibly an example block. + ((not lang) (org-html-encode-plain-text code)) + ;; Plain text explicitly set. + ((not org-html-htmlize-output-type) (org-html-encode-plain-text code)) + ;; No htmlize library or an inferior version of htmlize. + ((not (and (or (require 'htmlize nil t) + (error "Please install htmlize from \ +https://github.com/hniksic/emacs-htmlize")) + (fboundp 'htmlize-region-for-paste))) ;; Emit a warning. (message "Cannot fontify src block (htmlize.el >= 1.34 required)") - ;; Simple transcoding. (org-html-encode-plain-text code)) (t ;; Map language @@ -1845,32 +2173,36 @@ is the language used for CODE, as a string, or nil." (cond ;; Case 1: Language is not associated with any Emacs mode ((not (functionp lang-mode)) - ;; Simple transcoding. (org-html-encode-plain-text code)) ;; Case 2: Default. Fontify code. (t ;; htmlize - (setq code (with-temp-buffer - ;; Switch to language-specific mode. - (funcall lang-mode) - (insert code) - ;; Fontify buffer. - (org-font-lock-ensure) - ;; Remove formatting on newline characters. - (save-excursion - (let ((beg (point-min)) - (end (point-max))) - (goto-char beg) - (while (progn (end-of-line) (< (point) end)) - (put-text-property (point) (1+ (point)) 'face nil) - (forward-char 1)))) - (org-src-mode) - (set-buffer-modified-p nil) - ;; Htmlize region. - (org-html-htmlize-region-for-paste - (point-min) (point-max)))) + (setq code + (let ((output-type org-html-htmlize-output-type) + (font-prefix org-html-htmlize-font-prefix)) + (with-temp-buffer + ;; Switch to language-specific mode. + (funcall lang-mode) + (insert code) + ;; Fontify buffer. + (org-font-lock-ensure) + ;; Remove formatting on newline characters. + (save-excursion + (let ((beg (point-min)) + (end (point-max))) + (goto-char beg) + (while (progn (end-of-line) (< (point) end)) + (put-text-property (point) (1+ (point)) 'face nil) + (forward-char 1)))) + (org-src-mode) + (set-buffer-modified-p nil) + ;; Htmlize region. + (let ((org-html-htmlize-output-type output-type) + (org-html-htmlize-font-prefix font-prefix)) + (org-html-htmlize-region-for-paste + (point-min) (point-max)))))) ;; Strip any enclosing <pre></pre> tags. - (let* ((beg (and (string-match "\\`<pre[^>]*>\n*" code) (match-end 0))) + (let* ((beg (and (string-match "\\`<pre[^>]*>\n?" code) (match-end 0))) (end (and beg (string-match "</pre>\\'" code)))) (if (and beg end) (substring code beg end) code))))))))) @@ -1883,7 +2215,7 @@ alist between line numbers and references (as returned by `org-export-unravel-code'), a boolean specifying if labels should appear in the source code, and the number associated to the first line of code." - (let* ((code-lines (org-split-string code "\n")) + (let* ((code-lines (split-string code "\n")) (code-length (length code-lines)) (num-fmt (and num-start @@ -1921,38 +2253,39 @@ a plist used as a communication channel." ;; Does the src block contain labels? (retain-labels (org-element-property :retain-labels element)) ;; Does it have line numbers? - (num-start (case (org-element-property :number-lines element) - (continued (org-export-get-loc element info)) - (new 0)))) + (num-start (org-export-get-loc element info))) (org-html-do-format-code code lang refs retain-labels num-start))) ;;; Tables of Contents -(defun org-html-toc (depth info) +(defun org-html-toc (depth info &optional scope) "Build a table of contents. -DEPTH is an integer specifying the depth of the table. INFO is a -plist used as a communication channel. Return the table of -contents as a string, or nil if it is empty." +DEPTH is an integer specifying the depth of the table. INFO is +a plist used as a communication channel. Optional argument SCOPE +is an element defining the scope of the table. Return the table +of contents as a string, or nil if it is empty." (let ((toc-entries (mapcar (lambda (headline) (cons (org-html--format-toc-headline headline info) (org-export-get-relative-level headline info))) - (org-export-collect-headlines info depth))) - (outer-tag (if (and (org-html-html5-p info) - (plist-get info :html-html5-fancy)) - "nav" - "div"))) + (org-export-collect-headlines info depth scope)))) (when toc-entries - (concat (format "<%s id=\"table-of-contents\">\n" outer-tag) - (format "<h%d>%s</h%d>\n" - org-html-toplevel-hlevel - (org-html--translate "Table of Contents" info) - org-html-toplevel-hlevel) - "<div id=\"text-table-of-contents\">" - (org-html--toc-text toc-entries) - "</div>\n" - (format "</%s>\n" outer-tag))))) + (let ((toc (concat "<div id=\"text-table-of-contents\">" + (org-html--toc-text toc-entries) + "</div>\n"))) + (if scope toc + (let ((outer-tag (if (org-html--html5-fancy-p info) + "nav" + "div"))) + (concat (format "<%s id=\"table-of-contents\">\n" outer-tag) + (let ((top-level (plist-get info :html-toplevel-hlevel))) + (format "<h%d>%s</h%d>\n" + top-level + (org-html--translate "Table of Contents" info) + top-level)) + toc + (format "</%s>\n" outer-tag)))))))) (defun org-html--toc-text (toc-entries) "Return innards of a table of contents, as a string. @@ -1967,8 +2300,7 @@ and value is its relative level, as an integer." (level (cdr entry))) (concat (let* ((cnt (- level prev-level)) - (times (if (> cnt 0) (1- cnt) (- cnt))) - rtn) + (times (if (> cnt 0) (1- cnt) (- cnt)))) (setq prev-level level) (concat (org-html--make-string @@ -1991,35 +2323,21 @@ INFO is a plist used as a communication channel." (org-element-property :priority headline))) (text (org-export-data-with-backend (org-export-get-alt-title headline info) - ;; Create an anonymous back-end that will ignore any - ;; footnote-reference, link, radio-target and target - ;; in table of contents. - (org-export-create-backend - :parent 'html - :transcoders '((footnote-reference . ignore) - (link . (lambda (object c i) c)) - (radio-target . (lambda (object c i) c)) - (target . ignore))) + (org-export-toc-entry-backend 'html) info)) (tags (and (eq (plist-get info :with-tags) t) (org-export-get-tags headline info)))) (format "<a href=\"#%s\">%s</a>" ;; Label. - (org-export-solidify-link-text - (or (org-element-property :CUSTOM_ID headline) - (concat "sec-" - (mapconcat #'number-to-string headline-number "-")))) + (or (org-element-property :CUSTOM_ID headline) + (org-export-get-reference headline info)) ;; Body. (concat (and (not (org-export-low-level-p headline info)) (org-export-numbered-headline-p headline info) (concat (mapconcat #'number-to-string headline-number ".") ". ")) - (apply (if (not (eq org-html-format-headline-function 'ignore)) - (lambda (todo todo-type priority text tags &rest ignore) - (funcall org-html-format-headline-function - todo todo-type priority text tags)) - #'org-html-format-headline) + (apply (plist-get info :html-format-headline-function) todo todo-type priority text tags :section-number nil))))) (defun org-html-list-of-listings (info) @@ -2029,17 +2347,19 @@ of listings as a string, or nil if it is empty." (let ((lol-entries (org-export-collect-listings info))) (when lol-entries (concat "<div id=\"list-of-listings\">\n" - (format "<h%d>%s</h%d>\n" - org-html-toplevel-hlevel - (org-html--translate "List of Listings" info) - org-html-toplevel-hlevel) + (let ((top-level (plist-get info :html-toplevel-hlevel))) + (format "<h%d>%s</h%d>\n" + top-level + (org-html--translate "List of Listings" info) + top-level)) "<div id=\"text-list-of-listings\">\n<ul>\n" (let ((count 0) (initial-fmt (format "<span class=\"listing-number\">%s</span>" (org-html--translate "Listing %d:" info)))) (mapconcat (lambda (entry) - (let ((label (org-element-property :name entry)) + (let ((label (and (org-element-property :name entry) + (org-export-get-reference entry info))) (title (org-trim (org-export-data (or (org-export-get-caption entry t) @@ -2048,10 +2368,12 @@ of listings as a string, or nil if it is empty." (concat "<li>" (if (not label) - (concat (format initial-fmt (incf count)) " " title) + (concat (format initial-fmt (cl-incf count)) + " " + title) (format "<a href=\"#%s\">%s %s</a>" - (org-export-solidify-link-text label) - (format initial-fmt (incf count)) + label + (format initial-fmt (cl-incf count)) title)) "</li>"))) lol-entries "\n")) @@ -2064,17 +2386,19 @@ of tables as a string, or nil if it is empty." (let ((lol-entries (org-export-collect-tables info))) (when lol-entries (concat "<div id=\"list-of-tables\">\n" - (format "<h%d>%s</h%d>\n" - org-html-toplevel-hlevel - (org-html--translate "List of Tables" info) - org-html-toplevel-hlevel) + (let ((top-level (plist-get info :html-toplevel-hlevel))) + (format "<h%d>%s</h%d>\n" + top-level + (org-html--translate "List of Tables" info) + top-level)) "<div id=\"text-list-of-tables\">\n<ul>\n" (let ((count 0) (initial-fmt (format "<span class=\"table-number\">%s</span>" (org-html--translate "Table %d:" info)))) (mapconcat (lambda (entry) - (let ((label (org-element-property :name entry)) + (let ((label (and (org-element-property :name entry) + (org-export-get-reference entry info))) (title (org-trim (org-export-data (or (org-export-get-caption entry t) @@ -2083,10 +2407,12 @@ of tables as a string, or nil if it is empty." (concat "<li>" (if (not label) - (concat (format initial-fmt (incf count)) " " title) + (concat (format initial-fmt (cl-incf count)) + " " + title) (format "<a href=\"#%s\">%s %s</a>" - (org-export-solidify-link-text label) - (format initial-fmt (incf count)) + label + (format initial-fmt (cl-incf count)) title)) "</li>"))) lol-entries "\n")) @@ -2097,24 +2423,24 @@ of tables as a string, or nil if it is empty." ;;;; Bold -(defun org-html-bold (bold contents info) +(defun org-html-bold (_bold contents info) "Transcode BOLD from Org to HTML. CONTENTS is the text with bold markup. INFO is a plist holding contextual information." - (format (or (cdr (assq 'bold org-html-text-markup-alist)) "%s") + (format (or (cdr (assq 'bold (plist-get info :html-text-markup-alist))) "%s") contents)) ;;;; Center Block -(defun org-html-center-block (center-block contents info) +(defun org-html-center-block (_center-block contents _info) "Transcode a CENTER-BLOCK element from Org to HTML. CONTENTS holds the contents of the block. INFO is a plist holding contextual information." - (format "<div class=\"center\">\n%s</div>" contents)) + (format "<div class=\"org-center\">\n%s</div>" contents)) ;;;; Clock -(defun org-html-clock (clock contents info) +(defun org-html-clock (clock _contents _info) "Transcode a CLOCK element from Org to HTML. CONTENTS is nil. INFO is a plist used as a communication channel." @@ -2124,19 +2450,17 @@ channel." </span> </p>" org-clock-string - (org-translate-time - (org-element-property :raw-value - (org-element-property :value clock))) + (org-timestamp-translate (org-element-property :value clock)) (let ((time (org-element-property :duration clock))) (and time (format " <span class=\"timestamp\">(%s)</span>" time))))) ;;;; Code -(defun org-html-code (code contents info) +(defun org-html-code (code _contents info) "Transcode CODE from Org to HTML. CONTENTS is nil. INFO is a plist holding contextual information." - (format (or (cdr (assq 'code org-html-text-markup-alist)) "%s") + (format (or (cdr (assq 'code (plist-get info :html-text-markup-alist))) "%s") (org-html-encode-plain-text (org-element-property :value code)))) ;;;; Drawer @@ -2145,17 +2469,13 @@ information." "Transcode a DRAWER element from Org to HTML. CONTENTS holds the contents of the block. INFO is a plist holding contextual information." - (if (functionp org-html-format-drawer-function) - (funcall org-html-format-drawer-function - (org-element-property :drawer-name drawer) - contents) - ;; If there's no user defined function: simply - ;; display contents of the drawer. - contents)) + (funcall (plist-get info :html-format-drawer-function) + (org-element-property :drawer-name drawer) + contents)) ;;;; Dynamic Block -(defun org-html-dynamic-block (dynamic-block contents info) +(defun org-html-dynamic-block (_dynamic-block contents _info) "Transcode a DYNAMIC-BLOCK element from Org to HTML. CONTENTS holds the contents of the block. INFO is a plist holding contextual information. See `org-export-data'." @@ -2163,7 +2483,7 @@ holding contextual information. See `org-export-data'." ;;;; Entity -(defun org-html-entity (entity contents info) +(defun org-html-entity (entity _contents _info) "Transcode an ENTITY object from Org to HTML. CONTENTS are the definition itself. INFO is a plist holding contextual information." @@ -2171,18 +2491,25 @@ contextual information." ;;;; Example Block -(defun org-html-example-block (example-block contents info) +(defun org-html-example-block (example-block _contents info) "Transcode a EXAMPLE-BLOCK element from Org to HTML. CONTENTS is nil. INFO is a plist holding contextual information." - (if (org-export-read-attribute :attr_html example-block :textarea) - (org-html--textarea-block example-block) - (format "<pre class=\"example\">\n%s</pre>" - (org-html-format-code example-block info)))) + (let ((attributes (org-export-read-attribute :attr_html example-block))) + (if (plist-get attributes :textarea) + (org-html--textarea-block example-block) + (format "<pre class=\"example\"%s>\n%s</pre>" + (let* ((name (org-element-property :name example-block)) + (a (org-html--make-attribute-string + (if (or (not name) (plist-member attributes :id)) + attributes + (plist-put attributes :id name))))) + (if (org-string-nw-p a) (concat " " a) "")) + (org-html-format-code example-block info))))) ;;;; Export Snippet -(defun org-html-export-snippet (export-snippet contents info) +(defun org-html-export-snippet (export-snippet _contents _info) "Transcode a EXPORT-SNIPPET object from Org to HTML. CONTENTS is nil. INFO is a plist holding contextual information." @@ -2191,7 +2518,7 @@ information." ;;;; Export Block -(defun org-html-export-block (export-block contents info) +(defun org-html-export-block (export-block _contents _info) "Transcode a EXPORT-BLOCK element from Org to HTML. CONTENTS is nil. INFO is a plist holding contextual information." (when (string= (org-element-property :type export-block) "HTML") @@ -2199,7 +2526,7 @@ CONTENTS is nil. INFO is a plist holding contextual information." ;;;; Fixed Width -(defun org-html-fixed-width (fixed-width contents info) +(defun org-html-fixed-width (fixed-width _contents _info) "Transcode a FIXED-WIDTH element from Org to HTML. CONTENTS is nil. INFO is a plist holding contextual information." (format "<pre class=\"example\">\n%s</pre>" @@ -2209,135 +2536,117 @@ CONTENTS is nil. INFO is a plist holding contextual information." ;;;; Footnote Reference -(defun org-html-footnote-reference (footnote-reference contents info) +(defun org-html-footnote-reference (footnote-reference _contents info) "Transcode a FOOTNOTE-REFERENCE element from Org to HTML. CONTENTS is nil. INFO is a plist holding contextual information." (concat ;; Insert separator between two footnotes in a row. (let ((prev (org-export-get-previous-element footnote-reference info))) (when (eq (org-element-type prev) 'footnote-reference) - org-html-footnote-separator)) - (cond - ((not (org-export-footnote-first-reference-p footnote-reference info)) - (org-html-format-footnote-reference - (org-export-get-footnote-number footnote-reference info) - "IGNORED" 100)) - ;; Inline definitions are secondary strings. - ((eq (org-element-property :type footnote-reference) 'inline) - (org-html-format-footnote-reference - (org-export-get-footnote-number footnote-reference info) - "IGNORED" 1)) - ;; Non-inline footnotes definitions are full Org data. - (t (org-html-format-footnote-reference - (org-export-get-footnote-number footnote-reference info) - "IGNORED" 1))))) + (plist-get info :html-footnote-separator))) + (let* ((n (org-export-get-footnote-number footnote-reference info)) + (id (format "fnr.%d%s" + n + (if (org-export-footnote-first-reference-p + footnote-reference info) + "" + ".100")))) + (format + (plist-get info :html-footnote-format) + (org-html--anchor + id n (format " class=\"footref\" href=\"#fn.%d\"" n) info))))) ;;;; Headline -(defun org-html-format-headline--wrap - (headline info &optional format-function &rest extra-keys) - "Transcode a HEADLINE element from Org to HTML. -CONTENTS holds the contents of the headline. INFO is a plist -holding contextual information." - (let* ((level (+ (org-export-get-relative-level headline info) - (1- org-html-toplevel-hlevel))) - (headline-number (org-export-get-headline-number headline info)) - (section-number (and (not (org-export-low-level-p headline info)) - (org-export-numbered-headline-p headline info) - (mapconcat 'number-to-string - headline-number "."))) - (todo (and (plist-get info :with-todo-keywords) - (let ((todo (org-element-property :todo-keyword headline))) - (and todo (org-export-data todo info))))) - (todo-type (and todo (org-element-property :todo-type headline))) - (priority (and (plist-get info :with-priority) - (org-element-property :priority headline))) - (text (org-export-data (org-element-property :title headline) info)) - (tags (and (plist-get info :with-tags) - (org-export-get-tags headline info))) - (headline-label (or (org-element-property :CUSTOM_ID headline) - (concat "sec-" (mapconcat 'number-to-string - headline-number "-")))) - (format-function - (cond ((functionp format-function) format-function) - ((not (eq org-html-format-headline-function 'ignore)) - (lambda (todo todo-type priority text tags &rest ignore) - (funcall org-html-format-headline-function - todo todo-type priority text tags))) - (t 'org-html-format-headline)))) - (apply format-function - todo todo-type priority text tags - :headline-label headline-label :level level - :section-number section-number extra-keys))) - (defun org-html-headline (headline contents info) "Transcode a HEADLINE element from Org to HTML. CONTENTS holds the contents of the headline. INFO is a plist holding contextual information." (unless (org-element-property :footnote-section-p headline) - (let* ((contents (or contents "")) - (numberedp (org-export-numbered-headline-p headline info)) - (level (org-export-get-relative-level headline info)) - (text (org-export-data (org-element-property :title headline) info)) - (todo (and (plist-get info :with-todo-keywords) - (let ((todo (org-element-property :todo-keyword headline))) - (and todo (org-export-data todo info))))) - (todo-type (and todo (org-element-property :todo-type headline))) - (tags (and (plist-get info :with-tags) - (org-export-get-tags headline info))) - (priority (and (plist-get info :with-priority) - (org-element-property :priority headline))) - (section-number (mapconcat #'number-to-string - (org-export-get-headline-number - headline info) "-")) - (ids (delq 'nil - (list (org-element-property :CUSTOM_ID headline) - (concat "sec-" section-number) - (org-element-property :ID headline)))) - (preferred-id (car ids)) - (extra-ids (mapconcat - (lambda (id) - (org-html--anchor - (org-export-solidify-link-text - (if (org-uuidgen-p id) (concat "ID-" id) id)))) - (cdr ids) "")) - ;; Create the headline text. - (full-text (org-html-format-headline--wrap headline info))) + (let* ((numberedp (org-export-numbered-headline-p headline info)) + (numbers (org-export-get-headline-number headline info)) + (level (+ (org-export-get-relative-level headline info) + (1- (plist-get info :html-toplevel-hlevel)))) + (todo (and (plist-get info :with-todo-keywords) + (let ((todo (org-element-property :todo-keyword headline))) + (and todo (org-export-data todo info))))) + (todo-type (and todo (org-element-property :todo-type headline))) + (priority (and (plist-get info :with-priority) + (org-element-property :priority headline))) + (text (org-export-data (org-element-property :title headline) info)) + (tags (and (plist-get info :with-tags) + (org-export-get-tags headline info))) + (full-text (funcall (plist-get info :html-format-headline-function) + todo todo-type priority text tags info)) + (contents (or contents "")) + (ids (delq nil + (list (org-element-property :CUSTOM_ID headline) + (org-export-get-reference headline info) + (org-element-property :ID headline)))) + (preferred-id (car ids)) + (extra-ids + (mapconcat + (lambda (id) + (org-html--anchor + (if (org-uuidgen-p id) (concat "ID-" id) id) + nil nil info)) + (cdr ids) ""))) (if (org-export-low-level-p headline info) - ;; This is a deep sub-tree: export it as a list item. - (let* ((type (if numberedp 'ordered 'unordered)) - (itemized-body - (org-html-format-list-item - contents type nil info nil - (concat (org-html--anchor preferred-id) extra-ids - full-text)))) + ;; This is a deep sub-tree: export it as a list item. + (let* ((html-type (if numberedp "ol" "ul"))) (concat (and (org-export-first-sibling-p headline info) - (org-html-begin-plain-list type)) - itemized-body + (apply #'format "<%s class=\"org-%s\">\n" + (make-list 2 html-type))) + (org-html-format-list-item + contents (if numberedp 'ordered 'unordered) + nil info nil + (concat (org-html--anchor preferred-id nil nil info) + extra-ids + full-text)) "\n" (and (org-export-last-sibling-p headline info) - (org-html-end-plain-list type)))) + (format "</%s>\n" html-type)))) ;; Standard headline. Export it as a section. - (let ((extra-class (org-element-property :HTML_CONTAINER_CLASS headline)) - (level1 (+ level (1- org-html-toplevel-hlevel))) - (first-content (car (org-element-contents headline)))) - (format "<%s id=\"%s\" class=\"%s\">%s%s</%s>\n" - (org-html--container headline info) - (format "outline-container-%s" - (or (org-element-property :CUSTOM_ID headline) - (concat "sec-" section-number))) - (concat (format "outline-%d" level1) (and extra-class " ") - extra-class) - (format "\n<h%d id=\"%s\">%s%s</h%d>\n" - level1 preferred-id extra-ids full-text level1) - ;; When there is no section, pretend there is an - ;; empty one to get the correct <div class="outline- - ;; ...> which is needed by `org-info.js'. - (if (not (eq (org-element-type first-content) 'section)) - (concat (org-html-section first-content "" info) - contents) - contents) - (org-html--container headline info))))))) + (let ((extra-class (org-element-property :HTML_CONTAINER_CLASS headline)) + (first-content (car (org-element-contents headline)))) + (format "<%s id=\"%s\" class=\"%s\">%s%s</%s>\n" + (org-html--container headline info) + (concat "outline-container-" + (org-export-get-reference headline info)) + (concat (format "outline-%d" level) + (and extra-class " ") + extra-class) + (format "\n<h%d id=\"%s\">%s%s</h%d>\n" + level + preferred-id + extra-ids + (concat + (and numberedp + (format + "<span class=\"section-number-%d\">%s</span> " + level + (mapconcat #'number-to-string numbers "."))) + full-text) + level) + ;; When there is no section, pretend there is an + ;; empty one to get the correct <div + ;; class="outline-...> which is needed by + ;; `org-info.js'. + (if (eq (org-element-type first-content) 'section) contents + (concat (org-html-section first-content "" info) contents)) + (org-html--container headline info))))))) + +(defun org-html-format-headline-default-function + (todo _todo-type priority text tags info) + "Default format function for a headline. +See `org-html-format-headline-function' for details." + (let ((todo (org-html--todo todo info)) + (priority (org-html--priority priority info)) + (tags (org-html--tags tags info))) + (concat todo (and todo " ") + priority (and priority " ") + text + (and tags "   ") tags))) (defun org-html--container (headline info) (or (org-element-property :HTML_CONTAINER headline) @@ -2347,100 +2656,115 @@ holding contextual information." ;;;; Horizontal Rule -(defun org-html-horizontal-rule (horizontal-rule contents info) +(defun org-html-horizontal-rule (_horizontal-rule _contents info) "Transcode an HORIZONTAL-RULE object from Org to HTML. CONTENTS is nil. INFO is a plist holding contextual information." (org-html-close-tag "hr" nil info)) ;;;; Inline Src Block -(defun org-html-inline-src-block (inline-src-block contents info) +(defun org-html-inline-src-block (inline-src-block _contents info) "Transcode an INLINE-SRC-BLOCK element from Org to HTML. CONTENTS holds the contents of the item. INFO is a plist holding contextual information." - (let* ((org-lang (org-element-property :language inline-src-block)) - (code (org-element-property :value inline-src-block))) - (error "Cannot export inline src block"))) + (let* ((lang (org-element-property :language inline-src-block)) + (code (org-html-fontify-code + (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)))) + (if (not lbl) "" (format " id=\"%s\"" lbl))))) + (format "<code class=\"src src-%s\"%s>%s</code>" lang label code))) ;;;; Inlinetask -(defun org-html-format-section (text class &optional id) - "Format a section with TEXT into a HTML div with CLASS and ID." - (let ((extra (concat (when id (format " id=\"%s\"" id))))) - (concat (format "<div class=\"%s\"%s>\n" class extra) text "</div>\n"))) - (defun org-html-inlinetask (inlinetask contents info) "Transcode an INLINETASK element from Org to HTML. CONTENTS holds the contents of the block. INFO is a plist holding contextual information." - (cond - ;; If `org-html-format-inlinetask-function' is not 'ignore, call it - ;; with appropriate arguments. - ((not (eq org-html-format-inlinetask-function 'ignore)) - (let ((format-function - (function* - (lambda (todo todo-type priority text tags - &key contents &allow-other-keys) - (funcall org-html-format-inlinetask-function - todo todo-type priority text tags contents))))) - (org-html-format-headline--wrap - inlinetask info format-function :contents contents))) - ;; Otherwise, use a default template. - (t (format "<div class=\"inlinetask\">\n<b>%s</b>%s\n%s</div>" - (org-html-format-headline--wrap inlinetask info) - (org-html-close-tag "br" nil info) - contents)))) + (let* ((todo (and (plist-get info :with-todo-keywords) + (let ((todo (org-element-property :todo-keyword inlinetask))) + (and todo (org-export-data todo info))))) + (todo-type (and todo (org-element-property :todo-type inlinetask))) + (priority (and (plist-get info :with-priority) + (org-element-property :priority inlinetask))) + (text (org-export-data (org-element-property :title inlinetask) info)) + (tags (and (plist-get info :with-tags) + (org-export-get-tags inlinetask info)))) + (funcall (plist-get info :html-format-inlinetask-function) + todo todo-type priority text tags contents info))) + +(defun org-html-format-inlinetask-default-function + (todo todo-type priority text tags contents info) + "Default format function for a inlinetasks. +See `org-html-format-inlinetask-function' for details." + (format "<div class=\"inlinetask\">\n<b>%s</b>%s\n%s</div>" + (org-html-format-headline-default-function + todo todo-type priority text tags info) + (org-html-close-tag "br" nil info) + contents)) ;;;; Italic -(defun org-html-italic (italic contents info) +(defun org-html-italic (_italic contents info) "Transcode ITALIC from Org to HTML. CONTENTS is the text with italic markup. INFO is a plist holding contextual information." - (format (or (cdr (assq 'italic org-html-text-markup-alist)) "%s") contents)) + (format + (or (cdr (assq 'italic (plist-get info :html-text-markup-alist))) "%s") + contents)) ;;;; Item -(defun org-html-checkbox (checkbox) - "Format CHECKBOX into HTML." - (case checkbox (on "<code>[X]</code>") - (off "<code>[ ]</code>") - (trans "<code>[-]</code>") - (t ""))) +(defun org-html-checkbox (checkbox info) + "Format CHECKBOX into HTML. +INFO is a plist holding contextual information. See +`org-html-checkbox-type' for customization options." + (cdr (assq checkbox + (cdr (assq (plist-get info :html-checkbox-type) + org-html-checkbox-types))))) (defun org-html-format-list-item (contents type checkbox info - &optional term-counter-id - headline) + &optional term-counter-id + headline) "Format a list item into HTML." - (let ((checkbox (concat (org-html-checkbox checkbox) (and checkbox " "))) - (br (org-html-close-tag "br" nil info))) + (let ((class (if checkbox + (format " class=\"%s\"" + (symbol-name checkbox)) "")) + (checkbox (concat (org-html-checkbox checkbox info) + (and checkbox " "))) + (br (org-html-close-tag "br" nil info)) + (extra-newline (if (and (org-string-nw-p contents) headline) "\n" ""))) (concat - (case type - (ordered + (pcase type + (`ordered (let* ((counter term-counter-id) (extra (if counter (format " value=\"%s\"" counter) ""))) (concat - (format "<li%s>" extra) + (format "<li%s%s>" class extra) (when headline (concat headline br))))) - (unordered + (`unordered (let* ((id term-counter-id) (extra (if id (format " id=\"%s\"" id) ""))) (concat - (format "<li%s>" extra) + (format "<li%s%s>" class extra) (when headline (concat headline br))))) - (descriptive + (`descriptive (let* ((term term-counter-id)) (setq term (or term "(no term)")) ;; Check-boxes in descriptive lists are associated to tag. - (concat (format "<dt> %s </dt>" - (concat checkbox term)) + (concat (format "<dt%s>%s</dt>" + class (concat checkbox term)) "<dd>")))) (unless (eq type 'descriptive) checkbox) - contents - (case type - (ordered "</li>") - (unordered "</li>") - (descriptive "</dd>"))))) + extra-newline + (and (org-string-nw-p contents) (org-trim contents)) + extra-newline + (pcase type + (`ordered "</li>") + (`unordered "</li>") + (`descriptive "</dd>"))))) (defun org-html-item (item contents info) "Transcode an ITEM element from Org to HTML. @@ -2457,7 +2781,7 @@ contextual information." ;;;; Keyword -(defun org-html-keyword (keyword contents info) +(defun org-html-keyword (keyword _contents info) "Transcode a KEYWORD element from Org to HTML. CONTENTS is nil. INFO is a plist holding contextual information." (let ((key (org-element-property :key keyword)) @@ -2465,13 +2789,13 @@ CONTENTS is nil. INFO is a plist holding contextual information." (cond ((string= key "HTML") value) ((string= key "TOC") - (let ((value (downcase value))) + (let ((case-fold-search t)) (cond ((string-match "\\<headlines\\>" value) - (let ((depth (or (and (string-match "[0-9]+" value) - (string-to-number (match-string 0 value))) - (plist-get info :with-toc)))) - (org-html-toc depth info))) + (let ((depth (and (string-match "\\<[0-9]+\\>" value) + (string-to-number (match-string 0 value)))) + (localp (string-match-p "\\<local\\>" value))) + (org-html-toc depth info (and localp keyword)))) ((string= "listings" value) (org-html-list-of-listings info)) ((string= "tables" value) (org-html-list-of-tables info)))))))) @@ -2479,10 +2803,11 @@ 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 is -a symbol among `mathjax', `dvipng', `imagemagick', `verbatim' nil -and t. See `org-html-with-latex' for more information. INFO is -a plist containing export properties." +PROCESSING-TYPE designates the tool used for conversion. It can +be `mathjax', `verbatim', 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) (let ((bfn (or (buffer-file-name) @@ -2497,7 +2822,7 @@ a plist containing export properties." "\n") "\n"))))) (setq cache-relpath - (concat "ltxpng/" + (concat (file-name-as-directory org-preview-latex-image-directory) (file-name-sans-extension (file-name-nondirectory bfn))) cache-dir (file-name-directory bfn)) @@ -2507,57 +2832,60 @@ a plist containing export properties." (setq latex-frag (concat latex-header latex-frag)))) (with-temp-buffer (insert latex-frag) - (org-format-latex cache-relpath cache-dir nil "Creating LaTeX Image..." - nil nil processing-type) + (org-format-latex cache-relpath nil nil cache-dir nil + "Creating LaTeX Image..." nil processing-type) (buffer-string)))) -(defun org-html-latex-environment (latex-environment contents info) +(defun org-html-latex-environment (latex-environment _contents info) "Transcode a LATEX-ENVIRONMENT element from Org to HTML. CONTENTS is nil. INFO is a plist holding contextual information." (let ((processing-type (plist-get info :with-latex)) (latex-frag (org-remove-indentation (org-element-property :value latex-environment))) (attributes (org-export-read-attribute :attr_html latex-environment))) - (case processing-type - ((t mathjax) - (org-html-format-latex latex-frag 'mathjax info)) - ((dvipng imagemagick) - (let ((formula-link - (org-html-format-latex latex-frag processing-type info))) - (when (and formula-link (string-match "file:\\([^]]*\\)" formula-link)) - ;; Do not provide a caption or a name to be consistent with - ;; `mathjax' handling. - (org-html--wrap-image - (org-html--format-image - (match-string 1 formula-link) attributes info) info)))) - (t latex-frag)))) + (cond + ((memq processing-type '(t mathjax)) + (org-html-format-latex latex-frag 'mathjax 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)) + ;; Do not provide a caption or a name to be consistent with + ;; `mathjax' handling. + (org-html--wrap-image + (org-html--format-image + (match-string 1 formula-link) attributes info) info)))) + (t latex-frag)))) ;;;; Latex Fragment -(defun org-html-latex-fragment (latex-fragment contents info) +(defun org-html-latex-fragment (latex-fragment _contents info) "Transcode a LATEX-FRAGMENT object from Org to HTML. CONTENTS is nil. INFO is a plist holding contextual information." (let ((latex-frag (org-element-property :value latex-fragment)) (processing-type (plist-get info :with-latex))) - (case processing-type - ((t mathjax) - (org-html-format-latex latex-frag 'mathjax info)) - ((dvipng imagemagick) - (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)))) - (t latex-frag)))) + (cond + ((memq processing-type '(t mathjax)) + (org-html-format-latex latex-frag 'mathjax 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)))) + (t latex-frag)))) ;;;; Line Break -(defun org-html-line-break (line-break contents info) +(defun org-html-line-break (_line-break _contents info) "Transcode a LINE-BREAK object from Org to HTML. CONTENTS is nil. INFO is a plist holding contextual information." (concat (org-html-close-tag "br" nil info) "\n")) ;;;; Link +(defun org-html-image-link-filter (data _backend info) + (org-export-insert-image-links data info org-html-inline-image-rules)) + (defun org-html-inline-image-p (link info) "Non-nil when LINK is meant to appear as an image. INFO is a plist used as a communication channel. LINK is an @@ -2565,19 +2893,20 @@ inline image when it has no description and targets an image file (see `org-html-inline-image-rules' for more information), or if its description is a single link targeting an image file." (if (not (org-element-contents link)) - (org-export-inline-image-p link org-html-inline-image-rules) + (org-export-inline-image-p + link (plist-get info :html-inline-image-rules)) (not (let ((link-count 0)) (org-element-map (org-element-contents link) (cons 'plain-text org-element-all-objects) (lambda (obj) - (case (org-element-type obj) - (plain-text (org-string-nw-p obj)) - (link (if (= link-count 1) t - (incf link-count) - (not (org-export-inline-image-p - obj org-html-inline-image-rules)))) - (otherwise t))) + (pcase (org-element-type obj) + (`plain-text (org-string-nw-p obj)) + (`link (if (= link-count 1) t + (cl-incf link-count) + (not (org-export-inline-image-p + obj (plist-get info :html-inline-image-rules))))) + (_ t))) info t))))) (defvar org-html-standalone-image-predicate) @@ -2599,9 +2928,9 @@ further. For example, to check for only captioned standalone images, set it to: (lambda (paragraph) (org-element-property :caption paragraph))" - (let ((paragraph (case (org-element-type element) - (paragraph element) - (link (org-export-get-parent element))))) + (let ((paragraph (pcase (org-element-type element) + (`paragraph element) + (`link (org-export-get-parent element))))) (and (eq (org-element-type paragraph) 'paragraph) (or (not (fboundp 'org-html-standalone-image-predicate)) (funcall org-html-standalone-image-predicate paragraph)) @@ -2609,76 +2938,71 @@ images, set it to: (let ((link-count 0)) (org-element-map (org-element-contents paragraph) (cons 'plain-text org-element-all-objects) - #'(lambda (obj) - (when (case (org-element-type obj) - (plain-text (org-string-nw-p obj)) - (link (or (> (incf link-count) 1) - (not (org-html-inline-image-p obj info)))) - (otherwise t)) - (throw 'exit nil))) + (lambda (obj) + (when (pcase (org-element-type obj) + (`plain-text (org-string-nw-p obj)) + (`link (or (> (cl-incf link-count) 1) + (not (org-html-inline-image-p obj info)))) + (_ t)) + (throw 'exit nil))) info nil 'link) (= link-count 1)))))) (defun org-html-link (link desc info) "Transcode a LINK object from Org to HTML. - DESC is the description part of the link, or the empty string. INFO is a plist holding contextual information. See `org-export-data'." - (let* ((home (when (plist-get info :html-link-home) - (org-trim (plist-get info :html-link-home)))) - (use-abs-url (plist-get info :html-link-use-abs-url)) - (link-org-files-as-html-maybe - (function - (lambda (raw-path info) - "Treat links to `file.org' as links to `file.html', if needed. - See `org-html-link-org-files-as-html'." - (cond - ((and org-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))) - (t raw-path))))) + (let* ((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'. + (cond + ((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))) + (t raw-path)))) (type (org-element-property :type link)) (raw-path (org-element-property :path link)) ;; Ensure DESC really exists, or set it to nil. (desc (org-string-nw-p desc)) (path (cond - ((member type '("http" "https" "ftp" "mailto")) - (org-link-escape - (org-link-unescape - (concat type ":" raw-path)) org-link-escape-chars-browser)) + ((member type '("http" "https" "ftp" "mailto" "news")) + (url-encode-url (org-link-unescape (concat type ":" raw-path)))) ((string= type "file") - ;; Treat links to ".org" files as ".html", if needed. + ;; During publishing, turn absolute file names belonging + ;; to base directory into relative file names. Otherwise, + ;; append "file" protocol to absolute file name. (setq raw-path - (funcall link-org-files-as-html-maybe raw-path info)) - ;; If file path is absolute, prepend it with protocol - ;; component - "file:". - (cond - ((file-name-absolute-p raw-path) - (setq raw-path (concat "file:" raw-path))) - ((and home use-abs-url) - (setq raw-path (concat (file-name-as-directory home) raw-path)))) + (org-export-file-uri + (org-publish-file-relative-name raw-path info))) + ;; Possibly append `:html-link-home' to relative file + ;; name. + (let ((home (and (plist-get info :html-link-home) + (org-trim (plist-get info :html-link-home))))) + (when (and home + (plist-get info :html-link-use-abs-url) + (file-name-absolute-p raw-path)) + (setq raw-path (concat (file-name-as-directory home) raw-path)))) + ;; Maybe turn ".org" into ".html". + (setq raw-path (funcall link-org-files-as-html-maybe raw-path info)) ;; Add search option, if any. A search option can be - ;; relative to a custom-id or a headline title. Any other - ;; option is ignored. + ;; relative to a custom-id, a headline title, a name or + ;; a target. (let ((option (org-element-property :search-option link))) (cond ((not option) raw-path) - ((eq (aref option 0) ?#) (concat raw-path option)) - ;; External fuzzy link: try to resolve it if path - ;; belongs to current project, if any. - ((eq (aref option 0) ?*) - (concat - raw-path - (let ((numbers - (org-publish-resolve-external-fuzzy-link - (org-element-property :path link) option))) - (and numbers (concat "#sec-" - (mapconcat 'number-to-string - numbers "-")))))) - (t raw-path)))) + ;; Since HTML back-end use custom-id value as-is, + ;; resolving is them is trivial. + ((eq (string-to-char option) ?#) (concat raw-path option)) + (t + (concat raw-path + "#" + (org-publish-resolve-external-link + option + (org-element-property :path link))))))) (t raw-path))) ;; Extract attributes from parent's paragraph. HACK: Only do ;; this for the first link in parent (inner image link for @@ -2695,12 +3019,14 @@ INFO is a plist holding contextual information. See (org-export-read-attribute :attr_html parent)))) (attributes (let ((attr (org-html--make-attribute-string attributes-plist))) - (if (org-string-nw-p attr) (concat " " attr) ""))) - protocol) + (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)) ;; Image file. - ((and org-html-inline-images - (org-export-inline-image-p link org-html-inline-image-rules)) + ((and (plist-get info :html-inline-images) + (org-export-inline-image-p + link (plist-get info :html-inline-image-rules))) (org-html--format-image path attributes-plist info)) ;; Radio target: Transcode target's contents and use them as ;; link's description. @@ -2708,18 +3034,18 @@ INFO is a plist holding contextual information. See (let ((destination (org-export-resolve-radio-link link info))) (if (not destination) desc (format "<a href=\"#%s\"%s>%s</a>" - (org-export-solidify-link-text - (org-element-property :value destination)) - attributes desc)))) + (org-export-get-reference destination info) + attributes + desc)))) ;; Links pointing to a headline: Find destination and build ;; appropriate referencing command. ((member type '("custom-id" "fuzzy" "id")) (let ((destination (if (string= type "fuzzy") (org-export-resolve-fuzzy-link link info) (org-export-resolve-id-link link info)))) - (case (org-element-type destination) + (pcase (org-element-type destination) ;; ID link points to an external file. - (plain-text + (`plain-text (let ((fragment (concat "ID-" path)) ;; Treat links to ".org" files as ".html", if needed. (path (funcall link-org-files-as-html-maybe @@ -2727,86 +3053,87 @@ INFO is a plist holding contextual information. See (format "<a href=\"%s#%s\"%s>%s</a>" path fragment attributes (or desc destination)))) ;; Fuzzy link points nowhere. - ((nil) + (`nil (format "<i>%s</i>" (or desc (org-export-data (org-element-property :raw-link link) info)))) ;; Link points to a headline. - (headline - (let ((href - ;; What href to use? - (cond - ;; Case 1: Headline is linked via it's CUSTOM_ID - ;; property. Use CUSTOM_ID. - ((string= type "custom-id") - (org-element-property :CUSTOM_ID destination)) - ;; Case 2: Headline is linked via it's ID property - ;; or through other means. Use the default href. - ((member type '("id" "fuzzy")) - (format "sec-%s" - (mapconcat 'number-to-string - (org-export-get-headline-number - destination info) "-"))) - (t (error "Shouldn't reach here")))) + (`headline + (let ((href (or (org-element-property :CUSTOM_ID destination) + (org-export-get-reference destination info))) ;; What description to use? (desc ;; Case 1: Headline is numbered and LINK has no ;; description. Display section number. (if (and (org-export-numbered-headline-p destination info) (not desc)) - (mapconcat 'number-to-string + (mapconcat #'number-to-string (org-export-get-headline-number destination info) ".") ;; Case 2: Either the headline is un-numbered or ;; LINK has a custom description. Display LINK's ;; description or headline's title. - (or desc (org-export-data (org-element-property - :title destination) info))))) - (format "<a href=\"#%s\"%s>%s</a>" - (org-export-solidify-link-text href) attributes desc))) + (or desc + (org-export-data + (org-element-property :title destination) info))))) + (format "<a href=\"#%s\"%s>%s</a>" href attributes desc))) ;; Fuzzy link points to a target or an element. - (t - (let* ((path (org-export-solidify-link-text path)) - (org-html-standalone-image-predicate 'org-html--has-caption-p) + (_ + (let* ((ref (org-export-get-reference destination info)) + (org-html-standalone-image-predicate + #'org-html--has-caption-p) (number (cond (desc nil) ((org-html-standalone-image-p destination info) (org-export-get-ordinal (org-element-map destination 'link - 'identity info t) + #'identity info t) info 'link 'org-html-standalone-image-p)) (t (org-export-get-ordinal destination info nil 'org-html--has-caption-p)))) (desc (cond (desc) ((not number) "No description for this link") ((numberp number) (number-to-string number)) - (t (mapconcat 'number-to-string number "."))))) - (format "<a href=\"#%s\"%s>%s</a>" path attributes desc)))))) + (t (mapconcat #'number-to-string number "."))))) + (format "<a href=\"#%s\"%s>%s</a>" ref attributes desc)))))) ;; Coderef: replace link with the reference name or the ;; equivalent line number. ((string= type "coderef") - (let ((fragment (concat "coderef-" path))) - (format "<a href=\"#%s\"%s%s>%s</a>" + (let ((fragment (concat "coderef-" (org-html-encode-plain-text path)))) + (format "<a href=\"#%s\" %s%s>%s</a>" fragment - (org-trim - (format (concat "class=\"coderef\"" - " onmouseover=\"CodeHighlightOn(this, '%s');\"" - " onmouseout=\"CodeHighlightOff(this, '%s');\"") - fragment fragment)) + (format "class=\"coderef\" onmouseover=\"CodeHighlightOn(this, \ +'%s');\" onmouseout=\"CodeHighlightOff(this, '%s');\"" + fragment fragment) attributes (format (org-export-get-coderef-format path desc) (org-export-resolve-coderef path info))))) - ;; Link type is handled by a special function. - ((functionp (setq protocol (nth 2 (assoc type org-link-protocols)))) - (funcall protocol (org-link-unescape path) desc 'html)) ;; External link with a description part. - ((and path desc) (format "<a href=\"%s\"%s>%s</a>" path attributes desc)) + ((and path desc) (format "<a href=\"%s\"%s>%s</a>" + (org-html-encode-plain-text path) + attributes + desc)) ;; External link without a description part. - (path (format "<a href=\"%s\"%s>%s</a>" path attributes path)) + (path (let ((path (org-html-encode-plain-text path))) + (format "<a href=\"%s\"%s>%s</a>" + path + attributes + (org-link-unescape path)))) ;; No path, only description. Try to do something useful. (t (format "<i>%s</i>" desc))))) +;;;; Node Property + +(defun org-html-node-property (node-property _contents _info) + "Transcode a NODE-PROPERTY element from Org to HTML. +CONTENTS is nil. INFO is a plist holding contextual +information." + (format "%s:%s" + (org-element-property :key node-property) + (let ((value (org-element-property :value node-property))) + (if value (concat " " value) "")))) + ;;;; Paragraph (defun org-html-paragraph (paragraph contents info) @@ -2815,13 +3142,19 @@ CONTENTS is the contents of the paragraph, as a string. INFO is the plist used as a communication channel." (let* ((parent (org-export-get-parent paragraph)) (parent-type (org-element-type parent)) - (style '((footnote-definition " class=\"footpara\""))) - (extra (or (cadr (assoc parent-type style)) ""))) + (style '((footnote-definition " class=\"footpara\"") + (org-data " class=\"footpara\""))) + (attributes (org-html--make-attribute-string + (org-export-read-attribute :attr_html paragraph))) + (extra (or (cadr (assq parent-type style)) ""))) (cond - ((and (eq (org-element-type parent) 'item) - (= (org-element-property :begin paragraph) - (org-element-property :contents-begin parent))) - ;; Leading paragraph in a list item have no tags. + ((and (eq parent-type 'item) + (not (org-export-get-previous-element paragraph info)) + (let ((followers (org-export-get-next-element paragraph info 2))) + (and (not (cdr followers)) + (memq (org-element-type (car followers)) '(nil plain-list))))) + ;; First paragraph in an item has no tag if it is alone or + ;; followed, at most, by a sub-list. contents) ((org-html-standalone-image-p paragraph info) ;; Standalone image. @@ -2829,73 +3162,63 @@ the plist used as a communication channel." (let ((raw (org-export-data (org-export-get-caption paragraph) info)) (org-html-standalone-image-predicate - 'org-html--has-caption-p)) + #'org-html--has-caption-p)) (if (not (org-string-nw-p raw)) raw - (concat - "<span class=\"figure-number\">" - (format (org-html--translate "Figure %d:" info) - (org-export-get-ordinal - (org-element-map paragraph 'link - 'identity info t) - info nil 'org-html-standalone-image-p)) - "</span> " raw)))) - (label (org-element-property :name paragraph))) + (concat "<span class=\"figure-number\">" + (format (org-html--translate "Figure %d:" info) + (org-export-get-ordinal + (org-element-map paragraph 'link + #'identity info t) + info nil #'org-html-standalone-image-p)) + " </span>" + raw)))) + (label (and (org-element-property :name paragraph) + (org-export-get-reference paragraph info)))) (org-html--wrap-image contents info caption label))) ;; Regular paragraph. - (t (format "<p%s>\n%s</p>" extra contents))))) + (t (format "<p%s%s>\n%s</p>" + (if (org-string-nw-p attributes) + (concat " " attributes) "") + extra contents))))) ;;;; Plain List -;; FIXME Maybe arg1 is not needed because <li value="20"> already sets -;; the correct value for the item counter -(defun org-html-begin-plain-list (type &optional arg1) - "Insert the beginning of the HTML list depending on TYPE. -When ARG1 is a string, use it as the start parameter for ordered -lists." - (case type - (ordered - (format "<ol class=\"org-ol\"%s>" - (if arg1 (format " start=\"%d\"" arg1) ""))) - (unordered "<ul class=\"org-ul\">") - (descriptive "<dl class=\"org-dl\">"))) - -(defun org-html-end-plain-list (type) - "Insert the end of the HTML list depending on TYPE." - (case type - (ordered "</ol>") - (unordered "</ul>") - (descriptive "</dl>"))) - -(defun org-html-plain-list (plain-list contents info) +(defun org-html-plain-list (plain-list contents _info) "Transcode a PLAIN-LIST element from Org to HTML. CONTENTS is the contents of the list. INFO is a plist holding contextual information." - (let* (arg1 ;; (assoc :counter (org-element-map plain-list 'item - (type (org-element-property :type plain-list))) - (format "%s\n%s%s" - (org-html-begin-plain-list type) - contents (org-html-end-plain-list type)))) + (let* ((type (pcase (org-element-property :type plain-list) + (`ordered "ol") + (`unordered "ul") + (`descriptive "dl") + (other (error "Unknown HTML list type: %s" other)))) + (class (format "org-%s" type)) + (attributes (org-export-read-attribute :attr_html plain-list))) + (format "<%s %s>\n%s</%s>" + type + (org-html--make-attribute-string + (plist-put attributes :class + (org-trim + (mapconcat #'identity + (list class (plist-get attributes :class)) + " ")))) + contents + type))) ;;;; Plain Text (defun org-html-convert-special-strings (string) "Convert special characters in STRING to HTML." - (let ((all org-html-special-string-regexps) - e a re rpl start) - (while (setq a (pop all)) - (setq re (car a) rpl (cdr a) start 0) - (while (string-match re string start) - (setq string (replace-match rpl t nil string)))) - string)) + (dolist (a org-html-special-string-regexps string) + (let ((re (car a)) + (rpl (cdr a))) + (setq string (replace-regexp-in-string re rpl string t))))) (defun org-html-encode-plain-text (text) "Convert plain text characters from TEXT to HTML equivalent. Possible conversions are set in `org-html-protect-char-alist'." - (mapc - (lambda (pair) - (setq text (replace-regexp-in-string (car pair) (cdr pair) text t t))) - org-html-protect-char-alist) - text) + (dolist (pair org-html-protect-char-alist text) + (setq text (replace-regexp-in-string (car pair) (cdr pair) text t t)))) (defun org-html-plain-text (text info) "Transcode a TEXT string from Org to HTML. @@ -2923,60 +3246,52 @@ contextual information." ;; Planning -(defun org-html-planning (planning contents info) +(defun org-html-planning (planning _contents info) "Transcode a PLANNING element from Org to HTML. CONTENTS is nil. INFO is a plist used as a communication channel." - (let ((span-fmt "<span class=\"timestamp-kwd\">%s</span> <span class=\"timestamp\">%s</span>")) - (format - "<p><span class=\"timestamp-wrapper\">%s</span></p>" - (mapconcat - 'identity - (delq nil - (list - (let ((closed (org-element-property :closed planning))) - (when closed - (format span-fmt org-closed-string - (org-translate-time - (org-element-property :raw-value closed))))) - (let ((deadline (org-element-property :deadline planning))) - (when deadline - (format span-fmt org-deadline-string - (org-translate-time - (org-element-property :raw-value deadline))))) - (let ((scheduled (org-element-property :scheduled planning))) - (when scheduled - (format span-fmt org-scheduled-string - (org-translate-time - (org-element-property :raw-value scheduled))))))) - " ")))) + (format + "<p><span class=\"timestamp-wrapper\">%s</span></p>" + (org-trim + (mapconcat + (lambda (pair) + (let ((timestamp (cdr pair))) + (when timestamp + (let ((string (car pair))) + (format "<span class=\"timestamp-kwd\">%s</span> \ +<span class=\"timestamp\">%s</span> " + string + (org-html-plain-text (org-timestamp-translate timestamp) + info)))))) + `((,org-closed-string . ,(org-element-property :closed planning)) + (,org-deadline-string . ,(org-element-property :deadline planning)) + (,org-scheduled-string . ,(org-element-property :scheduled planning))) + "")))) ;;;; Property Drawer -(defun org-html-property-drawer (property-drawer contents info) +(defun org-html-property-drawer (_property-drawer contents _info) "Transcode a PROPERTY-DRAWER element from Org to HTML. -CONTENTS is nil. INFO is a plist holding contextual -information." - ;; The property drawer isn't exported but we want separating blank - ;; lines nonetheless. - "") +CONTENTS holds the contents of the drawer. INFO is a plist +holding contextual information." + (and (org-string-nw-p contents) + (format "<pre class=\"example\">\n%s</pre>" contents))) ;;;; 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 "<blockquote>\n%s</blockquote>" contents)) - -;;;; Quote Section - -(defun org-html-quote-section (quote-section contents info) - "Transcode a QUOTE-SECTION element from Org to HTML. -CONTENTS is nil. INFO is a plist holding contextual information." - (let ((value (org-remove-indentation - (org-element-property :value quote-section)))) - (when value (format "<pre>\n%s</pre>" value)))) + (format "<blockquote%s>\n%s</blockquote>" + (let* ((name (org-element-property :name quote-block)) + (attributes (org-export-read-attribute :attr_html quote-block)) + (a (org-html--make-attribute-string + (if (or (not name) (plist-member attributes :id)) + attributes + (plist-put attributes :id name))))) + (if (org-string-nw-p a) (concat " " a) "")) + contents)) ;;;; Section @@ -2989,16 +3304,19 @@ holding contextual information." (if (not parent) contents ;; Get div's class and id references. (let* ((class-num (+ (org-export-get-relative-level parent info) - (1- org-html-toplevel-hlevel))) + (1- (plist-get info :html-toplevel-hlevel)))) (section-number - (mapconcat - 'number-to-string - (org-export-get-headline-number parent info) "-"))) + (and (org-export-numbered-headline-p parent info) + (mapconcat + #'number-to-string + (org-export-get-headline-number parent info) "-")))) ;; Build return value. - (format "<div class=\"outline-text-%d\" id=\"text-%s\">\n%s</div>" + (format "<div class=\"outline-text-%d\" id=\"text-%s\">\n%s</div>\n" class-num - (or (org-element-property :CUSTOM_ID parent) section-number) - contents))))) + (or (org-element-property :CUSTOM_ID parent) + section-number + (org-export-get-reference parent info)) + (or contents "")))))) ;;;; Radio Target @@ -3006,9 +3324,8 @@ 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 ((id (org-export-solidify-link-text - (org-element-property :value radio-target)))) - (org-html--anchor id text))) + (let ((ref (org-export-get-reference radio-target info))) + (org-html--anchor ref text nil info))) ;;;; Special Block @@ -3016,52 +3333,72 @@ contextual information." "Transcode a SPECIAL-BLOCK element from Org to HTML. CONTENTS holds the contents of the block. INFO is a plist holding contextual information." - (let* ((block-type (downcase - (org-element-property :type special-block))) - (contents (or contents "")) - (html5-fancy (and (org-html-html5-p info) - (plist-get info :html-html5-fancy) - (member block-type org-html-html5-elements))) - (attributes (org-export-read-attribute :attr_html special-block))) + (let* ((block-type (org-element-property :type special-block)) + (html5-fancy (and (org-html--html5-fancy-p info) + (member block-type org-html-html5-elements))) + (attributes (org-export-read-attribute :attr_html special-block))) (unless html5-fancy (let ((class (plist-get attributes :class))) - (setq attributes (plist-put attributes :class - (if class (concat class " " block-type) - block-type))))) - (setq attributes (org-html--make-attribute-string attributes)) - (when (not (equal attributes "")) - (setq attributes (concat " " attributes))) - (if html5-fancy - (format "<%s%s>\n%s</%s>" block-type attributes - contents block-type) - (format "<div%s>\n%s\n</div>" attributes contents)))) + (setq attributes (plist-put attributes :class + (if class (concat class " " block-type) + block-type))))) + (let* ((contents (or contents "")) + (name (org-element-property :name special-block)) + (a (org-html--make-attribute-string + (if (or (not name) (plist-member attributes :id)) + attributes + (plist-put attributes :id name)))) + (str (if (org-string-nw-p a) (concat " " a) ""))) + (if html5-fancy + (format "<%s%s>\n%s</%s>" block-type str contents block-type) + (format "<div%s>\n%s\n</div>" str contents))))) ;;;; Src Block -(defun org-html-src-block (src-block contents info) +(defun org-html-src-block (src-block _contents info) "Transcode a SRC-BLOCK element from Org to HTML. CONTENTS holds the contents of the item. INFO is a plist holding contextual information." (if (org-export-read-attribute :attr_html src-block :textarea) (org-html--textarea-block src-block) - (let ((lang (org-element-property :language src-block)) - (caption (org-export-get-caption src-block)) + (let* ((lang (org-element-property :language src-block)) (code (org-html-format-code src-block info)) - (label (let ((lbl (org-element-property :name src-block))) - (if (not lbl) "" - (format " id=\"%s\"" - (org-export-solidify-link-text lbl)))))) + (label (let ((lbl (and (org-element-property :name src-block) + (org-export-get-reference src-block info)))) + (if lbl (format " id=\"%s\"" lbl) ""))) + (klipsify (and (plist-get info :html-klipsify-src) + (member lang '("javascript" "js" + "ruby" "scheme" "clojure" "php" "html"))))) (if (not lang) (format "<pre class=\"example\"%s>\n%s</pre>" label code) - (format - "<div class=\"org-src-container\">\n%s%s\n</div>" - (if (not caption) "" - (format "<label class=\"org-src-name\">%s</label>" - (org-export-data caption info))) - (format "\n<pre class=\"src src-%s\"%s>%s</pre>" lang label code)))))) + (format "<div class=\"org-src-container\">\n%s%s\n</div>" + ;; Build caption. + (let ((caption (org-export-get-caption src-block))) + (if (not caption) "" + (let ((listing-number + (format + "<span class=\"listing-number\">%s </span>" + (format + (org-html--translate "Listing %d:" info) + (org-export-get-ordinal + src-block info nil #'org-html--has-caption-p))))) + (format "<label class=\"org-src-name\">%s%s</label>" + listing-number + (org-trim (org-export-data caption info)))))) + ;; Contents. + (if klipsify + (format "<pre><code class=\"src src-%s\"%s%s>%s</code></pre>" + lang + label + (if (string= lang "html") + " data-editor-type=\"html\"" + "") + code) + (format "<pre class=\"src src-%s\"%s>%s</pre>" + lang label code))))))) ;;;; Statistics Cookie -(defun org-html-statistics-cookie (statistics-cookie contents info) +(defun org-html-statistics-cookie (statistics-cookie _contents _info) "Transcode a STATISTICS-COOKIE object from Org to HTML. CONTENTS is nil. INFO is a plist holding contextual information." (let ((cookie-value (org-element-property :value statistics-cookie))) @@ -3069,16 +3406,18 @@ CONTENTS is nil. INFO is a plist holding contextual information." ;;;; Strike-Through -(defun org-html-strike-through (strike-through contents info) +(defun org-html-strike-through (_strike-through contents info) "Transcode STRIKE-THROUGH from Org to HTML. CONTENTS is the text with strike-through markup. INFO is a plist holding contextual information." - (format (or (cdr (assq 'strike-through org-html-text-markup-alist)) "%s") - contents)) + (format + (or (cdr (assq 'strike-through (plist-get info :html-text-markup-alist))) + "%s") + contents)) ;;;; Subscript -(defun org-html-subscript (subscript contents info) +(defun org-html-subscript (_subscript contents _info) "Transcode a SUBSCRIPT object from Org to HTML. CONTENTS is the contents of the object. INFO is a plist holding contextual information." @@ -3086,7 +3425,7 @@ contextual information." ;;;; Superscript -(defun org-html-superscript (superscript contents info) +(defun org-html-superscript (_superscript contents _info) "Transcode a SUPERSCRIPT object from Org to HTML. CONTENTS is the contents of the object. INFO is a plist holding contextual information." @@ -3101,24 +3440,30 @@ channel." (let* ((table-row (org-export-get-parent table-cell)) (table (org-export-get-parent-table table-cell)) (cell-attrs - (if (not org-html-table-align-individual-fields) "" + (if (not (plist-get info :html-table-align-individual-fields)) "" (format (if (and (boundp 'org-html-format-table-no-css) org-html-format-table-no-css) - " align=\"%s\"" " class=\"%s\"") + " align=\"%s\"" " class=\"org-%s\"") (org-export-table-cell-alignment table-cell info))))) (when (or (not contents) (string= "" (org-trim contents))) (setq contents " ")) (cond ((and (org-export-table-has-header-p table info) (= 1 (org-export-table-row-group table-row info))) - (concat "\n" (format (car org-html-table-header-tags) "col" cell-attrs) - contents (cdr org-html-table-header-tags))) - ((and org-html-table-use-header-tags-for-first-column + (let ((header-tags (plist-get info :html-table-header-tags))) + (concat "\n" (format (car header-tags) "col" cell-attrs) + contents + (cdr header-tags)))) + ((and (plist-get info :html-table-use-header-tags-for-first-column) (zerop (cdr (org-export-table-cell-address table-cell info)))) - (concat "\n" (format (car org-html-table-header-tags) "row" cell-attrs) - contents (cdr org-html-table-header-tags))) - (t (concat "\n" (format (car org-html-table-data-tags) cell-attrs) - contents (cdr org-html-table-data-tags)))))) + (let ((header-tags (plist-get info :html-table-header-tags))) + (concat "\n" (format (car header-tags) "row" cell-attrs) + contents + (cdr header-tags)))) + (t (let ((data-tags (plist-get info :html-table-data-tags))) + (concat "\n" (format (car data-tags) cell-attrs) + contents + (cdr data-tags))))))) ;;;; Table Row @@ -3129,40 +3474,45 @@ communication channel." ;; Rules are ignored since table separators are deduced from ;; borders of the current row. (when (eq (org-element-property :type table-row) 'standard) - (let* ((rowgroup-number (org-export-table-row-group table-row info)) - (row-number (org-export-table-row-number table-row info)) - (start-rowgroup-p + (let* ((group (org-export-table-row-group table-row info)) + (number (org-export-table-row-number table-row info)) + (start-group-p (org-export-table-row-starts-rowgroup-p table-row info)) - (end-rowgroup-p + (end-group-p (org-export-table-row-ends-rowgroup-p table-row info)) - ;; `top-row-p' and `end-rowgroup-p' are not used directly - ;; but should be set so that `org-html-table-row-tags' can - ;; use them (see the docstring of this variable.) - (top-row-p (and (equal start-rowgroup-p '(top)) - (equal end-rowgroup-p '(below top)))) - (bottom-row-p (and (equal start-rowgroup-p '(above)) - (equal end-rowgroup-p '(bottom above)))) - (rowgroup-tags + (topp (and (equal start-group-p '(top)) + (equal end-group-p '(below top)))) + (bottomp (and (equal start-group-p '(above)) + (equal end-group-p '(bottom above)))) + (row-open-tag + (pcase (plist-get info :html-table-row-open-tag) + ((and accessor (pred functionp)) + (funcall accessor + number group start-group-p end-group-p topp bottomp)) + (accessor accessor))) + (row-close-tag + (pcase (plist-get info :html-table-row-close-tag) + ((and accessor (pred functionp)) + (funcall accessor + number group start-group-p end-group-p topp bottomp)) + (accessor accessor))) + (group-tags (cond - ;; Case 1: Row belongs to second or subsequent rowgroups. - ((not (= 1 rowgroup-number)) - '("<tbody>" . "\n</tbody>")) - ;; Case 2: Row is from first rowgroup. Table has >=1 rowgroups. + ;; Row belongs to second or subsequent groups. + ((not (= 1 group)) '("<tbody>" . "\n</tbody>")) + ;; Row is from first group. Table has >=1 groups. ((org-export-table-has-header-p (org-export-get-parent-table table-row) info) '("<thead>" . "\n</thead>")) - ;; Case 2: Row is from first and only row group. + ;; Row is from first and only group. (t '("<tbody>" . "\n</tbody>"))))) - (concat - ;; Begin a rowgroup? - (when start-rowgroup-p (car rowgroup-tags)) - ;; Actual table row - (concat "\n" (eval (car org-html-table-row-tags)) - contents - "\n" - (eval (cdr org-html-table-row-tags))) - ;; End a rowgroup? - (when end-rowgroup-p (cdr rowgroup-tags)))))) + (concat (and start-group-p (car group-tags)) + (concat "\n" + row-open-tag + contents + "\n" + row-close-tag) + (and end-group-p (cdr group-tags)))))) ;;;; Table @@ -3178,7 +3528,7 @@ INFO is a plist used as a communication channel." (if (not special-column-p) (org-element-contents table-row) (cdr (org-element-contents table-row))))) -(defun org-html-table--table.el-table (table info) +(defun org-html-table--table.el-table (table _info) "Format table.el tables into HTML. INFO is a plist used as a communication channel." (when (eq (org-element-property :type table) 'table.el) @@ -3199,134 +3549,123 @@ INFO is a plist used as a communication channel." "Transcode a TABLE element from Org to HTML. CONTENTS is the contents of the table. INFO is a plist holding contextual information." - (case (org-element-property :type table) - ;; Case 1: table.el table. Convert it using appropriate tools. - (table.el (org-html-table--table.el-table table info)) - ;; Case 2: Standard table. - (t - (let* ((label (org-element-property :name table)) - (caption (org-export-get-caption table)) - (number (org-export-get-ordinal - table info nil 'org-html--has-caption-p)) - (attributes - (org-html--make-attribute-string - (org-combine-plists - (and label (list :id (org-export-solidify-link-text label))) - (and (not (org-html-html5-p info)) - (plist-get info :html-table-attributes)) - (org-export-read-attribute :attr_html table)))) - (alignspec - (if (and (boundp 'org-html-format-table-no-css) - org-html-format-table-no-css) - "align=\"%s\"" "class=\"%s\"")) - (table-column-specs - (function - (lambda (table info) - (mapconcat - (lambda (table-cell) - (let ((alignment (org-export-table-cell-alignment - table-cell info))) - (concat - ;; Begin a colgroup? - (when (org-export-table-cell-starts-colgroup-p - table-cell info) - "\n<colgroup>") - ;; Add a column. Also specify it's alignment. - (format "\n%s" - (org-html-close-tag - "col" (concat " " (format alignspec alignment)) info)) - ;; End a colgroup? - (when (org-export-table-cell-ends-colgroup-p - table-cell info) - "\n</colgroup>")))) - (org-html-table-first-row-data-cells table info) "\n"))))) - (format "<table%s>\n%s\n%s\n%s</table>" - (if (equal attributes "") "" (concat " " attributes)) - (if (not caption) "" - (format (if org-html-table-caption-above - "<caption class=\"t-above\">%s</caption>" - "<caption class=\"t-bottom\">%s</caption>") - (concat - "<span class=\"table-number\">" - (format (org-html--translate "Table %d:" info) number) - "</span> " (org-export-data caption info)))) - (funcall table-column-specs table info) - contents))))) + (if (eq (org-element-property :type table) 'table.el) + ;; "table.el" table. Convert it using appropriate tools. + (org-html-table--table.el-table table info) + ;; Standard table. + (let* ((caption (org-export-get-caption table)) + (number (org-export-get-ordinal + table info nil #'org-html--has-caption-p)) + (attributes + (org-html--make-attribute-string + (org-combine-plists + (and (org-element-property :name table) + (list :id (org-export-get-reference table info))) + (and (not (org-html-html5-p info)) + (plist-get info :html-table-attributes)) + (org-export-read-attribute :attr_html table)))) + (alignspec + (if (bound-and-true-p org-html-format-table-no-css) + "align=\"%s\"" + "class=\"org-%s\"")) + (table-column-specs + (lambda (table info) + (mapconcat + (lambda (table-cell) + (let ((alignment (org-export-table-cell-alignment + table-cell info))) + (concat + ;; Begin a colgroup? + (when (org-export-table-cell-starts-colgroup-p + table-cell info) + "\n<colgroup>") + ;; Add a column. Also specify its alignment. + (format "\n%s" + (org-html-close-tag + "col" (concat " " (format alignspec alignment)) info)) + ;; End a colgroup? + (when (org-export-table-cell-ends-colgroup-p + table-cell info) + "\n</colgroup>")))) + (org-html-table-first-row-data-cells table info) "\n")))) + (format "<table%s>\n%s\n%s\n%s</table>" + (if (equal attributes "") "" (concat " " attributes)) + (if (not caption) "" + (format (if (plist-get info :html-table-caption-above) + "<caption class=\"t-above\">%s</caption>" + "<caption class=\"t-bottom\">%s</caption>") + (concat + "<span class=\"table-number\">" + (format (org-html--translate "Table %d:" info) number) + "</span> " (org-export-data caption info)))) + (funcall table-column-specs table info) + contents)))) ;;;; Target -(defun org-html-target (target contents info) +(defun org-html-target (target _contents info) "Transcode a TARGET object from Org to HTML. CONTENTS is nil. INFO is a plist holding contextual information." - (let ((id (org-export-solidify-link-text - (org-element-property :value target)))) - (org-html--anchor id))) + (let ((ref (org-export-get-reference target info))) + (org-html--anchor ref nil nil info))) ;;;; Timestamp -(defun org-html-timestamp (timestamp contents info) +(defun org-html-timestamp (timestamp _contents info) "Transcode a TIMESTAMP object from Org to HTML. CONTENTS is nil. INFO is a plist holding contextual information." - (let ((value (org-html-plain-text - (org-timestamp-translate timestamp) info))) + (let ((value (org-html-plain-text (org-timestamp-translate timestamp) info))) (format "<span class=\"timestamp-wrapper\"><span class=\"timestamp\">%s</span></span>" (replace-regexp-in-string "--" "–" value)))) ;;;; Underline -(defun org-html-underline (underline contents info) +(defun org-html-underline (_underline contents info) "Transcode UNDERLINE from Org to HTML. CONTENTS is the text with underline markup. INFO is a plist holding contextual information." - (format (or (cdr (assq 'underline org-html-text-markup-alist)) "%s") + (format (or (cdr (assq 'underline (plist-get info :html-text-markup-alist))) + "%s") contents)) ;;;; Verbatim -(defun org-html-verbatim (verbatim contents info) +(defun org-html-verbatim (verbatim _contents info) "Transcode VERBATIM from Org to HTML. CONTENTS is nil. INFO is a plist holding contextual information." - (format (or (cdr (assq 'verbatim org-html-text-markup-alist)) "%s") + (format (or (cdr (assq 'verbatim (plist-get info :html-text-markup-alist))) "%s") (org-html-encode-plain-text (org-element-property :value verbatim)))) ;;;; Verse Block -(defun org-html-verse-block (verse-block contents info) +(defun org-html-verse-block (_verse-block contents info) "Transcode a VERSE-BLOCK element from Org to HTML. CONTENTS is verse block contents. INFO is a plist holding contextual information." - ;; Replace each newline character with line break. Also replace - ;; each blank line with a line break. - (setq contents (replace-regexp-in-string - "^ *\\\\\\\\$" (format "%s\n" (org-html-close-tag "br" nil info)) - (replace-regexp-in-string - "\\(\\\\\\\\\\)?[ \t]*\n" - (format "%s\n" (org-html-close-tag "br" nil info)) contents))) - ;; Replace each white space at beginning of a line with a - ;; non-breaking space. - (while (string-match "^[ \t]+" contents) - (let* ((num-ws (length (match-string 0 contents))) - (ws (let (out) (dotimes (i num-ws out) - (setq out (concat out " ")))))) - (setq contents (replace-match ws nil t contents)))) - (format "<p class=\"verse\">\n%s</p>" contents)) + (format "<p class=\"verse\">\n%s</p>" + ;; Replace leading white spaces with non-breaking spaces. + (replace-regexp-in-string + "^[ \t]+" (lambda (m) (org-html--make-string (length m) " ")) + ;; Replace each newline character with line break. Also + ;; remove any trailing "br" close-tag so as to avoid + ;; duplicates. + (let* ((br (org-html-close-tag "br" nil info)) + (re (format "\\(?:%s\\)?[ \t]*\n" (regexp-quote br)))) + (replace-regexp-in-string re (concat br "\n") contents))))) ;;; Filter Functions -(defun org-html-final-function (contents backend info) +(defun org-html-final-function (contents _backend info) "Filter to indent the HTML and convert HTML entities." (with-temp-buffer (insert contents) (set-auto-mode t) - (if org-html-indent + (if (plist-get info :html-indent) (indent-region (point-min) (point-max))) - (when org-html-use-unicode-chars - (require 'mm-url) - (mm-url-decode-entities)) (buffer-substring-no-properties (point-min) (point-max)))) @@ -3370,10 +3709,10 @@ is non-nil." ;;;###autoload (defun org-html-convert-region-to-html () - "Assume the current region has org-mode syntax, and convert it to HTML. + "Assume the current region has Org syntax, and convert it to HTML. This can be used in any buffer. For example, you can write an -itemized list in org-mode syntax in an HTML buffer and use this -command to convert it." +itemized list in Org syntax in an HTML buffer and use this command +to convert it." (interactive) (org-export-replace-region-by 'html)) @@ -3407,7 +3746,9 @@ file-local settings. Return output file's name." (interactive) - (let* ((extension (concat "." org-html-extension)) + (let* ((extension (concat "." (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 @@ -3424,7 +3765,8 @@ publishing directory. Return output file name." (org-publish-org-to 'html filename (concat "." (or (plist-get plist :html-extension) - org-html-extension "html")) + org-html-extension + "html")) plist pub-dir)) diff --git a/lisp/org/ox-icalendar.el b/lisp/org/ox-icalendar.el index fe6d08a85b5..4783f1158c7 100644 --- a/lisp/org/ox-icalendar.el +++ b/lisp/org/ox-icalendar.el @@ -1,4 +1,4 @@ -;;; ox-icalendar.el --- iCalendar Back-End for Org Export Engine +;;; ox-icalendar.el --- iCalendar Back-End for Org Export Engine -*- lexical-binding: t; -*- ;; Copyright (C) 2004-2017 Free Software Foundation, Inc. @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -31,7 +31,7 @@ ;;; Code: -(eval-when-compile (require 'cl)) +(require 'cl-lib) (require 'ox-ascii) (declare-function org-bbdb-anniv-export-ical "org-bbdb" nil) @@ -46,7 +46,7 @@ (defcustom org-icalendar-combined-agenda-file "~/org.ics" "The file name for the iCalendar file covering all agenda files. -This file is created with the command \\[org-icalendar-combine-agenda-files]. +This file is created with the command `\\[org-icalendar-combine-agenda-files]'. The file name should be absolute. It will be overwritten without warning." :group 'org-export-icalendar :type 'file) @@ -77,7 +77,7 @@ for timed events. If non-zero, alarms are created. (defcustom org-icalendar-exclude-tags nil "Tags that exclude a tree from export. This variable allows specifying different exclude tags from other -back-ends. It can also be set with the ICAL_EXCLUDE_TAGS +back-ends. It can also be set with the ICALENDAR_EXCLUDE_TAGS keyword." :group 'org-export-icalendar :type '(repeat (string :tag "Tag"))) @@ -85,10 +85,11 @@ keyword." (defcustom org-icalendar-use-deadline '(event-if-not-todo todo-due) "Contexts where iCalendar export should use a deadline time stamp. -This is a list with several symbols in it. Valid symbol are: +This is a list with possibly several symbols in it. Valid symbols are: + `event-if-todo' Deadlines in TODO entries become calendar events. `event-if-not-todo' Deadlines in non-TODO entries become calendar events. -`todo-due' Use deadlines in TODO entries as due-dates" +`todo-due' Use deadlines in TODO entries as due-dates." :group 'org-export-icalendar :type '(set :greedy t (const :tag "Deadlines in non-TODO entries become events" @@ -101,7 +102,8 @@ This is a list with several symbols in it. Valid symbol are: (defcustom org-icalendar-use-scheduled '(todo-start) "Contexts where iCalendar export should use a scheduling time stamp. -This is a list with several symbols in it. Valid symbol are: +This is a list with possibly several symbols in it. Valid symbols are: + `event-if-todo' Scheduling time stamps in TODO entries become an event. `event-if-not-todo' Scheduling time stamps in non-TODO entries become an event. `todo-start' Scheduling time stamps in TODO entries become start date. @@ -256,11 +258,18 @@ re-read the iCalendar file.") '((:exclude-tags "ICALENDAR_EXCLUDE_TAGS" nil org-icalendar-exclude-tags split) (:with-timestamps nil "<" org-icalendar-with-timestamps) - (:with-vtodo nil nil org-icalendar-include-todo) - ;; The following property will be non-nil when export has been - ;; started from org-agenda-mode. In this case, any entry without - ;; a non-nil "ICALENDAR_MARK" property will be ignored. - (:icalendar-agenda-view nil nil nil)) + ;; Other variables. + (:icalendar-alarm-time nil nil org-icalendar-alarm-time) + (:icalendar-categories nil nil org-icalendar-categories) + (:icalendar-date-time-format nil nil org-icalendar-date-time-format) + (:icalendar-include-bbdb-anniversaries nil nil org-icalendar-include-bbdb-anniversaries) + (:icalendar-include-body nil nil org-icalendar-include-body) + (:icalendar-include-sexps nil nil org-icalendar-include-sexps) + (:icalendar-include-todo nil nil org-icalendar-include-todo) + (:icalendar-store-UID nil nil org-icalendar-store-UID) + (:icalendar-timezone nil nil org-icalendar-timezone) + (:icalendar-use-deadline nil nil org-icalendar-use-deadline) + (:icalendar-use-scheduled nil nil org-icalendar-use-scheduled)) :filters-alist '((:filter-headline . org-icalendar-clear-blank-lines)) :menu-entry @@ -275,22 +284,18 @@ re-read the iCalendar file.") ;;; Internal Functions -(defun org-icalendar-create-uid (file &optional bell h-markers) +(defun org-icalendar-create-uid (file &optional bell) "Set ID property on headlines missing it in FILE. When optional argument BELL is non-nil, inform the user with -a message if the file was modified. With optional argument -H-MARKERS non-nil, it is a list of markers for the headlines -which will be updated." - (let ((pt (if h-markers (goto-char (car h-markers)) (point-min))) - modified-flag) +a message if the file was modified." + (let (modified-flag) (org-map-entries (lambda () (let ((entry (org-element-at-point))) - (unless (or (< (point) pt) (org-element-property :ID entry)) + (unless (org-element-property :ID entry) (org-id-get-create) (setq modified-flag t) - (forward-line)) - (when h-markers (setq org-map-continue-from (pop h-markers))))) + (forward-line)))) nil nil 'comment) (when (and bell modified-flag) (message "ID properties created in file \"%s\"" file) @@ -318,19 +323,17 @@ A headline is blocked when either ;; Check :ORDERED: node property. (catch 'blockedp (let ((current headline)) - (mapc (lambda (parent) - (cond - ((not (org-element-property :todo-keyword parent)) - (throw 'blockedp nil)) - ((org-not-nil (org-element-property :ORDERED parent)) - (let ((sibling current)) - (while (setq sibling (org-export-get-previous-element - sibling info)) - (when (eq (org-element-property :todo-type sibling) 'todo) - (throw 'blockedp t))))) - (t (setq current parent)))) - (org-export-get-genealogy headline)) - nil)))) + (dolist (parent (org-element-lineage headline)) + (cond + ((not (org-element-property :todo-keyword parent)) + (throw 'blockedp nil)) + ((org-not-nil (org-element-property :ORDERED parent)) + (let ((sibling current)) + (while (setq sibling (org-export-get-previous-element + sibling info)) + (when (eq (org-element-property :todo-type sibling) 'todo) + (throw 'blockedp t))))) + (t (setq current parent)))))))) (defun org-icalendar-use-UTC-date-time-p () "Non-nil when `org-icalendar-date-time-format' requires UTC time." @@ -338,7 +341,7 @@ A headline is blocked when either (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 utc) +(defun org-icalendar-convert-timestamp (timestamp keyword &optional end tz) "Convert TIMESTAMP to iCalendar format. TIMESTAMP is a timestamp object. KEYWORD is added in front of @@ -349,8 +352,11 @@ Also increase the hour by two (if time string contains a time), or the day by one (if it does not contain a time) when no explicit ending time is specified. -When optional argument UTC is non-nil, time will be expressed in -Universal Time, ignoring `org-icalendar-date-time-format'." +When optional argument TZ is non-nil, timezone data time will be +added to the timestamp. It can be the string \"UTC\", to use UTC +time, or a string in the IANA TZ database +format (e.g. \"Europe/London\"). In either case, the value of +`org-icalendar-date-time-format' will be ignored." (let* ((year-start (org-element-property :year-start timestamp)) (year-end (org-element-property :year-end timestamp)) (month-start (org-element-property :month-start timestamp)) @@ -384,8 +390,9 @@ Universal Time, ignoring `org-icalendar-date-time-format'." (concat keyword (format-time-string - (cond (utc ":%Y%m%dT%H%M%SZ") + (cond ((string-equal tz "UTC") ":%Y%m%dT%H%M%SZ") ((not with-time-p) ";VALUE=DATE:%Y%m%d") + ((stringp tz) (concat ";TZID=" tz ":%Y%m%dT%H%M%S")) (t (replace-regexp-in-string "%Z" org-icalendar-timezone org-icalendar-date-time-format @@ -393,8 +400,11 @@ Universal Time, ignoring `org-icalendar-date-time-format'." ;; Convert timestamp into internal time in order to use ;; `format-time-string' and fix any mistake (i.e. MI >= 60). (encode-time 0 mi h d m y) - (not (not (or utc (and with-time-p - (org-icalendar-use-UTC-date-time-p))))))))) + (and (or (string-equal tz "UTC") + (and (null tz) + with-time-p + (org-icalendar-use-UTC-date-time-p))) + t))))) (defun org-icalendar-dtstamp () "Return DTSTAMP property, as a string." @@ -405,27 +415,25 @@ Universal Time, ignoring `org-icalendar-date-time-format'." ENTRY is a headline or an inlinetask element. INFO is a plist used as a communication channel." (mapconcat - 'identity + #'identity (org-uniquify (let (categories) - (mapc (lambda (type) - (case type - (category - (push (org-export-get-category entry info) categories)) - (todo-state - (let ((todo (org-element-property :todo-keyword entry))) - (and todo (push todo categories)))) - (local-tags - (setq categories - (append (nreverse (org-export-get-tags entry info)) - categories))) - (all-tags - (setq categories - (append (nreverse (org-export-get-tags entry info nil t)) - categories))))) - org-icalendar-categories) - ;; Return list of categories, following specified order. - (nreverse categories))) ",")) + (dolist (type org-icalendar-categories (nreverse categories)) + (cl-case type + (category + (push (org-export-get-category entry info) categories)) + (todo-state + (let ((todo (org-element-property :todo-keyword entry))) + (and todo (push todo categories)))) + (local-tags + (setq categories + (append (nreverse (org-export-get-tags entry info)) + categories))) + (all-tags + (setq categories + (append (nreverse (org-export-get-tags entry info nil t)) + categories))))))) + ",")) (defun org-icalendar-transcode-diary-sexp (sexp uid summary) "Transcode a diary sexp into iCalendar format. @@ -457,7 +465,7 @@ or subject for the event." (mapconcat (lambda (line) ;; Limit each line to a maximum of 75 characters. If it is - ;; longer, fold it by using "\n " as a continuation marker. + ;; longer, fold it by using "\r\n " as a continuation marker. (let ((len (length line))) (if (<= len 75) line (let ((folded-line (substring line 0 75)) @@ -467,17 +475,17 @@ or subject for the event." ;; line, real contents must be split at 74 chars. (while (< (setq chunk-end (+ chunk-start 74)) len) (setq folded-line - (concat folded-line "\n " + (concat folded-line "\r\n " (substring line chunk-start chunk-end)) chunk-start chunk-end)) - (concat folded-line "\n " (substring line chunk-start)))))) - (org-split-string s "\n") "\n"))) + (concat folded-line "\r\n " (substring line chunk-start)))))) + (org-split-string s "\n") "\r\n"))) ;;; Filters -(defun org-icalendar-clear-blank-lines (headline back-end info) +(defun org-icalendar-clear-blank-lines (headline _back-end _info) "Remove blank lines in HEADLINE export. HEADLINE is a string representing a transcoded headline. BACK-END and INFO are ignored." @@ -522,99 +530,102 @@ inlinetask within the section." (cons 'org-data (cons nil (org-element-contents first)))))))) (concat - (unless (and (plist-get info :icalendar-agenda-view) - (not (org-element-property :ICALENDAR-MARK entry))) - (let ((todo-type (org-element-property :todo-type entry)) - (uid (or (org-element-property :ID entry) (org-id-new))) - (summary (org-icalendar-cleanup-string - (or (org-element-property :SUMMARY entry) - (org-export-data - (org-element-property :title entry) info)))) - (loc (org-icalendar-cleanup-string - (org-element-property :LOCATION entry))) - ;; Build description of the entry from associated - ;; section (headline) or contents (inlinetask). - (desc - (org-icalendar-cleanup-string - (or (org-element-property :DESCRIPTION entry) - (let ((contents (org-export-data inside info))) - (cond - ((not (org-string-nw-p contents)) nil) - ((wholenump org-icalendar-include-body) - (let ((contents (org-trim contents))) - (substring - contents 0 (min (length contents) - org-icalendar-include-body)))) - (org-icalendar-include-body (org-trim contents))))))) - (cat (org-icalendar-get-categories entry info))) - (concat - ;; Events: Delegate to `org-icalendar--vevent' to - ;; generate "VEVENT" component from scheduled, deadline, - ;; or any timestamp in the entry. - (let ((deadline (org-element-property :deadline entry))) - (and deadline - (memq (if todo-type 'event-if-todo 'event-if-not-todo) - org-icalendar-use-deadline) - (org-icalendar--vevent - entry deadline (concat "DL-" uid) - (concat "DL: " summary) loc desc cat))) - (let ((scheduled (org-element-property :scheduled entry))) - (and scheduled - (memq (if todo-type 'event-if-todo 'event-if-not-todo) - org-icalendar-use-scheduled) - (org-icalendar--vevent - entry scheduled (concat "SC-" uid) - (concat "S: " summary) loc desc cat))) - ;; When collecting plain timestamps from a headline and - ;; its title, skip inlinetasks since collection will - ;; happen once ENTRY is one of them. + (let ((todo-type (org-element-property :todo-type entry)) + (uid (or (org-element-property :ID entry) (org-id-new))) + (summary (org-icalendar-cleanup-string + (or (org-element-property :SUMMARY entry) + (org-export-data + (org-element-property :title entry) info)))) + (loc (org-icalendar-cleanup-string + (org-export-get-node-property + :LOCATION entry + (org-property-inherit-p "LOCATION")))) + ;; Build description of the entry from associated section + ;; (headline) or contents (inlinetask). + (desc + (org-icalendar-cleanup-string + (or (org-element-property :DESCRIPTION entry) + (let ((contents (org-export-data inside info))) + (cond + ((not (org-string-nw-p contents)) nil) + ((wholenump org-icalendar-include-body) + (let ((contents (org-trim contents))) + (substring + contents 0 (min (length contents) + org-icalendar-include-body)))) + (org-icalendar-include-body (org-trim contents))))))) + (cat (org-icalendar-get-categories entry info)) + (tz (org-export-get-node-property + :TIMEZONE entry + (org-property-inherit-p "TIMEZONE")))) + (concat + ;; Events: Delegate to `org-icalendar--vevent' to generate + ;; "VEVENT" component from scheduled, deadline, or any + ;; timestamp in the entry. + (let ((deadline (org-element-property :deadline entry))) + (and deadline + (memq (if todo-type 'event-if-todo 'event-if-not-todo) + org-icalendar-use-deadline) + (org-icalendar--vevent + entry deadline (concat "DL-" uid) + (concat "DL: " summary) loc desc cat tz))) + (let ((scheduled (org-element-property :scheduled entry))) + (and scheduled + (memq (if todo-type 'event-if-todo 'event-if-not-todo) + org-icalendar-use-scheduled) + (org-icalendar--vevent + entry scheduled (concat "SC-" uid) + (concat "S: " summary) loc desc cat tz))) + ;; When collecting plain timestamps from a headline and its + ;; title, skip inlinetasks since collection will happen once + ;; ENTRY is one of them. + (let ((counter 0)) + (mapconcat + #'identity + (org-element-map (cons (org-element-property :title entry) + (org-element-contents inside)) + 'timestamp + (lambda (ts) + (when (let ((type (org-element-property :type ts))) + (cl-case (plist-get info :with-timestamps) + (active (memq type '(active active-range))) + (inactive (memq type '(inactive inactive-range))) + ((t) t))) + (let ((uid (format "TS%d-%s" (cl-incf counter) uid))) + (org-icalendar--vevent + entry ts uid summary loc desc cat tz)))) + info nil (and (eq type 'headline) 'inlinetask)) + "")) + ;; Task: First check if it is appropriate to export it. If + ;; so, call `org-icalendar--vtodo' to transcode it into + ;; a "VTODO" component. + (when (and todo-type + (cl-case (plist-get info :icalendar-include-todo) + (all t) + (unblocked + (and (eq type 'headline) + (not (org-icalendar-blocked-headline-p + entry info)))) + ((t) (eq todo-type 'todo)))) + (org-icalendar--vtodo entry uid summary loc desc cat tz)) + ;; Diary-sexp: Collect every diary-sexp element within ENTRY + ;; and its title, and transcode them. If ENTRY is + ;; a headline, skip inlinetasks: they will be handled + ;; separately. + (when org-icalendar-include-sexps (let ((counter 0)) - (mapconcat - #'identity - (org-element-map (cons (org-element-property :title entry) - (org-element-contents inside)) - 'timestamp - (lambda (ts) - (when (let ((type (org-element-property :type ts))) - (case (plist-get info :with-timestamps) - (active (memq type '(active active-range))) - (inactive (memq type '(inactive inactive-range))) - ((t) t))) - (let ((uid (format "TS%d-%s" (incf counter) uid))) - (org-icalendar--vevent - entry ts uid summary loc desc cat)))) - info nil (and (eq type 'headline) 'inlinetask)) - "")) - ;; Task: First check if it is appropriate to export it. - ;; If so, call `org-icalendar--vtodo' to transcode it - ;; into a "VTODO" component. - (when (and todo-type - (case (plist-get info :with-vtodo) - (all t) - (unblocked - (and (eq type 'headline) - (not (org-icalendar-blocked-headline-p - entry info)))) - ((t) (eq todo-type 'todo)))) - (org-icalendar--vtodo entry uid summary loc desc cat)) - ;; Diary-sexp: Collect every diary-sexp element within - ;; ENTRY and its title, and transcode them. If ENTRY is - ;; a headline, skip inlinetasks: they will be handled - ;; separately. - (when org-icalendar-include-sexps - (let ((counter 0)) - (mapconcat #'identity - (org-element-map - (cons (org-element-property :title entry) - (org-element-contents inside)) - 'diary-sexp - (lambda (sexp) - (org-icalendar-transcode-diary-sexp - (org-element-property :value sexp) - (format "DS%d-%s" (incf counter) uid) - summary)) - info nil (and (eq type 'headline) 'inlinetask)) - "")))))) + (mapconcat #'identity + (org-element-map + (cons (org-element-property :title entry) + (org-element-contents inside)) + 'diary-sexp + (lambda (sexp) + (org-icalendar-transcode-diary-sexp + (org-element-property :value sexp) + (format "DS%d-%s" (cl-incf counter) uid) + summary)) + info nil (and (eq type 'headline) 'inlinetask)) + ""))))) ;; If ENTRY is a headline, call current function on every ;; inlinetask within it. In agenda export, this is independent ;; from the mark (or lack thereof) on the entry. @@ -627,7 +638,7 @@ inlinetask within the section." contents)))) (defun org-icalendar--vevent - (entry timestamp uid summary location description categories) + (entry timestamp uid summary location description categories timezone) "Create a VEVENT component. ENTRY is either a headline or an inlinetask element. TIMESTAMP @@ -636,7 +647,8 @@ is the unique identifier for the event. SUMMARY defines a short summary or subject for the event. LOCATION defines the intended venue for the event. DESCRIPTION provides the complete description of the event. CATEGORIES defines the categories the -event belongs to. +event belongs to. TIMEZONE specifies a time zone for this event +only. Return VEVENT component as a string." (org-icalendar-fold-string @@ -646,12 +658,12 @@ Return VEVENT component as a string." (concat "BEGIN:VEVENT\n" (org-icalendar-dtstamp) "\n" "UID:" uid "\n" - (org-icalendar-convert-timestamp timestamp "DTSTART") "\n" - (org-icalendar-convert-timestamp timestamp "DTEND" t) "\n" + (org-icalendar-convert-timestamp timestamp "DTSTART" nil timezone) "\n" + (org-icalendar-convert-timestamp timestamp "DTEND" t timezone) "\n" ;; RRULE. (when (org-element-property :repeater-type timestamp) (format "RRULE:FREQ=%s;INTERVAL=%d\n" - (case (org-element-property :repeater-unit timestamp) + (cl-case (org-element-property :repeater-unit timestamp) (hour "HOURLY") (day "DAILY") (week "WEEKLY") (month "MONTHLY") (year "YEARLY")) (org-element-property :repeater-value timestamp))) @@ -665,7 +677,7 @@ Return VEVENT component as a string." "END:VEVENT")))) (defun org-icalendar--vtodo - (entry uid summary location description categories) + (entry uid summary location description categories timezone) "Create a VTODO component. ENTRY is either a headline or an inlinetask element. UID is the @@ -673,6 +685,7 @@ unique identifier for the task. SUMMARY defines a short summary or subject for the task. LOCATION defines the intended venue for the task. DESCRIPTION provides the complete description of the task. CATEGORIES defines the categories the task belongs to. +TIMEZONE specifies a time zone for this TODO only. Return VTODO component as a string." (let ((start (or (and (memq 'todo-start org-icalendar-use-scheduled) @@ -691,11 +704,11 @@ Return VTODO component as a string." (concat "BEGIN:VTODO\n" "UID:TODO-" uid "\n" (org-icalendar-dtstamp) "\n" - (org-icalendar-convert-timestamp start "DTSTART") "\n" + (org-icalendar-convert-timestamp start "DTSTART" nil timezone) "\n" (and (memq 'todo-due org-icalendar-use-deadline) (org-element-property :deadline entry) (concat (org-icalendar-convert-timestamp - (org-element-property :deadline entry) "DUE") + (org-element-property :deadline entry) "DUE" nil timezone) "\n")) "SUMMARY:" summary "\n" (and (org-string-nw-p location) (format "LOCATION:%s\n" location)) @@ -821,7 +834,8 @@ Return ICS file name." ;; links will not be collected at the end of sections. (let ((outfile (org-export-output-file-name ".ics" subtreep))) (org-export-to-file 'icalendar outfile - async subtreep visible-only body-only '(:ascii-charset utf-8) + async subtreep visible-only body-only + '(:ascii-charset utf-8 :ascii-links-to-notes nil) (lambda (file) (run-hook-with-args 'org-icalendar-after-save-hook file) nil)))) @@ -835,27 +849,23 @@ external process." ;; Asynchronous export is not interactive, so we will not call ;; `org-check-agenda-file'. Instead we remove any non-existent ;; agenda file from the list. - (let ((files (org-remove-if-not 'file-exists-p (org-agenda-files t)))) + (let ((files (cl-remove-if-not #'file-exists-p (org-agenda-files t)))) (org-export-async-start (lambda (results) - (mapc (lambda (f) (org-export-add-to-stack f 'icalendar)) - results)) + (dolist (f results) (org-export-add-to-stack f 'icalendar))) `(let (output-files) - (mapc (lambda (file) - (with-current-buffer (org-get-agenda-file-buffer file) - (push (expand-file-name (org-icalendar-export-to-ics)) - output-files))) - ',files) - output-files))) + (dolist (file ',files outputfiles) + (with-current-buffer (org-get-agenda-file-buffer file) + (push (expand-file-name (org-icalendar-export-to-ics)) + output-files)))))) (let ((files (org-agenda-files t))) (org-agenda-prepare-buffers files) (unwind-protect - (mapc (lambda (file) - (catch 'nextfile - (org-check-agenda-file file) - (with-current-buffer (org-get-agenda-file-buffer file) - (org-icalendar-export-to-ics)))) - files) + (dolist (file files) + (catch 'nextfile + (org-check-agenda-file file) + (with-current-buffer (org-get-agenda-file-buffer file) + (org-icalendar-export-to-ics)))) (org-release-buffers org-agenda-new-buffers))))) ;;;###autoload @@ -870,110 +880,94 @@ The file is stored under the name chosen in `org-icalendar-combined-agenda-file'." (interactive) (if async - (let ((files (org-remove-if-not 'file-exists-p (org-agenda-files t)))) + (let ((files (cl-remove-if-not #'file-exists-p (org-agenda-files t)))) (org-export-async-start - (lambda (dummy) + (lambda (_) (org-export-add-to-stack (expand-file-name org-icalendar-combined-agenda-file) 'icalendar)) - `(apply 'org-icalendar--combine-files nil ',files))) - (apply 'org-icalendar--combine-files nil (org-agenda-files t)))) + `(apply #'org-icalendar--combine-files ',files))) + (apply #'org-icalendar--combine-files (org-agenda-files t)))) (defun org-icalendar-export-current-agenda (file) "Export current agenda view to an iCalendar FILE. This function assumes major mode for current buffer is `org-agenda-mode'." - (let (org-export-babel-evaluate ; Don't evaluate Babel block - (org-icalendar-combined-agenda-file file) - (marker-list - ;; Collect the markers pointing to entries in the current - ;; agenda buffer. - (let (markers) - (save-excursion - (goto-char (point-min)) - (while (not (eobp)) - (let ((m (or (org-get-at-bol 'org-hd-marker) - (org-get-at-bol 'org-marker)))) - (and m (push m markers))) - (beginning-of-line 2))) - (nreverse markers)))) - (apply 'org-icalendar--combine-files - ;; Build restriction alist. - (let (restriction) - ;; Sort markers in each association within RESTRICTION. - (mapcar (lambda (x) (setcdr x (sort (copy-sequence (cdr x)) '<)) x) - (dolist (m marker-list restriction) - (let* ((pos (marker-position m)) - (file (buffer-file-name - (org-base-buffer (marker-buffer m)))) - (file-markers (assoc file restriction))) - ;; Add POS in FILE association if one exists - ;; or create a new association for FILE. - (if file-markers (push pos (cdr file-markers)) - (push (list file pos) restriction)))))) - (org-agenda-files nil 'ifmode)))) - -(defun org-icalendar--combine-files (restriction &rest files) + (let* ((org-export-use-babel) ;don't evaluate Babel blocks + (contents + (org-export-string-as + (with-output-to-string + (save-excursion + (let ((p (point-min)) + (seen nil)) ;prevent duplicates + (while (setq p (next-single-property-change p 'org-hd-marker)) + (let ((m (get-text-property p 'org-hd-marker))) + (when (and m (not (member m seen))) + (push m seen) + (with-current-buffer (marker-buffer m) + (org-with-wide-buffer + (goto-char (marker-position m)) + (princ + (org-element-normalize-string + (buffer-substring (point) + (org-entry-end-position)))))))) + (forward-line))))) + 'icalendar t + '(:ascii-charset utf-8 :ascii-links-to-notes nil + :icalendar-include-todo all)))) + (with-temp-file file + (insert + (org-icalendar--vcalendar + org-icalendar-combined-name + user-full-name + (or (org-string-nw-p org-icalendar-timezone) (cadr (current-time-zone))) + org-icalendar-combined-description + contents))) + (run-hook-with-args 'org-icalendar-after-save-hook file))) + +(defun org-icalendar--combine-files (&rest files) "Combine entries from multiple files into an iCalendar file. -RESTRICTION, when non-nil, is an alist where key is a file name -and value a list of buffer positions pointing to entries that -should appear in the calendar. It only makes sense if the -function was called from an agenda buffer. FILES is a list of -files to build the calendar from." - (org-agenda-prepare-buffers files) - (unwind-protect - (progn - (with-temp-file org-icalendar-combined-agenda-file - (insert - (org-icalendar--vcalendar - ;; Name. - org-icalendar-combined-name - ;; Owner. - user-full-name - ;; Timezone. - (or (org-string-nw-p org-icalendar-timezone) - (cadr (current-time-zone))) - ;; Description. - org-icalendar-combined-description - ;; Contents. - (concat - ;; Agenda contents. - (mapconcat - (lambda (file) - (catch 'nextfile - (org-check-agenda-file file) - (with-current-buffer (org-get-agenda-file-buffer file) - (let ((marks (cdr (assoc (expand-file-name file) - restriction)))) +FILES is a list of files to build the calendar from." + ;; At the end of the process, all buffers related to FILES are going + ;; to be killed. Make sure to only kill the ones opened in the + ;; process. + (let ((org-agenda-new-buffers nil)) + (unwind-protect + (progn + (with-temp-file org-icalendar-combined-agenda-file + (insert + (org-icalendar--vcalendar + ;; Name. + org-icalendar-combined-name + ;; Owner. + user-full-name + ;; Timezone. + (or (org-string-nw-p org-icalendar-timezone) + (cadr (current-time-zone))) + ;; Description. + org-icalendar-combined-description + ;; Contents. + (concat + ;; Agenda contents. + (mapconcat + (lambda (file) + (catch 'nextfile + (org-check-agenda-file file) + (with-current-buffer (org-get-agenda-file-buffer file) ;; Create ID if necessary. (when org-icalendar-store-UID - (org-icalendar-create-uid file t marks)) - (unless (and restriction (not marks)) - ;; Add a hook adding :ICALENDAR_MARK: property - ;; to each entry appearing in agenda view. - ;; Use `apply-partially' because the function - ;; still has to accept one argument. - (let ((org-export-before-processing-hook - (cons (apply-partially - (lambda (m-list dummy) - (mapc (lambda (m) - (org-entry-put - m "ICALENDAR-MARK" "t")) - m-list)) - (sort marks '>)) - org-export-before-processing-hook))) - (org-export-as - 'icalendar nil nil t - (list :ascii-charset 'utf-8 - :icalendar-agenda-view restriction)))))))) - files "") - ;; BBDB anniversaries. - (when (and org-icalendar-include-bbdb-anniversaries - (require 'org-bbdb nil t)) - (with-output-to-string (org-bbdb-anniv-export-ical))))))) - (run-hook-with-args 'org-icalendar-after-save-hook - org-icalendar-combined-agenda-file)) - (org-release-buffers org-agenda-new-buffers))) + (org-icalendar-create-uid file t)) + (org-export-as + 'icalendar nil nil t + '(:ascii-charset utf-8 :ascii-links-to-notes nil))))) + files "") + ;; BBDB anniversaries. + (when (and org-icalendar-include-bbdb-anniversaries + (require 'org-bbdb nil t)) + (with-output-to-string (org-bbdb-anniv-export-ical))))))) + (run-hook-with-args 'org-icalendar-after-save-hook + org-icalendar-combined-agenda-file)) + (org-release-buffers org-agenda-new-buffers)))) (provide 'ox-icalendar) diff --git a/lisp/org/ox-latex.el b/lisp/org/ox-latex.el index 3eee86a3ae7..61b6b8cca92 100644 --- a/lisp/org/ox-latex.el +++ b/lisp/org/ox-latex.el @@ -1,4 +1,4 @@ -;;; ox-latex.el --- LaTeX Back-End for Org Export Engine +;;; ox-latex.el --- LaTeX Back-End for Org Export Engine -*- lexical-binding: t; -*- ;; Copyright (C) 2011-2017 Free Software Foundation, Inc. @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -26,7 +26,7 @@ ;;; Code: -(eval-when-compile (require 'cl)) +(require 'cl-lib) (require 'ox) (require 'ox-publish) @@ -43,8 +43,6 @@ (center-block . org-latex-center-block) (clock . org-latex-clock) (code . org-latex-code) - (comment . (lambda (&rest args) "")) - (comment-block . (lambda (&rest args) "")) (drawer . org-latex-drawer) (dynamic-block . org-latex-dynamic-block) (entity . org-latex-entity) @@ -65,13 +63,13 @@ (latex-fragment . org-latex-latex-fragment) (line-break . org-latex-line-break) (link . org-latex-link) + (node-property . org-latex-node-property) (paragraph . org-latex-paragraph) (plain-list . org-latex-plain-list) (plain-text . org-latex-plain-text) (planning . org-latex-planning) - (property-drawer . (lambda (&rest args) "")) + (property-drawer . org-latex-property-drawer) (quote-block . org-latex-quote-block) - (quote-section . org-latex-quote-section) (radio-target . org-latex-radio-target) (section . org-latex-section) (special-block . org-latex-special-block) @@ -88,8 +86,10 @@ (timestamp . org-latex-timestamp) (underline . org-latex-underline) (verbatim . org-latex-verbatim) - (verse-block . org-latex-verse-block)) - :export-block '("LATEX" "TEX") + (verse-block . org-latex-verse-block) + ;; Pseudo objects and elements. + (latex-math-block . org-latex-math-block) + (latex-matrices . org-latex-matrices)) :menu-entry '(?l "Export to LaTeX" ((?L "As LaTeX buffer" org-latex-export-as-latex) @@ -99,13 +99,58 @@ (lambda (a s v b) (if a (org-latex-export-to-pdf t s v b) (org-open-file (org-latex-export-to-pdf nil s v b))))))) - :options-alist '((:latex-class "LATEX_CLASS" nil org-latex-default-class t) - (:latex-class-options "LATEX_CLASS_OPTIONS" nil nil t) - (:latex-header "LATEX_HEADER" nil nil newline) - (:latex-header-extra "LATEX_HEADER_EXTRA" nil nil newline) - (:latex-hyperref-p nil "texht" org-latex-with-hyperref t) - ;; Redefine regular options. - (:date "DATE" nil "\\today" t))) + :filters-alist '((:filter-options . org-latex-math-block-options-filter) + (:filter-paragraph . org-latex-clean-invalid-line-breaks) + (:filter-parse-tree org-latex-math-block-tree-filter + org-latex-matrices-tree-filter + org-latex-image-link-filter) + (:filter-verse-block . org-latex-clean-invalid-line-breaks)) + :options-alist + '((:latex-class "LATEX_CLASS" nil org-latex-default-class t) + (:latex-class-options "LATEX_CLASS_OPTIONS" nil nil t) + (:latex-header "LATEX_HEADER" nil nil newline) + (:latex-header-extra "LATEX_HEADER_EXTRA" nil nil newline) + (:description "DESCRIPTION" nil nil parse) + (:keywords "KEYWORDS" nil nil parse) + (:subtitle "SUBTITLE" nil nil parse) + ;; Other variables. + (:latex-active-timestamp-format nil nil org-latex-active-timestamp-format) + (:latex-caption-above nil nil org-latex-caption-above) + (:latex-classes nil nil org-latex-classes) + (:latex-default-figure-position nil nil org-latex-default-figure-position) + (:latex-default-table-environment nil nil org-latex-default-table-environment) + (:latex-default-table-mode nil nil org-latex-default-table-mode) + (:latex-diary-timestamp-format nil nil org-latex-diary-timestamp-format) + (:latex-footnote-defined-format nil nil org-latex-footnote-defined-format) + (:latex-footnote-separator nil nil org-latex-footnote-separator) + (:latex-format-drawer-function nil nil org-latex-format-drawer-function) + (:latex-format-headline-function nil nil org-latex-format-headline-function) + (:latex-format-inlinetask-function nil nil org-latex-format-inlinetask-function) + (:latex-hyperref-template nil nil org-latex-hyperref-template t) + (:latex-image-default-height nil nil org-latex-image-default-height) + (:latex-image-default-option nil nil org-latex-image-default-option) + (:latex-image-default-width nil nil org-latex-image-default-width) + (:latex-images-centered nil nil org-latex-images-centered) + (:latex-inactive-timestamp-format nil nil org-latex-inactive-timestamp-format) + (:latex-inline-image-rules nil nil org-latex-inline-image-rules) + (:latex-link-with-unknown-path-format nil nil org-latex-link-with-unknown-path-format) + (:latex-listings nil nil org-latex-listings) + (:latex-listings-langs nil nil org-latex-listings-langs) + (:latex-listings-options nil nil org-latex-listings-options) + (:latex-minted-langs nil nil org-latex-minted-langs) + (:latex-minted-options nil nil org-latex-minted-options) + (:latex-prefer-user-labels nil nil org-latex-prefer-user-labels) + (:latex-subtitle-format nil nil org-latex-subtitle-format) + (:latex-subtitle-separate nil nil org-latex-subtitle-separate) + (:latex-table-scientific-notation nil nil org-latex-table-scientific-notation) + (:latex-tables-booktabs nil nil org-latex-tables-booktabs) + (:latex-tables-centered nil nil org-latex-tables-centered) + (:latex-text-markup-alist nil nil org-latex-text-markup-alist) + (:latex-title-command nil nil org-latex-title-command) + (:latex-toc-command nil nil org-latex-toc-command) + (:latex-compiler "LATEX_COMPILER" nil org-latex-compiler) + ;; Redefine regular options. + (:date "DATE" nil "\\today" parse))) @@ -164,11 +209,112 @@ ("uk" . "ukrainian")) "Alist between language code and corresponding Babel option.") +(defconst org-latex-polyglossia-language-alist + '(("am" "amharic") + ("ast" "asturian") + ("ar" "arabic") + ("bo" "tibetan") + ("bn" "bengali") + ("bg" "bulgarian") + ("br" "breton") + ("bt-br" "brazilian") + ("ca" "catalan") + ("cop" "coptic") + ("cs" "czech") + ("cy" "welsh") + ("da" "danish") + ("de" "german" "german") + ("de-at" "german" "austrian") + ("de-de" "german" "german") + ("dv" "divehi") + ("el" "greek") + ("en" "english" "usmax") + ("en-au" "english" "australian") + ("en-gb" "english" "uk") + ("en-nz" "english" "newzealand") + ("en-us" "english" "usmax") + ("eo" "esperanto") + ("es" "spanish") + ("et" "estonian") + ("eu" "basque") + ("fa" "farsi") + ("fi" "finnish") + ("fr" "french") + ("fu" "friulan") + ("ga" "irish") + ("gd" "scottish") + ("gl" "galician") + ("he" "hebrew") + ("hi" "hindi") + ("hr" "croatian") + ("hu" "magyar") + ("hy" "armenian") + ("id" "bahasai") + ("ia" "interlingua") + ("is" "icelandic") + ("it" "italian") + ("kn" "kannada") + ("la" "latin" "modern") + ("la-modern" "latin" "modern") + ("la-classic" "latin" "classic") + ("la-medieval" "latin" "medieval") + ("lo" "lao") + ("lt" "lithuanian") + ("lv" "latvian") + ("mr" "maranthi") + ("ml" "malayalam") + ("nl" "dutch") + ("nb" "norsk") + ("nn" "nynorsk") + ("nko" "nko") + ("no" "norsk") + ("oc" "occitan") + ("pl" "polish") + ("pms" "piedmontese") + ("pt" "portuges") + ("rm" "romansh") + ("ro" "romanian") + ("ru" "russian") + ("sa" "sanskrit") + ("hsb" "usorbian") + ("dsb" "lsorbian") + ("sk" "slovak") + ("sl" "slovenian") + ("se" "samin") + ("sq" "albanian") + ("sr" "serbian") + ("sv" "swedish") + ("syr" "syriac") + ("ta" "tamil") + ("te" "telugu") + ("th" "thai") + ("tk" "turkmen") + ("tr" "turkish") + ("uk" "ukrainian") + ("ur" "urdu") + ("vi" "vietnamese")) + "Alist between language code and corresponding Polyglossia option") + + + (defconst org-latex-table-matrix-macros '(("bordermatrix" . "\\cr") - ("qbordermatrix" . "\\cr") - ("kbordermatrix" . "\\\\")) + ("qbordermatrix" . "\\cr") + ("kbordermatrix" . "\\\\")) "Alist between matrix macros and their row ending.") +(defconst org-latex-math-environments-re + (format + "\\`[ \t]*\\\\begin{%s\\*?}" + (regexp-opt + '("equation" "eqnarray" "math" "displaymath" + "align" "gather" "multline" "flalign" "alignat" + "xalignat" "xxalignat" + "subequations" + ;; breqn + "dmath" "dseries" "dgroup" "darray" + ;; empheq + "empheq"))) + "Regexp of LaTeX math environments.") ;;; User Configurable Variables @@ -178,6 +324,79 @@ :tag "Org Export LaTeX" :group 'org-export) +;;;; Generic + +(defcustom org-latex-caption-above '(table) + "When non-nil, place caption string at the beginning of elements. +Otherwise, place it near the end. When value is a list of +symbols, put caption above selected elements only. Allowed +symbols are: `image', `table', `src-block' and `special-block'." + :group 'org-export-latex + :version "26.1" + :package-version '(Org . "8.3") + :type '(choice + (const :tag "For all elements" t) + (const :tag "For no element" nil) + (set :tag "For the following elements only" :greedy t + (const :tag "Images" image) + (const :tag "Tables" table) + (const :tag "Source code" src-block) + (const :tag "Special blocks" special-block)))) + +(defcustom org-latex-prefer-user-labels nil + "Use user-provided labels instead of internal ones when non-nil. + +When this variable is non-nil, Org will use the value of +CUSTOM_ID property, NAME keyword or Org target as the key for the +\\label commands generated. + +By default, Org generates its own internal labels during LaTeX +export. This process ensures that the \\label keys are unique +and valid, but it means the keys are not available in advance of +the export process. + +Setting this variable gives you control over how Org generates +labels during LaTeX export, so that you may know their keys in +advance. One reason to do this is that it allows you to refer to +various elements using a single label both in Org's link syntax +and in embedded LaTeX code. + +For example, when this variable is non-nil, a headline like this: + + ** Some section + :PROPERTIES: + :CUSTOM_ID: sec:foo + :END: + This is section [[#sec:foo]]. + #+BEGIN_EXPORT latex + And this is still section \\ref{sec:foo}. + #+END_EXPORT + +will be exported to LaTeX as: + + \\subsection{Some section} + \\label{sec:foo} + This is section \\ref{sec:foo}. + And this is still section \\ref{sec:foo}. + +Note, however, that setting this variable introduces a limitation +on the possible values for CUSTOM_ID and NAME. When this +variable is non-nil, Org passes their value to \\label unchanged. +You are responsible for ensuring that the value is a valid LaTeX +\\label key, and that no other \\label commands with the same key +appear elsewhere in your document. (Keys may contain letters, +numbers, and the following punctuation: '_' '.' '-' ':'.) There +are no such limitations on CUSTOM_ID and NAME when this variable +is nil. + +For headlines that do not define the CUSTOM_ID property or +elements without a NAME, Org will continue to use its default +labeling scheme to generate labels and resolve links into proper +references." + :group 'org-export-latex + :type 'boolean + :version "26.1" + :package-version '(Org . "8.3")) ;;;; Preamble @@ -264,11 +483,15 @@ AUTO will automatically be replaced with a coding system derived from `buffer-file-coding-system'. See also the variable `org-latex-inputenc-alist' for a way to influence this mechanism. -Likewise, if your header contains \"\\usepackage[AUTO]{babel}\", -AUTO will be replaced with the language related to the language -code specified by `org-export-default-language', which see. Note -that constructions such as \"\\usepackage[french,AUTO,english]{babel}\" -are permitted. +Likewise, if your header contains \"\\usepackage[AUTO]{babel}\" +or \"\\usepackage[AUTO]{polyglossia}\", AUTO will be replaced +with the language related to the language code specified by +`org-export-default-language'. Note that constructions such as +\"\\usepackage[french,AUTO,english]{babel}\" are permitted. For +Polyglossia the language will be set via the macros +\"\\setmainlanguage\" and \"\\setotherlanguage\". See also +`org-latex-guess-babel-language' and +`org-latex-guess-polyglossia-language'. The sectioning structure ------------------------ @@ -328,11 +551,42 @@ are written as utf8 files." (defcustom org-latex-title-command "\\maketitle" "The command used to insert the title just after \\begin{document}. -If this string contains the formatting specification \"%s\" then -it will be used as a formatting string, passing the title as an -argument." + +This format string may contain these elements: + + %a for AUTHOR keyword + %t for TITLE keyword + %s for SUBTITLE keyword + %k for KEYWORDS line + %d for DESCRIPTION line + %c for CREATOR line + %l for Language keyword + %L for capitalized language keyword + %D for DATE keyword + +If you need to use a \"%\" character, you need to escape it +like that: \"%%\". + +Setting :latex-title-command in publishing projects will take +precedence over this variable." :group 'org-export-latex - :type 'string) + :type '(string :tag "Format string")) + +(defcustom org-latex-subtitle-format "\\\\\\medskip\n\\large %s" + "Format string used for transcoded subtitle. +The format string should have at most one \"%s\"-expression, +which is replaced with the subtitle." + :group 'org-export-latex + :version "26.1" + :package-version '(Org . "8.3") + :type '(string :tag "Format string")) + +(defcustom org-latex-subtitle-separate nil + "Non-nil means the subtitle is not typeset as part of title." + :group 'org-export-latex + :version "26.1" + :package-version '(Org . "8.3") + :type 'boolean) (defcustom org-latex-toc-command "\\tableofcontents\n\n" "LaTeX command to set the table of contents, list of figures, etc. @@ -341,10 +595,36 @@ the toc:nil option, not to those generated with #+TOC keyword." :group 'org-export-latex :type 'string) -(defcustom org-latex-with-hyperref t - "Toggle insertion of \\hypersetup{...} in the preamble." +(defcustom org-latex-hyperref-template + "\\hypersetup{\n pdfauthor={%a},\n pdftitle={%t},\n pdfkeywords={%k}, + pdfsubject={%d},\n pdfcreator={%c}, \n pdflang={%L}}\n" + "Template for hyperref package options. + +This format string may contain these elements: + + %a for AUTHOR keyword + %t for TITLE keyword + %s for SUBTITLE keyword + %k for KEYWORDS line + %d for DESCRIPTION line + %c for CREATOR line + %l for Language keyword + %L for capitalized language keyword + %D for DATE keyword + +If you need to use a \"%\" character, you need to escape it +like that: \"%%\". + +As a special case, a nil value prevents template from being +inserted. + +Setting :latex-hyperref-template in publishing projects will take +precedence over this variable." :group 'org-export-latex - :type 'boolean) + :version "26.1" + :package-version '(Org . "8.3") + :type '(choice (const :tag "No template" nil) + (string :tag "Format string"))) ;;;; Headline @@ -352,17 +632,15 @@ the toc:nil option, not to those generated with #+TOC keyword." 'org-latex-format-headline-default-function "Function for formatting the headline's text. -This function will be called with 5 arguments: -TODO the todo keyword (string or nil). +This function will be called with six arguments: +TODO the todo keyword (string or nil) TODO-TYPE the type of todo (symbol: `todo', `done', nil) PRIORITY the priority of the headline (integer or nil) -TEXT the main headline text (string). -TAGS the tags as a list of strings (list of strings or nil). - -The function result will be used in the section format string. +TEXT the main headline text (string) +TAGS the tags (list of strings or nil) +INFO the export options (plist) -Use `org-latex-format-headline-default-function' by default, -which format headlines like for Org version prior to 8.0." +The function result will be used in the section format string." :group 'org-export-latex :version "24.4" :package-version '(Org . "8.0") @@ -376,6 +654,16 @@ which format headlines like for Org version prior to 8.0." :group 'org-export-latex :type 'string) +(defcustom org-latex-footnote-defined-format "\\textsuperscript{\\ref{%s}}" + "Format string used to format reference to footnote already defined. +%s will be replaced by the label of the referred footnote." + :group 'org-export-latex + :type '(choice + (const :tag "Use plain superscript (default)" "\\textsuperscript{\\ref{%s}}") + (const :tag "Use Memoir/KOMA-Script footref" "\\footref{%s}") + (string :tag "Other format string")) + :version "26.1" + :package-version '(Org . "9.0")) ;;;; Timestamps @@ -397,6 +685,14 @@ which format headlines like for Org version prior to 8.0." ;;;; Links +(defcustom org-latex-images-centered t + "When non-nil, images are centered." + :group 'org-export-latex + :version "26.1" + :package-version '(Org . "9.0") + :type 'boolean + :safe #'booleanp) + (defcustom org-latex-image-default-option "" "Default option for images." :group 'org-export-latex @@ -422,13 +718,17 @@ environment." :package-version '(Org . "8.0") :type 'string) -(defcustom org-latex-default-figure-position "htb" - "Default position for latex figures." +(defcustom org-latex-default-figure-position "htbp" + "Default position for LaTeX figures." :group 'org-export-latex - :type 'string) + :type 'string + :version "26.1" + :package-version '(Org . "9.0") + :safe #'stringp) (defcustom org-latex-inline-image-rules - '(("file" . "\\.\\(pdf\\|jpeg\\|jpg\\|png\\|ps\\|eps\\|tikz\\|pgf\\|svg\\)\\'")) + `(("file" . ,(regexp-opt + '("pdf" "jpeg" "jpg" "png" "ps" "eps" "tikz" "pgf" "svg")))) "Rules characterizing image files that can be inlined into LaTeX. A rule consists in an association whose key is the type of link @@ -489,12 +789,14 @@ When modifying this variable, it may be useful to change :type '(choice (const :tag "Table" table) (const :tag "Matrix" math) (const :tag "Inline matrix" inline-math) - (const :tag "Verbatim" verbatim))) + (const :tag "Verbatim" verbatim)) + :safe (lambda (s) (memq s '(table math inline-math verbatim)))) (defcustom org-latex-tables-centered t "When non-nil, tables are exported in a center environment." :group 'org-export-latex - :type 'boolean) + :type 'boolean + :safe #'booleanp) (defcustom org-latex-tables-booktabs nil "When non-nil, display tables in a formal \"booktabs\" style. @@ -505,13 +807,8 @@ attributes." :group 'org-export-latex :version "24.4" :package-version '(Org . "8.0") - :type 'boolean) - -(defcustom org-latex-table-caption-above t - "When non-nil, place caption string at the beginning of the table. -Otherwise, place it near the end." - :group 'org-export-latex - :type 'boolean) + :type 'boolean + :safe #'booleanp) (defcustom org-latex-table-scientific-notation "%s\\,(%s)" "Format string to display numbers in scientific notation. @@ -526,11 +823,10 @@ When nil, no transformation is made." (string :tag "Format string") (const :tag "No formatting" nil))) - ;;;; Text markup (defcustom org-latex-text-markup-alist '((bold . "\\textbf{%s}") - (code . verb) + (code . protectedtexttt) (italic . "\\emph{%s}") (strike-through . "\\sout{%s}") (underline . "\\uline{%s}") @@ -550,14 +846,15 @@ to typeset and try to protect special characters. If no association can be found for a given markup, text will be returned as-is." :group 'org-export-latex + :version "26.1" + :package-version '(Org . "8.3") :type 'alist :options '(bold code italic strike-through underline verbatim)) ;;;; Drawers -(defcustom org-latex-format-drawer-function - (lambda (name contents) contents) +(defcustom org-latex-format-drawer-function (lambda (_ contents) contents) "Function called to format a drawer in LaTeX code. The function must accept two parameters: @@ -568,51 +865,31 @@ The function should return the string to be exported. The default function simply returns the value of CONTENTS." :group 'org-export-latex - :version "24.4" + :version "26.1" :package-version '(Org . "8.3") :type 'function) ;;;; Inlinetasks -(defcustom org-latex-format-inlinetask-function 'ignore +(defcustom org-latex-format-inlinetask-function + 'org-latex-format-inlinetask-default-function "Function called to format an inlinetask in LaTeX code. -The function must accept six parameters: - TODO the todo keyword, as a string - TODO-TYPE the todo type, a symbol among `todo', `done' and nil. - PRIORITY the inlinetask priority, as a string - NAME the inlinetask name, as a string. - TAGS the inlinetask tags, as a list of strings. - CONTENTS the contents of the inlinetask, as a string. - -The function should return the string to be exported. +The function must accept seven parameters: + TODO the todo keyword (string or nil) + TODO-TYPE the todo type (symbol: `todo', `done', nil) + PRIORITY the inlinetask priority (integer or nil) + NAME the inlinetask name (string) + TAGS the inlinetask tags (list of strings or nil) + CONTENTS the contents of the inlinetask (string or nil) + INFO the export options (plist) -For example, the variable could be set to the following function -in order to mimic default behavior: - -\(defun org-latex-format-inlinetask (todo type priority name tags contents) -\"Format an inline task element for LaTeX export.\" - (let ((full-title - (concat - (when todo - (format \"\\\\textbf{\\\\textsf{\\\\textsc{%s}}} \" todo)) - (when priority (format \"\\\\framebox{\\\\#%c} \" priority)) - title - (when tags - (format \"\\\\hfill{}\\\\textsc{:%s:}\" - (mapconcat \\='identity tags \":\"))))) - (format (concat \"\\\\begin{center}\\n\" - \"\\\\fbox{\\n\" - \"\\\\begin{minipage}[c]{.6\\\\textwidth}\\n\" - \"%s\\n\\n\" - \"\\\\rule[.8em]{\\\\textwidth}{2pt}\\n\\n\" - \"%s\" - \"\\\\end{minipage}}\" - \"\\\\end{center}\") - full-title contents))" +The function should return the string to be exported." :group 'org-export-latex - :type 'function) + :type 'function + :version "26.1" + :package-version '(Org . "8.3")) ;; Src blocks @@ -640,7 +917,7 @@ the minted package to `org-latex-packages-alist', for example using customize, or with (require \\='ox-latex) - (add-to-list \\='org-latex-packages-alist \\='(\"\" \"minted\")) + (add-to-list \\='org-latex-packages-alist \\='(\"newfloat\" \"minted\")) In addition, it is necessary to install pygments \(http://pygments.org), and to configure the variable @@ -656,7 +933,8 @@ into previewing problems, please consult :type '(choice (const :tag "Use listings" t) (const :tag "Use minted" minted) - (const :tag "Export verbatim" nil))) + (const :tag "Export verbatim" nil)) + :safe (lambda (s) (memq s '(t nil minted)))) (defcustom org-latex-listings-langs '((emacs-lisp "Lisp") (lisp "Lisp") (clojure "Lisp") @@ -668,7 +946,9 @@ into previewing problems, please consult (shell-script "bash") (gnuplot "Gnuplot") (ocaml "Caml") (caml "Caml") - (sql "SQL") (sqlite "sql")) + (sql "SQL") (sqlite "sql") + (makefile "make") + (R "r")) "Alist mapping languages to their listing language counterpart. The key is a symbol, the major mode symbol without the \"-mode\". The value is the string that should be inserted as the language @@ -676,6 +956,8 @@ parameter for the listings package. If the mode name and the listings name are the same, the language does not need an entry in this list - but it does not hurt if it is present." :group 'org-export-latex + :version "26.1" + :package-version '(Org . "8.3") :type '(repeat (list (symbol :tag "Major mode ") @@ -697,7 +979,13 @@ will typeset the code in a small size font with underlined, bold black keywords. Note that the same options will be applied to blocks of all -languages." +languages. If you need block-specific options, you may use the +following syntax: + + #+ATTR_LATEX: :options key1=value1,key2=value2 + #+BEGIN_SRC <LANG> + ... + #+END_SRC" :group 'org-export-latex :type '(repeat (list @@ -744,41 +1032,132 @@ will result in src blocks being exported with \\begin{minted}[bgcolor=bg,frame=lines]{<LANG>} as the start of the minted environment. Note that the same -options will be applied to blocks of all languages." +options will be applied to blocks of all languages. If you need +block-specific options, you may use the following syntax: + + #+ATTR_LATEX: :options key1=value1,key2=value2 + #+BEGIN_SRC <LANG> + ... + #+END_SRC" :group 'org-export-latex :type '(repeat (list (string :tag "Minted option name ") (string :tag "Minted option value")))) -(defvar org-latex-custom-lang-environments nil +(defcustom org-latex-custom-lang-environments nil "Alist mapping languages to language-specific LaTeX environments. It is used during export of src blocks by the listings and minted -latex packages. For example, +latex packages. The environment may be a simple string, composed of +only letters and numbers. In this case, the string is directly the +name of the latex environment to use. The environment may also be +a format string. In this case the format string will be directly +exported. This format string may contain these elements: + + %s for the formatted source + %c for the caption + %f for the float attribute + %l for an appropriate label + %o for the LaTeX attributes + +For example, (setq org-latex-custom-lang-environments - \\='((python \"pythoncode\"))) + \\='((python \"pythoncode\") + (ocaml \"\\\\begin{listing} +\\\\begin{minted}[%o]{ocaml} +%s\\\\end{minted} +\\\\caption{%c} +\\\\label{%l}\"))) -would have the effect that if org encounters begin_src python -during latex export it will output +would have the effect that if Org encounters a Python source block +during LaTeX export it will produce \\begin{pythoncode} <src block body> - \\end{pythoncode}") + \\end{pythoncode} + +and if Org encounters an Ocaml source block during LaTeX export it +will produce + + \\begin{listing} + \\begin{minted}[<attr_latex options>]{ocaml} + <src block body> + \\end{minted} + \\caption{<caption>} + \\label{<label>} + \\end{listing}" + :group 'org-export-latex + :type '(repeat + (list + (symbol :tag "Language name ") + (string :tag "Environment name or format string"))) + :version "26.1" + :package-version '(Org . "9.0")) ;;;; Compilation +(defcustom org-latex-compiler-file-string "%% Intended LaTeX compiler: %s\n" + "LaTeX compiler format-string. +See also `org-latex-compiler'." + :group 'org-export-latex + :type '(choice + (const :tag "Comment" "%% Intended LaTeX compiler: %s\n") + (const :tag "latex-mode file variable" "%% -*- latex-run-command: %s -*-\n") + (const :tag "AUCTeX file variable" "%% -*- LaTeX-command: %s -*-\n") + (string :tag "custom format" "%% %s")) + :version "26.1" + :package-version '(Org . "9.0")) + +(defcustom org-latex-compiler "pdflatex" + "LaTeX compiler to use. + +Must be an element in `org-latex-compilers' or the empty quote. +Can also be set in buffers via #+LATEX_COMPILER. See also +`org-latex-compiler-file-string'." + :group 'org-export-latex + :type '(choice + (const :tag "pdfLaTeX" "pdflatex") + (const :tag "XeLaTeX" "xelatex") + (const :tag "LuaLaTeX" "lualatex") + (const :tag "Unset" "")) + :version "26.1" + :package-version '(Org . "9.0")) + +(defconst org-latex-compilers '("pdflatex" "xelatex" "lualatex") + "Known LaTeX compilers. +See also `org-latex-compiler'.") + +(defcustom org-latex-bib-compiler "bibtex" + "Command to process a LaTeX file's bibliography. + +The shorthand %bib in `org-latex-pdf-process' is replaced with +this value. + +A better approach is to use a compiler suit such as `latexmk'." + :group 'org-export-latex + :type '(choice (const :tag "BibTeX" "bibtex") + (const :tag "Biber" "biber") + (string :tag "Other process")) + :version "26.1" + :package-version '(Org . "9.0")) + (defcustom org-latex-pdf-process - '("pdflatex -interaction nonstopmode -output-directory %o %f" - "pdflatex -interaction nonstopmode -output-directory %o %f" - "pdflatex -interaction nonstopmode -output-directory %o %f") + '("%latex -interaction nonstopmode -output-directory %o %f" + "%latex -interaction nonstopmode -output-directory %o %f" + "%latex -interaction nonstopmode -output-directory %o %f") "Commands to process a LaTeX file to a PDF file. + This is a list of strings, each of them will be given to the shell as a command. %f in the command will be replaced by the -full file name, %b by the file base name (i.e. without directory -and extension parts) and %o by the base directory of the file. +relative file name, %F by the absolute file name, %b by the file +base name (i.e. without directory and extension parts), %o by the +base directory of the file, %O by the absolute file name of the +output file, %latex is the LaTeX compiler (see +`org-latex-compiler'), and %bib is the BibTeX-like compiler (see +`org-latex-bib-compiler'). The reason why this is a list is that it usually takes several runs of `pdflatex', maybe mixed with a call to `bibtex'. Org @@ -786,18 +1165,8 @@ does not have a clever mechanism to detect which of these commands have to be run to get to a stable result, and it also does not do any error checking. -By default, Org uses 3 runs of `pdflatex' to do the processing. -If you have texi2dvi on your system and if that does not cause -the infamous egrep/locale bug: - - http://lists.gnu.org/archive/html/bug-texinfo/2010-03/msg00031.html - -then `texi2dvi' is the superior choice as it automates the LaTeX -build process by calling the \"correct\" combinations of -auxiliary programs. Org does offer `texi2dvi' as one of the -customize options. Alternatively, `rubber' and `latexmk' also -provide similar functionality. The latter supports `biber' out -of the box. +Consider a smart LaTeX compiler such as `texi2dvi' or `latexmk', +which calls the \"correct\" combinations of auxiliary programs. Alternatively, this may be a Lisp function that does the processing, so you could use this to apply the machinery of @@ -807,44 +1176,33 @@ file name as its single argument." :type '(choice (repeat :tag "Shell command sequence" (string :tag "Shell command")) - (const :tag "2 runs of pdflatex" - ("pdflatex -interaction nonstopmode -output-directory %o %f" - "pdflatex -interaction nonstopmode -output-directory %o %f")) - (const :tag "3 runs of pdflatex" - ("pdflatex -interaction nonstopmode -output-directory %o %f" - "pdflatex -interaction nonstopmode -output-directory %o %f" - "pdflatex -interaction nonstopmode -output-directory %o %f")) - (const :tag "pdflatex,bibtex,pdflatex,pdflatex" - ("pdflatex -interaction nonstopmode -output-directory %o %f" - "bibtex %b" - "pdflatex -interaction nonstopmode -output-directory %o %f" - "pdflatex -interaction nonstopmode -output-directory %o %f")) - (const :tag "2 runs of xelatex" - ("xelatex -interaction nonstopmode -output-directory %o %f" - "xelatex -interaction nonstopmode -output-directory %o %f")) - (const :tag "3 runs of xelatex" - ("xelatex -interaction nonstopmode -output-directory %o %f" - "xelatex -interaction nonstopmode -output-directory %o %f" - "xelatex -interaction nonstopmode -output-directory %o %f")) - (const :tag "xelatex,bibtex,xelatex,xelatex" - ("xelatex -interaction nonstopmode -output-directory %o %f" - "bibtex %b" - "xelatex -interaction nonstopmode -output-directory %o %f" - "xelatex -interaction nonstopmode -output-directory %o %f")) + (const :tag "2 runs of latex" + ("%latex -interaction nonstopmode -output-directory %o %f" + "%latex -interaction nonstopmode -output-directory %o %f")) + (const :tag "3 runs of latex" + ("%latex -interaction nonstopmode -output-directory %o %f" + "%latex -interaction nonstopmode -output-directory %o %f" + "%latex -interaction nonstopmode -output-directory %o %f")) + (const :tag "latex,bibtex,latex,latex" + ("%latex -interaction nonstopmode -output-directory %o %f" + "%bib %b" + "%latex -interaction nonstopmode -output-directory %o %f" + "%latex -interaction nonstopmode -output-directory %o %f")) (const :tag "texi2dvi" - ("texi2dvi -p -b -V %f")) - (const :tag "rubber" - ("rubber -d --into %o %f")) + ("cd %o; LATEX=\"%latex\" texi2dvi -p -b -V %b.tex")) (const :tag "latexmk" - ("latexmk -g -pdf %f")) + ("latexmk -g -pdf -pdflatex=\"%latex\" -outdir=%o %f")) (function))) (defcustom org-latex-logfiles-extensions - '("aux" "idx" "log" "out" "toc" "nav" "snm" "vrb") + '("aux" "bcf" "blg" "fdb_latexmk" "fls" "figlist" "idx" "log" "nav" "out" + "ptc" "run.xml" "snm" "toc" "vrb" "xdv") "The list of file extensions to consider as LaTeX logfiles. -The logfiles will be remove if `org-latex-remove-logfiles' is +The logfiles will be removed if `org-latex-remove-logfiles' is non-nil." :group 'org-export-latex + :version "26.1" + :package-version '(Org . "8.3") :type '(repeat (string :tag "Extension"))) (defcustom org-latex-remove-logfiles t @@ -855,19 +1213,20 @@ logfiles to remove, set `org-latex-logfiles-extensions'." :group 'org-export-latex :type 'boolean) -(defcustom org-latex-known-errors - '(("Reference.*?undefined" . "[undefined reference]") - ("Citation.*?undefined" . "[undefined citation]") - ("Undefined control sequence" . "[undefined control sequence]") - ("^! LaTeX.*?Error" . "[LaTeX error]") - ("^! Package.*?Error" . "[package error]") - ("Runaway argument" . "Runaway argument")) +(defcustom org-latex-known-warnings + '(("Reference.*?undefined" . "[undefined reference]") + ("Runaway argument" . "[runaway argument]") + ("Underfull \\hbox" . "[underfull hbox]") + ("Overfull \\hbox" . "[overfull hbox]") + ("Citation.*?undefined" . "[undefined citation]") + ("Undefined control sequence" . "[undefined control sequence]")) "Alist of regular expressions and associated messages for the user. -The regular expressions are used to find possible errors in the -log of a latex-run." +The regular expressions are used to find possible warnings in the +log of a latex-run. These warnings will be reported after +calling `org-latex-compile'." :group 'org-export-latex - :version "24.4" - :package-version '(Org . "8.0") + :version "26.1" + :package-version '(Org . "8.3") :type '(repeat (cons (string :tag "Regexp") @@ -877,6 +1236,54 @@ log of a latex-run." ;;; Internal Functions +(defun org-latex--caption-above-p (element info) + "Non nil when caption is expected to be located above ELEMENT. +INFO is a plist holding contextual information." + (let ((above (plist-get info :latex-caption-above))) + (if (symbolp above) above + (let ((type (org-element-type element))) + (memq (if (eq type 'link) 'image type) above))))) + +(defun org-latex--label (datum info &optional force full) + "Return an appropriate label for DATUM. +DATUM is an element or a `target' type object. INFO is the +current export state, as a plist. + +Return nil if element DATUM has no NAME or VALUE affiliated +keyword or no CUSTOM_ID property, unless FORCE is non-nil. In +this case always return a unique label. + +Eventually, if FULL is non-nil, wrap label within \"\\label{}\"." + (let* ((type (org-element-type datum)) + (user-label + (org-element-property + (cl-case type + ((headline inlinetask) :CUSTOM_ID) + (target :value) + (otherwise :name)) + datum)) + (label + (and (or user-label force) + (if (and user-label (plist-get info :latex-prefer-user-labels)) + user-label + (concat (cl-case type + (headline "sec:") + (table "tab:") + (latex-environment + (and (string-match-p + org-latex-math-environments-re + (org-element-property :value datum)) + "eq:")) + (paragraph + (and (org-element-property :caption datum) + "fig:"))) + (org-export-get-reference datum info)))))) + (cond ((not full) label) + (label (format "\\label{%s}%s" + label + (if (eq type 'target) "" "\n"))) + (t "")))) + (defun org-latex--caption/label-string (element info) "Return caption and label LaTeX string for ELEMENT. @@ -884,25 +1291,43 @@ INFO is a plist holding contextual information. If there's no caption nor label, return the empty string. For non-floats, see `org-latex--wrap-label'." - (let* ((label (org-element-property :name element)) - (label-str (if (not (org-string-nw-p label)) "" - (format "\\label{%s}" - (org-export-solidify-link-text label)))) + (let* ((label (org-latex--label element info nil t)) (main (org-export-get-caption element)) + (attr (org-export-read-attribute :attr_latex element)) + (type (org-element-type element)) + (nonfloat (or (and (plist-member attr :float) + (not (plist-get attr :float)) + main) + (and (eq type 'src-block) + (not (plist-get attr :float)) + (null (plist-get info :latex-listings))))) (short (org-export-get-caption element t)) - (caption-from-attr-latex (org-export-read-attribute :attr_latex element :caption))) + (caption-from-attr-latex (plist-get attr :caption))) (cond ((org-string-nw-p caption-from-attr-latex) (concat caption-from-attr-latex "\n")) - ((and (not main) (equal label-str "")) "") - ((not main) (concat label-str "\n")) + ((and (not main) (equal label "")) "") + ((not main) label) ;; Option caption format with short name. - (short (format "\\caption[%s]{%s%s}\n" - (org-export-data short info) - label-str - (org-export-data main info))) - ;; Standard caption format. - (t (format "\\caption{%s%s}\n" label-str (org-export-data main info)))))) + (t + (format (if nonfloat "\\captionof{%s}%s{%s%s}\n" + "\\caption%s%s{%s%s}\n") + (let ((type* (if (eq type 'latex-environment) + (org-latex--environment-type element) + type))) + (if nonfloat + (cl-case type* + (paragraph "figure") + (image "figure") + (special-block "figure") + (src-block (if (plist-get info :latex-listings) + "listing" + "figure")) + (t (symbol-name type*))) + "")) + (if short (format "[%s]" (org-export-data short info)) "") + label + (org-export-data main info)))))) (defun org-latex-guess-inputenc (header) "Set the coding system in inputenc to what the buffer is. @@ -945,8 +1370,8 @@ Return the new header." header (let ((options (save-match-data (org-split-string (match-string 1 header) ",[ \t]*"))) - (language (cdr (assoc language-code - org-latex-babel-language-alist)))) + (language (cdr (assoc-string language-code + org-latex-babel-language-alist t)))) ;; If LANGUAGE is already loaded, return header without AUTO. ;; Otherwise, replace AUTO with language or append language if ;; AUTO is not present. @@ -958,13 +1383,90 @@ Return the new header." ", ") t nil header 1))))) +(defun org-latex-guess-polyglossia-language (header info) + "Set the Polyglossia language according to the LANGUAGE keyword. + +HEADER is the LaTeX header string. INFO is the plist used as +a communication channel. + +Insertion of guessed language only happens when the Polyglossia +package has been explicitly loaded. + +The argument to Polyglossia may be \"AUTO\" which is then +replaced with the language of the document or +`org-export-default-language'. Note, the language is really set +using \setdefaultlanguage and not as an option to the package. + +Return the new header." + (let ((language (plist-get info :language))) + ;; If no language is set or Polyglossia is not loaded, return + ;; HEADER as-is. + (if (or (not (stringp language)) + (not (string-match + "\\\\usepackage\\(?:\\[\\([^]]+?\\)\\]\\){polyglossia}\n" + header))) + header + (let* ((options (org-string-nw-p (match-string 1 header))) + (languages (and options + ;; Reverse as the last loaded language is + ;; the main language. + (nreverse + (delete-dups + (save-match-data + (org-split-string + (replace-regexp-in-string + "AUTO" language options t) + ",[ \t]*")))))) + (main-language-set + (string-match-p "\\\\setmainlanguage{.*?}" header))) + (replace-match + (concat "\\usepackage{polyglossia}\n" + (mapconcat + (lambda (l) + (let ((l (or (assoc l org-latex-polyglossia-language-alist) + l))) + (format (if main-language-set "\\setotherlanguage%s{%s}\n" + (setq main-language-set t) + "\\setmainlanguage%s{%s}\n") + (if (and (consp l) (= (length l) 3)) + (format "[variant=%s]" (nth 2 l)) + "") + (nth 1 l)))) + languages + "")) + t t header 0))))) + +(defun org-latex--remove-packages (pkg-alist info) + "Remove packages based on the current LaTeX compiler. + +If the fourth argument of an element is set in pkg-alist, and it +is not a member of the LaTeX compiler of the document, the packages +is removed. See also `org-latex-compiler'. + +Return modified pkg-alist." + (let ((compiler (or (plist-get info :latex-compiler) ""))) + (if (member-ignore-case compiler org-latex-compilers) + (delq nil + (mapcar + (lambda (pkg) + (unless (and + (listp pkg) + (let ((third (nth 3 pkg))) + (and third + (not (member-ignore-case + compiler + (if (listp third) third (list third))))))) + pkg)) + pkg-alist)) + pkg-alist))) + (defun org-latex--find-verb-separator (s) "Return a character not used in string S. This is used to choose a separator for constructs like \\verb." (let ((ll "~,./?;':\"|!@#%^&-_=+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ<>()[]{}")) - (loop for c across ll - when (not (string-match (regexp-quote (char-to-string c)) s)) - return (char-to-string c)))) + (cl-loop for c across ll + when (not (string-match (regexp-quote (char-to-string c)) s)) + return (char-to-string c)))) (defun org-latex--make-option-string (options) "Return a comma separated string of keywords and values. @@ -972,135 +1474,203 @@ OPTIONS is an alist where the key is the options keyword as a string, and the value a list containing the keyword value, or nil." (mapconcat (lambda (pair) - (concat (first pair) - (when (> (length (second pair)) 0) - (concat "=" (second pair))))) + (pcase-let ((`(,keyword ,value) pair)) + (concat keyword + (and (> (length value) 0) + (concat "=" value))))) options ",")) -(defun org-latex--wrap-label (element output) +(defun org-latex--wrap-label (element output info) "Wrap label associated to ELEMENT around OUTPUT, if appropriate. -This function shouldn't be used for floats. See +INFO is the current export state, as a plist. This function +should not be used for floats. See `org-latex--caption/label-string'." - (let ((label (org-element-property :name element))) - (if (not (and (org-string-nw-p output) (org-string-nw-p label))) output - (concat (format "\\label{%s}\n" (org-export-solidify-link-text label)) - output)))) + (if (not (and (org-string-nw-p output) (org-element-property :name element))) + output + (concat (format "\\phantomsection\n\\label{%s}\n" + (org-latex--label element info)) + output))) + +(defun org-latex--protect-text (text) + "Protect special characters in string TEXT and return it." + (replace-regexp-in-string "[\\{}$%&_#~^]" "\\\\\\&" text)) -(defun org-latex--text-markup (text markup) +(defun org-latex--text-markup (text markup info) "Format TEXT depending on MARKUP text markup. -See `org-latex-text-markup-alist' for details." - (let ((fmt (cdr (assq markup org-latex-text-markup-alist)))) - (cond - ;; No format string: Return raw text. - ((not fmt) text) - ;; Handle the `verb' special case: Find and appropriate separator - ;; and use "\\verb" command. - ((eq 'verb fmt) - (let ((separator (org-latex--find-verb-separator text))) - (concat "\\verb" separator - (replace-regexp-in-string "\n" " " text) - separator))) - ;; Handle the `protectedtexttt' special case: Protect some - ;; special chars and use "\texttt{%s}" format string. - ((eq 'protectedtexttt fmt) - (let ((start 0) - (trans '(("\\" . "\\textbackslash{}") - ("~" . "\\textasciitilde{}") - ("^" . "\\textasciicircum{}"))) - (rtn "") - char) - (while (string-match "[\\{}$%&_#~^]" text) - (setq char (match-string 0 text)) - (if (> (match-beginning 0) 0) - (setq rtn (concat rtn (substring text 0 (match-beginning 0))))) - (setq text (substring text (1+ (match-beginning 0)))) - (setq char (or (cdr (assoc char trans)) (concat "\\" char)) - rtn (concat rtn char))) - (setq text (concat rtn text) - fmt "\\texttt{%s}") - (while (string-match "--" text) - (setq text (replace-match "-{}-" t t text))) - (format fmt text))) - ;; Else use format string. - (t (format fmt text))))) +INFO is a plist used as a communication channel. See +`org-latex-text-markup-alist' for details." + (let ((fmt (cdr (assq markup (plist-get info :latex-text-markup-alist))))) + (cl-case fmt + ;; No format string: Return raw text. + ((nil) text) + ;; Handle the `verb' special case: Find an appropriate separator + ;; and use "\\verb" command. + (verb + (let ((separator (org-latex--find-verb-separator text))) + (concat "\\verb" + separator + (replace-regexp-in-string "\n" " " text) + separator))) + ;; Handle the `protectedtexttt' special case: Protect some + ;; special chars and use "\texttt{%s}" format string. + (protectedtexttt + (format "\\texttt{%s}" + (replace-regexp-in-string + "--\\|[\\{}$%&_#~^]" + (lambda (m) + (cond ((equal m "--") "-{}-") + ((equal m "\\") "\\textbackslash{}") + ((equal m "~") "\\textasciitilde{}") + ((equal m "^") "\\textasciicircum{}") + (t (org-latex--protect-text m)))) + text nil t))) + ;; Else use format string. + (t (format fmt text))))) (defun org-latex--delayed-footnotes-definitions (element info) "Return footnotes definitions in ELEMENT as a string. INFO is a plist used as a communication channel. -Footnotes definitions are returned within \"\\footnotetxt{}\" +Footnotes definitions are returned within \"\\footnotetext{}\" commands. This function is used within constructs that don't support -\"\\footnote{}\" command (i.e. an item's tag). In that case, +\"\\footnote{}\" command (e.g., an item tag). In that case, \"\\footnotemark\" is used within the construct and the function just outside of it." (mapconcat (lambda (ref) - (format - "\\footnotetext[%s]{%s}" - (org-export-get-footnote-number ref info) - (org-trim - (org-export-data - (org-export-get-footnote-definition ref info) info)))) + (let ((def (org-export-get-footnote-definition ref info))) + (format "\\footnotetext[%d]{%s%s}" + (org-export-get-footnote-number ref info) + (org-trim (org-latex--label def info t t)) + (org-trim (org-export-data def info))))) ;; Find every footnote reference in ELEMENT. - (let* (all-refs - search-refs ; For byte-compiler. - (search-refs - (function - (lambda (data) - ;; Return a list of all footnote references never seen - ;; before in DATA. - (org-element-map data 'footnote-reference - (lambda (ref) - (when (org-export-footnote-first-reference-p ref info) - (push ref all-refs) - (when (eq (org-element-property :type ref) 'standard) - (funcall search-refs - (org-export-get-footnote-definition ref info))))) - info) - (reverse all-refs))))) + (letrec ((all-refs nil) + (search-refs + (lambda (data) + ;; Return a list of all footnote references never seen + ;; before in DATA. + (org-element-map data 'footnote-reference + (lambda (ref) + (when (org-export-footnote-first-reference-p ref info) + (push ref all-refs) + (when (eq (org-element-property :type ref) 'standard) + (funcall search-refs + (org-export-get-footnote-definition ref info))))) + info) + (reverse all-refs)))) (funcall search-refs element)) "")) +(defun org-latex--translate (s info) + "Translate string S according to specified language. +INFO is a plist used as a communication channel." + (org-export-translate s :latex info)) + +(defun org-latex--format-spec (info) + "Create a format-spec for document meta-data. +INFO is a plist used as a communication channel." + (let ((language (let ((lang (plist-get info :language))) + (or (cdr (assoc-string lang org-latex-babel-language-alist t)) + (nth 1 (assoc-string lang org-latex-polyglossia-language-alist t)) + lang)))) + `((?a . ,(org-export-data (plist-get info :author) info)) + (?t . ,(org-export-data (plist-get info :title) info)) + (?k . ,(org-export-data (org-latex--wrap-latex-math-block + (plist-get info :keywords) info) + info)) + (?d . ,(org-export-data (org-latex--wrap-latex-math-block + (plist-get info :description) info) + info)) + (?c . ,(plist-get info :creator)) + (?l . ,language) + (?L . ,(capitalize language)) + (?D . ,(org-export-get-date info))))) + +(defun org-latex--insert-compiler (info) + "Insert LaTeX_compiler info into the document. +INFO is a plist used as a communication channel." + (let ((compiler (plist-get info :latex-compiler))) + (and (org-string-nw-p org-latex-compiler-file-string) + (member (or compiler "") org-latex-compilers) + (format org-latex-compiler-file-string compiler)))) + + +;;; Filters + +(defun org-latex-matrices-tree-filter (tree _backend info) + (org-latex--wrap-latex-matrices tree info)) + +(defun org-latex-math-block-tree-filter (tree _backend info) + (org-latex--wrap-latex-math-block tree info)) + +(defun org-latex-math-block-options-filter (info _backend) + (dolist (prop '(:author :date :title) info) + (plist-put info prop + (org-latex--wrap-latex-math-block (plist-get info prop) info)))) + +(defun org-latex-clean-invalid-line-breaks (data _backend _info) + (replace-regexp-in-string + "\\(\\end{[A-Za-z0-9*]+}\\|^\\)[ \t]*\\\\\\\\[ \t]*$" "\\1" + data)) ;;; Template +;;;###autoload +(defun org-latex-make-preamble (info &optional template snippet?) + "Return a formatted LaTeX preamble. +INFO is a plist used as a communication channel. Optional +argument TEMPLATE, when non-nil, is the header template string, +as expected by `org-splice-latex-header'. When SNIPPET? is +non-nil, only includes packages relevant to image generation, as +specified in `org-latex-default-packages-alist' or +`org-latex-packages-alist'." + (let* ((class (plist-get info :latex-class)) + (class-template + (or template + (let* ((class-options (plist-get info :latex-class-options)) + (header (nth 1 (assoc class (plist-get info :latex-classes))))) + (and (stringp header) + (if (not class-options) header + (replace-regexp-in-string + "^[ \t]*\\\\documentclass\\(\\(\\[[^]]*\\]\\)?\\)" + class-options header t nil 1)))) + (user-error "Unknown LaTeX class `%s'" class)))) + (org-latex-guess-polyglossia-language + (org-latex-guess-babel-language + (org-latex-guess-inputenc + (org-element-normalize-string + (org-splice-latex-header + class-template + (org-latex--remove-packages org-latex-default-packages-alist info) + (org-latex--remove-packages org-latex-packages-alist info) + snippet? + (mapconcat #'org-element-normalize-string + (list (plist-get info :latex-header) + (and (not snippet?) + (plist-get info :latex-header-extra))) + "")))) + info) + info))) + (defun org-latex-template (contents info) "Return complete document string after LaTeX conversion. CONTENTS is the transcoded contents string. INFO is a plist holding export options." - (let ((title (org-export-data (plist-get info :title) info))) + (let ((title (org-export-data (plist-get info :title) info)) + (spec (org-latex--format-spec info))) (concat ;; Time-stamp. (and (plist-get info :time-stamp-file) (format-time-string "%% Created %Y-%m-%d %a %H:%M\n")) + ;; LaTeX compiler. + (org-latex--insert-compiler info) ;; Document class and packages. - (let* ((class (plist-get info :latex-class)) - (class-options (plist-get info :latex-class-options)) - (header (nth 1 (assoc class org-latex-classes))) - (document-class-string - (and (stringp header) - (if (not class-options) header - (replace-regexp-in-string - "^[ \t]*\\\\documentclass\\(\\(\\[[^]]*\\]\\)?\\)" - class-options header t nil 1))))) - (if (not document-class-string) - (user-error "Unknown LaTeX class `%s'" class) - (org-latex-guess-babel-language - (org-latex-guess-inputenc - (org-element-normalize-string - (org-splice-latex-header - document-class-string - org-latex-default-packages-alist - org-latex-packages-alist nil - (concat (org-element-normalize-string - (plist-get info :latex-header)) - (plist-get info :latex-header-extra))))) - info))) + (org-latex-make-preamble info) ;; Possibly limit depth for headline numbering. (let ((sec-num (plist-get info :section-numbers))) (when (integerp sec-num) @@ -1117,40 +1687,46 @@ holding export options." ;; Date. (let ((date (and (plist-get info :with-date) (org-export-get-date info)))) (format "\\date{%s}\n" (org-export-data date info))) - ;; Title - (format "\\title{%s}\n" title) + ;; Title and subtitle. + (let* ((subtitle (plist-get info :subtitle)) + (formatted-subtitle + (when subtitle + (format (plist-get info :latex-subtitle-format) + (org-export-data subtitle info)))) + (separate (plist-get info :latex-subtitle-separate))) + (concat + (format "\\title{%s%s}\n" title + (if separate "" (or formatted-subtitle ""))) + (when (and separate subtitle) + (concat formatted-subtitle "\n")))) ;; Hyperref options. - (when (plist-get info :latex-hyperref-p) - (format "\\hypersetup{\n pdfkeywords={%s},\n pdfsubject={%s},\n pdfcreator={%s}}\n" - (or (plist-get info :keywords) "") - (or (plist-get info :description) "") - (if (not (plist-get info :with-creator)) "" - (plist-get info :creator)))) + (let ((template (plist-get info :latex-hyperref-template))) + (and (stringp template) + (format-spec template spec))) ;; Document start. "\\begin{document}\n\n" ;; Title command. - (org-element-normalize-string - (cond ((string= "" title) nil) - ((not (stringp org-latex-title-command)) nil) - ((string-match "\\(?:[^%]\\|^\\)%s" - org-latex-title-command) - (format org-latex-title-command title)) - (t org-latex-title-command))) + (let* ((title-command (plist-get info :latex-title-command)) + (command (and (stringp title-command) + (format-spec title-command spec)))) + (org-element-normalize-string + (cond ((not (plist-get info :with-title)) nil) + ((string= "" title) nil) + ((not (stringp command)) nil) + ((string-match "\\(?:[^%]\\|^\\)%s" command) + (format command title)) + (t command)))) ;; Table of contents. (let ((depth (plist-get info :with-toc))) (when depth (concat (when (wholenump depth) (format "\\setcounter{tocdepth}{%d}\n" depth)) - org-latex-toc-command))) + (plist-get info :latex-toc-command)))) ;; Document's body. contents ;; Creator. - (let ((creator-info (plist-get info :with-creator))) - (cond - ((not creator-info) "") - ((eq creator-info 'comment) - (format "%% %s\n" (plist-get info :creator))) - (t (concat (plist-get info :creator) "\n")))) + (and (plist-get info :with-creator) + (concat (plist-get info :creator) "\n")) ;; Document end. "\\end{document}"))) @@ -1160,11 +1736,11 @@ holding export options." ;;;; Bold -(defun org-latex-bold (bold contents info) +(defun org-latex-bold (_bold contents info) "Transcode BOLD from Org to LaTeX. CONTENTS is the text with bold markup. INFO is a plist holding contextual information." - (org-latex--text-markup contents 'bold)) + (org-latex--text-markup contents 'bold info)) ;;;; Center Block @@ -1174,23 +1750,20 @@ contextual information." CONTENTS holds the contents of the center block. INFO is a plist holding contextual information." (org-latex--wrap-label - center-block - (format "\\begin{center}\n%s\\end{center}" contents))) + center-block (format "\\begin{center}\n%s\\end{center}" contents) info)) ;;;; Clock -(defun org-latex-clock (clock contents info) +(defun org-latex-clock (clock _contents info) "Transcode a CLOCK element from Org to LaTeX. CONTENTS is nil. INFO is a plist holding contextual information." (concat "\\noindent" (format "\\textbf{%s} " org-clock-string) - (format org-latex-inactive-timestamp-format - (concat (org-translate-time - (org-element-property :raw-value - (org-element-property :value clock))) + (format (plist-get info :latex-inactive-timestamp-format) + (concat (org-timestamp-translate (org-element-property :value clock)) (let ((time (org-element-property :duration clock))) (and time (format " (%s)" time))))) "\\\\")) @@ -1198,11 +1771,11 @@ information." ;;;; Code -(defun org-latex-code (code contents info) +(defun org-latex-code (code _contents info) "Transcode a CODE object from Org to LaTeX. CONTENTS is nil. INFO is a plist used as a communication channel." - (org-latex--text-markup (org-element-property :value code) 'code)) + (org-latex--text-markup (org-element-property :value code) 'code info)) ;;;; Drawer @@ -1212,9 +1785,9 @@ channel." CONTENTS holds the contents of the block. INFO is a plist holding contextual information." (let* ((name (org-element-property :drawer-name drawer)) - (output (funcall org-latex-format-drawer-function + (output (funcall (plist-get info :latex-format-drawer-function) name contents))) - (org-latex--wrap-label drawer output))) + (org-latex--wrap-label drawer output info))) ;;;; Dynamic Block @@ -1223,35 +1796,40 @@ holding contextual information." "Transcode a DYNAMIC-BLOCK element from Org to LaTeX. CONTENTS holds the contents of the block. INFO is a plist holding contextual information. See `org-export-data'." - (org-latex--wrap-label dynamic-block contents)) + (org-latex--wrap-label dynamic-block contents info)) ;;;; Entity -(defun org-latex-entity (entity contents info) +(defun org-latex-entity (entity _contents _info) "Transcode an ENTITY object from Org to LaTeX. CONTENTS are the definition itself. INFO is a plist holding contextual information." - (let ((ent (org-element-property :latex entity))) - (if (org-element-property :latex-math-p entity) (format "$%s$" ent) ent))) + (org-element-property :latex entity)) ;;;; Example Block -(defun org-latex-example-block (example-block contents info) +(defun org-latex-example-block (example-block _contents info) "Transcode an EXAMPLE-BLOCK element from Org to LaTeX. CONTENTS is nil. INFO is a plist holding contextual information." (when (org-string-nw-p (org-element-property :value example-block)) - (org-latex--wrap-label - example-block - (format "\\begin{verbatim}\n%s\\end{verbatim}" - (org-export-format-code-default example-block info))))) + (let ((environment (or (org-export-read-attribute + :attr_latex example-block :environment) + "verbatim"))) + (org-latex--wrap-label + example-block + (format "\\begin{%s}\n%s\\end{%s}" + environment + (org-export-format-code-default example-block info) + environment) + info)))) ;;;; Export Block -(defun org-latex-export-block (export-block contents info) +(defun org-latex-export-block (export-block _contents _info) "Transcode a EXPORT-BLOCK element from Org to LaTeX. CONTENTS is nil. INFO is a plist holding contextual information." (when (member (org-element-property :type export-block) '("LATEX" "TEX")) @@ -1260,7 +1838,7 @@ CONTENTS is nil. INFO is a plist holding contextual information." ;;;; Export Snippet -(defun org-latex-export-snippet (export-snippet contents info) +(defun org-latex-export-snippet (export-snippet _contents _info) "Transcode a EXPORT-SNIPPET object from Org to LaTeX. CONTENTS is nil. INFO is a plist holding contextual information." (when (eq (org-export-snippet-backend export-snippet) 'latex) @@ -1269,46 +1847,60 @@ CONTENTS is nil. INFO is a plist holding contextual information." ;;;; Fixed Width -(defun org-latex-fixed-width (fixed-width contents info) +(defun org-latex-fixed-width (fixed-width _contents info) "Transcode a FIXED-WIDTH element from Org to LaTeX. CONTENTS is nil. INFO is a plist holding contextual information." (org-latex--wrap-label fixed-width (format "\\begin{verbatim}\n%s\\end{verbatim}" (org-remove-indentation - (org-element-property :value fixed-width))))) + (org-element-property :value fixed-width))) + info)) ;;;; Footnote Reference -(defun org-latex-footnote-reference (footnote-reference contents info) +(defun org-latex-footnote-reference (footnote-reference _contents info) "Transcode a FOOTNOTE-REFERENCE element from Org to LaTeX. CONTENTS is nil. INFO is a plist holding contextual information." - (concat - ;; Insert separator between two footnotes in a row. - (let ((prev (org-export-get-previous-element footnote-reference info))) - (when (eq (org-element-type prev) 'footnote-reference) - org-latex-footnote-separator)) - (cond - ;; Use \footnotemark if the footnote has already been defined. - ((not (org-export-footnote-first-reference-p footnote-reference info)) - (format "\\footnotemark[%s]{}" - (org-export-get-footnote-number footnote-reference info))) - ;; Use \footnotemark if reference is within another footnote - ;; reference, footnote definition or table cell. - ((loop for parent in (org-export-get-genealogy footnote-reference) - thereis (memq (org-element-type parent) - '(footnote-reference footnote-definition table-cell))) - "\\footnotemark") - ;; Otherwise, define it with \footnote command. - (t - (let ((def (org-export-get-footnote-definition footnote-reference info))) - (concat - (format "\\footnote{%s}" (org-trim (org-export-data def info))) - ;; Retrieve all footnote references within the footnote and - ;; add their definition after it, since LaTeX doesn't support - ;; them inside. - (org-latex--delayed-footnotes-definitions def info))))))) + (let ((label (org-element-property :label footnote-reference))) + (concat + ;; Insert separator between two footnotes in a row. + (let ((prev (org-export-get-previous-element footnote-reference info))) + (when (eq (org-element-type prev) 'footnote-reference) + (plist-get info :latex-footnote-separator))) + (cond + ;; Use `:latex-footnote-defined-format' if the footnote has + ;; already been defined. + ((not (org-export-footnote-first-reference-p footnote-reference info)) + (format (plist-get info :latex-footnote-defined-format) + (org-latex--label + (org-export-get-footnote-definition footnote-reference info) + info t))) + ;; Use \footnotemark if reference is within another footnote + ;; reference, footnote definition or table cell. + ((org-element-lineage footnote-reference + '(footnote-reference footnote-definition table-cell)) + "\\footnotemark") + ;; Otherwise, define it with \footnote command. + (t + (let ((def (org-export-get-footnote-definition footnote-reference info))) + (concat + (format "\\footnote{%s%s}" (org-trim (org-export-data def info)) + ;; Only insert a \label if there exist another + ;; reference to def. + (cond ((not label) "") + ((org-element-map (plist-get info :parse-tree) 'footnote-reference + (lambda (f) + (and (not (eq f footnote-reference)) + (equal (org-element-property :label f) label) + (org-trim (org-latex--label def info t t)))) + info t)) + (t ""))) + ;; Retrieve all footnote references within the footnote and + ;; add their definition after it, since LaTeX doesn't support + ;; them inside. + (org-latex--delayed-footnotes-definitions def info)))))))) ;;;; Headline @@ -1321,7 +1913,7 @@ holding contextual information." (let* ((class (plist-get info :latex-class)) (level (org-export-get-relative-level headline info)) (numberedp (org-export-numbered-headline-p headline info)) - (class-sectioning (assoc class org-latex-classes)) + (class-sectioning (assoc class (plist-get info :latex-classes))) ;; Section formatting will set two placeholders: one for ;; the title and the other for the contents. (section-fmt @@ -1365,16 +1957,12 @@ holding contextual information." (org-element-property :priority headline))) ;; Create the headline text along with a no-tag version. ;; The latter is required to remove tags from toc. - (full-text (funcall org-latex-format-headline-function - todo todo-type priority text tags)) + (full-text (funcall (plist-get info :latex-format-headline-function) + todo todo-type priority text tags info)) ;; Associate \label to the headline for internal links. - (headline-label - (format "\\label{sec-%s}\n" - (mapconcat 'number-to-string - (org-export-get-headline-number headline info) - "-"))) + (headline-label (org-latex--label headline info t t)) (pre-blanks - (make-string (org-element-property :pre-blank headline) 10))) + (make-string (org-element-property :pre-blank headline) ?\n))) (if (or (not section-fmt) (org-export-low-level-p headline info)) ;; This is a deep sub-tree: export it as a list item. Also ;; export as items headlines for which no section format has @@ -1386,7 +1974,8 @@ holding contextual information." (format "\\begin{%s}\n" (if numberedp 'enumerate 'itemize))) ;; Itemize headline "\\item" - (and full-text (org-string-match-p "\\`[ \t]*\\[" full-text) + (and full-text + (string-match-p "\\`[ \t]*\\[" full-text) "\\relax") " " full-text "\n" headline-label @@ -1404,15 +1993,32 @@ holding contextual information." ;; an alternative heading when possible, and when this is not ;; identical to the usual heading. (let ((opt-title - (funcall org-latex-format-headline-function + (funcall (plist-get info :latex-format-headline-function) todo todo-type priority (org-export-data-with-backend (org-export-get-alt-title headline info) section-back-end info) - (and (eq (plist-get info :with-tags) t) tags)))) - (if (and numberedp opt-title + (and (eq (plist-get info :with-tags) t) tags) + info)) + ;; Maybe end local TOC (see `org-latex-keyword'). + (contents + (concat + contents + (let ((case-fold-search t) + (section + (let ((first (car (org-element-contents headline)))) + (and (eq (org-element-type first) 'section) first)))) + (org-element-map section 'keyword + (lambda (k) + (and (equal (org-element-property :key k) "TOC") + (let ((v (org-element-property :value k))) + (and (string-match-p "\\<headlines\\>" v) + (string-match-p "\\<local\\>" v) + (format "\\stopcontents[level-%d]" level))))) + info t))))) + (if (and opt-title (not (equal opt-title full-text)) - (string-match "\\`\\\\\\(.*?[^*]\\){" section-fmt)) + (string-match "\\`\\\\\\(.+?\\){" section-fmt)) (format (replace-match "\\1[%s]" nil nil section-fmt 1) ;; Replace square brackets with parenthesis ;; since square brackets are not supported in @@ -1427,7 +2033,7 @@ holding contextual information." (concat headline-label pre-blanks contents)))))))) (defun org-latex-format-headline-default-function - (todo todo-type priority text tags) + (todo _todo-type priority text tags _info) "Default format function for a headline. See `org-latex-format-headline-function' for details." (concat @@ -1435,12 +2041,13 @@ See `org-latex-format-headline-function' for details." (and priority (format "\\framebox{\\#%c} " priority)) text (and tags - (format "\\hfill{}\\textsc{%s}" (mapconcat 'identity tags ":"))))) + (format "\\hfill{}\\textsc{%s}" + (mapconcat #'org-latex--protect-text tags ":"))))) ;;;; Horizontal Rule -(defun org-latex-horizontal-rule (horizontal-rule contents info) +(defun org-latex-horizontal-rule (horizontal-rule _contents info) "Transcode an HORIZONTAL-RULE object from Org to LaTeX. CONTENTS is nil. INFO is a plist holding contextual information." (let ((attr (org-export-read-attribute :attr_latex horizontal-rule)) @@ -1454,47 +2061,47 @@ CONTENTS is nil. INFO is a plist holding contextual information." "\n") (org-latex--wrap-label horizontal-rule - (format "\\rule{%s}{%s}" - (or (plist-get attr :width) "\\linewidth") - (or (plist-get attr :thickness) "0.5pt")))))) + (format "\\noindent\\rule{%s}{%s}" + (or (plist-get attr :width) "\\textwidth") + (or (plist-get attr :thickness) "0.5pt")) + info)))) ;;;; Inline Src Block -(defun org-latex-inline-src-block (inline-src-block contents info) +(defun org-latex-inline-src-block (inline-src-block _contents info) "Transcode an INLINE-SRC-BLOCK element from Org to LaTeX. CONTENTS holds the contents of the item. INFO is a plist holding contextual information." (let* ((code (org-element-property :value inline-src-block)) (separator (org-latex--find-verb-separator code))) - (cond - ;; Do not use a special package: transcode it verbatim. - ((not org-latex-listings) - (concat "\\verb" separator code separator)) - ;; Use minted package. - ((eq org-latex-listings 'minted) - (let* ((org-lang (org-element-property :language inline-src-block)) - (mint-lang (or (cadr (assq (intern org-lang) - org-latex-minted-langs)) - (downcase org-lang))) - (options (org-latex--make-option-string - org-latex-minted-options))) - (concat (format "\\mint%s{%s}" - (if (string= options "") "" (format "[%s]" options)) - mint-lang) - separator code separator))) - ;; Use listings package. - (t - ;; Maybe translate language's name. - (let* ((org-lang (org-element-property :language inline-src-block)) - (lst-lang (or (cadr (assq (intern org-lang) - org-latex-listings-langs)) - org-lang)) - (options (org-latex--make-option-string - (append org-latex-listings-options - `(("language" ,lst-lang)))))) - (concat (format "\\lstinline[%s]" options) - separator code separator)))))) + (cl-case (plist-get info :latex-listings) + ;; Do not use a special package: transcode it verbatim. + ((nil) (format "\\texttt{%s}" (org-latex--text-markup code 'code info))) + ;; Use minted package. + (minted + (let* ((org-lang (org-element-property :language inline-src-block)) + (mint-lang (or (cadr (assq (intern org-lang) + (plist-get info :latex-minted-langs))) + (downcase org-lang))) + (options (org-latex--make-option-string + (plist-get info :latex-minted-options)))) + (format "\\mintinline%s{%s}{%s}" + (if (string= options "") "" (format "[%s]" options)) + mint-lang + code))) + ;; Use listings package. + (otherwise + ;; Maybe translate language's name. + (let* ((org-lang (org-element-property :language inline-src-block)) + (lst-lang (or (cadr (assq (intern org-lang) + (plist-get info :latex-listings-langs))) + org-lang)) + (options (org-latex--make-option-string + (append (plist-get info :latex-listings-options) + `(("language" ,lst-lang)))))) + (concat (format "\\lstinline[%s]" options) + separator code separator)))))) ;;;; Inlinetask @@ -1511,40 +2118,40 @@ holding contextual information." (tags (and (plist-get info :with-tags) (org-export-get-tags inlinetask info))) (priority (and (plist-get info :with-priority) - (org-element-property :priority inlinetask)))) - ;; If `org-latex-format-inlinetask-function' is provided, call it - ;; with appropriate arguments. - (if (not (eq org-latex-format-inlinetask-function 'ignore)) - (funcall org-latex-format-inlinetask-function - todo todo-type priority title tags contents) - ;; Otherwise, use a default template. - (org-latex--wrap-label - inlinetask - (let ((full-title - (concat - (when todo (format "\\textbf{\\textsf{\\textsc{%s}}} " todo)) - (when priority (format "\\framebox{\\#%c} " priority)) - title - (when tags (format "\\hfill{}\\textsc{:%s:}" - (mapconcat #'identity tags ":")))))) - (concat "\\begin{center}\n" - "\\fbox{\n" - "\\begin{minipage}[c]{.6\\textwidth}\n" - full-title "\n\n" - (and (org-string-nw-p contents) - (concat "\\rule[.8em]{\\textwidth}{2pt}\n\n" contents)) - "\\end{minipage}\n" - "}\n" - "\\end{center}")))))) + (org-element-property :priority inlinetask))) + (contents (concat (org-latex--label inlinetask info) contents))) + (funcall (plist-get info :latex-format-inlinetask-function) + todo todo-type priority title tags contents info))) + +(defun org-latex-format-inlinetask-default-function + (todo _todo-type priority title tags contents _info) + "Default format function for a inlinetasks. +See `org-latex-format-inlinetask-function' for details." + (let ((full-title + (concat (when todo (format "\\textbf{\\textsf{\\textsc{%s}}} " todo)) + (when priority (format "\\framebox{\\#%c} " priority)) + title + (when tags + (format "\\hfill{}\\textsc{:%s:}" + (mapconcat #'org-latex--protect-text tags ":")))))) + (concat "\\begin{center}\n" + "\\fbox{\n" + "\\begin{minipage}[c]{.6\\textwidth}\n" + full-title "\n\n" + (and (org-string-nw-p contents) + (concat "\\rule[.8em]{\\textwidth}{2pt}\n\n" contents)) + "\\end{minipage}\n" + "}\n" + "\\end{center}"))) ;;;; Italic -(defun org-latex-italic (italic contents info) +(defun org-latex-italic (_italic contents info) "Transcode ITALIC from Org to LaTeX. CONTENTS is the text with italic markup. INFO is a plist holding contextual information." - (org-latex--text-markup contents 'italic)) + (org-latex--text-markup contents 'italic info)) ;;;; Item @@ -1565,14 +2172,14 @@ contextual information." (when (and (eq (org-element-type parent) 'plain-list) (eq (org-element-property :type parent) 'ordered)) - (incf level))) + (cl-incf level))) level))) (and count (< level 5) (format "\\setcounter{enum%s}{%s}\n" (nth (1- level) '("i" "ii" "iii" "iv")) (1- count))))) - (checkbox (case (org-element-property :checkbox item) + (checkbox (cl-case (org-element-property :checkbox item) (on "$\\boxtimes$ ") (off "$\\square$ ") (trans "$\\boxminus$ "))) @@ -1591,7 +2198,7 @@ contextual information." ;; unless the brackets comes from an initial export ;; snippet (i.e. it is inserted willingly by the user). ((and contents - (org-string-match-p "\\`[ \t]*\\[" contents) + (string-match-p "\\`[ \t]*\\[" contents) (not (let ((e (car (org-element-contents item)))) (and (eq (org-element-type e) 'paragraph) (let ((o (car (org-element-contents e)))) @@ -1612,7 +2219,7 @@ contextual information." ;;;; Keyword -(defun org-latex-keyword (keyword contents info) +(defun org-latex-keyword (keyword _contents info) "Transcode a KEYWORD element from Org to LaTeX. CONTENTS is nil. INFO is a plist holding contextual information." (let ((key (org-element-property :key keyword)) @@ -1621,60 +2228,107 @@ CONTENTS is nil. INFO is a plist holding contextual information." ((string= key "LATEX") value) ((string= key "INDEX") (format "\\index{%s}" value)) ((string= key "TOC") - (let ((value (downcase value))) + (let ((case-fold-search t)) (cond - ((string-match "\\<headlines\\>" value) - (let ((depth (or (and (string-match "[0-9]+" value) - (string-to-number (match-string 0 value))) - (plist-get info :with-toc)))) - (concat - (when (wholenump depth) - (format "\\setcounter{tocdepth}{%s}\n" depth)) - "\\tableofcontents"))) - ((string= "tables" value) "\\listoftables") - ((string= "listings" value) - (cond - ((eq org-latex-listings 'minted) "\\listoflistings") - (org-latex-listings "\\lstlistoflistings") - ;; At the moment, src blocks with a caption are wrapped - ;; into a figure environment. - (t "\\listoffigures"))))))))) + ((string-match-p "\\<headlines\\>" value) + (let* ((localp (string-match-p "\\<local\\>" value)) + (parent (org-element-lineage keyword '(headline))) + (level (if (not (and localp parent)) 0 + (org-export-get-relative-level parent info))) + (depth + (and (string-match "\\<[0-9]+\\>" value) + (format + "\\setcounter{tocdepth}{%d}" + (+ (string-to-number (match-string 0 value)) level))))) + (if (and localp parent) + ;; Start local TOC, assuming package "titletoc" is + ;; required. + (format "\\startcontents[level-%d] +\\printcontents[level-%d]{}{0}{%s}" + level level (or depth "")) + (concat depth (and depth "\n") "\\tableofcontents")))) + ((string-match-p "\\<tables\\>" value) "\\listoftables") + ((string-match-p "\\<listings\\>" value) + (cl-case (plist-get info :latex-listings) + ((nil) "\\listoffigures") + (minted "\\listoflistings") + (otherwise "\\lstlistoflistings"))))))))) ;;;; Latex Environment -(defun org-latex-latex-environment (latex-environment contents info) +(defun org-latex--environment-type (latex-environment) + "Return the TYPE of LATEX-ENVIRONMENT. + +The TYPE is determined from the actual latex environment, and +could be a member of `org-latex-caption-above' or `math'." + (let* ((latex-begin-re "\\\\begin{\\([A-Za-z0-9*]+\\)}") + (value (org-remove-indentation + (org-element-property :value latex-environment))) + (env (or (and (string-match latex-begin-re value) + (match-string 1 value)) + ""))) + (cond + ((string-match-p org-latex-math-environments-re value) 'math) + ((string-match-p + (eval-when-compile + (regexp-opt '("table" "longtable" "tabular" "tabu" "longtabu"))) + env) + 'table) + ((string-match-p "figure" env) 'image) + ((string-match-p + (eval-when-compile + (regexp-opt '("lstlisting" "listing" "verbatim" "minted"))) + env) + 'src-block) + (t 'special-block)))) + +(defun org-latex-latex-environment (latex-environment _contents info) "Transcode a LATEX-ENVIRONMENT element from Org to LaTeX. CONTENTS is nil. INFO is a plist holding contextual information." (when (plist-get info :with-latex) - (let ((label (org-element-property :name latex-environment)) - (value (org-remove-indentation - (org-element-property :value latex-environment)))) - (if (not (org-string-nw-p label)) value + (let* ((value (org-remove-indentation + (org-element-property :value latex-environment))) + (type (org-latex--environment-type latex-environment)) + (caption (if (eq type 'math) + (org-latex--label latex-environment info nil t) + (org-latex--caption/label-string latex-environment info))) + (caption-above-p + (memq type (append (plist-get info :latex-caption-above) '(math))))) + (if (not (or (org-element-property :name latex-environment) + (org-element-property :caption latex-environment))) + value ;; Environment is labeled: label must be within the environment ;; (otherwise, a reference pointing to that element will count - ;; the section instead). + ;; the section instead). Also insert caption if `latex-environment' + ;; is not a math environment. (with-temp-buffer (insert value) - (goto-char (point-min)) - (forward-line) - (insert - (format "\\label{%s}\n" (org-export-solidify-link-text label))) + (if caption-above-p + (progn + (goto-char (point-min)) + (forward-line)) + (goto-char (point-max)) + (forward-line -1)) + (insert caption) (buffer-string)))))) - ;;;; Latex Fragment -(defun org-latex-latex-fragment (latex-fragment contents info) +(defun org-latex-latex-fragment (latex-fragment _contents _info) "Transcode a LATEX-FRAGMENT object from Org to LaTeX. CONTENTS is nil. INFO is a plist holding contextual information." - (when (plist-get info :with-latex) - (org-element-property :value latex-fragment))) + (let ((value (org-element-property :value latex-fragment))) + ;; Trim math markers since the fragment is enclosed within + ;; a latex-math-block object anyway. + (cond ((string-match-p "\\`\\$[^$]" value) (substring value 1 -1)) + ((string-prefix-p "\\(" value) (substring value 2 -2)) + (t value)))) ;;;; Line Break -(defun org-latex-line-break (line-break contents info) +(defun org-latex-line-break (_line-break _contents _info) "Transcode a LINE-BREAK object from Org to LaTeX. CONTENTS is nil. INFO is a plist holding contextual information." "\\\\\n") @@ -1682,6 +2336,9 @@ CONTENTS is nil. INFO is a plist holding contextual information." ;;;; Link +(defun org-latex-image-link-filter (data _backend info) + (org-export-insert-image-links data info org-latex-inline-image-rules)) + (defun org-latex--inline-image (link info) "Return LaTeX code for an inline image. LINK is the link pointing to the inline image. INFO is a plist @@ -1692,36 +2349,43 @@ used as a communication channel." (expand-file-name raw-path)))) (filetype (file-name-extension path)) (caption (org-latex--caption/label-string parent info)) + (caption-above-p (org-latex--caption-above-p link info)) ;; Retrieve latex attributes from the element around. (attr (org-export-read-attribute :attr_latex parent)) (float (let ((float (plist-get attr :float))) - (cond ((and (not float) (plist-member attr :float)) nil) - ((string= float "wrap") 'wrap) + (cond ((string= float "wrap") 'wrap) + ((string= float "sideways") 'sideways) ((string= float "multicolumn") 'multicolumn) + ((and (plist-member attr :float) (not float)) 'nonfloat) ((or float (org-element-property :caption parent) (org-string-nw-p (plist-get attr :caption))) - 'figure)))) + 'figure) + (t 'nonfloat)))) (placement (let ((place (plist-get attr :placement))) - (cond (place (format "%s" place)) - ((eq float 'wrap) "{l}{0.5\\textwidth}") - ((eq float 'figure) - (format "[%s]" org-latex-default-figure-position)) - (t "")))) + (cond + (place (format "%s" place)) + ((eq float 'wrap) "{l}{0.5\\textwidth}") + ((eq float 'figure) + (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))) (comment-include (if (plist-get attr :comment-include) "%" "")) ;; It is possible to specify width and height in the ;; ATTR_LATEX line, and also via default variables. (width (cond ((plist-get attr :width)) ((plist-get attr :height) "") ((eq float 'wrap) "0.48\\textwidth") - (t org-latex-image-default-width))) + (t (plist-get info :latex-image-default-width)))) (height (cond ((plist-get attr :height)) ((or (plist-get attr :width) (memq float '(figure wrap))) "") - (t org-latex-image-default-height))) + (t (plist-get info :latex-image-default-height)))) (options (let ((opt (or (plist-get attr :options) - org-latex-image-default-option))) + (plist-get info :latex-image-default-option)))) (if (not (string-match "\\`\\[\\(.*\\)\\]\\'" opt)) opt (match-string 1 opt)))) image-code) @@ -1750,11 +2414,17 @@ used as a communication channel." (setq options (concat options ",width=" width))) (when (org-string-nw-p height) (setq options (concat options ",height=" height))) + (let ((search-option (org-element-property :search-option link))) + (when (and search-option + (equal filetype "pdf") + (string-match-p "\\`[0-9]+\\'" search-option) + (not (string-match-p "page=" options))) + (setq options (concat options ",page=" search-option)))) (setq image-code (format "\\includegraphics%s{%s}" (cond ((not (org-string-nw-p options)) "") - ((= (aref options 0) ?,) - (format "[%s]"(substring options 1))) + ((string-prefix-p "," options) + (format "[%s]" (substring options 1))) (t (format "[%s]" options))) path)) (when (equal filetype "svg") @@ -1767,20 +2437,53 @@ used as a communication channel." image-code nil t)))) ;; Return proper string, depending on FLOAT. - (case float - (wrap (format "\\begin{wrapfigure}%s -\\centering + (pcase float + (`wrap (format "\\begin{wrapfigure}%s +%s%s +%s%s +%s\\end{wrapfigure}" + placement + (if caption-above-p caption "") + (if center "\\centering" "") + comment-include image-code + (if caption-above-p "" caption))) + (`sideways (format "\\begin{sidewaysfigure} +%s%s +%s%s +%s\\end{sidewaysfigure}" + (if caption-above-p caption "") + (if center "\\centering" "") + comment-include image-code + (if caption-above-p "" caption))) + (`multicolumn (format "\\begin{figure*}%s %s%s -%s\\end{wrapfigure}" placement comment-include image-code caption)) - (multicolumn (format "\\begin{figure*}%s -\\centering %s%s -%s\\end{figure*}" placement comment-include image-code caption)) - (figure (format "\\begin{figure}%s -\\centering +%s\\end{figure*}" + placement + (if caption-above-p caption "") + (if center "\\centering" "") + comment-include image-code + (if caption-above-p "" caption))) + (`figure (format "\\begin{figure}%s %s%s -%s\\end{figure}" placement comment-include image-code caption)) - (otherwise image-code)))) +%s%s +%s\\end{figure}" + placement + (if caption-above-p caption "") + (if center "\\centering" "") + comment-include image-code + (if caption-above-p "" caption))) + ((guard center) + (format "\\begin{center} +%s%s +%s\\end{center}" + (if caption-above-p caption "") + image-code + (if caption-above-p "" caption))) + (_ + (concat (if caption-above-p caption "") + image-code + (if caption-above-p caption "")))))) (defun org-latex-link (link desc info) "Transcode a LINK object from Org to LaTeX. @@ -1789,20 +2492,19 @@ 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 (replace-regexp-in-string - "%" "\\%" (org-element-property :path link) nil t)) + (raw-path (org-element-property :path link)) ;; Ensure DESC really exists, or set it to nil. (desc (and (not (string= desc "")) desc)) (imagep (org-export-inline-image-p - link org-latex-inline-image-rules)) - (path (cond - ((member type '("http" "https" "ftp" "mailto")) - (concat type ":" raw-path)) - ((and (string= type "file") (file-name-absolute-p raw-path)) - (concat "file:" raw-path)) - (t raw-path))) - protocol) + 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))))) (cond + ;; Link type is handled by a special function. + ((org-export-custom-protocol-maybe link desc 'latex)) ;; Image file. (imagep (org-latex--inline-image link info)) ;; Radio link: Transcode target's contents and use them as link's @@ -1811,8 +2513,7 @@ INFO is a plist holding contextual information. See (let ((destination (org-export-resolve-radio-link link info))) (if (not destination) desc (format "\\hyperref[%s]{%s}" - (org-export-solidify-link-text - (org-element-property :value destination)) + (org-export-get-reference destination info) desc)))) ;; Links pointing to a headline: Find destination and build ;; appropriate referencing command. @@ -1820,14 +2521,14 @@ INFO is a plist holding contextual information. See (let ((destination (if (string= type "fuzzy") (org-export-resolve-fuzzy-link link info) (org-export-resolve-id-link link info)))) - (case (org-element-type destination) + (cl-case (org-element-type destination) ;; Id link points to an external file. (plain-text (if desc (format "\\href{%s}{%s}" destination desc) (format "\\url{%s}" destination))) ;; Fuzzy link points nowhere. - ('nil - (format org-latex-link-with-unknown-path-format + ((nil) + (format (plist-get info :latex-link-with-unknown-path-format) (or desc (org-export-data (org-element-property :raw-link link) info)))) @@ -1836,12 +2537,7 @@ INFO is a plist holding contextual information. See ;; number. Otherwise, display description or headline's ;; title. (headline - (let ((label - (format "sec-%s" - (mapconcat - 'number-to-string - (org-export-get-headline-number destination info) - "-")))) + (let ((label (org-latex--label destination info t))) (if (and (not desc) (org-export-numbered-headline-p destination info)) (format "\\ref{%s}" label) @@ -1851,28 +2547,37 @@ INFO is a plist holding contextual information. See (org-element-property :title destination) info)))))) ;; Fuzzy link points to a target. Do as above. (otherwise - (let ((path (org-export-solidify-link-text path))) - (if (not desc) (format "\\ref{%s}" path) - (format "\\hyperref[%s]{%s}" path desc))))))) + (let ((ref (org-latex--label destination info t))) + (if (not desc) (format "\\ref{%s}" ref) + (format "\\hyperref[%s]{%s}" ref desc))))))) ;; Coderef: replace link with the reference name or the ;; equivalent line number. ((string= type "coderef") (format (org-export-get-coderef-format path desc) (org-export-resolve-coderef path info))) - ;; Link type is handled by a special function. - ((functionp (setq protocol (nth 2 (assoc type org-link-protocols)))) - (funcall protocol (org-link-unescape path) desc 'latex)) ;; External link with a description part. ((and path desc) (format "\\href{%s}{%s}" path desc)) ;; External link without a description part. (path (format "\\url{%s}" path)) ;; No path, only description. Try to do something useful. - (t (format org-latex-link-with-unknown-path-format desc))))) + (t (format (plist-get info :latex-link-with-unknown-path-format) desc))))) + + +;;;; Node Property + +(defun org-latex-node-property (node-property _contents _info) + "Transcode a NODE-PROPERTY element from Org to LaTeX. +CONTENTS is nil. INFO is a plist holding contextual +information." + (format "%s:%s" + (org-element-property :key node-property) + (let ((value (org-element-property :value node-property))) + (if value (concat " " value) "")))) ;;;; Paragraph -(defun org-latex-paragraph (paragraph contents info) +(defun org-latex-paragraph (_paragraph contents _info) "Transcode a PARAGRAPH element from Org to LaTeX. CONTENTS is the contents of the paragraph, as a string. INFO is the plist used as a communication channel." @@ -1898,7 +2603,8 @@ contextual information." latex-type (or (plist-get attr :options) "") contents - latex-type)))) + latex-type) + info))) ;;;; Plain Text @@ -1907,54 +2613,42 @@ contextual information." "Transcode a TEXT string from Org to LaTeX. TEXT is the string to transcode. INFO is a plist holding contextual information." - (let ((specialp (plist-get info :with-special-strings)) - (output text)) - ;; Protect %, #, &, $, _, { and }. - (while (string-match "\\([^\\]\\|^\\)\\([%$#&{}_]\\)" output) - (setq output - (replace-match - (format "\\%s" (match-string 2 output)) nil t output 2))) - ;; Protect ^. - (setq output - (replace-regexp-in-string - "\\([^\\]\\|^\\)\\(\\^\\)" "\\\\^{}" output nil nil 2)) - ;; Protect \. If special strings are used, be careful not to - ;; protect "\" in "\-" constructs. - (let ((symbols (if specialp "-%$#&{}^_\\" "%$#&{}^_\\"))) - (setq output + (let* ((specialp (plist-get info :with-special-strings)) + (output + ;; Turn LaTeX into \LaTeX{} and TeX into \TeX{}. + (let ((case-fold-search nil)) (replace-regexp-in-string - (format "\\(?:[^\\]\\|^\\)\\(\\\\\\)\\(?:[^%s]\\|$\\)" symbols) - "$\\backslash$" output nil t 1))) - ;; Protect ~. - (setq output - (replace-regexp-in-string - "\\([^\\]\\|^\\)\\(~\\)" "\\textasciitilde{}" output nil t 2)) + "\\<\\(?:La\\)?TeX\\>" "\\\\\\&{}" + ;; Protect ^, ~, %, #, &, $, _, { and }. Also protect \. + ;; However, if special strings are used, be careful not + ;; to protect "\" in "\-" constructs. + (replace-regexp-in-string + (concat "[%$#&{}_~^]\\|\\\\" (and specialp "\\([^-]\\|$\\)")) + (lambda (m) + (cl-case (string-to-char m) + (?\\ "$\\\\backslash$\\1") + (?~ "\\\\textasciitilde{}") + (?^ "\\\\^{}") + (t "\\\\\\&"))) + text))))) ;; Activate smart quotes. Be sure to provide original TEXT string ;; since OUTPUT may have been modified. (when (plist-get info :with-smart-quotes) (setq output (org-export-activate-smart-quotes output :latex info text))) - ;; LaTeX into \LaTeX{} and TeX into \TeX{}. - (let ((case-fold-search nil) - (start 0)) - (while (string-match "\\<\\(\\(?:La\\)?TeX\\)\\>" output start) - (setq output (replace-match - (format "\\%s{}" (match-string 1 output)) nil t output) - start (match-end 0)))) ;; Convert special strings. (when specialp - (setq output - (replace-regexp-in-string "\\.\\.\\." "\\ldots{}" output nil t))) + (setq output (replace-regexp-in-string "\\.\\.\\." "\\\\ldots{}" output))) ;; Handle break preservation if required. (when (plist-get info :preserve-breaks) (setq output (replace-regexp-in-string - "\\(\\\\\\\\\\)?[ \t]*\n" " \\\\\\\\\n" output))) + "\\(?:[ \t]*\\\\\\\\\\)?[ \t]*\n" "\\\\\n" output nil t))) ;; Return value. output)) ;;;; Planning -(defun org-latex-planning (planning contents info) +(defun org-latex-planning (planning _contents info) "Transcode a PLANNING element from Org to LaTeX. CONTENTS is nil. INFO is a plist holding contextual information." @@ -1968,27 +2662,165 @@ information." (when closed (concat (format "\\textbf{%s} " org-closed-string) - (format org-latex-inactive-timestamp-format - (org-translate-time - (org-element-property :raw-value closed)))))) + (format (plist-get info :latex-inactive-timestamp-format) + (org-timestamp-translate closed))))) (let ((deadline (org-element-property :deadline planning))) (when deadline (concat (format "\\textbf{%s} " org-deadline-string) - (format org-latex-active-timestamp-format - (org-translate-time - (org-element-property :raw-value deadline)))))) + (format (plist-get info :latex-active-timestamp-format) + (org-timestamp-translate deadline))))) (let ((scheduled (org-element-property :scheduled planning))) (when scheduled (concat (format "\\textbf{%s} " org-scheduled-string) - (format org-latex-active-timestamp-format - (org-translate-time - (org-element-property :raw-value scheduled)))))))) + (format (plist-get info :latex-active-timestamp-format) + (org-timestamp-translate scheduled))))))) " ") "\\\\")) +;;;; Property Drawer + +(defun org-latex-property-drawer (_property-drawer contents _info) + "Transcode a PROPERTY-DRAWER element from Org to LaTeX. +CONTENTS holds the contents of the drawer. INFO is a plist +holding contextual information." + (and (org-string-nw-p contents) + (format "\\begin{verbatim}\n%s\\end{verbatim}" contents))) + + +;;;; Pseudo Element: LaTeX Matrices + +;; `latex-matrices' elements have the following properties: +;; `:caption', `:post-blank' and `:markup' (`inline', `equation' or +;; `math'). + +(defun org-latex--wrap-latex-matrices (data info) + "Merge contiguous tables with the same mode within a pseudo-element. +DATA is a parse tree or a secondary string. INFO is a plist +containing export options. Modify DATA by side-effect and return +it." + (org-element-map data 'table + (lambda (table) + (when (eq (org-element-property :type table) 'org) + (let ((mode (or (org-export-read-attribute :attr_latex table :mode) + (plist-get info :latex-default-table-mode)))) + (when (and (member mode '("inline-math" "math")) + ;; Do not wrap twice the same table. + (not (eq (org-element-type + (org-element-property :parent table)) + 'latex-matrices))) + (let* ((caption (and (not (string= mode "inline-math")) + (org-element-property :caption table))) + (matrices + (list 'latex-matrices + (list :caption caption + :markup + (cond ((string= mode "inline-math") 'inline) + (caption 'equation) + (t 'math))))) + (previous table) + (next (org-export-get-next-element table info))) + (org-element-insert-before matrices table) + ;; Swallow all contiguous tables sharing the same mode. + (while (and + (zerop (or (org-element-property :post-blank previous) 0)) + (setq next (org-export-get-next-element previous info)) + (eq (org-element-type next) 'table) + (eq (org-element-property :type next) 'org) + (string= (or (org-export-read-attribute + :attr_latex next :mode) + (plist-get info :latex-default-table-mode)) + mode)) + (org-element-extract-element previous) + (org-element-adopt-elements matrices previous) + (setq previous next)) + ;; Inherit `:post-blank' from the value of the last + ;; swallowed table. Set the latter's `:post-blank' + ;; value to 0 so as to not duplicate empty lines. + (org-element-put-property + matrices :post-blank (org-element-property :post-blank previous)) + (org-element-put-property previous :post-blank 0) + (org-element-extract-element previous) + (org-element-adopt-elements matrices previous)))))) + info) + data) + +(defun org-latex-matrices (matrices contents _info) + "Transcode a MATRICES element from Org to LaTeX. +CONTENTS is a string. INFO is a plist used as a communication +channel." + (format (cl-case (org-element-property :markup matrices) + (inline "\\(%s\\)") + (equation "\\begin{equation}\n%s\\end{equation}") + (t "\\[\n%s\\]")) + contents)) + + +;;;; Pseudo Object: LaTeX Math Block + +;; `latex-math-block' objects have the following property: +;; `:post-blank'. + +(defun org-latex--wrap-latex-math-block (data info) + "Merge contiguous math objects in a pseudo-object container. +DATA is a parse tree or a secondary string. INFO is a plist +containing export options. Modify DATA by side-effect and return it." + (let ((valid-object-p + ;; Non-nil when OBJ can be added to the latex math block B. + (lambda (obj b) + (pcase (org-element-type obj) + (`entity (org-element-property :latex-math-p obj)) + (`latex-fragment + (let ((value (org-element-property :value obj))) + (or (string-prefix-p "\\(" value) + (string-match-p "\\`\\$[^$]" value)))) + ((and type (or `subscript `superscript)) + (not (memq type (mapcar #'org-element-type + (org-element-contents b))))))))) + (org-element-map data '(entity latex-fragment subscript superscript) + (lambda (object) + ;; Skip objects already wrapped. + (when (and (not (eq (org-element-type + (org-element-property :parent object)) + 'latex-math-block)) + (funcall valid-object-p object nil)) + (let ((math-block (list 'latex-math-block nil)) + (next-elements (org-export-get-next-element object info t)) + (last object)) + ;; Wrap MATH-BLOCK around OBJECT in DATA. + (org-element-insert-before math-block object) + (org-element-extract-element object) + (org-element-adopt-elements math-block object) + (when (zerop (or (org-element-property :post-blank object) 0)) + ;; MATH-BLOCK swallows consecutive math objects. + (catch 'exit + (dolist (next next-elements) + (unless (funcall valid-object-p next math-block) + (throw 'exit nil)) + (org-element-extract-element next) + (org-element-adopt-elements math-block next) + ;; Eschew the case: \beta$x$ -> \(\betax\). + (unless (memq (org-element-type next) + '(subscript superscript)) + (org-element-put-property last :post-blank 1)) + (setq last next) + (when (> (or (org-element-property :post-blank next) 0) 0) + (throw 'exit nil))))) + (org-element-put-property + math-block :post-blank (org-element-property :post-blank last))))) + info nil '(subscript superscript latex-math-block) t) + ;; Return updated DATA. + data)) + +(defun org-latex-math-block (_math-block contents _info) + "Transcode a MATH-BLOCK object from Org to LaTeX. +CONTENTS is a string. INFO is a plist used as a communication +channel." + (when (org-string-nw-p contents) + (format "\\(%s\\)" (org-trim contents)))) + ;;;; Quote Block (defun org-latex-quote-block (quote-block contents info) @@ -1996,18 +2828,7 @@ information." CONTENTS holds the contents of the block. INFO is a plist holding contextual information." (org-latex--wrap-label - quote-block - (format "\\begin{quote}\n%s\\end{quote}" contents))) - - -;;;; Quote Section - -(defun org-latex-quote-section (quote-section contents info) - "Transcode a QUOTE-SECTION element from Org to LaTeX. -CONTENTS is nil. INFO is a plist holding contextual information." - (let ((value (org-remove-indentation - (org-element-property :value quote-section)))) - (when value (format "\\begin{verbatim}\n%s\\end{verbatim}" value)))) + quote-block (format "\\begin{quote}\n%s\\end{quote}" contents) info)) ;;;; Radio Target @@ -2016,15 +2837,12 @@ CONTENTS is nil. INFO is a plist holding contextual information." "Transcode a RADIO-TARGET object from Org to LaTeX. TEXT is the text of the target. INFO is a plist holding contextual information." - (format "\\label{%s}%s" - (org-export-solidify-link-text - (org-element-property :value radio-target)) - text)) + (format "\\label{%s}%s" (org-export-get-reference radio-target info) text)) ;;;; Section -(defun org-latex-section (section contents info) +(defun org-latex-section (_section contents _info) "Transcode a SECTION element from Org to LaTeX. CONTENTS holds the contents of the section. INFO is a plist holding contextual information." @@ -2037,85 +2855,110 @@ holding contextual information." "Transcode a SPECIAL-BLOCK element from Org to LaTeX. CONTENTS holds the contents of the block. INFO is a plist holding contextual information." - (let ((type (downcase (org-element-property :type special-block))) - (opt (org-export-read-attribute :attr_latex special-block :options))) + (let ((type (org-element-property :type special-block)) + (opt (org-export-read-attribute :attr_latex special-block :options)) + (caption (org-latex--caption/label-string special-block info)) + (caption-above-p (org-latex--caption-above-p special-block info))) (concat (format "\\begin{%s}%s\n" type (or opt "")) - ;; Insert any label or caption within the block - ;; (otherwise, a reference pointing to that element will - ;; count the section instead). - (org-latex--caption/label-string special-block info) + (and caption-above-p caption) contents + (and (not caption-above-p) caption) (format "\\end{%s}" type)))) ;;;; Src Block -(defun org-latex-src-block (src-block contents info) +(defun org-latex-src-block (src-block _contents info) "Transcode a SRC-BLOCK element from Org to LaTeX. CONTENTS holds the contents of the item. INFO is a plist holding contextual information." (when (org-string-nw-p (org-element-property :value src-block)) (let* ((lang (org-element-property :language src-block)) (caption (org-element-property :caption src-block)) + (caption-above-p (org-latex--caption-above-p src-block info)) (label (org-element-property :name src-block)) (custom-env (and lang (cadr (assq (intern lang) org-latex-custom-lang-environments)))) - (num-start (case (org-element-property :number-lines src-block) - (continued (org-export-get-loc src-block info)) - (new 0))) + (num-start (org-export-get-loc src-block info)) (retain-labels (org-element-property :retain-labels src-block)) (attributes (org-export-read-attribute :attr_latex src-block)) - (float (plist-get attributes :float))) + (float (plist-get attributes :float)) + (listings (plist-get info :latex-listings))) (cond ;; Case 1. No source fontification. - ((not org-latex-listings) + ((not listings) (let* ((caption-str (org-latex--caption/label-string src-block info)) (float-env - (cond ((and (not float) (plist-member attributes :float)) "%s") - ((string= "multicolumn" float) - (format "\\begin{figure*}[%s]\n%%s%s\n\\end{figure*}" - org-latex-default-figure-position - caption-str)) - ((or caption float) - (format "\\begin{figure}[H]\n%%s%s\n\\end{figure}" - caption-str)) + (cond ((string= "multicolumn" float) + (format "\\begin{figure*}[%s]\n%s%%s\n%s\\end{figure*}" + (plist-get info :latex-default-figure-position) + (if caption-above-p caption-str "") + (if caption-above-p "" caption-str))) + (caption (concat + (if caption-above-p caption-str "") + "%s" + (if caption-above-p "" (concat "\n" caption-str)))) (t "%s")))) (format float-env (concat (format "\\begin{verbatim}\n%s\\end{verbatim}" (org-export-format-code-default src-block info)))))) ;; Case 2. Custom environment. - (custom-env (format "\\begin{%s}\n%s\\end{%s}\n" - custom-env - (org-export-format-code-default src-block info) - custom-env)) + (custom-env + (let ((caption-str (org-latex--caption/label-string src-block info)) + (formatted-src (org-export-format-code-default src-block info))) + (if (string-match-p "\\`[a-zA-Z0-9]+\\'" custom-env) + (format "\\begin{%s}\n%s\\end{%s}\n" + custom-env + (concat (and caption-above-p caption-str) + formatted-src + (and (not caption-above-p) caption-str)) + custom-env) + (format-spec custom-env + `((?s . ,formatted-src) + (?c . ,caption) + (?f . ,float) + (?l . ,(org-latex--label src-block info)) + (?o . ,(or (plist-get attributes :options) ""))))))) ;; Case 3. Use minted package. - ((eq org-latex-listings 'minted) + ((eq listings 'minted) (let* ((caption-str (org-latex--caption/label-string src-block info)) (float-env - (cond ((and (not float) (plist-member attributes :float)) "%s") - ((string= "multicolumn" float) - (format "\\begin{listing*}\n%%s\n%s\\end{listing*}" - caption-str)) - ((or caption float) - (format "\\begin{listing}[H]\n%%s\n%s\\end{listing}" - caption-str)) - (t "%s"))) + (cond + ((string= "multicolumn" float) + (format "\\begin{listing*}[%s]\n%s%%s\n%s\\end{listing*}" + (plist-get info :latex-default-figure-position) + (if caption-above-p caption-str "") + (if caption-above-p "" caption-str))) + (caption + (format "\\begin{listing}[%s]\n%s%%s\n%s\\end{listing}" + (plist-get info :latex-default-figure-position) + (if caption-above-p caption-str "") + (if caption-above-p "" caption-str))) + ((string= "t" float) + (concat (format "\\begin{listing}[%s]\n" + (plist-get info :latex-default-figure-position)) + "%s\n\\end{listing}")) + (t "%s"))) + (options (plist-get info :latex-minted-options)) (body (format "\\begin{minted}[%s]{%s}\n%s\\end{minted}" ;; Options. - (org-latex--make-option-string - (if (or (not num-start) - (assoc "linenos" org-latex-minted-options)) - org-latex-minted-options - (append - `(("linenos") - ("firstnumber" ,(number-to-string (1+ num-start)))) - org-latex-minted-options))) + (concat + (org-latex--make-option-string + (if (or (not num-start) (assoc "linenos" options)) + options + (append + `(("linenos") + ("firstnumber" ,(number-to-string (1+ num-start)))) + options))) + (let ((local-options (plist-get attributes :options))) + (and local-options (concat "," local-options)))) ;; Language. - (or (cadr (assq (intern lang) org-latex-minted-langs)) + (or (cadr (assq (intern lang) + (plist-get info :latex-minted-langs))) (downcase lang)) ;; Source code. (let* ((code-info (org-export-unravel-code src-block)) @@ -2126,7 +2969,7 @@ contextual information." "\n"))))) (org-export-format-code (car code-info) - (lambda (loc num ref) + (lambda (loc _num ref) (concat loc (when ref @@ -2142,7 +2985,9 @@ contextual information." ;; Case 4. Use listings package. (t (let ((lst-lang - (or (cadr (assq (intern lang) org-latex-listings-langs)) lang)) + (or (cadr (assq (intern lang) + (plist-get info :latex-listings-langs))) + lang)) (caption-str (when caption (let ((main (org-export-get-caption src-block)) @@ -2151,28 +2996,33 @@ contextual information." (format "{%s}" (org-export-data main info)) (format "{[%s]%s}" (org-export-data secondary info) - (org-export-data main info))))))) + (org-export-data main info)))))) + (lst-opt (plist-get info :latex-listings-options))) (concat ;; Options. (format "\\lstset{%s}\n" - (org-latex--make-option-string - (append - org-latex-listings-options - (cond - ((and (not float) (plist-member attributes :float)) nil) - ((string= "multicolumn" float) '(("float" "*"))) - ((and float (not (assoc "float" org-latex-listings-options))) - `(("float" ,org-latex-default-figure-position)))) - `(("language" ,lst-lang)) - (if label `(("label" ,label)) '(("label" " "))) - (if caption-str `(("caption" ,caption-str)) '(("caption" " "))) - (cond ((assoc "numbers" org-latex-listings-options) nil) - ((not num-start) '(("numbers" "none"))) - ((zerop num-start) '(("numbers" "left"))) - (t `(("numbers" "left") - ("firstnumber" - ,(number-to-string (1+ num-start))))))))) + (concat + (org-latex--make-option-string + (append + lst-opt + (cond + ((and (not float) (plist-member attributes :float)) nil) + ((string= "multicolumn" float) '(("float" "*"))) + ((and float (not (assoc "float" lst-opt))) + `(("float" ,(plist-get info :latex-default-figure-position))))) + `(("language" ,lst-lang)) + (if label + `(("label" ,(org-latex--label src-block info))) + '(("label" " "))) + (if caption-str `(("caption" ,caption-str)) '(("caption" " "))) + `(("captionpos" ,(if caption-above-p "t" "b"))) + (cond ((assoc "numbers" lst-opt) nil) + ((not num-start) '(("numbers" "none"))) + (t `(("firstnumber" ,(number-to-string (1+ num-start))) + ("numbers" "left")))))) + (let ((local-options (plist-get attributes :options))) + (and local-options (concat "," local-options))))) ;; Source code. (format "\\begin{lstlisting}\n%s\\end{lstlisting}" @@ -2183,21 +3033,21 @@ contextual information." (org-split-string (car code-info) "\n"))))) (org-export-format-code (car code-info) - (lambda (loc num ref) + (lambda (loc _num ref) (concat loc (when ref ;; Ensure references are flushed to the right, ;; separated with 6 spaces from the widest line of ;; code - (concat (make-string (+ (- max-width (length loc)) 6) ? ) + (concat (make-string (+ (- max-width (length loc)) 6) ?\s) (format "(%s)" ref))))) nil (and retain-labels (cdr code-info)))))))))))) ;;;; Statistics Cookie -(defun org-latex-statistics-cookie (statistics-cookie contents info) +(defun org-latex-statistics-cookie (statistics-cookie _contents _info) "Transcode a STATISTICS-COOKIE object from Org to LaTeX. CONTENTS is nil. INFO is a plist holding contextual information." (replace-regexp-in-string @@ -2206,11 +3056,11 @@ CONTENTS is nil. INFO is a plist holding contextual information." ;;;; Strike-Through -(defun org-latex-strike-through (strike-through contents info) +(defun org-latex-strike-through (_strike-through contents info) "Transcode STRIKE-THROUGH from Org to LaTeX. CONTENTS is the text with strike-through markup. INFO is a plist holding contextual information." - (org-latex--text-markup contents 'strike-through)) + (org-latex--text-markup contents 'strike-through info)) ;;;; Subscript @@ -2219,22 +3069,11 @@ holding contextual information." "Transcode a subscript or superscript object. OBJECT is an Org object. INFO is a plist used as a communication channel." - (let ((in-script-p - ;; Non-nil if object is already in a sub/superscript. - (let ((parent object)) - (catch 'exit - (while (setq parent (org-export-get-parent parent)) - (let ((type (org-element-type parent))) - (cond ((memq type '(subscript superscript)) - (throw 'exit t)) - ((memq type org-element-all-elements) - (throw 'exit nil)))))))) - (type (org-element-type object)) - (output "")) + (let ((output "")) (org-element-map (org-element-contents object) (cons 'plain-text org-element-all-objects) (lambda (obj) - (case (org-element-type obj) + (cl-case (org-element-type obj) ((entity latex-fragment) (let ((data (org-trim (org-export-data obj info)))) (string-match @@ -2255,33 +3094,14 @@ channel." (let ((blank (org-element-property :post-blank obj))) (and blank (> blank 0) "\\ "))))))) info nil org-element-recursive-objects) - ;; Result. Do not wrap into math mode if already in a subscript - ;; or superscript. Do not wrap into curly brackets if OUTPUT is - ;; a single character. Also merge consecutive subscript and - ;; superscript into the same math snippet. - (concat (and (not in-script-p) - (let ((prev (org-export-get-previous-element object info))) - (or (not prev) - (not (eq (org-element-type prev) - (if (eq type 'subscript) 'superscript - 'subscript))) - (let ((blank (org-element-property :post-blank prev))) - (and blank (> blank 0))))) - "$") - (if (eq (org-element-type object) 'subscript) "_" "^") + ;; Result. Do not wrap into curly brackets if OUTPUT is a single + ;; character. + (concat (if (eq (org-element-type object) 'subscript) "_" "^") (and (> (length output) 1) "{") output - (and (> (length output) 1) "}") - (and (not in-script-p) - (or (let ((blank (org-element-property :post-blank object))) - (and blank (> blank 0))) - (not (eq (org-element-type - (org-export-get-next-element object info)) - (if (eq type 'subscript) 'superscript - 'subscript)))) - "$")))) + (and (> (length output) 1) "}")))) -(defun org-latex-subscript (subscript contents info) +(defun org-latex-subscript (subscript _contents info) "Transcode a SUBSCRIPT object from Org to LaTeX. CONTENTS is the contents of the object. INFO is a plist holding contextual information." @@ -2290,7 +3110,7 @@ contextual information." ;;;; Superscript -(defun org-latex-superscript (superscript contents info) +(defun org-latex-superscript (superscript _contents info) "Transcode a SUPERSCRIPT object from Org to LaTeX. CONTENTS is the contents of the object. INFO is a plist holding contextual information." @@ -2316,7 +3136,7 @@ contextual information." ;; "table.el" table. Convert it using appropriate tools. (org-latex--table.el-table table info) (let ((type (or (org-export-read-attribute :attr_latex table :mode) - org-latex-default-table-mode))) + (plist-get info :latex-default-table-mode)))) (cond ;; Case 1: Verbatim table. ((string= type "verbatim") @@ -2333,10 +3153,12 @@ contextual information." ;; table, insert their definition just after it. (org-latex--delayed-footnotes-definitions table info))))))) -(defun org-latex--align-string (table info) +(defun org-latex--align-string (table info &optional math?) "Return an appropriate LaTeX alignment string. TABLE is the considered table. INFO is a plist used as -a communication channel." +a communication channel. When optional argument MATH? is +non-nil, TABLE is meant to be a matrix, where all cells are +centered." (or (org-export-read-attribute :attr_latex table :align) (let (align) ;; Extract column groups and alignment from first (non-rule) @@ -2352,10 +3174,11 @@ a communication channel." ;; Check left border for the first cell only. (when (and (memq 'left borders) (not align)) (push "|" align)) - (push (case (org-export-table-cell-alignment cell info) - (left "l") - (right "r") - (center "c")) + (push (if math? "c" ;center cells in matrices + (cl-case (org-export-table-cell-alignment cell info) + (left "l") + (right "r") + (center "c"))) align) (when (memq 'right borders) (push "|" align)))) info) @@ -2376,14 +3199,15 @@ This function assumes TABLE has `org' as its `:type' property and (alignment (org-latex--align-string table info)) ;; Determine environment for the table: longtable, tabular... (table-env (or (plist-get attr :environment) - org-latex-default-table-environment)) + (plist-get info :latex-default-table-environment))) ;; If table is a float, determine environment: table, table* ;; or sidewaystable. (float-env (unless (member table-env '("longtable" "longtabu")) (let ((float (plist-get attr :float))) (cond ((and (not float) (plist-member attr :float)) nil) - ((string= float "sidewaystable") "sidewaystable") + ((or (string= float "sidewaystable") + (string= float "sideways")) "sidewaystable") ((string= float "multicolumn") "table*") ((or float (org-element-property :caption table) @@ -2392,23 +3216,26 @@ This function assumes TABLE has `org' as its `:type' property and ;; Extract others display options. (fontsize (let ((font (plist-get attr :font))) (and font (concat font "\n")))) - (width (plist-get attr :width)) + ;; "tabular" environment doesn't allow to define a width. + (width (and (not (equal table-env "tabular")) (plist-get attr :width))) (spreadp (plist-get attr :spread)) - (placement (or (plist-get attr :placement) - (format "[%s]" org-latex-default-figure-position))) + (placement + (or (plist-get attr :placement) + (format "[%s]" (plist-get info :latex-default-figure-position)))) (centerp (if (plist-member attr :center) (plist-get attr :center) - org-latex-tables-centered))) + (plist-get info :latex-tables-centered))) + (caption-above-p (org-latex--caption-above-p table info))) ;; Prepare the final format string for the table. (cond ;; Longtable. ((equal "longtable" table-env) (concat (and fontsize (concat "{" fontsize)) (format "\\begin{longtable}{%s}\n" alignment) - (and org-latex-table-caption-above + (and caption-above-p (org-string-nw-p caption) (concat caption "\\\\\n")) contents - (and (not org-latex-table-caption-above) + (and (not caption-above-p) (org-string-nw-p caption) (concat caption "\\\\\n")) "\\end{longtable}\n" @@ -2421,11 +3248,11 @@ This function assumes TABLE has `org' as its `:type' property and (format " %s %s " (if spreadp "spread" "to") width) "") alignment) - (and org-latex-table-caption-above + (and caption-above-p (org-string-nw-p caption) (concat caption "\\\\\n")) contents - (and (not org-latex-table-caption-above) + (and (not caption-above-p) (org-string-nw-p caption) (concat caption "\\\\\n")) "\\end{longtabu}\n" @@ -2434,9 +3261,15 @@ This function assumes TABLE has `org' as its `:type' property and (t (concat (cond (float-env (concat (format "\\begin{%s}%s\n" float-env placement) - (if org-latex-table-caption-above caption "") + (if caption-above-p caption "") (when centerp "\\centering\n") fontsize)) + ((and (not float-env) caption) + (concat + (and centerp "\\begin{center}\n" ) + (if caption-above-p caption "") + (cond ((and fontsize centerp) fontsize) + (fontsize (concat "{" fontsize))))) (centerp (concat "\\begin{center}\n" fontsize)) (fontsize (concat "{" fontsize))) (cond ((equal "tabu" table-env) @@ -2454,8 +3287,13 @@ This function assumes TABLE has `org' as its `:type' property and table-env))) (cond (float-env - (concat (if org-latex-table-caption-above "" caption) + (concat (if caption-above-p "" (concat "\n" caption)) (format "\n\\end{%s}" float-env))) + ((and (not float-env) caption) + (concat + (if caption-above-p "" (concat "\n" caption)) + (and centerp "\n\\end{center}") + (and fontsize (not centerp) "}"))) (centerp "\n\\end{center}") (fontsize "}"))))))) @@ -2489,10 +3327,10 @@ property." (let ((n 0) (pos 0)) (while (and (< (length output) pos) (setq pos (string-match "^\\\\hline\n?" output pos))) - (incf n) + (cl-incf n) (unless (= n 2) (setq output (replace-match "" nil nil output)))))) (let ((centerp (if (plist-member attr :center) (plist-get attr :center) - org-latex-tables-centered))) + (plist-get info :latex-tables-centered)))) (if (not centerp) output (format "\\begin{center}\n%s\n\\end{center}" output)))))) @@ -2503,54 +3341,30 @@ TABLE is the table type element to transcode. INFO is a plist used as a communication channel. This function assumes TABLE has `org' as its `:type' property and -`inline-math' or `math' as its `:mode' attribute.." - (let* ((caption (org-latex--caption/label-string table info)) - (attr (org-export-read-attribute :attr_latex table)) - (inlinep (equal (plist-get attr :mode) "inline-math")) +`inline-math' or `math' as its `:mode' attribute." + (let* ((attr (org-export-read-attribute :attr_latex table)) (env (or (plist-get attr :environment) - org-latex-default-table-environment)) + (plist-get info :latex-default-table-environment))) (contents (mapconcat (lambda (row) - ;; Ignore horizontal rules. - (when (eq (org-element-property :type row) 'standard) + (if (eq (org-element-property :type row) 'rule) "\\hline" ;; Return each cell unmodified. (concat (mapconcat (lambda (cell) (substring (org-element-interpret-data cell) 0 -1)) - (org-element-map row 'table-cell 'identity info) "&") + (org-element-map row 'table-cell #'identity info) "&") (or (cdr (assoc env org-latex-table-matrix-macros)) "\\\\") "\n"))) - (org-element-map table 'table-row 'identity info) "")) - ;; Variables related to math clusters (contiguous math tables - ;; of the same type). - (mode (org-export-read-attribute :attr_latex table :mode)) - (prev (org-export-get-previous-element table info)) - (next (org-export-get-next-element table info)) - (same-mode-p - (lambda (table) - ;; Non-nil when TABLE has the same mode as current table. - (string= (or (org-export-read-attribute :attr_latex table :mode) - org-latex-default-table-mode) - mode)))) + (org-element-map table 'table-row #'identity info) ""))) (concat - ;; Opening string. If TABLE is in the middle of a table cluster, - ;; do not insert any. - (cond ((and prev - (eq (org-element-type prev) 'table) - (memq (org-element-property :post-blank prev) '(0 nil)) - (funcall same-mode-p prev)) - nil) - (inlinep "\\(") - ((org-string-nw-p caption) (concat "\\begin{equation}\n" caption)) - (t "\\[")) ;; Prefix. - (or (plist-get attr :math-prefix) "") + (plist-get attr :math-prefix) ;; Environment. Also treat special cases. - (cond ((equal env "array") - (let ((align (org-latex--align-string table info))) - (format "\\begin{array}{%s}\n%s\\end{array}" align contents))) + (cond ((member env '("array" "tabular")) + (format "\\begin{%s}{%s}\n%s\\end{%s}" + env (org-latex--align-string table info t) contents env)) ((assoc env org-latex-table-matrix-macros) (format "\\%s%s{\n%s}" env @@ -2558,28 +3372,7 @@ This function assumes TABLE has `org' as its `:type' property and contents)) (t (format "\\begin{%s}\n%s\\end{%s}" env contents env))) ;; Suffix. - (or (plist-get attr :math-suffix) "") - ;; Closing string. If TABLE is in the middle of a table cluster, - ;; do not insert any. If it closes such a cluster, be sure to - ;; close the cluster with a string matching the opening string. - (cond ((and next - (eq (org-element-type next) 'table) - (memq (org-element-property :post-blank table) '(0 nil)) - (funcall same-mode-p next)) - nil) - (inlinep "\\)") - ;; Find cluster beginning to know which environment to use. - ((let ((cluster-beg table) prev) - (while (and (setq prev (org-export-get-previous-element - cluster-beg info)) - (memq (org-element-property :post-blank prev) - '(0 nil)) - (funcall same-mode-p prev)) - (setq cluster-beg prev)) - (and (or (org-element-property :caption cluster-beg) - (org-element-property :name cluster-beg)) - "\n\\end{equation}"))) - (t "\\]"))))) + (plist-get attr :math-suffix)))) ;;;; Table Cell @@ -2588,16 +3381,18 @@ This function assumes TABLE has `org' as its `:type' property and "Transcode a TABLE-CELL element from Org to LaTeX. CONTENTS is the cell contents. INFO is a plist used as a communication channel." - (concat (if (and contents - org-latex-table-scientific-notation - (string-match orgtbl-exp-regexp contents)) - ;; Use appropriate format string for scientific - ;; notation. - (format org-latex-table-scientific-notation - (match-string 1 contents) - (match-string 2 contents)) - contents) - (when (org-export-get-next-element table-cell info) " & "))) + (concat + (let ((scientific-format (plist-get info :latex-table-scientific-notation))) + (if (and contents + scientific-format + (string-match orgtbl-exp-regexp contents)) + ;; Use appropriate format string for scientific + ;; notation. + (format scientific-format + (match-string 1 contents) + (match-string 2 contents)) + contents)) + (when (org-export-get-next-element table-cell info) " & "))) ;;;; Table Row @@ -2606,87 +3401,106 @@ a communication channel." "Transcode a TABLE-ROW element from Org to LaTeX. CONTENTS is the contents of the row. INFO is a plist used as a communication channel." - ;; Rules are ignored since table separators are deduced from - ;; borders of the current row. - (when (eq (org-element-property :type table-row) 'standard) - (let* ((attr (org-export-read-attribute :attr_latex - (org-export-get-parent table-row))) - (longtablep (member (or (plist-get attr :environment) - org-latex-default-table-environment) - '("longtable" "longtabu"))) - (booktabsp (if (plist-member attr :booktabs) - (plist-get attr :booktabs) - org-latex-tables-booktabs)) - ;; TABLE-ROW's borders are extracted from its first cell. - (borders (org-export-table-cell-borders - (car (org-element-contents table-row)) info))) + (let* ((attr (org-export-read-attribute :attr_latex + (org-export-get-parent table-row))) + (booktabsp (if (plist-member attr :booktabs) (plist-get attr :booktabs) + (plist-get info :latex-tables-booktabs))) + (longtablep + (member (or (plist-get attr :environment) + (plist-get info :latex-default-table-environment)) + '("longtable" "longtabu")))) + (if (eq (org-element-property :type table-row) 'rule) + (cond + ((not booktabsp) "\\hline") + ((not (org-export-get-previous-element table-row info)) "\\toprule") + ((not (org-export-get-next-element table-row info)) "\\bottomrule") + ((and longtablep + (org-export-table-row-ends-header-p + (org-export-get-previous-element table-row info) info)) + "") + (t "\\midrule")) (concat ;; When BOOKTABS are activated enforce top-rule even when no ;; hline was specifically marked. - (cond ((and booktabsp (memq 'top borders)) "\\toprule\n") - ((and (memq 'top borders) (memq 'above borders)) "\\hline\n")) + (and booktabsp (not (org-export-get-previous-element table-row info)) + "\\toprule\n") contents "\\\\\n" (cond - ;; Special case for long tables. Define header and footers. + ;; Special case for long tables. Define header and footers. ((and longtablep (org-export-table-row-ends-header-p table-row info)) - (format "%s + (let ((columns (cdr (org-export-table-dimensions + (org-export-get-parent-table table-row) info)))) + (format "%s +\\endfirsthead +\\multicolumn{%d}{l}{%s} \\\\ +%s +%s \\\\\n +%s \\endhead -%s\\multicolumn{%d}{r}{Continued on next page} \\\\ +%s\\multicolumn{%d}{r}{%s} \\\\ \\endfoot \\endlastfoot" - (if booktabsp "\\midrule" "\\hline") - (if booktabsp "\\midrule" "\\hline") - ;; Number of columns. - (cdr (org-export-table-dimensions - (org-export-get-parent-table table-row) info)))) + (if booktabsp "\\midrule" "\\hline") + columns + (org-latex--translate "Continued from previous page" info) + (cond + ((not (org-export-table-row-starts-header-p table-row info)) + "") + (booktabsp "\\toprule\n") + (t "\\hline\n")) + contents + (if booktabsp "\\midrule" "\\hline") + (if booktabsp "\\midrule" "\\hline") + columns + (org-latex--translate "Continued on next page" info)))) ;; When BOOKTABS are activated enforce bottom rule even when ;; no hline was specifically marked. - ((and booktabsp (memq 'bottom borders)) "\\bottomrule") - ((and (memq 'bottom borders) (memq 'below borders)) "\\hline") - ((memq 'below borders) (if booktabsp "\\midrule" "\\hline"))))))) + ((and booktabsp (not (org-export-get-next-element table-row info))) + "\\bottomrule")))))) ;;;; Target -(defun org-latex-target (target contents info) +(defun org-latex-target (target _contents info) "Transcode a TARGET object from Org to LaTeX. CONTENTS is nil. INFO is a plist holding contextual information." - (format "\\label{%s}" - (org-export-solidify-link-text (org-element-property :value target)))) + (format "\\label{%s}" (org-latex--label target info))) ;;;; Timestamp -(defun org-latex-timestamp (timestamp contents info) +(defun org-latex-timestamp (timestamp _contents info) "Transcode a TIMESTAMP object from Org to LaTeX. CONTENTS is nil. INFO is a plist holding contextual information." - (let ((value (org-latex-plain-text - (org-timestamp-translate timestamp) info))) - (case (org-element-property :type timestamp) - ((active active-range) (format org-latex-active-timestamp-format value)) - ((inactive inactive-range) - (format org-latex-inactive-timestamp-format value)) - (otherwise (format org-latex-diary-timestamp-format value))))) + (let ((value (org-latex-plain-text (org-timestamp-translate timestamp) info))) + (format + (plist-get info + (cl-case (org-element-property :type timestamp) + ((active active-range) :latex-active-timestamp-format) + ((inactive inactive-range) :latex-inactive-timestamp-format) + (otherwise :latex-diary-timestamp-format))) + value))) ;;;; Underline -(defun org-latex-underline (underline contents info) +(defun org-latex-underline (_underline contents info) "Transcode UNDERLINE from Org to LaTeX. CONTENTS is the text with underline markup. INFO is a plist holding contextual information." - (org-latex--text-markup contents 'underline)) + (org-latex--text-markup contents 'underline info)) ;;;; Verbatim -(defun org-latex-verbatim (verbatim contents info) +(defun org-latex-verbatim (verbatim _contents info) "Transcode a VERBATIM object from Org to LaTeX. CONTENTS is nil. INFO is a plist used as a communication channel." - (org-latex--text-markup (org-element-property :value verbatim) 'verbatim)) + (org-latex--text-markup + (org-element-property :value verbatim) 'verbatim info)) ;;;; Verse Block @@ -2701,16 +3515,15 @@ contextual information." ;; character and change each white space at beginning of a line ;; into a space of 1 em. Also change each blank line with ;; a vertical space of 1 em. - (progn - (setq contents (replace-regexp-in-string - "^ *\\\\\\\\$" "\\\\vspace*{1em}" - (replace-regexp-in-string - "\\(\\\\\\\\\\)?[ \t]*\n" " \\\\\\\\\n" contents))) - (while (string-match "^[ \t]+" contents) - (let ((new-str (format "\\hspace*{%dem}" - (length (match-string 0 contents))))) - (setq contents (replace-match new-str nil t contents)))) - (format "\\begin{verse}\n%s\\end{verse}" contents)))) + (format "\\begin{verse}\n%s\\end{verse}" + (replace-regexp-in-string + "^[ \t]+" (lambda (m) (format "\\hspace*{%dem}" (length m))) + (replace-regexp-in-string + "^[ \t]*\\\\\\\\$" "\\vspace*{1em}" + (replace-regexp-in-string + "\\([ \t]*\\\\\\\\\\)?[ \t]*\n" "\\\\\n" + contents nil t) nil t) nil t)) + info)) @@ -2753,9 +3566,9 @@ is non-nil." ;;;###autoload (defun org-latex-convert-region-to-latex () - "Assume the current region has org-mode syntax, and convert it to LaTeX. + "Assume the current region has Org syntax, and convert it to LaTeX. This can be used in any buffer. For example, you can write an -itemized list in org-mode syntax in an LaTeX buffer and use this +itemized list in Org syntax in an LaTeX buffer and use this command to convert it." (interactive) (org-export-replace-region-by 'latex)) @@ -2831,86 +3644,78 @@ Return PDF file's name." "Compile a TeX file. TEXFILE is the name of the file being compiled. Processing is -done through the command specified in `org-latex-pdf-process'. +done through the command specified in `org-latex-pdf-process', +which see. Output is redirected to \"*Org PDF LaTeX Output*\" +buffer. When optional argument SNIPPET is non-nil, TEXFILE is a temporary file used to preview a LaTeX snippet. In this case, do not -create a log buffer and do not bother removing log files. - -Return PDF file name or an error if it couldn't be produced." - (let* ((base-name (file-name-sans-extension (file-name-nondirectory texfile))) - (full-name (file-truename texfile)) - (out-dir (file-name-directory texfile)) - ;; Properly set working directory for compilation. - (default-directory (if (file-name-absolute-p texfile) - (file-name-directory full-name) - default-directory)) - errors) - (unless snippet (message "Processing LaTeX file %s..." texfile)) - (save-window-excursion - (cond - ;; A function is provided: Apply it. - ((functionp org-latex-pdf-process) - (funcall org-latex-pdf-process (shell-quote-argument texfile))) - ;; A list is provided: Replace %b, %f and %o with appropriate - ;; values in each command before applying it. Output is - ;; redirected to "*Org PDF LaTeX Output*" buffer. - ((consp org-latex-pdf-process) - (let ((outbuf (and (not snippet) - (get-buffer-create "*Org PDF LaTeX Output*")))) - (mapc - (lambda (command) - (shell-command - (replace-regexp-in-string - "%b" (shell-quote-argument base-name) - (replace-regexp-in-string - "%f" (shell-quote-argument full-name) - (replace-regexp-in-string - "%o" (shell-quote-argument out-dir) command t t) t t) t t) - outbuf)) - org-latex-pdf-process) - ;; Collect standard errors from output buffer. - (setq errors (and (not snippet) (org-latex--collect-errors outbuf))))) - (t (error "No valid command to process to PDF"))) - (let ((pdffile (concat out-dir base-name ".pdf"))) - ;; Check for process failure. Provide collected errors if - ;; possible. - (if (not (file-exists-p pdffile)) - (error "PDF file %s wasn't produced%s" pdffile - (if errors (concat ": " errors) "")) - ;; Else remove log files, when specified, and signal end of - ;; process to user, along with any error encountered. - (when (and (not snippet) org-latex-remove-logfiles) - (dolist (file (directory-files - out-dir t - (concat (regexp-quote base-name) - "\\(?:\\.[0-9]+\\)?" - "\\." - (regexp-opt org-latex-logfiles-extensions)))) - (delete-file file))) - (message (concat "Process completed" - (if (not errors) "." - (concat " with errors: " errors))))) - ;; Return output file name. - pdffile)))) - -(defun org-latex--collect-errors (buffer) - "Collect some kind of errors from \"pdflatex\" command output. - -BUFFER is the buffer containing output. - -Return collected error types as a string, or nil if there was -none." +create a log buffer and do not remove log files. + +Return PDF file name or raise an error if it couldn't be +produced." + (unless snippet (message "Processing LaTeX file %s..." texfile)) + (let* ((compiler + (or (with-temp-buffer + (save-excursion (insert-file-contents texfile)) + (and (search-forward-regexp (regexp-opt org-latex-compilers) + (line-end-position 2) + t) + (progn (beginning-of-line) (looking-at-p "%")) + (match-string 0))) + "pdflatex")) + (process (if (functionp org-latex-pdf-process) org-latex-pdf-process + ;; Replace "%latex" and "%bibtex" with, + ;; respectively, "%L" and "%B" so as to adhere to + ;; `format-spec' specifications. + (mapcar (lambda (command) + (replace-regexp-in-string + "%\\(?:bib\\|la\\)tex\\>" + (lambda (m) (upcase (substring m 0 2))) + command)) + org-latex-pdf-process))) + (spec `((?B . ,(shell-quote-argument org-latex-bib-compiler)) + (?L . ,(shell-quote-argument compiler)))) + (log-buf-name "*Org PDF LaTeX Output*") + (log-buf (and (not snippet) (get-buffer-create log-buf-name))) + (outfile (org-compile-file texfile process "pdf" + (format "See %S for details" log-buf-name) + log-buf spec))) + (unless snippet + (when org-latex-remove-logfiles + (mapc #'delete-file + (directory-files + (file-name-directory outfile) + t + (concat (regexp-quote (file-name-base outfile)) + "\\(?:\\.[0-9]+\\)?\\." + (regexp-opt org-latex-logfiles-extensions)) + t))) + (let ((warnings (org-latex--collect-warnings log-buf))) + (message (concat "PDF file produced" + (cond + ((eq warnings 'error) " with errors.") + (warnings (concat " with warnings: " warnings)) + (t ".")))))) + ;; Return output file name. + outfile)) + +(defun org-latex--collect-warnings (buffer) + "Collect some warnings from \"pdflatex\" command output. +BUFFER is the buffer containing output. Return collected +warnings types as a string, `error' if a LaTeX error was +encountered or nil if there was none." (with-current-buffer buffer (save-excursion (goto-char (point-max)) (when (re-search-backward "^[ \t]*This is .*?TeX.*?Version" nil t) - (let ((case-fold-search t) - (errors "")) - (dolist (latex-error org-latex-known-errors) - (when (save-excursion (re-search-forward (car latex-error) nil t)) - (setq errors (concat errors " " (cdr latex-error))))) - (and (org-string-nw-p errors) (org-trim errors))))))) + (if (re-search-forward "^!" nil t) 'error + (let ((case-fold-search t) + (warnings "")) + (dolist (warning org-latex-known-warnings) + (when (save-excursion (re-search-forward (car warning) nil t)) + (setq warnings (concat warnings " " (cdr warning))))) + (org-string-nw-p (org-trim warnings)))))))) ;;;###autoload (defun org-latex-publish-to-latex (plist filename pub-dir) @@ -2936,9 +3741,13 @@ Return output file name." ;; in working directory and then moved to publishing directory. (org-publish-attachment plist - (org-latex-compile - (org-publish-org-to - 'latex filename ".tex" plist (file-name-directory filename))) + ;; Default directory could be anywhere when this function is + ;; called. We ensure it is set to source file directory during + ;; compilation so as to not break links to external documents. + (let ((default-directory (file-name-directory filename))) + (org-latex-compile + (org-publish-org-to + 'latex filename ".tex" plist (file-name-directory filename)))) pub-dir)) diff --git a/lisp/org/ox-man.el b/lisp/org/ox-man.el index e5b1479c5f5..6fb3041d587 100644 --- a/lisp/org/ox-man.el +++ b/lisp/org/ox-man.el @@ -1,4 +1,4 @@ -;; ox-man.el --- Man Back-End for Org Export Engine +;; ox-man.el --- Man Back-End for Org Export Engine -*- lexical-binding: t; -*- ;; Copyright (C) 2011-2017 Free Software Foundation, Inc. @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -29,18 +29,17 @@ ;; ;; M-: (org-export-to-buffer 'man "*Test Man*") RET ;; -;; in an org-mode buffer then switch to the buffer to see the Man -;; export. See ox.el for more details on how this exporter works. +;; in an Org buffer then switch to the buffer to see the Man export. +;; See ox.el for more details on how this exporter works. ;; ;; It introduces one new buffer keywords: ;; "MAN_CLASS_OPTIONS". ;;; Code: +(require 'cl-lib) (require 'ox) -(eval-when-compile (require 'cl)) - (defvar org-export-man-default-packages-alist) (defvar org-export-man-packages-alist) (defvar orgtbl-exp-regexp) @@ -53,10 +52,7 @@ '((babel-call . org-man-babel-call) (bold . org-man-bold) (center-block . org-man-center-block) - (clock . org-man-clock) (code . org-man-code) - (comment . (lambda (&rest args) "")) - (comment-block . (lambda (&rest args) "")) (drawer . org-man-drawer) (dynamic-block . org-man-dynamic-block) (entity . org-man-entity) @@ -76,13 +72,13 @@ (keyword . org-man-keyword) (line-break . org-man-line-break) (link . org-man-link) + (node-property . org-man-node-property) (paragraph . org-man-paragraph) (plain-list . org-man-plain-list) (plain-text . org-man-plain-text) (planning . org-man-planning) - (property-drawer . (lambda (&rest args) "")) + (property-drawer . org-man-property-drawer) (quote-block . org-man-quote-block) - (quote-section . org-man-quote-section) (radio-target . org-man-radio-target) (section . org-man-section) (special-block . org-man-special-block) @@ -100,9 +96,8 @@ (underline . org-man-underline) (verbatim . org-man-verbatim) (verse-block . org-man-verse-block)) - :export-block "MAN" :menu-entry - '(?m "Export to MAN" + '(?M "Export to MAN" ((?m "As MAN file" org-man-export-to-man) (?p "As PDF file" org-man-export-to-pdf) (?o "As PDF file and open" @@ -112,7 +107,13 @@ :options-alist '((:man-class "MAN_CLASS" nil nil t) (:man-class-options "MAN_CLASS_OPTIONS" nil nil t) - (:man-header-extra "MAN_HEADER" nil nil newline))) + (:man-header-extra "MAN_HEADER" nil nil newline) + ;; Other variables. + (:man-tables-centered nil nil org-man-tables-centered) + (:man-tables-verbatim nil nil org-man-tables-verbatim) + (:man-table-scientific-notation nil nil org-man-table-scientific-notation) + (:man-source-highlight nil nil org-man-source-highlight) + (:man-source-highlight-langs nil nil org-man-source-highlight-langs))) @@ -199,21 +200,6 @@ in this list - but it does not hurt if it is present." (string :tag "Listings language")))) - -(defvar org-man-custom-lang-environments nil - "Alist mapping languages to language-specific Man environments. - -It is used during export of src blocks by the listings and -man packages. For example, - - (setq org-man-custom-lang-environments - \\='((python \"pythoncode\"))) - -would have the effect that if org encounters begin_src python -during man export." -) - - ;;; Compilation (defcustom org-man-pdf-process @@ -222,11 +208,13 @@ during man export." "tbl %f | eqn | groff -man | ps2pdf - > %b.pdf") "Commands to process a Man file to a PDF file. + This is a list of strings, each of them will be given to the shell as a command. %f in the command will be replaced by the -full file name, %b by the file base name (i.e. without directory -and extension parts) and %o by the base directory of the file. - +relative file name, %F by the absolute file name, %b by the file +base name (i.e. without directory and extension parts), %o by the +base directory of the file and %O by the absolute file name of +the output file. By default, Org uses 3 runs of to do the processing. @@ -297,6 +285,10 @@ This function shouldn't be used for floats. See output (concat (format "%s\n.br\n" label) output)))) +(defun org-man--protect-text (text) + "Protect minus and backslash characters in string TEXT." + (replace-regexp-in-string "-" "\\-" text nil t)) + ;;; Template @@ -305,7 +297,8 @@ This function shouldn't be used for floats. See "Return complete document string after Man conversion. CONTENTS is the transcoded contents string. INFO is a plist holding export options." - (let* ((title (org-export-data (plist-get info :title) info)) + (let* ((title (when (plist-get info :with-title) + (org-export-data (plist-get info :title) info))) (attr (read (format "(%s)" (mapconcat #'identity @@ -338,7 +331,7 @@ holding export options." ;;; Bold -(defun org-man-bold (bold contents info) +(defun org-man-bold (_bold contents _info) "Transcode BOLD from Org to Man. CONTENTS is the text with bold markup. INFO is a plist holding contextual information." @@ -347,7 +340,7 @@ contextual information." ;;; Center Block -(defun org-man-center-block (center-block contents info) +(defun org-man-center-block (center-block contents _info) "Transcode a CENTER-BLOCK element from Org to Man. CONTENTS holds the contents of the center block. INFO is a plist holding contextual information." @@ -358,37 +351,17 @@ holding contextual information." contents))) -;;; Clock - -(defun org-man-clock (clock contents info) - "Transcode a CLOCK element from Org to Man. -CONTENTS is nil. INFO is a plist holding contextual -information." - "" ) - - ;;; Code -(defun org-man-code (code contents info) - "Transcode a CODE object from Org to Man. -CONTENTS is nil. INFO is a plist used as a communication -channel." - (format "\\fC%s\\fP" code)) - - -;;; Comment -;; -;; Comments are ignored. - - -;;; Comment Block -;; -;; Comment Blocks are ignored. +(defun org-man-code (code _contents _info) + "Transcode a CODE object from Org to Man." + (format "\\fC%s\\fP" + (org-man--protect-text (org-element-property :value code)))) ;;; Drawer -(defun org-man-drawer (drawer contents info) +(defun org-man-drawer (_drawer contents _info) "Transcode a DRAWER element from Org to Man. DRAWER holds the drawer information CONTENTS holds the contents of the block. @@ -398,7 +371,7 @@ channel." ;;; Dynamic Block -(defun org-man-dynamic-block (dynamic-block contents info) +(defun org-man-dynamic-block (dynamic-block contents _info) "Transcode a DYNAMIC-BLOCK element from Org to Man. CONTENTS holds the contents of the block. INFO is a plist holding contextual information. See `org-export-data'." @@ -407,7 +380,7 @@ holding contextual information. See `org-export-data'." ;;; Entity -(defun org-man-entity (entity contents info) +(defun org-man-entity (entity _contents _info) "Transcode an ENTITY object from Org to Man. CONTENTS are the definition itself. INFO is a plist holding contextual information." @@ -416,7 +389,7 @@ contextual information." ;;; Example Block -(defun org-man-example-block (example-block contents info) +(defun org-man-example-block (example-block _contents info) "Transcode an EXAMPLE-BLOCK element from Org to Man. CONTENTS is nil. INFO is a plist holding contextual information." @@ -428,7 +401,7 @@ information." ;;; Export Block -(defun org-man-export-block (export-block contents info) +(defun org-man-export-block (export-block _contents _info) "Transcode a EXPORT-BLOCK element from Org to Man. CONTENTS is nil. INFO is a plist holding contextual information." (when (string= (org-element-property :type export-block) "MAN") @@ -437,7 +410,7 @@ CONTENTS is nil. INFO is a plist holding contextual information." ;;; Export Snippet -(defun org-man-export-snippet (export-snippet contents info) +(defun org-man-export-snippet (export-snippet _contents _info) "Transcode a EXPORT-SNIPPET object from Org to Man. CONTENTS is nil. INFO is a plist holding contextual information." (when (eq (org-export-snippet-backend export-snippet) 'man) @@ -446,7 +419,7 @@ CONTENTS is nil. INFO is a plist holding contextual information." ;;; Fixed Width -(defun org-man-fixed-width (fixed-width contents info) +(defun org-man-fixed-width (fixed-width _contents _info) "Transcode a FIXED-WIDTH element from Org to Man. CONTENTS is nil. INFO is a plist holding contextual information." (org-man--wrap-label @@ -472,16 +445,15 @@ CONTENTS is nil. INFO is a plist holding contextual information." CONTENTS holds the contents of the headline. INFO is a plist holding contextual information." (let* ((level (org-export-get-relative-level headline info)) - (numberedp (org-export-numbered-headline-p headline info)) - ;; Section formatting will set two placeholders: one for the - ;; title and the other for the contents. - (section-fmt - (case level - (1 ".SH \"%s\"\n%s") - (2 ".SS \"%s\"\n%s") - (3 ".SS \"%s\"\n%s") - (t nil))) - (text (org-export-data (org-element-property :title headline) info))) + ;; Section formatting will set two placeholders: one for the + ;; title and the other for the contents. + (section-fmt + (pcase level + (1 ".SH \"%s\"\n%s") + (2 ".SS \"%s\"\n%s") + (3 ".SS \"%s\"\n%s") + (_ nil))) + (text (org-export-data (org-element-property :title headline) info))) (cond ;; Case 1: This is a footnote section: ignore it. @@ -493,20 +465,20 @@ holding contextual information." ((or (not section-fmt) (org-export-low-level-p headline info)) ;; Build the real contents of the sub-tree. (let ((low-level-body - (concat - ;; If the headline is the first sibling, start a list. - (when (org-export-first-sibling-p headline info) - (format "%s\n" ".RS")) - ;; Itemize headline - ".TP\n.ft I\n" text "\n.ft\n" - contents ".RE"))) - ;; If headline is not the last sibling simply return - ;; LOW-LEVEL-BODY. Otherwise, also close the list, before any - ;; blank line. - (if (not (org-export-last-sibling-p headline info)) low-level-body - (replace-regexp-in-string - "[ \t\n]*\\'" "" - low-level-body)))) + (concat + ;; If the headline is the first sibling, start a list. + (when (org-export-first-sibling-p headline info) + (format "%s\n" ".RS")) + ;; Itemize headline + ".TP\n.ft I\n" text "\n.ft\n" + contents ".RE"))) + ;; If headline is not the last sibling simply return + ;; LOW-LEVEL-BODY. Otherwise, also close the list, before any + ;; blank line. + (if (not (org-export-last-sibling-p headline info)) low-level-body + (replace-regexp-in-string + "[ \t\n]*\\'" "" + low-level-body)))) ;; Case 3. Standard headline. Export it as a section. (t (format section-fmt text contents ))))) @@ -520,23 +492,22 @@ holding contextual information." ;;; Inline Src Block -(defun org-man-inline-src-block (inline-src-block contents info) +(defun org-man-inline-src-block (inline-src-block _contents info) "Transcode an INLINE-SRC-BLOCK element from Org to Man. CONTENTS holds the contents of the item. INFO is a plist holding contextual information." (let* ((code (org-element-property :value inline-src-block))) (cond - (org-man-source-highlight - (let* ((tmpdir (if (featurep 'xemacs) - temp-directory - temporary-file-directory )) + ((plist-get info :man-source-highlight) + (let* ((tmpdir temporary-file-directory) (in-file (make-temp-name (expand-file-name "srchilite" tmpdir))) (out-file (make-temp-name (expand-file-name "reshilite" tmpdir))) (org-lang (org-element-property :language inline-src-block)) - (lst-lang (cadr (assq (intern org-lang) - org-man-source-highlight-langs))) + (lst-lang + (cadr (assq (intern org-lang) + (plist-get info :man-source-highlight-langs)))) (cmd (concat (expand-file-name "source-highlight") " -s " lst-lang @@ -564,7 +535,7 @@ contextual information." ;;; Inlinetask ;;; Italic -(defun org-man-italic (italic contents info) +(defun org-man-italic (_italic contents _info) "Transcode ITALIC from Org to Man. CONTENTS is the text with italic markup. INFO is a plist holding contextual information." @@ -575,17 +546,15 @@ contextual information." (defun org-man-item (item contents info) - "Transcode an ITEM element from Org to Man. CONTENTS holds the contents of the item. INFO is a plist holding contextual information." - (let* ((bullet (org-element-property :bullet item)) (type (org-element-property :type (org-element-property :parent item))) - (checkbox (case (org-element-property :checkbox item) - (on "\\o'\\(sq\\(mu'") ;; - (off "\\(sq ") ;; - (trans "\\o'\\(sq\\(mi'" ))) ;; + (checkbox (pcase (org-element-property :checkbox item) + (`on "\\o'\\(sq\\(mu'") + (`off "\\(sq ") + (`trans "\\o'\\(sq\\(mi'"))) (tag (let ((tag (org-element-property :tag item))) ;; Check-boxes must belong to the tag. @@ -593,24 +562,22 @@ contextual information." (concat checkbox (org-export-data tag info))))))) - (if (and (null tag ) - (null checkbox)) - (let* ((bullet (org-trim bullet)) - (marker (cond ((string= "-" bullet) "\\(em") - ((string= "*" bullet) "\\(bu") - ((eq type 'ordered) - (format "%s " (org-trim bullet))) - (t "\\(dg")))) - (concat ".IP " marker " 4\n" - (org-trim (or contents " " )))) - ; else + (if (and (null tag) (null checkbox)) + (let* ((bullet (org-trim bullet)) + (marker (cond ((string= "-" bullet) "\\(em") + ((string= "*" bullet) "\\(bu") + ((eq type 'ordered) + (format "%s " (org-trim bullet))) + (t "\\(dg")))) + (concat ".IP " marker " 4\n" + (org-trim (or contents " " )))) (concat ".TP\n" (or tag (concat " " checkbox)) "\n" (org-trim (or contents " " )))))) ;;; Keyword -(defun org-man-keyword (keyword contents info) +(defun org-man-keyword (keyword _contents _info) "Transcode a KEYWORD element from Org to Man. CONTENTS is nil. INFO is a plist holding contextual information." (let ((key (org-element-property :key keyword)) @@ -623,16 +590,16 @@ CONTENTS is nil. INFO is a plist holding contextual information." ;;; Line Break -(defun org-man-line-break (line-break contents info) +(defun org-man-line-break (_line-break _contents _info) "Transcode a LINE-BREAK object from Org to Man. CONTENTS is nil. INFO is a plist holding contextual information." - ".br\n") + "\n.br\n") ;;; 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. @@ -645,11 +612,11 @@ INFO is a plist holding contextual information. See (path (cond ((member type '("http" "https" "ftp" "mailto")) (concat type ":" raw-path)) - ((and (string= type "file") (file-name-absolute-p raw-path)) - (concat "file:" raw-path)) - (t raw-path))) - protocol) + ((string= type "file") (org-export-file-uri raw-path)) + (t raw-path)))) (cond + ;; Link type is handled by a special function. + ((org-export-custom-protocol-maybe link desc 'man)) ;; External link with a description part. ((and path desc) (format "%s \\fBat\\fP \\fI%s\\fP" path desc)) ;; External link without a description part. @@ -657,10 +624,20 @@ INFO is a plist holding contextual information. See ;; No path, only description. Try to do something useful. (t (format "\\fI%s\\fP" desc))))) +;;;; Node Property + +(defun org-man-node-property (node-property _contents _info) + "Transcode a NODE-PROPERTY element from Org to Man. +CONTENTS is nil. INFO is a plist holding contextual +information." + (format "%s:%s" + (org-element-property :key node-property) + (let ((value (org-element-property :value node-property))) + (if value (concat " " value) "")))) ;;; Paragraph -(defun org-man-paragraph (paragraph contents info) +(defun org-man-paragraph (paragraph contents _info) "Transcode a PARAGRAPH element from Org to Man. CONTENTS is the contents of the paragraph, as a string. INFO is the plist used as a communication channel." @@ -681,7 +658,7 @@ the plist used as a communication channel." ;;; Plain List -(defun org-man-plain-list (plain-list contents info) +(defun org-man-plain-list (_plain-list contents _info) "Transcode a PLAIN-LIST element from Org to Man. CONTENTS is the contents of the list. INFO is a plist holding contextual information." @@ -716,10 +693,16 @@ contextual information." ;;; Property Drawer +(defun org-man-property-drawer (_property-drawer contents _info) + "Transcode a PROPERTY-DRAWER element from Org to Man. +CONTENTS holds the contents of the drawer. INFO is a plist +holding contextual information." + (and (org-string-nw-p contents) + (format ".RS\n.nf\n%s\n.fi\n.RE" contents))) ;;; Quote Block -(defun org-man-quote-block (quote-block contents info) +(defun org-man-quote-block (quote-block contents _info) "Transcode a QUOTE-BLOCK element from Org to Man. CONTENTS holds the contents of the block. INFO is a plist holding contextual information." @@ -727,28 +710,19 @@ holding contextual information." quote-block (format ".RS\n%s\n.RE" contents))) -;;; Quote Section - -(defun org-man-quote-section (quote-section contents info) - "Transcode a QUOTE-SECTION element from Org to Man. -CONTENTS is nil. INFO is a plist holding contextual information." - (let ((value (org-remove-indentation - (org-element-property :value quote-section)))) - (when value (format ".RS\\fI%s\\fP\n.RE\n" value)))) - ;;; Radio Target -(defun org-man-radio-target (radio-target text info) +(defun org-man-radio-target (_radio-target text _info) "Transcode a RADIO-TARGET object from Org to Man. TEXT is the text of the target. INFO is a plist holding contextual information." - text ) + text) ;;; Section -(defun org-man-section (section contents info) +(defun org-man-section (_section contents _info) "Transcode a SECTION element from Org to Man. CONTENTS holds the contents of the section. INFO is a plist holding contextual information." @@ -757,70 +731,49 @@ holding contextual information." ;;; Special Block -(defun org-man-special-block (special-block contents info) +(defun org-man-special-block (special-block contents _info) "Transcode a SPECIAL-BLOCK element from Org to Man. CONTENTS holds the contents of the block. INFO is a plist holding contextual information." - (let ((type (downcase (org-element-property :type special-block)))) - (org-man--wrap-label - special-block - (format "%s\n" contents)))) + (org-man--wrap-label special-block (format "%s\n" contents))) ;;; Src Block -(defun org-man-src-block (src-block contents info) +(defun org-man-src-block (src-block _contents info) "Transcode a SRC-BLOCK element from Org to Man. CONTENTS holds the contents of the item. INFO is a plist holding contextual information." - (let* ((lang (org-element-property :language src-block)) - (code (org-element-property :value src-block)) - (custom-env (and lang - (cadr (assq (intern lang) - org-man-custom-lang-environments)))) - (num-start (case (org-element-property :number-lines src-block) - (continued (org-export-get-loc src-block info)) - (new 0))) - (retain-labels (org-element-property :retain-labels src-block))) - (cond - ;; Case 1. No source fontification. - ((not org-man-source-highlight) + (if (not (plist-get info :man-source-highlight)) (format ".RS\n.nf\n\\fC%s\\fP\n.fi\n.RE\n\n" - (org-export-format-code-default src-block info))) - (org-man-source-highlight - (let* ((tmpdir (if (featurep 'xemacs) - temp-directory - temporary-file-directory )) - - (in-file (make-temp-name - (expand-file-name "srchilite" tmpdir))) - (out-file (make-temp-name - (expand-file-name "reshilite" tmpdir))) - - (org-lang (org-element-property :language src-block)) - (lst-lang (cadr (assq (intern org-lang) - org-man-source-highlight-langs))) - - (cmd (concat "source-highlight" - " -s " lst-lang - " -f groff_man " - " -i " in-file - " -o " out-file))) - - (if lst-lang - (let ((code-block "")) - (with-temp-file in-file (insert code)) - (shell-command cmd) - (setq code-block (org-file-contents out-file)) - (delete-file in-file) - (delete-file out-file) - code-block) - (format ".RS\n.nf\n\\fC\\m[black]%s\\m[]\\fP\n.fi\n.RE" code))))))) + (org-export-format-code-default src-block info)) + (let* ((tmpdir temporary-file-directory) + (in-file (make-temp-name (expand-file-name "srchilite" tmpdir))) + (out-file (make-temp-name (expand-file-name "reshilite" tmpdir))) + (code (org-element-property :value src-block)) + (org-lang (org-element-property :language src-block)) + (lst-lang + (cadr (assq (intern org-lang) + (plist-get info :man-source-highlight-langs)))) + (cmd (concat "source-highlight" + " -s " lst-lang + " -f groff_man " + " -i " in-file + " -o " out-file))) + (if lst-lang + (let ((code-block "")) + (with-temp-file in-file (insert code)) + (shell-command cmd) + (setq code-block (org-file-contents out-file)) + (delete-file in-file) + (delete-file out-file) + code-block) + (format ".RS\n.nf\n\\fC\\m[black]%s\\m[]\\fP\n.fi\n.RE" code))))) ;;; Statistics Cookie -(defun org-man-statistics-cookie (statistics-cookie contents info) +(defun org-man-statistics-cookie (statistics-cookie _contents _info) "Transcode a STATISTICS-COOKIE object from Org to Man. CONTENTS is nil. INFO is a plist holding contextual information." (org-element-property :value statistics-cookie)) @@ -828,7 +781,7 @@ CONTENTS is nil. INFO is a plist holding contextual information." ;;; Strike-Through -(defun org-man-strike-through (strike-through contents info) +(defun org-man-strike-through (_strike-through contents _info) "Transcode STRIKE-THROUGH from Org to Man. CONTENTS is the text with strike-through markup. INFO is a plist holding contextual information." @@ -836,7 +789,7 @@ holding contextual information." ;;; Subscript -(defun org-man-subscript (subscript contents info) +(defun org-man-subscript (_subscript contents _info) "Transcode a SUBSCRIPT object from Org to Man. CONTENTS is the contents of the object. INFO is a plist holding contextual information." @@ -844,7 +797,7 @@ contextual information." ;;; Superscript "^_%s$ -(defun org-man-superscript (superscript contents info) +(defun org-man-superscript (_superscript contents _info) "Transcode a SUPERSCRIPT object from Org to Man. CONTENTS is the contents of the object. INFO is a plist holding contextual information." @@ -868,7 +821,7 @@ CONTENTS is the contents of the table. INFO is a plist holding contextual information." (cond ;; Case 1: verbatim table. - ((or org-man-tables-verbatim + ((or (plist-get info :man-tables-verbatim) (let ((attr (read (format "(%s)" (mapconcat #'identity @@ -907,14 +860,14 @@ a communication channel." (when (and (memq 'left borders) (not alignment)) (push "|" alignment)) (push - (case (org-export-table-cell-alignment cell info) - (left (concat "l" width divider)) - (right (concat "r" width divider)) - (center (concat "c" width divider))) + (concat (pcase (org-export-table-cell-alignment cell info) + (`left "l") (`right "r") (`center "c")) + width + divider) alignment) (when (memq 'right borders) (push "|" alignment)))) info) - (apply 'concat (reverse alignment)))) + (apply #'concat (reverse alignment)))) (defun org-man-table--org-table (table contents info) "Return appropriate Man code for an Org table. @@ -925,7 +878,6 @@ channel. This function assumes TABLE has `org' as its `:type' attribute." (let* ((attr (org-export-read-attribute :attr_man table)) - (label (org-element-property :name table)) (caption (and (not (plist-get attr :disable-caption)) (org-man--caption/label-string table info))) (divider (if (plist-get attr :divider) "|" " ")) @@ -943,7 +895,8 @@ This function assumes TABLE has `org' as its `:type' attribute." (let ((placement (plist-get attr :placement))) (cond ((string= placement 'center) "center") ((string= placement 'left) nil) - (t (if org-man-tables-centered "center" "")))) + ((plist-get info :man-tables-centered) "center") + (t ""))) (or (plist-get attr :boxtype) "box")))) (title-line (plist-get attr :title-line)) @@ -970,14 +923,14 @@ This function assumes TABLE has `org' as its `:type' attribute." (format "%s.\n" (let ((final-line "")) (when title-line - (dotimes (i (length first-line)) + (dotimes (_ (length first-line)) (setq final-line (concat final-line "cb" divider)))) (setq final-line (concat final-line "\n")) (if alignment (setq final-line (concat final-line alignment)) - (dotimes (i (length first-line)) + (dotimes (_ (length first-line)) (setq final-line (concat final-line "c" divider)))) final-line )) @@ -1018,69 +971,59 @@ This function assumes TABLE has `org' as its `:type' attribute." "Transcode a TABLE-CELL element from Org to Man CONTENTS is the cell contents. INFO is a plist used as a communication channel." - (concat (if (and contents - org-man-table-scientific-notation - (string-match orgtbl-exp-regexp contents)) - ;; Use appropriate format string for scientific - ;; notation. - (format org-man-table-scientific-notation - (match-string 1 contents) - (match-string 2 contents)) - contents ) - (when (org-export-get-next-element table-cell info) "\t"))) + (concat + (let ((scientific-format (plist-get info :man-table-scientific-notation))) + (if (and contents + scientific-format + (string-match orgtbl-exp-regexp contents)) + ;; Use appropriate format string for scientific notation. + (format scientific-format + (match-string 1 contents) + (match-string 2 contents)) + contents)) + (when (org-export-get-next-element table-cell info) "\t"))) ;;; Table Row (defun org-man-table-row (table-row contents info) - "Transcode a TABLE-ROW element from Org to Man + "Transcode a TABLE-ROW element from Org to Man. CONTENTS is the contents of the row. INFO is a plist used as a communication channel." - ;; Rules are ignored since table separators are deduced from - ;; borders of the current row. + ;; Rules are ignored since table separators are deduced from borders + ;; of the current row. (when (eq (org-element-property :type table-row) 'standard) - (let* ((attr (mapconcat 'identity - (org-element-property - :attr_man (org-export-get-parent table-row)) - " ")) - ;; TABLE-ROW's borders are extracted from its first cell. - (borders - (org-export-table-cell-borders - (car (org-element-contents table-row)) info))) + (let ((borders + ;; TABLE-ROW's borders are extracted from its first cell. + (org-export-table-cell-borders + (car (org-element-contents table-row)) info))) (concat - ;; Mark horizontal lines - (cond ((and (memq 'top borders) (memq 'above borders)) "_\n")) + (cond ((and (memq 'top borders) (memq 'above borders)) "_\n")) contents - - (cond - ;; When BOOKTABS are activated enforce bottom rule even when - ;; no hline was specifically marked. - ((and (memq 'bottom borders) (memq 'below borders)) "\n_") - ((memq 'below borders) "\n_")))))) + (cond ((and (memq 'bottom borders) (memq 'below borders)) "\n_") + ((memq 'below borders) "\n_")))))) ;;; Target -(defun org-man-target (target contents info) +(defun org-man-target (target _contents info) "Transcode a TARGET object from Org to Man. CONTENTS is nil. INFO is a plist holding contextual information." - (format "\\fI%s\\fP" - (org-export-solidify-link-text (org-element-property :value target)))) + (format "\\fI%s\\fP" (org-export-get-reference target info))) ;;; Timestamp -(defun org-man-timestamp (timestamp contents info) +(defun org-man-timestamp (_timestamp _contents _info) "Transcode a TIMESTAMP object from Org to Man. - CONTENTS is nil. INFO is a plist holding contextual - information." - "" ) +CONTENTS is nil. INFO is a plist holding contextual information." + "") ;;; Underline -(defun org-man-underline (underline contents info) +(defun org-man-underline (_underline contents _info) "Transcode UNDERLINE from Org to Man. CONTENTS is the text with underline markup. INFO is a plist holding contextual information." @@ -1089,16 +1032,15 @@ holding contextual information." ;;; Verbatim -(defun org-man-verbatim (verbatim contents info) - "Transcode a VERBATIM object from Org to Man. -CONTENTS is nil. INFO is a plist used as a communication -channel." - (format ".nf\n%s\n.fi" contents)) +(defun org-man-verbatim (verbatim _contents _info) + "Transcode a VERBATIM object from Org to Man." + (format "\\fI%s\\fP" + (org-man--protect-text (org-element-property :value verbatim)))) ;;; Verse Block -(defun org-man-verse-block (verse-block contents info) +(defun org-man-verse-block (_verse-block contents _info) "Transcode a VERSE-BLOCK element from Org to Man. CONTENTS is verse block contents. INFO is a plist holding contextual information." @@ -1182,68 +1124,15 @@ FILE is the name of the file being compiled. Processing is done through the command specified in `org-man-pdf-process'. Return PDF file name or an error if it couldn't be produced." - (let* ((base-name (file-name-sans-extension (file-name-nondirectory file))) - (full-name (file-truename file)) - (out-dir (file-name-directory file)) - ;; Properly set working directory for compilation. - (default-directory (if (file-name-absolute-p file) - (file-name-directory full-name) - default-directory)) - errors) - (message "Processing Groff file %s..." file) - (save-window-excursion - (cond - ;; A function is provided: Apply it. - ((functionp org-man-pdf-process) - (funcall org-man-pdf-process (shell-quote-argument file))) - ;; A list is provided: Replace %b, %f and %o with appropriate - ;; values in each command before applying it. Output is - ;; redirected to "*Org PDF Groff Output*" buffer. - ((consp org-man-pdf-process) - (let ((outbuf (get-buffer-create "*Org PDF Groff Output*"))) - (mapc - (lambda (command) - (shell-command - (replace-regexp-in-string - "%b" (shell-quote-argument base-name) - (replace-regexp-in-string - "%f" (shell-quote-argument full-name) - (replace-regexp-in-string - "%o" (shell-quote-argument out-dir) command t t) t t) t t) - outbuf)) - org-man-pdf-process) - ;; Collect standard errors from output buffer. - (setq errors (org-man-collect-errors outbuf)))) - (t (error "No valid command to process to PDF"))) - (let ((pdffile (concat out-dir base-name ".pdf"))) - ;; Check for process failure. Provide collected errors if - ;; possible. - (if (not (file-exists-p pdffile)) - (error "PDF file %s wasn't produced%s" pdffile - (if errors (concat ": " errors) "")) - ;; Else remove log files, when specified, and signal end of - ;; process to user, along with any error encountered. - (when org-man-remove-logfiles - (dolist (ext org-man-logfiles-extensions) - (let ((file (concat out-dir base-name "." ext))) - (when (file-exists-p file) (delete-file file))))) - (message (concat "Process completed" - (if (not errors) "." - (concat " with errors: " errors))))) - ;; Return output file name. - pdffile)))) - -(defun org-man-collect-errors (buffer) - "Collect some kind of errors from \"groff\" output -BUFFER is the buffer containing output. -Return collected error types as a string, or nil if there was -none." - (with-current-buffer buffer - (save-excursion - (goto-char (point-max)) - ;; Find final run - nil ))) - + (message "Processing Groff file %s..." file) + (let ((output (org-compile-file file org-man-pdf-process "pdf"))) + (when org-man-remove-logfiles + (let ((base (file-name-sans-extension output))) + (dolist (ext org-man-logfiles-extensions) + (let ((file (concat base "." ext))) + (when (file-exists-p file) (delete-file file)))))) + (message "Process completed.") + output)) (provide 'ox-man) diff --git a/lisp/org/ox-md.el b/lisp/org/ox-md.el index ab73f29dfa9..12188387355 100644 --- a/lisp/org/ox-md.el +++ b/lisp/org/ox-md.el @@ -1,4 +1,4 @@ -;;; ox-md.el --- Markdown Back-End for Org Export Engine +;;; ox-md.el --- Markdown Back-End for Org Export Engine -*- lexical-binding: t; -*- ;; Copyright (C) 2012-2017 Free Software Foundation, Inc. @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -28,9 +28,9 @@ ;;; Code: -(eval-when-compile (require 'cl)) +(require 'cl-lib) (require 'ox-html) - +(require 'ox-publish) ;;; User-Configurable Variables @@ -51,11 +51,29 @@ This variable can be set to either `atx' or `setext'." (const :tag "Use \"Setext\" style" setext))) +;;;; Footnotes + +(defcustom org-md-footnotes-section "%s%s" + "Format string for the footnotes section. +The first %s placeholder will be replaced with the localized Footnotes section +heading, the second with the contents of the Footnotes section." + :group 'org-export-md + :type 'string + :version "26.1" + :package-version '(Org . "9.0")) + +(defcustom org-md-footnote-format "<sup>%s</sup>" + "Format string for the footnote reference. +The %s will be replaced by the footnote reference itself." + :group 'org-export-md + :type 'string + :version "26.1" + :package-version '(Org . "9.0")) + ;;; Define Back-End (org-export-define-derived-backend 'md 'html - :export-block '("MD" "MARKDOWN") :filters-alist '((:filter-parse-tree . org-md-separate-elements)) :menu-entry '(?m "Export to Markdown" @@ -68,62 +86,64 @@ This variable can be set to either `atx' or `setext'." (org-open-file (org-md-export-to-markdown nil s v))))))) :translate-alist '((bold . org-md-bold) (code . org-md-verbatim) - (comment . (lambda (&rest args) "")) - (comment-block . (lambda (&rest args) "")) (example-block . org-md-example-block) + (export-block . org-md-export-block) (fixed-width . org-md-example-block) - (footnote-definition . ignore) - (footnote-reference . ignore) (headline . org-md-headline) (horizontal-rule . org-md-horizontal-rule) (inline-src-block . org-md-verbatim) (inner-template . org-md-inner-template) (italic . org-md-italic) (item . org-md-item) + (keyword . org-md-keyword) (line-break . org-md-line-break) (link . org-md-link) + (node-property . org-md-node-property) (paragraph . org-md-paragraph) (plain-list . org-md-plain-list) (plain-text . org-md-plain-text) + (property-drawer . org-md-property-drawer) (quote-block . org-md-quote-block) - (quote-section . org-md-example-block) (section . org-md-section) (src-block . org-md-example-block) (template . org-md-template) - (verbatim . org-md-verbatim))) - + (verbatim . org-md-verbatim)) + :options-alist + '((:md-footnote-format nil nil org-md-footnote-format) + (:md-footnotes-section nil nil org-md-footnotes-section) + (:md-headline-style nil nil org-md-headline-style))) ;;; Filters -(defun org-md-separate-elements (tree backend info) +(defun org-md-separate-elements (tree _backend info) "Fix blank lines between elements. TREE is the parse tree being exported. BACKEND is the export back-end used. INFO is a plist used as a communication channel. -Enforce a blank line between elements. There are three -exceptions to this rule: +Enforce a blank line between elements. There are two exceptions +to this rule: 1. Preserve blank lines between sibling items in a plain list, - 2. Outside of plain lists, preserve blank lines between - a paragraph and a plain list, - - 3. In an item, remove any blank line before the very first - paragraph and the next sub-list. + 2. In an item, remove any blank line before the very first + paragraph and the next sub-list when the latter ends the + current item. Assume BACKEND is `md'." (org-element-map tree (remq 'item org-element-all-elements) (lambda (e) - (cond - ((not (and (eq (org-element-type e) 'paragraph) - (eq (org-element-type (org-export-get-next-element e info)) - 'plain-list))) - (org-element-put-property e :post-blank 1)) - ((not (eq (org-element-type (org-element-property :parent e)) 'item))) - (t (org-element-put-property - e :post-blank (if (org-export-get-previous-element e info) 1 0)))))) + (org-element-put-property + e :post-blank + (if (and (eq (org-element-type e) 'paragraph) + (eq (org-element-type (org-element-property :parent e)) 'item) + (org-export-first-sibling-p e info) + (let ((next (org-export-get-next-element e info))) + (and (eq (org-element-type next) 'plain-list) + (not (org-export-get-next-element next info))))) + 0 + 1)))) ;; Return updated tree. tree) @@ -133,7 +153,7 @@ Assume BACKEND is `md'." ;;;; Bold -(defun org-md-bold (bold contents info) +(defun org-md-bold (_bold contents _info) "Transcode BOLD object into Markdown format. CONTENTS is the text within bold markup. INFO is a plist used as a communication channel." @@ -142,22 +162,22 @@ a communication channel." ;;;; Code and Verbatim -(defun org-md-verbatim (verbatim contents info) +(defun org-md-verbatim (verbatim _contents _info) "Transcode VERBATIM object into Markdown format. CONTENTS is nil. INFO is a plist used as a communication channel." (let ((value (org-element-property :value verbatim))) (format (cond ((not (string-match "`" value)) "`%s`") - ((or (string-match "\\``" value) - (string-match "`\\'" value)) + ((or (string-prefix-p "`" value) + (string-suffix-p "`" value)) "`` %s ``") (t "``%s``")) value))) -;;;; Example Block and Src Block +;;;; Example Block, Src Block and export Block -(defun org-md-example-block (example-block contents info) +(defun org-md-example-block (example-block _contents info) "Transcode EXAMPLE-BLOCK element into Markdown format. CONTENTS is nil. INFO is a plist used as a communication channel." @@ -166,6 +186,14 @@ channel." (org-remove-indentation (org-export-format-code-default example-block info)))) +(defun org-md-export-block (export-block contents info) + "Transcode a EXPORT-BLOCK element from Org to Markdown. +CONTENTS is nil. INFO is a plist holding contextual information." + (if (member (org-element-property :type export-block) '("MARKDOWN" "MD")) + (org-remove-indentation (org-element-property :value export-block)) + ;; Also include HTML export blocks. + (org-export-with-backend 'html export-block contents info))) + ;;;; Headline @@ -189,45 +217,94 @@ a communication channel." (and (plist-get info :with-priority) (let ((char (org-element-property :priority headline))) (and char (format "[#%c] " char))))) - (anchor - (when (plist-get info :with-toc) - (org-html--anchor - (or (org-element-property :CUSTOM_ID headline) - (concat "sec-" - (mapconcat 'number-to-string - (org-export-get-headline-number - headline info) "-")))))) ;; Headline text without tags. - (heading (concat todo priority title))) + (heading (concat todo priority title)) + (style (plist-get info :md-headline-style))) (cond ;; Cannot create a headline. Fall-back to a list. ((or (org-export-low-level-p headline info) - (not (memq org-md-headline-style '(atx setext))) - (and (eq org-md-headline-style 'atx) (> level 6)) - (and (eq org-md-headline-style 'setext) (> level 2))) + (not (memq style '(atx setext))) + (and (eq style 'atx) (> level 6)) + (and (eq style 'setext) (> level 2))) (let ((bullet (if (not (org-export-numbered-headline-p headline info)) "-" (concat (number-to-string (car (last (org-export-get-headline-number headline info)))) ".")))) - (concat bullet (make-string (- 4 (length bullet)) ? ) heading tags - "\n\n" - (and contents - (replace-regexp-in-string "^" " " contents))))) - ;; Use "Setext" style. - ((eq org-md-headline-style 'setext) - (concat heading tags anchor "\n" - (make-string (length heading) (if (= level 1) ?= ?-)) - "\n\n" - contents)) - ;; Use "atx" style. - (t (concat (make-string level ?#) " " heading tags anchor "\n\n" contents)))))) - + (concat bullet (make-string (- 4 (length bullet)) ?\s) heading tags "\n\n" + (and contents (replace-regexp-in-string "^" " " contents))))) + (t + (let ((anchor + (and (org-md--headline-referred-p headline info) + (format "<a id=\"%s\"></a>" + (or (org-element-property :CUSTOM_ID headline) + (org-export-get-reference headline info)))))) + (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 "\\<headlines\\>" value) + (let ((n (and + (string-match "\\<[0-9]+\\>" value) + (string-to-number (match-string 0 value)))) + (local? (string-match-p "\\<local\\>" 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) +(defun org-md-horizontal-rule (_horizontal-rule _contents _info) "Transcode HORIZONTAL-RULE element into Markdown format. CONTENTS is the horizontal rule contents. INFO is a plist used as a communication channel." @@ -236,7 +313,7 @@ as a communication channel." ;;;; Italic -(defun org-md-italic (italic contents info) +(defun org-md-italic (_italic contents _info) "Transcode ITALIC object into Markdown format. CONTENTS is the text within italic markup. INFO is a plist used as a communication channel." @@ -261,19 +338,41 @@ a communication channel." ".")))) (concat bullet (make-string (- 4 (length bullet)) ? ) - (case (org-element-property :checkbox item) - (on "[X] ") - (trans "[-] ") - (off "[ ] ")) + (pcase (org-element-property :checkbox item) + (`on "[X] ") + (`trans "[-] ") + (`off "[ ] ")) (let ((tag (org-element-property :tag item))) (and tag (format "**%s:** "(org-export-data tag info)))) (and contents (org-trim (replace-regexp-in-string "^" " " contents)))))) + +;;;; Keyword + +(defun org-md-keyword (keyword contents info) + "Transcode a KEYWORD element into Markdown format. +CONTENTS is nil. INFO is a plist used as a communication +channel." + (pcase (org-element-property :key keyword) + ((or "MARKDOWN" "MD") (org-element-property :value keyword)) + ("TOC" + (let ((case-fold-search t) + (value (org-element-property :value keyword))) + (cond + ((string-match-p "\\<headlines\\>" value) + (let ((depth (and (string-match "\\<[0-9]+\\>" value) + (string-to-number (match-string 0 value)))) + (local? (string-match-p "\\<local\\>" value))) + (org-remove-indentation + (org-md--build-toc info depth keyword local?))))))) + (_ (org-export-with-backend 'html keyword contents info)))) + + ;;;; Line Break -(defun org-md-line-break (line-break contents info) +(defun org-md-line-break (_line-break _contents _info) "Transcode LINE-BREAK object into Markdown format. CONTENTS is nil. INFO is a plist used as a communication channel." @@ -287,32 +386,55 @@ channel." CONTENTS is the link's description. INFO is a plist used as a communication channel." (let ((link-org-files-as-md - (function - (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)))) + (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))) (cond - ((member type '("custom-id" "id")) - (let ((destination (org-export-resolve-id-link link info))) - (if (stringp destination) ; External file. - (let ((path (funcall link-org-files-as-md destination))) - (if (not contents) (format "<%s>" path) - (format "[%s](%s)" contents path))) - (concat - (and contents (concat contents " ")) - (format "(%s)" - (format (org-export-translate "See section %s" :html info) - (mapconcat 'number-to-string - (org-export-get-headline-number - destination info) - "."))))))) + ;; Link type is handled by a special function. + ((org-export-custom-protocol-maybe link contents 'md)) + ((member type '("custom-id" "id" "fuzzy")) + (let ((destination (if (string= type "fuzzy") + (org-export-resolve-fuzzy-link link info) + (org-export-resolve-id-link link info)))) + (pcase (org-element-type destination) + (`plain-text ; External file. + (let ((path (funcall link-org-files-as-md destination))) + (if (not contents) (format "<%s>" path) + (format "[%s](%s)" contents path)))) + (`headline + (format + "[%s](#%s)" + ;; Description. + (cond ((org-string-nw-p contents)) + ((org-export-numbered-headline-p destination info) + (mapconcat #'number-to-string + (org-export-get-headline-number destination info) + ".")) + (t (org-export-data (org-element-property :title destination) + info))) + ;; Reference. + (or (org-element-property :CUSTOM_ID destination) + (org-export-get-reference destination info)))) + (_ + (let ((description + (or (org-string-nw-p contents) + (let ((number (org-export-get-ordinal destination info))) + (cond + ((not number) nil) + ((atom number) (number-to-string number)) + (t (mapconcat #'number-to-string number "."))))))) + (when description + (format "[%s](#%s)" + 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))) - (if (not (file-name-absolute-p raw-path)) raw-path - (expand-file-name raw-path)))) + (cond ((not (equal "file" type)) (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))) @@ -324,53 +446,46 @@ a communication channel." (format (org-export-get-coderef-format ref contents) (org-export-resolve-coderef ref info)))) ((equal type "radio") contents) - ((equal type "fuzzy") - (let ((destination (org-export-resolve-fuzzy-link link info))) - (if (org-string-nw-p contents) contents - (when destination - (let ((number (org-export-get-ordinal destination info))) - (when number - (if (atom number) (number-to-string number) - (mapconcat 'number-to-string number ".")))))))) - ;; Link type is handled by a special function. - ((let ((protocol (nth 2 (assoc type org-link-protocols)))) - (and (functionp protocol) - (funcall protocol - (org-link-unescape (org-element-property :path link)) - contents - 'md)))) (t (let* ((raw-path (org-element-property :path link)) (path (cond - ((member type '("http" "https" "ftp")) + ((member type '("http" "https" "ftp" "mailto" "irc")) (concat type ":" raw-path)) ((string= type "file") - (let ((path (funcall link-org-files-as-md raw-path))) - (if (not (file-name-absolute-p path)) path - ;; If file path is absolute, prepend it - ;; with "file:" component. - (concat "file:" path)))) + (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))))))) +;;;; Node Property + +(defun org-md-node-property (node-property _contents _info) + "Transcode a NODE-PROPERTY element into Markdown syntax. +CONTENTS is nil. INFO is a plist holding contextual +information." + (format "%s:%s" + (org-element-property :key node-property) + (let ((value (org-element-property :value node-property))) + (if value (concat " " value) "")))) + + ;;;; Paragraph -(defun org-md-paragraph (paragraph contents info) +(defun org-md-paragraph (paragraph contents _info) "Transcode PARAGRAPH element into Markdown format. CONTENTS is the paragraph contents. INFO is a plist used as a communication channel." (let ((first-object (car (org-element-contents paragraph)))) ;; If paragraph starts with a #, protect it. - (if (and (stringp first-object) (string-match "\\`#" first-object)) - (replace-regexp-in-string "\\`#" "\\#" contents nil t) + (if (and (stringp first-object) (string-prefix-p "#" first-object)) + (concat "\\" contents) contents))) ;;;; Plain List -(defun org-md-plain-list (plain-list contents info) +(defun org-md-plain-list (_plain-list contents _info) "Transcode PLAIN-LIST element into Markdown format. CONTENTS is the plain-list contents. INFO is a plist used as a communication channel." @@ -403,9 +518,19 @@ contextual information." text) +;;;; Property Drawer + +(defun org-md-property-drawer (_property-drawer contents _info) + "Transcode a PROPERTY-DRAWER element into Markdown format. +CONTENTS holds the contents of the drawer. INFO is a plist +holding contextual information." + (and (org-string-nw-p contents) + (replace-regexp-in-string "^" " " contents))) + + ;;;; Quote Block -(defun org-md-quote-block (quote-block contents info) +(defun org-md-quote-block (_quote-block contents _info) "Transcode QUOTE-BLOCK element into Markdown format. CONTENTS is the quote-block contents. INFO is a plist used as a communication channel." @@ -416,7 +541,7 @@ a communication channel." ;;;; Section -(defun org-md-section (section contents info) +(defun org-md-section (_section contents _info) "Transcode SECTION element into Markdown format. CONTENTS is the section contents. INFO is a plist used as a communication channel." @@ -425,15 +550,97 @@ a communication channel." ;;;; Template +(defun org-md--build-toc (info &optional n keyword local) + "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. + +Optional argument KEYWORD specifies the TOC keyword, if any, from +which the table of contents generation has been initiated. + +When optional argument LOCAL is non-nil, build a table of +contents according to the current headline." + (concat + (unless local + (let ((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)) + (number (format "%d." + (org-last + (org-export-get-headline-number headline info)))) + (bullet (concat number (make-string (- 4 (length number)) ?\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))) + (let ((tags (org-export-get-tags headline info))) + (and tags + (format ":%s:" + (mapconcat #'identity tags ":"))))))) + (concat indentation bullet title tags))) + (org-export-collect-headlines info n (and local keyword)) "\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 holding export options." ;; Make sure CONTENTS is separated from table of contents and ;; footnotes with at least a blank line. - (org-trim (org-html-inner-template (concat "\n" contents "\n") info))) - -(defun org-md-template (contents info) + (concat + ;; Table of contents. + (let ((depth (plist-get info :with-toc))) + (when depth + (concat (org-md--build-toc info (and (wholenump depth) depth)) "\n"))) + ;; Document contents. + contents + "\n" + ;; Footnotes section. + (org-md--footnote-section info))) + +(defun org-md-template (contents _info) "Return complete document string after Markdown conversion. CONTENTS is the transcoded contents string. INFO is a plist used as a communication channel." @@ -472,9 +679,9 @@ non-nil." ;;;###autoload (defun org-md-convert-region-to-md () - "Assume the current region has org-mode syntax, and convert it to Markdown. + "Assume the current region has Org syntax, and convert it to Markdown. This can be used in any buffer. For example, you can write an -itemized list in org-mode syntax in a Markdown buffer and use +itemized list in Org syntax in a Markdown buffer and use this command to convert it." (interactive) (org-export-replace-region-by 'md)) @@ -505,6 +712,16 @@ Return output file's name." (let ((outfile (org-export-output-file-name ".md" subtreep))) (org-export-to-file 'md outfile async subtreep visible-only))) +;;;###autoload +(defun org-md-publish-to-md (plist filename pub-dir) + "Publish an org file to Markdown. + +FILENAME is the filename of the Org file to be published. PLIST +is the property list for the given project. PUB-DIR is the +publishing directory. + +Return output file name." + (org-publish-org-to 'md filename ".md" plist pub-dir)) (provide 'ox-md) diff --git a/lisp/org/ox-odt.el b/lisp/org/ox-odt.el index 5430bdaead8..a19bab29c16 100644 --- a/lisp/org/ox-odt.el +++ b/lisp/org/ox-odt.el @@ -1,4 +1,4 @@ -;;; ox-odt.el --- OpenDocument Text Exporter for Org Mode +;;; ox-odt.el --- OpenDocument Text Exporter for Org Mode -*- lexical-binding: t; -*- ;; Copyright (C) 2010-2017 Free Software Foundation, Inc. @@ -19,18 +19,17 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;;; Code: -(eval-when-compile - (require 'cl) - (require 'table nil 'noerror)) +(require 'cl-lib) (require 'format-spec) (require 'ox) (require 'org-compat) +(require 'table nil 'noerror) ;;; Define Back-End @@ -59,13 +58,13 @@ (latex-fragment . org-odt-latex-fragment) (line-break . org-odt-line-break) (link . org-odt-link) + (node-property . org-odt-node-property) (paragraph . org-odt-paragraph) (plain-list . org-odt-plain-list) (plain-text . org-odt-plain-text) (planning . org-odt-planning) (property-drawer . org-odt-property-drawer) (quote-block . org-odt-quote-block) - (quote-section . org-odt-quote-section) (radio-target . org-odt-radio-target) (section . org-odt-section) (special-block . org-odt-special-block) @@ -83,11 +82,11 @@ (underline . org-odt-underline) (verbatim . org-odt-verbatim) (verse-block . org-odt-verse-block)) - :export-block "ODT" :filters-alist '((:filter-parse-tree . (org-odt--translate-latex-fragments org-odt--translate-description-lists - org-odt--translate-list-tables))) + org-odt--translate-list-tables + org-odt--translate-image-links))) :menu-entry '(?o "Export to ODT" ((?o "As ODT file" org-odt-export-to-odt) @@ -97,29 +96,53 @@ (org-open-file (org-odt-export-to-odt nil s v) 'system)))))) :options-alist '((:odt-styles-file "ODT_STYLES_FILE" nil nil t) + (:description "DESCRIPTION" nil nil newline) + (:keywords "KEYWORDS" nil nil space) + (:subtitle "SUBTITLE" nil nil parse) + ;; Other variables. + (:odt-content-template-file nil nil org-odt-content-template-file) + (:odt-display-outline-level nil nil org-odt-display-outline-level) + (:odt-fontify-srcblocks nil nil org-odt-fontify-srcblocks) + (:odt-format-drawer-function nil nil org-odt-format-drawer-function) + (:odt-format-headline-function nil nil org-odt-format-headline-function) + (:odt-format-inlinetask-function nil nil org-odt-format-inlinetask-function) + (: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. - (:with-latex nil "tex" org-odt-with-latex))) + (:with-latex nil "tex" org-odt-with-latex) + ;; Retrieve LaTeX header for fragments. + (:latex-header "LATEX_HEADER" nil nil newline))) ;;; Dependencies ;;; Hooks -;;; Function Declarations +;;; Function and Dynamically Scoped Variables Declarations -(declare-function org-id-find-id-file "org-id" (id)) (declare-function hfy-face-to-style "htmlfontify" (fn)) (declare-function hfy-face-or-def-to-name "htmlfontify" (fn)) (declare-function archive-zip-extract "arc-mode" (archive name)) (declare-function org-create-math-formula "org" (latex-frag &optional mathml-file)) (declare-function browse-url-file-url "browse-url" (file)) +(defvar nxml-auto-insert-xml-declaration-flag) ; nxml-mode.el +(defvar archive-zip-extract) ; arc-mode.el +(defvar hfy-end-span-handler) ; htmlfontify.el +(defvar hfy-begin-span-handler) ; htmlfontify.el +(defvar hfy-face-to-css) ; htmlfontify.el +(defvar hfy-html-quote-map) ; htmlfontify.el +(defvar hfy-html-quote-regex) ; htmlfontify.el ;;; Internal Variables (defconst org-odt-lib-dir - (file-name-directory load-file-name) + (file-name-directory (or load-file-name (buffer-file-name))) "Location of ODT exporter. Use this to infer values of `org-odt-styles-dir' and `org-odt-schema-dir'.") @@ -157,7 +180,7 @@ and `org-odt-data-dir'.") (eval-when-compile (and (boundp 'org-odt-data-dir) org-odt-data-dir ; see make install (expand-file-name "./styles/" org-odt-data-dir))) - (expand-file-name "../../etc/styles/" org-odt-lib-dir) ; git + (expand-file-name "../etc/styles/" org-odt-lib-dir) ; git (expand-file-name "./etc/styles/" org-odt-lib-dir) ; elpa (expand-file-name "./org/" data-directory) ; system ) @@ -167,23 +190,14 @@ heuristically based on the values of `org-odt-lib-dir' and `org-odt-data-dir'.") (defconst org-odt-styles-dir - (let* ((styles-dir - (catch 'styles-dir - (message "Debug (ox-odt): Searching for OpenDocument styles files...") - (mapc (lambda (styles-dir) - (when styles-dir - (message "Debug (ox-odt): Trying %s..." styles-dir) - (when (and (file-readable-p - (expand-file-name - "OrgOdtContentTemplate.xml" styles-dir)) - (file-readable-p - (expand-file-name - "OrgOdtStyles.xml" styles-dir))) - (message "Debug (ox-odt): Using styles under %s" - styles-dir) - (throw 'styles-dir styles-dir)))) - org-odt-styles-dir-list) - nil))) + (let ((styles-dir + (cl-find-if + (lambda (dir) + (and dir + (file-readable-p + (expand-file-name "OrgOdtContentTemplate.xml" dir)) + (file-readable-p (expand-file-name "OrgOdtStyles.xml" dir)))) + org-odt-styles-dir-list))) (unless styles-dir (error "Error (ox-odt): Cannot find factory styles files, aborting")) styles-dir) @@ -192,13 +206,12 @@ heuristically based on the values of `org-odt-lib-dir' and This directory contains the following XML files - \"OrgOdtStyles.xml\" and \"OrgOdtContentTemplate.xml\". These XML files are used as the default values of - `org-odt-styles-file' and - `org-odt-content-template-file'. + `org-odt-styles-file' and `org-odt-content-template-file'. The default value of this variable varies depending on the -version of org in use and is initialized from -`org-odt-styles-dir-list'. Note that the user could be using org -from one of: org's own private git repository, GNU ELPA tar or +version of Org in use and is initialized from +`org-odt-styles-dir-list'. Note that the user could be using Org +from one of: Org own private git repository, GNU ELPA tar or standard Emacs.") (defconst org-odt-bookmark-prefix "OrgXref.") @@ -263,7 +276,6 @@ except that the foreground and background colors are set according to the default face identified by the `htmlfontify'.") (defvar hfy-optimizations) -(define-obsolete-variable-alias 'hfy-optimisations 'hfy-optimizations "25.1") (defvar org-odt-embedded-formulas-count 0) (defvar org-odt-embedded-images-count 0) (defvar org-odt-image-size-probe-method @@ -369,28 +381,14 @@ visually." (require 'rng-loc) (defcustom org-odt-schema-dir - (let* ((schema-dir - (catch 'schema-dir - (message "Debug (ox-odt): Searching for OpenDocument schema files...") - (mapc - (lambda (schema-dir) - (when schema-dir - (message "Debug (ox-odt): Trying %s..." schema-dir) - (when (and (file-expand-wildcards - (expand-file-name "od-manifest-schema*.rnc" - schema-dir)) - (file-expand-wildcards - (expand-file-name "od-schema*.rnc" - schema-dir)) - (file-readable-p - (expand-file-name "schemas.xml" schema-dir))) - (message "Debug (ox-odt): Using schema files under %s" - schema-dir) - (throw 'schema-dir schema-dir)))) - org-odt-schema-dir-list) - (message "Debug (ox-odt): No OpenDocument schema files installed") - nil))) - schema-dir) + (cl-find-if + (lambda (dir) + (and dir + (file-expand-wildcards + (expand-file-name "od-manifest-schema*.rnc" dir)) + (file-expand-wildcards (expand-file-name "od-schema*.rnc" dir)) + (file-readable-p (expand-file-name "schemas.xml" dir)))) + org-odt-schema-dir-list) "Directory that contains OpenDocument schema files. This directory contains: @@ -647,8 +645,7 @@ values. See Info node `(emacs) File Variables'." ;;;; Drawers -(defcustom org-odt-format-drawer-function - (lambda (name contents) contents) +(defcustom org-odt-format-drawer-function (lambda (_name contents) contents) "Function called to format a drawer in ODT code. The function must accept two parameters: @@ -659,14 +656,15 @@ The function should return the string to be exported. The default value simply returns the value of CONTENTS." :group 'org-export-odt - :version "24.4" + :version "26.1" :package-version '(Org . "8.3") :type 'function) ;;;; Headline -(defcustom org-odt-format-headline-function 'ignore +(defcustom org-odt-format-headline-function + 'org-odt-format-headline-default-function "Function to format headline text. This function will be called with 5 arguments: @@ -678,14 +676,15 @@ TAGS the tags string, separated with colons (string or nil). The function result will be used as headline text." :group 'org-export-odt - :version "24.4" - :package-version '(Org . "8.0") + :version "26.1" + :package-version '(Org . "8.3") :type 'function) ;;;; Inlinetasks -(defcustom org-odt-format-inlinetask-function 'ignore +(defcustom org-odt-format-inlinetask-function + 'org-odt-format-inlinetask-default-function "Function called to format an inlinetask in ODT code. The function must accept six parameters: @@ -698,8 +697,8 @@ The function must accept six parameters: The function should return the string to be exported." :group 'org-export-odt - :version "24.4" - :package-version '(Org . "8.0") + :version "26.1" + :package-version '(Org . "8.3") :type 'function) @@ -750,15 +749,15 @@ link's path." :value-type (regexp :tag "Path"))) (defcustom org-odt-inline-image-rules - '(("file" . "\\.\\(jpeg\\|jpg\\|png\\|gif\\)\\'")) + '(("file" . "\\.\\(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 to consider, and value is a regexp that will be matched against link's path." :group 'org-export-odt - :version "24.4" - :package-version '(Org . "8.0") + :version "26.1" + :package-version '(Org . "8.3") :type '(alist :key-type (string :tag "Type") :value-type (regexp :tag "Path"))) @@ -787,8 +786,8 @@ Use the latter option if you do not want the custom styles to be based on your current display settings. It is necessary that the styles.xml already contains needed styles for colorizing to work. -This variable is effective only if -`org-odt-fontify-srcblocks' is turned on." +This variable is effective only if `org-odt-fontify-srcblocks' is +turned on." :group 'org-export-odt :version "24.1" :type 'boolean) @@ -825,8 +824,7 @@ TABLE-STYLE-NAME is the style associated with the table through TABLE-TEMPLATE-NAME is a set of - upto 9 - automatic TABLE-CELL-STYLE-NAMEs and PARAGRAPH-STYLE-NAMEs (as defined -below) that is included in -`org-odt-content-template-file'. +below) that is included in `org-odt-content-template-file'. TABLE-CELL-STYLE-NAME := TABLE-TEMPLATE-NAME + TABLE-CELL-TYPE + \"TableCell\" @@ -977,11 +975,11 @@ See `org-odt--build-date-styles' for implementation details." (repeater-unit (org-element-property :repeater-unit timestamp))) (concat - (case repeater-type + (cl-case repeater-type (catchup "++") (restart ".+") (cumulate "+")) (when repeater-value (number-to-string repeater-value)) - (case repeater-unit + (cl-case repeater-unit (hour "h") (day "d") (week "w") (month "m") (year "y")))))) (concat @@ -1020,29 +1018,28 @@ See `org-odt--build-date-styles' for implementation details." (defun org-odt--zip-extract (archive members target) (when (atom members) (setq members (list members))) - (mapc (lambda (member) - (require 'arc-mode) - (let* ((--quote-file-name - ;; This is shamelessly stolen from `archive-zip-extract'. - (lambda (name) - (if (or (not (memq system-type '(windows-nt ms-dos))) - (and (boundp 'w32-quote-process-args) - (null w32-quote-process-args))) - (shell-quote-argument name) - name))) - (target (funcall --quote-file-name target)) - (archive (expand-file-name archive)) - (archive-zip-extract - (list "unzip" "-qq" "-o" "-d" target)) - exit-code command-output) - (setq command-output - (with-temp-buffer - (setq exit-code (archive-zip-extract archive member)) - (buffer-string))) - (unless (zerop exit-code) - (message command-output) - (error "Extraction failed")))) - members)) + (require 'arc-mode) + (dolist (member members) + (let* ((--quote-file-name + ;; This is shamelessly stolen from `archive-zip-extract'. + (lambda (name) + (if (or (not (memq system-type '(windows-nt ms-dos))) + (and (boundp 'w32-quote-process-args) + (null w32-quote-process-args))) + (shell-quote-argument name) + name))) + (target (funcall --quote-file-name target)) + (archive (expand-file-name archive)) + (archive-zip-extract + (list "unzip" "-qq" "-o" "-d" target)) + exit-code command-output) + (setq command-output + (with-temp-buffer + (setq exit-code (archive-zip-extract archive member)) + (buffer-string))) + (unless (zerop exit-code) + (message command-output) + (error "Extraction failed"))))) ;;;; Target @@ -1069,13 +1066,20 @@ See `org-odt--build-date-styles' for implementation details." ;;;; Table of Contents -(defun org-odt-begin-toc (index-title depth) +(defun org-odt--format-toc (title entries depth) + "Return a table of contents. +TITLE is the title of the table, as a string, or nil. ENTRIES is +the contents of the table, as a string. DEPTH is an integer +specifying the depth of the table." (concat - (format " - <text:table-of-content text:style-name=\"OrgIndexSection\" text:protected=\"true\" text:name=\"Table of Contents\"> - <text:table-of-content-source text:outline-level=\"%d\"> - <text:index-title-template text:style-name=\"Contents_20_Heading\">%s</text:index-title-template> -" depth index-title) + " +<text:table-of-content text:style-name=\"OrgIndexSection\" text:protected=\"true\" text:name=\"Table of Contents\">\n" + (format " <text:table-of-content-source text:outline-level=\"%d\">" depth) + (and title + (format " + <text:index-title-template text:style-name=\"Contents_20_Heading\">%s</text:index-title-template> +" + title)) (let ((levels (number-sequence 1 10))) (mapconcat @@ -1087,59 +1091,61 @@ See `org-odt--build-date-styles' for implementation details." <text:index-entry-chapter/> <text:index-entry-text/> <text:index-entry-link-end/> - </text:table-of-content-entry-template> -" level level)) levels "")) - - (format " - </text:table-of-content-source> - - <text:index-body> - <text:index-title text:style-name=\"Sect1\" text:name=\"Table of Contents1_Head\"> - <text:p text:style-name=\"Contents_20_Heading\">%s</text:p> - </text:index-title> - " index-title))) - -(defun org-odt-end-toc () - (format " - </text:index-body> - </text:table-of-content> -")) - -(defun* org-odt-format-toc-headline - (todo todo-type priority text tags - &key level section-number headline-label &allow-other-keys) - (setq text - (concat - ;; Section number. - (when section-number (concat section-number ". ")) - ;; Todo. - (when todo - (let ((style (if (member todo org-done-keywords) - "OrgDone" "OrgTodo"))) - (format "<text:span text:style-name=\"%s\">%s</text:span> " - style todo))) - (when priority - (let* ((style (format "OrgPriority-%s" priority)) - (priority (format "[#%c]" priority))) - (format "<text:span text:style-name=\"%s\">%s</text:span> " - style priority))) - ;; Title. - text - ;; Tags. - (when tags - (concat - (format " <text:span text:style-name=\"%s\">[%s]</text:span>" - "OrgTags" - (mapconcat - (lambda (tag) - (format - "<text:span text:style-name=\"%s\">%s</text:span>" - "OrgTag" tag)) tags " : ")))))) + </text:table-of-content-entry-template>\n" + level level)) levels "")) + " + </text:table-of-content-source> + <text:index-body>" + (and title + (format " + <text:index-title text:style-name=\"Sect1\" text:name=\"Table of Contents1_Head\"> + <text:p text:style-name=\"Contents_20_Heading\">%s</text:p> + </text:index-title>\n" + title)) + entries + " + </text:index-body> +</text:table-of-content>")) + +(cl-defun org-odt-format-toc-headline + (todo _todo-type priority text tags + &key _level section-number headline-label &allow-other-keys) (format "<text:a xlink:type=\"simple\" xlink:href=\"#%s\">%s</text:a>" - headline-label text)) - -(defun org-odt-toc (depth info) - (assert (wholenump depth)) + headline-label + (concat + ;; Section number. + (and section-number (concat section-number ". ")) + ;; Todo. + (when todo + (let ((style (if (member todo org-done-keywords) + "OrgDone" "OrgTodo"))) + (format "<text:span text:style-name=\"%s\">%s</text:span> " + style todo))) + (when priority + (let* ((style (format "OrgPriority-%s" priority)) + (priority (format "[#%c]" priority))) + (format "<text:span text:style-name=\"%s\">%s</text:span> " + style priority))) + ;; Title. + text + ;; Tags. + (when tags + (concat + (format " <text:span text:style-name=\"%s\">[%s]</text:span>" + "OrgTags" + (mapconcat + (lambda (tag) + (format + "<text:span text:style-name=\"%s\">%s</text:span>" + "OrgTag" tag)) tags " : "))))))) + +(defun org-odt-toc (depth info &optional scope) + "Build a table of contents. +DEPTH is an integer specifying the depth of the table. INFO is +a plist containing current export properties. Optional argument +SCOPE, when non-nil, defines the scope of the table. Return the +table of contents as a string, or nil." + (cl-assert (wholenump depth)) ;; When a headline is marked as a radio target, as in the example below: ;; ;; ** <<<Some Heading>>> @@ -1150,24 +1156,14 @@ See `org-odt--build-date-styles' for implementation details." ;; /TOC/, as otherwise there will be duplicated anchors one in TOC ;; and one in the document body. ;; - ;; FIXME-1: Currently exported headings are memoized. `org-export.el' - ;; doesn't provide a way to disable memoization. So this doesn't - ;; work. - ;; - ;; FIXME-2: Are there any other objects that need to be suppressed - ;; within TOC? - (let* ((title (org-export-translate "Table of Contents" :utf-8 info)) - (headlines (org-export-collect-headlines - info (and (wholenump depth) depth))) - (backend (org-export-create-backend - :parent (org-export-backend-name - (plist-get info :back-end)) - :transcoders (mapcar - (lambda (type) (cons type (lambda (d c i) c))) - (list 'radio-target))))) + ;; Likewise, links, footnote references and regular targets are also + ;; suppressed. + (let* ((headlines (org-export-collect-headlines info depth scope)) + (backend (org-export-toc-entry-backend + (org-export-backend-name (plist-get info :back-end))))) (when headlines - (concat - (org-odt-begin-toc title depth) + (org-odt--format-toc + (and (not scope) (org-export-translate "Table of Contents" :utf-8 info)) (mapconcat (lambda (headline) (let* ((entry (org-odt-format-headline--wrap @@ -1177,7 +1173,7 @@ See `org-odt--build-date-styles' for implementation details." (format "\n<text:p text:style-name=\"%s\">%s</text:p>" style entry))) headlines "\n") - (org-odt-end-toc))))) + depth)))) ;;;; Document styles @@ -1192,7 +1188,7 @@ Use `org-odt-object-counters' to generate an automatic OBJECT-NAME and STYLE-NAME. If OBJECT-PROPS is non-nil, add a new entry in `org-odt-automatic-styles'. Return (OBJECT-NAME . STYLE-NAME)." - (assert (stringp object-type)) + (cl-assert (stringp object-type)) (let* ((object (intern object-type)) (seqvar object) (seqno (1+ (or (plist-get org-odt-object-counters seqvar) 0))) @@ -1214,7 +1210,7 @@ new entry in `org-odt-automatic-styles'. Return (OBJECT-NAME (let ((checkbox (org-element-property :checkbox item))) (if (not checkbox) "" (format "<text:span text:style-name=\"%s\">%s</text:span>" - "OrgCode" (case checkbox + "OrgCode" (cl-case checkbox (on "[✓] ") ; CHECK MARK (off "[ ] ") (trans "[-] ")))))) @@ -1258,31 +1254,30 @@ new entry in `org-odt-automatic-styles'. Return (OBJECT-NAME (case-fold-search nil) (re (mapconcat 'identity (mapcar 'car fmt-alist) "\\|")) match rpl (start 0) (filler-beg 0) filler-end filler output) - (mapc - (lambda (pair) - (setq fmt (replace-regexp-in-string (car pair) (cdr pair) fmt t t))) - '(("\\(?:%[[:digit:]]*N\\)" . "") ; strip ns, us and ns - ("%C" . "Y") ; replace century with year - ("%D" . "%m/%d/%y") - ("%G" . "Y") ; year corresponding to iso week - ("%I" . "%H") ; hour on a 12-hour clock - ("%R" . "%H:%M") - ("%T" . "%H:%M:%S") - ("%U\\|%W" . "%V") ; week no. starting on Sun./Mon. - ("%Z" . "") ; time zone name - ("%c" . "%Y-%M-%d %a %H:%M" ) ; locale's date and time format - ("%g" . "%y") - ("%X" . "%x" ) ; locale's pref. time format - ("%j" . "") ; day of the year - ("%l" . "%k") ; like %I blank-padded - ("%s" . "") ; no. of secs since 1970-01-01 00:00:00 +0000 - ("%n" . "<text:line-break/>") - ("%r" . "%I:%M:%S %p") - ("%t" . "<text:tab/>") - ("%u\\|%w" . "") ; numeric day of week - Mon (1-7), Sun(0-6) - ("%x" . "%Y-%M-%d %a") ; locale's pref. time format - ("%z" . "") ; time zone in numeric form - )) + (dolist (pair + '(("\\(?:%[[:digit:]]*N\\)" . "") ; strip ns, us and ns + ("%C" . "Y") ; replace century with year + ("%D" . "%m/%d/%y") + ("%G" . "Y") ; year corresponding to iso week + ("%I" . "%H") ; hour on a 12-hour clock + ("%R" . "%H:%M") + ("%T" . "%H:%M:%S") + ("%U\\|%W" . "%V") ; week no. starting on Sun./Mon. + ("%Z" . "") ; time zone name + ("%c" . "%Y-%M-%d %a %H:%M" ) ; locale's date and time format + ("%g" . "%y") + ("%X" . "%x" ) ; locale's pref. time format + ("%j" . "") ; day of the year + ("%l" . "%k") ; like %I blank-padded + ("%s" . "") ; no. of secs since 1970-01-01 00:00:00 +0000 + ("%n" . "<text:line-break/>") + ("%r" . "%I:%M:%S %p") + ("%t" . "<text:tab/>") + ("%u\\|%w" . "") ; numeric day of week - Mon (1-7), Sun(0-6) + ("%x" . "%Y-%M-%d %a") ; locale's pref. time format + ("%z" . "") ; time zone in numeric form + )) + (setq fmt (replace-regexp-in-string (car pair) (cdr pair) fmt t t))) (while (string-match re fmt start) (setq match (match-string 0 fmt)) (setq rpl (assoc-default match fmt-alist)) @@ -1312,11 +1307,11 @@ CONTENTS is the transcoded contents string. RAW-DATA is the original parsed data. INFO is a plist holding export options." ;; Write meta file. (let ((title (org-export-data (plist-get info :title) info)) + (subtitle (org-export-data (plist-get info :subtitle) info)) (author (let ((author (plist-get info :author))) (if (not author) "" (org-export-data author info)))) - (email (plist-get info :email)) - (keywords (plist-get info :keywords)) - (description (plist-get info :description))) + (keywords (or (plist-get info :keywords) "")) + (description (or (plist-get info :description) ""))) (write-region (concat "<?xml version=\"1.0\" encoding=\"UTF-8\"?> @@ -1345,12 +1340,14 @@ original parsed data. INFO is a plist holding export options." (format "<meta:creation-date>%s</meta:creation-date>\n" iso-date))))) (format "<meta:generator>%s</meta:generator>\n" - (let ((creator-info (plist-get info :with-creator))) - (if (or (not creator-info) (eq creator-info 'comment)) "" - (plist-get info :creator)))) + (plist-get info :creator)) (format "<meta:keyword>%s</meta:keyword>\n" keywords) (format "<dc:subject>%s</dc:subject>\n" description) (format "<dc:title>%s</dc:title>\n" title) + (when (org-string-nw-p subtitle) + (format + "<meta:user-defined meta:name=\"subtitle\">%s</meta:user-defined>\n" + subtitle)) "\n" " </office:meta>\n" "</office:document-meta>") nil (concat org-odt-zip-dir "meta.xml")) @@ -1361,11 +1358,12 @@ original parsed data. INFO is a plist holding export options." ;; Copy styles.xml. Also dump htmlfontify styles, if there is any. ;; Write styles file. (let* ((styles-file (plist-get info :odt-styles-file)) - (styles-file (and styles-file (read (org-trim styles-file)))) + (styles-file (and (org-string-nw-p styles-file) + (read (org-trim styles-file)))) ;; Non-availability of styles.xml is not a critical ;; error. For now, throw an error. (styles-file (or styles-file - org-odt-styles-file + (plist-get info :odt-styles-file) (expand-file-name "OrgOdtStyles.xml" org-odt-styles-dir) (error "org-odt: Missing styles file?")))) @@ -1374,13 +1372,11 @@ original parsed data. INFO is a plist holding export options." (let ((archive (nth 0 styles-file)) (members (nth 1 styles-file))) (org-odt--zip-extract archive members org-odt-zip-dir) - (mapc - (lambda (member) - (when (org-file-image-p member) - (let* ((image-type (file-name-extension member)) - (media-type (format "image/%s" image-type))) - (org-odt-create-manifest-file-entry media-type member)))) - members))) + (dolist (member members) + (when (org-file-image-p member) + (let* ((image-type (file-name-extension member)) + (media-type (format "image/%s" image-type))) + (org-odt-create-manifest-file-entry media-type member)))))) ((and (stringp styles-file) (file-exists-p styles-file)) (let ((styles-file-type (file-name-extension styles-file))) (cond @@ -1390,7 +1386,7 @@ original parsed data. INFO is a plist holding export options." (org-odt--zip-extract styles-file "styles.xml" org-odt-zip-dir))))) (t (error "Invalid specification of styles.xml file: %S" - org-odt-styles-file))) + (plist-get info :odt-styles-file)))) ;; create a manifest entry for styles.xml (org-odt-create-manifest-file-entry "text/xml" "styles.xml") @@ -1423,7 +1419,7 @@ original parsed data. INFO is a plist holding export options." ;; currently the zip command zips up the entire temp directory so ;; that any auto-generated files created under the hood ends up in ;; the resulting odt file. - (set (make-local-variable 'backup-inhibited) t) + (setq-local backup-inhibited t) ;; Outline numbering is retained only upto LEVEL. ;; To disable outline numbering pass a LEVEL of 0. @@ -1451,7 +1447,7 @@ original parsed data. INFO is a plist holding export options." '("%Y-%M-%d %a" . "%Y-%M-%d %a %H:%M")))) (with-temp-buffer (insert-file-contents - (or org-odt-content-template-file + (or (plist-get info :odt-content-template-file) (expand-file-name "OrgOdtContentTemplate.xml" org-odt-styles-dir))) ;; Write automatic styles. @@ -1460,16 +1456,16 @@ original parsed data. INFO is a plist holding export options." (re-search-forward " </office:automatic-styles>" nil t) (goto-char (match-beginning 0)) ;; - Dump automatic table styles. - (loop for (style-name props) in - (plist-get org-odt-automatic-styles 'Table) do - (when (setq props (or (plist-get props :rel-width) "96")) - (insert (format org-odt-table-style-format style-name props)))) + (cl-loop for (style-name props) in + (plist-get org-odt-automatic-styles 'Table) do + (when (setq props (or (plist-get props :rel-width) "96")) + (insert (format org-odt-table-style-format style-name props)))) ;; - Dump date-styles. - (when org-odt-use-date-fields + (when (plist-get info :odt-use-date-fields) (insert (org-odt--build-date-styles (car custom-time-fmts) - "OrgDate1") + "OrgDate1") (org-odt--build-date-styles (cdr custom-time-fmts) - "OrgDate2"))) + "OrgDate2"))) ;; Update display level. ;; - Remove existing sequence decls. Also position the cursor. (goto-char (point-min)) @@ -1484,7 +1480,8 @@ original parsed data. INFO is a plist holding export options." (lambda (x) (format "<text:sequence-decl text:display-outline-level=\"%d\" text:name=\"%s\"/>" - org-odt-display-outline-level (nth 1 x))) + (plist-get info :odt-display-outline-level) + (nth 1 x))) org-odt-category-map-alist "\n"))) ;; Position the cursor to document body. (goto-char (point-min)) @@ -1493,7 +1490,10 @@ original parsed data. INFO is a plist holding export options." ;; Preamble - Title, Author, Date etc. (insert - (let* ((title (org-export-data (plist-get info :title) info)) + (let* ((title (and (plist-get info :with-title) + (org-export-data (plist-get info :title) info))) + (subtitle (when title + (org-export-data (plist-get info :subtitle) info))) (author (and (plist-get info :with-author) (let ((auth (plist-get info :author))) (and auth (org-export-data auth info))))) @@ -1505,10 +1505,20 @@ original parsed data. INFO is a plist holding export options." ;; Title. (when (org-string-nw-p title) (concat - (format "\n<text:p text:style-name=\"%s\">%s</text:p>" + (format "\n<text:p text:style-name=\"%s\">%s</text:p>\n" "OrgTitle" (format "\n<text:title>%s</text:title>" title)) ;; Separator. - "\n<text:p text:style-name=\"OrgTitle\"/>")) + "\n<text:p text:style-name=\"OrgTitle\"/>\n" + ;; Subtitle. + (when (org-string-nw-p subtitle) + (concat + (format "<text:p text:style-name=\"OrgSubtitle\">\n%s\n</text:p>\n" + (concat + "<text:user-defined style:data-style-name=\"N0\" text:name=\"subtitle\">\n" + subtitle + "</text:user-defined>\n")) + ;; Separator. + "<text:p text:style-name=\"OrgSubtitle\"/>\n")))) (cond ((and author (not email)) ;; Author only. @@ -1537,14 +1547,15 @@ original parsed data. INFO is a plist holding export options." (timestamp (and (not (cdr date)) (eq (org-element-type (car date)) 'timestamp) (car date)))) - (concat - (format "\n<text:p text:style-name=\"%s\">%s</text:p>" - "OrgSubtitle" - (if (and org-odt-use-date-fields timestamp) - (org-odt--format-timestamp (car date)) - (org-export-data (plist-get info :date) info))) - ;; Separator - "<text:p text:style-name=\"OrgSubtitle\"/>")))))) + (when date + (concat + (format "\n<text:p text:style-name=\"%s\">%s</text:p>" + "OrgSubtitle" + (if (and (plist-get info :odt-use-date-fields) timestamp) + (org-odt--format-timestamp (car date)) + (org-export-data date info))) + ;; Separator + "<text:p text:style-name=\"OrgSubtitle\"/>"))))))) ;; Table of Contents (let* ((with-toc (plist-get info :with-toc)) (depth (and with-toc (if (wholenump with-toc) @@ -1562,7 +1573,7 @@ original parsed data. INFO is a plist holding export options." ;;;; Bold -(defun org-odt-bold (bold contents info) +(defun org-odt-bold (_bold contents _info) "Transcode BOLD from Org to ODT. CONTENTS is the text with bold markup. INFO is a plist holding contextual information." @@ -1572,7 +1583,7 @@ contextual information." ;;;; Center Block -(defun org-odt-center-block (center-block contents info) +(defun org-odt-center-block (_center-block contents _info) "Transcode a CENTER-BLOCK element from Org to ODT. CONTENTS holds the contents of the center block. INFO is a plist holding contextual information." @@ -1599,7 +1610,7 @@ channel." ;;;; Code -(defun org-odt-code (code contents info) +(defun org-odt-code (code _contents _info) "Transcode a CODE object from Org to ODT. CONTENTS is nil. INFO is a plist used as a communication channel." @@ -1608,16 +1619,6 @@ channel." (org-element-property :value code)))) -;;;; Comment - -;; Comments are ignored. - - -;;;; Comment Block - -;; Comment Blocks are ignored. - - ;;;; Drawer (defun org-odt-drawer (drawer contents info) @@ -1625,14 +1626,14 @@ channel." CONTENTS holds the contents of the block. INFO is a plist holding contextual information." (let* ((name (org-element-property :drawer-name drawer)) - (output (funcall org-odt-format-drawer-function + (output (funcall (plist-get info :odt-format-drawer-function) name contents))) output)) ;;;; Dynamic Block -(defun org-odt-dynamic-block (dynamic-block contents info) +(defun org-odt-dynamic-block (_dynamic-block contents _info) "Transcode a DYNAMIC-BLOCK element from Org to ODT. CONTENTS holds the contents of the block. INFO is a plist holding contextual information. See `org-export-data'." @@ -1641,7 +1642,7 @@ holding contextual information. See `org-export-data'." ;;;; Entity -(defun org-odt-entity (entity contents info) +(defun org-odt-entity (entity _contents _info) "Transcode an ENTITY object from Org to ODT. CONTENTS are the definition itself. INFO is a plist holding contextual information." @@ -1650,7 +1651,7 @@ contextual information." ;;;; Example Block -(defun org-odt-example-block (example-block contents info) +(defun org-odt-example-block (example-block _contents info) "Transcode a EXAMPLE-BLOCK element from Org to ODT. CONTENTS is nil. INFO is a plist holding contextual information." (org-odt-format-code example-block info)) @@ -1658,7 +1659,7 @@ CONTENTS is nil. INFO is a plist holding contextual information." ;;;; Export Snippet -(defun org-odt-export-snippet (export-snippet contents info) +(defun org-odt-export-snippet (export-snippet _contents _info) "Transcode a EXPORT-SNIPPET object from Org to ODT. CONTENTS is nil. INFO is a plist holding contextual information." (when (eq (org-export-snippet-backend export-snippet) 'odt) @@ -1667,7 +1668,7 @@ CONTENTS is nil. INFO is a plist holding contextual information." ;;;; Export Block -(defun org-odt-export-block (export-block contents info) +(defun org-odt-export-block (export-block _contents _info) "Transcode a EXPORT-BLOCK element from Org to ODT. CONTENTS is nil. INFO is a plist holding contextual information." (when (string= (org-element-property :type export-block) "ODT") @@ -1676,10 +1677,10 @@ CONTENTS is nil. INFO is a plist holding contextual information." ;;;; Fixed Width -(defun org-odt-fixed-width (fixed-width contents info) +(defun org-odt-fixed-width (fixed-width _contents info) "Transcode a FIXED-WIDTH element from Org to ODT. CONTENTS is nil. INFO is a plist holding contextual information." - (org-odt-do-format-code (org-element-property :value fixed-width))) + (org-odt-do-format-code (org-element-property :value fixed-width) info)) ;;;; Footnote Definition @@ -1689,34 +1690,31 @@ CONTENTS is nil. INFO is a plist holding contextual information." ;;;; Footnote Reference -(defun org-odt-footnote-reference (footnote-reference contents info) +(defun org-odt-footnote-reference (footnote-reference _contents info) "Transcode a FOOTNOTE-REFERENCE element from Org to ODT. CONTENTS is nil. INFO is a plist holding contextual information." (let ((--format-footnote-definition - (function - (lambda (n def) - (setq n (format "%d" n)) - (let ((id (concat "fn" n)) - (note-class "footnote") - (par-style "Footnote")) - (format - "<text:note text:id=\"%s\" text:note-class=\"%s\">%s</text:note>" - id note-class - (concat - (format "<text:note-citation>%s</text:note-citation>" n) - (format "<text:note-body>%s</text:note-body>" def))))))) + (lambda (n def) + (setq n (format "%d" n)) + (let ((id (concat "fn" n)) + (note-class "footnote")) + (format + "<text:note text:id=\"%s\" text:note-class=\"%s\">%s</text:note>" + id note-class + (concat + (format "<text:note-citation>%s</text:note-citation>" n) + (format "<text:note-body>%s</text:note-body>" def)))))) (--format-footnote-reference - (function - (lambda (n) - (setq n (format "%d" n)) - (let ((note-class "footnote") - (ref-format "text") - (ref-name (concat "fn" n))) - (format - "<text:span text:style-name=\"%s\">%s</text:span>" - "OrgSuperscript" - (format "<text:note-ref text:note-class=\"%s\" text:reference-format=\"%s\" text:ref-name=\"%s\">%s</text:note-ref>" - note-class ref-format ref-name n))))))) + (lambda (n) + (setq n (format "%d" n)) + (let ((note-class "footnote") + (ref-format "text") + (ref-name (concat "fn" n))) + (format + "<text:span text:style-name=\"%s\">%s</text:span>" + "OrgSuperscript" + (format "<text:note-ref text:note-class=\"%s\" text:reference-format=\"%s\" text:ref-name=\"%s\">%s</text:note-ref>" + note-class ref-format ref-name n)))))) (concat ;; Insert separator between two footnotes in a row. (let ((prev (org-export-get-previous-element footnote-reference info))) @@ -1724,12 +1722,11 @@ CONTENTS is nil. INFO is a plist holding contextual information." (format "<text:span text:style-name=\"%s\">%s</text:span>" "OrgSuperscript" ","))) ;; Transcode footnote reference. - (let ((n (org-export-get-footnote-number footnote-reference info))) + (let ((n (org-export-get-footnote-number footnote-reference info nil t))) (cond - ((not (org-export-footnote-first-reference-p footnote-reference info)) + ((not + (org-export-footnote-first-reference-p footnote-reference info nil t)) (funcall --format-footnote-reference n)) - ;; Inline definitions are secondary strings. - ;; Non-inline footnotes definitions are full Org data. (t (let* ((raw (org-export-get-footnote-definition footnote-reference info)) @@ -1747,41 +1744,19 @@ CONTENTS is nil. INFO is a plist holding contextual information." "OrgFootnoteCenter" "OrgFootnoteQuotations"))))) info)))) - (if (eq (org-element-type raw) 'org-data) def - (format "\n<text:p text:style-name=\"%s\">%s</text:p>" - "Footnote" def))))) + ;; Inline definitions are secondary strings. We + ;; need to wrap them within a paragraph. + (if (eq (org-element-class (car (org-element-contents raw))) + 'element) + def + (format + "\n<text:p text:style-name=\"Footnote\">%s</text:p>" + def))))) (funcall --format-footnote-definition n def)))))))) ;;;; Headline -(defun* org-odt-format-headline - (todo todo-type priority text tags - &key level section-number headline-label &allow-other-keys) - (concat - ;; Todo. - (when todo - (let ((style (if (member todo org-done-keywords) "OrgDone" "OrgTodo"))) - (format "<text:span text:style-name=\"%s\">%s</text:span> " - style todo))) - (when priority - (let* ((style (format "OrgPriority-%s" priority)) - (priority (format "[#%c]" priority))) - (format "<text:span text:style-name=\"%s\">%s</text:span> " - style priority))) - ;; Title. - text - ;; Tags. - (when tags - (concat - "<text:tab/>" - (format "<text:span text:style-name=\"%s\">[%s]</text:span>" - "OrgTags" (mapconcat - (lambda (tag) - (format - "<text:span text:style-name=\"%s\">%s</text:span>" - "OrgTag" tag)) tags " : ")))))) - (defun org-odt-format-headline--wrap (headline backend info &optional format-function &rest extra-keys) @@ -1804,20 +1779,19 @@ INFO is a plist holding contextual information." (org-element-property :title headline) backend info)) (tags (and (plist-get info :with-tags) (org-export-get-tags headline info))) - (headline-label (concat "sec-" (mapconcat 'number-to-string - headline-number "-"))) - (format-function (cond - ((functionp format-function) format-function) - ((not (eq org-odt-format-headline-function 'ignore)) - (function* - (lambda (todo todo-type priority text tags - &allow-other-keys) - (funcall org-odt-format-headline-function - todo todo-type priority text tags)))) - (t 'org-odt-format-headline)))) + (headline-label (org-export-get-reference headline info)) + (format-function + (if (functionp format-function) format-function + (cl-function + (lambda (todo todo-type priority text tags + &key _level _section-number _headline-label + &allow-other-keys) + (funcall (plist-get info :odt-format-headline-function) + todo todo-type priority text tags)))))) (apply format-function - todo todo-type priority text tags - :headline-label headline-label :level level + todo todo-type priority text tags + :headline-label headline-label + :level level :section-number section-number extra-keys))) (defun org-odt-headline (headline contents info) @@ -1826,26 +1800,16 @@ CONTENTS holds the contents of the headline. INFO is a plist holding contextual information." ;; Case 1: This is a footnote section: ignore it. (unless (org-element-property :footnote-section-p headline) - (let* ((text (org-export-data (org-element-property :title headline) info)) - ;; Create the headline text. - (full-text (org-odt-format-headline--wrap headline nil info)) + (let* ((full-text (org-odt-format-headline--wrap headline nil info)) ;; Get level relative to current parsed data. (level (org-export-get-relative-level headline info)) + (numbered (org-export-numbered-headline-p headline info)) ;; Get canonical label for the headline. - (id (concat "sec-" (mapconcat 'number-to-string - (org-export-get-headline-number - headline info) "-"))) - ;; Get user-specified labels for the headline. - (extra-ids (list (org-element-property :CUSTOM_ID headline) - (org-element-property :ID headline))) + (id (org-export-get-reference headline info)) ;; Extra targets. (extra-targets - (mapconcat (lambda (x) - (when x - (let ((x (if (org-uuidgen-p x) (concat "ID-" x) x))) - (org-odt--target - "" (org-export-solidify-link-text x))))) - extra-ids "")) + (let ((id (org-element-property :ID headline))) + (if id (org-odt--target "" (concat "ID-" id)) ""))) ;; Title. (anchored-title (org-odt--target full-text id))) (cond @@ -1858,8 +1822,7 @@ holding contextual information." (and (org-export-first-sibling-p headline info) (format "\n<text:list text:style-name=\"%s\" %s>" ;; Choose style based on list type. - (if (org-export-numbered-headline-p headline info) - "OrgNumberedList" "OrgBulletedList") + (if numbered "OrgNumberedList" "OrgBulletedList") ;; If top-level list, re-start numbering. Otherwise, ;; continue numbering. (format "text:continue-numbering=\"%s\"" @@ -1886,16 +1849,45 @@ holding contextual information." (t (concat (format - "\n<text:h text:style-name=\"%s\" text:outline-level=\"%s\">%s</text:h>" - (format "Heading_20_%s" level) + "\n<text:h text:style-name=\"%s\" text:outline-level=\"%s\" text:is-list-header=\"%s\">%s</text:h>" + (format "Heading_20_%s%s" + level (if numbered "" "_unnumbered")) level + (if numbered "false" "true") (concat extra-targets anchored-title)) contents)))))) +(defun org-odt-format-headline-default-function + (todo todo-type priority text tags) + "Default format function for a headline. +See `org-odt-format-headline-function' for details." + (concat + ;; Todo. + (when todo + (let ((style (if (eq todo-type 'done) "OrgDone" "OrgTodo"))) + (format "<text:span text:style-name=\"%s\">%s</text:span> " style todo))) + (when priority + (let* ((style (format "OrgPriority-%c" priority)) + (priority (format "[#%c]" priority))) + (format "<text:span text:style-name=\"%s\">%s</text:span> " + style priority))) + ;; Title. + text + ;; Tags. + (when tags + (concat + "<text:tab/>" + (format "<text:span text:style-name=\"%s\">[%s]</text:span>" + "OrgTags" (mapconcat + (lambda (tag) + (format + "<text:span text:style-name=\"%s\">%s</text:span>" + "OrgTag" tag)) tags " : ")))))) + ;;;; Horizontal Rule -(defun org-odt-horizontal-rule (horizontal-rule contents info) +(defun org-odt-horizontal-rule (_horizontal-rule _contents _info) "Transcode an HORIZONTAL-RULE object from Org to ODT. CONTENTS is nil. INFO is a plist holding contextual information." (format "\n<text:p text:style-name=\"%s\">%s</text:p>" @@ -1913,18 +1905,15 @@ CONTENTS is nil. INFO is a plist holding contextual information." "Return a character not used in string S. This is used to choose a separator for constructs like \\verb." (let ((ll "~,./?;':\"|!@#%^&-_=+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ<>()[]{}")) - (loop for c across ll - when (not (string-match (regexp-quote (char-to-string c)) s)) - return (char-to-string c)))) + (cl-loop for c across ll + when (not (string-match (regexp-quote (char-to-string c)) s)) + return (char-to-string c)))) -(defun org-odt-inline-src-block (inline-src-block contents info) +(defun org-odt-inline-src-block (_inline-src-block _contents _info) "Transcode an INLINE-SRC-BLOCK element from Org to ODT. CONTENTS holds the contents of the item. INFO is a plist holding contextual information." - (let* ((org-lang (org-element-property :language inline-src-block)) - (code (org-element-property :value inline-src-block)) - (separator (org-odt--find-verb-separator code))) - (error "FIXME"))) + (error "FIXME")) ;;;; Inlinetask @@ -1933,33 +1922,37 @@ contextual information." "Transcode an INLINETASK element from Org to ODT. CONTENTS holds the contents of the block. INFO is a plist holding contextual information." - (cond - ;; If `org-odt-format-inlinetask-function' is not 'ignore, call it - ;; with appropriate arguments. - ((not (eq org-odt-format-inlinetask-function 'ignore)) - (let ((format-function - (function* - (lambda (todo todo-type priority text tags - &key contents &allow-other-keys) - (funcall org-odt-format-inlinetask-function - todo todo-type priority text tags contents))))) - (org-odt-format-headline--wrap - inlinetask nil info format-function :contents contents))) - ;; Otherwise, use a default template. - (t - (format "\n<text:p text:style-name=\"%s\">%s</text:p>" - "Text_20_body" - (org-odt--textbox - (concat - (format "\n<text:p text:style-name=\"%s\">%s</text:p>" - "OrgInlineTaskHeading" - (org-odt-format-headline--wrap inlinetask nil info)) - contents) - nil nil "OrgInlineTaskFrame" " style:rel-width=\"100%\""))))) + (let* ((todo + (and (plist-get info :with-todo-keywords) + (let ((todo (org-element-property :todo-keyword inlinetask))) + (and todo (org-export-data todo info))))) + (todo-type (and todo (org-element-property :todo-type inlinetask))) + (priority (and (plist-get info :with-priority) + (org-element-property :priority inlinetask))) + (text (org-export-data (org-element-property :title inlinetask) info)) + (tags (and (plist-get info :with-tags) + (org-export-get-tags inlinetask info)))) + (funcall (plist-get info :odt-format-inlinetask-function) + todo todo-type priority text tags contents))) + +(defun org-odt-format-inlinetask-default-function + (todo todo-type priority name tags contents) + "Default format function for a inlinetasks. +See `org-odt-format-inlinetask-function' for details." + (format "\n<text:p text:style-name=\"%s\">%s</text:p>" + "Text_20_body" + (org-odt--textbox + (concat + (format "\n<text:p text:style-name=\"%s\">%s</text:p>" + "OrgInlineTaskHeading" + (org-odt-format-headline-default-function + todo todo-type priority name tags)) + contents) + nil nil "OrgInlineTaskFrame" " style:rel-width=\"100%\""))) ;;;; Italic -(defun org-odt-italic (italic contents info) +(defun org-odt-italic (_italic contents _info) "Transcode ITALIC from Org to ODT. CONTENTS is the text with italic markup. INFO is a plist holding contextual information." @@ -1974,32 +1967,21 @@ contextual information." CONTENTS holds the contents of the item. INFO is a plist holding contextual information." (let* ((plain-list (org-export-get-parent item)) - (type (org-element-property :type plain-list)) - (counter (org-element-property :counter item)) - (tag (let ((tag (org-element-property :tag item))) - (and tag - (concat (org-odt--checkbox item) - (org-export-data tag info)))))) - (case type - ((ordered unordered descriptive-1 descriptive-2) - (format "\n<text:list-item>\n%s\n%s" - contents - (let* ((--element-has-a-table-p - (function - (lambda (element info) - (loop for el in (org-element-contents element) - thereis (eq (org-element-type el) 'table)))))) - (cond - ((funcall --element-has-a-table-p item info) - "</text:list-header>") - (t "</text:list-item>"))))) - (t (error "Unknown list type: %S" type))))) + (type (org-element-property :type plain-list))) + (unless (memq type '(ordered unordered descriptive-1 descriptive-2)) + (error "Unknown list type: %S" type)) + (format "\n<text:list-item>\n%s\n%s" + contents + (if (org-element-map item 'table #'identity info 'first-match) + "</text:list-header>" + "</text:list-item>")))) ;;;; Keyword -(defun org-odt-keyword (keyword contents info) +(defun org-odt-keyword (keyword _contents info) "Transcode a KEYWORD element from Org to ODT. -CONTENTS is nil. INFO is a plist holding contextual information." +CONTENTS is nil. INFO is a plist holding contextual +information." (let ((key (org-element-property :key keyword)) (value (org-element-property :value keyword))) (cond @@ -2008,14 +1990,15 @@ CONTENTS is nil. INFO is a plist holding contextual information." ;; FIXME (ignore)) ((string= key "TOC") - (let ((value (downcase value))) + (let ((case-fold-search t)) (cond - ((string-match "\\<headlines\\>" value) - (let ((depth (or (and (string-match "[0-9]+" value) + ((string-match-p "\\<headlines\\>" value) + (let ((depth (or (and (string-match "\\<[0-9]+\\>" value) (string-to-number (match-string 0 value))) - (plist-get info :with-toc)))) - (when (wholenump depth) (org-odt-toc depth info)))) - ((member value '("tables" "figures" "listings")) + (plist-get info :headline-levels))) + (localp (string-match-p "\\<local\\>" value))) + (org-odt-toc depth info (and localp keyword)))) + ((string-match-p "tables\\|figures\\|listings" value) ;; FIXME (ignore)))))))) @@ -2031,34 +2014,33 @@ CONTENTS is nil. INFO is a plist holding contextual information." ;; (unless (> (length ad-return-value) 0) ;; (setq ad-return-value (org-odt--encode-plain-text (ad-get-arg 0))))) -(defun org-odt-latex-environment (latex-environment contents info) +(defun org-odt-latex-environment (latex-environment _contents info) "Transcode a LATEX-ENVIRONMENT element from Org to ODT. CONTENTS is nil. INFO is a plist holding contextual information." (let* ((latex-frag (org-remove-indentation (org-element-property :value latex-environment)))) - (org-odt-do-format-code latex-frag))) + (org-odt-do-format-code latex-frag info))) ;;;; Latex Fragment ;; (when latex-frag ; FIXME -;; (setq href (org-propertize href :title "LaTeX Fragment" +;; (setq href (propertize href :title "LaTeX Fragment" ;; :description latex-frag))) ;; handle verbatim ;; provide descriptions -(defun org-odt-latex-fragment (latex-fragment contents info) +(defun org-odt-latex-fragment (latex-fragment _contents _info) "Transcode a LATEX-FRAGMENT object from Org to ODT. CONTENTS is nil. INFO is a plist holding contextual information." - (let* ((latex-frag (org-element-property :value latex-fragment)) - (processing-type (plist-get info :with-latex))) + (let ((latex-frag (org-element-property :value latex-fragment))) (format "<text:span text:style-name=\"%s\">%s</text:span>" "OrgCode" (org-odt--encode-plain-text latex-frag t)))) ;;;; Line Break -(defun org-odt-line-break (line-break contents info) +(defun org-odt-line-break (_line-break _contents _info) "Transcode a LINE-BREAK object from Org to ODT. CONTENTS is nil. INFO is a plist holding contextual information." "<text:line-break/>") @@ -2069,29 +2051,29 @@ CONTENTS is nil. INFO is a plist holding contextual information." ;;;; Links :: Label references (defun org-odt--enumerate (element info &optional predicate n) - (when predicate (assert (funcall predicate element info))) + (when predicate (cl-assert (funcall predicate element info))) (let* ((--numbered-parent-headline-at-<=-n - (function - (lambda (element n info) - (loop for x in (org-export-get-genealogy element) - thereis (and (eq (org-element-type x) 'headline) - (<= (org-export-get-relative-level x info) n) - (org-export-numbered-headline-p x info) - x))))) + (lambda (element n info) + (cl-loop for x in (org-element-lineage element) + thereis (and (eq (org-element-type x) 'headline) + (<= (org-export-get-relative-level x info) n) + (org-export-numbered-headline-p x info) + x)))) (--enumerate - (function - (lambda (element scope info &optional predicate) - (let ((counter 0)) - (org-element-map (or scope (plist-get info :parse-tree)) - (org-element-type element) - (lambda (el) - (and (or (not predicate) (funcall predicate el info)) - (incf counter) - (eq element el) - counter)) - info 'first-match))))) + (lambda (element scope info &optional predicate) + (let ((counter 0)) + (org-element-map (or scope (plist-get info :parse-tree)) + (org-element-type element) + (lambda (el) + (and (or (not predicate) (funcall predicate el info)) + (cl-incf counter) + (eq element el) + counter)) + info 'first-match)))) (scope (funcall --numbered-parent-headline-at-<=-n - element (or n org-odt-display-outline-level) info)) + element + (or n (plist-get info :odt-display-outline-level)) + info)) (ordinal (funcall --enumerate element scope info predicate)) (tag (concat @@ -2116,20 +2098,22 @@ the generated string. Return value is a string if OP is set to `reference' or a cons cell like CAPTION . SHORT-CAPTION) where CAPTION and SHORT-CAPTION are strings." - (assert (memq (org-element-type element) '(link table src-block paragraph))) - (let* ((caption-from - (case (org-element-type element) + (cl-assert (memq (org-element-type element) '(link table src-block paragraph))) + (let* ((element-or-parent + (cl-case (org-element-type element) (link (org-export-get-parent-element element)) (t element))) ;; Get label and caption. - (label (org-element-property :name caption-from)) - (caption (org-export-get-caption caption-from)) - (caption (and caption (org-export-data caption info))) + (label (and (or (org-element-property :name element) + (org-element-property :name element-or-parent)) + (org-export-get-reference element-or-parent info))) + (caption (let ((c (org-export-get-caption element-or-parent))) + (and c (org-export-data c info)))) ;; FIXME: We don't use short-caption for now (short-caption nil)) (when (or label caption) (let* ((default-category - (case (org-element-type element) + (cl-case (org-element-type element) (table "__Table__") (src-block "__Listing__") ((link paragraph) @@ -2145,19 +2129,17 @@ SHORT-CAPTION are strings." (t (error "Don't know how to format label for element type: %s" (org-element-type element))))) seqno) - (assert default-category) - (destructuring-bind (counter label-style category predicate) - (assoc-default default-category org-odt-category-map-alist) + (cl-assert default-category) + (pcase-let + ((`(,counter ,label-style ,category ,predicate) + (assoc-default default-category org-odt-category-map-alist))) ;; Compute sequence number of the element. (setq seqno (org-odt--enumerate element info predicate)) ;; Localize category string. (setq category (org-export-translate category :utf-8 info)) - (case op + (cl-case op ;; Case 1: Handle Label definition. (definition - ;; Assign an internal label, if user has not provided one - (setq label (org-export-solidify-link-text - (or label (format "%s-%s" default-category seqno)))) (cons (concat ;; Sneak in a bookmark. The bookmark is used when the @@ -2179,14 +2161,13 @@ SHORT-CAPTION are strings." short-caption)) ;; Case 2: Handle Label reference. (reference - (assert label) - (setq label (org-export-solidify-link-text label)) (let* ((fmt (cddr (assoc-string label-style org-odt-label-styles t))) (fmt1 (car fmt)) (fmt2 (cadr fmt))) (format "<text:sequence-ref text:reference-format=\"%s\" text:ref-name=\"%s\">%s</text:sequence-ref>" - fmt1 label (format-spec fmt2 `((?e . ,category) - (?n . ,seqno)))))) + fmt1 + label + (format-spec fmt2 `((?e . ,category) (?n . ,seqno)))))) (t (error "Unknown %S on label" op)))))))) @@ -2199,7 +2180,7 @@ SHORT-CAPTION are strings." (target-dir "Images/") (target-file (format "%s%04d.%s" target-dir - (incf org-odt-embedded-images-count) image-type))) + (cl-incf org-odt-embedded-images-count) image-type))) (message "Embedding %s as %s..." (substring-no-properties path) target-file) @@ -2211,8 +2192,8 @@ SHORT-CAPTION are strings." (org-odt-create-manifest-file-entry media-type target-file) target-file)) -(defun org-odt--image-size (file &optional user-width - user-height scale dpi embed-as) +(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) @@ -2224,7 +2205,7 @@ SHORT-CAPTION are strings." (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 org-odt-pixels-per-inch)) + (dpi (or dpi (plist-get info :odt-pixels-per-inch))) (anchor-type (or embed-as "paragraph")) (user-width (and (not scale) user-width)) (user-height (and (not scale) user-height)) @@ -2278,7 +2259,7 @@ SHORT-CAPTION are strings." "Return ODT code for an inline image. LINK is the link pointing to the inline image. INFO is a plist used as a communication channel." - (assert (eq (org-element-type element) 'link)) + (cl-assert (eq (org-element-type element) 'link)) (let* ((src (let* ((type (org-element-property :type element)) (raw-path (org-element-property :path element))) (cond ((member type '("http" "https")) @@ -2293,7 +2274,7 @@ used as a communication channel." "\n<draw:image xlink:href=\"%s\" xlink:type=\"simple\" xlink:show=\"embed\" xlink:actuate=\"onLoad\"/>" (org-odt--copy-image-file src-expanded))) ;; Extract attributes from #+ATTR_ODT line. - (attr-from (case (org-element-type element) + (attr-from (cl-case (org-element-type element) (link (org-export-get-parent-element element)) (t element))) ;; Convert attributes to a plist. @@ -2313,7 +2294,7 @@ used as a communication channel." ;; Handle `:width', `:height' and `:scale' properties. Read ;; them as numbers since we need them for computations. (size (org-odt--image-size - src-expanded + src-expanded info (let ((width (plist-get attr-plist :width))) (and width (read width))) (let ((length (plist-get attr-plist :length))) @@ -2327,7 +2308,7 @@ used as a communication channel." (standalone-link-p (org-odt--standalone-link-p element info)) (embed-as (if standalone-link-p "paragraph" "as-char")) (captions (org-odt-format-label element info 'definition)) - (caption (car captions)) (short-caption (cdr captions)) + (caption (car captions)) (entity (concat (and caption "Captioned") embed-as "Image")) ;; Check if this link was created by LaTeX-to-PNG converter. (replaces (org-element-property @@ -2342,14 +2323,13 @@ used as a communication channel." ;; description. This quite useful for debugging. (desc (and replaces (org-element-property :value replaces)))) (org-odt--render-image/formula entity href width height - captions user-frame-params title desc))) + captions user-frame-params title desc))) ;;;; Links :: Math formula (defun org-odt-link--inline-formula (element info) - (let* ((src (let* ((type (org-element-property :type element)) - (raw-path (org-element-property :path element))) + (let* ((src (let ((raw-path (org-element-property :path element))) (cond ((file-name-absolute-p raw-path) (expand-file-name raw-path)) @@ -2365,7 +2345,6 @@ used as a communication channel." (standalone-link-p (org-odt--standalone-link-p element info)) (embed-as (if standalone-link-p 'paragraph 'character)) (captions (org-odt-format-label element info 'definition)) - (caption (car captions)) (short-caption (cdr captions)) ;; Check if this link was created by LaTeX-to-MathML ;; converter. (replaces (org-element-property @@ -2383,7 +2362,7 @@ used as a communication channel." (cond ((eq embed-as 'character) (org-odt--render-image/formula "InlineFormula" href width height - nil nil title desc)) + nil nil title desc)) (t (let* ((equation (org-odt--render-image/formula "CaptionedDisplayFormula" href width height @@ -2398,7 +2377,7 @@ used as a communication channel." (defun org-odt--copy-formula-file (src-file) "Returns the internal name of the file" (let* ((target-dir (format "Formula-%04d/" - (incf org-odt-embedded-formulas-count))) + (cl-incf org-odt-embedded-formulas-count))) (target-file (concat target-dir "content.xml"))) ;; Create a directory for holding formula file. Also enter it in ;; to manifest. @@ -2408,13 +2387,13 @@ used as a communication channel." ;; Copy over the formula file from user directory to zip ;; directory. (message "Embedding %s as %s..." src-file target-file) - (let ((case-fold-search nil)) + (let ((ext (file-name-extension src-file))) (cond ;; Case 1: Mathml. - ((string-match "\\.\\(mathml\\|mml\\)\\'" src-file) + ((member ext '("mathml" "mml")) (copy-file src-file (concat org-odt-zip-dir target-file) 'overwrite)) ;; Case 2: OpenDocument formula. - ((string-match "\\.odf\\'" src-file) + ((string= ext "odf") (org-odt--zip-extract src-file "content.xml" (concat org-odt-zip-dir target-dir))) (t (error "%s is not a formula file" src-file)))) @@ -2425,8 +2404,8 @@ used as a communication channel." ;;;; Targets (defun org-odt--render-image/formula (cfg-key href width height &optional - captions user-frame-params - &rest title-and-desc) + captions user-frame-params + &rest title-and-desc) (let* ((frame-cfg-alist ;; Each element of this alist is of the form (CFG-HANDLE ;; INNER-FRAME-PARAMS OUTER-FRAME-PARAMS). @@ -2488,11 +2467,11 @@ used as a communication channel." (lambda (default user) "Merge default and user frame params." (if (not user) default - (assert (= (length default) 3)) - (assert (= (length user) 3)) - (loop for u in user - for d in default - collect (or u d))))))) + (cl-assert (= (length default) 3)) + (cl-assert (= (length user) 3)) + (cl-loop for u in user + for d in default + collect (or u d))))))) (cond ;; Case 1: Image/Formula has no caption. ;; There is only one frame, one that surrounds the image @@ -2526,7 +2505,7 @@ used as a communication channel." caption)) width height outer))))) -(defun org-odt--enumerable-p (element info) +(defun org-odt--enumerable-p (element _info) ;; Element should have a caption or label. (or (org-element-property :caption element) (org-element-property :name element))) @@ -2543,8 +2522,8 @@ used as a communication channel." (org-element-property :name p)))) ;; Link should point to an image file. (lambda (l) - (assert (eq (org-element-type l) 'link)) - (org-export-inline-image-p l org-odt-inline-image-rules)))) + (cl-assert (eq (org-element-type l) 'link)) + (org-export-inline-image-p l (plist-get info :odt-inline-image-rules))))) (defun org-odt--enumerable-latex-image-p (element info) (org-odt--standalone-link-p @@ -2558,8 +2537,8 @@ used as a communication channel." (org-element-property :name p)))) ;; Link should point to an image file. (lambda (l) - (assert (eq (org-element-type l) 'link)) - (org-export-inline-image-p l org-odt-inline-image-rules)))) + (cl-assert (eq (org-element-type l) 'link)) + (org-export-inline-image-p l (plist-get info :odt-inline-image-rules))))) (defun org-odt--enumerable-formula-p (element info) (org-odt--standalone-link-p @@ -2570,12 +2549,12 @@ used as a communication channel." (org-element-property :name p))) ;; Link should point to a MathML or ODF file. (lambda (l) - (assert (eq (org-element-type l) 'link)) - (org-export-inline-image-p l org-odt-inline-formula-rules)))) + (cl-assert (eq (org-element-type l) 'link)) + (org-export-inline-image-p l (plist-get info :odt-inline-formula-rules))))) -(defun org-odt--standalone-link-p (element info &optional - paragraph-predicate - link-predicate) +(defun org-odt--standalone-link-p (element _info &optional + paragraph-predicate + link-predicate) "Test if ELEMENT is a standalone link for the purpose ODT export. INFO is a plist holding contextual information. @@ -2589,7 +2568,7 @@ PARAGRAPH-PREDICATE in addition to having no other content save for leading and trailing whitespaces. Return nil, otherwise." - (let ((p (case (org-element-type element) + (let ((p (cl-case (org-element-type element) (paragraph element) (link (and (or (not link-predicate) (funcall link-predicate element)) @@ -2599,23 +2578,24 @@ Return nil, otherwise." (when (or (not paragraph-predicate) (funcall paragraph-predicate p)) (let ((contents (org-element-contents p))) - (loop for x in contents - with inline-image-count = 0 - always (case (org-element-type x) - (plain-text - (not (org-string-nw-p x))) - (link - (and (or (not link-predicate) - (funcall link-predicate x)) - (= (incf inline-image-count) 1))) - (t nil)))))))) + (cl-loop for x in contents + with inline-image-count = 0 + always (cl-case (org-element-type x) + (plain-text + (not (org-string-nw-p x))) + (link + (and (or (not link-predicate) + (funcall link-predicate x)) + (= (cl-incf inline-image-count) 1))) + (t nil)))))))) (defun org-odt-link--infer-description (destination info) - ;; DESTINATION is a HEADLINE, a "<<target>>" or an element (like - ;; paragraph, verse-block etc) to which a "#+NAME: label" can be - ;; attached. Note that labels that are attached to captioned - ;; entities - inline images, math formulae and tables - get resolved - ;; as part of `org-odt-format-label' and `org-odt--enumerate'. + ;; DESTINATION is a headline or an element (like paragraph, + ;; verse-block etc) to which a "#+NAME: label" can be attached. + + ;; Note that labels that are attached to captioned entities - inline + ;; images, math formulae and tables - get resolved as part of + ;; `org-odt-format-label' and `org-odt--enumerate'. ;; Create a cross-reference to DESTINATION but make best-efforts to ;; create a *meaningful* description. Check item numbers, section @@ -2623,44 +2603,40 @@ Return nil, otherwise." ;; NOTE: Counterpart of `org-export-get-ordinal'. ;; FIXME: Handle footnote-definition footnote-reference? - (let* ((genealogy (org-export-get-genealogy destination)) + (let* ((genealogy (org-element-lineage destination)) (data (reverse genealogy)) - (label (case (org-element-type destination) - (headline - (format "sec-%s" (mapconcat 'number-to-string - (org-export-get-headline-number - destination info) "-"))) - (target - (org-element-property :value destination)) - (t (error "FIXME: Resolve %S" destination))))) + (label (let ((type (org-element-type destination))) + (if (memq type '(headline target)) + (org-export-get-reference destination info) + (error "FIXME: Unable to resolve %S" destination))))) (or (let* ( ;; Locate top-level list. (top-level-list - (loop for x on data - when (eq (org-element-type (car x)) 'plain-list) - return x)) + (cl-loop for x on data + when (eq (org-element-type (car x)) 'plain-list) + return x)) ;; Get list item nos. (item-numbers - (loop for (plain-list item . rest) on top-level-list by #'cddr - until (not (eq (org-element-type plain-list) 'plain-list)) - collect (when (eq (org-element-property :type - plain-list) - 'ordered) - (1+ (length (org-export-get-previous-element - item info t)))))) + (cl-loop for (plain-list item . rest) on top-level-list by #'cddr + until (not (eq (org-element-type plain-list) 'plain-list)) + collect (when (eq (org-element-property :type + plain-list) + 'ordered) + (1+ (length (org-export-get-previous-element + item info t)))))) ;; Locate top-most listified headline. (listified-headlines - (loop for x on data - when (and (eq (org-element-type (car x)) 'headline) - (org-export-low-level-p (car x) info)) - return x)) + (cl-loop for x on data + when (and (eq (org-element-type (car x)) 'headline) + (org-export-low-level-p (car x) info)) + return x)) ;; Get listified headline numbers. (listified-headline-nos - (loop for el in listified-headlines - when (eq (org-element-type el) 'headline) - collect (when (org-export-numbered-headline-p el info) - (1+ (length (org-export-get-previous-element - el info t))))))) + (cl-loop for el in listified-headlines + when (eq (org-element-type el) 'headline) + collect (when (org-export-numbered-headline-p el info) + (1+ (length (org-export-get-previous-element + el info t))))))) ;; Combine item numbers from both the listified headlines and ;; regular list items. @@ -2669,33 +2645,37 @@ Return nil, otherwise." (let ((item-numbers (append listified-headline-nos item-numbers))) (when (and item-numbers (not (memq nil item-numbers))) (format "<text:bookmark-ref text:reference-format=\"number-all-superior\" text:ref-name=\"%s\">%s</text:bookmark-ref>" - (org-export-solidify-link-text label) + label (mapconcat (lambda (n) (if (not n) " " - (concat (number-to-string n) "."))) + (concat (number-to-string n) "."))) item-numbers ""))))) ;; Case 2: Locate a regular and numbered headline in the ;; hierarchy. Display its section number. - (let ((headline (loop for el in (cons destination genealogy) - when (and (eq (org-element-type el) 'headline) - (not (org-export-low-level-p el info)) - (org-export-numbered-headline-p el info)) - return el))) + (let ((headline + (and + ;; Test if destination is a numbered headline. + (org-export-numbered-headline-p destination info) + (cl-loop for el in (cons destination genealogy) + when (and (eq (org-element-type el) 'headline) + (not (org-export-low-level-p el info)) + (org-export-numbered-headline-p el info)) + return el)))) ;; We found one. (when headline (format "<text:bookmark-ref text:reference-format=\"chapter\" text:ref-name=\"OrgXref.%s\">%s</text:bookmark-ref>" - (org-export-solidify-link-text label) + label (mapconcat 'number-to-string (org-export-get-headline-number headline info) ".")))) ;; Case 4: Locate a regular headline in the hierarchy. Display ;; its title. - (let ((headline (loop for el in (cons destination genealogy) - when (and (eq (org-element-type el) 'headline) - (not (org-export-low-level-p el info))) - return el))) + (let ((headline (cl-loop for el in (cons destination genealogy) + when (and (eq (org-element-type el) 'headline) + (not (org-export-low-level-p el info))) + return el))) ;; We found one. (when headline (format "<text:bookmark-ref text:reference-format=\"text\" text:ref-name=\"OrgXref.%s\">%s</text:bookmark-ref>" - (org-export-solidify-link-text label) + label (let ((title (org-element-property :title headline))) (org-export-data title info))))) (error "FIXME?")))) @@ -2711,24 +2691,23 @@ INFO is a plist holding contextual information. See ;; Ensure DESC really exists, or set it to nil. (desc (and (not (string= desc "")) desc)) (imagep (org-export-inline-image-p - link org-odt-inline-image-rules)) + link (plist-get info :odt-inline-image-rules))) (path (cond ((member type '("http" "https" "ftp" "mailto")) (concat type ":" raw-path)) - ((and (string= type "file") (file-name-absolute-p raw-path)) - (concat "file:" 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)) - protocol) + (path (replace-regexp-in-string "&" "&" path))) (cond + ;; Link type is handled by a special function. + ((org-export-custom-protocol-maybe link desc 'odt)) ;; Image file. - ((and (not desc) (org-export-inline-image-p - link org-odt-inline-image-rules)) - (org-odt-link--inline-image link info)) + ((and (not desc) imagep) (org-odt-link--inline-image link info)) ;; Formula file. - ((and (not desc) (org-export-inline-image-p - link org-odt-inline-formula-rules)) + ((and (not desc) + (org-export-inline-image-p + link (plist-get info :odt-inline-formula-rules))) (org-odt-link--inline-formula link info)) ;; Radio target: Transcode target's contents and use them as ;; link's description. @@ -2737,8 +2716,7 @@ INFO is a plist holding contextual information. See (if (not destination) desc (format "<text:bookmark-ref text:reference-format=\"text\" text:ref-name=\"OrgXref.%s\">%s</text:bookmark-ref>" - (org-export-solidify-link-text - (org-element-property :value destination)) + (org-export-get-reference destination info) desc)))) ;; Links pointing to a headline: Find destination and build ;; appropriate referencing command. @@ -2746,55 +2724,46 @@ INFO is a plist holding contextual information. See (let ((destination (if (string= type "fuzzy") (org-export-resolve-fuzzy-link link info) (org-export-resolve-id-link link info)))) - (case (org-element-type destination) - ;; Case 1: Fuzzy link points nowhere. - ('nil - (format "<text:span text:style-name=\"%s\">%s</text:span>" - "Emphasis" - (or desc - (org-export-data (org-element-property :raw-link link) - info)))) - ;; Case 2: Fuzzy link points to a headline. + (cl-case (org-element-type destination) + ;; Fuzzy link points to a headline. If there's + ;; a description, create a hyperlink. Otherwise, try to + ;; provide a meaningful description. (headline - ;; If there's a description, create a hyperlink. - ;; Otherwise, try to provide a meaningful description. (if (not desc) (org-odt-link--infer-description destination info) - (let* ((headline-no - (org-export-get-headline-number destination info)) - (label - (format "sec-%s" - (mapconcat 'number-to-string headline-no "-")))) + (let ((label + (or (and (string= type "custom-id") + (org-element-property :CUSTOM_ID destination)) + (org-export-get-reference destination info)))) (format "<text:a xlink:type=\"simple\" xlink:href=\"#%s\">%s</text:a>" label desc)))) - ;; Case 3: Fuzzy link points to a target. + ;; Fuzzy link points to a target. If there's a description, + ;; create a hyperlink. Otherwise, try to provide + ;; a meaningful description. (target - ;; If there's a description, create a hyperlink. - ;; Otherwise, try to provide a meaningful description. - (if (not desc) (org-odt-link--infer-description destination info) - (let ((label (org-element-property :value destination))) - (format "<text:a xlink:type=\"simple\" xlink:href=\"#%s\">%s</text:a>" - (org-export-solidify-link-text label) - desc)))) - ;; Case 4: Fuzzy link points to some element (e.g., an - ;; inline image, a math formula or a table). + (format "<text:a xlink:type=\"simple\" xlink:href=\"#%s\">%s</text:a>" + (org-export-get-reference destination info) + (or desc (org-export-get-ordinal destination info)))) + ;; Fuzzy link points to some element (e.g., an inline image, + ;; a math formula or a table). (otherwise (let ((label-reference - (ignore-errors (org-odt-format-label - destination info 'reference)))) - (cond ((not label-reference) - (org-odt-link--infer-description destination info)) - ;; LINK has no description. Create - ;; a cross-reference showing entity's sequence - ;; number. - ((not desc) label-reference) - ;; LINK has description. Insert a hyperlink with - ;; user-provided description. - (t - (let ((label (org-element-property :name destination))) - (format "<text:a xlink:type=\"simple\" xlink:href=\"#%s\">%s</text:a>" - (org-export-solidify-link-text label) - desc))))))))) + (ignore-errors + (org-odt-format-label destination info 'reference)))) + (cond + ((not label-reference) + (org-odt-link--infer-description destination info)) + ;; LINK has no description. Create + ;; a cross-reference showing entity's sequence + ;; number. + ((not desc) label-reference) + ;; LINK has description. Insert a hyperlink with + ;; user-provided description. + (t + (format + "<text:a xlink:type=\"simple\" xlink:href=\"#%s\">%s</text:a>" + (org-export-get-reference destination info) + desc)))))))) ;; Coderef: replace link with the reference name or the ;; equivalent line number. ((string= type "coderef") @@ -2805,9 +2774,6 @@ INFO is a plist holding contextual information. See (format "<text:bookmark-ref text:reference-format=\"number\" text:ref-name=\"OrgXref.%s\">%s</text:bookmark-ref>" href line-no)))) - ;; Link type is handled by a special function. - ((functionp (setq protocol (nth 2 (assoc type org-link-protocols)))) - (funcall protocol (org-link-unescape path) desc 'odt)) ;; External link with a description part. ((and path desc) (let ((link-contents (org-element-contents link))) @@ -2816,7 +2782,8 @@ INFO is a plist holding contextual information. See (let ((desc-element (car link-contents))) (and (eq (org-element-type desc-element) 'link) (org-export-inline-image-p - desc-element org-odt-inline-image-rules)))) + desc-element + (plist-get info :odt-inline-image-rules))))) ;; Format link as a clickable image. (format "\n<draw:a xlink:type=\"simple\" xlink:href=\"%s\">\n%s\n</draw:a>" path desc) @@ -2832,6 +2799,18 @@ INFO is a plist holding contextual information. See "Emphasis" desc))))) +;;;; Node Property + +(defun org-odt-node-property (node-property _contents _info) + "Transcode a NODE-PROPERTY element from Org to ODT. +CONTENTS is nil. INFO is a plist holding contextual +information." + (org-odt--encode-plain-text + (format "%s:%s" + (org-element-property :key node-property) + (let ((value (org-element-property :value node-property))) + (if value (concat " " value) ""))))) + ;;;; Paragraph (defun org-odt--paragraph-style (paragraph) @@ -2841,7 +2820,7 @@ Style is a symbol among `quoted', `centered' and nil." (while (and (setq up (org-element-property :parent up)) (not (memq (org-element-type up) '(center-block quote-block section))))) - (case (org-element-type up) + (cl-case (org-element-type up) (center-block 'centered) (quote-block 'quoted)))) @@ -2853,7 +2832,7 @@ a plist used as a communication channel. DEFAULT, CENTER and QUOTE are, respectively, style to use when paragraph belongs to no special environment, a center block, or a quote block." (format "\n<text:p text:style-name=\"%s\">%s</text:p>" - (case (org-odt--paragraph-style paragraph) + (cl-case (org-odt--paragraph-style paragraph) (quoted quote) (centered center) (otherwise default)) @@ -2879,13 +2858,13 @@ the plist used as a communication channel." ;;;; Plain List -(defun org-odt-plain-list (plain-list contents info) +(defun org-odt-plain-list (plain-list contents _info) "Transcode a PLAIN-LIST element from Org to ODT. CONTENTS is the contents of the list. INFO is a plist holding contextual information." (format "\n<text:list text:style-name=\"%s\" %s>\n%s</text:list>" ;; Choose style based on list type. - (case (org-element-property :type plain-list) + (cl-case (org-element-property :type plain-list) (ordered "OrgNumberedList") (unordered "OrgBulletedList") (descriptive-1 "OrgDescriptionList") @@ -2902,22 +2881,15 @@ contextual information." (defun org-odt--encode-tabs-and-spaces (line) (replace-regexp-in-string - "\\([\t]\\|\\([ ]+\\)\\)" + "\\(\t\\| \\{2,\\}\\)" (lambda (s) - (cond - ((string= s "\t") "<text:tab/>") - (t (let ((n (length s))) - (cond - ((= n 1) " ") - ((> n 1) (concat " " (format "<text:s text:c=\"%d\"/>" (1- n)))) - (t "")))))) + (if (string= s "\t") "<text:tab/>" + (format " <text:s text:c=\"%d\"/>" (1- (length s))))) line)) (defun org-odt--encode-plain-text (text &optional no-whitespace-filling) - (mapc - (lambda (pair) - (setq text (replace-regexp-in-string (car pair) (cdr pair) text t t))) - '(("&" . "&") ("<" . "<") (">" . ">"))) + (dolist (pair '(("&" . "&") ("<" . "<") (">" . ">"))) + (setq text (replace-regexp-in-string (car pair) (cdr pair) text t t))) (if no-whitespace-filling text (org-odt--encode-tabs-and-spaces text))) @@ -2934,11 +2906,9 @@ contextual information." (setq output (org-export-activate-smart-quotes output :utf-8 info text))) ;; Convert special strings. (when (plist-get info :with-special-strings) - (mapc - (lambda (pair) - (setq output - (replace-regexp-in-string (car pair) (cdr pair) output t nil))) - org-odt-special-string-regexps)) + (dolist (pair org-odt-special-string-regexps) + (setq output + (replace-regexp-in-string (car pair) (cdr pair) output t nil)))) ;; Handle break preservation if required. (when (plist-get info :preserve-breaks) (setq output (replace-regexp-in-string @@ -2978,34 +2948,24 @@ channel." ;;;; Property Drawer -(defun org-odt-property-drawer (property-drawer contents info) +(defun org-odt-property-drawer (_property-drawer contents _info) "Transcode a PROPERTY-DRAWER element from Org to ODT. -CONTENTS is nil. INFO is a plist holding contextual -information." - ;; The property drawer isn't exported but we want separating blank - ;; lines nonetheless. - "") +CONTENTS holds the contents of the drawer. INFO is a plist +holding contextual information." + (and (org-string-nw-p contents) + (format "<text:p text:style-name=\"OrgFixedWidthBlock\">%s</text:p>" + contents))) ;;;; Quote Block -(defun org-odt-quote-block (quote-block contents info) +(defun org-odt-quote-block (_quote-block contents _info) "Transcode a QUOTE-BLOCK element from Org to ODT. CONTENTS holds the contents of the block. INFO is a plist holding contextual information." contents) -;;;; Quote Section - -(defun org-odt-quote-section (quote-section contents info) - "Transcode a QUOTE-SECTION element from Org to ODT. -CONTENTS is nil. INFO is a plist holding contextual information." - (let ((value (org-remove-indentation - (org-element-property :value quote-section)))) - (when value (org-odt-do-format-code value)))) - - ;;;; Section (defun org-odt-format-section (text style &optional name) @@ -3016,7 +2976,7 @@ CONTENTS is nil. INFO is a plist holding contextual information." text))) -(defun org-odt-section (section contents info) ; FIXME +(defun org-odt-section (_section contents _info) ; FIXME "Transcode a SECTION element from Org to ODT. CONTENTS holds the contents of the section. INFO is a plist holding contextual information." @@ -3028,9 +2988,7 @@ holding contextual information." "Transcode a RADIO-TARGET object from Org to ODT. TEXT is the text of the target. INFO is a plist holding contextual information." - (org-odt--target - text (org-export-solidify-link-text - (org-element-property :value radio-target)))) + (org-odt--target text (org-export-get-reference radio-target info))) ;;;; Special Block @@ -3039,7 +2997,7 @@ contextual information." "Transcode a SPECIAL-BLOCK element from Org to ODT. CONTENTS holds the contents of the block. INFO is a plist holding contextual information." - (let ((type (downcase (org-element-property :type special-block))) + (let ((type (org-element-property :type special-block)) (attributes (org-export-read-attribute :attr_odt special-block))) (cond ;; Annotation. @@ -3109,31 +3067,30 @@ and prefix with \"OrgSrc\". For example, (cons style-name style))) (defun org-odt-htmlfontify-string (line) - (let* ((hfy-html-quote-regex "\\([<\"&> ]\\)") + (let* ((hfy-html-quote-regex "\\([<\"&> \t]\\)") (hfy-html-quote-map '(("\"" """) ("<" "<") ("&" "&") (">" ">") (" " "<text:s/>") - (" " "<text:tab/>"))) + ("\t" "<text:tab/>"))) (hfy-face-to-css 'org-odt-hfy-face-to-css) (hfy-optimizations-1 (copy-sequence hfy-optimizations)) - (hfy-optimizations (add-to-list 'hfy-optimizations-1 - 'body-text-only)) + (hfy-optimizations (cl-pushnew 'body-text-only hfy-optimizations-1)) (hfy-begin-span-handler - (lambda (style text-block text-id text-begins-block-p) + (lambda (style _text-block _text-id _text-begins-block-p) (insert (format "<text:span text:style-name=\"%s\">" style)))) - (hfy-end-span-handler (lambda nil (insert "</text:span>")))) - (org-no-warnings (htmlfontify-string line)))) + (hfy-end-span-handler (lambda () (insert "</text:span>")))) + (with-no-warnings (htmlfontify-string line)))) (defun org-odt-do-format-code - (code &optional lang refs retain-labels num-start) + (code info &optional lang refs retain-labels num-start) (let* ((lang (or (assoc-default lang org-src-lang-modes) lang)) (lang-mode (and lang (intern (format "%s-mode" lang)))) (code-lines (org-split-string code "\n")) (code-length (length code-lines)) (use-htmlfontify-p (and (functionp lang-mode) - org-odt-fontify-srcblocks + (plist-get info :odt-fontify-srcblocks) (require 'htmlfontify nil t) (fboundp 'htmlfontify-string))) (code (if (not use-htmlfontify-p) code @@ -3147,19 +3104,20 @@ and prefix with \"OrgSrc\". For example, (par-style (if use-htmlfontify-p "OrgSrcBlock" "OrgFixedWidthBlock")) (i 0)) - (assert (= code-length (length (org-split-string code "\n")))) + (cl-assert (= code-length (length (org-split-string code "\n")))) (setq code (org-export-format-code code (lambda (loc line-num ref) (setq par-style - (concat par-style (and (= (incf i) code-length) "LastLine"))) + (concat par-style (and (= (cl-incf i) code-length) + "LastLine"))) (setq loc (concat loc (and ref retain-labels (format " (%s)" ref)))) (setq loc (funcall fontifier loc)) (when ref (setq loc (org-odt--target loc (concat "coderef-" ref)))) - (assert par-style) + (cl-assert par-style) (setq loc (format "\n<text:p text:style-name=\"%s\">%s</text:p>" par-style loc)) (if (not line-num) loc @@ -3185,19 +3143,15 @@ and prefix with \"OrgSrc\". For example, ;; Does the src block contain labels? (retain-labels (org-element-property :retain-labels element)) ;; Does it have line numbers? - (num-start (case (org-element-property :number-lines element) - (continued (org-export-get-loc element info)) - (new 0)))) - (org-odt-do-format-code code lang refs retain-labels num-start))) + (num-start (org-export-get-loc element info))) + (org-odt-do-format-code code info lang refs retain-labels num-start))) -(defun org-odt-src-block (src-block contents info) +(defun org-odt-src-block (src-block _contents info) "Transcode a SRC-BLOCK element from Org to ODT. CONTENTS holds the contents of the item. INFO is a plist holding contextual information." - (let* ((lang (org-element-property :language src-block)) - (attributes (org-export-read-attribute :attr_odt src-block)) - (captions (org-odt-format-label src-block info 'definition)) - (caption (car captions)) (short-caption (cdr captions))) + (let* ((attributes (org-export-read-attribute :attr_odt src-block)) + (caption (car (org-odt-format-label src-block info 'definition)))) (concat (and caption (format "\n<text:p text:style-name=\"%s\">%s</text:p>" @@ -3211,7 +3165,7 @@ contextual information." ;;;; Statistics Cookie -(defun org-odt-statistics-cookie (statistics-cookie contents info) +(defun org-odt-statistics-cookie (statistics-cookie _contents _info) "Transcode a STATISTICS-COOKIE object from Org to ODT. CONTENTS is nil. INFO is a plist holding contextual information." (let ((cookie-value (org-element-property :value statistics-cookie))) @@ -3221,7 +3175,7 @@ CONTENTS is nil. INFO is a plist holding contextual information." ;;;; Strike-Through -(defun org-odt-strike-through (strike-through contents info) +(defun org-odt-strike-through (_strike-through contents _info) "Transcode STRIKE-THROUGH from Org to ODT. CONTENTS is the text with strike-through markup. INFO is a plist holding contextual information." @@ -3231,7 +3185,7 @@ holding contextual information." ;;;; Subscript -(defun org-odt-subscript (subscript contents info) +(defun org-odt-subscript (_subscript contents _info) "Transcode a SUBSCRIPT object from Org to ODT. CONTENTS is the contents of the object. INFO is a plist holding contextual information." @@ -3241,7 +3195,7 @@ contextual information." ;;;; Superscript -(defun org-odt-superscript (superscript contents info) +(defun org-odt-superscript (_superscript contents _info) "Transcode a SUPERSCRIPT object from Org to ODT. CONTENTS is the contents of the object. INFO is a plist holding contextual information." @@ -3255,7 +3209,7 @@ contextual information." (let* ((table (org-export-get-parent-table element)) (table-attributes (org-export-read-attribute :attr_odt table)) (table-style (plist-get table-attributes :style))) - (assoc table-style org-odt-table-styles))) + (assoc table-style (plist-get info :odt-table-styles)))) (defun org-odt-get-table-cell-styles (table-cell info) "Retrieve styles applicable to a table cell. @@ -3296,23 +3250,23 @@ styles congruent with the ODF-1.2 specification." (cell-style-selectors (nth 2 style-spec)) (cell-type (cond - ((and (cdr (assoc 'use-first-column-styles cell-style-selectors)) + ((and (cdr (assq 'use-first-column-styles cell-style-selectors)) (= c 0)) "FirstColumn") - ((and (cdr (assoc 'use-last-column-styles cell-style-selectors)) + ((and (cdr (assq 'use-last-column-styles cell-style-selectors)) (= (1+ c) (cdr table-dimensions))) "LastColumn") - ((and (cdr (assoc 'use-first-row-styles cell-style-selectors)) + ((and (cdr (assq 'use-first-row-styles cell-style-selectors)) (= r 0)) "FirstRow") - ((and (cdr (assoc 'use-last-row-styles cell-style-selectors)) + ((and (cdr (assq 'use-last-row-styles cell-style-selectors)) (= (1+ r) (car table-dimensions))) "LastRow") - ((and (cdr (assoc 'use-banding-rows-styles cell-style-selectors)) + ((and (cdr (assq 'use-banding-rows-styles cell-style-selectors)) (= (% r 2) 1)) "EvenRow") - ((and (cdr (assoc 'use-banding-rows-styles cell-style-selectors)) + ((and (cdr (assq 'use-banding-rows-styles cell-style-selectors)) (= (% r 2) 0)) "OddRow") - ((and (cdr (assoc 'use-banding-columns-styles cell-style-selectors)) + ((and (cdr (assq 'use-banding-columns-styles cell-style-selectors)) (= (% c 2) 1)) "EvenColumn") - ((and (cdr (assoc 'use-banding-columns-styles cell-style-selectors)) + ((and (cdr (assq 'use-banding-columns-styles cell-style-selectors)) (= (% c 2) 0)) "OddColumn") (t "")))) (concat template-name cell-type))))) @@ -3370,17 +3324,16 @@ channel." (1+ horiz-span)))))) (unless contents (setq contents "")) (concat - (assert paragraph-style) + (cl-assert paragraph-style) (format "\n<table:table-cell%s>\n%s\n</table:table-cell>" cell-attributes (let ((table-cell-contents (org-element-contents table-cell))) - (if (memq (org-element-type (car table-cell-contents)) - org-element-all-elements) + (if (eq (org-element-class (car table-cell-contents)) 'element) contents (format "\n<text:p text:style-name=\"%s\">%s</text:p>" paragraph-style contents)))) (let (s) - (dotimes (i horiz-span s) + (dotimes (_ horiz-span s) (setq s (concat s "\n<table:covered-table-cell/>")))) "\n"))) @@ -3431,7 +3384,7 @@ communication channel." "Transcode a TABLE element from Org to ODT. CONTENTS is the contents of the table. INFO is a plist holding contextual information." - (case (org-element-property :type table) + (cl-case (org-element-property :type table) ;; Case 1: table.el doesn't support export to OD format. Strip ;; such tables from export. (table.el @@ -3448,20 +3401,19 @@ contextual information." (attributes (org-export-read-attribute :attr_odt table)) (custom-table-style (nth 1 (org-odt-table-style-spec table info))) (table-column-specs - (function - (lambda (table info) - (let* ((table-style (or custom-table-style "OrgTable")) - (column-style (format "%sColumn" table-style))) - (mapconcat - (lambda (table-cell) - (let ((width (1+ (or (org-export-table-cell-width - table-cell info) 0))) - (s (format - "\n<table:table-column table:style-name=\"%s\"/>" - column-style)) - out) - (dotimes (i width out) (setq out (concat s out))))) - (org-odt-table-first-row-data-cells table info) "\n")))))) + (lambda (table info) + (let* ((table-style (or custom-table-style "OrgTable")) + (column-style (format "%sColumn" table-style))) + (mapconcat + (lambda (table-cell) + (let ((width (1+ (or (org-export-table-cell-width + table-cell info) 0))) + (s (format + "\n<table:table-column table:style-name=\"%s\"/>" + column-style)) + out) + (dotimes (_ width out) (setq out (concat s out))))) + (org-odt-table-first-row-data-cells table info) "\n"))))) (concat ;; caption. (when caption @@ -3490,84 +3442,84 @@ contextual information. Use `org-odt--table' to typeset the table. Handle details pertaining to indentation here." (let* ((--element-preceded-by-table-p - (function - (lambda (element info) - (loop for el in (org-export-get-previous-element element info t) - thereis (eq (org-element-type el) 'table))))) + (lambda (element info) + (cl-loop for el in (org-export-get-previous-element element info t) + thereis (eq (org-element-type el) 'table)))) (--walk-list-genealogy-and-collect-tags - (function - (lambda (table info) - (let* ((genealogy (org-export-get-genealogy table)) - (list-genealogy - (when (eq (org-element-type (car genealogy)) 'item) - (loop for el in genealogy - when (memq (org-element-type el) - '(item plain-list)) - collect el))) - (llh-genealogy - (apply 'nconc - (loop for el in genealogy - when (and (eq (org-element-type el) 'headline) - (org-export-low-level-p el info)) - collect - (list el - (assq 'headline - (org-element-contents - (org-export-get-parent el))))))) - parent-list) - (nconc - ;; Handle list genealogy. - (loop for el in list-genealogy collect - (case (org-element-type el) - (plain-list - (setq parent-list el) - (cons "</text:list>" - (format "\n<text:list text:style-name=\"%s\" %s>" - (case (org-element-property :type el) - (ordered "OrgNumberedList") - (unordered "OrgBulletedList") - (descriptive-1 "OrgDescriptionList") - (descriptive-2 "OrgDescriptionList")) - "text:continue-numbering=\"true\""))) - (item - (cond - ((not parent-list) - (if (funcall --element-preceded-by-table-p table info) - '("</text:list-header>" . "<text:list-header>") - '("</text:list-item>" . "<text:list-header>"))) - ((funcall --element-preceded-by-table-p - parent-list info) - '("</text:list-header>" . "<text:list-header>")) - (t '("</text:list-item>" . "<text:list-item>")))))) - ;; Handle low-level headlines. - (loop for el in llh-genealogy - with step = 'item collect - (case step - (plain-list - (setq step 'item) ; Flip-flop - (setq parent-list el) - (cons "</text:list>" - (format "\n<text:list text:style-name=\"%s\" %s>" - (if (org-export-numbered-headline-p - el info) - "OrgNumberedList" - "OrgBulletedList") - "text:continue-numbering=\"true\""))) - (item - (setq step 'plain-list) ; Flip-flop - (cond - ((not parent-list) - (if (funcall --element-preceded-by-table-p table info) - '("</text:list-header>" . "<text:list-header>") - '("</text:list-item>" . "<text:list-header>"))) - ((let ((section? (org-export-get-previous-element - parent-list info))) - (and section? - (eq (org-element-type section?) 'section) - (assq 'table (org-element-contents section?)))) - '("</text:list-header>" . "<text:list-header>")) - (t - '("</text:list-item>" . "<text:list-item>"))))))))))) + (lambda (table info) + (let* ((genealogy (org-element-lineage table)) + (list-genealogy + (when (eq (org-element-type (car genealogy)) 'item) + (cl-loop for el in genealogy + when (memq (org-element-type el) + '(item plain-list)) + collect el))) + (llh-genealogy + (apply #'nconc + (cl-loop + for el in genealogy + when (and (eq (org-element-type el) 'headline) + (org-export-low-level-p el info)) + collect + (list el + (assq 'headline + (org-element-contents + (org-export-get-parent el))))))) + parent-list) + (nconc + ;; Handle list genealogy. + (cl-loop + for el in list-genealogy collect + (cl-case (org-element-type el) + (plain-list + (setq parent-list el) + (cons "</text:list>" + (format "\n<text:list text:style-name=\"%s\" %s>" + (cl-case (org-element-property :type el) + (ordered "OrgNumberedList") + (unordered "OrgBulletedList") + (descriptive-1 "OrgDescriptionList") + (descriptive-2 "OrgDescriptionList")) + "text:continue-numbering=\"true\""))) + (item + (cond + ((not parent-list) + (if (funcall --element-preceded-by-table-p table info) + '("</text:list-header>" . "<text:list-header>") + '("</text:list-item>" . "<text:list-header>"))) + ((funcall --element-preceded-by-table-p + parent-list info) + '("</text:list-header>" . "<text:list-header>")) + (t '("</text:list-item>" . "<text:list-item>")))))) + ;; Handle low-level headlines. + (cl-loop for el in llh-genealogy + with step = 'item collect + (cl-case step + (plain-list + (setq step 'item) ; Flip-flop + (setq parent-list el) + (cons "</text:list>" + (format "\n<text:list text:style-name=\"%s\" %s>" + (if (org-export-numbered-headline-p + el info) + "OrgNumberedList" + "OrgBulletedList") + "text:continue-numbering=\"true\""))) + (item + (setq step 'plain-list) ; Flip-flop + (cond + ((not parent-list) + (if (funcall --element-preceded-by-table-p table info) + '("</text:list-header>" . "<text:list-header>") + '("</text:list-item>" . "<text:list-header>"))) + ((let ((section? (org-export-get-previous-element + parent-list info))) + (and section? + (eq (org-element-type section?) 'section) + (assq 'table (org-element-contents section?)))) + '("</text:list-header>" . "<text:list-header>")) + (t + '("</text:list-item>" . "<text:list-item>")))))))))) (close-open-tags (funcall --walk-list-genealogy-and-collect-tags table info))) ;; OpenDocument schema does not permit table to occur within a @@ -3613,7 +3565,7 @@ pertaining to indentation here." ;; ;; - Description lists are simulated as plain lists. ;; - Low-level headlines can be listified. - ;; - In Org-mode, a table can occur not only as a regular list + ;; - In Org mode, a table can occur not only as a regular list ;; item, but also within description lists and low-level ;; headlines. @@ -3635,26 +3587,24 @@ pertaining to indentation here." ;;;; Target -(defun org-odt-target (target contents info) +(defun org-odt-target (target _contents info) "Transcode a TARGET object from Org to ODT. CONTENTS is nil. INFO is a plist holding contextual information." - (let ((value (org-element-property :value target))) - (org-odt--target "" (org-export-solidify-link-text value)))) + (org-odt--target "" (org-export-get-reference target info))) ;;;; Timestamp -(defun org-odt-timestamp (timestamp contents info) +(defun org-odt-timestamp (timestamp _contents info) "Transcode a TIMESTAMP object from Org to ODT. CONTENTS is nil. INFO is a plist used as a communication channel." - (let* ((raw-value (org-element-property :raw-value timestamp)) - (type (org-element-property :type timestamp))) - (if (not org-odt-use-date-fields) + (let ((type (org-element-property :type timestamp))) + (if (not (plist-get info :odt-use-date-fields)) (let ((value (org-odt-plain-text (org-timestamp-translate timestamp) info))) - (case (org-element-property :type timestamp) + (cl-case (org-element-property :type timestamp) ((active active-range) (format "<text:span text:style-name=\"%s\">%s</text:span>" "OrgActiveTimestamp" value)) @@ -3662,7 +3612,7 @@ channel." (format "<text:span text:style-name=\"%s\">%s</text:span>" "OrgInactiveTimestamp" value)) (otherwise value))) - (case type + (cl-case type (active (format "<text:span text:style-name=\"%s\">%s</text:span>" "OrgActiveTimestamp" @@ -3687,12 +3637,12 @@ channel." (format "<text:span text:style-name=\"%s\">%s</text:span>" "OrgDiaryTimestamp" (org-odt-plain-text (org-timestamp-translate timestamp) - info))))))) + info))))))) ;;;; Underline -(defun org-odt-underline (underline contents info) +(defun org-odt-underline (_underline contents _info) "Transcode UNDERLINE from Org to ODT. CONTENTS is the text with underline markup. INFO is a plist holding contextual information." @@ -3702,7 +3652,7 @@ holding contextual information." ;;;; Verbatim -(defun org-odt-verbatim (verbatim contents info) +(defun org-odt-verbatim (verbatim _contents _info) "Transcode a VERBATIM object from Org to ODT. CONTENTS is nil. INFO is a plist used as a communication channel." @@ -3713,33 +3663,36 @@ channel." ;;;; Verse Block -(defun org-odt-verse-block (verse-block contents info) +(defun org-odt-verse-block (_verse-block contents _info) "Transcode a VERSE-BLOCK element from Org to ODT. CONTENTS is verse block contents. INFO is a plist holding contextual information." - ;; Add line breaks to each line of verse. - (setq contents (replace-regexp-in-string - "\\(<text:line-break/>\\)?[ \t]*\n" - "<text:line-break/>" contents)) - ;; Replace tabs and spaces. - (setq contents (org-odt--encode-tabs-and-spaces contents)) - ;; Surround it in a verse environment. - (format "\n<text:p text:style-name=\"%s\">%s</text:p>" - "OrgVerse" contents)) + (format "\n<text:p text:style-name=\"OrgVerse\">%s</text:p>" + (replace-regexp-in-string + ;; Replace leading tabs and spaces. + "^[ \t]+" #'org-odt--encode-tabs-and-spaces + ;; Add line breaks to each line of verse. + (replace-regexp-in-string + "\\(<text:line-break/>\\)?[ \t]*$" "<text:line-break/>" contents)))) ;;; Filters +;;; Images + +(defun org-odt--translate-image-links (data _backend info) + (org-export-insert-image-links data info org-odt-inline-image-rules)) + ;;;; LaTeX fragments -(defun org-odt--translate-latex-fragments (tree backend info) +(defun org-odt--translate-latex-fragments (tree _backend info) (let ((processing-type (plist-get info :with-latex)) (count 0)) ;; Normalize processing-type to one of dvipng, mathml or verbatim. ;; If the desired converter is not available, force verbatim ;; processing. - (case processing-type + (cl-case processing-type ((t mathml) (if (and (fboundp 'org-format-latex-mathml-available-p) (org-format-latex-mathml-available-p)) @@ -3765,70 +3718,74 @@ contextual information." (when (memq processing-type '(mathml dvipng imagemagick)) (org-element-map tree '(latex-fragment latex-environment) (lambda (latex-*) - (incf count) + (cl-incf count) (let* ((latex-frag (org-element-property :value latex-*)) (input-file (plist-get info :input-file)) (cache-dir (file-name-directory input-file)) (cache-subdir (concat - (case processing-type + (cl-case processing-type ((dvipng imagemagick) "ltxpng/") (mathml "ltxmathml/")) (file-name-sans-extension (file-name-nondirectory input-file)))) (display-msg - (case processing-type - ((dvipng imagemagick) (format "Creating LaTeX Image %d..." count)) + (cl-case processing-type + ((dvipng imagemagick) + (format "Creating LaTeX Image %d..." count)) (mathml (format "Creating MathML snippet %d..." count)))) ;; Get an Org-style link to PNG image or the MathML ;; file. - (org-link - (let ((link (with-temp-buffer - (insert latex-frag) - (org-format-latex cache-subdir cache-dir - nil display-msg - nil nil processing-type) - (buffer-substring-no-properties - (point-min) (point-max))))) - (if (not (string-match "file:\\([^]]*\\)" link)) - (prog1 nil (message "LaTeX Conversion failed.")) - link)))) - (when org-link - ;; Conversion succeeded. Parse above Org-style link to a - ;; `link' object. - (let* ((link (car (org-element-map (with-temp-buffer - (org-mode) - (insert org-link) - (org-element-parse-buffer)) - 'link 'identity)))) - ;; Orphan the link. - (org-element-put-property link :parent nil) - (let* ( - (replacement - (case (org-element-type latex-*) - ;; Case 1: LaTeX environment. - ;; Mimic a "standalone image or formula" by - ;; enclosing the `link' in a `paragraph'. - ;; Copy over original attributes, captions to - ;; the enclosing paragraph. - (latex-environment - (org-element-adopt-elements - (list 'paragraph - (list :style "OrgFormula" - :name (org-element-property :name - latex-*) - :caption (org-element-property :caption - latex-*))) - link)) - ;; Case 2: LaTeX fragment. - ;; No special action. - (latex-fragment link)))) - ;; Note down the object that link replaces. - (org-element-put-property replacement :replaces - (list (org-element-type latex-*) - (list :value latex-frag))) - ;; Replace now. - (org-element-set-element latex-* replacement)))))) - info))) + (link + (with-temp-buffer + (insert latex-frag) + ;; When converting to a PNG image, make sure to + ;; copy all LaTeX header specifications from the + ;; Org source. + (unless (eq processing-type 'mathml) + (let ((h (plist-get info :latex-header))) + (when h + (insert "\n" + (replace-regexp-in-string + "^" "#+LATEX_HEADER: " h))))) + (org-format-latex cache-subdir nil nil cache-dir + nil display-msg nil + processing-type) + (goto-char (point-min)) + (skip-chars-forward " \t\n") + (org-element-link-parser)))) + (if (not (eq 'link (org-element-type link))) + (message "LaTeX Conversion failed.") + ;; Conversion succeeded. Parse above Org-style link to + ;; a `link' object. + (let ((replacement + (cl-case (org-element-type latex-*) + ;;LaTeX environment. Mimic a "standalone image + ;; or formula" by enclosing the `link' in + ;; a `paragraph'. Copy over original + ;; attributes, captions to the enclosing + ;; paragraph. + (latex-environment + (org-element-adopt-elements + (list 'paragraph + (list :style "OrgFormula" + :name + (org-element-property :name latex-*) + :caption + (org-element-property :caption latex-*))) + link)) + ;; LaTeX fragment. No special action. + (latex-fragment link)))) + ;; Note down the object that link replaces. + (org-element-put-property replacement :replaces + (list (org-element-type latex-*) + (list :value latex-frag))) + ;; Restore blank after initial element or object. + (org-element-put-property + replacement :post-blank + (org-element-property :post-blank latex-*)) + ;; Replace now. + (org-element-set-element latex-* replacement))))) + info nil nil t))) tree) @@ -3837,7 +3794,7 @@ contextual information." ;; This translator is necessary to handle indented tables in a uniform ;; manner. See comment in `org-odt--table'. -(defun org-odt--translate-description-lists (tree backend info) +(defun org-odt--translate-description-lists (tree _backend info) ;; OpenDocument has no notion of a description list. So simulate it ;; using plain lists. Description lists in the exported document ;; are typeset in the same manner as they are in a typical HTML @@ -3870,7 +3827,7 @@ contextual information." ;; (org-element-map tree 'plain-list (lambda (el) - (when (equal (org-element-property :type el) 'descriptive) + (when (eq (org-element-property :type el) 'descriptive) (org-element-set-element el (apply 'org-element-adopt-elements @@ -3929,11 +3886,11 @@ contextual information." ;; themselves and the list can be arbitrarily deep. ;; ;; Inspired by following thread: -;; https://lists.gnu.org/archive/html/emacs-orgmode/2011-03/msg01101.html +;; https://lists.gnu.org/r/emacs-orgmode/2011-03/msg01101.html ;; Translate lists to tables -(defun org-odt--translate-list-tables (tree backend info) +(defun org-odt--translate-list-tables (tree _backend info) (org-element-map tree 'plain-list (lambda (l1-list) (when (org-export-read-attribute :attr_odt l1-list :list-table) @@ -3994,42 +3951,38 @@ contextual information." (insert "<?xml version=\"1.0\" encoding=\"UTF-8\"?> <manifest:manifest xmlns:manifest=\"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0\" manifest:version=\"1.2\">\n") - (mapc - (lambda (file-entry) - (let* ((version (nth 2 file-entry)) - (extra (if (not version) "" - (format " manifest:version=\"%s\"" version)))) - (insert - (format org-odt-manifest-file-entry-tag - (nth 0 file-entry) (nth 1 file-entry) extra)))) - org-odt-manifest-file-entries) + (dolist (file-entry org-odt-manifest-file-entries) + (let* ((version (nth 2 file-entry)) + (extra (if (not version) "" + (format " manifest:version=\"%s\"" version)))) + (insert + (format org-odt-manifest-file-entry-tag + (nth 0 file-entry) (nth 1 file-entry) extra)))) (insert "\n</manifest:manifest>")))) (defmacro org-odt--export-wrap (out-file &rest body) `(let* ((--out-file ,out-file) (out-file-type (file-name-extension --out-file)) (org-odt-xml-files '("META-INF/manifest.xml" "content.xml" - "meta.xml" "styles.xml")) + "meta.xml" "styles.xml")) ;; Initialize temporary workarea. All files that end up in ;; the exported document get parked/created here. (org-odt-zip-dir (file-name-as-directory - (make-temp-file (format "%s-" out-file-type) t))) + (make-temp-file (format "%s-" out-file-type) t))) (org-odt-manifest-file-entries nil) (--cleanup-xml-buffers - (function - (lambda nil - ;; Kill all XML buffers. - (mapc (lambda (file) - (let ((buf (find-buffer-visiting - (concat org-odt-zip-dir file)))) - (when buf - (with-current-buffer buf - (set-buffer-modified-p nil) - (kill-buffer buf))))) - org-odt-xml-files) - ;; Delete temporary directory and also other embedded - ;; files that get copied there. - (delete-directory org-odt-zip-dir t))))) + (lambda () + ;; Kill all XML buffers. + (dolist (file org-odt-xml-files) + (let ((buf (find-buffer-visiting + (concat org-odt-zip-dir file)))) + (when buf + (with-current-buffer buf + (set-buffer-modified-p nil) + (kill-buffer buf))))) + ;; Delete temporary directory and also other embedded + ;; files that get copied there. + (delete-directory org-odt-zip-dir t)))) (condition-case err (progn (unless (executable-find "zip") @@ -4052,16 +4005,15 @@ contextual information." ;; Write out the manifest entries before zipping (org-odt-write-manifest-file) ;; Save all XML files. - (mapc (lambda (file) - (let ((buf (find-buffer-visiting - (concat org-odt-zip-dir file)))) - (when buf - (with-current-buffer buf - ;; Prettify output if needed. - (when org-odt-prettify-xml - (indent-region (point-min) (point-max))) - (save-buffer 0))))) - org-odt-xml-files) + (dolist (file org-odt-xml-files) + (let ((buf (find-buffer-visiting + (concat org-odt-zip-dir file)))) + (when buf + (with-current-buffer buf + ;; Prettify output if needed. + (when org-odt-prettify-xml + (indent-region (point-min) (point-max))) + (save-buffer 0))))) ;; Run zip. (let* ((target --out-file) (target-name (file-name-nondirectory target)) @@ -4079,19 +4031,17 @@ contextual information." ;; directory. (with-current-buffer (find-file-noselect (concat org-odt-zip-dir "content.xml") t) - (mapc - (lambda (cmd) - (message "Running %s" (mapconcat 'identity cmd " ")) - (setq err-string - (with-output-to-string - (setq exitcode - (apply 'call-process (car cmd) - nil standard-output nil (cdr cmd))))) - (or (zerop exitcode) - (error (concat "Unable to create OpenDocument file." - " Zip failed with error (%s)") - err-string))) - cmds))) + (dolist (cmd cmds) + (message "Running %s" (mapconcat 'identity cmd " ")) + (setq err-string + (with-output-to-string + (setq exitcode + (apply 'call-process (car cmd) + nil standard-output nil (cdr cmd))))) + (or (zerop exitcode) + (error (concat "Unable to create OpenDocument file." + " Zip failed with error (%s)") + err-string))))) ;; Move the zip file from temporary work directory to ;; user-mandated location. (rename-file (concat org-odt-zip-dir target-name) target) @@ -4135,9 +4085,9 @@ MathML source to kill ring depending on the value of (setq frag (and (setq frag (and (region-active-p) (buffer-substring (region-beginning) (region-end)))) - (loop for e in org-latex-regexps - thereis (when (string-match (nth 1 e) frag) - (match-string (nth 2 e) frag))))) + (cl-loop for e in org-latex-regexps + thereis (when (string-match (nth 1 e) frag) + (match-string (nth 2 e) frag))))) (read-string "LaTeX Fragment: " frag nil frag)) ,(let ((odf-filename (expand-file-name (concat @@ -4265,12 +4215,12 @@ Return output file's name." (when out-fmt-spec (throw 'done (cons (car e) out-fmt-spec)))))))) -(defun org-odt-do-convert (in-file out-fmt &optional prefix-arg) +(defun org-odt-do-convert (in-file out-fmt &optional open) "Workhorse routine for `org-odt-convert'." (require 'browse-url) - (let* ((in-file (expand-file-name (or in-file buffer-file-name))) - (dummy (or (file-readable-p in-file) - (error "Cannot read %s" in-file))) + (let* ((in-file (let ((f (expand-file-name (or in-file buffer-file-name)))) + (if (file-readable-p f) f + (error "Cannot read %s" in-file)))) (in-fmt (file-name-extension in-file)) (out-fmt (or out-fmt (error "Output format unspecified"))) (how (or (org-odt-reachable-p in-fmt out-fmt) @@ -4300,7 +4250,7 @@ Return output file's name." (cond ((file-exists-p out-file) (message "Exported to %s" out-file) - (when prefix-arg + (when open (message "Opening %s..." out-file) (org-open-file out-file 'system)) out-file) @@ -4333,12 +4283,10 @@ form (CONVERTER-PROCESS . OUTPUT-FMT-ALIST). See (defun org-odt-reachable-formats (in-fmt) "Return list of formats to which IN-FMT can be converted. The list of the form (OUTPUT-FMT-1 OUTPUT-FMT-2 ...)." - (let (l) - (mapc (lambda (e) (add-to-list 'l e)) - (apply 'append (mapcar - (lambda (e) (mapcar 'car (cdr e))) - (org-odt-do-reachable-formats in-fmt)))) - l)) + (copy-sequence + (apply #'append (mapcar + (lambda (e) (mapcar #'car (cdr e))) + (org-odt-do-reachable-formats in-fmt))))) (defun org-odt-convert-read-params () "Return IN-FILE and OUT-FMT params for `org-odt-do-convert'. @@ -4358,25 +4306,23 @@ This is a helper routine for interactive use." (list in-file out-fmt))) ;;;###autoload -(defun org-odt-convert (&optional in-file out-fmt prefix-arg) +(defun org-odt-convert (&optional in-file out-fmt open) "Convert IN-FILE to format OUT-FMT using a command line converter. IN-FILE is the file to be converted. If unspecified, it defaults to variable `buffer-file-name'. OUT-FMT is the desired output -format. Use `org-odt-convert-process' as the converter. -If PREFIX-ARG is non-nil then the newly converted file is opened -using `org-open-file'." +format. Use `org-odt-convert-process' as the converter. If OPEN +is non-nil then the newly converted file is opened using +`org-open-file'." (interactive (append (org-odt-convert-read-params) current-prefix-arg)) - (org-odt-do-convert in-file out-fmt prefix-arg)) + (org-odt-do-convert in-file out-fmt open)) ;;; Library Initializations -(mapc - (lambda (desc) - ;; Let Emacs open all OpenDocument files in archive mode - (add-to-list 'auto-mode-alist - (cons (concat "\\." (car desc) "\\'") 'archive-mode))) - org-odt-file-extensions) +(dolist (desc org-odt-file-extensions) + ;; Let Emacs open all OpenDocument files in archive mode. + (add-to-list 'auto-mode-alist + (cons (concat "\\." (car desc) "\\'") 'archive-mode))) (provide 'ox-odt) diff --git a/lisp/org/ox-org.el b/lisp/org/ox-org.el index 312221dc822..7db3a66ee8f 100644 --- a/lisp/org/ox-org.el +++ b/lisp/org/ox-org.el @@ -1,4 +1,4 @@ -;;; ox-org.el --- Org Back-End for Org Export Engine +;;; ox-org.el --- Org Back-End for Org Export Engine -*- lexical-binding: t; -*- ;; Copyright (C) 2013-2017 Free Software Foundation, Inc. @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -26,6 +26,7 @@ (require 'ox) (declare-function htmlize-buffer "ext:htmlize" (&optional buffer)) +(defvar htmlize-output-type) (defgroup org-export-org nil "Options for exporting Org mode files to Org." @@ -34,8 +35,6 @@ :version "24.4" :package-version '(Org . "8.0")) -(define-obsolete-variable-alias - 'org-export-htmlized-org-css-url 'org-org-htmlized-css-url "24.4") (defcustom org-org-htmlized-css-url nil "URL pointing to the CSS defining colors for htmlized Emacs buffers. Normally when creating an htmlized version of an Org buffer, @@ -45,7 +44,7 @@ look bad if different people with different fontification setup work on the same website. When this variable is non-nil, creating an htmlized version of an Org buffer using `org-org-export-as-org' will include a link to this URL if the -setting of `org-html-htmlize-output-type' is 'css." +setting of `org-html-htmlize-output-type' is `css'." :group 'org-export-org :type '(choice (const :tag "Don't include external stylesheet link" nil) @@ -57,13 +56,12 @@ setting of `org-html-htmlize-output-type' is 'css." (center-block . org-org-identity) (clock . org-org-identity) (code . org-org-identity) - (comment . (lambda (&rest args) "")) - (comment-block . (lambda (&rest args) "")) (diary-sexp . org-org-identity) (drawer . org-org-identity) (dynamic-block . org-org-identity) (entity . org-org-identity) (example-block . org-org-identity) + (export-block . org-org-export-block) (fixed-width . org-org-identity) (footnote-definition . ignore) (footnote-reference . org-org-identity) @@ -78,14 +76,14 @@ setting of `org-html-htmlize-output-type' is 'css." (latex-environment . org-org-identity) (latex-fragment . org-org-identity) (line-break . org-org-identity) - (link . org-org-identity) + (link . org-org-link) (node-property . org-org-identity) + (template . org-org-template) (paragraph . org-org-identity) (plain-list . org-org-identity) (planning . org-org-identity) (property-drawer . org-org-identity) (quote-block . org-org-identity) - (quote-section . org-org-identity) (radio-target . org-org-identity) (section . org-org-section) (special-block . org-org-identity) @@ -109,9 +107,35 @@ setting of `org-html-htmlize-output-type' is 'css." (?v "As Org file and open" (lambda (a s v b) (if a (org-org-export-to-org t s v b) - (org-open-file (org-org-export-to-org nil s v b)))))))) - -(defun org-org-identity (blob contents info) + (org-open-file (org-org-export-to-org nil s v b))))))) + :filters-alist '((:filter-parse-tree . org-org--add-missing-sections))) + +(defun org-org--add-missing-sections (tree _backend _info) + "Ensure each headline has an associated section. + +TREE is the parse tree being exported. + +Footnotes relative to the headline are inserted in the section, +using `org-org-section'. However, this function is not called if +the headline doesn't contain any section in the first place, so +we make sure it is always called." + (org-element-map tree 'headline + (lambda (h) + (let ((first-child (car (org-element-contents h))) + (new-section (org-element-create 'section))) + (pcase (org-element-type first-child) + (`section nil) + (`nil (org-element-adopt-elements h new-section)) + (_ (org-element-insert-before new-section first-child)))))) + tree) + +(defun org-org-export-block (export-block _contents _info) + "Transcode a EXPORT-BLOCK element from Org to LaTeX. +CONTENTS and INFO are ignored." + (and (equal (org-element-property :type export-block) "ORG") + (org-element-property :value export-block))) + +(defun org-org-identity (blob contents _info) "Transcode BLOB element or object back into Org syntax. CONTENTS is its contents, as a string or nil. INFO is ignored." (let ((case-fold-search t)) @@ -133,17 +157,54 @@ CONTENTS is its contents, as a string or nil. INFO is ignored." (org-export-get-relative-level headline info)) (org-element-headline-interpreter headline contents))) -(defun org-org-keyword (keyword contents info) +(defun org-org-keyword (keyword _contents _info) "Transcode KEYWORD element back into Org syntax. -CONTENTS is nil. INFO is ignored. This function ignores -keywords targeted at other export back-ends." - (unless (member (org-element-property :key keyword) - (mapcar - (lambda (block-cons) - (and (eq (cdr block-cons) 'org-element-export-block-parser) - (car block-cons))) - org-element-block-name-alist)) - (org-element-keyword-interpreter keyword nil))) +CONTENTS is nil. INFO is ignored." + (let ((key (org-element-property :key keyword))) + (unless (member key + '("AUTHOR" "CREATOR" "DATE" "EMAIL" "OPTIONS" "TITLE")) + (org-element-keyword-interpreter keyword nil)))) + +(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) + (org-element-link-interpreter link contents))) + +(defun org-org-template (contents info) + "Return Org document template with document keywords. +CONTENTS is the transcoded contents string. INFO is a plist used +as a communication channel." + (concat + (and (plist-get info :time-stamp-file) + (format-time-string "# Created %Y-%m-%d %a %H:%M\n")) + (org-element-normalize-string + (mapconcat #'identity + (org-element-map (plist-get info :parse-tree) 'keyword + (lambda (k) + (and (string-equal (org-element-property :key k) "OPTIONS") + (concat "#+OPTIONS: " + (org-element-property :value k))))) + "\n")) + (and (plist-get info :with-title) + (format "#+TITLE: %s\n" (org-export-data (plist-get info :title) info))) + (and (plist-get info :with-date) + (let ((date (org-export-data (org-export-get-date info) info))) + (and (org-string-nw-p date) + (format "#+DATE: %s\n" date)))) + (and (plist-get info :with-author) + (let ((author (org-export-data (plist-get info :author) info))) + (and (org-string-nw-p author) + (format "#+AUTHOR: %s\n" author)))) + (and (plist-get info :with-email) + (let ((email (org-export-data (plist-get info :email) info))) + (and (org-string-nw-p email) + (format "#+EMAIL: %s\n" email)))) + (and (plist-get info :with-creator) + (org-string-nw-p (plist-get info :creator)) + (format "#+CREATOR: %s\n" (plist-get info :creator))) + contents)) (defun org-org-section (section contents info) "Transcode SECTION element back into Org syntax. @@ -152,28 +213,28 @@ a communication channel." (concat (org-element-normalize-string contents) ;; Insert footnote definitions appearing for the first time in this - ;; section. Indeed, some of them may not be available to narrowing - ;; so we make sure all of them are included in the result. - (let ((footnotes-alist - (org-element-map section 'footnote-reference + ;; section, or in the relative headline title. Indeed, some of + ;; them may not be available to narrowing so we make sure all of + ;; them are included in the result. + (let ((footnotes + (org-element-map + (list (org-export-get-parent-headline section) section) + 'footnote-reference (lambda (fn) (and (eq (org-element-property :type fn) 'standard) (org-export-footnote-first-reference-p fn info) - (cons (org-element-property :label fn) - (org-export-get-footnote-definition fn info)))) - info))) - (and footnotes-alist - (concat "\n" - (mapconcat - (lambda (d) - (org-element-normalize-string - (concat (format "[%s] "(car d)) - (org-export-data (cdr d) info)))) - footnotes-alist "\n")))) - (make-string (or (org-element-property :post-blank section) 0) ?\n))) + (org-element-normalize-string + (format "[fn:%s] %s" + (org-element-property :label fn) + (org-export-data + (org-export-get-footnote-definition fn info) + info))))) + info nil 'headline t))) + (and footnotes (concat "\n" (mapconcat #'identity footnotes "\n")))))) ;;;###autoload -(defun org-org-export-as-org (&optional async subtreep visible-only ext-plist) +(defun org-org-export-as-org + (&optional async subtreep visible-only body-only ext-plist) "Export current buffer to an Org buffer. If narrowing is active in the current buffer, only export its @@ -192,6 +253,9 @@ first. When optional argument VISIBLE-ONLY is non-nil, don't export contents of hidden elements. +When optional argument BODY-ONLY is non-nil, strip document +keywords from output. + EXT-PLIST, when provided, is a property list with external parameters overriding Org default settings, but still inferior to file-local settings. @@ -201,10 +265,11 @@ be displayed when `org-export-show-temporary-export-buffer' is non-nil." (interactive) (org-export-to-buffer 'org "*Org ORG Export*" - async subtreep visible-only nil ext-plist (lambda () (org-mode)))) + async subtreep visible-only body-only ext-plist (lambda () (org-mode)))) ;;;###autoload -(defun org-org-export-to-org (&optional async subtreep visible-only ext-plist) +(defun org-org-export-to-org + (&optional async subtreep visible-only body-only ext-plist) "Export current buffer to an org file. If narrowing is active in the current buffer, only export its @@ -223,6 +288,9 @@ first. When optional argument VISIBLE-ONLY is non-nil, don't export contents of hidden elements. +When optional argument BODY-ONLY is non-nil, strip document +keywords from output. + EXT-PLIST, when provided, is a property list with external parameters overriding Org default settings, but still inferior to file-local settings. @@ -231,7 +299,7 @@ Return output file name." (interactive) (let ((outfile (org-export-output-file-name ".org" subtreep))) (org-export-to-file 'org outfile - async subtreep visible-only nil ext-plist))) + async subtreep visible-only body-only ext-plist))) ;;;###autoload (defun org-org-publish-to-org (plist filename pub-dir) @@ -244,7 +312,8 @@ publishing directory. Return output file name." (org-publish-org-to 'org filename ".org" plist pub-dir) (when (plist-get plist :htmlized-source) - (require 'htmlize) + (or (require 'htmlize nil t) + (error "Please install htmlize from https://github.com/hniksic/emacs-htmlize")) (require 'ox-html) (let* ((org-inhibit-startup t) (htmlize-output-type 'css) @@ -255,7 +324,7 @@ Return output file name." newbuf) (with-current-buffer work-buffer (org-font-lock-ensure) - (show-all) + (outline-show-all) (org-show-block-all) (setq newbuf (htmlize-buffer))) (with-current-buffer newbuf diff --git a/lisp/org/ox-publish.el b/lisp/org/ox-publish.el index 4ebc073990e..c2416dba381 100644 --- a/lisp/org/ox-publish.el +++ b/lisp/org/ox-publish.el @@ -1,4 +1,4 @@ -;;; ox-publish.el --- Publish Related Org Mode Files as a Website +;;; ox-publish.el --- Publish Related Org Mode Files as a Website -*- lexical-binding: t; -*- ;; Copyright (C) 2006-2017 Free Software Foundation, Inc. ;; Author: David O'Toole <dto@gnu.org> @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -38,7 +38,7 @@ ;;; Code: -(eval-when-compile (require 'cl)) +(require 'cl-lib) (require 'format-spec) (require 'ox) @@ -46,24 +46,28 @@ ;;; Variables -(defvar org-publish-temp-files nil - "Temporary list of files to be published.") - ;; Here, so you find the variable right before it's used the first time: (defvar org-publish-cache nil "This will cache timestamps and titles for files in publishing projects. Blocks could hash sha1 values here.") +(defvar org-publish-after-publishing-hook nil + "Hook run each time a file is published. +Every function in this hook will be called with two arguments: +the name of the original file and the name of the file +produced.") + (defgroup org-publish nil - "Options for publishing a set of Org-mode and related files." + "Options for publishing a set of files." :tag "Org Publishing" :group 'org) (defcustom org-publish-project-alist nil "Association list to control publishing behavior. -Each element of the alist is a publishing “project”. The CAR of +\\<org-mode-map> +Each element of the alist is a publishing project. The car of each element is a string, uniquely identifying the project. The -CDR of each element is in one of the following forms: +cdr of each element is in one of the following forms: 1. A well-formed property list with an even number of elements, alternating keys and values, specifying parameters for the @@ -80,7 +84,7 @@ When the CDR of an element of org-publish-project-alist is in this second form, the elements of the list after `:components' are taken to be components of the project, which group together files requiring different publishing options. When you publish -such a project with \\[org-publish], the components all publish. +such a project with `\\[org-publish]', the components all publish. When a property is given a value in `org-publish-project-alist', its setting overrides the value of the corresponding user @@ -97,13 +101,17 @@ Most properties are optional, but some should always be set: Extension (without the dot!) of source files. This can be a regular expression. If not given, \"org\" will be used as - default extension. + default extension. If it is `any', include all the files, + even without extension. `:publishing-directory' Directory (possibly remote) where output files will be published. +If `:recursive' is non-nil files in sub-directories of +`:base-directory' are considered. + The `:exclude' property may be used to prevent certain files from being published. Its value may be a string or regexp matching file names you don't want to be published. @@ -135,12 +143,16 @@ date. `:preparation-function' Function to be called before publishing this project. This - may also be a list of functions. + may also be a list of functions. Preparation functions are + called with the project properties list as their sole + argument. `:completion-function' Function to be called after publishing this project. This - may also be a list of functions. + may also be a list of functions. Completion functions are + called with the project properties list as their sole + argument. Some properties control details of the Org publishing process, and are equivalent to the corresponding user variables listed in @@ -169,7 +181,9 @@ included. See the back-end documentation for more information. :with-footnotes `org-export-with-footnotes' :with-inlinetasks `org-export-with-inlinetasks' :with-latex `org-export-with-latex' + :with-planning `org-export-with-planning' :with-priority `org-export-with-priority' + :with-properties `org-export-with-properties' :with-smart-quotes `org-export-with-smart-quotes' :with-special-strings `org-export-with-special-strings' :with-statistics-cookies' `org-export-with-statistics-cookies' @@ -179,7 +193,7 @@ included. See the back-end documentation for more information. :with-tags `org-export-with-tags' :with-tasks `org-export-with-tasks' :with-timestamps `org-export-with-timestamps' - :with-planning `org-export-with-planning' + :with-title `org-export-with-title' :with-todo-keywords `org-export-with-todo-keywords' The following properties may be used to control publishing of @@ -192,18 +206,12 @@ a site-map of files or summary page for a given project. `:sitemap-filename' - Filename for output of sitemap. Defaults to \"sitemap.org\". + Filename for output of site-map. Defaults to \"sitemap.org\". `:sitemap-title' Title of site-map page. Defaults to name of file. - `:sitemap-function' - - Plugin function to use for generation of site-map. Defaults - to `org-publish-org-sitemap', which generates a plain list of - links to all files in the project. - `:sitemap-style' Can be `list' (site-map is just an itemized list of the @@ -211,19 +219,42 @@ a site-map of files or summary page for a given project. structure of the source files is reflected in the site-map). Defaults to `tree'. - `:sitemap-sans-extension' + `:sitemap-format-entry' + + Plugin function used to format entries in the site-map. It + is called with three arguments: the file or directory name + relative to base directory, the site map style and the + current project. It has to return a string. - Remove extension from site-map's file-names. Useful to have - cool URIs (see http://www.w3.org/Provider/Style/URI). - Defaults to nil. + Defaults to `org-publish-sitemap-default-entry', which turns + file names into links and use document titles as + descriptions. For specific formatting needs, one can use + `org-publish-find-date', `org-publish-find-title' and + `org-publish-find-property', to retrieve additional + information about published documents. + + `:sitemap-function' + + Plugin function to use for generation of site-map. It is + called with two arguments: the title of the site-map, as + a string, and a representation of the files involved in the + project, as returned by `org-list-to-lisp'. The latter can + further be transformed using `org-list-to-generic', + `org-list-to-subtree' and alike. It has to return a string. + + Defaults to `org-publish-sitemap-default', which generates + a plain list of links to all files in the project. If you create a site-map file, adjust the sorting like this: `:sitemap-sort-folders' Where folders should appear in the site-map. Set this to - `first' (default) or `last' to display folders first or last, - respectively. Any other value will mix files and folders. + `first' or `last' to display folders first or last, + respectively. When set to `ignore' (default), folders are + ignored altogether. Any other value will mix files and + folders. This variable has no effect when site-map style is + `tree'. `:sitemap-sort-files' @@ -285,17 +316,28 @@ You can overwrite this default per project in your :group 'org-export-publish :type 'symbol) -(defcustom org-publish-sitemap-sort-folders 'first - "A symbol, denoting if folders are sorted first in sitemaps. -Possible values are `first', `last', and nil. +(defcustom org-publish-sitemap-sort-folders 'ignore + "A symbol, denoting if folders are sorted first in site-maps. + +Possible values are `first', `last', `ignore' and nil. If `first', folders will be sorted before files. If `last', folders are sorted to the end after the files. -Any other value will not mix files and folders. +If `ignore', folders do not appear in the site-map. +Any other value will mix files and folders. You can overwrite this default per project in your -`org-publish-project-alist', using `:sitemap-sort-folders'." +`org-publish-project-alist', using `:sitemap-sort-folders'. + +This variable is ignored when site-map style is `tree'." :group 'org-export-publish - :type 'symbol) + :type '(choice + (const :tag "Folders before files" first) + (const :tag "Folders after files" last) + (const :tag "No folder in site-map" ignore) + (const :tag "Mix folders and files" nil)) + :version "26.1" + :package-version '(Org . "9.1") + :safe #'symbolp) (defcustom org-publish-sitemap-sort-ignore-case nil "Non-nil when site-map sorting should ignore case. @@ -305,25 +347,8 @@ You can overwrite this default per project in your :group 'org-export-publish :type 'boolean) -(defcustom org-publish-sitemap-date-format "%Y-%m-%d" - "Format for printing a date in the sitemap. -See `format-time-string' for allowed formatters." - :group 'org-export-publish - :type 'string) - -(defcustom org-publish-sitemap-file-entry-format "%t" - "Format string for site-map file entry. -You could use brackets to delimit on what part the link will be. - -%t is the title. -%a is the author. -%d is the date formatted using `org-publish-sitemap-date-format'." - :group 'org-export-publish - :type 'string) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Timestamp-related functions (defun org-publish-timestamp-filename (filename &optional pub-dir pub-func) @@ -333,7 +358,7 @@ You could use brackets to delimit on what part the link will be. (concat "X" (if (fboundp 'sha1) (sha1 filename) (md5 filename)))) (defun org-publish-needed-p - (filename &optional pub-dir pub-func true-pub-dir base-dir) + (filename &optional pub-dir pub-func _true-pub-dir base-dir) "Non-nil if FILENAME should be published in PUB-DIR using PUB-FUNC. TRUE-PUB-DIR is where the file will truly end up. Currently we are not using this - maybe it can eventually be used to check if @@ -346,11 +371,11 @@ still decide about that independently." filename pub-dir pub-func base-dir)))) (if rtn (message "Publishing file %s using `%s'" filename pub-func) (when org-publish-list-skipped-files - (message "Skipping unmodified file %s" filename))) + (message "Skipping unmodified file %s" filename))) rtn)) (defun org-publish-update-timestamp - (filename &optional pub-dir pub-func base-dir) + (filename &optional pub-dir pub-func _base-dir) "Update publishing timestamp for file FILENAME. If there is no timestamp, create one." (let ((key (org-publish-timestamp-filename filename pub-dir pub-func)) @@ -359,17 +384,33 @@ If there is no timestamp, create one." (defun org-publish-remove-all-timestamps () "Remove all files in the timestamp directory." - (let ((dir org-publish-timestamp-directory) - files) + (let ((dir org-publish-timestamp-directory)) (when (and (file-exists-p dir) (file-directory-p dir)) - (mapc 'delete-file (directory-files dir 'full "[^.]\\'")) + (mapc #'delete-file (directory-files dir 'full "[^.]\\'")) (org-publish-reset-cache)))) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Getting project information out of `org-publish-project-alist' +(defun org-publish-property (property project &optional default) + "Return value PROPERTY, as a symbol, in PROJECT. +DEFAULT is returned when PROPERTY is not actually set in PROJECT +definition." + (let ((properties (cdr project))) + (if (plist-member properties property) + (plist-get properties property) + default))) + +(defun org-publish--expand-file-name (file project) + "Return full file name for FILE in PROJECT. +When FILE is a relative file name, it is expanded according to +project base directory. Always return the true name of the file, +ignoring symlinks." + (file-truename + (if (file-name-absolute-p file) file + (expand-file-name file (org-publish-property :base-directory project))))) + (defun org-publish-expand-projects (projects-alist) "Expand projects in PROJECTS-ALIST. This splices all the components into the list." @@ -377,178 +418,111 @@ This splices all the components into the list." (while (setq p (pop rest)) (if (setq components (plist-get (cdr p) :components)) (setq rest (append - (mapcar (lambda (x) (assoc x org-publish-project-alist)) - components) + (mapcar + (lambda (x) + (or (assoc x org-publish-project-alist) + (user-error "Unknown component %S in project %S" + x (car p)))) + components) rest)) (push p rtn))) (nreverse (delete-dups (delq nil rtn))))) -(defvar org-publish-sitemap-sort-files) -(defvar org-publish-sitemap-sort-folders) -(defvar org-publish-sitemap-ignore-case) -(defvar org-publish-sitemap-requested) -(defvar org-publish-sitemap-date-format) -(defvar org-publish-sitemap-file-entry-format) -(defun org-publish-compare-directory-files (a b) - "Predicate for `sort', that sorts folders and files for sitemap." - (let ((retval t)) - (when (or org-publish-sitemap-sort-files org-publish-sitemap-sort-folders) - ;; First we sort files: - (when org-publish-sitemap-sort-files - (case org-publish-sitemap-sort-files - (alphabetically - (let* ((adir (file-directory-p a)) - (aorg (and (string-match "\\.org$" a) (not adir))) - (bdir (file-directory-p b)) - (borg (and (string-match "\\.org$" b) (not bdir))) - (A (if aorg (concat (file-name-directory a) - (org-publish-find-title a)) a)) - (B (if borg (concat (file-name-directory b) - (org-publish-find-title b)) b))) - (setq retval (if org-publish-sitemap-ignore-case - (not (string-lessp (upcase B) (upcase A))) - (not (string-lessp B A)))))) - ((anti-chronologically chronologically) - (let* ((adate (org-publish-find-date a)) - (bdate (org-publish-find-date b)) - (A (+ (lsh (car adate) 16) (cadr adate))) - (B (+ (lsh (car bdate) 16) (cadr bdate)))) - (setq retval - (if (eq org-publish-sitemap-sort-files 'chronologically) (<= A B) - (>= A B))))))) - ;; Directory-wise wins: - (when org-publish-sitemap-sort-folders - ;; a is directory, b not: - (cond - ((and (file-directory-p a) (not (file-directory-p b))) - (setq retval (equal org-publish-sitemap-sort-folders 'first))) - ;; a is not a directory, but b is: - ((and (not (file-directory-p a)) (file-directory-p b)) - (setq retval (equal org-publish-sitemap-sort-folders 'last)))))) - retval)) - -(defun org-publish-get-base-files-1 - (base-dir &optional recurse match skip-file skip-dir) - "Set `org-publish-temp-files' with files from BASE-DIR directory. -If RECURSE is non-nil, check BASE-DIR recursively. If MATCH is -non-nil, restrict this list to the files matching the regexp -MATCH. If SKIP-FILE is non-nil, skip file matching the regexp -SKIP-FILE. If SKIP-DIR is non-nil, don't check directories -matching the regexp SKIP-DIR when recursing through BASE-DIR." - (mapc (lambda (f) - (let ((fd-p (file-directory-p f)) - (fnd (file-name-nondirectory f))) - (if (and fd-p recurse - (not (string-match "^\\.+$" fnd)) - (if skip-dir (not (string-match skip-dir fnd)) t)) - (org-publish-get-base-files-1 - f recurse match skip-file skip-dir) - (unless (or fd-p ;; this is a directory - (and skip-file (string-match skip-file fnd)) - (not (file-exists-p (file-truename f))) - (not (string-match match fnd))) - - (pushnew f org-publish-temp-files))))) - (let ((all-files (if (not recurse) (directory-files base-dir t match) - ;; If RECURSE is non-nil, we want all files - ;; matching MATCH and sub-directories. - (org-remove-if-not - (lambda (file) - (or (file-directory-p file) - (and match (string-match match file)))) - (directory-files base-dir t))))) - (if (not org-publish-sitemap-requested) all-files - (sort all-files 'org-publish-compare-directory-files))))) - -(defun org-publish-get-base-files (project &optional exclude-regexp) - "Return a list of all files in PROJECT. -If EXCLUDE-REGEXP is set, this will be used to filter out -matching filenames." - (let* ((project-plist (cdr project)) - (base-dir (file-name-as-directory - (plist-get project-plist :base-directory))) - (include-list (plist-get project-plist :include)) - (recurse (plist-get project-plist :recursive)) - (extension (or (plist-get project-plist :base-extension) "org")) - ;; sitemap-... variables are dynamically scoped for - ;; org-publish-compare-directory-files: - (org-publish-sitemap-requested - (plist-get project-plist :auto-sitemap)) - (sitemap-filename - (or (plist-get project-plist :sitemap-filename) "sitemap.org")) - (org-publish-sitemap-sort-folders - (if (plist-member project-plist :sitemap-sort-folders) - (plist-get project-plist :sitemap-sort-folders) - org-publish-sitemap-sort-folders)) - (org-publish-sitemap-sort-files - (cond ((plist-member project-plist :sitemap-sort-files) - (plist-get project-plist :sitemap-sort-files)) - ;; For backward compatibility: - ((plist-member project-plist :sitemap-alphabetically) - (if (plist-get project-plist :sitemap-alphabetically) - 'alphabetically nil)) - (t org-publish-sitemap-sort-files))) - (org-publish-sitemap-ignore-case - (if (plist-member project-plist :sitemap-ignore-case) - (plist-get project-plist :sitemap-ignore-case) - org-publish-sitemap-sort-ignore-case)) - (match (if (eq extension 'any) "^[^\\.]" - (concat "^[^\\.].*\\.\\(" extension "\\)$")))) - ;; Make sure `org-publish-sitemap-sort-folders' has an accepted - ;; value. - (unless (memq org-publish-sitemap-sort-folders '(first last)) - (setq org-publish-sitemap-sort-folders nil)) - - (setq org-publish-temp-files nil) - (if org-publish-sitemap-requested - (pushnew (expand-file-name (concat base-dir sitemap-filename)) - org-publish-temp-files)) - (org-publish-get-base-files-1 base-dir recurse match - ;; FIXME distinguish exclude regexp - ;; for skip-file and skip-dir? - exclude-regexp exclude-regexp) - (mapc (lambda (f) - (pushnew - (expand-file-name (concat base-dir f)) - org-publish-temp-files)) - include-list) - org-publish-temp-files)) +(defun org-publish-get-base-files (project) + "Return a list of all files in PROJECT." + (let* ((base-dir (file-name-as-directory + (org-publish-property :base-directory project))) + (extension (or (org-publish-property :base-extension project) "org")) + (match (if (eq extension 'any) "" + (format "^[^\\.].*\\.\\(%s\\)$" extension))) + (base-files + (cl-remove-if #'file-directory-p + (if (org-publish-property :recursive project) + (directory-files-recursively base-dir match) + (directory-files base-dir t match t))))) + (org-uniquify + (append + ;; Files from BASE-DIR. Apply exclusion filter before adding + ;; included files. + (let ((exclude-regexp (org-publish-property :exclude project))) + (if exclude-regexp + (cl-remove-if + (lambda (f) + ;; Match against relative names, yet BASE-DIR file + ;; names are absolute. + (string-match exclude-regexp + (file-relative-name f base-dir))) + base-files) + base-files)) + ;; Sitemap file. + (and (org-publish-property :auto-sitemap project) + (list (expand-file-name + (or (org-publish-property :sitemap-filename project) + "sitemap.org") + base-dir))) + ;; Included files. + (mapcar (lambda (f) (expand-file-name f base-dir)) + (org-publish-property :include project)))))) (defun org-publish-get-project-from-filename (filename &optional up) - "Return the project that FILENAME belongs to." - (let* ((filename (expand-file-name filename)) - project-name) - - (catch 'p-found - (dolist (prj org-publish-project-alist) - (unless (plist-get (cdr prj) :components) - ;; [[info:org:Selecting%20files]] shows how this is supposed to work: - (let* ((r (plist-get (cdr prj) :recursive)) - (b (expand-file-name (file-name-as-directory - (plist-get (cdr prj) :base-directory)))) - (x (or (plist-get (cdr prj) :base-extension) "org")) - (e (plist-get (cdr prj) :exclude)) - (i (plist-get (cdr prj) :include)) - (xm (concat "^" b (if r ".+" "[^/]+") "\\.\\(" x "\\)$"))) - (when - (or (and i - (member filename - (mapcar (lambda (file) - (expand-file-name file b)) - i))) - (and (not (and e (string-match e filename))) - (string-match xm filename))) - (setq project-name (car prj)) - (throw 'p-found project-name)))))) - (when up - (dolist (prj org-publish-project-alist) - (if (member project-name (plist-get (cdr prj) :components)) - (setq project-name (car prj))))) - (assoc project-name org-publish-project-alist))) + "Return a project that FILENAME belongs to. +When UP is non-nil, return a meta-project (i.e., with a :components part) +publishing FILENAME." + (let* ((filename (file-truename filename)) + (project + (cl-some + (lambda (p) + ;; Ignore meta-projects. + (unless (org-publish-property :components p) + (let ((base (file-truename + (org-publish-property :base-directory p)))) + (cond + ;; Check if FILENAME is explicitly included in one + ;; project. + ((cl-some (lambda (f) (file-equal-p f filename)) + (mapcar (lambda (f) (expand-file-name f base)) + (org-publish-property :include p))) + p) + ;; Exclude file names matching :exclude property. + ((let ((exclude-re (org-publish-property :exclude p))) + (and exclude-re + (string-match-p exclude-re + (file-relative-name filename base)))) + nil) + ;; Check :extension. Handle special `any' + ;; extension. + ((let ((extension (org-publish-property :base-extension p))) + (not (or (eq extension 'any) + (string= (or extension "org") + (file-name-extension filename))))) + nil) + ;; Check if FILENAME belong to project's base + ;; directory, or some of its sub-directories + ;; if :recursive in non-nil. + ((org-publish-property :recursive p) + (and (file-in-directory-p filename base) p)) + ((file-equal-p base (file-name-directory filename)) p) + (t nil))))) + org-publish-project-alist))) + (cond + ((not project) nil) + ((not up) project) + ;; When optional argument UP is non-nil, return the top-most + ;; meta-project effectively publishing FILENAME. + (t + (letrec ((find-parent-project + (lambda (project) + (or (cl-some + (lambda (p) + (and (member (car project) + (org-publish-property :components p)) + (funcall find-parent-project p))) + org-publish-project-alist) + project)))) + (funcall find-parent-project project)))))) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Tools for publishing functions in back-ends (defun org-publish-org-to (backend filename extension plist &optional pub-dir) @@ -567,29 +541,31 @@ Return output file name." (unless (or (not pub-dir) (file-exists-p pub-dir)) (make-directory pub-dir t)) ;; Check if a buffer visiting FILENAME is already open. (let* ((org-inhibit-startup t) - (visitingp (find-buffer-visiting filename)) - (work-buffer (or visitingp (find-file-noselect filename)))) - (prog1 (with-current-buffer work-buffer - (let ((output-file - (org-export-output-file-name extension nil pub-dir)) - (body-p (plist-get plist :body-only))) - (org-export-to-file backend output-file - nil nil nil body-p - ;; Add `org-publish-collect-numbering' and - ;; `org-publish-collect-index' to final output - ;; filters. The latter isn't dependent on - ;; `:makeindex', since we want to keep it up-to-date - ;; in cache anyway. - (org-combine-plists - plist - `(:filter-final-output - ,(cons 'org-publish-collect-numbering - (cons 'org-publish-collect-index - (plist-get plist :filter-final-output)))))))) + (visiting (find-buffer-visiting filename)) + (work-buffer (or visiting (find-file-noselect filename)))) + (unwind-protect + (with-current-buffer work-buffer + (let ((output (org-export-output-file-name extension nil pub-dir))) + (org-export-to-file backend output + nil nil nil (plist-get plist :body-only) + ;; Add `org-publish--store-crossrefs' and + ;; `org-publish-collect-index' to final output filters. + ;; The latter isn't dependent on `:makeindex', since we + ;; want to keep it up-to-date in cache anyway. + (org-combine-plists + plist + `(:crossrefs + ,(org-publish-cache-get-file-property + ;; Normalize file names in cache. + (file-truename filename) :crossrefs nil t) + :filter-final-output + (org-publish--store-crossrefs + org-publish-collect-index + ,@(plist-get plist :filter-final-output))))))) ;; Remove opened buffer in the process. - (unless visitingp (kill-buffer work-buffer))))) + (unless visiting (kill-buffer work-buffer))))) -(defun org-publish-attachment (plist filename pub-dir) +(defun org-publish-attachment (_plist filename pub-dir) "Publish a file with no transformation of any kind. FILENAME is the filename of the Org file to be published. PLIST @@ -599,268 +575,327 @@ publishing directory. Return output file name." (unless (file-directory-p pub-dir) (make-directory pub-dir t)) - (or (equal (expand-file-name (file-name-directory filename)) - (file-name-as-directory (expand-file-name pub-dir))) - (copy-file filename - (expand-file-name (file-name-nondirectory filename) pub-dir) - t))) + (let ((output (expand-file-name (file-name-nondirectory filename) pub-dir))) + (unless (file-equal-p (expand-file-name (file-name-directory filename)) + (file-name-as-directory (expand-file-name pub-dir))) + (copy-file filename output t)) + ;; Return file name. + output)) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Publishing files, sets of files, and indices +;;; Publishing files, sets of files (defun org-publish-file (filename &optional project no-cache) "Publish file FILENAME from PROJECT. -If NO-CACHE is not nil, do not initialize org-publish-cache and -write it to disk. This is needed, since this function is used to -publish single files, when entire projects are published. -See `org-publish-projects'." +If NO-CACHE is not nil, do not initialize `org-publish-cache'. +This is needed, since this function is used to publish single +files, when entire projects are published (see +`org-publish-projects')." (let* ((project (or project - (or (org-publish-get-project-from-filename filename) - (error "File %s not part of any known project" - (abbreviate-file-name filename))))) + (org-publish-get-project-from-filename filename) + (user-error "File %S is not part of any known project" + (abbreviate-file-name filename)))) (project-plist (cdr project)) - (ftname (expand-file-name filename)) (publishing-function - (or (plist-get project-plist :publishing-function) - (error "No publishing function chosen"))) + (pcase (org-publish-property :publishing-function project) + (`nil (user-error "No publishing function chosen")) + ((and f (pred listp)) f) + (f (list f)))) (base-dir (file-name-as-directory - (expand-file-name - (or (plist-get project-plist :base-directory) - (error "Project %s does not have :base-directory defined" - (car project)))))) - (pub-dir + (or (org-publish-property :base-directory project) + (user-error "Project %S does not have :base-directory defined" + (car project))))) + (pub-base-dir (file-name-as-directory - (file-truename - (or (eval (plist-get project-plist :publishing-directory)) - (error "Project %s does not have :publishing-directory defined" - (car project)))))) - tmp-pub-dir) + (or (org-publish-property :publishing-directory project) + (user-error + "Project %S does not have :publishing-directory defined" + (car project))))) + (pub-dir + (file-name-directory + (expand-file-name (file-relative-name filename base-dir) + pub-base-dir)))) (unless no-cache (org-publish-initialize-cache (car project))) - (setq tmp-pub-dir - (file-name-directory - (concat pub-dir - (and (string-match (regexp-quote base-dir) ftname) - (substring ftname (match-end 0)))))) - (if (listp publishing-function) - ;; allow chain of publishing functions - (mapc (lambda (f) - (when (org-publish-needed-p - filename pub-dir f tmp-pub-dir base-dir) - (funcall f project-plist filename tmp-pub-dir) - (org-publish-update-timestamp filename pub-dir f base-dir))) - publishing-function) - (when (org-publish-needed-p - filename pub-dir publishing-function tmp-pub-dir base-dir) - (funcall publishing-function project-plist filename tmp-pub-dir) - (org-publish-update-timestamp - filename pub-dir publishing-function base-dir))) - (unless no-cache (org-publish-write-cache-file)))) - -(defun org-publish--run-functions (functions) - (cond - ((null functions) nil) - ((functionp functions) (funcall functions)) - ((consp functions) (mapc #'funcall functions)) - (t (error "Neither a function nor a list: %S" functions)))) + ;; Allow chain of publishing functions. + (dolist (f publishing-function) + (when (org-publish-needed-p filename pub-base-dir f pub-dir base-dir) + (let ((output (funcall f project-plist filename pub-dir))) + (org-publish-update-timestamp filename pub-base-dir f base-dir) + (run-hook-with-args 'org-publish-after-publishing-hook + filename + output)))) + ;; Make sure to write cache to file after successfully publishing + ;; a file, so as to minimize impact of a publishing failure. + (org-publish-write-cache-file))) (defun org-publish-projects (projects) "Publish all files belonging to the PROJECTS alist. If `:auto-sitemap' is set, publish the sitemap too. If -`:makeindex' is set, also produce a file theindex.org." - (mapc - (lambda (project) - ;; Each project uses its own cache file: - (org-publish-initialize-cache (car project)) - (let* ((project-plist (cdr project)) - (exclude-regexp (plist-get project-plist :exclude)) - (sitemap-p (plist-get project-plist :auto-sitemap)) - (sitemap-filename (or (plist-get project-plist :sitemap-filename) - "sitemap.org")) - (sitemap-function (or (plist-get project-plist :sitemap-function) - 'org-publish-org-sitemap)) - (org-publish-sitemap-date-format - (or (plist-get project-plist :sitemap-date-format) - org-publish-sitemap-date-format)) - (org-publish-sitemap-file-entry-format - (or (plist-get project-plist :sitemap-file-entry-format) - org-publish-sitemap-file-entry-format)) - (preparation-function - (plist-get project-plist :preparation-function)) - (completion-function (plist-get project-plist :completion-function)) - (files (org-publish-get-base-files project exclude-regexp)) - (theindex +`:makeindex' is set, also produce a file \"theindex.org\"." + (dolist (project (org-publish-expand-projects projects)) + (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)))) + ;; Each project uses its own cache file. + (org-publish-initialize-cache (car project)) + (when (org-publish-property :auto-sitemap project) + (let ((sitemap-filename + (or (org-publish-property :sitemap-filename project) + "sitemap.org"))) + (org-publish-sitemap project sitemap-filename))) + ;; Publish all files from PROJECT except "theindex.org". Its + ;; publishing will be deferred until "theindex.inc" is + ;; populated. + (let ((theindex (expand-file-name "theindex.org" - (plist-get project-plist :base-directory)))) - (org-publish--run-functions preparation-function) - (if sitemap-p (funcall sitemap-function project sitemap-filename)) - ;; Publish all files from PROJECT excepted "theindex.org". Its - ;; publishing will be deferred until "theindex.inc" is - ;; populated. - (dolist (file files) - (unless (equal file theindex) - (org-publish-file file project t))) - ;; Populate "theindex.inc", if needed, and publish - ;; "theindex.org". - (when (plist-get project-plist :makeindex) - (org-publish-index-generate-theindex - project (plist-get project-plist :base-directory)) - (org-publish-file theindex project t)) - (org-publish--run-functions completion-function) - (org-publish-write-cache-file))) - (org-publish-expand-projects projects))) - -(defun org-publish-org-sitemap (project &optional sitemap-filename) + (org-publish-property :base-directory project)))) + (dolist (file (org-publish-get-base-files project)) + (unless (file-equal-p file theindex) + (org-publish-file file project t))) + ;; Populate "theindex.inc", if needed, and publish + ;; "theindex.org". + (when (org-publish-property :makeindex project) + (org-publish-index-generate-theindex + project (org-publish-property :base-directory project)) + (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))))) + (org-publish-write-cache-file))) + + +;;; Site map generation + +(defun org-publish--sitemap-files-to-lisp (files project style format-entry) + "Represent FILES as a parsed plain list. +FILES is the list of files in the site map. PROJECT is the +current project. STYLE determines is either `list' or `tree'. +FORMAT-ENTRY is a function called on each file which should +return a string. Return value is a list as returned by +`org-list-to-lisp'." + (let ((root (expand-file-name + (file-name-as-directory + (org-publish-property :base-directory project))))) + (pcase style + (`list + (cons 'unordered + (mapcar + (lambda (f) + (list (funcall format-entry + (file-relative-name f root) + style + project))) + files))) + (`tree + (letrec ((files-only (cl-remove-if #'directory-name-p files)) + (directories (cl-remove-if-not #'directory-name-p files)) + (subtree-to-list + (lambda (dir) + (cons 'unordered + (nconc + ;; Files in DIR. + (mapcar + (lambda (f) + (list (funcall format-entry + (file-relative-name f root) + style + project))) + (cl-remove-if-not + (lambda (f) (string= dir (file-name-directory f))) + files-only)) + ;; Direct sub-directories. + (mapcar + (lambda (sub) + (list (funcall format-entry + (file-relative-name sub root) + style + project) + (funcall subtree-to-list sub))) + (cl-remove-if-not + (lambda (f) + (string= + dir + ;; Parent directory. + (file-name-directory (directory-file-name f)))) + directories))))))) + (funcall subtree-to-list root))) + (_ (user-error "Unknown site-map style: `%s'" style))))) + +(defun org-publish-sitemap (project &optional sitemap-filename) "Create a sitemap of pages in set defined by PROJECT. Optionally set the filename of the sitemap with SITEMAP-FILENAME. Default for SITEMAP-FILENAME is `sitemap.org'." - (let* ((project-plist (cdr project)) - (dir (file-name-as-directory - (plist-get project-plist :base-directory))) - (localdir (file-name-directory dir)) - (indent-str (make-string 2 ?\ )) - (exclude-regexp (plist-get project-plist :exclude)) - (files (nreverse - (org-publish-get-base-files project exclude-regexp))) - (sitemap-filename (concat dir (or sitemap-filename "sitemap.org"))) - (sitemap-title (or (plist-get project-plist :sitemap-title) - (concat "Sitemap for project " (car project)))) - (sitemap-style (or (plist-get project-plist :sitemap-style) - 'tree)) - (sitemap-sans-extension - (plist-get project-plist :sitemap-sans-extension)) - (visiting (find-buffer-visiting sitemap-filename)) - (ifn (file-name-nondirectory sitemap-filename)) - file sitemap-buffer) - (with-current-buffer - (let ((org-inhibit-startup t)) - (setq sitemap-buffer - (or visiting (find-file sitemap-filename)))) - (erase-buffer) - (insert (concat "#+TITLE: " sitemap-title "\n\n")) - (while (setq file (pop files)) - (let ((fn (file-name-nondirectory file)) - (link (file-relative-name file dir)) - (oldlocal localdir)) - (when sitemap-sans-extension - (setq link (file-name-sans-extension link))) - ;; sitemap shouldn't list itself - (unless (equal (file-truename sitemap-filename) - (file-truename file)) - (if (eq sitemap-style 'list) - (message "Generating list-style sitemap for %s" sitemap-title) - (message "Generating tree-style sitemap for %s" sitemap-title) - (setq localdir (concat (file-name-as-directory dir) - (file-name-directory link))) - (unless (string= localdir oldlocal) - (if (string= localdir dir) - (setq indent-str (make-string 2 ?\ )) - (let ((subdirs - (split-string - (directory-file-name - (file-name-directory - (file-relative-name localdir dir))) "/")) - (subdir "") - (old-subdirs (split-string - (file-relative-name oldlocal dir) "/"))) - (setq indent-str (make-string 2 ?\ )) - (while (string= (car old-subdirs) (car subdirs)) - (setq indent-str (concat indent-str (make-string 2 ?\ ))) - (pop old-subdirs) - (pop subdirs)) - (dolist (d subdirs) - (setq subdir (concat subdir d "/")) - (insert (concat indent-str " + " d "\n")) - (setq indent-str (make-string - (+ (length indent-str) 2) ?\ ))))))) - ;; This is common to 'flat and 'tree - (let ((entry - (org-publish-format-file-entry - org-publish-sitemap-file-entry-format file project-plist)) - (regexp "\\(.*\\)\\[\\([^][]+\\)\\]\\(.*\\)")) - (cond ((string-match-p regexp entry) - (string-match regexp entry) - (insert (concat indent-str " + " (match-string 1 entry) - "[[file:" link "][" - (match-string 2 entry) - "]]" (match-string 3 entry) "\n"))) - (t - (insert (concat indent-str " + [[file:" link "][" - entry - "]]\n")))))))) - (save-buffer)) - (or visiting (kill-buffer sitemap-buffer)))) - -(defun org-publish-format-file-entry (fmt file project-plist) - (format-spec - fmt - `((?t . ,(org-publish-find-title file t)) - (?d . ,(format-time-string org-publish-sitemap-date-format - (org-publish-find-date file))) - (?a . ,(or (plist-get project-plist :author) user-full-name))))) - -(defun org-publish-find-title (file &optional reset) - "Find the title of FILE in project." - (or - (and (not reset) (org-publish-cache-get-file-property file :title nil t)) - (let* ((org-inhibit-startup t) - (visiting (find-buffer-visiting file)) - (buffer (or visiting (find-file-noselect file)))) - (with-current-buffer buffer - (let ((title - (let ((property - (plist-get - ;; protect local variables in open buffers - (if visiting - (org-export-with-buffer-copy (org-export-get-environment)) - (org-export-get-environment)) - :title))) - (if property - (org-no-properties (org-element-interpret-data property)) - (file-name-nondirectory (file-name-sans-extension file)))))) - (unless visiting (kill-buffer buffer)) - (org-publish-cache-set-file-property file :title title) - title))))) - -(defun org-publish-find-date (file) - "Find the date of FILE in project. + (let* ((root (expand-file-name + (file-name-as-directory + (org-publish-property :base-directory project)))) + (sitemap-filename (concat root (or sitemap-filename "sitemap.org"))) + (title (or (org-publish-property :sitemap-title project) + (concat "Sitemap for project " (car project)))) + (style (or (org-publish-property :sitemap-style project) + 'tree)) + (sitemap-builder (or (org-publish-property :sitemap-function project) + #'org-publish-sitemap-default)) + (format-entry (or (org-publish-property :sitemap-format-entry project) + #'org-publish-sitemap-default-entry)) + (sort-folders + (org-publish-property :sitemap-sort-folders project + org-publish-sitemap-sort-folders)) + (sort-files + (org-publish-property :sitemap-sort-files project + org-publish-sitemap-sort-files)) + (ignore-case + (org-publish-property :sitemap-ignore-case project + org-publish-sitemap-sort-ignore-case)) + (org-file-p (lambda (f) (equal "org" (file-name-extension f)))) + (sort-predicate + (lambda (a b) + (let ((retval t)) + ;; First we sort files: + (pcase sort-files + (`alphabetically + (let ((A (if (funcall org-file-p a) + (concat (file-name-directory a) + (org-publish-find-title a project)) + a)) + (B (if (funcall org-file-p b) + (concat (file-name-directory b) + (org-publish-find-title b project)) + b))) + (setq retval + (if ignore-case + (not (string-lessp (upcase B) (upcase A))) + (not (string-lessp B A)))))) + ((or `anti-chronologically `chronologically) + (let* ((adate (org-publish-find-date a project)) + (bdate (org-publish-find-date b project)) + (A (+ (lsh (car adate) 16) (cadr adate))) + (B (+ (lsh (car bdate) 16) (cadr bdate)))) + (setq retval + (if (eq sort-files 'chronologically) + (<= A B) + (>= A B))))) + (`nil nil) + (_ (user-error "Invalid sort value %s" sort-files))) + ;; Directory-wise wins: + (when (memq sort-folders '(first last)) + ;; a is directory, b not: + (cond + ((and (file-directory-p a) (not (file-directory-p b))) + (setq retval (eq sort-folders 'first))) + ;; a is not a directory, but b is: + ((and (not (file-directory-p a)) (file-directory-p b)) + (setq retval (eq sort-folders 'last))))) + retval)))) + (message "Generating sitemap for %s" title) + (with-temp-file sitemap-filename + (insert + (let ((files (remove sitemap-filename + (org-publish-get-base-files project)))) + ;; Add directories, if applicable. + (unless (and (eq style 'list) (eq sort-folders 'ignore)) + (setq files + (nconc (remove root (org-uniquify + (mapcar #'file-name-directory files))) + files))) + ;; Eventually sort all entries. + (when (or sort-files (not (memq sort-folders 'ignore))) + (setq files (sort files sort-predicate))) + (funcall sitemap-builder + title + (org-publish--sitemap-files-to-lisp + files project style format-entry))))))) + +(defun org-publish-find-property (file property project &optional backend) + "Find the PROPERTY of FILE in project. + +PROPERTY is a keyword referring to an export option, as defined +in `org-export-options-alist' or in export back-ends. In the +latter case, optional argument BACKEND has to be set to the +back-end where the option is defined, e.g., + + (org-publish-find-property file :subtitle 'latex) + +Return value may be a string or a list, depending on the type of +PROPERTY, i.e. \"behavior\" parameter from `org-export-options-alist'." + (let ((file (org-publish--expand-file-name file project))) + (when (and (file-readable-p file) (not (directory-name-p file))) + (let* ((org-inhibit-startup t) + (visiting (find-buffer-visiting file)) + (buffer (or visiting (find-file-noselect file)))) + (unwind-protect + (plist-get (with-current-buffer buffer + (if (not visiting) (org-export-get-environment backend) + ;; Protect local variables in open buffers. + (org-export-with-buffer-copy + (org-export-get-environment backend)))) + property) + (unless visiting (kill-buffer buffer))))))) + +(defun org-publish-find-title (file project) + "Find the title of FILE in PROJECT." + (let ((file (org-publish--expand-file-name file project))) + (or (org-publish-cache-get-file-property file :title nil t) + (let* ((parsed-title (org-publish-find-property file :title project)) + (title + (if parsed-title + ;; Remove property so that the return value is + ;; cache-able (i.e., it can be `read' back). + (org-no-properties + (org-element-interpret-data parsed-title)) + (file-name-nondirectory (file-name-sans-extension file))))) + (org-publish-cache-set-file-property file :title title) + title)))) + +(defun org-publish-find-date (file project) + "Find the date of FILE in PROJECT. This function assumes FILE is either a directory or an Org file. If FILE is an Org file and provides a DATE keyword use it. In any other case use the file system's modification time. Return time in `current-time' format." - (if (file-directory-p file) (nth 5 (file-attributes file)) - (let* ((org-inhibit-startup t) - (visiting (find-buffer-visiting file)) - (file-buf (or visiting (find-file-noselect file nil))) - (date (plist-get - (with-current-buffer file-buf - (if visiting - (org-export-with-buffer-copy (org-export-get-environment)) - (org-export-get-environment))) - :date))) - (unless visiting (kill-buffer file-buf)) - ;; DATE is either a timestamp object or a secondary string. If it - ;; is a timestamp or if the secondary string contains a timestamp, - ;; convert it to internal format. Otherwise, use FILE - ;; modification time. - (cond ((eq (org-element-type date) 'timestamp) - (org-time-string-to-time (org-element-interpret-data date))) - ((let ((ts (and (consp date) (assq 'timestamp date)))) - (and ts - (let ((value (org-element-interpret-data ts))) - (and (org-string-nw-p value) - (org-time-string-to-time value)))))) - ((file-exists-p file) (nth 5 (file-attributes file))) - (t (error "No such file: \"%s\"" file)))))) - + (let ((file (org-publish--expand-file-name file project))) + (if (file-directory-p file) (nth 5 (file-attributes file)) + (let ((date (org-publish-find-property file :date project))) + ;; DATE is a secondary string. If it contains a time-stamp, + ;; convert it to internal format. Otherwise, use FILE + ;; modification time. + (cond ((let ((ts (and (consp date) (assq 'timestamp date)))) + (and ts + (let ((value (org-element-interpret-data ts))) + (and (org-string-nw-p value) + (org-time-string-to-time value)))))) + ((file-exists-p file) (nth 5 (file-attributes file))) + (t (error "No such file: \"%s\"" file))))))) + +(defun org-publish-sitemap-default-entry (entry style project) + "Default format for site map ENTRY, as a string. +ENTRY is a file name. STYLE is the style of the sitemap. +PROJECT is the current project." + (cond ((not (directory-name-p entry)) + (format "[[file:%s][%s]]" + entry + (org-publish-find-title entry project))) + ((eq style 'tree) + ;; Return only last subdir. + (file-name-nondirectory (directory-file-name entry))) + (t entry))) + +(defun org-publish-sitemap-default (title list) + "Default site map, as a string. +TITLE is the the title of the site map. LIST is an internal +representation for the files to include, as returned by +`org-list-to-lisp'. PROJECT is the current project." + (concat "#+TITLE: " title "\n\n" + (org-list-to-org list))) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Interactive publishing functions ;;;###autoload @@ -877,25 +912,28 @@ When optional argument FORCE is non-nil, force publishing all files in PROJECT. With a non-nil optional argument ASYNC, publishing will be done asynchronously, in another process." (interactive - (list - (assoc (org-icompleting-read - "Publish project: " - org-publish-project-alist nil t) - org-publish-project-alist) - current-prefix-arg)) - (let ((project-alist (if (not (stringp project)) (list project) - ;; If this function is called in batch mode, - ;; project is still a string here. - (list (assoc project org-publish-project-alist))))) - (if async - (org-export-async-start (lambda (results) nil) - `(let ((org-publish-use-timestamps-flag - (if ',force nil ,org-publish-use-timestamps-flag))) - (org-publish-projects ',project-alist))) - (save-window-excursion - (let* ((org-publish-use-timestamps-flag - (if force nil org-publish-use-timestamps-flag))) - (org-publish-projects project-alist)))))) + (list (assoc (completing-read "Publish project: " + org-publish-project-alist nil t) + org-publish-project-alist) + current-prefix-arg)) + (let ((project (if (not (stringp project)) project + ;; If this function is called in batch mode, + ;; PROJECT is still a string here. + (assoc project org-publish-project-alist)))) + (cond + ((not project)) + (async + (org-export-async-start (lambda (_) nil) + `(let ((org-publish-use-timestamps-flag + ,(and (not force) org-publish-use-timestamps-flag))) + ;; Expand components right now as external process may not + ;; be aware of complete `org-publish-project-alist'. + (org-publish-projects + ',(org-publish-expand-projects (list project)))))) + (t (save-window-excursion + (let ((org-publish-use-timestamps-flag + (and (not force) org-publish-use-timestamps-flag))) + (org-publish-projects (list project)))))))) ;;;###autoload (defun org-publish-all (&optional force async) @@ -906,7 +944,7 @@ optional argument ASYNC, publishing will be done asynchronously, in another process." (interactive "P") (if async - (org-export-async-start (lambda (results) nil) + (org-export-async-start (lambda (_) nil) `(progn (when ',force (org-publish-remove-all-timestamps)) (let ((org-publish-use-timestamps-flag @@ -928,7 +966,7 @@ asynchronously, in another process." (interactive "P") (let ((file (buffer-file-name (buffer-base-buffer)))) (if async - (org-export-async-start (lambda (results) nil) + (org-export-async-start (lambda (_) nil) `(let ((org-publish-use-timestamps-flag (if ',force nil ,org-publish-use-timestamps-flag))) (org-publish-file ,file))) @@ -954,7 +992,7 @@ the project." ;;; Index generation -(defun org-publish-collect-index (output backend info) +(defun org-publish-collect-index (output _backend info) "Update index for a file in cache. OUTPUT is the output from transcoding current file. BACKEND is @@ -969,7 +1007,7 @@ PARENT is a reference to the headline, if any, containing the original index keyword. When non-nil, this reference is a cons cell. Its CAR is a symbol among `id', `custom-id' and `name' and its CDR is a string." - (let ((file (plist-get info :input-file))) + (let ((file (file-truename (plist-get info :input-file)))) (org-publish-cache-set-file-property file :index (delete-dups @@ -998,8 +1036,7 @@ its CDR is a string." "Retrieve full index from cache and build \"theindex.org\". PROJECT is the project the index relates to. DIRECTORY is the publishing directory." - (let ((all-files (org-publish-get-base-files - project (plist-get (cdr project) :exclude))) + (let ((all-files (org-publish-get-base-files project)) full-index) ;; Compile full index and sort it alphabetically. (dolist (file all-files @@ -1027,10 +1064,11 @@ publishing directory." ;; Compute the first difference between last entry and ;; current one: it tells the level at which new items ;; should be added. - (let* ((rank (if (equal entry last-entry) (1- (length entry)) - (loop for n from 0 to (length entry) - unless (equal (nth n entry) (nth n last-entry)) - return n))) + (let* ((rank + (if (equal entry last-entry) (1- (length entry)) + (cl-loop for n from 0 to (length entry) + unless (equal (nth n entry) (nth n last-entry)) + return n))) (len (length (nthcdr rank entry)))) ;; For each term after the first difference, create ;; a new sub-list with the term as body. Moreover, @@ -1038,18 +1076,18 @@ publishing directory." (dotimes (n len) (insert (concat - (make-string (* (+ rank n) 2) ? ) " - " + (make-string (* (+ rank n) 2) ?\s) " - " (if (not (= (1- len) n)) (nth (+ rank n) entry) ;; Last term: Link it to TARGET, if possible. (let ((target (nth 2 idx))) (format "[[%s][%s]]" ;; Destination. - (case (car target) - ('nil (format "file:%s" file)) - (id (format "id:%s" (cdr target))) - (custom-id (format "file:%s::#%s" file (cdr target))) - (otherwise (format "file:%s::*%s" file (cdr target)))) + (pcase (car target) + (`nil (format "file:%s" file)) + (`id (format "id:%s" (cdr target))) + (`custom-id (format "file:%s::#%s" file (cdr target))) + (_ (format "file:%s::*%s" file (cdr target)))) ;; Description. (car (last entry))))) "\n")))) @@ -1068,31 +1106,76 @@ publishing directory." ;; This part implements tools to resolve [[file.org::*Some headline]] ;; links, where "file.org" belongs to the current project. -(defun org-publish-collect-numbering (output backend info) +(defun org-publish--store-crossrefs (output _backend info) + "Store cross-references for current published file. + +OUTPUT is the produced output, as a string. BACKEND is the export +back-end used, as a symbol. INFO is the final export state, as +a plist. + +This function is meant to be used as a final output filter. See +`org-publish-org-to'." (org-publish-cache-set-file-property - (plist-get info :input-file) :numbering - (mapcar (lambda (entry) - (cons (org-split-string - (replace-regexp-in-string - "\\[[0-9]+%\\]\\|\\[[0-9]+/[0-9]+\\]" "" - (org-element-property :raw-value (car entry)))) - (cdr entry))) - (plist-get info :headline-numbering))) + (file-truename (plist-get info :input-file)) + :crossrefs + ;; Update `:crossrefs' so as to remove unused references and search + ;; cells. Actually used references are extracted from + ;; `:internal-references', with references as strings removed. See + ;; `org-export-get-reference' for details. + (cl-remove-if (lambda (pair) (stringp (car pair))) + (plist-get info :internal-references))) ;; Return output unchanged. output) -(defun org-publish-resolve-external-fuzzy-link (file fuzzy) - "Return numbering for headline matching FUZZY search in FILE. - -Return value is a list of numbers, or nil. This function allows -the resolution of external fuzzy links like: - - [[file.org::*fuzzy][description]]" - (when org-publish-cache - (cdr (assoc (org-split-string - (if (eq (aref fuzzy 0) ?*) (substring fuzzy 1) fuzzy)) - (org-publish-cache-get-file-property - (expand-file-name file) :numbering nil t))))) +(defun org-publish-resolve-external-link (search file) + "Return reference for element matching string SEARCH in FILE. + +Return value is an internal reference, as a string. + +This function allows resolving external links with a search +option, e.g., + + [[file.org::*heading][description]] + [[file.org::#custom-id][description]] + [[file.org::fuzzy][description]] + +It only makes sense to use this if export back-end builds +references with `org-export-get-reference'." + (if (not org-publish-cache) + (progn + (message "Reference %S in file %S cannot be resolved without publishing" + search + file) + "MissingReference") + (let* ((filename (file-truename file)) + (crossrefs + (org-publish-cache-get-file-property filename :crossrefs nil t)) + (cells + (org-export-string-to-search-cell (org-link-unescape search)))) + (or + ;; Look for reference associated to search cells triggered by + ;; LINK. It can match when targeted file has been published + ;; already. + (let ((known (cdr (cl-some (lambda (c) (assoc c crossrefs)) cells)))) + (and known (org-export-format-reference known))) + ;; Search cell is unknown so far. Generate a new internal + ;; reference that will be used when the targeted file will be + ;; published. + (let ((new (org-export-new-reference crossrefs))) + (dolist (cell cells) (push (cons cell new) crossrefs)) + (org-publish-cache-set-file-property filename :crossrefs crossrefs) + (org-export-format-reference new)))))) + +(defun org-publish-file-relative-name (filename info) + "Convert FILENAME to be relative to current project's base directory. +INFO is the plist containing the current export state. The +function does not change relative file names." + (let ((base (plist-get info :base-directory))) + (if (and base + (file-name-absolute-p filename) + (file-in-directory-p filename base)) + (file-relative-name filename base) + filename))) @@ -1109,13 +1192,12 @@ If FREE-CACHE, empty the cache." (error "Cannot find cache-file name in `org-publish-write-cache-file'")) (with-temp-file cache-file (let (print-level print-length) - (insert "(setq org-publish-cache (make-hash-table :test 'equal :weakness nil :size 100))\n") + (insert "(setq org-publish-cache \ +\(make-hash-table :test 'equal :weakness nil :size 100))\n") (maphash (lambda (k v) (insert - (format (concat "(puthash %S " - (if (or (listp v) (symbolp v)) - "'" "") - "%S org-publish-cache)\n") k v))) + (format "(puthash %S %s%S org-publish-cache)\n" + k (if (or (listp v) (symbolp v)) "'" "") v))) org-publish-cache))) (when free-cache (org-publish-reset-cache)))) @@ -1123,7 +1205,8 @@ If FREE-CACHE, empty the cache." "Initialize the projects cache if not initialized yet and return it." (unless project-name - (error "Cannot initialize `org-publish-cache' without projects name in `org-publish-initialize-cache'")) + (error "Cannot initialize `org-publish-cache' without projects name in \ +`org-publish-initialize-cache'")) (unless (file-exists-p org-publish-timestamp-directory) (make-directory org-publish-timestamp-directory t)) @@ -1157,7 +1240,7 @@ If FREE-CACHE, empty the cache." (setq org-publish-cache nil)) (defun org-publish-cache-file-needs-publishing - (filename &optional pub-dir pub-func base-dir) + (filename &optional pub-dir pub-func _base-dir) "Check the timestamp of the last publishing of FILENAME. Return non-nil if the file needs publishing. Also check if any included files have been more recently published, so that @@ -1165,33 +1248,42 @@ the file including them will be republished as well." (unless org-publish-cache (error "`org-publish-cache-file-needs-publishing' called, but no cache present")) - (let* ((case-fold-search t) - (key (org-publish-timestamp-filename filename pub-dir pub-func)) + (let* ((key (org-publish-timestamp-filename filename pub-dir pub-func)) (pstamp (org-publish-cache-get key)) (org-inhibit-startup t) - (visiting (find-buffer-visiting filename)) - included-files-ctime buf) - + included-files-ctime) (when (equal (file-name-extension filename) "org") - (setq buf (find-file (expand-file-name filename))) - (with-current-buffer buf - (goto-char (point-min)) - (while (re-search-forward - "^#\\+INCLUDE:[ \t]+\"\\([^\t\n\r\"]*\\)\"[ \t]*.*$" nil t) - (let* ((included-file (expand-file-name (match-string 1))) - (ctime (org-publish-cache-ctime-of-src included-file))) - (unless (member ctime included-files-ctime) - ;; FIXME: The original code insisted on appending this ctime - ;; to the end of the list, even tho the order seems irrelevant. - (setq included-files-ctime - (append included-files-ctime (list ctime))))))) - (unless visiting (kill-buffer buf))) - (if (null pstamp) t - (let ((ctime (org-publish-cache-ctime-of-src filename))) - (or (< pstamp ctime) - (when included-files-ctime - (not (null (delq nil (mapcar (lambda(ct) (< ctime ct)) - included-files-ctime)))))))))) + (let ((visiting (find-buffer-visiting filename)) + (buf (find-file-noselect filename)) + (case-fold-search t)) + (unwind-protect + (with-current-buffer buf + (goto-char (point-min)) + (while (re-search-forward "^[ \t]*#\\+INCLUDE:" nil t) + (let* ((element (org-element-at-point)) + (included-file + (and (eq (org-element-type element) 'keyword) + (let ((value (org-element-property :value element))) + (and value + (string-match + "\\`\\(\".+?\"\\|\\S-+\\)\\(?:\\s-+\\|$\\)" + value) + (let ((m (match-string 1 value))) + (org-unbracket-string + "\"" "\"" + ;; Ignore search suffix. + (if (string-match "::.*?\"?\\'" m) + (substring m 0 (match-beginning 0)) + m)))))))) + (when included-file + (push (org-publish-cache-ctime-of-src + (expand-file-name included-file)) + included-files-ctime))))) + (unless visiting (kill-buffer buf))))) + (or (null pstamp) + (let ((ctime (org-publish-cache-ctime-of-src filename))) + (or (< pstamp ctime) + (cl-some (lambda (ct) (< ctime ct)) included-files-ctime)))))) (defun org-publish-cache-set-file-property (filename property value &optional project-name) @@ -1206,35 +1298,32 @@ will be created. Return VALUE." filename property value nil project-name)))) (defun org-publish-cache-get-file-property - (filename property &optional default no-create project-name) + (filename property &optional default no-create project-name) "Return the value for a PROPERTY of file FILENAME in publishing cache. -Use cache file of PROJECT-NAME. Return the value of that PROPERTY -or DEFAULT, if the value does not yet exist. If the entry will -be created, unless NO-CREATE is not nil." - ;; Evtl. load the requested cache file: - (if project-name (org-publish-initialize-cache project-name)) - (let ((pl (org-publish-cache-get filename)) retval) - (if pl - (if (plist-member pl property) - (setq retval (plist-get pl property)) - (setq retval default)) - ;; no pl yet: - (unless no-create - (org-publish-cache-set filename (list property default))) - (setq retval default)) - retval)) +Use cache file of PROJECT-NAME. Return the value of that PROPERTY, +or DEFAULT, if the value does not yet exist. Create the entry, +if necessary, unless NO-CREATE is non-nil." + (when project-name (org-publish-initialize-cache project-name)) + (let ((properties (org-publish-cache-get filename))) + (cond ((null properties) + (unless no-create + (org-publish-cache-set filename (list property default))) + default) + ((plist-member properties property) (plist-get properties property)) + (t default)))) (defun org-publish-cache-get (key) "Return the value stored in `org-publish-cache' for key KEY. -Returns nil, if no value or nil is found, or the cache does not -exist." +Return nil, if no value or nil is found. Raise an error if the +cache does not exist." (unless org-publish-cache (error "`org-publish-cache-get' called, but no cache present")) (gethash key org-publish-cache)) (defun org-publish-cache-set (key value) "Store KEY VALUE pair in `org-publish-cache'. -Returns value on success, else nil." +Returns value on success, else nil. Raise an error if the cache +does not exist." (unless org-publish-cache (error "`org-publish-cache-set' called, but no cache present")) (puthash key value org-publish-cache)) diff --git a/lisp/org/ox-texinfo.el b/lisp/org/ox-texinfo.el index 31d91ebfb80..60618c1c30e 100644 --- a/lisp/org/ox-texinfo.el +++ b/lisp/org/ox-texinfo.el @@ -1,4 +1,4 @@ -;;; ox-texinfo.el --- Texinfo Back-End for Org Export Engine +;;; ox-texinfo.el --- Texinfo Back-End for Org Export Engine -*- lexical-binding: t; -*- ;; Copyright (C) 2012-2017 Free Software Foundation, Inc. ;; Author: Jonathan Leech-Pepin <jonathan.leechpepin at gmail dot com> @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -25,7 +25,7 @@ ;;; Code: -(eval-when-compile (require 'cl)) +(require 'cl-lib) (require 'ox) (defvar orgtbl-exp-regexp) @@ -39,8 +39,6 @@ (center-block . org-texinfo-center-block) (clock . org-texinfo-clock) (code . org-texinfo-code) - (comment . (lambda (&rest args) "")) - (comment-block . (lambda (&rest args) "")) (drawer . org-texinfo-drawer) (dynamic-block . org-texinfo-dynamic-block) (entity . org-texinfo-entity) @@ -58,18 +56,19 @@ (keyword . org-texinfo-keyword) (line-break . org-texinfo-line-break) (link . org-texinfo-link) + (node-property . org-texinfo-node-property) (paragraph . org-texinfo-paragraph) (plain-list . org-texinfo-plain-list) (plain-text . org-texinfo-plain-text) (planning . org-texinfo-planning) (property-drawer . org-texinfo-property-drawer) (quote-block . org-texinfo-quote-block) - (quote-section . org-texinfo-quote-section) (radio-target . org-texinfo-radio-target) (section . org-texinfo-section) (special-block . org-texinfo-special-block) (src-block . org-texinfo-src-block) (statistics-cookie . org-texinfo-statistics-cookie) + (strike-through . org-texinfo-strike-through) (subscript . org-texinfo-subscript) (superscript . org-texinfo-superscript) (table . org-texinfo-table) @@ -78,28 +77,47 @@ (target . org-texinfo-target) (template . org-texinfo-template) (timestamp . org-texinfo-timestamp) + (underline . org-texinfo-underline) (verbatim . org-texinfo-verbatim) (verse-block . org-texinfo-verse-block)) - :export-block "TEXINFO" :filters-alist - '((:filter-headline . org-texinfo-filter-section-blank-lines) + '((:filter-headline . org-texinfo--filter-section-blank-lines) (:filter-parse-tree . org-texinfo--normalize-headlines) - (:filter-section . org-texinfo-filter-section-blank-lines)) + (:filter-section . org-texinfo--filter-section-blank-lines) + (:filter-final-output . org-texinfo--untabify)) :menu-entry '(?i "Export to Texinfo" ((?t "As TEXI file" org-texinfo-export-to-texinfo) - (?i "As INFO file" org-texinfo-export-to-info))) + (?i "As INFO file" org-texinfo-export-to-info) + (?o "As INFO file and open" + (lambda (a s v b) + (if a (org-texinfo-export-to-info t s v b) + (org-open-file (org-texinfo-export-to-info nil s v b))))))) :options-alist '((:texinfo-filename "TEXINFO_FILENAME" nil nil t) (:texinfo-class "TEXINFO_CLASS" nil org-texinfo-default-class t) (:texinfo-header "TEXINFO_HEADER" nil nil newline) (:texinfo-post-header "TEXINFO_POST_HEADER" nil nil newline) - (:subtitle "SUBTITLE" nil nil newline) + (:subtitle "SUBTITLE" nil nil parse) (:subauthor "SUBAUTHOR" nil nil newline) (:texinfo-dircat "TEXINFO_DIR_CATEGORY" nil nil t) (:texinfo-dirtitle "TEXINFO_DIR_TITLE" nil nil t) (:texinfo-dirdesc "TEXINFO_DIR_DESC" nil nil t) - (:texinfo-printed-title "TEXINFO_PRINTED_TITLE" nil nil t))) + (:texinfo-printed-title "TEXINFO_PRINTED_TITLE" nil nil t) + ;; Other variables. + (:texinfo-classes nil nil org-texinfo-classes) + (:texinfo-format-headline-function nil nil org-texinfo-format-headline-function) + (:texinfo-node-description-column nil nil org-texinfo-node-description-column) + (:texinfo-active-timestamp-format nil nil org-texinfo-active-timestamp-format) + (:texinfo-inactive-timestamp-format nil nil org-texinfo-inactive-timestamp-format) + (:texinfo-diary-timestamp-format nil nil org-texinfo-diary-timestamp-format) + (:texinfo-link-with-unknown-path-format nil nil org-texinfo-link-with-unknown-path-format) + (:texinfo-tables-verbatim nil nil org-texinfo-tables-verbatim) + (:texinfo-table-scientific-notation nil nil org-texinfo-table-scientific-notation) + (:texinfo-table-default-markup nil nil org-texinfo-table-default-markup) + (:texinfo-text-markup-alist nil nil org-texinfo-text-markup-alist) + (:texinfo-format-drawer-function nil nil org-texinfo-format-drawer-function) + (:texinfo-format-inlinetask-function nil nil org-texinfo-format-inlinetask-function))) @@ -129,17 +147,19 @@ If nil it will default to `buffer-file-coding-system'." (defcustom org-texinfo-classes '(("info" "@documentencoding AUTO\n@documentlanguage AUTO" - ("@chapter %s" . "@unnumbered %s") - ("@section %s" . "@unnumberedsec %s") - ("@subsection %s" . "@unnumberedsubsec %s") - ("@subsubsection %s" . "@unnumberedsubsubsec %s"))) + ("@chapter %s" "@unnumbered %s" "@appendix %s") + ("@section %s" "@unnumberedsec %s" "@appendixsec %s") + ("@subsection %s" "@unnumberedsubsec %s" "@appendixsubsec %s") + ("@subsubsection %s" "@unnumberedsubsubsec %s" "@appendixsubsubsec %s"))) "Alist of Texinfo classes and associated header and structure. If #+TEXINFO_CLASS is set in the buffer, use its value and the -associated information. Here is the structure of each cell: +associated information. Here is the structure of a class +definition: (class-name header-string - (numbered-section . unnumbered-section) + (numbered-1 unnumbered-1 appendix-1) + (numbered-2 unnumbered-2 appendix-2) ...) @@ -171,29 +191,24 @@ The sectioning structure The sectioning structure of the class is given by the elements following the header string. For each sectioning level, a number of strings is specified. A %s formatter is mandatory in each -section string and will be replaced by the title of the section. - -Instead of a list of sectioning commands, you can also specify -a function name. That function will be called with two -parameters, the reduced) level of the headline, and a predicate -non-nil when the headline should be numbered. It must return -a format string in which the section title will be added." +section string and will be replaced by the title of the section." :group 'org-export-texinfo - :version "24.4" - :package-version '(Org . "8.2") + :version "26.1" + :package-version '(Org . "9.1") :type '(repeat (list (string :tag "Texinfo class") (string :tag "Texinfo header") (repeat :tag "Levels" :inline t (choice - (cons :tag "Heading" + (list :tag "Heading" (string :tag " numbered") - (string :tag "unnumbered")) - (function :tag "Hook computing sectioning")))))) + (string :tag "unnumbered") + (string :tag " appendix"))))))) ;;;; Headline -(defcustom org-texinfo-format-headline-function 'ignore +(defcustom org-texinfo-format-headline-function + 'org-texinfo-format-headline-default-function "Function to format headline text. This function will be called with 5 arguments: @@ -203,23 +218,11 @@ PRIORITY the priority of the headline (integer or nil) TEXT the main headline text (string). TAGS the tags as a list of strings (list of strings or nil). -The function result will be used in the section format string. - -As an example, one could set the variable to the following, in -order to reproduce the default set-up: - -\(defun org-texinfo-format-headline (todo todo-type priority text tags) - \"Default format function for a headline.\" - (concat (when todo - (format \"\\\\textbf{\\\\textsc{\\\\textsf{%s}}} \" todo)) - (when priority - (format \"\\\\framebox{\\\\#%c} \" priority)) - text - (when tags - (format \"\\\\hfill{}\\\\textsc{%s}\" - (mapconcat \\='identity tags \":\"))))" +The function result will be used in the section format string." :group 'org-export-texinfo - :type 'function) + :type 'function + :version "26.1" + :package-version '(Org . "8.3")) ;;;; Node listing (menu) @@ -263,6 +266,7 @@ be placed after the end of the title." (defcustom org-texinfo-table-scientific-notation "%s\\,(%s)" "Format string to display numbers in scientific notation. + The format should have \"%s\" twice, for mantissa and exponent \(i.e. \"%s\\\\times10^{%s}\"). @@ -272,39 +276,48 @@ When nil, no transformation is made." (string :tag "Format string") (const :tag "No formatting" nil))) -(defcustom org-texinfo-def-table-markup "@samp" - "Default setting for @table environments." +(defcustom org-texinfo-table-default-markup "@asis" + "Default markup for first column in two-column tables. + +This should an indicating command, e.g., \"@code\", \"@kbd\" or +\"@samp\". + +It can be overridden locally using the \":indic\" attribute." :group 'org-export-texinfo - :type 'string) + :type 'string + :version "26.1" + :package-version '(Org . "9.1") + :safe #'stringp) ;;;; Text markup (defcustom org-texinfo-text-markup-alist '((bold . "@strong{%s}") (code . code) (italic . "@emph{%s}") - (verbatim . verb) - (comment . "@c %s")) + (verbatim . samp)) "Alist of Texinfo expressions to convert text markup. -The key must be a symbol among `bold', `italic' and `comment'. -The value is a formatting string to wrap fontified text with. +The key must be a symbol among `bold', `code', `italic', +`strike-through', `underscore' and `verbatim'. The value is +a formatting string to wrap fontified text with. -Value can also be set to the following symbols: `verb' and -`code'. For the former, Org will use \"@verb\" to -create a format string and select a delimiter character that -isn't in the string. For the latter, Org will use \"@code\" -to typeset and try to protect special characters. +Value can also be set to the following symbols: `verb', `samp' +and `code'. With the first one, Org uses \"@verb\" to create +a format string and selects a delimiter character that isn't in +the string. For the other two, Org uses \"@samp\" or \"@code\" +to typeset and protects special characters. -If no association can be found for a given markup, text will be -returned as-is." +When no association is found for a given markup, text is returned +as-is." :group 'org-export-texinfo + :version "26.1" + :package-version '(Org . "9.1") :type 'alist - :options '(bold code italic verbatim comment)) + :options '(bold code italic strike-through underscore verbatim)) ;;;; Drawers -(defcustom org-texinfo-format-drawer-function - (lambda (name contents) contents) +(defcustom org-texinfo-format-drawer-function (lambda (_name contents) contents) "Function called to format a drawer in Texinfo code. The function must accept two parameters: @@ -321,7 +334,8 @@ The default function simply returns the value of CONTENTS." ;;;; Inlinetasks -(defcustom org-texinfo-format-inlinetask-function 'ignore +(defcustom org-texinfo-format-inlinetask-function + 'org-texinfo-format-inlinetask-default-function "Function called to format an inlinetask in Texinfo code. The function must accept six parameters: @@ -332,38 +346,24 @@ The function must accept six parameters: TAGS the inlinetask tags, as a list of strings. CONTENTS the contents of the inlinetask, as a string. -The function should return the string to be exported. - -For example, the variable could be set to the following function -in order to mimic default behavior: - -\(defun org-texinfo-format-inlinetask (todo type priority name tags contents) -\"Format an inline task element for Texinfo export.\" - (let ((full-title - (concat - (when todo - (format \"@strong{%s} \" todo)) - (when priority (format \"#%c \" priority)) - title - (when tags - (format \":%s:\" - (mapconcat \\='identity tags \":\"))))) - (format (concat \"@center %s\n\n\" - \"%s\" - \"\n\")) - full-title contents))" +The function should return the string to be exported." :group 'org-export-texinfo :type 'function) ;;;; Compilation -(defcustom org-texinfo-info-process '("makeinfo %f") +(defcustom org-texinfo-info-process '("makeinfo --no-split %f") "Commands to process a Texinfo file to an INFO file. -This is list of strings, each of them will be given to the shell -as a command. %f in the command will be replaced by the full -file name, %b by the file base name (i.e without extension) and -%o by the base directory of the file." + +This is a list of strings, each of them will be given to the +shell as a command. %f in the command will be replaced by the +relative file name, %F by the absolute file name, %b by the file +base name (i.e. without directory and extension parts), %o by the +base directory of the file and %O by the absolute file name of +the output file." :group 'org-export-texinfo + :version "26.1" + :package-version '(Org . "9.1") :type '(repeat :tag "Shell command sequence" (string :tag "Shell command"))) @@ -398,15 +398,23 @@ Specified coding system will be matched against these strings. If two strings share the same prefix (e.g. \"ISO-8859-1\" and \"ISO-8859-15\"), the most specific one has to be listed first.") +(defconst org-texinfo-inline-image-rules + (list (cons "file" + (regexp-opt '("eps" "pdf" "png" "jpg" "jpeg" "gif" "svg")))) + "Rules characterizing image files that can be inlined.") + ;;; Internal Functions -(defun org-texinfo-filter-section-blank-lines (headline back-end info) +(defun org-texinfo--untabify (s _backend _info) + "Remove TAB characters in string S." + (replace-regexp-in-string "\t" (make-string tab-width ?\s) s)) + +(defun org-texinfo--filter-section-blank-lines (headline _backend _info) "Filter controlling number of blank lines after a section." - (let ((blanks (make-string 2 ?\n))) - (replace-regexp-in-string "\n\\(?:\n[ \t]*\\)*\\'" blanks headline))) + (replace-regexp-in-string "\n\\(?:\n[ \t]*\\)*\\'" "\n\n" headline)) -(defun org-texinfo--normalize-headlines (tree back-end info) +(defun org-texinfo--normalize-headlines (tree _backend info) "Normalize headlines in TREE. BACK-END is the symbol specifying back-end used for export. INFO @@ -435,76 +443,128 @@ Return new tree." "Return a character not used in string S. This is used to choose a separator for constructs like \\verb." (let ((ll "~,./?;':\"|!@#%^&-_=+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ<>()[]{}")) - (loop for c across ll - when (not (string-match (regexp-quote (char-to-string c)) s)) - return (char-to-string c)))) + (cl-loop for c across ll + when (not (string-match (regexp-quote (char-to-string c)) s)) + return (char-to-string c)))) -(defun org-texinfo--text-markup (text markup) +(defun org-texinfo--text-markup (text markup _info) "Format TEXT depending on MARKUP text markup. -See `org-texinfo-text-markup-alist' for details." - (let ((fmt (cdr (assq markup org-texinfo-text-markup-alist)))) - (cond - ;; No format string: Return raw text. - ((not fmt) text) - ((eq 'verb fmt) - (let ((separator (org-texinfo--find-verb-separator text))) - (concat "@verb{" separator text separator "}"))) - ((eq 'code fmt) - (let ((start 0) - (rtn "") - char) - (while (string-match "[@{}]" text) - (setq char (match-string 0 text)) - (if (> (match-beginning 0) 0) - (setq rtn (concat rtn (substring text 0 (match-beginning 0))))) - (setq text (substring text (1+ (match-beginning 0)))) - (setq char (concat "@" char) - rtn (concat rtn char))) - (setq text (concat rtn text) - fmt "@code{%s}") - (format fmt text))) - ;; Else use format string. - (t (format fmt text))))) - -(defun org-texinfo--get-node (blob info) - "Return node or anchor associated to BLOB. -BLOB is an element or object. INFO is a plist used as +INFO is a plist used as a communication channel. See +`org-texinfo-text-markup-alist' for details." + (pcase (cdr (assq markup org-texinfo-text-markup-alist)) + (`nil text) ;no markup: return raw text + (`code (format "@code{%s}" (org-texinfo--sanitize-content text))) + (`samp (format "@samp{%s}" (org-texinfo--sanitize-content text))) + (`verb + (let ((separator (org-texinfo--find-verb-separator text))) + (format "@verb{%s%s%s}" separator text separator))) + ;; Else use format string. + (fmt (format fmt text)))) + +(defun org-texinfo--get-node (datum info) + "Return node or anchor associated to DATUM. +DATUM is an element or object. INFO is a plist used as a communication channel. The function guarantees the node or anchor name is unique." (let ((cache (plist-get info :texinfo-node-cache))) - (or (cdr (assq blob cache)) - (let ((name - (org-texinfo--sanitize-node - (case (org-element-type blob) - (headline - (org-export-data (org-export-get-alt-title blob info) info)) - ((radio-target target) (org-element-property :value blob)) - (otherwise (or (org-element-property :name blob) "")))))) - ;; Ensure NAME is unique. - (while (rassoc name cache) (setq name (concat name "x"))) - (plist-put info :texinfo-node-cache (cons (cons blob name) cache)) + (or (cdr (assq datum cache)) + (let* ((salt 0) + (basename + (org-texinfo--sanitize-node + (if (eq (org-element-type datum) 'headline) + (org-texinfo--sanitize-title + (org-export-get-alt-title datum info) info) + (org-export-get-reference datum info)))) + (name basename)) + ;; Ensure NAME is unique and not reserved node name "Top". + (while (or (equal name "Top") (rassoc name cache)) + (setq name (concat basename (format " %d" (cl-incf salt))))) + (plist-put info :texinfo-node-cache (cons (cons datum name) cache)) name)))) -;;;; Menu sanitizing - (defun org-texinfo--sanitize-node (title) "Bend string TITLE to node line requirements. Trim string and collapse multiple whitespace characters as they -are not significant. Also remove the following characters: @ -{ } ( ) : . ," - (replace-regexp-in-string - "[:,.]" "" +are not significant. Replace leading left parenthesis, when +followed by a right parenthesis, with a square bracket. Remove +periods, commas and colons." + (org-trim (replace-regexp-in-string - "\\`(\\(.*)\\)" "[\\1" - (org-trim (replace-regexp-in-string "[ \t]\\{2,\\}" " " title))))) - -;;;; Content sanitizing + "[ \t]+" " " + (replace-regexp-in-string + "[:,.]" "" + (replace-regexp-in-string "\\`(\\(.*?)\\)" "[\\1" title))))) + +(defun org-texinfo--sanitize-title (title info) + "Make TITLE suitable as a section name. +TITLE is a string or a secondary string. INFO is the current +export state, as a plist." + (org-export-data-with-backend + title + (org-export-create-backend + :parent 'texinfo + :transcoders '((footnote-reference . ignore) + (link . (lambda (l c i) + (or c + (org-export-data + (org-element-property :raw-link l) + i)))) + (radio-target . (lambda (_r c _i) c)) + (target . ignore))) + info)) (defun org-texinfo--sanitize-content (text) "Escape special characters in string TEXT. Special characters are: @ { }" (replace-regexp-in-string "[@{}]" "@\\&" text)) +(defun org-texinfo--wrap-float (value info &optional type label caption short) + "Wrap string VALUE within a @float command. +INFO is the current export state, as a plist. TYPE is float +type, as a string. LABEL is the cross reference label for the +float, as a string. CAPTION and SHORT are, respectively, the +caption and shortcaption used for the float, as secondary +strings (e.g., returned by `org-export-get-caption')." + (let* ((backend + (org-export-create-backend + :parent 'texinfo + :transcoders '((link . (lambda (l c i) + (or c + (org-export-data + (org-element-property :raw-link l) + i)))) + (radio-target . (lambda (_r c _i) c)) + (target . ignore)))) + (short-backend + (org-export-create-backend + :parent 'texinfo + :transcoders + '((footnote-reference . ignore) + (inline-src-block . ignore) + (link . (lambda (l c i) + (or c + (org-export-data + (org-element-property :raw-link l) + i)))) + (radio-target . (lambda (_r c _i) c)) + (target . ignore) + (verbatim . ignore)))) + (short-str + (if (and short caption) + (format "@shortcaption{%s}\n" + (org-export-data-with-backend short short-backend info)) + "")) + (caption-str + (if (or short caption) + (format "@caption{%s}\n" + (org-export-data-with-backend + (or caption short) + (if (equal short-str "") short-backend backend) + info)) + ""))) + (format "@float %s%s\n%s\n%s%s@end float" + type (if label (concat "," label) "") value caption-str short-str))) + ;;; Template (defun org-texinfo-template (contents info) @@ -537,7 +597,7 @@ holding export options." (name (symbol-name (or org-texinfo-coding-system buffer-file-coding-system)))) (dolist (system org-texinfo-supported-coding-systems "UTF-8") - (when (org-string-match-p (regexp-quote system) name) + (when (string-match-p (regexp-quote system) name) (throw 'coding-system system)))))) (language (plist-get info :language)) (case-fold-search nil)) @@ -574,7 +634,7 @@ holding export options." (let ((dirdesc (let ((desc (plist-get info :texinfo-dirdesc))) (cond ((not desc) nil) - ((org-string-match-p "\\.$" desc) desc) + ((string-suffix-p "." desc) desc) (t (concat desc ".")))))) (if dirdesc (format "%-23s %s" dirtitle dirdesc) dirtitle)) "\n" @@ -582,11 +642,14 @@ holding export options." ;; Title "@finalout\n" "@titlepage\n" - (format "@title %s\n" (or (plist-get info :texinfo-printed-title) title)) - (let ((subtitle (plist-get info :subtitle))) - (and subtitle - (org-element-normalize-string - (replace-regexp-in-string "^" "@subtitle " subtitle)))) + (when (plist-get info :with-title) + (concat + (format "@title %s\n" + (or (plist-get info :texinfo-printed-title) title "")) + (let ((subtitle (plist-get info :subtitle))) + (when subtitle + (format "@subtitle %s\n" + (org-export-data subtitle info)))))) (when (plist-get info :with-author) (concat ;; Primary author. @@ -608,11 +671,17 @@ holding export options." "@end titlepage\n\n" ;; Table of contents. (and (plist-get info :with-toc) "@contents\n\n") - ;; Configure Top Node when not for Tex + ;; Configure Top Node when not for TeX. Also include contents + ;; from the first section of the document. "@ifnottex\n" "@node Top\n" (format "@top %s\n" title) - (and copying "@insertcopying\n") + (let* ((first-section + (org-element-map (plist-get info :parse-tree) 'section + #'identity info t '(headline))) + (top-contents + (org-export-data (org-element-contents first-section) info))) + (and (org-string-nw-p top-contents) (concat "\n" top-contents))) "@end ifnottex\n\n" ;; Menu. (org-texinfo-make-menu (plist-get info :parse-tree) info 'master) @@ -620,10 +689,8 @@ holding export options." ;; Document's body. contents "\n" ;; Creator. - (case (plist-get info :with-creator) - ((nil) nil) - (comment (format "@c %s\n" (plist-get info :creator))) - (otherwise (concat (plist-get info :creator) "\n"))) + (and (plist-get info :with-creator) + (concat (plist-get info :creator) "\n")) ;; Document end. "@bye"))) @@ -633,15 +700,15 @@ holding export options." ;;;; Bold -(defun org-texinfo-bold (bold contents info) +(defun org-texinfo-bold (_bold contents info) "Transcode BOLD from Org to Texinfo. CONTENTS is the text with bold markup. INFO is a plist holding contextual information." - (org-texinfo--text-markup contents 'bold)) + (org-texinfo--text-markup contents 'bold info)) ;;;; Center Block -(defun org-texinfo-center-block (center-block contents info) +(defun org-texinfo-center-block (_center-block contents _info) "Transcode a CENTER-BLOCK element from Org to Texinfo. CONTENTS holds the contents of the block. INFO is a plist used as a communication channel." @@ -649,28 +716,26 @@ as a communication channel." ;;;; Clock -(defun org-texinfo-clock (clock contents info) +(defun org-texinfo-clock (clock _contents info) "Transcode a CLOCK element from Org to Texinfo. CONTENTS is nil. INFO is a plist holding contextual information." (concat "@noindent" (format "@strong{%s} " org-clock-string) - (format org-texinfo-inactive-timestamp-format - (concat (org-translate-time - (org-element-property :raw-value - (org-element-property :value clock))) + (format (plist-get info :texinfo-inactive-timestamp-format) + (concat (org-timestamp-translate (org-element-property :value clock)) (let ((time (org-element-property :duration clock))) (and time (format " (%s)" time))))) "@*")) ;;;; Code -(defun org-texinfo-code (code contents info) +(defun org-texinfo-code (code _contents info) "Transcode a CODE object from Org to Texinfo. CONTENTS is nil. INFO is a plist used as a communication channel." - (org-texinfo--text-markup (org-element-property :value code) 'code)) + (org-texinfo--text-markup (org-element-property :value code) 'code info)) ;;;; Drawer @@ -679,13 +744,13 @@ channel." CONTENTS holds the contents of the block. INFO is a plist holding contextual information." (let* ((name (org-element-property :drawer-name drawer)) - (output (funcall org-texinfo-format-drawer-function + (output (funcall (plist-get info :texinfo-format-drawer-function) name contents))) output)) ;;;; Dynamic Block -(defun org-texinfo-dynamic-block (dynamic-block contents info) +(defun org-texinfo-dynamic-block (_dynamic-block contents _info) "Transcode a DYNAMIC-BLOCK element from Org to Texinfo. CONTENTS holds the contents of the block. INFO is a plist holding contextual information." @@ -693,33 +758,69 @@ holding contextual information." ;;;; Entity -(defun org-texinfo-entity (entity contents info) - "Transcode an ENTITY object from Org to Texinfo. -CONTENTS are the definition itself. INFO is a plist holding -contextual information." - (let ((ent (org-element-property :latex entity))) - (if (org-element-property :latex-math-p entity) (format "@math{%s}" ent) ent))) +(defun org-texinfo-entity (entity _contents _info) + "Transcode an ENTITY object from Org to Texinfo." + ;; Since there is not specific Texinfo entry in entities, use + ;; Texinfo-specific commands whenever possible, and fallback to + ;; UTF-8 otherwise. + (pcase (org-element-property :name entity) + ("AElig" "@AE{}") + ("aelig" "@ae{}") + ((or "bull" "bullet") "@bullet{}") + ("copy" "@copyright{}") + ("deg" "@textdegree{}") + ((or "dots" "hellip") "@dots{}") + ("equiv" "@equiv{}") + ((or "euro" "EUR") "@euro{}") + ((or "ge" "geq") "@geq{}") + ("laquo" "@guillemetleft{}") + ("iexcl" "@exclamdown{}") + ("imath" "@dotless{i}") + ("iquest" "@questiondown{}") + ("jmath" "@dotless{j}") + ((or "le" "leq") "@leq{}") + ("lsaquo" "@guilsinglleft{}") + ("mdash" "---") + ("minus" "@minus{}") + ("nbsp" "@tie{}") + ("ndash" "--") + ("OElig" "@OE{}") + ("oelig" "@oe{}") + ("ordf" "@ordf{}") + ("ordm" "@ordm{}") + ("pound" "@pound{}") + ("raquo" "@guillemetright{}") + ((or "rArr" "Rightarrow") "@result{}") + ("reg" "@registeredsymbol{}") + ((or "rightarrow" "to" "rarr") "@arrow{}") + ("rsaquo" "@guilsinglright{}") + ("thorn" "@th{}") + ("THORN" "@TH{}") + ((and (pred (string-prefix-p "_")) name) ;spacing entities + (format "@w{%s}" (substring name 1))) + (_ (org-element-property :utf-8 entity)))) ;;;; Example Block -(defun org-texinfo-example-block (example-block contents info) +(defun org-texinfo-example-block (example-block _contents info) "Transcode an EXAMPLE-BLOCK element from Org to Texinfo. CONTENTS is nil. INFO is a plist holding contextual information." - (format "@verbatim\n%s@end verbatim" - (org-export-format-code-default example-block info))) + (format "@example\n%s@end example" + (org-texinfo--sanitize-content + (org-export-format-code-default example-block info)))) -;;;; Export Block +;;; Export Block -(defun org-texinfo-export-block (export-block contents info) +(defun org-texinfo-export-block (export-block _contents _info) "Transcode a EXPORT-BLOCK element from Org to Texinfo. CONTENTS is nil. INFO is a plist holding contextual information." (when (string= (org-element-property :type export-block) "TEXINFO") (org-remove-indentation (org-element-property :value export-block)))) -;;;; Export Snippet +;;; Export Snippet -(defun org-texinfo-export-snippet (export-snippet contents info) +(defun org-texinfo-export-snippet (export-snippet _contents _info) "Transcode a EXPORT-SNIPPET object from Org to Texinfo. CONTENTS is nil. INFO is a plist holding contextual information." (when (eq (org-export-snippet-backend export-snippet) 'texinfo) @@ -727,17 +828,17 @@ CONTENTS is nil. INFO is a plist holding contextual information." ;;;; Fixed Width -(defun org-texinfo-fixed-width (fixed-width contents info) +(defun org-texinfo-fixed-width (fixed-width _contents _info) "Transcode a FIXED-WIDTH element from Org to Texinfo. CONTENTS is nil. INFO is a plist holding contextual information." - (format "@example\n%s\n@end example" + (format "@example\n%s@end example" (org-remove-indentation (org-texinfo--sanitize-content (org-element-property :value fixed-width))))) ;;;; Footnote Reference -(defun org-texinfo-footnote-reference (footnote contents info) +(defun org-texinfo-footnote-reference (footnote _contents info) "Create a footnote reference for FOOTNOTE. FOOTNOTE is the footnote to define. CONTENTS is nil. INFO is a @@ -748,102 +849,94 @@ plist holding contextual information." ;;;; Headline +(defun org-texinfo--structuring-command (headline info) + "Return Texinfo structuring command string for HEADLINE element. +Return nil if HEADLINE is to be ignored, `plain-list' if it +should be exported as a plain-list item. INFO is a plist holding +contextual information." + (cond + ((org-element-property :footnote-section-p headline) nil) + ((org-not-nil (org-export-get-node-property :COPYING headline t)) nil) + ((org-export-low-level-p headline info) 'plain-list) + (t + (let ((class (plist-get info :texinfo-class))) + (pcase (assoc class (plist-get info :texinfo-classes)) + (`(,_ ,_ . ,sections) + (pcase (nth (1- (org-export-get-relative-level headline info)) + sections) + (`(,numbered ,unnumbered ,appendix) + (cond + ((org-not-nil (org-export-get-node-property :APPENDIX headline t)) + appendix) + ((org-not-nil (org-export-get-node-property :INDEX headline t)) + unnumbered) + ((org-export-numbered-headline-p headline info) numbered) + (t unnumbered))) + (`nil 'plain-list) + (_ (user-error "Invalid Texinfo class specification: %S" class)))) + (_ (user-error "Invalid Texinfo class specification: %S" class))))))) + (defun org-texinfo-headline (headline contents info) "Transcode a HEADLINE element from Org to Texinfo. CONTENTS holds the contents of the headline. INFO is a plist holding contextual information." - (let* ((class (plist-get info :texinfo-class)) - (level (org-export-get-relative-level headline info)) - (numberedp (org-export-numbered-headline-p headline info)) - (class-sectioning (assoc class org-texinfo-classes)) - ;; Find the index type, if any. - (index (org-element-property :INDEX headline)) - ;; Create node info, to insert it before section formatting. - ;; Use custom menu title if present. - (node (format "@node %s\n" (org-texinfo--get-node headline info))) - ;; Section formatting will set two placeholders: one for the - ;; title and the other for the contents. - (section-fmt - (if (org-not-nil (org-element-property :APPENDIX headline)) - "@appendix %s\n%s" - (let ((sec (if (and (symbolp (nth 2 class-sectioning)) - (fboundp (nth 2 class-sectioning))) - (funcall (nth 2 class-sectioning) level numberedp) - (nth (1+ level) class-sectioning)))) - (cond - ;; No section available for that LEVEL. - ((not sec) nil) - ;; Section format directly returned by a function. - ((stringp sec) sec) - ;; (numbered-section . unnumbered-section) - ((not (consp (cdr sec))) - (concat (if (or index (not numberedp)) (cdr sec) (car sec)) - "\n%s")))))) - (todo - (and (plist-get info :with-todo-keywords) - (let ((todo (org-element-property :todo-keyword headline))) - (and todo (org-export-data todo info))))) - (todo-type (and todo (org-element-property :todo-type headline))) - (tags (and (plist-get info :with-tags) - (org-export-get-tags headline info))) - (priority (and (plist-get info :with-priority) - (org-element-property :priority headline))) - (text (org-export-data (org-element-property :title headline) info)) - (full-text (if (not (eq org-texinfo-format-headline-function 'ignore)) - ;; User-defined formatting function. - (funcall org-texinfo-format-headline-function - todo todo-type priority text tags) - ;; Default formatting. - (concat - (when todo - (format "@strong{%s} " todo)) - (when priority (format "@emph{#%s} " priority)) - text - (when tags - (format " :%s:" - (mapconcat 'identity tags ":")))))) - (contents (if (org-string-nw-p contents) (concat "\n" contents) ""))) - (cond - ;; Case 1: This is a footnote section: ignore it. - ((org-element-property :footnote-section-p headline) nil) - ;; Case 2: This is the `copying' section: ignore it - ;; This is used elsewhere. - ((org-not-nil (org-element-property :COPYING headline)) nil) - ;; Case 3: An index. If it matches one of the known indexes, - ;; print it as such following the contents, otherwise - ;; print the contents and leave the index up to the user. - (index - (concat node - (format - section-fmt - full-text - (concat contents - (and (member index '("cp" "fn" "ky" "pg" "tp" "vr")) - (concat "\n@printindex " index)))))) - ;; Case 4: This is a deep sub-tree: export it as a list item. - ;; Also export as items headlines for which no section - ;; format has been found. - ((or (not section-fmt) (org-export-low-level-p headline info)) - ;; Build the real contents of the sub-tree. - (concat (and (org-export-first-sibling-p headline info) - (format "@%s\n" (if numberedp 'enumerate 'itemize))) - "@item\n" full-text "\n" - contents - (if (org-export-last-sibling-p headline info) - (format "@end %s" (if numberedp 'enumerate 'itemize)) - "\n"))) - ;; Case 5: Standard headline. Export it as a section. - (t (concat node (format section-fmt full-text contents)))))) + (let ((section-fmt (org-texinfo--structuring-command headline info))) + (when section-fmt + (let* ((todo + (and (plist-get info :with-todo-keywords) + (let ((todo (org-element-property :todo-keyword headline))) + (and todo (org-export-data todo info))))) + (todo-type (and todo (org-element-property :todo-type headline))) + (tags (and (plist-get info :with-tags) + (org-export-get-tags headline info))) + (priority (and (plist-get info :with-priority) + (org-element-property :priority headline))) + (text (org-texinfo--sanitize-title + (org-element-property :title headline) info)) + (full-text + (funcall (plist-get info :texinfo-format-headline-function) + todo todo-type priority text tags)) + (contents + (concat "\n" + (if (org-string-nw-p contents) + (concat "\n" contents) + "") + (let ((index (org-element-property :INDEX headline))) + (and (member index '("cp" "fn" "ky" "pg" "tp" "vr")) + (format "\n@printindex %s\n" index)))))) + (cond + ((eq section-fmt 'plain-list) + (let ((numbered? (org-export-numbered-headline-p headline info))) + (concat (and (org-export-first-sibling-p headline info) + (format "@%s\n" (if numbered? 'enumerate 'itemize))) + "@item\n" full-text "\n" + contents + (if (org-export-last-sibling-p headline info) + (format "@end %s" (if numbered? 'enumerate 'itemize)) + "\n")))) + (t + (concat (format "@node %s\n" (org-texinfo--get-node headline info)) + (format section-fmt full-text) + contents))))))) + +(defun org-texinfo-format-headline-default-function + (todo _todo-type priority text tags) + "Default format function for a headline. +See `org-texinfo-format-headline-function' for details." + (concat (when todo (format "@strong{%s} " todo)) + (when priority (format "@emph{#%s} " priority)) + text + (when tags (format " :%s:" (mapconcat 'identity tags ":"))))) ;;;; Inline Src Block -(defun org-texinfo-inline-src-block (inline-src-block contents info) +(defun org-texinfo-inline-src-block (inline-src-block _contents _info) "Transcode an INLINE-SRC-BLOCK element from Org to Texinfo. CONTENTS holds the contents of the item. INFO is a plist holding contextual information." - (let* ((code (org-element-property :value inline-src-block)) - (separator (org-texinfo--find-verb-separator code))) - (concat "@verb{" separator code separator "}"))) + (format "@code{%s}" + (org-texinfo--sanitize-content + (org-element-property :value inline-src-block)))) ;;;; Inlinetask @@ -860,31 +953,27 @@ holding contextual information." (org-export-get-tags inlinetask info))) (priority (and (plist-get info :with-priority) (org-element-property :priority inlinetask)))) - ;; If `org-texinfo-format-inlinetask-function' is provided, call it - ;; with appropriate arguments. - (if (not (eq org-texinfo-format-inlinetask-function 'ignore)) - (funcall org-texinfo-format-inlinetask-function - todo todo-type priority title tags contents) - ;; Otherwise, use a default template. - (let ((full-title - (concat - (when todo (format "@strong{%s} " todo)) - (when priority (format "#%c " priority)) - title - (when tags (format ":%s:" - (mapconcat 'identity tags ":")))))) - (format (concat "@center %s\n\n" - "%s" - "\n") - full-title contents))))) + (funcall (plist-get info :texinfo-format-inlinetask-function) + todo todo-type priority title tags contents))) + +(defun org-texinfo-format-inlinetask-default-function + (todo _todo-type priority title tags contents) + "Default format function for a inlinetasks. +See `org-texinfo-format-inlinetask-function' for details." + (let ((full-title + (concat (when todo (format "@strong{%s} " todo)) + (when priority (format "#%c " priority)) + title + (when tags (format ":%s:" (mapconcat #'identity tags ":")))))) + (format "@center %s\n\n%s\n" full-title contents))) ;;;; Italic -(defun org-texinfo-italic (italic contents info) +(defun org-texinfo-italic (_italic contents info) "Transcode ITALIC from Org to Texinfo. CONTENTS is the text with italic markup. INFO is a plist holding contextual information." - (org-texinfo--text-markup contents 'italic)) + (org-texinfo--text-markup contents 'italic info)) ;;;; Item @@ -892,39 +981,76 @@ contextual information." "Transcode an ITEM element from Org to Texinfo. CONTENTS holds the contents of the item. INFO is a plist holding contextual information." - (format "@item%s\n%s" - (let ((tag (org-element-property :tag item))) - (if tag (concat " " (org-export-data tag info)) "")) - (or contents ""))) + (let* ((tag (org-element-property :tag item)) + (split (org-string-nw-p + (org-export-read-attribute :attr_texinfo + (org-element-property :parent item) + :sep))) + (items (and tag + (let ((tag (org-export-data tag info))) + (if split + (split-string tag (regexp-quote split) t "[ \t\n]+") + (list tag)))))) + (format "%s\n%s" + (pcase items + (`nil "@item") + (`(,item) (concat "@item " item)) + (`(,item . ,items) + (concat "@item " item "\n" + (mapconcat (lambda (i) (concat "@itemx " i)) + items + "\n")))) + (or contents "")))) ;;;; Keyword -(defun org-texinfo-keyword (keyword contents info) +(defun org-texinfo-keyword (keyword _contents info) "Transcode a KEYWORD element from Org to Texinfo. CONTENTS is nil. INFO is a plist holding contextual information." - (let ((key (org-element-property :key keyword)) - (value (org-element-property :value keyword))) - (cond - ((string= key "TEXINFO") value) - ((string= key "CINDEX") (format "@cindex %s" value)) - ((string= key "FINDEX") (format "@findex %s" value)) - ((string= key "KINDEX") (format "@kindex %s" value)) - ((string= key "PINDEX") (format "@pindex %s" value)) - ((string= key "TINDEX") (format "@tindex %s" value)) - ((string= key "VINDEX") (format "@vindex %s" value))))) + (let ((value (org-element-property :value keyword))) + (pcase (org-element-property :key keyword) + ("TEXINFO" value) + ("CINDEX" (format "@cindex %s" value)) + ("FINDEX" (format "@findex %s" value)) + ("KINDEX" (format "@kindex %s" value)) + ("PINDEX" (format "@pindex %s" value)) + ("TINDEX" (format "@tindex %s" value)) + ("VINDEX" (format "@vindex %s" value)) + ("TOC" + (cond ((string-match-p "\\<tables\\>" value) + (concat "@listoffloats " + (org-export-translate "Table" :utf-8 info))) + ((string-match-p "\\<listings\\>" value) + (concat "@listoffloats " + (org-export-translate "Listing" :utf-8 info)))))))) ;;;; Line Break -(defun org-texinfo-line-break (line-break contents info) +(defun org-texinfo-line-break (_line-break _contents _info) "Transcode a LINE-BREAK object from Org to Texinfo. CONTENTS is nil. INFO is a plist holding contextual information." "@*\n") ;;;; Link +(defun org-texinfo--@ref (datum description info) + "Return @ref command for element or object DATUM. +DESCRIPTION is the printed name of the section, as a string, or +nil." + (let ((node-name (org-texinfo--get-node datum info)) + ;; Sanitize DESCRIPTION for cross-reference use. In + ;; particular, remove colons as they seem to cause pain (even + ;; within @asis{...}) to the Texinfo reader. + (title (and description + (replace-regexp-in-string + "[ \t]*:+" "" + (replace-regexp-in-string "," "@comma{}" description))))) + (if (or (not title) (equal title node-name)) + (format "@ref{%s}" node-name) + (format "@ref{%s, , %s}" node-name title)))) + (defun org-texinfo-link (link desc info) "Transcode a LINK object from Org to Texinfo. - DESC is the description part of the link, or the empty string. INFO is a plist holding contextual information. See `org-export-data'." @@ -935,78 +1061,81 @@ INFO is a plist holding contextual information. See (path (cond ((member type '("http" "https" "ftp")) (concat type ":" raw-path)) - ((and (string= type "file") (file-name-absolute-p raw-path)) - (concat "file:" raw-path)) - (t raw-path))) - protocol) + ((string= type "file") (org-export-file-uri raw-path)) + (t raw-path)))) (cond + ((org-export-custom-protocol-maybe link desc 'texinfo)) + ((org-export-inline-image-p link org-texinfo-inline-image-rules) + (org-texinfo--inline-image link info)) ((equal type "radio") (let ((destination (org-export-resolve-radio-link link info))) (if (not destination) desc - (format "@ref{%s,,%s}" - (org-texinfo--get-node destination info) - desc)))) + (org-texinfo--@ref destination desc info)))) ((member type '("custom-id" "id" "fuzzy")) (let ((destination (if (equal type "fuzzy") (org-export-resolve-fuzzy-link link info) (org-export-resolve-id-link link info)))) - (case (org-element-type destination) - ((nil) + (pcase (org-element-type destination) + (`nil (format org-texinfo-link-with-unknown-path-format (org-texinfo--sanitize-content path))) ;; Id link points to an external file. - (plain-text + (`plain-text (if desc (format "@uref{file://%s,%s}" destination desc) (format "@uref{file://%s}" destination))) - (headline - (format "@ref{%s,%s}" - (org-texinfo--get-node destination info) - (cond - (desc) - ((org-export-numbered-headline-p destination info) - (org-export-data - (org-element-property :title destination) info)) - (t - (mapconcat - #'number-to-string - (org-export-get-headline-number destination info) "."))))) - (otherwise - (let ((topic - (or desc - (if (and (eq (org-element-type destination) 'headline) - (not (org-export-numbered-headline-p - destination info))) - (org-export-data - (org-element-property :title destination) info)) - (let ((n (org-export-get-ordinal destination info))) - (cond - ((not n) nil) - ((integerp n) n) - (t (mapconcat #'number-to-string n "."))))))) - (when topic - (format "@ref{%s,,%s}" - (org-texinfo--get-node destination info) - topic))))))) - ((equal type "info") - (let* ((info-path (split-string path "[:#]")) - (info-manual (car info-path)) - (info-node (or (cadr info-path) "top")) - (title (or desc ""))) - (format "@ref{%s,%s,,%s,}" info-node title info-manual))) + ((or `headline + ;; Targets within headlines cannot be turned into + ;; @anchor{}, so we refer to the headline parent + ;; directly. + (and `target + (guard (eq 'headline + (org-element-type + (org-element-property :parent destination)))))) + (let ((headline (org-element-lineage destination '(headline) t))) + (org-texinfo--@ref headline desc info))) + (_ (org-texinfo--@ref destination desc info))))) ((string= type "mailto") (format "@email{%s}" (concat (org-texinfo--sanitize-content path) - (and desc (concat "," desc))))) - ((let ((protocol (nth 2 (assoc type org-link-protocols)))) - (and (functionp protocol) - (funcall protocol (org-link-unescape path) desc 'texinfo)))) + (and desc (concat ", " desc))))) ;; External link with a description part. - ((and path desc) (format "@uref{%s,%s}" path desc)) + ((and path desc) (format "@uref{%s, %s}" path desc)) ;; External link without a description part. (path (format "@uref{%s}" path)) ;; No path, only description. Try to do something useful. - (t (format org-texinfo-link-with-unknown-path-format desc))))) + (t + (format (plist-get info :texinfo-link-with-unknown-path-format) desc))))) + +(defun org-texinfo--inline-image (link info) + "Return Texinfo code for an inline image. +LINK is the link pointing to the inline image. INFO is the +current state of the export, as a plist." + (let* ((parent (org-export-get-parent-element link)) + (label (and (org-element-property :name parent) + (org-texinfo--get-node parent info))) + (caption (org-export-get-caption parent)) + (shortcaption (org-export-get-caption parent t)) + (path (org-element-property :path link)) + (filename + (file-name-sans-extension + (if (file-name-absolute-p path) (expand-file-name path) path))) + (extension (file-name-extension path)) + (attributes (org-export-read-attribute :attr_texinfo parent)) + (height (or (plist-get attributes :height) "")) + (width (or (plist-get attributes :width) "")) + (alt (or (plist-get attributes :alt) "")) + (image (format "@image{%s,%s,%s,%s,%s}" + filename width height alt extension))) + (cond ((or caption shortcaption) + (org-texinfo--wrap-float image + info + (org-export-translate "Figure" :utf-8 info) + label + caption + shortcaption)) + (label (concat "@anchor{" label "}\n" image)) + (t image)))) ;;;; Menu @@ -1046,19 +1175,19 @@ is an integer, build the menu recursively, down to this depth." (cond ((not level) (org-texinfo--format-entries (org-texinfo--menu-entries scope info) info)) - ((zerop level) nil) + ((zerop level) "\n") (t - (org-element-normalize-string - (mapconcat - (lambda (h) - (let ((entries (org-texinfo--menu-entries h info))) - (when entries - (concat - (format "%s\n\n%s\n" - (org-export-data (org-export-get-alt-title h info) info) - (org-texinfo--format-entries entries info)) - (org-texinfo--build-menu h info (1- level)))))) - (org-texinfo--menu-entries scope info) ""))))) + (mapconcat + (lambda (h) + (let ((entries (org-texinfo--menu-entries h info))) + (when entries + (concat + (format "%s\n\n%s\n" + (org-export-data (org-export-get-alt-title h info) info) + (org-texinfo--format-entries entries info)) + (org-texinfo--build-menu h info (1- level)))))) + (org-texinfo--menu-entries scope info) + "")))) (defun org-texinfo--format-entries (entries info) "Format all direct menu entries in SCOPE, as a string. @@ -1067,8 +1196,13 @@ a plist containing contextual information." (org-element-normalize-string (mapconcat (lambda (h) - (let* ((title (org-export-data - (org-export-get-alt-title h info) info)) + (let* ((title + ;; Colons are used as a separator between title and node + ;; name. Remove them. + (replace-regexp-in-string + "[ \t]+:+" "" + (org-texinfo--sanitize-title + (org-export-get-alt-title h info) info))) (node (org-texinfo--get-node h info)) (entry (concat "* " title ":" (if (string= title node) ":" @@ -1090,18 +1224,26 @@ holding contextual information." (cached-entries (gethash scope cache 'no-cache))) (if (not (eq cached-entries 'no-cache)) cached-entries (puthash scope - (org-element-map (org-element-contents scope) 'headline - (lambda (h) - (and (not (org-not-nil (org-element-property :COPYING h))) - (not (org-element-property :footnote-section-p h)) - (not (org-export-low-level-p h info)) - h)) - info nil 'headline) + (cl-remove-if + (lambda (h) + (org-not-nil (org-export-get-node-property :COPYING h t))) + (org-export-collect-headlines info 1 scope)) cache)))) +;;;; Node Property + +(defun org-texinfo-node-property (node-property _contents _info) + "Transcode a NODE-PROPERTY element from Org to Texinfo. +CONTENTS is nil. INFO is a plist holding contextual +information." + (format "%s:%s" + (org-element-property :key node-property) + (let ((value (org-element-property :value node-property))) + (if value (concat " " value) "")))) + ;;;; Paragraph -(defun org-texinfo-paragraph (paragraph contents info) +(defun org-texinfo-paragraph (_paragraph contents _info) "Transcode a PARAGRAPH element from Org to Texinfo. CONTENTS is the contents of the paragraph, as a string. INFO is the plist used as a communication channel." @@ -1114,7 +1256,10 @@ the plist used as a communication channel." CONTENTS is the contents of the list. INFO is a plist holding contextual information." (let* ((attr (org-export-read-attribute :attr_texinfo plain-list)) - (indic (or (plist-get attr :indic) org-texinfo-def-table-markup)) + (indic (let ((i (or (plist-get attr :indic) + (plist-get info :texinfo-table-default-markup)))) + ;; Allow indicating commands with missing @ sign. + (if (string-prefix-p "@" i) i (concat "@" i)))) (table-type (plist-get attr :table-type)) (type (org-element-property :type plain-list)) (list-type (cond @@ -1141,16 +1286,14 @@ contextual information." (setq output (org-export-activate-smart-quotes output :texinfo info text))) ;; LaTeX into @LaTeX{} and TeX into @TeX{} - (let ((case-fold-search nil) - (start 0)) - (while (string-match "\\(\\(?:La\\)?TeX\\)" output start) - (setq output (replace-match - (format "@%s{}" (match-string 1 output)) nil t output) - start (match-end 0)))) + (let ((case-fold-search nil)) + (setq output (replace-regexp-in-string "\\(?:La\\)?TeX" "@\\&{}" output))) ;; Convert special strings. (when (plist-get info :with-special-strings) - (while (string-match (regexp-quote "...") output) - (setq output (replace-match "@dots{}" nil t output)))) + (setq output + (replace-regexp-in-string + "\\.\\.\\." "@dots{}" + (replace-regexp-in-string "\\\\-" "@-" output)))) ;; Handle break preservation if required. (when (plist-get info :preserve-breaks) (setq output (replace-regexp-in-string @@ -1160,7 +1303,7 @@ contextual information." ;;;; Planning -(defun org-texinfo-planning (planning contents info) +(defun org-texinfo-planning (planning _contents info) "Transcode a PLANNING element from Org to Texinfo. CONTENTS is nil. INFO is a plist holding contextual information." @@ -1174,39 +1317,35 @@ information." (when closed (concat (format "@strong{%s} " org-closed-string) - (format org-texinfo-inactive-timestamp-format - (org-translate-time - (org-element-property :raw-value closed)))))) + (format (plist-get info :texinfo-inactive-timestamp-format) + (org-timestamp-translate closed))))) (let ((deadline (org-element-property :deadline planning))) (when deadline (concat (format "@strong{%s} " org-deadline-string) - (format org-texinfo-active-timestamp-format - (org-translate-time - (org-element-property :raw-value deadline)))))) + (format (plist-get info :texinfo-active-timestamp-format) + (org-timestamp-translate deadline))))) (let ((scheduled (org-element-property :scheduled planning))) (when scheduled (concat (format "@strong{%s} " org-scheduled-string) - (format org-texinfo-active-timestamp-format - (org-translate-time - (org-element-property :raw-value scheduled)))))))) + (format (plist-get info :texinfo-active-timestamp-format) + (org-timestamp-translate scheduled))))))) " ") "@*")) ;;;; Property Drawer -(defun org-texinfo-property-drawer (property-drawer contents info) +(defun org-texinfo-property-drawer (_property-drawer contents _info) "Transcode a PROPERTY-DRAWER element from Org to Texinfo. -CONTENTS is nil. INFO is a plist holding contextual -information." - ;; The property drawer isn't exported but we want separating blank - ;; lines nonetheless. - "") +CONTENTS holds the contents of the drawer. INFO is a plist +holding contextual information." + (and (org-string-nw-p contents) + (format "@verbatim\n%s@end verbatim" contents))) ;;;; Quote Block -(defun org-texinfo-quote-block (quote-block contents info) +(defun org-texinfo-quote-block (quote-block contents _info) "Transcode a QUOTE-BLOCK element from Org to Texinfo. CONTENTS holds the contents of the block. INFO is a plist holding contextual information." @@ -1216,15 +1355,6 @@ holding contextual information." (format " %s" title))))) (format "%s\n%s@end quotation" start-quote contents))) -;;;; Quote Section - -(defun org-texinfo-quote-section (quote-section contents info) - "Transcode a QUOTE-SECTION element from Org to Texinfo. -CONTENTS is nil. INFO is a plist holding contextual information." - (let ((value (org-remove-indentation - (org-element-property :value quote-section)))) - (when value (format "@verbatim\n%s@end verbatim" value)))) - ;;;; Radio Target (defun org-texinfo-radio-target (radio-target text info) @@ -1232,8 +1362,7 @@ CONTENTS is nil. INFO is a plist holding contextual information." TEXT is the text of the target. INFO is a plist holding contextual information." (format "@anchor{%s}%s" - (org-export-solidify-link-text - (org-element-property :value radio-target)) + (org-texinfo--get-node radio-target info) text)) ;;;; Section @@ -1242,40 +1371,67 @@ contextual information." "Transcode a SECTION element from Org to Texinfo. CONTENTS holds the contents of the section. INFO is a plist holding contextual information." - (concat contents - (let ((parent (org-export-get-parent-headline section))) - (and parent (org-texinfo-make-menu parent info))))) + (let ((parent (org-export-get-parent-headline section))) + (when parent ;ignore very first section + (org-trim + (concat contents "\n" (org-texinfo-make-menu parent info)))))) ;;;; Special Block -(defun org-texinfo-special-block (special-block contents info) +(defun org-texinfo-special-block (special-block contents _info) "Transcode a SPECIAL-BLOCK element from Org to Texinfo. CONTENTS holds the contents of the block. INFO is a plist used as a communication channel." - contents) + (let ((opt (org-export-read-attribute :attr_texinfo special-block :options)) + (type (org-element-property :type special-block))) + (format "@%s%s\n%s@end %s" + type + (if opt (concat " " opt) "") + (or contents "") + type))) ;;;; Src Block -(defun org-texinfo-src-block (src-block contents info) +(defun org-texinfo-src-block (src-block _contents info) "Transcode a SRC-BLOCK element from Org to Texinfo. CONTENTS holds the contents of the item. INFO is a plist holding contextual information." - (let ((lispp (org-string-match-p "lisp" - (org-element-property :language src-block))) - (code (org-texinfo--sanitize-content - (org-export-format-code-default src-block info)))) - (format (if lispp "@lisp\n%s@end lisp" "@example\n%s@end example") code))) + (let* ((lisp (string-match-p "lisp" + (org-element-property :language src-block))) + (code (org-texinfo--sanitize-content + (org-export-format-code-default src-block info))) + (value (format + (if lisp "@lisp\n%s@end lisp" "@example\n%s@end example") + code)) + (caption (org-export-get-caption src-block)) + (shortcaption (org-export-get-caption src-block t))) + (if (not (or caption shortcaption)) value + (org-texinfo--wrap-float value + info + (org-export-translate "Listing" :utf-8 info) + (org-texinfo--get-node src-block info) + caption + shortcaption)))) ;;;; Statistics Cookie -(defun org-texinfo-statistics-cookie (statistics-cookie contents info) +(defun org-texinfo-statistics-cookie (statistics-cookie _contents _info) "Transcode a STATISTICS-COOKIE object from Org to Texinfo. CONTENTS is nil. INFO is a plist holding contextual information." (org-element-property :value statistics-cookie)) + +;;;; Strike-through + +(defun org-texinfo-strike-through (_strike-through contents info) + "Transcode STRIKE-THROUGH from Org to Texinfo. +CONTENTS is the text with strike-through markup. INFO is a plist +holding contextual information." + (org-texinfo--text-markup contents 'strike-through info)) + ;;;; Subscript -(defun org-texinfo-subscript (subscript contents info) +(defun org-texinfo-subscript (_subscript contents _info) "Transcode a SUBSCRIPT object from Org to Texinfo. CONTENTS is the contents of the object. INFO is a plist holding contextual information." @@ -1283,7 +1439,7 @@ contextual information." ;;;; Superscript -(defun org-texinfo-superscript (superscript contents info) +(defun org-texinfo-superscript (_superscript contents _info) "Transcode a SUPERSCRIPT object from Org to Texinfo. CONTENTS is the contents of the object. INFO is a plist holding contextual information." @@ -1302,10 +1458,19 @@ contextual information." (let* ((col-width (org-export-read-attribute :attr_texinfo table :columns)) (columns (if col-width (format "@columnfractions %s" col-width) - (org-texinfo-table-column-widths table info)))) - (format "@multitable %s\n%s@end multitable" - columns - contents)))) + (org-texinfo-table-column-widths table info))) + (caption (org-export-get-caption table)) + (shortcaption (org-export-get-caption table t)) + (table-str (format "@multitable %s\n%s@end multitable" + columns + contents))) + (if (not (or caption shortcaption)) table-str + (org-texinfo--wrap-float table-str + info + (org-export-translate "Table" :utf-8 info) + (org-texinfo--get-node table info) + caption + shortcaption))))) (defun org-texinfo-table-column-widths (table info) "Determine the largest table cell in each column to process alignment. @@ -1324,7 +1489,7 @@ a communication channel." (let ((w (- (org-element-property :contents-end cell) (org-element-property :contents-begin cell)))) (aset widths idx (max w (aref widths idx)))) - (incf idx)) + (cl-incf idx)) info))) info) (format "{%s}" (mapconcat (lambda (w) (make-string w ?a)) widths "} {")))) @@ -1335,16 +1500,18 @@ a communication channel." "Transcode a TABLE-CELL element from Org to Texinfo. CONTENTS is the cell contents. INFO is a plist used as a communication channel." - (concat (if (and contents - org-texinfo-table-scientific-notation - (string-match orgtbl-exp-regexp contents)) - ;; Use appropriate format string for scientific - ;; notation. - (format org-texinfo-table-scientific-notation - (match-string 1 contents) - (match-string 2 contents)) - contents) - (when (org-export-get-next-element table-cell info) "\n@tab "))) + (concat + (let ((scientific-notation + (plist-get info :texinfo-table-scientific-notation))) + (if (and contents + scientific-notation + (string-match orgtbl-exp-regexp contents)) + ;; Use appropriate format string for scientific notation. + (format scientific-notation + (match-string 1 contents) + (match-string 2 contents)) + contents)) + (when (org-export-get-next-element table-cell info) "\n@tab "))) ;;;; Table Row @@ -1365,39 +1532,47 @@ a communication channel." ;;;; Target -(defun org-texinfo-target (target contents info) +(defun org-texinfo-target (target _contents info) "Transcode a TARGET object from Org to Texinfo. CONTENTS is nil. INFO is a plist holding contextual information." - (format "@anchor{%s}" - (org-export-solidify-link-text (org-element-property :value target)))) + (format "@anchor{%s}" (org-texinfo--get-node target info))) ;;;; Timestamp -(defun org-texinfo-timestamp (timestamp contents info) +(defun org-texinfo-timestamp (timestamp _contents info) "Transcode a TIMESTAMP object from Org to Texinfo. CONTENTS is nil. INFO is a plist holding contextual information." (let ((value (org-texinfo-plain-text (org-timestamp-translate timestamp) info))) - (case (org-element-property :type timestamp) - ((active active-range) - (format org-texinfo-active-timestamp-format value)) - ((inactive inactive-range) - (format org-texinfo-inactive-timestamp-format value)) - (t (format org-texinfo-diary-timestamp-format value))))) + (pcase (org-element-property :type timestamp) + ((or `active `active-range) + (format (plist-get info :texinfo-active-timestamp-format) value)) + ((or `inactive `inactive-range) + (format (plist-get info :texinfo-inactive-timestamp-format) value)) + (_ (format (plist-get info :texinfo-diary-timestamp-format) value))))) + +;;;; Underline + +(defun org-texinfo-underline (_underline contents info) + "Transcode UNDERLINE from Org to Texinfo. +CONTENTS is the text with underline markup. INFO is a plist +holding contextual information." + (org-texinfo--text-markup contents 'underline info)) ;;;; Verbatim -(defun org-texinfo-verbatim (verbatim contents info) +(defun org-texinfo-verbatim (verbatim _contents info) "Transcode a VERBATIM object from Org to Texinfo. CONTENTS is nil. INFO is a plist used as a communication channel." - (org-texinfo--text-markup (org-element-property :value verbatim) 'verbatim)) + (org-texinfo--text-markup + (org-element-property :value verbatim) 'verbatim info)) ;;;; Verse Block -(defun org-texinfo-verse-block (verse-block contents info) +(defun org-texinfo-verse-block (_verse-block contents _info) "Transcode a VERSE-BLOCK element from Org to Texinfo. CONTENTS is verse block contents. INFO is a plist holding contextual information." @@ -1406,6 +1581,7 @@ contextual information." ;;; Interactive functions +;;;###autoload (defun org-texinfo-export-to-texinfo (&optional async subtreep visible-only body-only ext-plist) "Export current buffer to a Texinfo file. @@ -1436,10 +1612,11 @@ file-local settings. Return output file's name." (interactive) (let ((outfile (org-export-output-file-name ".texi" subtreep)) - (org-export-coding-system `,org-texinfo-coding-system)) + (org-export-coding-system org-texinfo-coding-system)) (org-export-to-file 'texinfo outfile async subtreep visible-only body-only ext-plist))) +;;;###autoload (defun org-texinfo-export-to-info (&optional async subtreep visible-only body-only ext-plist) "Export current buffer to Texinfo then process through to INFO. @@ -1473,7 +1650,7 @@ directory. Return INFO file's name." (interactive) (let ((outfile (org-export-output-file-name ".texi" subtreep)) - (org-export-coding-system `,org-texinfo-coding-system)) + (org-export-coding-system org-texinfo-coding-system)) (org-export-to-file 'texinfo outfile async subtreep visible-only body-only ext-plist (lambda (file) (org-texinfo-compile file))))) @@ -1491,99 +1668,36 @@ Return output file name." ;;;###autoload (defun org-texinfo-convert-region-to-texinfo () - "Assume the current region has org-mode syntax, and convert it to Texinfo. + "Assume the current region has Org syntax, and convert it to Texinfo. This can be used in any buffer. For example, you can write an -itemized list in org-mode syntax in an Texinfo buffer and use -this command to convert it." +itemized list in Org syntax in an Texinfo buffer and use this +command to convert it." (interactive) (org-export-replace-region-by 'texinfo)) (defun org-texinfo-compile (file) "Compile a texinfo file. -FILE is the name of the file being compiled. Processing is -done through the command specified in `org-texinfo-info-process'. +FILE is the name of the file being compiled. Processing is done +through the command specified in `org-texinfo-info-process', +which see. Output is redirected to \"*Org INFO Texinfo Output*\" +buffer. Return INFO file name or an error if it couldn't be produced." - (let* ((base-name (file-name-sans-extension (file-name-nondirectory file))) - (full-name (file-truename file)) - (out-dir (file-name-directory file)) - ;; Properly set working directory for compilation. - (default-directory (if (file-name-absolute-p file) - (file-name-directory full-name) - default-directory)) - errors) - (message "Processing Texinfo file %s..." file) - (save-window-excursion - ;; Replace %b, %f and %o with appropriate values in each command - ;; before applying it. Output is redirected to "*Org INFO - ;; Texinfo Output*" buffer. - (let ((outbuf (get-buffer-create "*Org INFO Texinfo Output*"))) - (dolist (command org-texinfo-info-process) - (shell-command - (replace-regexp-in-string - "%b" (shell-quote-argument base-name) - (replace-regexp-in-string - "%f" (shell-quote-argument full-name) - (replace-regexp-in-string - "%o" (shell-quote-argument out-dir) command t t) t t) t t) - outbuf)) - ;; Collect standard errors from output buffer. - (setq errors (org-texinfo-collect-errors outbuf))) - (let ((infofile (concat out-dir base-name ".info"))) - ;; Check for process failure. Provide collected errors if - ;; possible. - (if (not (file-exists-p infofile)) - (error "INFO file %s wasn't produced%s" infofile - (if errors (concat ": " errors) "")) - ;; Else remove log files, when specified, and signal end of - ;; process to user, along with any error encountered. - (when org-texinfo-remove-logfiles - (dolist (ext org-texinfo-logfiles-extensions) - (let ((file (concat out-dir base-name "." ext))) - (when (file-exists-p file) (delete-file file))))) - (message (concat "Process completed" - (if (not errors) "." - (concat " with errors: " errors))))) - ;; Return output file name. - infofile)))) - -(defun org-texinfo-collect-errors (buffer) - "Collect some kind of errors from \"makeinfo\" command output. - -BUFFER is the buffer containing output. - -Return collected error types as a string, or nil if there was -none." - (with-current-buffer buffer - (save-excursion - (goto-char (point-min)) - ;; Find final "makeinfo" run. - (when t - (let ((case-fold-search t) - (errors "")) - (when (save-excursion - (re-search-forward "perhaps incorrect sectioning?" nil t)) - (setq errors (concat errors " [incorrect sectioning]"))) - (when (save-excursion - (re-search-forward "missing close brace" nil t)) - (setq errors (concat errors " [syntax error]"))) - (when (save-excursion - (re-search-forward "Unknown command" nil t)) - (setq errors (concat errors " [undefined @command]"))) - (when (save-excursion - (re-search-forward "No matching @end" nil t)) - (setq errors (concat errors " [block incomplete]"))) - (when (save-excursion - (re-search-forward "requires a sectioning" nil t)) - (setq errors (concat errors " [invalid section command]"))) - (when (save-excursion - (re-search-forward "\\[unexpected\ ]" nil t)) - (setq errors (concat errors " [unexpected error]"))) - (when (save-excursion - (re-search-forward "misplaced " nil t)) - (setq errors (concat errors " [syntax error]"))) - (and (org-string-nw-p errors) (org-trim errors))))))) + (message "Processing Texinfo file %s..." file) + (let* ((log-name "*Org INFO Texinfo Output*") + (log (get-buffer-create log-name)) + (output + (org-compile-file file org-texinfo-info-process "info" + (format "See %S for details" log-name) + log))) + (when org-texinfo-remove-logfiles + (let ((base (file-name-sans-extension output))) + (dolist (ext org-texinfo-logfiles-extensions) + (let ((file (concat base "." ext))) + (when (file-exists-p file) (delete-file file)))))) + (message "Process completed.") + output)) (provide 'ox-texinfo) diff --git a/lisp/org/ox.el b/lisp/org/ox.el index 59b66710dc1..8ea47d8ba6d 100644 --- a/lisp/org/ox.el +++ b/lisp/org/ox.el @@ -1,4 +1,4 @@ -;;; ox.el --- Generic Export Engine for Org Mode +;;; ox.el --- Export Framework for Org Mode -*- lexical-binding: t; -*- ;; Copyright (C) 2012-2017 Free Software Foundation, Inc. @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -28,12 +28,10 @@ ;; Besides that parser, the generic exporter is made of three distinct ;; parts: ;; -;; - The communication channel consists in a property list, which is +;; - The communication channel consists of a property list, which is ;; created and updated during the process. Its use is to offer ;; every piece of information, would it be about initial environment -;; or contextual data, all in a single place. The exhaustive list -;; of properties is given in "The Communication Channel" section of -;; this file. +;; or contextual data, all in a single place. ;; ;; - The transcoder walks the parse tree, ignores or treat as plain ;; text elements and objects according to export options, and @@ -46,8 +44,9 @@ ;; output from back-end transcoders. See "The Filter System" ;; section for more information. ;; -;; The core function is `org-export-as'. It returns the transcoded -;; buffer as a string. +;; The core functions is `org-export-as'. It returns the transcoded +;; buffer as a string. Its derivatives are `org-export-to-buffer' and +;; `org-export-to-file'. ;; ;; An export back-end is defined with `org-export-define-backend'. ;; This function can also support specific buffer keywords, OPTION @@ -64,32 +63,31 @@ ;; Tools for common tasks across back-ends are implemented in the ;; following part of the file. ;; -;; Then, a wrapper macro for asynchronous export, -;; `org-export-async-start', along with tools to display results. are -;; given in the penultimate part. +;; Eventually, a dispatcher (`org-export-dispatch') is provided in the +;; last one. ;; -;; Eventually, a dispatcher (`org-export-dispatch') for standard -;; back-ends is provided in the last one. +;; See <http://orgmode.org/worg/dev/org-export-reference.html> for +;; more information. ;;; Code: -(eval-when-compile (require 'cl)) +(require 'cl-lib) +(require 'ob-exp) (require 'org-element) (require 'org-macro) -(require 'ob-exp) +(require 'tabulated-list) +(declare-function org-src-coderef-format "org-src" (&optional element)) +(declare-function org-src-coderef-regexp "org-src" (fmt &optional label)) (declare-function org-publish "ox-publish" (project &optional force async)) (declare-function org-publish-all "ox-publish" (&optional force async)) -(declare-function - org-publish-current-file "ox-publish" (&optional force async)) -(declare-function org-publish-current-project "ox-publish" - (&optional force async)) +(declare-function org-publish-current-file "ox-publish" (&optional force async)) +(declare-function org-publish-current-project "ox-publish" (&optional force async)) (defvar org-publish-project-alist) (defvar org-table-number-fraction) (defvar org-table-number-regexp) - ;;; Internal Variables ;; @@ -101,22 +99,21 @@ "Maximum nesting depth for headlines, counting from 0.") (defconst org-export-options-alist - '((:author "AUTHOR" nil user-full-name t) - (:creator "CREATOR" nil org-export-creator-string) - (:date "DATE" nil nil t) - (:description "DESCRIPTION" nil nil newline) + '((:title "TITLE" nil nil parse) + (:date "DATE" nil nil parse) + (:author "AUTHOR" nil user-full-name parse) (:email "EMAIL" nil user-mail-address t) + (:language "LANGUAGE" nil org-export-default-language t) + (:select-tags "SELECT_TAGS" nil org-export-select-tags split) (:exclude-tags "EXCLUDE_TAGS" nil org-export-exclude-tags split) + (:creator "CREATOR" nil org-export-creator-string) (:headline-levels nil "H" org-export-headline-levels) - (:keywords "KEYWORDS" nil nil space) - (:language "LANGUAGE" nil org-export-default-language t) (:preserve-breaks nil "\\n" org-export-preserve-breaks) (:section-numbers nil "num" org-export-with-section-numbers) - (:select-tags "SELECT_TAGS" nil org-export-select-tags split) (:time-stamp-file nil "timestamp" org-export-time-stamp-file) - (:title "TITLE" nil nil space) (:with-archived-trees nil "arch" org-export-with-archived-trees) (:with-author nil "author" org-export-with-author) + (:with-broken-links nil "broken-links" org-export-with-broken-links) (:with-clocks nil "c" org-export-with-clocks) (:with-creator nil "creator" org-export-with-creator) (:with-date nil "date" org-export-with-date) @@ -130,6 +127,7 @@ (:with-latex nil "tex" org-export-with-latex) (:with-planning nil "p" org-export-with-planning) (:with-priority nil "pri" org-export-with-priority) + (:with-properties nil "prop" org-export-with-properties) (:with-smart-quotes nil "'" org-export-with-smart-quotes) (:with-special-strings nil "-" org-export-with-special-strings) (:with-statistics-cookies nil "stat" org-export-with-statistics-cookies) @@ -139,10 +137,11 @@ (:with-tags nil "tags" org-export-with-tags) (:with-tasks nil "tasks" org-export-with-tasks) (:with-timestamps nil "<" org-export-with-timestamps) + (:with-title nil "title" org-export-with-title) (:with-todo-keywords nil "todo" org-export-with-todo-keywords)) "Alist between export properties and ways to set them. -The CAR of the alist is the property name, and the CDR is a list +The key of the alist is the property name, and the value is a list like (KEYWORD OPTION DEFAULT BEHAVIOR) where: KEYWORD is a string representing a buffer keyword, or nil. Each @@ -161,6 +160,9 @@ BEHAVIOR determines how Org should handle multiple keywords for a newline. `split' Split values at white spaces, and cons them to the previous list. + `parse' Parse value as a list of strings and Org objects, + which can then be transcoded with, e.g., + `org-export-data'. It implies `space' behavior. Values set through KEYWORD and OPTION have precedence over DEFAULT. @@ -176,13 +178,12 @@ way they are handled must be hard-coded into `org-export--get-inbuffer-options' function.") (defconst org-export-filters-alist - '((:filter-bold . org-export-filter-bold-functions) + '((:filter-body . org-export-filter-body-functions) + (:filter-bold . org-export-filter-bold-functions) (:filter-babel-call . org-export-filter-babel-call-functions) (:filter-center-block . org-export-filter-center-block-functions) (:filter-clock . org-export-filter-clock-functions) (:filter-code . org-export-filter-code-functions) - (:filter-comment . org-export-filter-comment-functions) - (:filter-comment-block . org-export-filter-comment-block-functions) (:filter-diary-sexp . org-export-filter-diary-sexp-functions) (:filter-drawer . org-export-filter-drawer-functions) (:filter-dynamic-block . org-export-filter-dynamic-block-functions) @@ -215,7 +216,6 @@ way they are handled must be hard-coded into (:filter-planning . org-export-filter-planning-functions) (:filter-property-drawer . org-export-filter-property-drawer-functions) (:filter-quote-block . org-export-filter-quote-block-functions) - (:filter-quote-section . org-export-filter-quote-section-functions) (:filter-radio-target . org-export-filter-radio-target-functions) (:filter-section . org-export-filter-section-functions) (:filter-special-block . org-export-filter-special-block-functions) @@ -258,6 +258,16 @@ whose extension is either \"png\", \"jpeg\", \"jpg\", \"gif\", See `org-export-inline-image-p' for more information about rules.") +(defconst org-export-ignored-local-variables + '(org-font-lock-keywords + org-element--cache org-element--cache-objects org-element--cache-sync-keys + org-element--cache-sync-requests org-element--cache-sync-timer) + "List of variables not copied through upon buffer duplication. +Export process takes place on a copy of the original buffer. +When this copy is created, all Org related local variables not in +this list are copied to the new buffer. Variables with an +unreadable value are also ignored.") + (defvar org-export-async-debug nil "Non-nil means asynchronous export process should leave data behind. @@ -277,7 +287,7 @@ containing the back-end used, as a symbol, and either a process or the time at which it finished. It is used to build the menu from `org-export-stack'.") -(defvar org-export--registered-backends nil +(defvar org-export-registered-backends nil "List of backends currently available in the exporter. This variable is set with `org-export-define-backend' and `org-export-define-derived-backend' functions.") @@ -303,6 +313,7 @@ there is no export process in progress. It can be used to teach Babel blocks how to act differently according to the back-end used.") + ;;; User-configurable Variables ;; @@ -336,41 +347,46 @@ e.g. \"arch:nil\"." :type '(choice (const :tag "Not at all" nil) (const :tag "Headline only" headline) - (const :tag "Entirely" t))) + (const :tag "Entirely" t)) + :safe (lambda (x) (memq x '(t nil headline)))) (defcustom org-export-with-author t "Non-nil means insert author name into the exported file. This option can also be set with the OPTIONS keyword, e.g. \"author:nil\"." :group 'org-export-general - :type 'boolean) + :type 'boolean + :safe #'booleanp) (defcustom org-export-with-clocks nil "Non-nil means export CLOCK keywords. This option can also be set with the OPTIONS keyword, e.g. \"c:t\"." :group 'org-export-general - :type 'boolean) + :type 'boolean + :safe #'booleanp) -(defcustom org-export-with-creator 'comment +(defcustom org-export-with-creator nil "Non-nil means the postamble should contain a creator sentence. -The sentence can be set in `org-export-creator-string' and -defaults to \"Generated by Org mode XX in Emacs XXX.\". +The sentence can be set in `org-export-creator-string', which +see. -If the value is `comment' insert it as a comment." +This option can also be set with the OPTIONS keyword, e.g., +\"creator:t\"." :group 'org-export-general - :type '(choice - (const :tag "No creator sentence" nil) - (const :tag "Sentence as a comment" comment) - (const :tag "Insert the sentence" t))) + :version "26.1" + :package-version '(Org . "8.3") + :type 'boolean + :safe #'booleanp) (defcustom org-export-with-date t "Non-nil means insert date in the exported document. This option can also be set with the OPTIONS keyword, e.g. \"date:nil\"." :group 'org-export-general - :type 'boolean) + :type 'boolean + :safe #'booleanp) (defcustom org-export-date-timestamp-format nil "Time-stamp format string to use for DATE keyword. @@ -383,7 +399,8 @@ string." :group 'org-export-general :type '(choice (string :tag "Time-stamp format string") - (const :tag "No format string" nil))) + (const :tag "No format string" nil)) + :safe (lambda (x) (or (null x) (stringp x)))) (defcustom org-export-creator-string (format "Emacs %s (Org mode %s)" @@ -392,16 +409,18 @@ string." "Information about the creator of the document. This option can also be set on with the CREATOR keyword." :group 'org-export-general - :type '(string :tag "Creator string")) + :type '(string :tag "Creator string") + :safe #'stringp) (defcustom org-export-with-drawers '(not "LOGBOOK") "Non-nil means export contents of standard drawers. When t, all drawers are exported. This may also be a list of -drawer names to export. If that list starts with `not', only -drawers with such names will be ignored. +drawer names to export, as strings. If that list starts with +`not', only drawers with such names will be ignored. -This variable doesn't apply to properties drawers. +This variable doesn't apply to properties drawers. See +`org-export-with-properties' instead. This option can also be set with the OPTIONS keyword, e.g. \"d:nil\"." @@ -417,14 +436,16 @@ e.g. \"d:nil\"." (const :format "" not) (repeat :tag "Specify names of drawers to ignore during export" :inline t - (string :tag "Drawer name"))))) + (string :tag "Drawer name")))) + :safe (lambda (x) (or (booleanp x) (consp x)))) (defcustom org-export-with-email nil "Non-nil means insert author email into the exported file. This option can also be set with the OPTIONS keyword, e.g. \"email:t\"." :group 'org-export-general - :type 'boolean) + :type 'boolean + :safe #'booleanp) (defcustom org-export-with-emphasize t "Non-nil means interpret *word*, /word/, _word_ and +word+. @@ -436,7 +457,8 @@ respectively. This option can also be set with the OPTIONS keyword, e.g. \"*:nil\"." :group 'org-export-general - :type 'boolean) + :type 'boolean + :safe #'booleanp) (defcustom org-export-exclude-tags '("noexport") "Tags that exclude a tree from export. @@ -447,30 +469,26 @@ carry one of the `org-export-select-tags' will be removed. This option can also be set with the EXCLUDE_TAGS keyword." :group 'org-export-general - :type '(repeat (string :tag "Tag"))) + :type '(repeat (string :tag "Tag")) + :safe (lambda (x) (and (listp x) (cl-every #'stringp x)))) (defcustom org-export-with-fixed-width t - "Non-nil means lines starting with \":\" will be in fixed width font. - -This can be used to have pre-formatted text, fragments of code -etc. For example: - : ;; Some Lisp examples - : (while (defc cnt) - : (ding)) -will be looking just like this in also HTML. See also the QUOTE -keyword. Not all export backends support this. - + "Non-nil means export lines starting with \":\". This option can also be set with the OPTIONS keyword, e.g. \"::nil\"." :group 'org-export-general - :type 'boolean) + :version "24.4" + :package-version '(Org . "8.0") + :type 'boolean + :safe #'booleanp) (defcustom org-export-with-footnotes t "Non-nil means Org footnotes should be exported. This option can also be set with the OPTIONS keyword, e.g. \"f:nil\"." :group 'org-export-general - :type 'boolean) + :type 'boolean + :safe #'booleanp) (defcustom org-export-with-latex t "Non-nil means process LaTeX environments and fragments. @@ -487,7 +505,8 @@ t Allow export of math snippets." :type '(choice (const :tag "Do not process math in any way" nil) (const :tag "Interpret math snippets" t) - (const :tag "Leave math verbatim" verbatim))) + (const :tag "Leave math verbatim" verbatim)) + :safe (lambda (x) (memq x '(t nil verbatim)))) (defcustom org-export-headline-levels 3 "The last level which is still exported as a headline. @@ -498,7 +517,8 @@ when exported, but back-end behavior may differ. This option can also be set with the OPTIONS keyword, e.g. \"H:2\"." :group 'org-export-general - :type 'integer) + :type 'integer + :safe #'integerp) (defcustom org-export-default-language "en" "The default language for export and clocktable translations, as a string. @@ -507,14 +527,16 @@ This may have an association in `org-export-smart-quotes-alist' and `org-export-dictionary'. This option can also be set with the LANGUAGE keyword." :group 'org-export-general - :type '(string :tag "Language")) + :type '(string :tag "Language") + :safe #'stringp) (defcustom org-export-preserve-breaks nil "Non-nil means preserve all line breaks when exporting. This option can also be set with the OPTIONS keyword, e.g. \"\\n:t\"." :group 'org-export-general - :type 'boolean) + :type 'boolean + :safe #'booleanp) (defcustom org-export-with-entities t "Non-nil means interpret entities when exporting. @@ -528,7 +550,8 @@ and the user option `org-entities-user'. This option can also be set with the OPTIONS keyword, e.g. \"e:nil\"." :group 'org-export-general - :type 'boolean) + :type 'boolean + :safe #'booleanp) (defcustom org-export-with-inlinetasks t "Non-nil means inlinetasks should be exported. @@ -537,7 +560,8 @@ e.g. \"inline:nil\"." :group 'org-export-general :version "24.4" :package-version '(Org . "8.0") - :type 'boolean) + :type 'boolean + :safe #'booleanp) (defcustom org-export-with-planning nil "Non-nil means include planning info in export. @@ -550,14 +574,35 @@ e.g. \"p:t\"." :group 'org-export-general :version "24.4" :package-version '(Org . "8.0") - :type 'boolean) + :type 'boolean + :safe #'booleanp) (defcustom org-export-with-priority nil "Non-nil means include priority cookies in export. This option can also be set with the OPTIONS keyword, e.g. \"pri:t\"." :group 'org-export-general - :type 'boolean) + :type 'boolean + :safe #'booleanp) + +(defcustom org-export-with-properties nil + "Non-nil means export contents of properties drawers. + +When t, all properties are exported. This may also be a list of +properties to export, as strings. + +This option can also be set with the OPTIONS keyword, +e.g. \"prop:t\"." + :group 'org-export-general + :version "26.1" + :package-version '(Org . "8.3") + :type '(choice + (const :tag "All properties" t) + (const :tag "None" nil) + (repeat :tag "Selected properties" + (string :tag "Property name"))) + :safe (lambda (x) (or (booleanp x) + (and (listp x) (cl-every #'stringp x))))) (defcustom org-export-with-section-numbers t "Non-nil means add section numbers to headlines when exporting. @@ -568,7 +613,8 @@ headlines whose relative level is higher or equal to n. This option can also be set with the OPTIONS keyword, e.g. \"num:t\"." :group 'org-export-general - :type 'boolean) + :type 'boolean + :safe #'booleanp) (defcustom org-export-select-tags '("export") "Tags that select a tree for export. @@ -580,7 +626,8 @@ tagging it with one of the `org-export-exclude-tags'. This option can also be set with the SELECT_TAGS keyword." :group 'org-export-general - :type '(repeat (string :tag "Tag"))) + :type '(repeat (string :tag "Tag")) + :safe (lambda (x) (and (listp x) (cl-every #'stringp x)))) (defcustom org-export-with-smart-quotes nil "Non-nil means activate smart quotes during export. @@ -595,7 +642,8 @@ E.g., you can load Babel for french like this: :group 'org-export-general :version "24.4" :package-version '(Org . "8.0") - :type 'boolean) + :type 'boolean + :safe #'booleanp) (defcustom org-export-with-special-strings t "Non-nil means interpret \"\\-\", \"--\" and \"---\" for export. @@ -612,7 +660,8 @@ When this option is turned on, these strings will be exported as: This option can also be set with the OPTIONS keyword, e.g. \"-:nil\"." :group 'org-export-general - :type 'boolean) + :type 'boolean + :safe #'booleanp) (defcustom org-export-with-statistics-cookies t "Non-nil means include statistics cookies in export. @@ -621,7 +670,8 @@ e.g. \"stat:nil\"" :group 'org-export-general :version "24.4" :package-version '(Org . "8.0") - :type 'boolean) + :type 'boolean + :safe #'booleanp) (defcustom org-export-with-sub-superscripts t "Non-nil means interpret \"_\" and \"^\" for export. @@ -658,7 +708,8 @@ frequently in plain text." :type '(choice (const :tag "Interpret them" t) (const :tag "Curly brackets only" {}) - (const :tag "Do not interpret them" nil))) + (const :tag "Do not interpret them" nil)) + :safe (lambda (x) (memq x '(t nil {})))) (defcustom org-export-with-toc t "Non-nil means create a table of contents in exported files. @@ -676,20 +727,19 @@ e.g. \"toc:nil\" or \"toc:3\"." :type '(choice (const :tag "No Table of Contents" nil) (const :tag "Full Table of Contents" t) - (integer :tag "TOC to level"))) + (integer :tag "TOC to level")) + :safe (lambda (x) (or (booleanp x) + (integerp x)))) (defcustom org-export-with-tables t - "If non-nil, lines starting with \"|\" define a table. -For example: - - | Name | Address | Birthday | - |-------------+----------+-----------| - | Arthur Dent | England | 29.2.2100 | - + "Non-nil means export tables. This option can also be set with the OPTIONS keyword, e.g. \"|:nil\"." :group 'org-export-general - :type 'boolean) + :version "24.4" + :package-version '(Org . "8.0") + :type 'boolean + :safe #'booleanp) (defcustom org-export-with-tags t "If nil, do not export tags, just remove them from headlines. @@ -704,7 +754,8 @@ e.g. \"tags:nil\"." :type '(choice (const :tag "Off" nil) (const :tag "Not in TOC" not-in-toc) - (const :tag "On" t))) + (const :tag "On" t)) + :safe (lambda (x) (memq x '(t nil not-in-toc)))) (defcustom org-export-with-tasks t "Non-nil means include TODO items for export. @@ -725,14 +776,28 @@ e.g. \"tasks:nil\"." (const :tag "Not-done tasks" todo) (const :tag "Only done tasks" done) (repeat :tag "Specific TODO keywords" - (string :tag "Keyword")))) + (string :tag "Keyword"))) + :safe (lambda (x) (or (memq x '(nil t todo done)) + (and (listp x) + (cl-every #'stringp x))))) + +(defcustom org-export-with-title t + "Non-nil means print title into the exported file. +This option can also be set with the OPTIONS keyword, +e.g. \"title:nil\"." + :group 'org-export-general + :version "26.1" + :package-version '(Org . "8.3") + :type 'boolean + :safe #'booleanp) (defcustom org-export-time-stamp-file t "Non-nil means insert a time stamp into the exported file. -The time stamp shows when the file was created. This option can +The time stamp shows when the file was created. This option can also be set with the OPTIONS keyword, e.g. \"timestamp:nil\"." :group 'org-export-general - :type 'boolean) + :type 'boolean + :safe #'booleanp) (defcustom org-export-with-timestamps t "Non nil means allow timestamps in export. @@ -754,7 +819,8 @@ This option can also be set with the OPTIONS keyword, e.g. (const :tag "All timestamps" t) (const :tag "Only active timestamps" active) (const :tag "Only inactive timestamps" inactive) - (const :tag "No timestamp" nil))) + (const :tag "No timestamp" nil)) + :safe (lambda (x) (memq x '(t nil active inactive)))) (defcustom org-export-with-todo-keywords t "Non-nil means include TODO keywords in export. @@ -772,12 +838,33 @@ is nil. You can also allow them through local buffer variables." :package-version '(Org . "8.0") :type 'boolean) +(defcustom org-export-with-broken-links nil + "Non-nil means do not raise an error on broken links. + +When this variable is non-nil, broken links are ignored, without +stopping the export process. If it is set to `mark', broken +links are marked as such in the output, with a string like + + [BROKEN LINK: path] + +where PATH is the un-resolvable reference. + +This option can also be set with the OPTIONS keyword, e.g., +\"broken-links:mark\"." + :group 'org-export-general + :version "26.1" + :package-version '(Org . "9.0") + :type '(choice + (const :tag "Ignore broken links" t) + (const :tag "Mark broken links in output" mark) + (const :tag "Raise an error" nil))) + (defcustom org-export-snippet-translation-alist nil "Alist between export snippets back-ends and exporter back-ends. This variable allows providing shortcuts for export snippets. -For example, with a value of \((\"h\" . \"html\")), the +For example, with a value of \\='((\"h\" . \"html\")), the HTML back-end will recognize the contents of \"@@h:<b>@@\" as HTML code while every other back-end will ignore it." :group 'org-export-general @@ -785,7 +872,35 @@ HTML code while every other back-end will ignore it." :package-version '(Org . "8.0") :type '(repeat (cons (string :tag "Shortcut") - (string :tag "Back-end")))) + (string :tag "Back-end"))) + :safe (lambda (x) + (and (listp x) + (cl-every #'consp x) + (cl-every #'stringp (mapcar #'car x)) + (cl-every #'stringp (mapcar #'cdr x))))) + +(defcustom org-export-global-macros nil + "Alist between macro names and expansion templates. + +This variable defines macro expansion templates available +globally. Associations follow the pattern + + (NAME . TEMPLATE) + +where NAME is a string beginning with a letter and consisting of +alphanumeric characters only. + +TEMPLATE is the string to which the macro is going to be +expanded. Inside, \"$1\", \"$2\"... are place-holders for +macro's arguments. Moreover, if the template starts with +\"(eval\", it will be parsed as an Elisp expression and evaluated +accordingly." + :group 'org-export-general + :version "26.1" + :package-version '(Org . "9.1") + :type '(repeat + (cons (string :tag "Name") + (string :tag "Template")))) (defcustom org-export-coding-system nil "Coding system for the exported file." @@ -794,11 +909,12 @@ HTML code while every other back-end will ignore it." :package-version '(Org . "8.0") :type 'coding-system) -(defcustom org-export-copy-to-kill-ring 'if-interactive +(defcustom org-export-copy-to-kill-ring nil "Non-nil means pushing export output to the kill ring. This variable is ignored during asynchronous export." :group 'org-export-general - :version "24.3" + :version "26.1" + :package-version '(Org . "8.3") :type '(choice (const :tag "Always" t) (const :tag "When export is done interactively" if-interactive) @@ -825,21 +941,29 @@ these cases." (defcustom org-export-in-background nil "Non-nil means export and publishing commands will run in background. Results from an asynchronous export are never displayed -automatically. But you can retrieve them with \\[org-export-stack]." +automatically. But you can retrieve them with `\\[org-export-stack]'." :group 'org-export-general :version "24.4" :package-version '(Org . "8.0") :type 'boolean) -(defcustom org-export-async-init-file user-init-file +(defcustom org-export-async-init-file nil "File used to initialize external export process. -Value must be an absolute file name. It defaults to user's -initialization file. Though, a specific configuration makes the -process faster and the export more portable." + +Value must be either nil or an absolute file name. When nil, the +external process is launched like a regular Emacs session, +loading user's initialization file and any site specific +configuration. If a file is provided, it, and only it, is loaded +at start-up. + +Therefore, using a specific configuration makes the process to +load faster and the export more portable." :group 'org-export-general :version "24.4" :package-version '(Org . "8.0") - :type '(file :must-match t)) + :type '(choice + (const :tag "Regular startup" nil) + (file :tag "Specific start-up file" :must-match t))) (defcustom org-export-dispatch-use-expert-ui nil "Non-nil means using a non-intrusive `org-export-dispatch'. @@ -887,17 +1011,16 @@ mode." ;; Eventually `org-export-barf-if-invalid-backend' returns an error ;; when a given back-end hasn't been registered yet. -(defstruct (org-export-backend (:constructor org-export-create-backend) - (:copier nil)) +(cl-defstruct (org-export-backend (:constructor org-export-create-backend) + (:copier nil)) name parent transcoders options filters blocks menu) +;;;###autoload (defun org-export-get-backend (name) "Return export back-end named after NAME. NAME is a symbol. Return nil if no such back-end is found." - (catch 'found - (dolist (b org-export--registered-backends) - (when (eq (org-export-backend-name b) name) - (throw 'found b))))) + (cl-find-if (lambda (b) (and (eq name (org-export-backend-name b)))) + org-export-registered-backends)) (defun org-export-register-backend (backend) "Register BACKEND as a known export back-end. @@ -909,16 +1032,12 @@ BACKEND is a structure with `org-export-backend' type." (let ((parent (org-export-backend-parent backend))) (when (and parent (not (org-export-get-backend parent))) (error "Cannot use unknown \"%s\" back-end as a parent" parent))) - ;; Register dedicated export blocks in the parser. - (dolist (name (org-export-backend-blocks backend)) - (add-to-list 'org-element-block-name-alist - (cons name 'org-element-export-block-parser))) ;; If a back-end with the same name as BACKEND is already ;; registered, replace it with BACKEND. Otherwise, simply add ;; BACKEND to the list of registered back-ends. (let ((old (org-export-get-backend (org-export-backend-name backend)))) - (if old (setcar (memq old org-export--registered-backends) backend) - (push backend org-export--registered-backends)))) + (if old (setcar (memq old org-export-registered-backends) backend) + (push backend org-export-registered-backends)))) (defun org-export-barf-if-invalid-backend (backend) "Signal an error if BACKEND isn't defined." @@ -969,7 +1088,9 @@ BACKEND is an export back-end, as return by, e.g,, for the shape of the return value. Unlike to `org-export-backend-options', this function also -returns options inherited from parent back-ends, if any." +returns options inherited from parent back-ends, if any. + +Return nil if BACKEND is unknown." (when (symbolp backend) (setq backend (org-export-get-backend backend))) (when backend (let ((options (org-export-backend-options backend)) @@ -1039,14 +1160,6 @@ back-end. BODY can start with pre-defined keyword arguments. The following keywords are understood: - :export-block - - String, or list of strings, representing block names that - will not be parsed. This is used to specify blocks that will - contain raw code specific to the back-end. These blocks - still have to be handled by the relative `export-block' type - translator. - :filters-alist Alist between filters and function, or list of functions, @@ -1060,7 +1173,7 @@ keywords are understood: Menu entry for the export dispatcher. It should be a list like: - (KEY DESCRIPTION-OR-ORDINAL ACTION-OR-MENU) + \\='(KEY DESCRIPTION-OR-ORDINAL ACTION-OR-MENU) where : @@ -1084,17 +1197,17 @@ keywords are understood: If it is an alist, associations should follow the pattern: - (KEY DESCRIPTION ACTION) + \\='(KEY DESCRIPTION ACTION) where KEY, DESCRIPTION and ACTION are described above. Valid values include: - (?m \"My Special Back-end\" my-special-export-function) + \\='(?m \"My Special Back-end\" my-special-export-function) or - (?l \"Export to LaTeX\" + \\='(?l \"Export to LaTeX\" (?p \"As PDF file\" org-latex-export-to-pdf) (?o \"As PDF file and open\" (lambda (a s v b) @@ -1105,7 +1218,7 @@ keywords are understood: or the following, which will be added to the previous sub-menu, - (?l 1 + \\='(?l 1 ((?B \"As TEX buffer (Beamer)\" org-beamer-export-as-latex) (?P \"As PDF file (Beamer)\" org-beamer-export-to-pdf))) @@ -1116,22 +1229,19 @@ keywords are understood: `org-export-options-alist' for more information about structure of the values." (declare (indent 1)) - (let (blocks filters menu-entry options contents) + (let (filters menu-entry options) (while (keywordp (car body)) - (case (pop body) - (:export-block (let ((names (pop body))) - (setq blocks (if (consp names) (mapcar 'upcase names) - (list (upcase names)))))) - (:filters-alist (setq filters (pop body))) - (:menu-entry (setq menu-entry (pop body))) - (:options-alist (setq options (pop body))) - (t (pop body)))) + (let ((keyword (pop body))) + (pcase keyword + (:filters-alist (setq filters (pop body))) + (:menu-entry (setq menu-entry (pop body))) + (:options-alist (setq options (pop body))) + (_ (error "Unknown keyword: %s" keyword))))) (org-export-register-backend (org-export-create-backend :name backend :transcoders transcoders :options options :filters filters - :blocks blocks :menu menu-entry)))) (defun org-export-define-derived-backend (child parent &rest body) @@ -1143,14 +1253,6 @@ the parent back-end. BODY can start with pre-defined keyword arguments. The following keywords are understood: - :export-block - - String, or list of strings, representing block names that - will not be parsed. This is used to specify blocks that will - contain raw code specific to the back-end. These blocks - still have to be handled by the relative `export-block' type - translator. - :filters-alist Alist of filters that will overwrite or complete filters @@ -1187,24 +1289,21 @@ The back-end could then be called with, for example: (org-export-to-buffer \\='my-latex \"*Test my-latex*\")" (declare (indent 2)) - (let (blocks filters menu-entry options transcoders contents) + (let (filters menu-entry options transcoders) (while (keywordp (car body)) - (case (pop body) - (:export-block (let ((names (pop body))) - (setq blocks (if (consp names) (mapcar 'upcase names) - (list (upcase names)))))) - (:filters-alist (setq filters (pop body))) - (:menu-entry (setq menu-entry (pop body))) - (:options-alist (setq options (pop body))) - (:translate-alist (setq transcoders (pop body))) - (t (pop body)))) + (let ((keyword (pop body))) + (pcase keyword + (:filters-alist (setq filters (pop body))) + (:menu-entry (setq menu-entry (pop body))) + (:options-alist (setq options (pop body))) + (:translate-alist (setq transcoders (pop body))) + (_ (error "Unknown keyword: %s" keyword))))) (org-export-register-backend (org-export-create-backend :name child :parent parent :transcoders transcoders :options options :filters filters - :blocks blocks :menu menu-entry)))) @@ -1223,274 +1322,7 @@ The back-end could then be called with, for example: ;; `org-export-options-alist' variable. ;; ;; 2. Tree properties are extracted directly from the parsed tree, -;; just before export, by `org-export-collect-tree-properties'. -;; -;; Here is the full list of properties available during transcode -;; process, with their category and their value type. -;; -;; + `:author' :: Author's name. -;; - category :: option -;; - type :: string -;; -;; + `:back-end' :: Current back-end used for transcoding. -;; - category :: tree -;; - type :: symbol -;; -;; + `:creator' :: String to write as creation information. -;; - category :: option -;; - type :: string -;; -;; + `:date' :: String to use as date. -;; - category :: option -;; - type :: string -;; -;; + `:description' :: Description text for the current data. -;; - category :: option -;; - type :: string -;; -;; + `:email' :: Author's email. -;; - category :: option -;; - type :: string -;; -;; + `:exclude-tags' :: Tags for exclusion of subtrees from export -;; process. -;; - category :: option -;; - type :: list of strings -;; -;; + `:export-options' :: List of export options available for current -;; process. -;; - category :: none -;; - type :: list of symbols, among `subtree', `body-only' and -;; `visible-only'. -;; -;; + `:exported-data' :: Hash table used for memoizing -;; `org-export-data'. -;; - category :: tree -;; - type :: hash table -;; -;; + `:filetags' :: List of global tags for buffer. Used by -;; `org-export-get-tags' to get tags with inheritance. -;; - category :: option -;; - type :: list of strings -;; -;; + `:footnote-definition-alist' :: Alist between footnote labels and -;; their definition, as parsed data. Only non-inlined footnotes -;; are represented in this alist. Also, every definition isn't -;; guaranteed to be referenced in the parse tree. The purpose of -;; this property is to preserve definitions from oblivion -;; (i.e. when the parse tree comes from a part of the original -;; buffer), it isn't meant for direct use in a back-end. To -;; retrieve a definition relative to a reference, use -;; `org-export-get-footnote-definition' instead. -;; - category :: option -;; - type :: alist (STRING . LIST) -;; -;; + `:headline-levels' :: Maximum level being exported as an -;; headline. Comparison is done with the relative level of -;; headlines in the parse tree, not necessarily with their -;; actual level. -;; - category :: option -;; - type :: integer -;; -;; + `:headline-offset' :: Difference between relative and real level -;; of headlines in the parse tree. For example, a value of -1 -;; means a level 2 headline should be considered as level -;; 1 (cf. `org-export-get-relative-level'). -;; - category :: tree -;; - type :: integer -;; -;; + `:headline-numbering' :: Alist between headlines and their -;; numbering, as a list of numbers -;; (cf. `org-export-get-headline-number'). -;; - category :: tree -;; - type :: alist (INTEGER . LIST) -;; -;; + `:id-alist' :: Alist between ID strings and destination file's -;; path, relative to current directory. It is used by -;; `org-export-resolve-id-link' to resolve ID links targeting an -;; external file. -;; - category :: option -;; - type :: alist (STRING . STRING) -;; -;; + `:ignore-list' :: List of elements and objects that should be -;; ignored during export. -;; - category :: tree -;; - type :: list of elements and objects -;; -;; + `:input-buffer' :: Name of input buffer. -;; - category :: option -;; - type :: string -;; -;; + `:input-file' :: Full path to input file, if any. -;; - category :: option -;; - type :: string or nil -;; -;; + `:keywords' :: List of keywords attached to data. -;; - category :: option -;; - type :: string -;; -;; + `:language' :: Default language used for translations. -;; - category :: option -;; - type :: string -;; -;; + `:output-file' :: Full path to output file, if any. -;; - category :: option -;; - type :: string or nil -;; -;; + `:parse-tree' :: Whole parse tree, available at any time during -;; transcoding. -;; - category :: option -;; - type :: list (as returned by `org-element-parse-buffer') -;; -;; + `:preserve-breaks' :: Non-nil means transcoding should preserve -;; all line breaks. -;; - category :: option -;; - type :: symbol (nil, t) -;; -;; + `:section-numbers' :: Non-nil means transcoding should add -;; section numbers to headlines. -;; - category :: option -;; - type :: symbol (nil, t) -;; -;; + `:select-tags' :: List of tags enforcing inclusion of sub-trees -;; in transcoding. When such a tag is present, subtrees without -;; it are de facto excluded from the process. See -;; `use-select-tags'. -;; - category :: option -;; - type :: list of strings -;; -;; + `:time-stamp-file' :: Non-nil means transcoding should insert -;; a time stamp in the output. -;; - category :: option -;; - type :: symbol (nil, t) -;; -;; + `:translate-alist' :: Alist between element and object types and -;; transcoding functions relative to the current back-end. -;; Special keys `inner-template', `template' and `plain-text' are -;; also possible. -;; - category :: option -;; - type :: alist (SYMBOL . FUNCTION) -;; -;; + `:with-archived-trees' :: Non-nil when archived subtrees should -;; also be transcoded. If it is set to the `headline' symbol, -;; only the archived headline's name is retained. -;; - category :: option -;; - type :: symbol (nil, t, `headline') -;; -;; + `:with-author' :: Non-nil means author's name should be included -;; in the output. -;; - category :: option -;; - type :: symbol (nil, t) -;; -;; + `:with-clocks' :: Non-nil means clock keywords should be exported. -;; - category :: option -;; - type :: symbol (nil, t) -;; -;; + `:with-creator' :: Non-nil means a creation sentence should be -;; inserted at the end of the transcoded string. If the value -;; is `comment', it should be commented. -;; - category :: option -;; - type :: symbol (`comment', nil, t) -;; -;; + `:with-date' :: Non-nil means output should contain a date. -;; - category :: option -;; - type :. symbol (nil, t) -;; -;; + `:with-drawers' :: Non-nil means drawers should be exported. If -;; its value is a list of names, only drawers with such names -;; will be transcoded. If that list starts with `not', drawer -;; with these names will be skipped. -;; - category :: option -;; - type :: symbol (nil, t) or list of strings -;; -;; + `:with-email' :: Non-nil means output should contain author's -;; email. -;; - category :: option -;; - type :: symbol (nil, t) -;; -;; + `:with-emphasize' :: Non-nil means emphasized text should be -;; interpreted. -;; - category :: option -;; - type :: symbol (nil, t) -;; -;; + `:with-fixed-width' :: Non-nil if transcoder should interpret -;; strings starting with a colon as a fixed-with (verbatim) area. -;; - category :: option -;; - type :: symbol (nil, t) -;; -;; + `:with-footnotes' :: Non-nil if transcoder should interpret -;; footnotes. -;; - category :: option -;; - type :: symbol (nil, t) -;; -;; + `:with-latex' :: Non-nil means `latex-environment' elements and -;; `latex-fragment' objects should appear in export output. When -;; this property is set to `verbatim', they will be left as-is. -;; - category :: option -;; - type :: symbol (`verbatim', nil, t) -;; -;; + `:with-planning' :: Non-nil means transcoding should include -;; planning info. -;; - category :: option -;; - type :: symbol (nil, t) -;; -;; + `:with-priority' :: Non-nil means transcoding should include -;; priority cookies. -;; - category :: option -;; - type :: symbol (nil, t) -;; -;; + `:with-smart-quotes' :: Non-nil means activate smart quotes in -;; plain text. -;; - category :: option -;; - type :: symbol (nil, t) -;; -;; + `:with-special-strings' :: Non-nil means transcoding should -;; interpret special strings in plain text. -;; - category :: option -;; - type :: symbol (nil, t) -;; -;; + `:with-sub-superscript' :: Non-nil means transcoding should -;; interpret subscript and superscript. With a value of "{}", -;; only interpret those using curly brackets. -;; - category :: option -;; - type :: symbol (nil, {}, t) -;; -;; + `:with-tables' :: Non-nil means transcoding should interpret -;; tables. -;; - category :: option -;; - type :: symbol (nil, t) -;; -;; + `:with-tags' :: Non-nil means transcoding should keep tags in -;; headlines. A `not-in-toc' value will remove them from the -;; table of contents, if any, nonetheless. -;; - category :: option -;; - type :: symbol (nil, t, `not-in-toc') -;; -;; + `:with-tasks' :: Non-nil means transcoding should include -;; headlines with a TODO keyword. A `todo' value will only -;; include headlines with a todo type keyword while a `done' -;; value will do the contrary. If a list of strings is provided, -;; only tasks with keywords belonging to that list will be kept. -;; - category :: option -;; - type :: symbol (t, todo, done, nil) or list of strings -;; -;; + `:with-timestamps' :: Non-nil means transcoding should include -;; time stamps. Special value `active' (resp. `inactive') ask to -;; export only active (resp. inactive) timestamps. Otherwise, -;; completely remove them. -;; - category :: option -;; - type :: symbol: (`active', `inactive', t, nil) -;; -;; + `:with-toc' :: Non-nil means that a table of contents has to be -;; added to the output. An integer value limits its depth. -;; - category :: option -;; - type :: symbol (nil, t or integer) -;; -;; + `:with-todo-keywords' :: Non-nil means transcoding should -;; include TODO keywords. -;; - category :: option -;; - type :: symbol (nil, t) - +;; just before export, by `org-export--collect-tree-properties'. ;;;; Environment Options ;; @@ -1520,6 +1352,7 @@ The back-end could then be called with, for example: ;; along with their value in order to set them as buffer local ;; variables later in the process. +;;;###autoload (defun org-export-get-environment (&optional backend subtreep ext-plist) "Collect export options from the current buffer. @@ -1535,7 +1368,7 @@ inferior to file-local settings." ;; First install #+BIND variables since these must be set before ;; global options are read. (dolist (pair (org-export--list-bound-variables)) - (org-set-local (car pair) (nth 1 pair))) + (set (make-local-variable (car pair)) (nth 1 pair))) ;; Get and prioritize export options... (org-combine-plists ;; ... from global variables... @@ -1545,69 +1378,31 @@ inferior to file-local settings." ;; ... from in-buffer settings... (org-export--get-inbuffer-options backend) ;; ... and from subtree, when appropriate. - (and subtreep (org-export--get-subtree-options backend)) - ;; Eventually add misc. properties. - (list - :back-end - backend - :translate-alist (org-export-get-all-transcoders backend) - :footnote-definition-alist - ;; Footnotes definitions must be collected in the original - ;; buffer, as there's no insurance that they will still be in - ;; the parse tree, due to possible narrowing. - (let (alist) - (org-with-wide-buffer - (goto-char (point-min)) - (while (re-search-forward org-footnote-definition-re nil t) - (let ((def (save-match-data (org-element-at-point)))) - (when (eq (org-element-type def) 'footnote-definition) - (push - (cons (org-element-property :label def) - (let ((cbeg (org-element-property :contents-begin def))) - (when cbeg - (org-element--parse-elements - cbeg (org-element-property :contents-end def) - nil nil nil nil (list 'org-data nil))))) - alist)))) - alist)) - :id-alist - ;; Collect id references. - (let (alist) - (org-with-wide-buffer - (goto-char (point-min)) - (while (re-search-forward "\\[\\[id:\\S-+?\\]" nil t) - (let ((link (org-element-context))) - (when (eq (org-element-type link) 'link) - (let* ((id (org-element-property :path link)) - (file (org-id-find-id-file id))) - (when file - (push (cons id (file-relative-name file)) alist))))))) - alist)))) + (and subtreep (org-export--get-subtree-options backend)))) (defun org-export--parse-option-keyword (options &optional backend) "Parse an OPTIONS line and return values as a plist. Optional argument BACKEND is an export back-end, as returned by, e.g., `org-export-create-backend'. It specifies which back-end specific items to read, if any." - (let* ((all - ;; Priority is given to back-end specific options. - (append (and backend (org-export-get-all-options backend)) - org-export-options-alist)) - plist) - (dolist (option all) - (let ((property (car option)) - (item (nth 2 option))) - (when (and item - (not (plist-member plist property)) - (string-match (concat "\\(\\`\\|[ \t]\\)" - (regexp-quote item) - ":\\(([^)\n]+)\\|[^ \t\n\r;,.]*\\)") - options)) - (setq plist (plist-put plist - property - (car (read-from-string - (match-string 2 options)))))))) - plist)) + (let ((line + (let ((s 0) alist) + (while (string-match "\\(.+?\\):\\((.*?)\\|\\S-*\\)[ \t]*" options s) + (setq s (match-end 0)) + (push (cons (match-string 1 options) + (read (match-string 2 options))) + alist)) + alist)) + ;; Priority is given to back-end specific options. + (all (append (org-export-get-all-options backend) + org-export-options-alist)) + (plist)) + (when line + (dolist (entry all plist) + (let ((item (nth 2 entry))) + (when item + (let ((v (assoc-string item line t))) + (when v (setq plist (plist-put plist (car entry) (cdr v))))))))))) (defun org-export--get-subtree-options (&optional backend) "Get export options in subtree at point. @@ -1615,60 +1410,50 @@ Optional argument BACKEND is an export back-end, as returned by, e.g., `org-export-create-backend'. It specifies back-end used for export. Return options as a plist." ;; For each buffer keyword, create a headline property setting the - ;; same property in communication channel. The name for the property - ;; is the keyword with "EXPORT_" appended to it. + ;; same property in communication channel. The name for the + ;; property is the keyword with "EXPORT_" appended to it. (org-with-wide-buffer - (let (prop plist) - ;; Make sure point is at a heading. - (if (org-at-heading-p) (org-up-heading-safe) (org-back-to-heading t)) - ;; Take care of EXPORT_TITLE. If it isn't defined, use headline's - ;; title (with no todo keyword, priority cookie or tag) as its - ;; fallback value. - (when (setq prop (or (org-entry-get (point) "EXPORT_TITLE") - (progn (looking-at org-complex-heading-regexp) - (org-match-string-no-properties 4)))) - (setq plist - (plist-put - plist :title - (org-element-parse-secondary-string - prop (org-element-restriction 'keyword))))) - ;; EXPORT_OPTIONS are parsed in a non-standard way. - (when (setq prop (org-entry-get (point) "EXPORT_OPTIONS")) - (setq plist - (nconc plist (org-export--parse-option-keyword prop backend)))) - ;; Handle other keywords. TITLE keyword is excluded as it has - ;; been handled already. - (let ((seen '("TITLE"))) - (mapc - (lambda (option) - (let ((property (car option)) - (keyword (nth 1 option))) - (when (and keyword (not (member keyword seen))) - (let* ((subtree-prop (concat "EXPORT_" keyword)) - ;; Export properties are not case-sensitive. - (value (let ((case-fold-search t)) - (org-entry-get (point) subtree-prop)))) - (push keyword seen) - (when (and value (not (plist-member plist property))) - (setq plist - (plist-put - plist - property - (cond - ;; Parse VALUE if required. - ((member keyword org-element-document-properties) - (org-element-parse-secondary-string - value (org-element-restriction 'keyword))) - ;; If BEHAVIOR is `split' expected value is - ;; a list of strings, not a string. - ((eq (nth 4 option) 'split) (org-split-string value)) - (t value))))))))) - ;; Look for both general keywords and back-end specific - ;; options, with priority given to the latter. - (append (and backend (org-export-get-all-options backend)) - org-export-options-alist))) - ;; Return value. - plist))) + ;; Make sure point is at a heading. + (if (org-at-heading-p) (org-up-heading-safe) (org-back-to-heading t)) + (let ((plist + ;; EXPORT_OPTIONS are parsed in a non-standard way. Take + ;; care of them right from the start. + (let ((o (org-entry-get (point) "EXPORT_OPTIONS" 'selective))) + (and o (org-export--parse-option-keyword o backend)))) + ;; Take care of EXPORT_TITLE. If it isn't defined, use + ;; headline's title (with no todo keyword, priority cookie or + ;; tag) as its fallback value. + (cache (list + (cons "TITLE" + (or (org-entry-get (point) "EXPORT_TITLE" 'selective) + (let ((case-fold-search nil)) + (looking-at org-complex-heading-regexp) + (match-string-no-properties 4)))))) + ;; Look for both general keywords and back-end specific + ;; options, with priority given to the latter. + (options (append (org-export-get-all-options backend) + org-export-options-alist))) + ;; Handle other keywords. Then return PLIST. + (dolist (option options plist) + (let ((property (car option)) + (keyword (nth 1 option))) + (when keyword + (let ((value + (or (cdr (assoc keyword cache)) + (let ((v (org-entry-get (point) + (concat "EXPORT_" keyword) + 'selective))) + (push (cons keyword v) cache) v)))) + (when value + (setq plist + (plist-put plist + property + (cl-case (nth 4 option) + (parse + (org-element-parse-secondary-string + value (org-element-restriction 'keyword))) + (split (split-string value)) + (t value)))))))))))) (defun org-export--get-inbuffer-options (&optional backend) "Return current buffer export options, as a plist. @@ -1679,113 +1464,140 @@ which back-end specific options should also be read in the process. Assume buffer is in Org mode. Narrowing, if any, is ignored." - (let* (plist - get-options ; For byte-compiler. - (case-fold-search t) + (let* ((case-fold-search t) (options (append ;; Priority is given to back-end specific options. - (and backend (org-export-get-all-options backend)) + (org-export-get-all-options backend) org-export-options-alist)) (regexp (format "^[ \t]*#\\+%s:" - (regexp-opt (nconc (delq nil (mapcar 'cadr options)) + (regexp-opt (nconc (delq nil (mapcar #'cadr options)) org-export-special-keywords)))) - (find-properties - (lambda (keyword) - ;; Return all properties associated to KEYWORD. - (let (properties) - (dolist (option options properties) - (when (equal (nth 1 option) keyword) - (pushnew (car option) properties)))))) - (get-options - (lambda (&optional files plist) - ;; 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 ((file (expand-file-name - (org-remove-double-quotes (org-trim val))))) - ;; Avoid circular dependencies. - (unless (member file files) - (with-temp-buffer - (insert (org-file-contents file 'noerror)) - (let ((org-inhibit-startup t)) (org-mode)) - (setq plist (funcall get-options - (cons file files) plist)))))) - ((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)) - (let ((behavior (nth 4 (assq property options)))) + 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-unbracket-string "\"" "\"" (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 - (plist-put - plist property - ;; Handle value depending on specified - ;; BEHAVIOR. - (case behavior - (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) - ,@(org-split-string val))) - ('t val) - (otherwise - (if (not (plist-member plist property)) val - (plist-get plist property)))))))))))))) - ;; Return final value. - plist)))) - ;; Read options in the current buffer. - (setq plist (funcall get-options - (and buffer-file-name (list buffer-file-name)) nil)) - ;; Parse keywords specified in `org-element-document-properties' - ;; and return PLIST. - (dolist (keyword org-element-document-properties plist) - (dolist (property (funcall find-properties keyword)) - (let ((value (plist-get plist property))) - (when (stringp value) - (setq plist - (plist-put plist property - (or (org-element-parse-secondary-string - value (org-element-restriction 'keyword)) - ;; When TITLE keyword sets an empty - ;; string, make sure it doesn't - ;; appear as nil in the plist. - (and (eq property :title) "")))))))))) + (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))))))))))))))))) + ;; Read options in the current buffer and return value. + (funcall get-options (and buffer-file-name (list buffer-file-name))) + ;; Parse properties in TO-PARSE. Remove newline characters not + ;; involved in line breaks to simulate `space' behavior. + ;; Finally return options. + (dolist (p to-parse plist) + (let ((value (org-element-parse-secondary-string + (plist-get plist p) + (org-element-restriction 'keyword)))) + (org-element-map value 'plain-text + (lambda (s) + (org-element-set-element + s (replace-regexp-in-string "\n" " " s)))) + (setq plist (plist-put plist p value))))))) + +(defun org-export--get-export-attributes + (&optional backend subtreep visible-only body-only) + "Return properties related to export process, as a plist. +Optional arguments BACKEND, SUBTREEP, VISIBLE-ONLY and BODY-ONLY +are like the arguments with the same names of function +`org-export-as'." + (list :export-options (delq nil + (list (and subtreep 'subtree) + (and visible-only 'visible-only) + (and body-only 'body-only))) + :back-end backend + :translate-alist (org-export-get-all-transcoders backend) + :exported-data (make-hash-table :test #'eq :size 4001))) (defun org-export--get-buffer-attributes () "Return properties related to buffer attributes, as a plist." - ;; Store full path of input file name, or nil. For internal use. - (let ((visited-file (buffer-file-name (buffer-base-buffer)))) - (list :input-file visited-file - :input-buffer (buffer-name (buffer-base-buffer))))) + (list :input-buffer (buffer-name (buffer-base-buffer)) + :input-file (buffer-file-name (buffer-base-buffer)))) (defun org-export--get-global-options (&optional backend) "Return global export options as a plist. @@ -1795,7 +1607,7 @@ which back-end specific export options should also be read in the process." (let (plist ;; Priority is given to back-end specific options. - (all (append (and backend (org-export-get-all-options backend)) + (all (append (org-export-get-all-options backend) org-export-options-alist))) (dolist (cell all plist) (let ((prop (car cell))) @@ -1804,13 +1616,9 @@ process." (plist-put plist prop - ;; Evaluate default value provided. If keyword is - ;; a member of `org-element-document-properties', - ;; parse it as a secondary string before storing it. + ;; Evaluate default value provided. (let ((value (eval (nth 3 cell)))) - (if (and (stringp value) - (member (nth 1 cell) - org-element-document-properties)) + (if (eq (nth 4 cell) 'parse) (org-element-parse-secondary-string value (org-element-restriction 'keyword)) value))))))))) @@ -1820,35 +1628,42 @@ 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 - (let* (collect-bind ; For byte-compiler. - (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 ((file (expand-file-name - (org-remove-double-quotes val)))) - (unless (member file files) - (with-temp-buffer - (let ((org-inhibit-startup t)) (org-mode)) - (insert (org-file-contents file 'noerror)) - (setq alist - (funcall collect-bind - (cons file files) - alist)))))))))) - alist))))) + (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-unbracket-string "\"" "\"" 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))))) @@ -1864,7 +1679,7 @@ BLOB is the element or object considered." ;; ;; Tree properties are information extracted from parse tree. They ;; are initialized at the beginning of the transcoding process by -;; `org-export-collect-tree-properties'. +;; `org-export--collect-tree-properties'. ;; ;; Dedicated functions focus on computing the value of specific tree ;; properties during initialization. Thus, @@ -1875,7 +1690,7 @@ BLOB is the element or object considered." ;; `org-export--collect-headline-numbering' builds an alist between ;; headlines and their numbering. -(defun org-export-collect-tree-properties (data info) +(defun org-export--collect-tree-properties (data info) "Extract tree properties from parse tree. DATA is the parse tree from which information is retrieved. INFO @@ -1883,59 +1698,38 @@ is a list holding export options. Following tree properties are set or updated: -`:exported-data' Hash table used to memoize results from - `org-export-data'. - -`:footnote-definition-alist' List of footnotes definitions in - original buffer and current parse tree. - `:headline-offset' Offset between true level of headlines and local level. An offset of -1 means a headline of level 2 should be considered as a level 1 headline in the context. -`:headline-numbering' Alist of all headlines as key an the +`:headline-numbering' Alist of all headlines as key and the associated numbering as value. -`:ignore-list' List of elements that should be ignored during - export. +`:id-alist' Alist of all ID references as key and associated file + as value. Return updated plist." - ;; Install the parse tree in the communication channel, in order to - ;; use `org-export-get-genealogy' and al. + ;; Install the parse tree in the communication channel. (setq info (plist-put info :parse-tree data)) - ;; Get the list of elements and objects to ignore, and put it into - ;; `:ignore-list'. Do not overwrite any user ignore that might have - ;; been done during parse tree filtering. - (setq info - (plist-put info - :ignore-list - (append (org-export--populate-ignore-list data info) - (plist-get info :ignore-list)))) ;; Compute `:headline-offset' in order to be able to use ;; `org-export-get-relative-level'. (setq info (plist-put info :headline-offset (- 1 (org-export--get-min-level data info)))) - ;; Update footnotes definitions list with definitions in parse tree. - ;; This is required since buffer expansion might have modified - ;; boundaries of footnote definitions contained in the parse tree. - ;; This way, definitions in `footnote-definition-alist' are bound to - ;; match those in the parse tree. - (let ((defs (plist-get info :footnote-definition-alist))) - (org-element-map data 'footnote-definition - (lambda (fn) - (push (cons (org-element-property :label fn) - `(org-data nil ,@(org-element-contents fn))) - defs))) - (setq info (plist-put info :footnote-definition-alist defs))) - ;; Properties order doesn't matter: get the rest of the tree - ;; properties. - (nconc - `(:headline-numbering ,(org-export--collect-headline-numbering data info) - :exported-data ,(make-hash-table :test 'eq :size 4001)) - info)) + ;; From now on, properties order doesn't matter: get the rest of the + ;; tree properties. + (org-combine-plists + info + (list :headline-numbering (org-export--collect-headline-numbering data info) + :id-alist + (org-element-map data 'link + (lambda (l) + (and (string= (org-element-property :type l) "id") + (let* ((id (org-element-property :path l)) + (file (car (org-id-find id)))) + (and file (cons id (file-relative-name file)))))))))) (defun org-export--get-min-level (data options) "Return minimum exportable headline's level in DATA. @@ -1943,20 +1737,18 @@ DATA is parsed tree as returned by `org-element-parse-buffer'. OPTIONS is a plist holding export options." (catch 'exit (let ((min-level 10000)) - (mapc - (lambda (blob) - (when (and (eq (org-element-type blob) 'headline) - (not (org-element-property :footnote-section-p blob)) - (not (memq blob (plist-get options :ignore-list)))) - (setq min-level (min (org-element-property :level blob) min-level))) - (when (= min-level 1) (throw 'exit 1))) - (org-element-contents data)) + (dolist (datum (org-element-contents data)) + (when (and (eq (org-element-type datum) 'headline) + (not (org-element-property :footnote-section-p datum)) + (not (memq datum (plist-get options :ignore-list)))) + (setq min-level (min (org-element-property :level datum) min-level)) + (when (= min-level 1) (throw 'exit 1)))) ;; If no headline was found, for the sake of consistency, set ;; minimum level to 1 nonetheless. (if (= min-level 10000) 1 min-level)))) (defun org-export--collect-headline-numbering (data options) - "Return numbering of all exportable headlines in a parse tree. + "Return numbering of all exportable, numbered headlines in a parse tree. DATA is the parse tree. OPTIONS is the plist holding export options. @@ -1967,93 +1759,75 @@ for a footnotes section." (let ((numbering (make-vector org-export-max-depth 0))) (org-element-map data 'headline (lambda (headline) - (unless (org-element-property :footnote-section-p headline) + (when (and (org-export-numbered-headline-p headline options) + (not (org-element-property :footnote-section-p headline))) (let ((relative-level (1- (org-export-get-relative-level headline options)))) (cons headline - (loop for n across numbering - for idx from 0 to org-export-max-depth - when (< idx relative-level) collect n - when (= idx relative-level) collect (aset numbering idx (1+ n)) - when (> idx relative-level) do (aset numbering idx 0)))))) + (cl-loop + for n across numbering + for idx from 0 to org-export-max-depth + when (< idx relative-level) collect n + when (= idx relative-level) collect (aset numbering idx (1+ n)) + when (> idx relative-level) do (aset numbering idx 0)))))) options))) -(defun org-export--populate-ignore-list (data options) - "Return list of elements and objects to ignore during export. -DATA is the parse tree to traverse. OPTIONS is the plist holding -export options." - (let* (ignore - walk-data - ;; First find trees containing a select tag, if any. - (selected (org-export--selected-trees data options)) - (walk-data - (lambda (data) - ;; Collect ignored elements or objects into IGNORE-LIST. - (let ((type (org-element-type data))) - (if (org-export--skip-p data options selected) (push data ignore) - (if (and (eq type 'headline) - (eq (plist-get options :with-archived-trees) 'headline) - (org-element-property :archivedp data)) - ;; If headline is archived but tree below has - ;; to be skipped, add it to ignore list. - (mapc (lambda (e) (push e ignore)) - (org-element-contents data)) - ;; Move into secondary string, if any. - (let ((sec-prop - (cdr (assq type org-element-secondary-value-alist)))) - (when sec-prop - (mapc walk-data (org-element-property sec-prop data)))) - ;; Move into recursive objects/elements. - (mapc walk-data (org-element-contents data)))))))) - ;; Main call. - (funcall walk-data data) - ;; Return value. - ignore)) - (defun org-export--selected-trees (data info) - "Return list of headlines and inlinetasks with a select tag in their tree. + "List headlines and inlinetasks with a select tag in their tree. DATA is parsed data as returned by `org-element-parse-buffer'. INFO is a plist holding export options." - (let* (selected-trees - walk-data ; For byte-compiler. - (walk-data - (function - (lambda (data genealogy) - (let ((type (org-element-type data))) - (cond - ((memq type '(headline inlinetask)) - (let ((tags (org-element-property :tags data))) - (if (loop for tag in (plist-get info :select-tags) - thereis (member tag tags)) - ;; When a select tag is found, mark full - ;; genealogy and every headline within the tree - ;; as acceptable. - (setq selected-trees - (append - genealogy - (org-element-map data '(headline inlinetask) - 'identity) - selected-trees)) - ;; If at a headline, continue searching in tree, - ;; recursively. - (when (eq type 'headline) - (mapc (lambda (el) - (funcall walk-data el (cons data genealogy))) - (org-element-contents data)))))) - ((or (eq type 'org-data) - (memq type org-element-greater-elements)) - (mapc (lambda (el) (funcall walk-data el genealogy)) - (org-element-contents data))))))))) - (funcall walk-data data nil) - selected-trees)) - -(defun org-export--skip-p (blob options selected) - "Non-nil when element or object BLOB should be skipped during export. + (let ((select (plist-get info :select-tags))) + (if (cl-some (lambda (tag) (member tag select)) (plist-get info :filetags)) + ;; If FILETAGS contains a select tag, every headline or + ;; inlinetask is returned. + (org-element-map data '(headline inlinetask) #'identity) + (letrec ((selected-trees nil) + (walk-data + (lambda (data genealogy) + (let ((type (org-element-type data))) + (cond + ((memq type '(headline inlinetask)) + (let ((tags (org-element-property :tags data))) + (if (cl-some (lambda (tag) (member tag select)) tags) + ;; When a select tag is found, mark full + ;; genealogy and every headline within the + ;; tree as acceptable. + (setq selected-trees + (append + genealogy + (org-element-map data '(headline inlinetask) + #'identity) + selected-trees)) + ;; If at a headline, continue searching in + ;; tree, recursively. + (when (eq type 'headline) + (dolist (el (org-element-contents data)) + (funcall walk-data el (cons data genealogy))))))) + ((or (eq type 'org-data) + (memq type org-element-greater-elements)) + (dolist (el (org-element-contents data)) + (funcall walk-data el genealogy)))))))) + (funcall walk-data data nil) + selected-trees)))) + +(defun org-export--skip-p (datum options selected) + "Non-nil when element or object DATUM should be skipped during export. OPTIONS is the plist holding export options. SELECTED, when non-nil, is a list of headlines or inlinetasks belonging to a tree with a select tag." - (case (org-element-type blob) + (cl-case (org-element-type datum) + ((comment comment-block) + ;; Skip all comments and comment blocks. Make to keep maximum + ;; number of blank lines around the comment so as to preserve + ;; local structure of the document upon interpreting it back into + ;; Org syntax. + (let* ((previous (org-export-get-previous-element datum options)) + (before (or (org-element-property :post-blank previous) 0)) + (after (or (org-element-property :post-blank datum) 0))) + (when previous + (org-element-put-property previous :post-blank (max before after 1)))) + t) (clock (not (plist-get options :with-clocks))) (drawer (let ((with-drawers-p (plist-get options :with-drawers))) @@ -2063,31 +1837,32 @@ a tree with a select tag." ;; every drawer whose name belong to that list. ;; Otherwise, ignore drawers whose name isn't in that ;; list. - (let ((name (org-element-property :drawer-name blob))) + (let ((name (org-element-property :drawer-name datum))) (if (eq (car with-drawers-p) 'not) (member-ignore-case name (cdr with-drawers-p)) (not (member-ignore-case name with-drawers-p)))))))) + (fixed-width (not (plist-get options :with-fixed-width))) ((footnote-definition footnote-reference) (not (plist-get options :with-footnotes))) ((headline inlinetask) (let ((with-tasks (plist-get options :with-tasks)) - (todo (org-element-property :todo-keyword blob)) - (todo-type (org-element-property :todo-type blob)) + (todo (org-element-property :todo-keyword datum)) + (todo-type (org-element-property :todo-type datum)) (archived (plist-get options :with-archived-trees)) - (tags (org-element-property :tags blob))) + (tags (org-export-get-tags datum options nil t))) (or - (and (eq (org-element-type blob) 'inlinetask) + (and (eq (org-element-type datum) 'inlinetask) (not (plist-get options :with-inlinetasks))) ;; Ignore subtrees with an exclude tag. - (loop for k in (plist-get options :exclude-tags) - thereis (member k tags)) + (cl-loop for k in (plist-get options :exclude-tags) + thereis (member k tags)) ;; When a select tag is present in the buffer, ignore any tree ;; without it. - (and selected (not (memq blob selected))) + (and selected (not (memq datum selected))) ;; Ignore commented sub-trees. - (org-element-property :commentedp blob) + (org-element-property :commentedp datum) ;; Ignore archived subtrees if `:with-archived-trees' is nil. - (and (not archived) (org-element-property :archivedp blob)) + (and (not archived) (org-element-property :archivedp datum)) ;; Ignore tasks, if specified by `:with-tasks' property. (and todo (or (not with-tasks) @@ -2095,18 +1870,26 @@ a tree with a select tag." (not (eq todo-type with-tasks))) (and (consp with-tasks) (not (member todo with-tasks)))))))) ((latex-environment latex-fragment) (not (plist-get options :with-latex))) + (node-property + (let ((properties-set (plist-get options :with-properties))) + (cond ((null properties-set) t) + ((consp properties-set) + (not (member-ignore-case (org-element-property :key datum) + properties-set)))))) (planning (not (plist-get options :with-planning))) + (property-drawer (not (plist-get options :with-properties))) (statistics-cookie (not (plist-get options :with-statistics-cookies))) + (table (not (plist-get options :with-tables))) (table-cell (and (org-export-table-has-special-column-p - (org-export-get-parent-table blob)) - (not (org-export-get-previous-element blob options)))) - (table-row (org-export-table-row-is-special-p blob options)) + (org-export-get-parent-table datum)) + (org-export-first-sibling-p datum options))) + (table-row (org-export-table-row-is-special-p datum options)) (timestamp ;; `:with-timestamps' only applies to isolated timestamps ;; objects, i.e. timestamp objects in a paragraph containing only ;; timestamps and whitespaces. - (when (let ((parent (org-export-get-parent-element blob))) + (when (let ((parent (org-export-get-parent-element datum))) (and (memq (org-element-type parent) '(paragraph verse-block)) (not (org-element-map parent (cons 'plain-text @@ -2114,12 +1897,12 @@ a tree with a select tag." (lambda (obj) (or (not (stringp obj)) (org-string-nw-p obj))) options t)))) - (case (plist-get options :with-timestamps) - ('nil t) + (cl-case (plist-get options :with-timestamps) + ((nil) t) (active - (not (memq (org-element-property :type blob) '(active active-range)))) + (not (memq (org-element-property :type datum) '(active active-range)))) (inactive - (not (memq (org-element-property :type blob) + (not (memq (org-element-property :type datum) '(inactive inactive-range))))))))) @@ -2136,14 +1919,6 @@ a tree with a select tag." ;; `org-export-data' or even use a temporary back-end by using ;; `org-export-data-with-backend'. ;; -;; Internally, three functions handle the filtering of objects and -;; elements during the export. In particular, -;; `org-export-ignore-element' marks an element or object so future -;; parse tree traversals skip it, `org-export--interpret-p' tells which -;; elements or objects should be seen as real Org syntax and -;; `org-export-expand' transforms the others back into their original -;; shape -;; ;; `org-export-transcoder' is an accessor returning appropriate ;; translator function for a given element or object. @@ -2152,7 +1927,7 @@ a tree with a select tag." INFO is a plist containing export directives." (let ((type (org-element-type blob))) ;; Return contents only for complete parse trees. - (if (eq type 'org-data) (lambda (blob contents info) contents) + (if (eq type 'org-data) (lambda (_datum contents _info) contents) (let ((transcoder (cdr (assq type (plist-get info :translate-alist))))) (and (functionp transcoder) transcoder))))) @@ -2164,101 +1939,103 @@ string. INFO is a plist holding export options. Return a string." (or (gethash data (plist-get info :exported-data)) - (let* ((type (org-element-type data)) - (results - (cond - ;; Ignored element/object. - ((memq data (plist-get info :ignore-list)) nil) - ;; Plain text. - ((eq type 'plain-text) - (org-export-filter-apply-functions - (plist-get info :filter-plain-text) - (let ((transcoder (org-export-transcoder data info))) - (if transcoder (funcall transcoder data info) data)) - info)) - ;; Uninterpreted element/object: change it back to Org - ;; syntax and export again resulting raw string. - ((not (org-export--interpret-p data info)) - (org-export-data - (org-export-expand - data - (mapconcat (lambda (blob) (org-export-data blob info)) - (org-element-contents data) - "")) - info)) - ;; Secondary string. - ((not type) - (mapconcat (lambda (obj) (org-export-data obj info)) data "")) - ;; Element/Object without contents or, as a special - ;; case, headline with archive tag and archived trees - ;; restricted to title only. - ((or (not (org-element-contents data)) - (and (eq type 'headline) - (eq (plist-get info :with-archived-trees) 'headline) - (org-element-property :archivedp data))) - (let ((transcoder (org-export-transcoder data info))) - (or (and (functionp transcoder) - (funcall transcoder data nil info)) - ;; Export snippets never return a nil value so - ;; that white spaces following them are never - ;; ignored. - (and (eq type 'export-snippet) "")))) - ;; Element/Object with contents. - (t - (let ((transcoder (org-export-transcoder data info))) - (when transcoder - (let* ((greaterp (memq type org-element-greater-elements)) - (objectp - (and (not greaterp) - (memq type org-element-recursive-objects))) - (contents - (mapconcat - (lambda (element) (org-export-data element info)) - (org-element-contents - (if (or greaterp objectp) data - ;; Elements directly containing - ;; objects must have their indentation - ;; normalized first. - (org-element-normalize-contents - data - ;; When normalizing contents of the - ;; first paragraph in an item or - ;; a footnote definition, ignore - ;; first line's indentation: there is - ;; none and it might be misleading. - (when (eq type 'paragraph) - (let ((parent (org-export-get-parent data))) + ;; Handle broken links according to + ;; `org-export-with-broken-links'. + (cl-macrolet + ((broken-link-handler + (&rest body) + `(condition-case err + (progn ,@body) + (org-link-broken + (pcase (plist-get info :with-broken-links) + (`nil (user-error "Unable to resolve link: %S" (nth 1 err))) + (`mark (org-export-data + (format "[BROKEN LINK: %s]" (nth 1 err)) info)) + (_ nil)))))) + (let* ((type (org-element-type data)) + (parent (org-export-get-parent data)) + (results + (cond + ;; Ignored element/object. + ((memq data (plist-get info :ignore-list)) nil) + ;; Plain text. + ((eq type 'plain-text) + (org-export-filter-apply-functions + (plist-get info :filter-plain-text) + (let ((transcoder (org-export-transcoder data info))) + (if transcoder (funcall transcoder data info) data)) + info)) + ;; Secondary string. + ((not type) + (mapconcat (lambda (obj) (org-export-data obj info)) data "")) + ;; Element/Object without contents or, as a special + ;; case, headline with archive tag and archived trees + ;; restricted to title only. + ((or (not (org-element-contents data)) + (and (eq type 'headline) + (eq (plist-get info :with-archived-trees) 'headline) + (org-element-property :archivedp data))) + (let ((transcoder (org-export-transcoder data info))) + (or (and (functionp transcoder) + (broken-link-handler + (funcall transcoder data nil info))) + ;; Export snippets never return a nil value so + ;; that white spaces following them are never + ;; ignored. + (and (eq type 'export-snippet) "")))) + ;; Element/Object with contents. + (t + (let ((transcoder (org-export-transcoder data info))) + (when transcoder + (let* ((greaterp (memq type org-element-greater-elements)) + (objectp + (and (not greaterp) + (memq type org-element-recursive-objects))) + (contents + (mapconcat + (lambda (element) (org-export-data element info)) + (org-element-contents + (if (or greaterp objectp) data + ;; Elements directly containing + ;; objects must have their indentation + ;; normalized first. + (org-element-normalize-contents + data + ;; When normalizing contents of the + ;; first paragraph in an item or + ;; a footnote definition, ignore + ;; first line's indentation: there is + ;; none and it might be misleading. + (when (eq type 'paragraph) (and (eq (car (org-element-contents parent)) data) (memq (org-element-type parent) - '(footnote-definition item)))))))) - ""))) - (funcall transcoder data - (if (not greaterp) contents - (org-element-normalize-string contents)) - info)))))))) - ;; Final result will be memoized before being returned. - (puthash - data - (cond - ((not results) "") - ((memq type '(org-data plain-text nil)) results) - ;; Append the same white space between elements or objects - ;; as in the original buffer, and call appropriate filters. - (t - (let ((results - (org-export-filter-apply-functions - (plist-get info (intern (format ":filter-%s" type))) - (let ((post-blank (or (org-element-property :post-blank data) - 0))) - (if (memq type org-element-all-elements) - (concat (org-element-normalize-string results) - (make-string post-blank ?\n)) - (concat results (make-string post-blank ?\s)))) - info))) - results))) - (plist-get info :exported-data))))) + '(footnote-definition item))))))) + ""))) + (broken-link-handler + (funcall transcoder data + (if (not greaterp) contents + (org-element-normalize-string contents)) + info))))))))) + ;; Final result will be memoized before being returned. + (puthash + data + (cond + ((not results) "") + ((memq type '(org-data plain-text nil)) results) + ;; Append the same white space between elements or objects + ;; as in the original buffer, and call appropriate filters. + (t + (org-export-filter-apply-functions + (plist-get info (intern (format ":filter-%s" type))) + (let ((blank (or (org-element-property :post-blank data) 0))) + (if (eq (org-element-class data parent) 'object) + (concat results (make-string blank ?\s)) + (concat (org-element-normalize-string results) + (make-string blank ?\n)))) + info))) + (plist-get info :exported-data)))))) (defun org-export-data-with-backend (data backend info) "Convert DATA into BACKEND format. @@ -2270,44 +2047,24 @@ channel. Unlike to `org-export-with-backend', this function will recursively convert DATA using BACKEND translation table." (when (symbolp backend) (setq backend (org-export-get-backend backend))) - (org-export-data - data - ;; Set-up a new communication channel with translations defined in - ;; BACKEND as the translate table and a new hash table for - ;; memoization. - (org-combine-plists - info - (list :back-end backend - :translate-alist (org-export-get-all-transcoders backend) - ;; Size of the hash table is reduced since this function - ;; will probably be used on small trees. - :exported-data (make-hash-table :test 'eq :size 401))))) - -(defun org-export--interpret-p (blob info) - "Non-nil if element or object BLOB should be interpreted during export. -If nil, BLOB will appear as raw Org syntax. Check is done -according to export options INFO, stored as a plist." - (case (org-element-type blob) - ;; ... entities... - (entity (plist-get info :with-entities)) - ;; ... emphasis... - ((bold italic strike-through underline) - (plist-get info :with-emphasize)) - ;; ... fixed-width areas. - (fixed-width (plist-get info :with-fixed-width)) - ;; ... LaTeX environments and fragments... - ((latex-environment latex-fragment) - (let ((with-latex-p (plist-get info :with-latex))) - (and with-latex-p (not (eq with-latex-p 'verbatim))))) - ;; ... sub/superscripts... - ((subscript superscript) - (let ((sub/super-p (plist-get info :with-sub-superscript))) - (if (eq sub/super-p '{}) - (org-element-property :use-brackets-p blob) - sub/super-p))) - ;; ... tables... - (table (plist-get info :with-tables)) - (otherwise t))) + ;; Set-up a new communication channel with translations defined in + ;; BACKEND as the translate table and a new hash table for + ;; memoization. + (let ((new-info + (org-combine-plists + info + (list :back-end backend + :translate-alist (org-export-get-all-transcoders backend) + ;; Size of the hash table is reduced since this + ;; function will probably be used on small trees. + :exported-data (make-hash-table :test 'eq :size 401))))) + (prog1 (org-export-data data new-info) + ;; Preserve `:internal-references', as those do not depend on + ;; the back-end used; we need to make sure that any new + ;; reference when the temporary back-end was active gets through + ;; the default one. + (plist-put info :internal-references + (plist-get new-info :internal-references))))) (defun org-export-expand (blob contents &optional with-affiliated) "Expand a parsed element or object to its original state. @@ -2318,18 +2075,12 @@ contents, as a string or nil. When optional argument WITH-AFFILIATED is non-nil, add affiliated keywords before output." (let ((type (org-element-type blob))) - (concat (and with-affiliated (memq type org-element-all-elements) + (concat (and with-affiliated + (eq (org-element-class blob) 'element) (org-element--interpret-affiliated-keywords blob)) (funcall (intern (format "org-element-%s-interpreter" type)) blob contents)))) -(defun org-export-ignore-element (element info) - "Add ELEMENT to `:ignore-list' in INFO. - -Any element in `:ignore-list' will be skipped when using -`org-element-map'. INFO is modified by side effects." - (plist-put info :ignore-list (cons element (plist-get info :ignore-list)))) - ;;; The Filter System @@ -2360,9 +2111,13 @@ Any element in `:ignore-list' will be skipped when using ;; tree. Users can set it through ;; `org-export-filter-parse-tree-functions' variable. ;; +;; - `:filter-body' applies to the body of the output, before template +;; translator chimes in. Users can set it through +;; `org-export-filter-body-functions' variable. +;; ;; - `:filter-final-output' applies to the final transcoded string. ;; Users can set it with `org-export-filter-final-output-functions' -;; variable +;; variable. ;; ;; - `:filter-plain-text' applies to any string not recognized as Org ;; syntax. `org-export-filter-plain-text-functions' allows users to @@ -2370,7 +2125,7 @@ Any element in `:ignore-list' will be skipped when using ;; ;; - `:filter-TYPE' applies on the string returned after an element or ;; object of type TYPE has been transcoded. A user can modify -;; `org-export-filter-TYPE-functions' +;; `org-export-filter-TYPE-functions' to install these filters. ;; ;; All filters sets are applied with ;; `org-export-filter-apply-functions' function. Filters in a set are @@ -2433,6 +2188,13 @@ contains no Org syntax, the back-end, as a symbol, and the communication channel, as a plist. It must return a string or nil.") +(defvar org-export-filter-body-functions nil + "List of functions applied to transcoded body. +Each filter is called with three arguments: a string which +contains no Org syntax, the back-end, as a symbol, and the +communication channel, as a plist. It must return a string or +nil.") + (defvar org-export-filter-final-output-functions nil "List of functions applied to the transcoded string. Each filter is called with three arguments: the full transcoded @@ -2461,18 +2223,6 @@ Each filter is called with three arguments: the transcoded data, as a string, the back-end, as a symbol, and the communication channel, as a plist. It must return a string or nil.") -(defvar org-export-filter-comment-functions nil - "List of functions applied to a transcoded comment. -Each filter is called with three arguments: the transcoded data, -as a string, the back-end, as a symbol, and the communication -channel, as a plist. It must return a string or nil.") - -(defvar org-export-filter-comment-block-functions nil - "List of functions applied to a transcoded comment-block. -Each filter is called with three arguments: the transcoded data, -as a string, the back-end, as a symbol, and the communication -channel, as a plist. It must return a string or nil.") - (defvar org-export-filter-diary-sexp-functions nil "List of functions applied to a transcoded diary-sexp. Each filter is called with three arguments: the transcoded data, @@ -2588,12 +2338,6 @@ data, as a string, the back-end, as a symbol, and the communication channel, as a plist. It must return a string or nil.") -(defvar org-export-filter-quote-section-functions nil - "List of functions applied to a transcoded quote-section. -Each filter is called with three arguments: the transcoded data, -as a string, the back-end, as a symbol, and the communication -channel, as a plist. It must return a string or nil.") - (defvar org-export-filter-section-functions nil "List of functions applied to a transcoded section. Each filter is called with three arguments: the transcoded data, @@ -2774,20 +2518,24 @@ channel, as a plist. It must return a string or nil.") (defun org-export-filter-apply-functions (filters value info) "Call every function in FILTERS. -Functions are called with arguments VALUE, current export -back-end's name and INFO. A function returning a nil value will -be skipped. If it returns the empty string, the process ends and -VALUE is ignored. +Functions are called with three arguments: a value, the export +back-end name and the communication channel. First function in +FILTERS is called with VALUE as its first argument. Second +function in FILTERS is called with the previous result as its +value, etc. + +Functions returning nil are skipped. Any function returning the +empty string ends the process, which returns the empty string. Call is done in a LIFO fashion, to be sure that developer specified filters, if any, are called first." - (catch 'exit + (catch :exit (let* ((backend (plist-get info :back-end)) (backend-name (and backend (org-export-backend-name backend)))) (dolist (filter filters value) (let ((result (funcall filter value backend-name info))) - (cond ((not result) value) - ((equal value "") (throw 'exit nil)) + (cond ((not result)) + ((equal result "") (throw :exit "")) (t (setq value result)))))))) (defun org-export-install-filters (info) @@ -2797,29 +2545,27 @@ Return the updated communication channel." (let (plist) ;; Install user-defined filters with `org-export-filters-alist' ;; and filters already in INFO (through ext-plist mechanism). - (mapc (lambda (p) - (let* ((prop (car p)) - (info-value (plist-get info prop)) - (default-value (symbol-value (cdr p)))) - (setq plist - (plist-put plist prop - ;; Filters in INFO will be called - ;; before those user provided. - (append (if (listp info-value) info-value - (list info-value)) - default-value))))) - org-export-filters-alist) + (dolist (p org-export-filters-alist) + (let* ((prop (car p)) + (info-value (plist-get info prop)) + (default-value (symbol-value (cdr p)))) + (setq plist + (plist-put plist prop + ;; Filters in INFO will be called + ;; before those user provided. + (append (if (listp info-value) info-value + (list info-value)) + default-value))))) ;; Prepend back-end specific filters to that list. - (mapc (lambda (p) - ;; Single values get consed, lists are appended. - (let ((key (car p)) (value (cdr p))) - (when value - (setq plist - (plist-put - plist key - (if (atom value) (cons value (plist-get plist key)) - (append value (plist-get plist key)))))))) - (org-export-get-all-filters (plist-get info :back-end))) + (dolist (p (org-export-get-all-filters (plist-get info :back-end))) + ;; Single values get consed, lists are appended. + (let ((key (car p)) (value (cdr p))) + (when value + (setq plist + (plist-put + plist key + (if (atom value) (cons value (plist-get plist key)) + (append value (plist-get plist key)))))))) ;; Return new communication channel. (org-combine-plists info plist))) @@ -2905,7 +2651,7 @@ The function assumes BUFFER's major mode is `org-mode'." (when (consp entry) (let ((var (car entry)) (val (cdr entry))) - (and (not (eq var 'org-font-lock-keywords)) + (and (not (memq var org-export-ignored-local-variables)) (or (memq var '(default-directory buffer-file-name @@ -2932,21 +2678,301 @@ The function assumes BUFFER's major mode is `org-mode'." (goto-char ,(point)) ;; Overlays with invisible property. ,@(let (ov-set) - (mapc - (lambda (ov) - (let ((invis-prop (overlay-get ov 'invisible))) - (when invis-prop - (push `(overlay-put - (make-overlay ,(overlay-start ov) - ,(overlay-end ov)) - 'invisible (quote ,invis-prop)) - ov-set)))) - (overlays-in (point-min) (point-max))) - ov-set))))) + (dolist (ov (overlays-in (point-min) (point-max)) ov-set) + (let ((invis-prop (overlay-get ov 'invisible))) + (when invis-prop + (push `(overlay-put + (make-overlay ,(overlay-start ov) + ,(overlay-end ov)) + 'invisible (quote ,invis-prop)) + ov-set))))))))) + +(defun org-export--delete-comment-trees () + "Delete commented trees and commented inlinetasks in the buffer. +Narrowing, if any, is ignored." + (org-with-wide-buffer + (goto-char (point-min)) + (let* ((case-fold-search t) + (regexp (concat org-outline-regexp-bol ".*" org-comment-string))) + (while (re-search-forward regexp nil t) + (let ((element (org-element-at-point))) + (when (org-element-property :commentedp element) + (delete-region (org-element-property :begin element) + (org-element-property :end element)))))))) + +(defun org-export--prune-tree (data info) + "Prune non exportable elements from DATA. +DATA is the parse tree to traverse. INFO is the plist holding +export info. Also set `:ignore-list' in INFO to a list of +objects which should be ignored during export, but not removed +from tree." + (letrec ((ignore nil) + ;; First find trees containing a select tag, if any. + (selected (org-export--selected-trees data info)) + (walk-data + (lambda (data) + ;; Prune non-exportable elements and objects from tree. + ;; As a special case, special rows and cells from tables + ;; are stored in IGNORE, as they still need to be + ;; accessed during export. + (when data + (let ((type (org-element-type data))) + (if (org-export--skip-p data info selected) + (if (memq type '(table-cell table-row)) (push data ignore) + (org-element-extract-element data)) + (if (and (eq type 'headline) + (eq (plist-get info :with-archived-trees) + 'headline) + (org-element-property :archivedp data)) + ;; If headline is archived but tree below has + ;; to be skipped, remove contents. + (org-element-set-contents data) + ;; Move into recursive objects/elements. + (mapc walk-data (org-element-contents data))) + ;; Move into secondary string, if any. + (dolist (p (cdr (assq type + org-element-secondary-value-alist))) + (mapc walk-data (org-element-property p data)))))))) + (definitions + ;; Collect definitions before possibly pruning them so as + ;; to avoid parsing them again if they are required. + (org-element-map data '(footnote-definition footnote-reference) + (lambda (f) + (cond + ((eq 'footnote-definition (org-element-type f)) f) + ((and (eq 'inline (org-element-property :type f)) + (org-element-property :label f)) + f) + (t nil)))))) + ;; If a select tag is active, also ignore the section before the + ;; first headline, if any. + (when selected + (let ((first-element (car (org-element-contents data)))) + (when (eq (org-element-type first-element) 'section) + (org-element-extract-element first-element)))) + ;; Prune tree and communication channel. + (funcall walk-data data) + (dolist (entry (append + ;; Priority is given to back-end specific options. + (org-export-get-all-options (plist-get info :back-end)) + org-export-options-alist)) + (when (eq (nth 4 entry) 'parse) + (funcall walk-data (plist-get info (car entry))))) + (let ((missing (org-export--missing-definitions data definitions))) + (funcall walk-data missing) + (org-export--install-footnote-definitions missing data)) + ;; Eventually set `:ignore-list'. + (plist-put info :ignore-list ignore))) + +(defun org-export--missing-definitions (tree definitions) + "List footnote definitions missing from TREE. +Missing definitions are searched within DEFINITIONS, which is +a list of footnote definitions or in the widened buffer." + (let* ((list-labels + (lambda (data) + ;; List all footnote labels encountered in DATA. Inline + ;; footnote references are ignored. + (org-element-map data 'footnote-reference + (lambda (reference) + (and (eq (org-element-property :type reference) 'standard) + (org-element-property :label reference)))))) + defined undefined missing-definitions) + ;; Partition DIRECT-REFERENCES between DEFINED and UNDEFINED + ;; references. + (let ((known-definitions + (org-element-map tree '(footnote-reference footnote-definition) + (lambda (f) + (and (or (eq (org-element-type f) 'footnote-definition) + (eq (org-element-property :type f) 'inline)) + (org-element-property :label f))))) + seen) + (dolist (l (funcall list-labels tree)) + (cond ((member l seen)) + ((member l known-definitions) (push l defined)) + (t (push l undefined))))) + ;; Complete MISSING-DEFINITIONS by finding the definition of every + ;; undefined label, first by looking into DEFINITIONS, then by + ;; searching the widened buffer. This is a recursive process + ;; since definitions found can themselves contain an undefined + ;; reference. + (while undefined + (let* ((label (pop undefined)) + (definition + (cond + ((cl-some + (lambda (d) (and (equal (org-element-property :label d) label) + d)) + definitions)) + ((pcase (org-footnote-get-definition label) + (`(,_ ,beg . ,_) + (org-with-wide-buffer + (goto-char beg) + (let ((datum (org-element-context))) + (if (eq (org-element-type datum) 'footnote-reference) + datum + ;; Parse definition with contents. + (save-restriction + (narrow-to-region + (org-element-property :begin datum) + (org-element-property :end datum)) + (org-element-map (org-element-parse-buffer) + 'footnote-definition #'identity nil t)))))) + (_ nil))) + (t (user-error "Definition not found for footnote %s" label))))) + (push label defined) + (push definition missing-definitions) + ;; Look for footnote references within DEFINITION, since + ;; we may need to also find their definition. + (dolist (l (funcall list-labels definition)) + (unless (or (member l defined) ;Known label + (member l undefined)) ;Processed later + (push l undefined))))) + ;; MISSING-DEFINITIONS may contain footnote references with inline + ;; definitions. Make sure those are changed into real footnote + ;; definitions. + (mapcar (lambda (d) + (if (eq (org-element-type d) 'footnote-definition) d + (let ((label (org-element-property :label d))) + (apply #'org-element-create + 'footnote-definition `(:label ,label :post-blank 1) + (org-element-contents d))))) + missing-definitions))) + +(defun org-export--install-footnote-definitions (definitions tree) + "Install footnote definitions in tree. + +DEFINITIONS is the list of footnote definitions to install. TREE +is the parse tree. + +If there is a footnote section in TREE, definitions found are +appended to it. If `org-footnote-section' is non-nil, a new +footnote section containing all definitions is inserted in TREE. +Otherwise, definitions are appended at the end of the section +containing their first reference." + (cond + ((null definitions)) + ;; If there is a footnote section, insert definitions there. + ((let ((footnote-section + (org-element-map tree 'headline + (lambda (h) (and (org-element-property :footnote-section-p h) h)) + nil t))) + (and footnote-section + (apply #'org-element-adopt-elements + footnote-section + (nreverse definitions))))) + ;; If there should be a footnote section, create one containing all + ;; the definitions at the end of the tree. + (org-footnote-section + (org-element-adopt-elements + tree + (org-element-create 'headline + (list :footnote-section-p t + :level 1 + :title org-footnote-section + :raw-value org-footnote-section) + (apply #'org-element-create + 'section + nil + (nreverse definitions))))) + ;; Otherwise add each definition at the end of the section where it + ;; is first referenced. + (t + (letrec ((seen nil) + (insert-definitions + (lambda (data) + ;; Insert footnote definitions in the same section as + ;; their first reference in DATA. + (org-element-map data 'footnote-reference + (lambda (reference) + (when (eq (org-element-property :type reference) 'standard) + (let ((label (org-element-property :label reference))) + (unless (member label seen) + (push label seen) + (let ((definition + (cl-some + (lambda (d) + (and (equal (org-element-property :label d) + label) + d)) + definitions))) + (org-element-adopt-elements + (org-element-lineage reference '(section)) + definition) + ;; Also insert definitions for nested + ;; references, if any. + (funcall insert-definitions definition)))))))))) + (funcall insert-definitions tree))))) + +(defun org-export--remove-uninterpreted-data (data info) + "Change uninterpreted elements back into Org syntax. +DATA is a parse tree or a secondary string. INFO is a plist +containing export options. It is modified by side effect and +returned by the function." + (org-element-map data + '(entity bold italic latex-environment latex-fragment strike-through + subscript superscript underline) + (lambda (datum) + (let ((new + (cl-case (org-element-type datum) + ;; ... entities... + (entity + (and (not (plist-get info :with-entities)) + (list (concat + (org-export-expand datum nil) + (make-string + (or (org-element-property :post-blank datum) 0) + ?\s))))) + ;; ... emphasis... + ((bold italic strike-through underline) + (and (not (plist-get info :with-emphasize)) + (let ((marker (cl-case (org-element-type datum) + (bold "*") + (italic "/") + (strike-through "+") + (underline "_")))) + (append + (list marker) + (org-element-contents datum) + (list (concat + marker + (make-string + (or (org-element-property :post-blank datum) + 0) + ?\s))))))) + ;; ... LaTeX environments and fragments... + ((latex-environment latex-fragment) + (and (eq (plist-get info :with-latex) 'verbatim) + (list (org-export-expand datum nil)))) + ;; ... sub/superscripts... + ((subscript superscript) + (let ((sub/super-p (plist-get info :with-sub-superscript)) + (bracketp (org-element-property :use-brackets-p datum))) + (and (or (not sub/super-p) + (and (eq sub/super-p '{}) (not bracketp))) + (append + (list (concat + (if (eq (org-element-type datum) 'subscript) + "_" + "^") + (and bracketp "{"))) + (org-element-contents datum) + (list (concat + (and bracketp "}") + (and (org-element-property :post-blank datum) + (make-string + (org-element-property :post-blank datum) + ?\s))))))))))) + (when new + ;; Splice NEW at DATUM location in parse tree. + (dolist (e new (org-element-extract-element datum)) + (unless (equal e "") (org-element-insert-before e datum)))))) + info nil nil t) + ;; Return modified parse tree. + data) ;;;###autoload (defun org-export-as - (backend &optional subtreep visible-only body-only ext-plist) + (backend &optional subtreep visible-only body-only ext-plist) "Transcode current Org buffer into BACKEND code. BACKEND is either an export back-end, as returned by, e.g., @@ -2978,75 +3004,76 @@ Return code as a string." (save-excursion (save-restriction ;; Narrow buffer to an appropriate region or subtree for - ;; parsing. If parsing subtree, be sure to remove main headline - ;; too. + ;; parsing. If parsing subtree, be sure to remove main + ;; headline, planning data and property drawer. (cond ((org-region-active-p) (narrow-to-region (region-beginning) (region-end))) (subtreep (org-narrow-to-subtree) (goto-char (point-min)) - (forward-line) + (org-end-of-meta-data) (narrow-to-region (point) (point-max)))) ;; Initialize communication channel with original buffer ;; attributes, unavailable in its copy. (let* ((org-export-current-backend (org-export-backend-name backend)) (info (org-combine-plists - (list :export-options - (delq nil - (list (and subtreep 'subtree) - (and visible-only 'visible-only) - (and body-only 'body-only)))) + (org-export--get-export-attributes + backend subtreep visible-only body-only) (org-export--get-buffer-attributes))) + (parsed-keywords + (delq nil + (mapcar (lambda (o) (and (eq (nth 4 o) 'parse) (nth 1 o))) + (append (org-export-get-all-options backend) + org-export-options-alist)))) tree) ;; Update communication channel and get parse tree. Buffer - ;; isn't parsed directly. Instead, a temporary copy is - ;; created, where include keywords, macros are expanded and - ;; code blocks are evaluated. + ;; isn't parsed directly. Instead, all buffer modifications + ;; and consequent parsing are undertaken in a temporary copy. (org-export-with-buffer-copy ;; Run first hook with current back-end's name as argument. (run-hook-with-args 'org-export-before-processing-hook (org-export-backend-name backend)) + ;; Include files, delete comments and expand macros. (org-export-expand-include-keyword) - ;; Update macro templates since #+INCLUDE keywords might have - ;; added some new ones. + (org-export--delete-comment-trees) (org-macro-initialize-templates) - (org-macro-replace-all org-macro-templates) - (org-export-execute-babel-code) - ;; Update radio targets since keyword inclusion might have - ;; added some more. + (org-macro-replace-all + (append org-macro-templates org-export-global-macros) + nil parsed-keywords) + ;; Refresh buffer properties and radio targets after + ;; potentially invasive previous changes. Likewise, do it + ;; again after executing Babel code. + (org-set-regexps-and-options) (org-update-radio-target-regexp) + (when org-export-use-babel + (org-babel-exp-process-buffer) + (org-set-regexps-and-options) + (org-update-radio-target-regexp)) ;; Run last hook with current back-end's name as argument. + ;; Update buffer properties and radio targets one last time + ;; before parsing. (goto-char (point-min)) (save-excursion (run-hook-with-args 'org-export-before-parsing-hook (org-export-backend-name backend))) - ;; Update communication channel with environment. Also - ;; install user's and developer's filters. + (org-set-regexps-and-options) + (org-update-radio-target-regexp) + ;; Update communication channel with environment. (setq info - (org-export-install-filters - (org-combine-plists - info (org-export-get-environment backend subtreep ext-plist)))) - ;; Special case: provide original file name or buffer name as - ;; default value for :title property. - (unless (plist-get info :title) - (plist-put - info :title - (let ((file (plist-get info :input-file))) - (if file (file-name-sans-extension (file-name-nondirectory file)) - (plist-get info :input-buffer))))) - ;; Expand export-specific set of macros: {{{author}}}, - ;; {{{date}}}, {{{email}}} and {{{title}}}. It must be done - ;; once regular macros have been expanded, since document - ;; keywords may contain one of them. - (org-macro-replace-all - (list (cons "author" - (org-element-interpret-data (plist-get info :author))) - (cons "date" - (org-element-interpret-data (plist-get info :date))) - ;; EMAIL is not a parsed keyword: store it as-is. - (cons "email" (or (plist-get info :email) "")) - (cons "title" - (org-element-interpret-data (plist-get info :title))))) + (org-combine-plists + info (org-export-get-environment backend subtreep ext-plist))) + ;; De-activate uninterpreted data from parsed keywords. + (dolist (entry (append (org-export-get-all-options backend) + org-export-options-alist)) + (pcase entry + (`(,p ,_ ,_ ,_ parse) + (let ((value (plist-get info p))) + (plist-put info + p + (org-export--remove-uninterpreted-data value info)))) + (_ nil))) + ;; Install user's and developer's filters. + (setq info (org-export-install-filters info)) ;; Call options filters and update export options. We do not ;; use `org-export-filter-apply-functions' here since the ;; arity of such filters is different. @@ -3054,24 +3081,54 @@ Return code as a string." (dolist (filter (plist-get info :filter-options)) (let ((result (funcall filter info backend-name))) (when result (setq info result))))) - ;; Parse buffer and call parse-tree filter on it. + ;; Expand export-specific set of macros: {{{author}}}, + ;; {{{date(FORMAT)}}}, {{{email}}} and {{{title}}}. It must + ;; be done once regular macros have been expanded, since + ;; parsed keywords may contain one of them. + (org-macro-replace-all + (list + (cons "author" (org-element-interpret-data (plist-get info :author))) + (cons "date" + (let* ((date (plist-get info :date)) + (value (or (org-element-interpret-data date) ""))) + (if (and (consp date) + (not (cdr date)) + (eq (org-element-type (car date)) 'timestamp)) + (format "(eval (if (org-string-nw-p \"$1\") %s %S))" + (format "(org-timestamp-format '%S \"$1\")" + (org-element-copy (car date))) + value) + value))) + (cons "email" (org-element-interpret-data (plist-get info :email))) + (cons "title" (org-element-interpret-data (plist-get info :title))) + (cons "results" "$1")) + 'finalize + parsed-keywords) + ;; Parse buffer. + (setq tree (org-element-parse-buffer nil visible-only)) + ;; Prune tree from non-exported elements and transform + ;; uninterpreted elements or objects in both parse tree and + ;; communication channel. + (org-export--prune-tree tree info) + (org-export--remove-uninterpreted-data tree info) + ;; Call parse tree filters. (setq tree (org-export-filter-apply-functions - (plist-get info :filter-parse-tree) - (org-element-parse-buffer nil visible-only) info)) + (plist-get info :filter-parse-tree) tree info)) ;; Now tree is complete, compute its properties and add them ;; to communication channel. - (setq info - (org-combine-plists - info (org-export-collect-tree-properties tree info))) + (setq info (org-export--collect-tree-properties tree info)) ;; Eventually transcode TREE. Wrap the resulting string into ;; a template. (let* ((body (org-element-normalize-string (or (org-export-data tree info) ""))) (inner-template (cdr (assq 'inner-template (plist-get info :translate-alist)))) - (full-body (if (not (functionp inner-template)) body - (funcall inner-template body info))) + (full-body (org-export-filter-apply-functions + (plist-get info :filter-body) + (if (not (functionp inner-template)) body + (funcall inner-template body info)) + info)) (template (cdr (assq 'template (plist-get info :translate-alist))))) ;; Remove all text properties since they cannot be @@ -3111,14 +3168,10 @@ Return code as a string." BACKEND is either an export back-end, as returned by, e.g., `org-export-create-backend', or a symbol referring to a registered back-end." - (if (not (org-region-active-p)) - (user-error "No active region to replace") - (let* ((beg (region-beginning)) - (end (region-end)) - (str (buffer-substring beg end)) rpl) - (setq rpl (org-export-string-as str backend t)) - (delete-region beg end) - (insert rpl)))) + (unless (org-region-active-p) (user-error "No active region to replace")) + (insert + (org-export-string-as + (delete-and-extract-region (region-beginning) (region-end)) backend t))) ;;;###autoload (defun org-export-insert-default-template (&optional backend subtreep) @@ -3144,7 +3197,8 @@ locally for the subtree through node properties." (cons "default" (mapcar (lambda (b) (symbol-name (org-export-backend-name b))) - org-export--registered-backends)))))) + org-export-registered-backends)) + nil t)))) options keywords) ;; Populate OPTIONS and KEYWORDS. (dolist (entry (cond ((eq backend 'default) org-export-options-alist) @@ -3158,43 +3212,14 @@ locally for the subtree through node properties." (keyword (unless (assoc keyword keywords) (let ((value (if (eq (nth 4 entry) 'split) - (mapconcat 'identity (eval (nth 3 entry)) " ") + (mapconcat #'identity (eval (nth 3 entry)) " ") (eval (nth 3 entry))))) (push (cons keyword value) keywords)))) (option (unless (assoc option options) (push (cons option (eval (nth 3 entry))) options)))))) ;; Move to an appropriate location in order to insert options. (unless subtreep (beginning-of-line)) - ;; First get TITLE, DATE, AUTHOR and EMAIL if they belong to the - ;; list of available keywords. - (when (assoc "TITLE" keywords) - (let ((title - (or (let ((visited-file (buffer-file-name (buffer-base-buffer)))) - (and visited-file - (file-name-sans-extension - (file-name-nondirectory visited-file)))) - (buffer-name (buffer-base-buffer))))) - (if (not subtreep) (insert (format "#+TITLE: %s\n" title)) - (org-entry-put node "EXPORT_TITLE" title)))) - (when (assoc "DATE" keywords) - (let ((date (with-temp-buffer (org-insert-time-stamp (current-time))))) - (if (not subtreep) (insert "#+DATE: " date "\n") - (org-entry-put node "EXPORT_DATE" date)))) - (when (assoc "AUTHOR" keywords) - (let ((author (cdr (assoc "AUTHOR" keywords)))) - (if subtreep (org-entry-put node "EXPORT_AUTHOR" author) - (insert - (format "#+AUTHOR:%s\n" - (if (not (org-string-nw-p author)) "" - (concat " " author))))))) - (when (assoc "EMAIL" keywords) - (let ((email (cdr (assoc "EMAIL" keywords)))) - (if subtreep (org-entry-put node "EXPORT_EMAIL" email) - (insert - (format "#+EMAIL:%s\n" - (if (not (org-string-nw-p email)) "" - (concat " " email))))))) - ;; Then (multiple) OPTIONS lines. Never go past fill-column. + ;; First (multiple) OPTIONS lines. Never go past fill-column. (when options (let ((items (mapcar @@ -3210,103 +3235,233 @@ locally for the subtree through node properties." (< (+ width (length (car items)) 1) fill-column)) (let ((item (pop items))) (insert " " item) - (incf width (1+ (length item)))))) + (cl-incf width (1+ (length item)))))) (insert "\n"))))) - ;; And the rest of keywords. - (dolist (key (sort keywords (lambda (k1 k2) (string< (car k1) (car k2))))) - (unless (member (car key) '("TITLE" "DATE" "AUTHOR" "EMAIL")) - (let ((val (cdr key))) - (if subtreep (org-entry-put node (concat "EXPORT_" (car key)) val) - (insert - (format "#+%s:%s\n" - (car key) - (if (org-string-nw-p val) (format " %s" val) ""))))))))) - -(defun org-export-expand-include-keyword (&optional included dir) + ;; Then the rest of keywords, in the order specified in either + ;; `org-export-options-alist' or respective export back-ends. + (dolist (key (nreverse keywords)) + (let ((val (cond ((equal (car key) "DATE") + (or (cdr key) + (with-temp-buffer + (org-insert-time-stamp (current-time))))) + ((equal (car key) "TITLE") + (or (let ((visited-file + (buffer-file-name (buffer-base-buffer)))) + (and visited-file + (file-name-sans-extension + (file-name-nondirectory visited-file)))) + (buffer-name (buffer-base-buffer)))) + (t (cdr key))))) + (if subtreep (org-entry-put node (concat "EXPORT_" (car key)) val) + (insert + (format "#+%s:%s\n" + (car key) + (if (org-string-nw-p val) (format " %s" val) "")))))))) + +(defun org-export-expand-include-keyword (&optional included dir footnotes) "Expand every include keyword in buffer. Optional argument INCLUDED is a list of included file names along with their line restriction, when appropriate. It is used to avoid infinite recursion. Optional argument DIR is the current working directory. It is used to properly resolve relative -paths." - (let ((case-fold-search t)) +paths. Optional argument FOOTNOTES is a hash-table used for +storing and resolving footnotes. It is created automatically." + (let ((case-fold-search t) + (file-prefix (make-hash-table :test #'equal)) + (current-prefix 0) + (footnotes (or footnotes (make-hash-table :test #'equal))) + (include-re "^[ \t]*#\\+INCLUDE:")) + ;; If :minlevel is not set the text-property + ;; `:org-include-induced-level' will be used to determine the + ;; relative level when expanding INCLUDE. + ;; Only affects included Org documents. (goto-char (point-min)) - (while (re-search-forward "^[ \t]*#\\+INCLUDE:" nil t) - (let ((element (save-match-data (org-element-at-point)))) - (when (eq (org-element-type element) 'keyword) - (beginning-of-line) - ;; Extract arguments from keyword's value. - (let* ((value (org-element-property :value element)) - (ind (org-get-indentation)) - (file (and (string-match - "^\\(\".+?\"\\|\\S-+\\)\\(?:\\s-+\\|$\\)" value) - (prog1 (expand-file-name - (org-remove-double-quotes - (match-string 1 value)) - dir) - (setq value (replace-match "" nil nil value))))) - (lines - (and (string-match - ":lines +\"\\(\\(?:[0-9]+\\)?-\\(?:[0-9]+\\)?\\)\"" - value) - (prog1 (match-string 1 value) - (setq value (replace-match "" nil nil value))))) - (env (cond ((string-match "\\<example\\>" value) 'example) - ((string-match "\\<src\\(?: +\\(.*\\)\\)?" value) - (match-string 1 value)))) - ;; Minimal level of included file defaults to the child - ;; level of the current headline, if any, or one. It - ;; only applies is the file is meant to be included as - ;; an Org one. - (minlevel - (and (not env) - (if (string-match ":minlevel +\\([0-9]+\\)" value) - (prog1 (string-to-number (match-string 1 value)) - (setq value (replace-match "" nil nil value))) - (let ((cur (org-current-level))) - (if cur (1+ (org-reduced-level cur)) 1)))))) - ;; Remove keyword. - (delete-region (point) (progn (forward-line) (point))) - (cond - ((not file) nil) - ((not (file-readable-p file)) - (error "Cannot include file %s" file)) - ;; Check if files has already been parsed. Look after - ;; inclusion lines too, as different parts of the same file - ;; can be included too. - ((member (list file lines) included) - (error "Recursive file inclusion: %s" file)) - (t + (while (re-search-forward include-re nil t) + (put-text-property (line-beginning-position) (line-end-position) + :org-include-induced-level + (1+ (org-reduced-level (or (org-current-level) 0))))) + ;; Expand INCLUDE keywords. + (goto-char (point-min)) + (while (re-search-forward include-re nil t) + (unless (org-in-commented-heading-p) + (let ((element (save-match-data (org-element-at-point)))) + (when (eq (org-element-type element) 'keyword) + (beginning-of-line) + ;; Extract arguments from keyword's value. + (let* ((value (org-element-property :value element)) + (ind (org-get-indentation)) + location + (file + (and (string-match + "^\\(\".+?\"\\|\\S-+\\)\\(?:\\s-+\\|$\\)" value) + (prog1 + (save-match-data + (let ((matched (match-string 1 value))) + (when (string-match "\\(::\\(.*?\\)\\)\"?\\'" + matched) + (setq location (match-string 2 matched)) + (setq matched + (replace-match "" nil nil matched 1))) + (expand-file-name + (org-unbracket-string "\"" "\"" matched) + dir))) + (setq value (replace-match "" nil nil value))))) + (only-contents + (and (string-match ":only-contents *\\([^: \r\t\n]\\S-*\\)?" + value) + (prog1 (org-not-nil (match-string 1 value)) + (setq value (replace-match "" nil nil value))))) + (lines + (and (string-match + ":lines +\"\\(\\(?:[0-9]+\\)?-\\(?:[0-9]+\\)?\\)\"" + value) + (prog1 (match-string 1 value) + (setq value (replace-match "" nil nil value))))) + (env (cond + ((string-match "\\<example\\>" value) 'literal) + ((string-match "\\<export\\(?: +\\(.*\\)\\)?" value) + 'literal) + ((string-match "\\<src\\(?: +\\(.*\\)\\)?" value) + 'literal))) + ;; Minimal level of included file defaults to the + ;; child level of the current headline, if any, or + ;; one. It only applies is the file is meant to be + ;; included as an Org one. + (minlevel + (and (not env) + (if (string-match ":minlevel +\\([0-9]+\\)" value) + (prog1 (string-to-number (match-string 1 value)) + (setq value (replace-match "" nil nil value))) + (get-text-property (point) + :org-include-induced-level)))) + (args (and (eq env 'literal) (match-string 1 value))) + (block (and (string-match "\\<\\(\\S-+\\)\\>" value) + (match-string 1 value)))) + ;; Remove keyword. + (delete-region (point) (line-beginning-position 2)) (cond - ((eq env 'example) - (insert - (let ((ind-str (make-string ind ? )) - (contents - (org-escape-code-in-string - (org-export--prepare-file-contents file lines)))) - (format "%s#+BEGIN_EXAMPLE\n%s%s#+END_EXAMPLE\n" - ind-str contents ind-str)))) - ((stringp env) - (insert - (let ((ind-str (make-string ind ? )) - (contents - (org-escape-code-in-string - (org-export--prepare-file-contents file lines)))) - (format "%s#+BEGIN_SRC %s\n%s%s#+END_SRC\n" - ind-str env contents ind-str)))) + ((not file) nil) + ((not (file-readable-p file)) + (error "Cannot include file %s" file)) + ;; Check if files has already been parsed. Look after + ;; inclusion lines too, as different parts of the same + ;; file can be included too. + ((member (list file lines) included) + (error "Recursive file inclusion: %s" file)) (t - (insert - (with-temp-buffer - (let ((org-inhibit-startup t)) (org-mode)) - (insert - (org-export--prepare-file-contents file lines ind minlevel)) - (org-export-expand-include-keyword - (cons (list file lines) included) - (file-name-directory file)) - (buffer-string))))))))))))) - -(defun org-export--prepare-file-contents (file &optional lines ind minlevel) - "Prepare the contents of FILE for inclusion and return them as a string. + (cond + ((eq env 'literal) + (insert + (let ((ind-str (make-string ind ?\s)) + (arg-str (if (stringp args) (format " %s" args) "")) + (contents + (org-escape-code-in-string + (org-export--prepare-file-contents file lines)))) + (format "%s#+BEGIN_%s%s\n%s%s#+END_%s\n" + ind-str block arg-str contents ind-str block)))) + ((stringp block) + (insert + (let ((ind-str (make-string ind ?\s)) + (contents + (org-export--prepare-file-contents file lines))) + (format "%s#+BEGIN_%s\n%s%s#+END_%s\n" + ind-str block contents ind-str block)))) + (t + (insert + (with-temp-buffer + (let ((org-inhibit-startup t) + (lines + (if location + (org-export--inclusion-absolute-lines + file location only-contents lines) + lines))) + (org-mode) + (insert + (org-export--prepare-file-contents + file lines ind minlevel + (or + (gethash file file-prefix) + (puthash file (cl-incf current-prefix) file-prefix)) + footnotes))) + (org-export-expand-include-keyword + (cons (list file lines) included) + (file-name-directory file) + footnotes) + (buffer-string))))) + ;; Expand footnotes after all files have been + ;; included. Footnotes are stored at end of buffer. + (unless included + (org-with-wide-buffer + (goto-char (point-max)) + (maphash (lambda (k v) + (insert (format "\n[fn:%s] %s\n" k v))) + footnotes)))))))))))) + +(defun org-export--inclusion-absolute-lines (file location only-contents lines) + "Resolve absolute lines for an included file with file-link. + +FILE is string file-name of the file to include. LOCATION is a +string name within FILE to be included (located via +`org-link-search'). If ONLY-CONTENTS is non-nil only the +contents of the named element will be included, as determined +Org-Element. If LINES is non-nil only those lines are included. + +Return a string of lines to be included in the format expected by +`org-export--prepare-file-contents'." + (with-temp-buffer + (insert-file-contents file) + (unless (eq major-mode 'org-mode) + (let ((org-inhibit-startup t)) (org-mode))) + (condition-case err + ;; Enforce consistent search. + (let ((org-link-search-must-match-exact-headline nil)) + (org-link-search location)) + (error + (error "%s for %s::%s" (error-message-string err) file location))) + (let* ((element (org-element-at-point)) + (contents-begin + (and only-contents (org-element-property :contents-begin element)))) + (narrow-to-region + (or contents-begin (org-element-property :begin element)) + (org-element-property (if contents-begin :contents-end :end) element)) + (when (and only-contents + (memq (org-element-type element) '(headline inlinetask))) + ;; Skip planning line and property-drawer. + (goto-char (point-min)) + (when (looking-at-p org-planning-line-re) (forward-line)) + (when (looking-at org-property-drawer-re) (goto-char (match-end 0))) + (unless (bolp) (forward-line)) + (narrow-to-region (point) (point-max)))) + (when lines + (org-skip-whitespace) + (beginning-of-line) + (let* ((lines (split-string lines "-")) + (lbeg (string-to-number (car lines))) + (lend (string-to-number (cadr lines))) + (beg (if (zerop lbeg) (point-min) + (goto-char (point-min)) + (forward-line (1- lbeg)) + (point))) + (end (if (zerop lend) (point-max) + (goto-char beg) + (forward-line (1- lend)) + (point)))) + (narrow-to-region beg end))) + (let ((end (point-max))) + (goto-char (point-min)) + (widen) + (let ((start-line (line-number-at-pos))) + (format "%d-%d" + start-line + (save-excursion + (+ start-line + (let ((counter 0)) + (while (< (point) end) (cl-incf counter) (forward-line)) + counter)))))))) + +(defun org-export--prepare-file-contents + (file &optional lines ind minlevel id footnotes) + "Prepare contents of FILE for inclusion and return it as a string. When optional argument LINES is a string specifying a range of lines, include only those lines. @@ -3314,12 +3469,20 @@ lines, include only those lines. Optional argument IND, when non-nil, is an integer specifying the global indentation of returned contents. Since its purpose is to allow an included file to stay in the same environment it was -created \(i.e., a list item), it doesn't apply past the first +created (e.g., a list item), it doesn't apply past the first headline encountered. Optional argument MINLEVEL, when non-nil, is an integer specifying the level that any top-level headline in the included -file should have." +file should have. + +Optional argument ID is an integer that will be inserted before +each footnote definition and reference if FILE is an Org file. +This is useful to avoid conflicts when more than one Org file +with footnotes is included in a document. + +Optional argument FOOTNOTES is a hash-table to store footnotes in +the included document." (with-temp-buffer (insert-file-contents file) (when lines @@ -3348,11 +3511,11 @@ file should have." (delete-region (point) (point-max)) ;; If IND is set, preserve indentation of include keyword until ;; the first headline encountered. - (when ind + (when (and ind (> ind 0)) (unless (eq major-mode 'org-mode) (let ((org-inhibit-startup t)) (org-mode))) (goto-char (point-min)) - (let ((ind-str (make-string ind ? ))) + (let ((ind-str (make-string ind ?\s))) (while (not (or (eobp) (looking-at org-outline-regexp-bol))) ;; Do not move footnote definitions out of column 0. (unless (and (looking-at org-footnote-definition-re) @@ -3370,25 +3533,67 @@ file should have." (let ((levels (org-map-entries (lambda () (org-reduced-level (org-current-level)))))) (when levels - (let ((offset (- minlevel (apply 'min levels)))) + (let ((offset (- minlevel (apply #'min levels)))) (unless (zerop offset) (when org-odd-levels-only (setq offset (* offset 2))) ;; Only change stars, don't bother moving whole ;; sections. (org-map-entries - (lambda () (if (< offset 0) (delete-char (abs offset)) - (insert (make-string offset ?*))))))))))) + (lambda () + (if (< offset 0) (delete-char (abs offset)) + (insert (make-string offset ?*))))))))))) + ;; Append ID to all footnote references and definitions, so they + ;; become file specific and cannot collide with footnotes in other + ;; included files. Further, collect relevant footnote definitions + ;; outside of LINES, in order to reintroduce them later. + (when id + (let ((marker-min (point-min-marker)) + (marker-max (point-max-marker)) + (get-new-label + (lambda (label) + ;; Generate new label from LABEL by prefixing it with + ;; "-ID-". + (format "-%d-%s" id label))) + (set-new-label + (lambda (f old new) + ;; Replace OLD label with NEW in footnote F. + (save-excursion + (goto-char (+ (org-element-property :begin f) 4)) + (looking-at (regexp-quote old)) + (replace-match new)))) + (seen-alist)) + (goto-char (point-min)) + (while (re-search-forward org-footnote-re nil t) + (let ((footnote (save-excursion + (backward-char) + (org-element-context)))) + (when (memq (org-element-type footnote) + '(footnote-definition footnote-reference)) + (let* ((label (org-element-property :label footnote))) + ;; Update the footnote-reference at point and collect + ;; the new label, which is only used for footnotes + ;; outsides LINES. + (when label + (let ((seen (cdr (assoc label seen-alist)))) + (if seen (funcall set-new-label footnote label seen) + (let ((new (funcall get-new-label label))) + (push (cons label new) seen-alist) + (org-with-wide-buffer + (let* ((def (org-footnote-get-definition label)) + (beg (nth 1 def))) + (when (and def + (or (< beg marker-min) + (>= beg marker-max))) + ;; Store since footnote-definition is + ;; outside of LINES. + (puthash new + (org-element-normalize-string (nth 3 def)) + footnotes)))) + (funcall set-new-label footnote label new))))))))) + (set-marker marker-min nil) + (set-marker marker-max nil))) (org-element-normalize-string (buffer-string)))) -(defun org-export-execute-babel-code () - "Execute every Babel code in the visible part of current buffer." - ;; Get a pristine copy of current buffer so Babel references can be - ;; properly resolved. - (let ((reference (org-export-copy-buffer))) - (unwind-protect (let ((org-current-export-file reference)) - (org-babel-exp-process-buffer)) - (kill-buffer reference)))) - (defun org-export--copy-to-kill-ring-p () "Return a non-nil value when output should be added to the kill ring. See also `org-export-copy-to-kill-ring'." @@ -3483,17 +3688,20 @@ the communication channel used for export, as a plist." (when (symbolp backend) (setq backend (org-export-get-backend backend))) (org-export-barf-if-invalid-backend backend) (let ((type (org-element-type data))) - (if (memq type '(nil org-data)) (error "No foreign transcoder available") - (let* ((all-transcoders (org-export-get-all-transcoders backend)) - (transcoder (cdr (assq type all-transcoders)))) - (if (not (functionp transcoder)) - (error "No foreign transcoder available") - (funcall - transcoder data contents - (org-combine-plists - info (list :back-end backend - :translate-alist all-transcoders - :exported-data (make-hash-table :test 'eq :size 401))))))))) + (when (memq type '(nil org-data)) (error "No foreign transcoder available")) + (let* ((all-transcoders (org-export-get-all-transcoders backend)) + (transcoder (cdr (assq type all-transcoders)))) + (unless (functionp transcoder) (error "No foreign transcoder available")) + (let ((new-info + (org-combine-plists + info (list + :back-end backend + :translate-alist all-transcoders + :exported-data (make-hash-table :test #'eq :size 401))))) + ;; `:internal-references' are shared across back-ends. + (prog1 (funcall transcoder data contents new-info) + (plist-put info :internal-references + (plist-get new-info :internal-references))))))) ;;;; For Export Snippets @@ -3529,127 +3737,168 @@ applied." ;; `org-export-get-footnote-number' provide easier access to ;; additional information relative to a footnote reference. -(defun org-export-collect-footnote-definitions (data info) +(defun org-export-get-footnote-definition (footnote-reference info) + "Return definition of FOOTNOTE-REFERENCE as parsed data. +INFO is the plist used as a communication channel. If no such +definition can be found, raise an error." + (let ((label (org-element-property :label footnote-reference))) + (if (not label) (org-element-contents footnote-reference) + (let ((cache (or (plist-get info :footnote-definition-cache) + (let ((hash (make-hash-table :test #'equal))) + (plist-put info :footnote-definition-cache hash) + hash)))) + (or + (gethash label cache) + (puthash label + (org-element-map (plist-get info :parse-tree) + '(footnote-definition footnote-reference) + (lambda (f) + (cond + ;; Skip any footnote with a different label. + ;; Also skip any standard footnote reference + ;; with the same label since those cannot + ;; contain a definition. + ((not (equal (org-element-property :label f) label)) nil) + ((eq (org-element-property :type f) 'standard) nil) + ((org-element-contents f)) + ;; Even if the contents are empty, we can not + ;; return nil since that would eventually raise + ;; the error. Instead, return the equivalent + ;; empty string. + (t ""))) + info t) + cache) + (error "Definition not found for footnote %s" label)))))) + +(defun org-export--footnote-reference-map + (function data info &optional body-first) + "Apply FUNCTION on every footnote reference in DATA. +INFO is a plist containing export state. By default, as soon as +a new footnote reference is encountered, FUNCTION is called onto +its definition. However, if BODY-FIRST is non-nil, this step is +delayed until the end of the process." + (letrec ((definitions nil) + (seen-refs nil) + (search-ref + (lambda (data delayp) + ;; Search footnote references through DATA, filling + ;; SEEN-REFS along the way. When DELAYP is non-nil, + ;; store footnote definitions so they can be entered + ;; later. + (org-element-map data 'footnote-reference + (lambda (f) + (funcall function f) + (let ((--label (org-element-property :label f))) + (unless (and --label (member --label seen-refs)) + (when --label (push --label seen-refs)) + ;; Search for subsequent references in footnote + ;; definition so numbering follows reading + ;; logic, unless DELAYP in non-nil. + (cond + (delayp + (push (org-export-get-footnote-definition f info) + definitions)) + ;; Do not force entering inline definitions, + ;; since `org-element-map' already traverses + ;; them at the right time. + ((eq (org-element-property :type f) 'inline)) + (t (funcall search-ref + (org-export-get-footnote-definition f info) + nil)))))) + info nil + ;; Don't enter footnote definitions since it will + ;; happen when their first reference is found. + ;; Moreover, if DELAYP is non-nil, make sure we + ;; postpone entering definitions of inline references. + (if delayp '(footnote-definition footnote-reference) + 'footnote-definition))))) + (funcall search-ref data body-first) + (funcall search-ref (nreverse definitions) nil))) + +(defun org-export-collect-footnote-definitions (info &optional data body-first) "Return an alist between footnote numbers, labels and definitions. -DATA is the parse tree from which definitions are collected. -INFO is the plist used as a communication channel. - -Definitions are sorted by order of references. They either -appear as Org data or as a secondary string for inlined -footnotes. Unreferenced definitions are ignored." - (let* (num-alist - collect-fn ; for byte-compiler. - (collect-fn - (function - (lambda (data) - ;; Collect footnote number, label and definition in DATA. - (org-element-map data 'footnote-reference - (lambda (fn) - (when (org-export-footnote-first-reference-p fn info) - (let ((def (org-export-get-footnote-definition fn info))) - (push - (list (org-export-get-footnote-number fn info) - (org-element-property :label fn) - def) - num-alist) - ;; Also search in definition for nested footnotes. - (when (eq (org-element-property :type fn) 'standard) - (funcall collect-fn def))))) - ;; Don't enter footnote definitions since it will happen - ;; when their first reference is found. - info nil 'footnote-definition))))) - (funcall collect-fn (plist-get info :parse-tree)) - (reverse num-alist))) - -(defun org-export-footnote-first-reference-p (footnote-reference info) +INFO is the current export state, as a plist. + +Definitions are collected throughout the whole parse tree, or +DATA when non-nil. + +Sorting is done by order of references. As soon as a new +reference is encountered, other references are searched within +its definition. However, if BODY-FIRST is non-nil, this step is +delayed after the whole tree is checked. This alters results +when references are found in footnote definitions. + +Definitions either appear as Org data or as a secondary string +for inlined footnotes. Unreferenced definitions are ignored." + (let ((n 0) labels alist) + (org-export--footnote-reference-map + (lambda (f) + ;; Collect footnote number, label and definition. + (let ((l (org-element-property :label f))) + (unless (and l (member l labels)) + (cl-incf n) + (push (list n l (org-export-get-footnote-definition f info)) alist)) + (when l (push l labels)))) + (or data (plist-get info :parse-tree)) info body-first) + (nreverse alist))) + +(defun org-export-footnote-first-reference-p + (footnote-reference info &optional data body-first) "Non-nil when a footnote reference is the first one for its label. FOOTNOTE-REFERENCE is the footnote reference being considered. -INFO is the plist used as a communication channel." - (let ((label (org-element-property :label footnote-reference))) - ;; Anonymous footnotes are always a first reference. - (if (not label) t - ;; Otherwise, return the first footnote with the same LABEL and - ;; test if it is equal to FOOTNOTE-REFERENCE. - (let* (search-refs ; for byte-compiler. - (search-refs - (function - (lambda (data) - (org-element-map data 'footnote-reference - (lambda (fn) - (cond - ((string= (org-element-property :label fn) label) - (throw 'exit fn)) - ;; If FN isn't inlined, be sure to traverse its - ;; definition before resuming search. See - ;; comments in `org-export-get-footnote-number' - ;; for more information. - ((eq (org-element-property :type fn) 'standard) - (funcall search-refs - (org-export-get-footnote-definition fn info))))) - ;; Don't enter footnote definitions since it will - ;; happen when their first reference is found. - info 'first-match 'footnote-definition))))) - (eq (catch 'exit (funcall search-refs (plist-get info :parse-tree))) - footnote-reference))))) +INFO is a plist containing current export state. -(defun org-export-get-footnote-definition (footnote-reference info) - "Return definition of FOOTNOTE-REFERENCE as parsed data. -INFO is the plist used as a communication channel. If no such -definition can be found, return the \"DEFINITION NOT FOUND\" -string." - (let ((label (org-element-property :label footnote-reference))) - (or (org-element-property :inline-definition footnote-reference) - (cdr (assoc label (plist-get info :footnote-definition-alist))) - "DEFINITION NOT FOUND."))) +Search is done throughout the whole parse tree, or DATA when +non-nil. -(defun org-export-get-footnote-number (footnote info) +By default, as soon as a new footnote reference is encountered, +other references are searched within its definition. However, if +BODY-FIRST is non-nil, this step is delayed after the whole tree +is checked. This alters results when references are found in +footnote definitions." + (let ((label (org-element-property :label footnote-reference))) + ;; Anonymous footnotes are always a first reference. + (or (not label) + (catch 'exit + (org-export--footnote-reference-map + (lambda (f) + (let ((l (org-element-property :label f))) + (when (and l label (string= label l)) + (throw 'exit (eq footnote-reference f))))) + (or data (plist-get info :parse-tree)) info body-first))))) + +(defun org-export-get-footnote-number (footnote info &optional data body-first) "Return number associated to a footnote. FOOTNOTE is either a footnote reference or a footnote definition. -INFO is the plist used as a communication channel." - (let* ((label (org-element-property :label footnote)) - seen-refs - search-ref ; For byte-compiler. - (search-ref - (function - (lambda (data) - ;; Search footnote references through DATA, filling - ;; SEEN-REFS along the way. - (org-element-map data 'footnote-reference - (lambda (fn) - (let ((fn-lbl (org-element-property :label fn))) - (cond - ;; Anonymous footnote match: return number. - ((and (not fn-lbl) (eq fn footnote)) - (throw 'exit (1+ (length seen-refs)))) - ;; Labels match: return number. - ((and label (string= label fn-lbl)) - (throw 'exit (1+ (length seen-refs)))) - ;; Anonymous footnote: it's always a new one. - ;; Also, be sure to return nil from the `cond' so - ;; `first-match' doesn't get us out of the loop. - ((not fn-lbl) (push 'inline seen-refs) nil) - ;; Label not seen so far: add it so SEEN-REFS. - ;; - ;; Also search for subsequent references in - ;; footnote definition so numbering follows - ;; reading logic. Note that we don't have to care - ;; about inline definitions, since - ;; `org-element-map' already traverses them at the - ;; right time. - ;; - ;; Once again, return nil to stay in the loop. - ((not (member fn-lbl seen-refs)) - (push fn-lbl seen-refs) - (funcall search-ref - (org-export-get-footnote-definition fn info)) - nil)))) - ;; Don't enter footnote definitions since it will - ;; happen when their first reference is found. - info 'first-match 'footnote-definition))))) - (catch 'exit (funcall search-ref (plist-get info :parse-tree))))) +INFO is the plist containing export state. + +Number is unique throughout the whole parse tree, or DATA, when +non-nil. + +By default, as soon as a new footnote reference is encountered, +counting process moves into its definition. However, if +BODY-FIRST is non-nil, this step is delayed until the end of the +process, leading to a different order when footnotes are nested." + (let ((count 0) + (seen) + (label (org-element-property :label footnote))) + (catch 'exit + (org-export--footnote-reference-map + (lambda (f) + (let ((l (org-element-property :label f))) + (cond + ;; Anonymous footnote match: return number. + ((and (not l) (not label) (eq footnote f)) (throw 'exit (1+ count))) + ;; Labels match: return number. + ((and label l (string= label l)) (throw 'exit (1+ count))) + ;; Otherwise store label and increase counter if label + ;; wasn't encountered yet. + ((not l) (cl-incf count)) + ((not (member l seen)) (push l seen) (cl-incf count))))) + (or data (plist-get info :parse-tree)) info body-first)))) ;;;; For Headlines @@ -3657,9 +3906,11 @@ INFO is the plist used as a communication channel." ;; `org-export-get-relative-level' is a shortcut to get headline ;; level, relatively to the lower headline level in the parsed tree. ;; -;; `org-export-get-headline-number' returns the section number of a +;; `org-export-get-headline-number' returns the section number of an ;; headline, while `org-export-number-to-roman' allows it to be -;; converted to roman numbers. +;; converted to roman numbers. With an optional argument, +;; `org-export-get-headline-number' returns a number to unnumbered +;; headlines (used for internal id). ;; ;; `org-export-low-level-p', `org-export-first-sibling-p' and ;; `org-export-last-sibling-p' are three useful predicates when it @@ -3695,16 +3946,18 @@ and the last level being considered as high enough, or nil." (and (> level limit) (- level limit)))))) (defun org-export-get-headline-number (headline info) - "Return HEADLINE numbering as a list of numbers. + "Return numbered HEADLINE numbering as a list of numbers. INFO is a plist holding contextual information." - (cdr (assoc headline (plist-get info :headline-numbering)))) + (and (org-export-numbered-headline-p headline info) + (cdr (assq headline (plist-get info :headline-numbering))))) (defun org-export-numbered-headline-p (headline info) "Return a non-nil value if HEADLINE element should be numbered. INFO is a plist used as a communication channel." - (let ((sec-num (plist-get info :section-numbers)) - (level (org-export-get-relative-level headline info))) - (if (wholenump sec-num) (<= level sec-num) sec-num))) + (unless (org-not-nil (org-export-get-node-property :UNNUMBERED headline t)) + (let ((sec-num (plist-get info :section-numbers)) + (level (org-export-get-relative-level headline info))) + (if (wholenump sec-num) (<= level sec-num) sec-num)))) (defun org-export-number-to-roman (n) "Convert integer N into a roman numeral." @@ -3728,30 +3981,21 @@ INFO is a plist used as a communication channel." ELEMENT has either an `headline' or an `inlinetask' type. INFO is a plist used as a communication channel. -Select tags (see `org-export-select-tags') and exclude tags (see -`org-export-exclude-tags') are removed from the list. - When non-nil, optional argument TAGS should be a list of strings. Any tag belonging to this list will also be removed. When optional argument INHERITED is non-nil, tags can also be inherited from parent headlines and FILETAGS keywords." - (org-remove-if - (lambda (tag) (or (member tag (plist-get info :select-tags)) - (member tag (plist-get info :exclude-tags)) - (member tag tags))) + (cl-remove-if + (lambda (tag) (member tag tags)) (if (not inherited) (org-element-property :tags element) ;; Build complete list of inherited tags. (let ((current-tag-list (org-element-property :tags element))) - (mapc - (lambda (parent) - (mapc - (lambda (tag) - (when (and (memq (org-element-type parent) '(headline inlinetask)) - (not (member tag current-tag-list))) - (push tag current-tag-list))) - (org-element-property :tags parent))) - (org-export-get-genealogy element)) + (dolist (parent (org-element-lineage element)) + (dolist (tag (org-element-property :tags parent)) + (when (and (memq (org-element-type parent) '(headline inlinetask)) + (not (member tag current-tag-list))) + (push tag current-tag-list)))) ;; Add FILETAGS keywords and return results. (org-uniquify (append (plist-get info :filetags) current-tag-list)))))) @@ -3768,7 +4012,7 @@ Return value is a string or nil." (let ((headline (if (eq (org-element-type blob) 'headline) blob (org-export-get-parent-headline blob)))) (if (not inherited) (org-element-property property blob) - (let ((parent headline) value) + (let ((parent headline)) (catch 'found (while parent (when (plist-member (nth 1 parent) property) @@ -3783,19 +4027,7 @@ INFO is a plist used as a communication channel. CATEGORY is automatically inherited from a parent headline, from #+CATEGORY: keyword or created out of original file name. If all fail, the fall-back value is \"???\"." - (or (let ((headline (if (eq (org-element-type blob) 'headline) blob - (org-export-get-parent-headline blob)))) - ;; Almost like `org-export-node-property', but we cannot trust - ;; `plist-member' as every headline has a `:CATEGORY' - ;; property, would it be nil or equal to "???" (which has the - ;; same meaning). - (let ((parent headline) value) - (catch 'found - (while parent - (let ((category (org-element-property :CATEGORY parent))) - (and category (not (equal "???" category)) - (throw 'found category))) - (setq parent (org-element-property :parent parent)))))) + (or (org-export-get-node-property :CATEGORY blob t) (org-element-map (plist-get info :parse-tree) 'keyword (lambda (kwd) (when (equal (org-element-property :key kwd) "CATEGORY") @@ -3805,23 +4037,31 @@ fail, the fall-back value is \"???\"." (and file (file-name-sans-extension (file-name-nondirectory file)))) "???")) -(defun org-export-get-alt-title (headline info) +(defun org-export-get-alt-title (headline _) "Return alternative title for HEADLINE, as a secondary string. -INFO is a plist used as a communication channel. If no optional -title is defined, fall-back to the regular title." - (or (org-element-property :alt-title headline) - (org-element-property :title headline))) - -(defun org-export-first-sibling-p (headline info) - "Non-nil when HEADLINE is the first sibling in its sub-tree. -INFO is a plist used as a communication channel." - (not (eq (org-element-type (org-export-get-previous-element headline info)) - 'headline))) - -(defun org-export-last-sibling-p (headline info) - "Non-nil when HEADLINE is the last sibling in its sub-tree. -INFO is a plist used as a communication channel." - (not (org-export-get-next-element headline info))) +If no optional title is defined, fall-back to the regular title." + (let ((alt (org-element-property :ALT_TITLE headline))) + (if alt (org-element-parse-secondary-string + alt (org-element-restriction 'headline) headline) + (org-element-property :title headline)))) + +(defun org-export-first-sibling-p (blob info) + "Non-nil when BLOB is the first sibling in its parent. +BLOB is an element or an object. If BLOB is a headline, non-nil +means it is the first sibling in the sub-tree. INFO is a plist +used as a communication channel." + (memq (org-element-type (org-export-get-previous-element blob info)) + '(nil section))) + +(defun org-export-last-sibling-p (datum info) + "Non-nil when DATUM is the last sibling in its parent. +DATUM is an element or an object. INFO is a plist used as +a communication channel." + (let ((next (org-export-get-next-element datum info))) + (or (not next) + (and (eq 'headline (org-element-type datum)) + (> (org-element-property :level datum) + (org-element-property :level next)))))) ;;;; For Keywords @@ -3852,8 +4092,8 @@ meant to be translated with `org-export-data' or alike." ;;;; For Links ;; -;; `org-export-solidify-link-text' turns a string into a safer version -;; for links, replacing most non-standard characters with hyphens. +;; `org-export-custom-protocol-maybe' handles custom protocol defined +;; in `org-link-parameters'. ;; ;; `org-export-get-coderef-format' returns an appropriate format ;; string for coderefs. @@ -3863,20 +4103,45 @@ meant to be translated with `org-export-data' or alike." ;; ;; `org-export-resolve-fuzzy-link' searches destination of fuzzy links ;; (i.e. links with "fuzzy" as type) within the parsed tree, and -;; returns an appropriate unique identifier when found, or nil. +;; returns an appropriate unique identifier. ;; ;; `org-export-resolve-id-link' returns the first headline with ;; specified id or custom-id in parse tree, the path to the external -;; file with the id or nil when neither was found. +;; file with the id. ;; ;; `org-export-resolve-coderef' associates a reference to a line ;; number in the element it belongs, or returns the reference itself ;; when the element isn't numbered. +;; +;; `org-export-file-uri' expands a filename as stored in :path value +;; of a "file" link into a file URI. +;; +;; Broken links raise a `org-link-broken' error, which is caught by +;; `org-export-data' for further processing, depending on +;; `org-export-with-broken-links' value. -(defun org-export-solidify-link-text (s) - "Take link text S and make a safe target out of it." - (save-match-data - (mapconcat 'identity (org-split-string s "[^a-zA-Z0-9_.-:]+") "-"))) +(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. + +DESC is its description, as a string, or nil. BACKEND is the +back-end used for export, as a symbol. + +Return output as a string, or nil if no protocol handles LINK. + +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")) + (not backend)) + (let ((protocol (org-link-get-parameter type :export))) + (and (functionp protocol) + (funcall protocol + (org-link-unescape (org-element-property :path link)) + desc + backend)))))) (defun org-export-get-coderef-format (path desc) "Return format string for code reference link. @@ -3902,18 +4167,57 @@ the provided rules is non-nil. The default rule is This only applies to links without a description." (and (not (org-element-contents link)) - (let ((case-fold-search t) - (rules (or rules org-export-default-inline-image-rule))) - (catch 'exit - (mapc - (lambda (rule) - (and (string= (org-element-property :type link) (car rule)) - (string-match (cdr rule) - (org-element-property :path link)) - (throw 'exit t))) - rules) - ;; Return nil if no rule matched. - nil)))) + (let ((case-fold-search t)) + (cl-some (lambda (rule) + (and (string= (org-element-property :type link) (car rule)) + (string-match-p (cdr rule) + (org-element-property :path link)))) + (or rules org-export-default-inline-image-rule))))) + +(defun org-export-insert-image-links (data info &optional rules) + "Insert image links in DATA. + +Org syntax does not support nested links. Nevertheless, some +export back-ends support images as descriptions of links. Since +images are really links to image files, we need to make an +exception about links nesting. + +This function recognizes links whose contents are really images +and turn them into proper nested links. It is meant to be used +as a parse tree filter in back-ends supporting such constructs. + +DATA is a parse tree. INFO is the current state of the export +process, as a plist. + +A description is a valid images if it matches any rule in RULES, +if non-nil, or `org-export-default-inline-image-rule' otherwise. +See `org-export-inline-image-p' for more information about the +structure of RULES. + +Return modified DATA." + (let ((link-re (format "\\`\\(?:%s\\|%s\\)\\'" + org-plain-link-re + org-angle-link-re)) + (case-fold-search t)) + (org-element-map data 'link + (lambda (l) + (let ((contents (org-element-interpret-data (org-element-contents l)))) + (when (and (org-string-nw-p contents) + (string-match link-re contents)) + (let ((type (match-string 1 contents)) + (path (match-string 2 contents))) + (when (cl-some (lambda (rule) + (and (string= type (car rule)) + (string-match-p (cdr rule) path))) + (or rules org-export-default-inline-image-rule)) + ;; Replace contents with image link. + (org-element-adopt-elements + (org-element-set-contents l nil) + (with-temp-buffer + (save-excursion (insert contents)) + (org-element-link-parser)))))))) + info nil nil t)) + data) (defun org-export-resolve-coderef (ref info) "Resolve a code reference REF. @@ -3921,33 +4225,90 @@ This only applies to links without a description." INFO is a plist used as a communication channel. Return associated line number in source code, or REF itself, -depending on src-block or example element's switches." - (org-element-map (plist-get info :parse-tree) '(example-block src-block) - (lambda (el) - (with-temp-buffer - (insert (org-trim (org-element-property :value el))) - (let* ((label-fmt (regexp-quote - (or (org-element-property :label-fmt el) - org-coderef-label-format))) - (ref-re - (format "^.*?\\S-.*?\\([ \t]*\\(%s\\)\\)[ \t]*$" - (replace-regexp-in-string "%s" ref label-fmt nil t)))) - ;; Element containing REF is found. Resolve it to either - ;; a label or a line number, as needed. - (when (re-search-backward ref-re nil t) - (cond - ((org-element-property :use-labels el) ref) - ((eq (org-element-property :number-lines el) 'continued) - (+ (org-export-get-loc el info) (line-number-at-pos))) - (t (line-number-at-pos))))))) - info 'first-match)) +depending on src-block or example element's switches. Throw an +error if no block contains REF." + (or (org-element-map (plist-get info :parse-tree) '(example-block src-block) + (lambda (el) + (with-temp-buffer + (insert (org-trim (org-element-property :value el))) + (let* ((label-fmt (or (org-element-property :label-fmt el) + org-coderef-label-format)) + (ref-re (org-src-coderef-regexp label-fmt ref))) + ;; Element containing REF is found. Resolve it to + ;; either a label or a line number, as needed. + (when (re-search-backward ref-re nil t) + (if (org-element-property :use-labels el) ref + (+ (or (org-export-get-loc el info) 0) + (line-number-at-pos))))))) + info 'first-match) + (signal 'org-link-broken (list ref)))) + +(defun org-export-search-cells (datum) + "List search cells for element or object DATUM. + +A search cell follows the pattern (TYPE . SEARCH) where + + TYPE is a symbol among `headline', `custom-id', `target' and + `other'. + + SEARCH is the string a link is expected to match. More + accurately, it is + + - headline's title, as a list of strings, if TYPE is + `headline'. + + - CUSTOM_ID value, as a string, if TYPE is `custom-id'. + + - target's or radio-target's name as a list of strings if + TYPE is `target'. + + - NAME affiliated keyword is TYPE is `other'. + +A search cell is the internal representation of a fuzzy link. It +ignores white spaces and statistics cookies, if applicable." + (pcase (org-element-type datum) + (`headline + (let ((title (split-string + (replace-regexp-in-string + "\\[[0-9]*\\(?:%\\|/[0-9]*\\)\\]" "" + (org-element-property :raw-value datum))))) + (delq nil + (list + (cons 'headline title) + (cons 'other title) + (let ((custom-id (org-element-property :custom-id datum))) + (and custom-id (cons 'custom-id custom-id))))))) + (`target + (list (cons 'target (split-string (org-element-property :value datum))))) + ((and (let name (org-element-property :name datum)) + (guard name)) + (list (cons 'other (split-string name)))) + (_ nil))) + +(defun org-export-string-to-search-cell (s) + "Return search cells associated to string S. +S is either the path of a fuzzy link or a search option, i.e., it +tries to match either a headline (through custom ID or title), +a target or a named element." + (pcase (string-to-char s) + (?* (list (cons 'headline (split-string (substring s 1))))) + (?# (list (cons 'custom-id (substring s 1)))) + ((let search (split-string s)) + (list (cons 'target search) (cons 'other search))))) + +(defun org-export-match-search-cell-p (datum cells) + "Non-nil when DATUM matches search cells CELLS. +DATUM is an element or object. CELLS is a list of search cells, +as returned by `org-export-search-cells'." + (let ((targets (org-export-search-cells datum))) + (and targets (cl-some (lambda (cell) (member cell targets)) cells)))) (defun org-export-resolve-fuzzy-link (link info) "Return LINK destination. INFO is a plist holding contextual information. -Return value can be an object, an element, or nil: +Return value can be an object or an element: - If LINK path matches a target object (i.e. <<path>>) return it. @@ -3955,86 +4316,41 @@ Return value can be an object, an element, or nil: (i.e. #+NAME: path) of an element, return that element. - If LINK path exactly matches any headline name, return that - element. If more than one headline share that name, priority - will be given to the one with the closest common ancestor, if - any, or the first one in the parse tree otherwise. + element. -- Otherwise, return nil. +- Otherwise, throw an error. Assume LINK type is \"fuzzy\". White spaces are not significant." - (let* ((raw-path (org-element-property :path link)) - (match-title-p (eq (aref raw-path 0) ?*)) - ;; Split PATH at white spaces so matches are space - ;; insensitive. - (path (org-split-string - (if match-title-p (substring raw-path 1) raw-path))) - ;; Cache for destinations that are not position dependent. - (link-cache - (or (plist-get info :resolve-fuzzy-link-cache) - (plist-get (setq info (plist-put info :resolve-fuzzy-link-cache - (make-hash-table :test 'equal))) - :resolve-fuzzy-link-cache))) - (cached (gethash path link-cache 'not-found))) - (cond - ;; Destination is not position dependent: use cached value. - ((and (not match-title-p) (not (eq cached 'not-found))) cached) - ;; First try to find a matching "<<path>>" unless user specified - ;; he was looking for a headline (path starts with a "*" - ;; character). - ((and (not match-title-p) - (let ((match (org-element-map (plist-get info :parse-tree) 'target - (lambda (blob) - (and (equal (org-split-string - (org-element-property :value blob)) - path) - blob)) - info 'first-match))) - (and match (puthash path match link-cache))))) - ;; Then try to find an element with a matching "#+NAME: path" - ;; affiliated keyword. - ((and (not match-title-p) - (let ((match (org-element-map (plist-get info :parse-tree) - org-element-all-elements - (lambda (el) - (let ((name (org-element-property :name el))) - (when (and name - (equal (org-split-string name) path)) - el))) - info 'first-match))) - (and match (puthash path match link-cache))))) - ;; Last case: link either points to a headline or to nothingness. - ;; Try to find the source, with priority given to headlines with - ;; the closest common ancestor. If such candidate is found, - ;; return it, otherwise return nil. - (t - (let ((find-headline - (function - ;; Return first headline whose `:raw-value' property is - ;; NAME in parse tree DATA, or nil. Statistics cookies - ;; are ignored. - (lambda (name data) - (org-element-map data 'headline - (lambda (headline) - (when (equal (org-split-string - (replace-regexp-in-string - "\\[[0-9]+%\\]\\|\\[[0-9]+/[0-9]+\\]" "" - (org-element-property :raw-value headline))) - name) - headline)) - info 'first-match))))) - ;; Search among headlines sharing an ancestor with link, from - ;; closest to farthest. - (catch 'exit - (mapc - (lambda (parent) - (let ((foundp (funcall find-headline path parent))) - (when foundp (throw 'exit foundp)))) - (let ((parent-hl (org-export-get-parent-headline link))) - (if (not parent-hl) (list (plist-get info :parse-tree)) - (cons parent-hl (org-export-get-genealogy parent-hl))))) - ;; No destination found: return nil. - (and (not match-title-p) (puthash path nil link-cache)))))))) + (let* ((search-cells (org-export-string-to-search-cell + (org-link-unescape (org-element-property :path link)))) + (link-cache (or (plist-get info :resolve-fuzzy-link-cache) + (let ((table (make-hash-table :test #'eq))) + (plist-put info :resolve-fuzzy-link-cache table) + table))) + (cached (gethash search-cells link-cache 'not-found))) + (if (not (eq cached 'not-found)) cached + (let ((matches + (org-element-map (plist-get info :parse-tree) + (cons 'target org-element-all-elements) + (lambda (datum) + (and (org-export-match-search-cell-p datum search-cells) + datum))))) + (unless matches + (signal 'org-link-broken (list (org-element-property :path link)))) + (puthash + search-cells + ;; There can be multiple matches for un-typed searches, i.e., + ;; for searches not starting with # or *. In this case, + ;; prioritize targets and names over headline titles. + ;; Matching both a name and a target is not valid, and + ;; therefore undefined. + (or (cl-some (lambda (datum) + (and (not (eq (org-element-type datum) 'headline)) + datum)) + matches) + (car matches)) + link-cache))))) (defun org-export-resolve-id-link (link info) "Return headline referenced as LINK destination. @@ -4042,18 +4358,19 @@ significant." INFO is a plist used as a communication channel. Return value can be the headline element matched in current parse -tree, a file name or nil. Assume LINK type is either \"id\" or -\"custom-id\"." +tree or a file name. Assume LINK type is either \"id\" or +\"custom-id\". Throw an error if no match is found." (let ((id (org-element-property :path link))) ;; First check if id is within the current parse tree. (or (org-element-map (plist-get info :parse-tree) 'headline (lambda (headline) - (when (or (string= (org-element-property :ID headline) id) - (string= (org-element-property :CUSTOM_ID headline) id)) + (when (or (equal (org-element-property :ID headline) id) + (equal (org-element-property :CUSTOM_ID headline) id)) headline)) info 'first-match) ;; Otherwise, look for external files. - (cdr (assoc id (plist-get info :id-alist)))))) + (cdr (assoc id (plist-get info :id-alist))) + (signal 'org-link-broken (list id))))) (defun org-export-resolve-radio-link (link info) "Return radio-target object referenced as LINK destination. @@ -4074,12 +4391,93 @@ has type \"radio\"." radio)) info 'first-match))) +(defun org-export-file-uri (filename) + "Return file URI associated to FILENAME." + (cond ((string-prefix-p "//" filename) (concat "file:" filename)) + ((not (file-name-absolute-p filename)) filename) + ((org-file-remote-p filename) (concat "file:/" filename)) + (t + (let ((fullname (expand-file-name filename))) + (concat (if (string-prefix-p "/" fullname) "file://" "file:///") + fullname))))) ;;;; For References ;; +;; `org-export-get-reference' associate a unique reference for any +;; object or element. It uses `org-export-new-reference' and +;; `org-export-format-reference' to, respectively, generate new +;; internal references and turn them into a string suitable for +;; output. +;; ;; `org-export-get-ordinal' associates a sequence number to any object ;; or element. +(defun org-export-new-reference (references) + "Return a unique reference, among REFERENCES. +REFERENCES is an alist whose values are in-use references, as +numbers. Returns a number, which is the internal representation +of a reference. See also `org-export-format-reference'." + ;; Generate random 7 digits hexadecimal numbers. Collisions + ;; increase exponentially with the numbers of references. However, + ;; the odds for encountering at least one collision with 1000 active + ;; references in the same document are roughly 0.2%, so this + ;; shouldn't be the bottleneck. + (let ((new (random #x10000000))) + (while (rassq new references) (setq new (random #x10000000))) + new)) + +(defun org-export-format-reference (reference) + "Format REFERENCE into a string. +REFERENCE is a number representing a reference, as returned by +`org-export-new-reference', which see." + (format "org%07x" reference)) + +(defun org-export-get-reference (datum info) + "Return a unique reference for DATUM, as a string. + +DATUM is either an element or an object. INFO is the current +export state, as a plist. + +This function checks `:crossrefs' property in INFO for search +cells matching DATUM before creating a new reference. Returned +reference consists of alphanumeric characters only." + (let ((cache (plist-get info :internal-references))) + (or (car (rassq datum cache)) + (let* ((crossrefs (plist-get info :crossrefs)) + (cells (org-export-search-cells datum)) + ;; Preserve any pre-existing association between + ;; a search cell and a reference, i.e., when some + ;; previously published document referenced a location + ;; within current file (see + ;; `org-publish-resolve-external-link'). + ;; + ;; However, there is no guarantee that search cells are + ;; unique, e.g., there might be duplicate custom ID or + ;; two headings with the same title in the file. + ;; + ;; As a consequence, before re-using any reference to + ;; an element or object, we check that it doesn't refer + ;; to a previous element or object. + (new (or (cl-some + (lambda (cell) + (let ((stored (cdr (assoc cell crossrefs)))) + (when stored + (let ((old (org-export-format-reference stored))) + (and (not (assoc old cache)) stored))))) + cells) + (org-export-new-reference cache))) + (reference-string (org-export-format-reference new))) + ;; Cache contains both data already associated to + ;; a reference and in-use internal references, so as to make + ;; unique references. + (dolist (cell cells) (push (cons cell new) cache)) + ;; Retain a direct association between reference string and + ;; DATUM since (1) not every object or element can be given + ;; a search cell (2) it permits quick lookup. + (push (cons reference-string datum) cache) + (plist-put info :internal-references cache) + reference-string)))) + (defun org-export-get-ordinal (element info &optional types predicate) "Return ordinal number of an element or object. @@ -4107,14 +4505,10 @@ objects of the same type." ;; table, item, or headline containing the object. (when (eq (org-element-type element) 'target) (setq element - (loop for parent in (org-export-get-genealogy element) - when - (memq - (org-element-type parent) - '(footnote-definition footnote-reference headline item - table)) - return parent))) - (case (org-element-type element) + (org-element-lineage + element + '(footnote-definition footnote-reference headline item table)))) + (cl-case (org-element-type element) ;; Special case 1: A headline returns its number as a list. (headline (org-export-get-headline-number element info)) ;; Special case 2: An item returns its number as a list. @@ -4134,8 +4528,8 @@ objects of the same type." (lambda (el) (cond ((eq element el) (1+ counter)) - ((not predicate) (incf counter) nil) - ((funcall predicate el info) (incf counter) nil))) + ((not predicate) (cl-incf counter) nil) + ((funcall predicate el info) (cl-incf counter) nil))) info 'first-match))))) @@ -4162,32 +4556,34 @@ objects of the same type." ;; code in a format suitable for plain text or verbatim output. (defun org-export-get-loc (element info) - "Return accumulated lines of code up to ELEMENT. - -INFO is the plist used as a communication channel. - -ELEMENT is excluded from count." - (let ((loc 0)) - (org-element-map (plist-get info :parse-tree) - `(src-block example-block ,(org-element-type element)) - (lambda (el) - (cond - ;; ELEMENT is reached: Quit the loop. - ((eq el element)) - ;; Only count lines from src-block and example-block elements - ;; with a "+n" or "-n" switch. A "-n" switch resets counter. - ((not (memq (org-element-type el) '(src-block example-block))) nil) - ((let ((linums (org-element-property :number-lines el))) - (when linums - ;; Accumulate locs or reset them. - (let ((lines (org-count-lines - (org-trim (org-element-property :value el))))) - (setq loc (if (eq linums 'new) lines (+ loc lines)))))) - ;; Return nil to stay in the loop. - nil))) - info 'first-match) - ;; Return value. - loc)) + "Return count of lines of code before ELEMENT. + +ELEMENT is an example-block or src-block element. INFO is the +plist used as a communication channel. + +Count includes every line of code in example-block or src-block +with a \"+n\" or \"-n\" switch before block. Return nil if +ELEMENT doesn't allow line numbering." + (pcase (org-element-property :number-lines element) + (`(new . ,n) n) + (`(continued . ,n) + (let ((loc 0)) + (org-element-map (plist-get info :parse-tree) '(src-block example-block) + (lambda (el) + ;; ELEMENT is reached: Quit loop and return locs. + (if (eq el element) (+ loc n) + ;; Only count lines from src-block and example-block + ;; elements with a "+n" or "-n" switch. + (let ((linum (org-element-property :number-lines el))) + (when linum + (let ((lines (org-count-lines + (org-element-property :value el)))) + ;; Accumulate locs or reset them. + (pcase linum + (`(new . ,n) (setq loc (+ n lines))) + (`(continued . ,n) (cl-incf loc (+ n lines))))))) + nil)) ;Return nil to stay in the loop. + info 'first-match))))) (defun org-export-unravel-code (element) "Clean source code and extract references out of it. @@ -4195,38 +4591,33 @@ ELEMENT is excluded from count." ELEMENT has either a `src-block' an `example-block' type. Return a cons cell whose CAR is the source code, cleaned from any -reference and protective comma and CDR is an alist between -relative line number (integer) and name of code reference on that -line (string)." +reference, protective commas and spurious indentation, and CDR is +an alist between relative line number (integer) and name of code +reference on that line (string)." (let* ((line 0) refs - ;; Get code and clean it. Remove blank lines at its - ;; beginning and end. + (value (org-element-property :value element)) + ;; Remove global indentation from code, if necessary. Also + ;; remove final newline character, since it doesn't belongs + ;; to the code proper. (code (replace-regexp-in-string - "\\`\\([ \t]*\n\\)+" "" - (replace-regexp-in-string - "\\([ \t]*\n\\)*[ \t]*\\'" "\n" - (org-element-property :value element)))) - ;; Get format used for references. - (label-fmt (regexp-quote - (or (org-element-property :label-fmt element) - org-coderef-label-format))) + "\n\\'" "" + (if (or org-src-preserve-indentation + (org-element-property :preserve-indent element)) + value + (org-remove-indentation value)))) ;; Build a regexp matching a loc with a reference. - (with-ref-re - (format "^.*?\\S-.*?\\([ \t]*\\(%s\\)[ \t]*\\)$" - (replace-regexp-in-string - "%s" "\\([-a-zA-Z0-9_ ]+\\)" label-fmt nil t)))) + (ref-re (org-src-coderef-regexp (org-src-coderef-format element)))) ;; Return value. (cons ;; Code with references removed. - (org-element-normalize-string - (mapconcat - (lambda (loc) - (incf line) - (if (not (string-match with-ref-re loc)) loc - ;; Ref line: remove ref, and signal its position in REFS. - (push (cons line (match-string 3 loc)) refs) - (replace-match "" nil nil loc 1))) - (org-split-string code "\n") "\n")) + (mapconcat + (lambda (loc) + (cl-incf line) + (if (not (string-match ref-re loc)) loc + ;; Ref line: remove ref, and add its position in REFS. + (push (cons line (match-string 3 loc)) refs) + (replace-match "" nil nil loc 1))) + (split-string code "\n") "\n") ;; Reference alist. refs))) @@ -4249,15 +4640,16 @@ number (i.e. ignoring NUM-LINES) and the name of the code reference on it. If it is nil, FUN's third argument will always be nil. It can be obtained through the use of `org-export-unravel-code' function." - (let ((--locs (org-split-string code "\n")) + (let ((--locs (split-string code "\n")) (--line 0)) - (org-element-normalize-string + (concat (mapconcat (lambda (--loc) - (incf --line) + (cl-incf --line) (let ((--ref (cdr (assq --line ref-alist)))) (funcall fun --loc (and num-lines (+ num-lines --line)) --ref))) - --locs "\n")))) + --locs "\n") + "\n"))) (defun org-export-format-code-default (element info) "Return source code from ELEMENT, formatted in a standard way. @@ -4274,14 +4666,12 @@ code." ;; Extract code and references. (let* ((code-info (org-export-unravel-code element)) (code (car code-info)) - (code-lines (org-split-string code "\n"))) + (code-lines (split-string code "\n"))) (if (null code-lines) "" (let* ((refs (and (org-element-property :retain-labels element) (cdr code-info))) ;; Handle line numbering. - (num-start (case (org-element-property :number-lines element) - (continued (org-export-get-loc element info)) - (new 0))) + (num-start (org-export-get-loc element info)) (num-fmt (and num-start (format "%%%ds " @@ -4301,9 +4691,9 @@ code." number-str loc (and ref - (concat (make-string - (- (+ 6 max-width) - (+ (length loc) (length number-str))) ? ) + (concat (make-string (- (+ 6 max-width) + (+ (length loc) (length number-str))) + ?\s) (format "(%s)" ref)))))) num-start refs))))) @@ -4331,30 +4721,30 @@ code." ;; `org-export-table-cell-ends-colgroup-p', ;; `org-export-table-row-starts-rowgroup-p', ;; `org-export-table-row-ends-rowgroup-p', -;; `org-export-table-row-starts-header-p' and -;; `org-export-table-row-ends-header-p' indicate position of current -;; row or cell within the table. +;; `org-export-table-row-starts-header-p', +;; `org-export-table-row-ends-header-p' and +;; `org-export-table-row-in-header-p' indicate position of current row +;; or cell within the table. (defun org-export-table-has-special-column-p (table) "Non-nil when TABLE has a special column. All special columns will be ignored during export." ;; The table has a special column when every first cell of every row ;; has an empty value or contains a symbol among "/", "#", "!", "$", - ;; "*" "_" and "^". Though, do not consider a first row containing - ;; only empty cells as special. - (let ((special-column-p 'empty)) + ;; "*" "_" and "^". Though, do not consider a first column + ;; containing only empty cells as special. + (let ((special-column? 'empty)) (catch 'exit - (mapc - (lambda (row) - (when (eq (org-element-property :type row) 'standard) - (let ((value (org-element-contents - (car (org-element-contents row))))) - (cond ((member value '(("/") ("#") ("!") ("$") ("*") ("_") ("^"))) - (setq special-column-p 'special)) - ((not value)) - (t (throw 'exit nil)))))) - (org-element-contents table)) - (eq special-column-p 'special)))) + (dolist (row (org-element-contents table)) + (when (eq (org-element-property :type row) 'standard) + (let ((value (org-element-contents + (car (org-element-contents row))))) + (cond ((member value + '(("/") ("#") ("!") ("$") ("*") ("_") ("^"))) + (setq special-column? 'special)) + ((null value)) + (t (throw 'exit nil)))))) + (eq special-column? 'special)))) (defun org-export-table-has-header-p (table info) "Non-nil when TABLE has a header. @@ -4362,32 +4752,31 @@ All special columns will be ignored during export." INFO is a plist used as a communication channel. A table has a header when it contains at least two row groups." - (let ((cache (or (plist-get info :table-header-cache) - (plist-get (setq info - (plist-put info :table-header-cache - (make-hash-table :test 'eq))) - :table-header-cache)))) - (or (gethash table cache) - (let ((rowgroup 1) row-flag) - (puthash - table - (org-element-map table 'table-row - (lambda (row) - (cond - ((> rowgroup 1) t) - ((and row-flag (eq (org-element-property :type row) 'rule)) - (incf rowgroup) (setq row-flag nil)) - ((and (not row-flag) (eq (org-element-property :type row) - 'standard)) - (setq row-flag t) nil))) - info 'first-match) - cache))))) - -(defun org-export-table-row-is-special-p (table-row info) + (let* ((cache (or (plist-get info :table-header-cache) + (let ((table (make-hash-table :test #'eq))) + (plist-put info :table-header-cache table) + table))) + (cached (gethash table cache 'no-cache))) + (if (not (eq cached 'no-cache)) cached + (let ((rowgroup 1) row-flag) + (puthash table + (org-element-map table 'table-row + (lambda (row) + (cond + ((> rowgroup 1) t) + ((and row-flag + (eq (org-element-property :type row) 'rule)) + (cl-incf rowgroup) + (setq row-flag nil)) + ((and (not row-flag) + (eq (org-element-property :type row) 'standard)) + (setq row-flag t) + nil))) + info 'first-match) + cache))))) + +(defun org-export-table-row-is-special-p (table-row _) "Non-nil if TABLE-ROW is considered special. - -INFO is a plist used as the communication channel. - All special rows will be ignored during export." (when (eq (org-element-property :type table-row) 'standard) (let ((first-cell (org-element-contents @@ -4404,19 +4793,17 @@ All special rows will be ignored during export." ;; ... it contains only alignment cookies and empty cells. (let ((special-row-p 'empty)) (catch 'exit - (mapc - (lambda (cell) - (let ((value (org-element-contents cell))) - ;; Since VALUE is a secondary string, the following - ;; checks avoid expanding it with `org-export-data'. - (cond ((not value)) - ((and (not (cdr value)) - (stringp (car value)) - (string-match "\\`<[lrc]?\\([0-9]+\\)?>\\'" - (car value))) - (setq special-row-p 'cookie)) - (t (throw 'exit nil))))) - (org-element-contents table-row)) + (dolist (cell (org-element-contents table-row)) + (let ((value (org-element-contents cell))) + ;; Since VALUE is a secondary string, the following + ;; checks avoid expanding it with `org-export-data'. + (cond ((not value)) + ((and (not (cdr value)) + (stringp (car value)) + (string-match "\\`<[lrc]?\\([0-9]+\\)?>\\'" + (car value))) + (setq special-row-p 'cookie)) + (t (throw 'exit nil))))) (eq special-row-p 'cookie))))))) (defun org-export-table-row-group (table-row info) @@ -4427,21 +4814,24 @@ INFO is a plist used as the communication channel. Return value is the group number, as an integer, or nil for special rows and rows separators. First group is also table's header." - (let ((cache (or (plist-get info :table-row-group-cache) - (plist-get (setq info - (plist-put info :table-row-group-cache - (make-hash-table :test 'eq))) - :table-row-group-cache)))) - (cond ((gethash table-row cache)) - ((eq (org-element-property :type table-row) 'rule) nil) - (t (let ((group 0) row-flag) - (org-element-map (org-export-get-parent table-row) 'table-row - (lambda (row) - (if (eq (org-element-property :type row) 'rule) - (setq row-flag nil) - (unless row-flag (incf group) (setq row-flag t))) - (when (eq table-row row) (puthash table-row group cache))) - info 'first-match)))))) + (when (eq (org-element-property :type table-row) 'standard) + (let* ((cache (or (plist-get info :table-row-group-cache) + (let ((table (make-hash-table :test #'eq))) + (plist-put info :table-row-group-cache table) + table))) + (cached (gethash table-row cache 'no-cache))) + (if (not (eq cached 'no-cache)) cached + ;; First time a row is queried, populate cache with all the + ;; rows from the table. + (let ((group 0) row-flag) + (org-element-map (org-export-get-parent table-row) 'table-row + (lambda (row) + (if (eq (org-element-property :type row) 'rule) + (setq row-flag nil) + (unless row-flag (cl-incf group) (setq row-flag t)) + (puthash row group cache))) + info)) + (gethash table-row cache))))) (defun org-export-table-cell-width (table-cell info) "Return TABLE-CELL contents width. @@ -4456,10 +4846,9 @@ same column as TABLE-CELL, or nil." (columns (length cells)) (column (- columns (length (memq table-cell cells)))) (cache (or (plist-get info :table-cell-width-cache) - (plist-get (setq info - (plist-put info :table-cell-width-cache - (make-hash-table :test 'eq))) - :table-cell-width-cache))) + (let ((table (make-hash-table :test #'eq))) + (plist-put info :table-cell-width-cache table) + table))) (width-vector (or (gethash table cache) (puthash table (make-vector columns 'empty) cache))) (value (aref width-vector column))) @@ -4500,10 +4889,9 @@ Possible values are `left', `right' and `center'." (columns (length cells)) (column (- columns (length (memq table-cell cells)))) (cache (or (plist-get info :table-cell-alignment-cache) - (plist-get (setq info - (plist-put info :table-cell-alignment-cache - (make-hash-table :test 'eq))) - :table-cell-alignment-cache))) + (let ((table (make-hash-table :test #'eq))) + (plist-put info :table-cell-alignment-cache table) + table))) (align-vector (or (gethash table cache) (puthash table (make-vector columns nil) cache)))) (or (aref align-vector column) @@ -4539,14 +4927,14 @@ Possible values are `left', `right' and `center'." (org-element-contents (elt (org-element-contents row) column)) info))) - (incf total-cells) + (cl-incf total-cells) ;; Treat an empty cell as a number if it follows ;; a number. (if (not (or (string-match org-table-number-regexp value) (and (string= value "") previous-cell-number-p))) (setq previous-cell-number-p nil) (setq previous-cell-number-p t) - (incf number-cells)))))) + (cl-incf number-cells)))))) ;; Return value. Alignment specified by cookies has ;; precedence over alignment deduced from cell's contents. (aset align-vector @@ -4579,14 +4967,13 @@ Returned borders ignore special rows." ;; another regular row has to be found above that rule. (let (rule-flag) (catch 'exit - (mapc (lambda (row) - (cond ((eq (org-element-property :type row) 'rule) - (setq rule-flag t)) - ((not (org-export-table-row-is-special-p row info)) - (if rule-flag (throw 'exit (push 'above borders)) - (throw 'exit nil))))) - ;; Look at every row before the current one. - (cdr (memq row (reverse (org-element-contents table))))) + ;; Look at every row before the current one. + (dolist (row (cdr (memq row (reverse (org-element-contents table))))) + (cond ((eq (org-element-property :type row) 'rule) + (setq rule-flag t)) + ((not (org-export-table-row-is-special-p row info)) + (if rule-flag (throw 'exit (push 'above borders)) + (throw 'exit nil))))) ;; No rule above, or rule found starts the table (ignoring any ;; special row): TABLE-CELL is at the top of the table. (when rule-flag (push 'above borders)) @@ -4595,14 +4982,13 @@ Returned borders ignore special rows." ;; non-regular row below is a rule. (let (rule-flag) (catch 'exit - (mapc (lambda (row) - (cond ((eq (org-element-property :type row) 'rule) - (setq rule-flag t)) - ((not (org-export-table-row-is-special-p row info)) - (if rule-flag (throw 'exit (push 'below borders)) - (throw 'exit nil))))) - ;; Look at every row after the current one. - (cdr (memq row (org-element-contents table)))) + ;; Look at every row after the current one. + (dolist (row (cdr (memq row (org-element-contents table)))) + (cond ((eq (org-element-property :type row) 'rule) + (setq rule-flag t)) + ((not (org-export-table-row-is-special-p row info)) + (if rule-flag (throw 'exit (push 'below borders)) + (throw 'exit nil))))) ;; No rule below, or rule found ends the table (modulo some ;; special row): TABLE-CELL is at the bottom of the table. (when rule-flag (push 'below borders)) @@ -4614,42 +5000,40 @@ Returned borders ignore special rows." (catch 'exit (let ((column (let ((cells (org-element-contents row))) (- (length cells) (length (memq table-cell cells)))))) - (mapc - (lambda (row) - (unless (eq (org-element-property :type row) 'rule) - (when (equal (org-element-contents - (car (org-element-contents row))) - '("/")) - (let ((column-groups - (mapcar - (lambda (cell) - (let ((value (org-element-contents cell))) - (when (member value '(("<") ("<>") (">") nil)) - (car value)))) - (org-element-contents row)))) - ;; There's a left border when previous cell, if - ;; any, ends a group, or current one starts one. - (when (or (and (not (zerop column)) - (member (elt column-groups (1- column)) - '(">" "<>"))) - (member (elt column-groups column) '("<" "<>"))) - (push 'left borders)) - ;; There's a right border when next cell, if any, - ;; starts a group, or current one ends one. - (when (or (and (/= (1+ column) (length column-groups)) - (member (elt column-groups (1+ column)) - '("<" "<>"))) - (member (elt column-groups column) '(">" "<>"))) - (push 'right borders)) - (throw 'exit nil))))) - ;; Table rows are read in reverse order so last column groups - ;; row has precedence over any previous one. - (reverse (org-element-contents table))))) + ;; Table rows are read in reverse order so last column groups + ;; row has precedence over any previous one. + (dolist (row (reverse (org-element-contents table))) + (unless (eq (org-element-property :type row) 'rule) + (when (equal (org-element-contents + (car (org-element-contents row))) + '("/")) + (let ((column-groups + (mapcar + (lambda (cell) + (let ((value (org-element-contents cell))) + (when (member value '(("<") ("<>") (">") nil)) + (car value)))) + (org-element-contents row)))) + ;; There's a left border when previous cell, if + ;; any, ends a group, or current one starts one. + (when (or (and (not (zerop column)) + (member (elt column-groups (1- column)) + '(">" "<>"))) + (member (elt column-groups column) '("<" "<>"))) + (push 'left borders)) + ;; There's a right border when next cell, if any, + ;; starts a group, or current one ends one. + (when (or (and (/= (1+ column) (length column-groups)) + (member (elt column-groups (1+ column)) + '("<" "<>"))) + (member (elt column-groups column) '(">" "<>"))) + (push 'right borders)) + (throw 'exit nil))))))) ;; Return value. borders)) (defun org-export-table-cell-starts-colgroup-p (table-cell info) - "Non-nil when TABLE-CELL is at the beginning of a row group. + "Non-nil when TABLE-CELL is at the beginning of a column group. INFO is a plist used as a communication channel." ;; A cell starts a column group either when it is at the beginning ;; of a row (or after the special column, if any) or when it has @@ -4660,7 +5044,7 @@ INFO is a plist used as a communication channel." (memq 'left (org-export-table-cell-borders table-cell info)))) (defun org-export-table-cell-ends-colgroup-p (table-cell info) - "Non-nil when TABLE-CELL is at the end of a row group. + "Non-nil when TABLE-CELL is at the end of a column group. INFO is a plist used as a communication channel." ;; A cell ends a column group either when it is at the end of a row ;; or when it has a right border. @@ -4670,7 +5054,7 @@ INFO is a plist used as a communication channel." (memq 'right (org-export-table-cell-borders table-cell info)))) (defun org-export-table-row-starts-rowgroup-p (table-row info) - "Non-nil when TABLE-ROW is at the beginning of a column group. + "Non-nil when TABLE-ROW is at the beginning of a row group. INFO is a plist used as a communication channel." (unless (or (eq (org-element-property :type table-row) 'rule) (org-export-table-row-is-special-p table-row info)) @@ -4679,7 +5063,7 @@ INFO is a plist used as a communication channel." (or (memq 'top borders) (memq 'above borders))))) (defun org-export-table-row-ends-rowgroup-p (table-row info) - "Non-nil when TABLE-ROW is at the end of a column group. + "Non-nil when TABLE-ROW is at the end of a row group. INFO is a plist used as a communication channel." (unless (or (eq (org-element-property :type table-row) 'rule) (org-export-table-row-is-special-p table-row info)) @@ -4687,36 +5071,47 @@ INFO is a plist used as a communication channel." (car (org-element-contents table-row)) info))) (or (memq 'bottom borders) (memq 'below borders))))) +(defun org-export-table-row-in-header-p (table-row info) + "Non-nil when TABLE-ROW is located within table's header. +INFO is a plist used as a communication channel. Always return +nil for special rows and rows separators." + (and (org-export-table-has-header-p + (org-export-get-parent-table table-row) info) + (eql (org-export-table-row-group table-row info) 1))) + (defun org-export-table-row-starts-header-p (table-row info) "Non-nil when TABLE-ROW is the first table header's row. INFO is a plist used as a communication channel." - (and (org-export-table-has-header-p - (org-export-get-parent-table table-row) info) - (org-export-table-row-starts-rowgroup-p table-row info) - (= (org-export-table-row-group table-row info) 1))) + (and (org-export-table-row-in-header-p table-row info) + (org-export-table-row-starts-rowgroup-p table-row info))) (defun org-export-table-row-ends-header-p (table-row info) "Non-nil when TABLE-ROW is the last table header's row. INFO is a plist used as a communication channel." - (and (org-export-table-has-header-p - (org-export-get-parent-table table-row) info) - (org-export-table-row-ends-rowgroup-p table-row info) - (= (org-export-table-row-group table-row info) 1))) + (and (org-export-table-row-in-header-p table-row info) + (org-export-table-row-ends-rowgroup-p table-row info))) (defun org-export-table-row-number (table-row info) "Return TABLE-ROW number. INFO is a plist used as a communication channel. Return value is -zero-based and ignores separators. The function returns nil for -special columns and separators." - (when (and (eq (org-element-property :type table-row) 'standard) - (not (org-export-table-row-is-special-p table-row info))) - (let ((number 0)) - (org-element-map (org-export-get-parent-table table-row) 'table-row - (lambda (row) - (cond ((eq row table-row) number) - ((eq (org-element-property :type row) 'standard) - (incf number) nil))) - info 'first-match)))) +zero-indexed and ignores separators. The function returns nil +for special rows and separators." + (when (eq (org-element-property :type table-row) 'standard) + (let* ((cache (or (plist-get info :table-row-number-cache) + (let ((table (make-hash-table :test #'eq))) + (plist-put info :table-row-number-cache table) + table))) + (cached (gethash table-row cache 'no-cache))) + (if (not (eq cached 'no-cache)) cached + ;; First time a row is queried, populate cache with all the + ;; rows from the table. + (let ((number -1)) + (org-element-map (org-export-get-parent-table table-row) 'table-row + (lambda (row) + (when (eq (org-element-property :type row) 'standard) + (puthash row (cl-incf number) cache))) + info)) + (gethash table-row cache))))) (defun org-export-table-dimensions (table info) "Return TABLE dimensions. @@ -4731,10 +5126,10 @@ rows (resp. columns)." (org-element-map table 'table-row (lambda (row) (when (eq (org-element-property :type row) 'standard) - (incf rows) + (cl-incf rows) (unless first-row (setq first-row row)))) info) ;; Set number of columns. - (org-element-map first-row 'table-cell (lambda (cell) (incf columns)) info) + (org-element-map first-row 'table-cell (lambda (_) (cl-incf columns)) info) ;; Return value. (cons rows columns))) @@ -4754,7 +5149,7 @@ function returns nil for other cells." (let ((col-count 0)) (org-element-map table-row 'table-cell (lambda (cell) - (if (eq cell table-cell) col-count (incf col-count) nil)) + (if (eq cell table-cell) col-count (cl-incf col-count) nil)) info 'first-match)))))) (defun org-export-get-table-cell-at (address table info) @@ -4774,16 +5169,16 @@ return nil." (lambda (row) (cond ((eq (org-element-property :type row) 'rule) nil) ((= row-count row-pos) row) - (t (incf row-count) nil))) + (t (cl-incf row-count) nil))) info 'first-match)) 'table-cell (lambda (cell) (if (= column-count column-pos) cell - (incf column-count) nil)) + (cl-incf column-count) nil)) info 'first-match))) -;;;; For Tables Of Contents +;;;; For Tables of Contents ;; ;; `org-export-collect-headlines' builds a list of all exportable ;; headline elements, maybe limited to a certain depth. One can then @@ -4793,8 +5188,11 @@ return nil." ;; Once the generic function `org-export-collect-elements' is defined, ;; `org-export-collect-tables', `org-export-collect-figures' and ;; `org-export-collect-listings' can be derived from it. +;; +;; `org-export-toc-entry-backend' builds a special anonymous back-end +;; useful to export table of contents' entries. -(defun org-export-collect-headlines (info &optional n) +(defun org-export-collect-headlines (info &optional n scope) "Collect headlines in order to build a table of contents. INFO is a plist used as a communication channel. @@ -4804,15 +5202,28 @@ the table of contents. Otherwise, it is set to the value of the last headline level. See `org-export-headline-levels' for more information. +Optional argument SCOPE, when non-nil, is an element. If it is +a headline, only children of SCOPE are collected. Otherwise, +collect children of the headline containing provided element. If +there is no such headline, collect all headlines. In any case, +argument N becomes relative to the level of that headline. + Return a list of all exportable headlines as parsed elements. -Footnote sections, if any, will be ignored." - (let ((limit (plist-get info :headline-levels))) - (setq n (if (wholenump n) (min n limit) limit)) - (org-element-map (plist-get info :parse-tree) 'headline - #'(lambda (headline) - (unless (org-element-property :footnote-section-p headline) - (let ((level (org-export-get-relative-level headline info))) - (and (<= level n) headline)))) +Footnote sections are ignored." + (let* ((scope (cond ((not scope) (plist-get info :parse-tree)) + ((eq (org-element-type scope) 'headline) scope) + ((org-export-get-parent-headline scope)) + (t (plist-get info :parse-tree)))) + (limit (plist-get info :headline-levels)) + (n (if (not (wholenump n)) limit + (min (if (eq (org-element-type scope) 'org-data) n + (+ (org-export-get-relative-level scope info) n)) + limit)))) + (org-element-map (org-element-contents scope) 'headline + (lambda (headline) + (unless (org-element-property :footnote-section-p headline) + (let ((level (org-export-get-relative-level headline info))) + (and (<= level n) headline)))) info))) (defun org-export-collect-elements (type info &optional predicate) @@ -4865,6 +5276,32 @@ INFO is a plist used as a communication channel. Return a list of src-block elements with a caption." (org-export-collect-elements 'src-block info)) +(defun org-export-toc-entry-backend (parent &rest transcoders) + "Return an export back-end appropriate for table of contents entries. + +PARENT is an export back-end the returned back-end should inherit +from. + +By default, the back-end removes footnote references and targets. +It also changes links and radio targets into regular text. +TRANSCODERS optional argument, when non-nil, specifies additional +transcoders. A transcoder follows the pattern (TYPE . FUNCTION) +where type is an element or object type and FUNCTION the function +transcoding it." + (declare (indent 1)) + (org-export-create-backend + :parent parent + :transcoders + (append transcoders + `((footnote-reference . ,#'ignore) + (link . ,(lambda (l c i) + (or c + (org-export-data + (org-element-property :raw-link l) + i)))) + (radio-target . ,(lambda (_r c _) c)) + (target . ,#'ignore))))) + ;;;; Smart Quotes ;; @@ -4874,131 +5311,238 @@ Return a list of src-block elements with a caption." ;; ;; Dictionary for smart quotes is stored in ;; `org-export-smart-quotes-alist'. -;; -;; Internally, regexps matching potential smart quotes (checks at -;; string boundaries are also necessary) are defined in -;; `org-export-smart-quotes-regexps'. (defconst org-export-smart-quotes-alist - '(("da" + '(("ar" + (primary-opening + :utf-8 "«" :html "«" :latex "\\guillemotleft{}" + :texinfo "@guillemetleft{}") + (primary-closing + :utf-8 "»" :html "»" :latex "\\guillemotright{}" + :texinfo "@guillemetright{}") + (secondary-opening :utf-8 "‹" :html "‹" :latex "\\guilsinglleft{}" + :texinfo "@guilsinglleft{}") + (secondary-closing :utf-8 "›" :html "›" :latex "\\guilsinglright{}" + :texinfo "@guilsinglright{}") + (apostrophe :utf-8 "’" :html "’")) + ("da" ;; one may use: »...«, "...", ›...‹, or '...'. ;; http://sproget.dk/raad-og-regler/retskrivningsregler/retskrivningsregler/a7-40-60/a7-58-anforselstegn/ ;; LaTeX quotes require Babel! - (opening-double-quote :utf-8 "»" :html "»" :latex ">>" - :texinfo "@guillemetright{}") - (closing-double-quote :utf-8 "«" :html "«" :latex "<<" - :texinfo "@guillemetleft{}") - (opening-single-quote :utf-8 "›" :html "›" :latex "\\frq{}" - :texinfo "@guilsinglright{}") - (closing-single-quote :utf-8 "‹" :html "‹" :latex "\\flq{}" - :texinfo "@guilsingleft{}") + (primary-opening + :utf-8 "»" :html "»" :latex ">>" :texinfo "@guillemetright{}") + (primary-closing + :utf-8 "«" :html "«" :latex "<<" :texinfo "@guillemetleft{}") + (secondary-opening + :utf-8 "›" :html "›" :latex "\\frq{}" :texinfo "@guilsinglright{}") + (secondary-closing + :utf-8 "‹" :html "‹" :latex "\\flq{}" :texinfo "@guilsingleft{}") (apostrophe :utf-8 "’" :html "’")) ("de" - (opening-double-quote :utf-8 "„" :html "„" :latex "\"`" - :texinfo "@quotedblbase{}") - (closing-double-quote :utf-8 "“" :html "“" :latex "\"'" - :texinfo "@quotedblleft{}") - (opening-single-quote :utf-8 "‚" :html "‚" :latex "\\glq{}" - :texinfo "@quotesinglbase{}") - (closing-single-quote :utf-8 "‘" :html "‘" :latex "\\grq{}" - :texinfo "@quoteleft{}") + (primary-opening + :utf-8 "„" :html "„" :latex "\"`" :texinfo "@quotedblbase{}") + (primary-closing + :utf-8 "“" :html "“" :latex "\"'" :texinfo "@quotedblleft{}") + (secondary-opening + :utf-8 "‚" :html "‚" :latex "\\glq{}" :texinfo "@quotesinglbase{}") + (secondary-closing + :utf-8 "‘" :html "‘" :latex "\\grq{}" :texinfo "@quoteleft{}") (apostrophe :utf-8 "’" :html "’")) ("en" - (opening-double-quote :utf-8 "“" :html "“" :latex "``" :texinfo "``") - (closing-double-quote :utf-8 "”" :html "”" :latex "''" :texinfo "''") - (opening-single-quote :utf-8 "‘" :html "‘" :latex "`" :texinfo "`") - (closing-single-quote :utf-8 "’" :html "’" :latex "'" :texinfo "'") + (primary-opening :utf-8 "“" :html "“" :latex "``" :texinfo "``") + (primary-closing :utf-8 "”" :html "”" :latex "''" :texinfo "''") + (secondary-opening :utf-8 "‘" :html "‘" :latex "`" :texinfo "`") + (secondary-closing :utf-8 "’" :html "’" :latex "'" :texinfo "'") (apostrophe :utf-8 "’" :html "’")) ("es" - (opening-double-quote :utf-8 "«" :html "«" :latex "\\guillemotleft{}" - :texinfo "@guillemetleft{}") - (closing-double-quote :utf-8 "»" :html "»" :latex "\\guillemotright{}" - :texinfo "@guillemetright{}") - (opening-single-quote :utf-8 "“" :html "“" :latex "``" :texinfo "``") - (closing-single-quote :utf-8 "”" :html "”" :latex "''" :texinfo "''") + (primary-opening + :utf-8 "«" :html "«" :latex "\\guillemotleft{}" + :texinfo "@guillemetleft{}") + (primary-closing + :utf-8 "»" :html "»" :latex "\\guillemotright{}" + :texinfo "@guillemetright{}") + (secondary-opening :utf-8 "“" :html "“" :latex "``" :texinfo "``") + (secondary-closing :utf-8 "”" :html "”" :latex "''" :texinfo "''") (apostrophe :utf-8 "’" :html "’")) ("fr" - (opening-double-quote :utf-8 "« " :html "« " :latex "\\og " - :texinfo "@guillemetleft{}@tie{}") - (closing-double-quote :utf-8 " »" :html " »" :latex "\\fg{}" - :texinfo "@tie{}@guillemetright{}") - (opening-single-quote :utf-8 "« " :html "« " :latex "\\og " - :texinfo "@guillemetleft{}@tie{}") - (closing-single-quote :utf-8 " »" :html " »" :latex "\\fg{}" - :texinfo "@tie{}@guillemetright{}") + (primary-opening + :utf-8 "« " :html "« " :latex "\\og " + :texinfo "@guillemetleft{}@tie{}") + (primary-closing + :utf-8 " »" :html " »" :latex "\\fg{}" + :texinfo "@tie{}@guillemetright{}") + (secondary-opening + :utf-8 "« " :html "« " :latex "\\og " + :texinfo "@guillemetleft{}@tie{}") + (secondary-closing :utf-8 " »" :html " »" :latex "\\fg{}" + :texinfo "@tie{}@guillemetright{}") + (apostrophe :utf-8 "’" :html "’")) + ("is" + (primary-opening + :utf-8 "„" :html "„" :latex "\"`" :texinfo "@quotedblbase{}") + (primary-closing + :utf-8 "“" :html "“" :latex "\"'" :texinfo "@quotedblleft{}") + (secondary-opening + :utf-8 "‚" :html "‚" :latex "\\glq{}" :texinfo "@quotesinglbase{}") + (secondary-closing + :utf-8 "‘" :html "‘" :latex "\\grq{}" :texinfo "@quoteleft{}") (apostrophe :utf-8 "’" :html "’")) ("no" ;; https://nn.wikipedia.org/wiki/Sitatteikn - (opening-double-quote :utf-8 "«" :html "«" :latex "\\guillemotleft{}" - :texinfo "@guillemetleft{}") - (closing-double-quote :utf-8 "»" :html "»" :latex "\\guillemotright{}" - :texinfo "@guillemetright{}") - (opening-single-quote :utf-8 "‘" :html "‘" :latex "`" :texinfo "`") - (closing-single-quote :utf-8 "’" :html "’" :latex "'" :texinfo "'") + (primary-opening + :utf-8 "«" :html "«" :latex "\\guillemotleft{}" + :texinfo "@guillemetleft{}") + (primary-closing + :utf-8 "»" :html "»" :latex "\\guillemotright{}" + :texinfo "@guillemetright{}") + (secondary-opening :utf-8 "‘" :html "‘" :latex "`" :texinfo "`") + (secondary-closing :utf-8 "’" :html "’" :latex "'" :texinfo "'") (apostrophe :utf-8 "’" :html "’")) ("nb" ;; https://nn.wikipedia.org/wiki/Sitatteikn - (opening-double-quote :utf-8 "«" :html "«" :latex "\\guillemotleft{}" - :texinfo "@guillemetleft{}") - (closing-double-quote :utf-8 "»" :html "»" :latex "\\guillemotright{}" - :texinfo "@guillemetright{}") - (opening-single-quote :utf-8 "‘" :html "‘" :latex "`" :texinfo "`") - (closing-single-quote :utf-8 "’" :html "’" :latex "'" :texinfo "'") + (primary-opening + :utf-8 "«" :html "«" :latex "\\guillemotleft{}" + :texinfo "@guillemetleft{}") + (primary-closing + :utf-8 "»" :html "»" :latex "\\guillemotright{}" + :texinfo "@guillemetright{}") + (secondary-opening :utf-8 "‘" :html "‘" :latex "`" :texinfo "`") + (secondary-closing :utf-8 "’" :html "’" :latex "'" :texinfo "'") (apostrophe :utf-8 "’" :html "’")) ("nn" ;; https://nn.wikipedia.org/wiki/Sitatteikn - (opening-double-quote :utf-8 "«" :html "«" :latex "\\guillemotleft{}" - :texinfo "@guillemetleft{}") - (closing-double-quote :utf-8 "»" :html "»" :latex "\\guillemotright{}" - :texinfo "@guillemetright{}") - (opening-single-quote :utf-8 "‘" :html "‘" :latex "`" :texinfo "`") - (closing-single-quote :utf-8 "’" :html "’" :latex "'" :texinfo "'") + (primary-opening + :utf-8 "«" :html "«" :latex "\\guillemotleft{}" + :texinfo "@guillemetleft{}") + (primary-closing + :utf-8 "»" :html "»" :latex "\\guillemotright{}" + :texinfo "@guillemetright{}") + (secondary-opening :utf-8 "‘" :html "‘" :latex "`" :texinfo "`") + (secondary-closing :utf-8 "’" :html "’" :latex "'" :texinfo "'") (apostrophe :utf-8 "’" :html "’")) - ("sv" - ;; based on https://sv.wikipedia.org/wiki/Citattecken - (opening-double-quote :utf-8 "”" :html "”" :latex "’’" :texinfo "’’") - (closing-double-quote :utf-8 "”" :html "”" :latex "’’" :texinfo "’’") - (opening-single-quote :utf-8 "’" :html "’" :latex "’" :texinfo "`") - (closing-single-quote :utf-8 "’" :html "’" :latex "’" :texinfo "'") + ("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 + ;; http://www.artlebedev.ru/kovodstvo/sections/104/ + (primary-opening :utf-8 "«" :html "«" :latex "{}<<" + :texinfo "@guillemetleft{}") + (primary-closing :utf-8 "»" :html "»" :latex ">>{}" + :texinfo "@guillemetright{}") + (secondary-opening + :utf-8 "„" :html "„" :latex "\\glqq{}" :texinfo "@quotedblbase{}") + (secondary-closing + :utf-8 "“" :html "“" :latex "\\grqq{}" :texinfo "@quotedblleft{}") + (apostrophe :utf-8 "’" :html: "'")) + ("sl" + ;; Based on https://sl.wikipedia.org/wiki/Narekovaj + (primary-opening :utf-8 "«" :html "«" :latex "{}<<" + :texinfo "@guillemetleft{}") + (primary-closing :utf-8 "»" :html "»" :latex ">>{}" + :texinfo "@guillemetright{}") + (secondary-opening + :utf-8 "„" :html "„" :latex "\\glqq{}" :texinfo "@quotedblbase{}") + (secondary-closing + :utf-8 "“" :html "“" :latex "\\grqq{}" :texinfo "@quotedblleft{}") (apostrophe :utf-8 "’" :html "’")) - ) + ("sv" + ;; Based on https://sv.wikipedia.org/wiki/Citattecken + (primary-opening :utf-8 "”" :html "”" :latex "’’" :texinfo "’’") + (primary-closing :utf-8 "”" :html "”" :latex "’’" :texinfo "’’") + (secondary-opening :utf-8 "’" :html "’" :latex "’" :texinfo "`") + (secondary-closing :utf-8 "’" :html "’" :latex "’" :texinfo "'") + (apostrophe :utf-8 "’" :html "’"))) "Smart quotes translations. Alist whose CAR is a language string and CDR is an alist with quote type as key and a plist associating various encodings to their translation as value. -A quote type can be any symbol among `opening-double-quote', -`closing-double-quote', `opening-single-quote', -`closing-single-quote' and `apostrophe'. +A quote type can be any symbol among `primary-opening', +`primary-closing', `secondary-opening', `secondary-closing' and +`apostrophe'. Valid encodings include `:utf-8', `:html', `:latex' and `:texinfo'. If no translation is found, the quote character is left as-is.") -(defconst org-export-smart-quotes-regexps - (list - ;; Possible opening quote at beginning of string. - "\\`\\([\"']\\)\\(\\w\\|\\s.\\|\\s_\\|\\s(\\)" - ;; Possible closing quote at beginning of string. - "\\`\\([\"']\\)\\(\\s-\\|\\s)\\|\\s.\\)" - ;; Possible apostrophe at beginning of string. - "\\`\\('\\)\\S-" - ;; Opening single and double quotes. - "\\(?:\\s-\\|\\s(\\)\\([\"']\\)\\(?:\\w\\|\\s.\\|\\s_\\)" - ;; Closing single and double quotes. - "\\(?:\\w\\|\\s.\\|\\s_\\)\\([\"']\\)\\(?:\\s-\\|\\s)\\|\\s.\\)" - ;; Apostrophe. - "\\S-\\('\\)\\S-" - ;; Possible opening quote at end of string. - "\\(?:\\s-\\|\\s(\\)\\([\"']\\)\\'" - ;; Possible closing quote at end of string. - "\\(?:\\w\\|\\s.\\|\\s_\\)\\([\"']\\)\\'" - ;; Possible apostrophe at end of string. - "\\S-\\('\\)\\'") - "List of regexps matching a quote or an apostrophe. -In every regexp, quote or apostrophe matched is put in group 1.") +(defun org-export--smart-quote-status (s info) + "Return smart quote status at the beginning of string S. +INFO is the current export state, as a plist." + (let* ((parent (org-element-property :parent s)) + (cache (or (plist-get info :smart-quote-cache) + (let ((table (make-hash-table :test #'eq))) + (plist-put info :smart-quote-cache table) + table))) + (value (gethash parent cache 'missing-data))) + (if (not (eq value 'missing-data)) (cdr (assq s value)) + (let (level1-open full-status) + (org-element-map + (let ((secondary (org-element-secondary-p s))) + (if secondary (org-element-property secondary parent) + (org-element-contents parent))) + 'plain-text + (lambda (text) + (let ((start 0) current-status) + (while (setq start (string-match "['\"]" text start)) + (push + (cond + ((equal (match-string 0 text) "\"") + (setf level1-open (not level1-open)) + (if level1-open 'primary-opening 'primary-closing)) + ;; Not already in a level 1 quote: this is an + ;; apostrophe. + ((not level1-open) 'apostrophe) + ;; Extract previous char and next char. As + ;; a special case, they can also be set to `blank', + ;; `no-blank' or nil. Then determine if current + ;; match is allowed as an opening quote or a closing + ;; quote. + (t + (let* ((previous + (if (> start 0) (substring text (1- start) start) + (let ((p (org-export-get-previous-element + text info))) + (cond ((not p) nil) + ((stringp p) (substring p -1)) + ((memq (org-element-property :post-blank p) + '(0 nil)) + 'no-blank) + (t 'blank))))) + (next + (if (< (1+ start) (length text)) + (substring text (1+ start) (+ start 2)) + (let ((n (org-export-get-next-element text info))) + (cond ((not n) nil) + ((stringp n) (substring n 0 1)) + (t 'no-blank))))) + (allow-open + (and (if (stringp previous) + (string-match "\\s\"\\|\\s-\\|\\s(" + previous) + (memq previous '(blank nil))) + (if (stringp next) + (string-match "\\w\\|\\s.\\|\\s_" next) + (eq next 'no-blank)))) + (allow-close + (and (if (stringp previous) + (string-match "\\w\\|\\s.\\|\\s_" previous) + (eq previous 'no-blank)) + (if (stringp next) + (string-match "\\s-\\|\\s)\\|\\s.\\|\\s\"" + next) + (memq next '(blank nil)))))) + (cond + ((and allow-open allow-close) (error "Should not happen")) + (allow-open 'secondary-opening) + (allow-close 'secondary-closing) + (t 'apostrophe))))) + current-status) + (cl-incf start)) + (when current-status + (push (cons text (nreverse current-status)) full-status)))) + info nil org-element-recursive-objects) + (puthash parent full-status cache) + (cdr (assq s full-status)))))) (defun org-export-activate-smart-quotes (s encoding info &optional original) "Replace regular quotes with \"smart\" quotes in string S. @@ -5013,107 +5557,18 @@ process, a non-nil ORIGINAL optional argument will provide that original string. Return the new string." - (if (equal s "") "" - (let* ((prev (org-export-get-previous-element (or original s) info)) - ;; Try to be flexible when computing number of blanks - ;; before object. The previous object may be a string - ;; introduced by the back-end and not completely parsed. - (pre-blank (and prev - (or (org-element-property :post-blank prev) - ;; A string with missing `:post-blank' - ;; property. - (and (stringp prev) - (string-match " *\\'" prev) - (length (match-string 0 prev))) - ;; Fallback value. - 0))) - (next (org-export-get-next-element (or original s) info)) - (get-smart-quote - (lambda (q type) - ;; Return smart quote associated to a give quote Q, as - ;; a string. TYPE is a symbol among `open', `close' and - ;; `apostrophe'. - (let ((key (case type - (apostrophe 'apostrophe) - (open (if (equal "'" q) 'opening-single-quote - 'opening-double-quote)) - (otherwise (if (equal "'" q) 'closing-single-quote - 'closing-double-quote))))) - (or (plist-get - (cdr (assq key - (cdr (assoc (plist-get info :language) - org-export-smart-quotes-alist)))) - encoding) - q))))) - (if (or (equal "\"" s) (equal "'" s)) - ;; Only a quote: no regexp can match. We have to check both - ;; sides and decide what to do. - (cond ((and (not prev) (not next)) s) - ((not prev) (funcall get-smart-quote s 'open)) - ((and (not next) (zerop pre-blank)) - (funcall get-smart-quote s 'close)) - ((not next) s) - ((zerop pre-blank) (funcall get-smart-quote s 'apostrophe)) - (t (funcall get-smart-quote 'open))) - ;; 1. Replace quote character at the beginning of S. - (cond - ;; Apostrophe? - ((and prev (zerop pre-blank) - (string-match (nth 2 org-export-smart-quotes-regexps) s)) - (setq s (replace-match - (funcall get-smart-quote (match-string 1 s) 'apostrophe) - nil t s 1))) - ;; Closing quote? - ((and prev (zerop pre-blank) - (string-match (nth 1 org-export-smart-quotes-regexps) s)) - (setq s (replace-match - (funcall get-smart-quote (match-string 1 s) 'close) - nil t s 1))) - ;; Opening quote? - ((and (or (not prev) (> pre-blank 0)) - (string-match (nth 0 org-export-smart-quotes-regexps) s)) - (setq s (replace-match - (funcall get-smart-quote (match-string 1 s) 'open) - nil t s 1)))) - ;; 2. Replace quotes in the middle of the string. - (setq s (replace-regexp-in-string - ;; Opening quotes. - (nth 3 org-export-smart-quotes-regexps) - (lambda (text) - (funcall get-smart-quote (match-string 1 text) 'open)) - s nil t 1)) - (setq s (replace-regexp-in-string - ;; Closing quotes. - (nth 4 org-export-smart-quotes-regexps) - (lambda (text) - (funcall get-smart-quote (match-string 1 text) 'close)) - s nil t 1)) - (setq s (replace-regexp-in-string - ;; Apostrophes. - (nth 5 org-export-smart-quotes-regexps) - (lambda (text) - (funcall get-smart-quote (match-string 1 text) 'apostrophe)) - s nil t 1)) - ;; 3. Replace quote character at the end of S. - (cond - ;; Apostrophe? - ((and next (string-match (nth 8 org-export-smart-quotes-regexps) s)) - (setq s (replace-match - (funcall get-smart-quote (match-string 1 s) 'apostrophe) - nil t s 1))) - ;; Closing quote? - ((and (not next) - (string-match (nth 7 org-export-smart-quotes-regexps) s)) - (setq s (replace-match - (funcall get-smart-quote (match-string 1 s) 'close) - nil t s 1))) - ;; Opening quote? - ((and next (string-match (nth 6 org-export-smart-quotes-regexps) s)) - (setq s (replace-match - (funcall get-smart-quote (match-string 1 s) 'open) - nil t s 1)))) - ;; Return string with smart quotes. - s)))) + (let ((quote-status + (copy-sequence (org-export--smart-quote-status (or original s) info)))) + (replace-regexp-in-string + "['\"]" + (lambda (match) + (or (plist-get + (cdr (assq (pop quote-status) + (cdr (assoc (plist-get info :language) + org-export-smart-quotes-alist)))) + encoding) + match)) + s nil t))) ;;;; Topology ;; @@ -5125,46 +5580,23 @@ Return the new string." ;; (`org-export-get-parent-table'), previous element or object ;; (`org-export-get-previous-element') and next element or object ;; (`org-export-get-next-element'). -;; -;; `org-export-get-genealogy' returns the full genealogy of a given -;; element or object, from closest parent to full parse tree. ;; defsubst org-export-get-parent must be defined before first use -(defun org-export-get-genealogy (blob) - "Return full genealogy relative to a given element or object. - -BLOB is the element or object being considered. - -Ancestors are returned from closest to farthest, the last one -being the full parse tree." - (let (genealogy (parent blob)) - (while (setq parent (org-element-property :parent parent)) - (push parent genealogy)) - (nreverse genealogy))) (defun org-export-get-parent-headline (blob) "Return BLOB parent headline or nil. BLOB is the element or object being considered." - (let ((parent blob)) - (while (and (setq parent (org-element-property :parent parent)) - (not (eq (org-element-type parent) 'headline)))) - parent)) + (org-element-lineage blob '(headline))) (defun org-export-get-parent-element (object) "Return first element containing OBJECT or nil. OBJECT is the object to consider." - (let ((parent object)) - (while (and (setq parent (org-element-property :parent parent)) - (memq (org-element-type parent) org-element-all-objects))) - parent)) + (org-element-lineage object org-element-all-elements)) (defun org-export-get-parent-table (object) "Return OBJECT parent table or nil. OBJECT is either a `table-cell' or `table-element' type object." - (let ((parent object)) - (while (and (setq parent (org-element-property :parent parent)) - (not (eq (org-element-type parent) 'table)))) - parent)) + (org-element-lineage object '(table))) (defun org-export-get-previous-element (blob info &optional n) "Return previous element or object. @@ -5177,27 +5609,19 @@ When optional argument N is a positive integer, return a list containing up to N siblings before BLOB, from farthest to closest. With any other non-nil value, return a list containing all of them." - (let ((siblings - ;; An object can belong to the contents of its parent or - ;; to a secondary string. We check the latter option - ;; first. - (let ((parent (org-export-get-parent blob))) - (or (let ((sec-value (org-element-property - (cdr (assq (org-element-type parent) - org-element-secondary-value-alist)) - parent))) - (and (memq blob sec-value) sec-value)) - (org-element-contents parent)))) - prev) + (let* ((secondary (org-element-secondary-p blob)) + (parent (org-export-get-parent blob)) + (siblings + (if secondary (org-element-property secondary parent) + (org-element-contents parent))) + prev) (catch 'exit - (mapc (lambda (obj) - (cond ((memq obj (plist-get info :ignore-list))) - ((null n) (throw 'exit obj)) - ((not (wholenump n)) (push obj prev)) - ((zerop n) (throw 'exit prev)) - (t (decf n) (push obj prev)))) - (cdr (memq blob (reverse siblings)))) - prev))) + (dolist (obj (cdr (memq blob (reverse siblings))) prev) + (cond ((memq obj (plist-get info :ignore-list))) + ((null n) (throw 'exit obj)) + ((not (wholenump n)) (push obj prev)) + ((zerop n) (throw 'exit prev)) + (t (cl-decf n) (push obj prev))))))) (defun org-export-get-next-element (blob info &optional n) "Return next element or object. @@ -5210,26 +5634,20 @@ When optional argument N is a positive integer, return a list containing up to N siblings after BLOB, from closest to farthest. With any other non-nil value, return a list containing all of them." - (let ((siblings - ;; An object can belong to the contents of its parent or to - ;; a secondary string. We check the latter option first. - (let ((parent (org-export-get-parent blob))) - (or (let ((sec-value (org-element-property - (cdr (assq (org-element-type parent) - org-element-secondary-value-alist)) - parent))) - (cdr (memq blob sec-value))) - (cdr (memq blob (org-element-contents parent)))))) - next) + (let* ((secondary (org-element-secondary-p blob)) + (parent (org-export-get-parent blob)) + (siblings + (cdr (memq blob + (if secondary (org-element-property secondary parent) + (org-element-contents parent))))) + next) (catch 'exit - (mapc (lambda (obj) - (cond ((memq obj (plist-get info :ignore-list))) - ((null n) (throw 'exit obj)) - ((not (wholenump n)) (push obj next)) - ((zerop n) (throw 'exit (nreverse next))) - (t (decf n) (push obj next)))) - siblings) - (nreverse next)))) + (dolist (obj siblings (nreverse next)) + (cond ((memq obj (plist-get info :ignore-list))) + ((null n) (throw 'exit obj)) + ((not (wholenump n)) (push obj next)) + ((zerop n) (throw 'exit (nreverse next))) + (t (cl-decf n) (push obj next))))))) ;;;; Translation @@ -5242,191 +5660,352 @@ them." '(("%e %n: %c" ("fr" :default "%e %n : %c" :html "%e %n : %c")) ("Author" + ("ar" :default "تأليف") ("ca" :default "Autor") ("cs" :default "Autor") ("da" :default "Forfatter") ("de" :default "Autor") ("eo" :html "Aŭtoro") ("es" :default "Autor") + ("et" :default "Autor") ("fi" :html "Tekijä") ("fr" :default "Auteur") ("hu" :default "Szerzõ") ("is" :html "Höfundur") ("it" :default "Autore") - ("ja" :html "著者" :utf-8 "著者") + ("ja" :default "著者" :html "著者") ("nl" :default "Auteur") ("no" :default "Forfatter") ("nb" :default "Forfatter") ("nn" :default "Forfattar") ("pl" :default "Autor") + ("pt_BR" :default "Autor") ("ru" :html "Автор" :utf-8 "Автор") + ("sl" :default "Avtor") ("sv" :html "Författare") ("uk" :html "Автор" :utf-8 "Автор") ("zh-CN" :html "作者" :utf-8 "作者") ("zh-TW" :html "作者" :utf-8 "作者")) + ("Continued from previous page" + ("ar" :default "تتمة الصفحة السابقة") + ("cs" :default "Pokračování z předchozí strany") + ("de" :default "Fortsetzung von vorheriger Seite") + ("es" :html "Continúa de la página anterior" :ascii "Continua de la pagina anterior" :default "Continúa de la página anterior") + ("fr" :default "Suite de la page précédente") + ("it" :default "Continua da pagina precedente") + ("ja" :default "前ページからの続き") + ("nl" :default "Vervolg van vorige pagina") + ("pt" :default "Continuação da página anterior") + ("ru" :html "(Продолжение)" + :utf-8 "(Продолжение)") + ("sl" :default "Nadaljevanje s prejšnje strani")) + ("Continued on next page" + ("ar" :default "التتمة في الصفحة التالية") + ("cs" :default "Pokračuje na další stránce") + ("de" :default "Fortsetzung nächste Seite") + ("es" :html "Continúa en la siguiente página" :ascii "Continua en la siguiente pagina" :default "Continúa en la siguiente página") + ("fr" :default "Suite page suivante") + ("it" :default "Continua alla pagina successiva") + ("ja" :default "次ページに続く") + ("nl" :default "Vervolg op volgende pagina") + ("pt" :default "Continua na página seguinte") + ("ru" :html "(Продолжение следует)" + :utf-8 "(Продолжение следует)") + ("sl" :default "Nadaljevanje na naslednji strani")) + ("Created" + ("cs" :default "Vytvořeno") + ("sl" :default "Ustvarjeno")) ("Date" + ("ar" :default "بتاريخ") ("ca" :default "Data") ("cs" :default "Datum") ("da" :default "Dato") ("de" :default "Datum") ("eo" :default "Dato") ("es" :default "Fecha") + ("et" :html "Kuupäev" :utf-8 "Kuupäev") ("fi" :html "Päivämäärä") ("hu" :html "Dátum") ("is" :default "Dagsetning") ("it" :default "Data") - ("ja" :html "日付" :utf-8 "日付") + ("ja" :default "日付" :html "日付") ("nl" :default "Datum") ("no" :default "Dato") ("nb" :default "Dato") ("nn" :default "Dato") ("pl" :default "Data") + ("pt_BR" :default "Data") ("ru" :html "Дата" :utf-8 "Дата") + ("sl" :default "Datum") ("sv" :default "Datum") ("uk" :html "Дата" :utf-8 "Дата") ("zh-CN" :html "日期" :utf-8 "日期") ("zh-TW" :html "日期" :utf-8 "日期")) ("Equation" + ("ar" :default "معادلة") + ("cs" :default "Rovnice") ("da" :default "Ligning") ("de" :default "Gleichung") - ("es" :html "Ecuación" :default "Ecuación") + ("es" :ascii "Ecuacion" :html "Ecuación" :default "Ecuación") + ("et" :html "Võrrand" :utf-8 "Võrrand") ("fr" :ascii "Equation" :default "Équation") + ("is" :default "Jafna") + ("ja" :default "方程式") ("no" :default "Ligning") ("nb" :default "Ligning") ("nn" :default "Likning") + ("pt_BR" :html "Equação" :default "Equação" :ascii "Equacao") + ("ru" :html "Уравнение" + :utf-8 "Уравнение") + ("sl" :default "Enačba") ("sv" :default "Ekvation") ("zh-CN" :html "方程" :utf-8 "方程")) ("Figure" + ("ar" :default "شكل") + ("cs" :default "Obrázek") ("da" :default "Figur") ("de" :default "Abbildung") ("es" :default "Figura") - ("ja" :html "図" :utf-8 "図") + ("et" :default "Joonis") + ("is" :default "Mynd") + ("ja" :default "図" :html "図") ("no" :default "Illustrasjon") ("nb" :default "Illustrasjon") ("nn" :default "Illustrasjon") + ("pt_BR" :default "Figura") + ("ru" :html "Рисунок" :utf-8 "Рисунок") ("sv" :default "Illustration") ("zh-CN" :html "图" :utf-8 "图")) ("Figure %d:" + ("ar" :default "شكل %d:") + ("cs" :default "Obrázek %d:") ("da" :default "Figur %d") ("de" :default "Abbildung %d:") ("es" :default "Figura %d:") + ("et" :default "Joonis %d:") ("fr" :default "Figure %d :" :html "Figure %d :") - ("ja" :html "図%d: " :utf-8 "図%d: ") + ("is" :default "Mynd %d") + ("ja" :default "図%d: " :html "図%d: ") ("no" :default "Illustrasjon %d") ("nb" :default "Illustrasjon %d") ("nn" :default "Illustrasjon %d") + ("pt_BR" :default "Figura %d:") + ("ru" :html "Рис. %d.:" :utf-8 "Рис. %d.:") + ("sl" :default "Slika %d") ("sv" :default "Illustration %d") ("zh-CN" :html "图%d " :utf-8 "图%d ")) ("Footnotes" + ("ar" :default "الهوامش") ("ca" :html "Peus de pàgina") - ("cs" :default "Pozn\xe1mky pod carou") + ("cs" :default "Poznámky pod čarou") ("da" :default "Fodnoter") ("de" :html "Fußnoten" :default "Fußnoten") ("eo" :default "Piednotoj") - ("es" :html "Nota al pie de página" :default "Nota al pie de página") + ("es" :ascii "Nota al pie de pagina" :html "Nota al pie de página" :default "Nota al pie de página") + ("et" :html "Allmärkused" :utf-8 "Allmärkused") ("fi" :default "Alaviitteet") ("fr" :default "Notes de bas de page") ("hu" :html "Lábjegyzet") ("is" :html "Aftanmálsgreinar") ("it" :html "Note a piè di pagina") - ("ja" :html "脚注" :utf-8 "脚注") + ("ja" :default "脚注" :html "脚注") ("nl" :default "Voetnoten") ("no" :default "Fotnoter") ("nb" :default "Fotnoter") ("nn" :default "Fotnotar") ("pl" :default "Przypis") + ("pt_BR" :html "Notas de Rodapé" :default "Notas de Rodapé" :ascii "Notas de Rodape") ("ru" :html "Сноски" :utf-8 "Сноски") + ("sl" :default "Opombe") ("sv" :default "Fotnoter") ("uk" :html "Примітки" :utf-8 "Примітки") ("zh-CN" :html "脚注" :utf-8 "脚注") ("zh-TW" :html "腳註" :utf-8 "腳註")) ("List of Listings" + ("ar" :default "قائمة بالبرامج") + ("cs" :default "Seznam programů") ("da" :default "Programmer") ("de" :default "Programmauflistungsverzeichnis") - ("es" :default "Indice de Listados de programas") + ("es" :ascii "Indice de Listados de programas" :html "Índice de Listados de programas" :default "Índice de Listados de programas") + ("et" :default "Loendite nimekiri") ("fr" :default "Liste des programmes") + ("ja" :default "ソースコード目次") ("no" :default "Dataprogrammer") ("nb" :default "Dataprogrammer") + ("ru" :html "Список распечаток" + :utf-8 "Список распечаток") + ("sl" :default "Seznam programskih izpisov") ("zh-CN" :html "代码目录" :utf-8 "代码目录")) ("List of Tables" + ("ar" :default "قائمة بالجداول") + ("cs" :default "Seznam tabulek") ("da" :default "Tabeller") ("de" :default "Tabellenverzeichnis") - ("es" :default "Indice de tablas") + ("es" :ascii "Indice de tablas" :html "Índice de tablas" :default "Índice de tablas") + ("et" :default "Tabelite nimekiri") ("fr" :default "Liste des tableaux") + ("is" :default "Töfluskrá" :html "Töfluskrá") + ("ja" :default "表目次") ("no" :default "Tabeller") ("nb" :default "Tabeller") ("nn" :default "Tabeller") + ("pt_BR" :default "Índice de Tabelas" :ascii "Indice de Tabelas") + ("ru" :html "Список таблиц" + :utf-8 "Список таблиц") + ("sl" :default "Seznam tabel") ("sv" :default "Tabeller") ("zh-CN" :html "表格目录" :utf-8 "表格目录")) + ("Listing" + ("ar" :default "برنامج") + ("cs" :default "Program") + ("da" :default "Program") + ("de" :default "Programmlisting") + ("es" :default "Listado de programa") + ("et" :default "Loend") + ("fr" :default "Programme" :html "Programme") + ("ja" :default "ソースコード") + ("no" :default "Dataprogram") + ("nb" :default "Dataprogram") + ("pt_BR" :default "Listagem") + ("ru" :html "Распечатка" + :utf-8 "Распечатка") + ("sl" :default "Izpis programa") + ("zh-CN" :html "代码" :utf-8 "代码")) ("Listing %d:" + ("ar" :default "برنامج %d:") + ("cs" :default "Program %d:") ("da" :default "Program %d") ("de" :default "Programmlisting %d") ("es" :default "Listado de programa %d") + ("et" :default "Loend %d") ("fr" :default "Programme %d :" :html "Programme %d :") - ("no" :default "Dataprogram") - ("nb" :default "Dataprogram") + ("ja" :default "ソースコード%d:") + ("no" :default "Dataprogram %d") + ("nb" :default "Dataprogram %d") + ("pt_BR" :default "Listagem %d") + ("ru" :html "Распечатка %d.:" + :utf-8 "Распечатка %d.:") + ("sl" :default "Izpis programa %d") ("zh-CN" :html "代码%d " :utf-8 "代码%d ")) + ("References" + ("ar" :default "المراجع") + ("cs" :default "Reference") + ("fr" :ascii "References" :default "Références") + ("de" :default "Quellen") + ("es" :default "Referencias") + ("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") + ("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") + ("sl" :default "Glej izpis programa %s")) ("See section %s" + ("ar" :default "انظر قسم %s") + ("cs" :default "Viz sekce %s") ("da" :default "jævnfør afsnit %s") ("de" :default "siehe Abschnitt %s") - ("es" :default "vea seccion %s") + ("es" :ascii "Vea seccion %s" :html "Vea sección %s" :default "Vea sección %s") + ("et" :html "Vaata peatükki %s" :utf-8 "Vaata peatükki %s") ("fr" :default "cf. section %s") - ("zh-CN" :html "参见第%d节" :utf-8 "参见第%s节")) + ("ja" :default "セクション %s を参照") + ("pt_BR" :html "Veja a seção %s" :default "Veja a seção %s" + :ascii "Veja a secao %s") + ("ru" :html "См. раздел %s" + :utf-8 "См. раздел %s") + ("sl" :default "Glej poglavje %d") + ("zh-CN" :html "参见第%s节" :utf-8 "参见第%s节")) + ("See table %s" + ("cs" :default "Viz tabulka %s") + ("fr" :default "cf. tableau %s" + :html "cf. tableau %s" :latex "cf.~tableau~%s") + ("sl" :default "Glej tabelo %s")) ("Table" + ("ar" :default "جدول") + ("cs" :default "Tabulka") ("de" :default "Tabelle") ("es" :default "Tabla") + ("et" :default "Tabel") ("fr" :default "Tableau") - ("ja" :html "表" :utf-8 "表") + ("is" :default "Tafla") + ("ja" :default "表" :html "表") + ("pt_BR" :default "Tabela") + ("ru" :html "Таблица" + :utf-8 "Таблица") ("zh-CN" :html "表" :utf-8 "表")) ("Table %d:" + ("ar" :default "جدول %d:") + ("cs" :default "Tabulka %d:") ("da" :default "Tabel %d") ("de" :default "Tabelle %d") ("es" :default "Tabla %d") + ("et" :default "Tabel %d") ("fr" :default "Tableau %d :") - ("ja" :html "表%d:" :utf-8 "表%d:") + ("is" :default "Tafla %d") + ("ja" :default "表%d:" :html "表%d:") ("no" :default "Tabell %d") ("nb" :default "Tabell %d") ("nn" :default "Tabell %d") + ("pt_BR" :default "Tabela %d") + ("ru" :html "Таблица %d.:" + :utf-8 "Таблица %d.:") + ("sl" :default "Tabela %d") ("sv" :default "Tabell %d") ("zh-CN" :html "表%d " :utf-8 "表%d ")) ("Table of Contents" + ("ar" :default "قائمة المحتويات") ("ca" :html "Índex") ("cs" :default "Obsah") ("da" :default "Indhold") ("de" :default "Inhaltsverzeichnis") ("eo" :default "Enhavo") - ("es" :html "Índice") + ("es" :ascii "Indice" :html "Índice" :default "Índice") + ("et" :default "Sisukord") ("fi" :html "Sisällysluettelo") ("fr" :ascii "Sommaire" :default "Table des matières") ("hu" :html "Tartalomjegyzék") ("is" :default "Efnisyfirlit") ("it" :default "Indice") - ("ja" :html "目次" :utf-8 "目次") + ("ja" :default "目次" :html "目次") ("nl" :default "Inhoudsopgave") ("no" :default "Innhold") ("nb" :default "Innhold") ("nn" :default "Innhald") ("pl" :html "Spis treści") + ("pt_BR" :html "Índice" :utf8 "Índice" :ascii "Indice") ("ru" :html "Содержание" :utf-8 "Содержание") + ("sl" :default "Kazalo") ("sv" :html "Innehåll") ("uk" :html "Зміст" :utf-8 "Зміст") ("zh-CN" :html "目录" :utf-8 "目录") ("zh-TW" :html "目錄" :utf-8 "目錄")) ("Unknown reference" + ("ar" :default "مرجع غير معرّف") ("da" :default "ukendt reference") ("de" :default "Unbekannter Verweis") - ("es" :default "referencia desconocida") + ("es" :default "Referencia desconocida") + ("et" :default "Tundmatu viide") ("fr" :ascii "Destination inconnue" :default "Référence inconnue") + ("ja" :default "不明な参照先") + ("pt_BR" :default "Referência desconhecida" + :ascii "Referencia desconhecida") + ("ru" :html "Неизвестная ссылка" + :utf-8 "Неизвестная ссылка") + ("sl" :default "Neznana referenca") ("zh-CN" :html "未知引用" :utf-8 "未知引用"))) "Dictionary for export engine. -Alist whose CAR is the string to translate and CDR is an alist -whose CAR is the language string and CDR is a plist whose +Alist whose car is the string to translate and cdr is an alist +whose car is the language string and cdr is a plist whose properties are possible charsets and values translated terms. -It is used as a database for `org-export-translate'. Since this +It is used as a database for `org-export-translate'. Since this function returns the string as-is if no translation was found, the variable only needs to record values different from the entry.") @@ -5437,9 +6016,9 @@ entry.") ENCODING is a symbol among `:ascii', `:html', `:latex', `:latin1' and `:utf-8'. INFO is a plist used as a communication channel. -Translation depends on `:language' property. Return the -translated string. If no translation is found, try to fall back -to `:default' encoding. If it fails, return S." +Translation depends on `:language' property. Return the +translated string. If no translation is found, try to fall back +to `:default' encoding. If it fails, return S." (let* ((lang (plist-get info :language)) (translations (cdr (assoc lang (cdr (assoc s org-export-dictionary)))))) @@ -5524,12 +6103,17 @@ and `org-export-to-file' for more specialized functions." (let* ((process-connection-type nil) (,proc-buffer (generate-new-buffer-name "*Org Export Process*")) (,process - (start-process - "org-export-process" ,proc-buffer - (expand-file-name invocation-name invocation-directory) - "-Q" "--batch" - "-l" org-export-async-init-file - "-l" ,temp-file))) + (apply + #'start-process + (append + (list "org-export-process" + ,proc-buffer + (expand-file-name invocation-name invocation-directory) + "--batch") + (if org-export-async-init-file + (list "-Q" "-l" org-export-async-init-file) + (list "-l" user-init-file)) + (list "-l" ,temp-file))))) ;; Register running process in stack. (org-export-add-to-stack (get-buffer ,proc-buffer) nil ,process) ;; Set-up sentinel in order to catch results. @@ -5698,45 +6282,43 @@ of subtree at point. When optional argument PUB-DIR is set, use it as the publishing directory. -When optional argument VISIBLE-ONLY is non-nil, don't export -contents of hidden elements. - Return file name as a string." (let* ((visited-file (buffer-file-name (buffer-base-buffer))) (base-name - ;; File name may come from EXPORT_FILE_NAME subtree - ;; property, assuming point is at beginning of said - ;; sub-tree. - (file-name-sans-extension - (or (and subtreep - (org-entry-get - (save-excursion - (ignore-errors (org-back-to-heading) (point))) - "EXPORT_FILE_NAME" t)) - ;; File name may be extracted from buffer's associated - ;; file, if any. - (and visited-file (file-name-nondirectory visited-file)) - ;; Can't determine file name on our own: Ask user. - (let ((read-file-name-function - (and org-completion-use-ido 'ido-read-file-name))) - (read-file-name - "Output file: " pub-dir nil nil nil - (lambda (name) - (string= (file-name-extension name t) extension))))))) + (concat + (file-name-sans-extension + (or + ;; Check EXPORT_FILE_NAME subtree property. + (and subtreep (org-entry-get nil "EXPORT_FILE_NAME" 'selective)) + ;; Check #+EXPORT_FILE_NAME keyword. + (org-with-point-at (point-min) + (catch :found + (let ((case-fold-search t)) + (while (re-search-forward + "^[ \t]*#\\+EXPORT_FILE_NAME:[ \t]+\\S-" nil t) + (let ((element (org-element-at-point))) + (when (eq 'keyword (org-element-type element)) + (throw :found + (org-element-property :value element)))))))) + ;; Extract from buffer's associated file, if any. + (and visited-file (file-name-nondirectory visited-file)) + ;; Can't determine file name on our own: ask user. + (read-file-name + "Output file: " pub-dir nil nil nil + (lambda (n) (string= extension (file-name-extension n t)))))) + extension)) (output-file ;; Build file name. Enforce EXTENSION over whatever user ;; may have come up with. PUB-DIR, if defined, always has ;; precedence over any provided path. (cond - (pub-dir - (concat (file-name-as-directory pub-dir) - (file-name-nondirectory base-name) - extension)) - ((file-name-absolute-p base-name) (concat base-name extension)) - (t (concat (file-name-as-directory ".") base-name extension))))) + (pub-dir (concat (file-name-as-directory pub-dir) + (file-name-nondirectory base-name))) + ((file-name-absolute-p base-name) base-name) + (t base-name)))) ;; If writing to OUTPUT-FILE would overwrite original file, append ;; EXTENSION another time to final name. - (if (and visited-file (org-file-equal-p visited-file output-file)) + (if (and visited-file (file-equal-p visited-file output-file)) (concat output-file extension) output-file))) @@ -5757,68 +6339,21 @@ removed beforehand. Return the new stack." "Menu for asynchronous export results and running processes." (interactive) (let ((buffer (get-buffer-create "*Org Export Stack*"))) - (set-buffer buffer) - (when (zerop (buffer-size)) (org-export-stack-mode)) - (org-export-stack-refresh) + (with-current-buffer buffer + (org-export-stack-mode) + (tabulated-list-print t)) (pop-to-buffer buffer)) (message "Type \"q\" to quit, \"?\" for help")) -(defun org-export--stack-source-at-point () - "Return source from export results at point in stack." - (let ((source (car (nth (1- (org-current-line)) org-export-stack-contents)))) - (if (not source) (error "Source unavailable, please refresh buffer") - (let ((source-name (if (stringp source) source (buffer-name source)))) - (if (save-excursion - (beginning-of-line) - (looking-at (concat ".* +" (regexp-quote source-name) "$"))) - source - ;; SOURCE is not consistent with current line. The stack - ;; view is outdated. - (error "Source unavailable; type `g' to update buffer")))))) - (defun org-export-stack-clear () "Remove all entries from export stack." (interactive) (setq org-export-stack-contents nil)) -(defun org-export-stack-refresh (&rest dummy) - "Refresh the asynchronous export stack. -DUMMY is ignored. Unavailable sources are removed from the list. -Return the new stack." - (let ((inhibit-read-only t)) - (org-preserve-lc - (erase-buffer) - (insert (concat - (let ((counter 0)) - (mapconcat - (lambda (entry) - (let ((proc-p (processp (nth 2 entry)))) - (concat - ;; Back-end. - (format " %-12s " (or (nth 1 entry) "")) - ;; Age. - (let ((data (nth 2 entry))) - (if proc-p (format " %6s " (process-status data)) - ;; Compute age of the results. - (org-format-seconds - "%4h:%.2m " - (float-time (time-since data))))) - ;; Source. - (format " %s" - (let ((source (car entry))) - (if (stringp source) source - (buffer-name source))))))) - ;; Clear stack from exited processes, dead buffers or - ;; non-existent files. - (setq org-export-stack-contents - (org-remove-if-not - (lambda (el) - (if (processp (nth 2 el)) - (buffer-live-p (process-buffer (nth 2 el))) - (let ((source (car el))) - (if (bufferp source) (buffer-live-p source) - (file-exists-p source))))) - org-export-stack-contents)) "\n"))))))) +(defun org-export-stack-refresh () + "Refresh the export stack." + (interactive) + (tabulated-list-print t)) (defun org-export-stack-remove (&optional source) "Remove export results at point from stack. @@ -5826,7 +6361,7 @@ If optional argument SOURCE is non-nil, remove it instead." (interactive) (let ((source (or source (org-export--stack-source-at-point)))) (setq org-export-stack-contents - (org-remove-if (lambda (el) (equal (car el) source)) + (cl-remove-if (lambda (el) (equal (car el) source)) org-export-stack-contents)))) (defun org-export-stack-view (&optional in-emacs) @@ -5842,11 +6377,10 @@ within Emacs." (defvar org-export-stack-mode-map (let ((km (make-sparse-keymap))) + (set-keymap-parent km tabulated-list-mode-map) (define-key km " " 'next-line) - (define-key km "n" 'next-line) (define-key km "\C-n" 'next-line) (define-key km [down] 'next-line) - (define-key km "p" 'previous-line) (define-key km "\C-p" 'previous-line) (define-key km "\C-?" 'previous-line) (define-key km [up] 'previous-line) @@ -5857,31 +6391,85 @@ within Emacs." km) "Keymap for Org Export Stack.") -(define-derived-mode org-export-stack-mode special-mode "Org-Stack" +(define-derived-mode org-export-stack-mode tabulated-list-mode "Org-Stack" "Mode for displaying asynchronous export stack. -Type \\[org-export-stack] to visualize the asynchronous export +Type `\\[org-export-stack]' to visualize the asynchronous export stack. -In an Org Export Stack buffer, use \\<org-export-stack-mode-map>\\[org-export-stack-view] to view export output -on current line, \\[org-export-stack-remove] to remove it from the stack and \\[org-export-stack-clear] to clear +In an Org Export Stack buffer, use \ +\\<org-export-stack-mode-map>`\\[org-export-stack-view]' to view export output +on current line, `\\[org-export-stack-remove]' to remove it from the stack and \ +`\\[org-export-stack-clear]' to clear stack completely. -Removing entries in an Org Export Stack buffer doesn't affect -files or buffers, only the display. +Removing entries in a stack buffer does not affect files +or buffers, only display. \\{org-export-stack-mode-map}" - (abbrev-mode 0) - (auto-fill-mode 0) - (setq buffer-read-only t - buffer-undo-list t - truncate-lines t - header-line-format - '(:eval - (format " %-12s | %6s | %s" "Back-End" "Age" "Source"))) - (org-add-hook 'post-command-hook 'org-export-stack-refresh nil t) - (set (make-local-variable 'revert-buffer-function) - 'org-export-stack-refresh)) + (setq tabulated-list-format + (vector (list "#" 4 #'org-export--stack-num-predicate) + (list "Back-End" 12 t) + (list "Age" 6 nil) + (list "Source" 0 nil))) + (setq tabulated-list-sort-key (cons "#" nil)) + (setq tabulated-list-entries #'org-export--stack-generate) + (add-hook 'tabulated-list-revert-hook #'org-export--stack-generate nil t) + (add-hook 'post-command-hook #'org-export-stack-refresh nil t) + (tabulated-list-init-header)) + +(defun org-export--stack-generate () + "Generate the asynchronous export stack for display. +Unavailable sources are removed from the list. Return a list +appropriate for `tabulated-list-print'." + ;; Clear stack from exited processes, dead buffers or non-existent + ;; files. + (setq org-export-stack-contents + (cl-remove-if-not + (lambda (el) + (if (processp (nth 2 el)) + (buffer-live-p (process-buffer (nth 2 el))) + (let ((source (car el))) + (if (bufferp source) (buffer-live-p source) + (file-exists-p source))))) + org-export-stack-contents)) + ;; Update `tabulated-list-entries'. + (let ((counter 0)) + (mapcar + (lambda (entry) + (let ((source (car entry))) + (list source + (vector + ;; Counter. + (number-to-string (cl-incf counter)) + ;; Back-End. + (if (nth 1 entry) (symbol-name (nth 1 entry)) "") + ;; Age. + (let ((info (nth 2 entry))) + (if (processp info) (symbol-name (process-status info)) + (format-seconds "%h:%.2m" (float-time (time-since info))))) + ;; Source. + (if (stringp source) source (buffer-name source)))))) + org-export-stack-contents))) + +(defun org-export--stack-num-predicate (a b) + (< (string-to-number (aref (nth 1 a) 0)) + (string-to-number (aref (nth 1 b) 0)))) + +(defun org-export--stack-source-at-point () + "Return source from export results at point in stack." + (let ((source (car (nth (1- (org-current-line)) org-export-stack-contents)))) + (if (not source) (error "Source unavailable, please refresh buffer") + (let ((source-name (if (stringp source) source (buffer-name source)))) + (if (save-excursion + (beginning-of-line) + (looking-at-p (concat ".* +" (regexp-quote source-name) "$"))) + source + ;; SOURCE is not consistent with current line. The stack + ;; view is outdated. + (error (substitute-command-keys + "Source unavailable; type `\\[org-export-stack-refresh]' \ +to refresh buffer"))))))) @@ -5907,10 +6495,12 @@ SPC and DEL (resp. C-n and C-p) keys. Set variable `org-export-dispatch-use-expert-ui' to switch to one flavor or the other. -When ARG is \\[universal-argument], repeat the last export action, with the same set -of options used back then, on the current buffer. +When ARG is `\\[universal-argument]', repeat the last export action, with the\ + same +set of options used back then, on the current buffer. -When ARG is \\[universal-argument] \\[universal-argument], display the asynchronous export stack." +When ARG is `\\[universal-argument] \\[universal-argument]', display the \ +asynchronous export stack." (interactive "P") (let* ((input (cond ((equal arg '(16)) '(stack)) @@ -5935,7 +6525,7 @@ When ARG is \\[universal-argument] \\[universal-argument], display the asynchron (optns (cdr input))) (unless (memq 'subtree optns) (move-marker org-export-dispatch-last-position nil)) - (case action + (cl-case action ;; First handle special hard-coded actions. (template (org-export-insert-default-template nil optns)) (stack (org-export-stack)) @@ -5944,7 +6534,7 @@ When ARG is \\[universal-argument] \\[universal-argument], display the asynchron (publish-current-project (org-publish-current-project (memq 'force optns) (memq 'async optns))) (publish-choose-project - (org-publish (assoc (org-icompleting-read + (org-publish (assoc (completing-read "Publish project: " org-publish-project-alist nil t) org-publish-project-alist) @@ -5995,19 +6585,19 @@ back to standard interface." ;; on the first key, if any. A nil value means KEY will ;; only be activated at first level. (if (or (eq access-key t) (eq access-key first-key)) - (org-propertize key 'face 'org-warning) + (propertize key 'face 'org-warning) key))) (fontify-value (lambda (value) ;; Fontify VALUE string. - (org-propertize value 'face 'font-lock-variable-name-face))) + (propertize value 'face 'font-lock-variable-name-face))) ;; Prepare menu entries by extracting them from registered ;; back-ends and sorting them by access key and by ordinal, ;; if any. (entries (sort (sort (delq nil - (mapcar 'org-export-backend-menu - org-export--registered-backends)) + (mapcar #'org-export-backend-menu + org-export-registered-backends)) (lambda (a b) (let ((key-a (nth 1 a)) (key-b (nth 1 b))) @@ -6037,8 +6627,8 @@ back to standard interface." (concat ;; Options are hard-coded. (format "[%s] Body only: %s [%s] Visible only: %s -[%s] Export scope: %s [%s] Force publishing: %s -[%s] Async export: %s\n\n" +\[%s] Export scope: %s [%s] Force publishing: %s +\[%s] Async export: %s\n\n" (funcall fontify-key "C-b" t) (funcall fontify-value (if (memq 'body options) "On " "Off")) @@ -6074,7 +6664,7 @@ back to standard interface." (concat (mapconcat (lambda (sub-entry) - (incf index) + (cl-incf index) (format (if (zerop (mod index 2)) " [%s] %-26s" "[%s] %s\n") @@ -6145,7 +6735,7 @@ back to standard interface." standard-prompt allowed-keys entries options first-key expertp)))) (defun org-export--dispatch-action - (prompt allowed-keys entries options first-key expertp) + (prompt allowed-keys entries options first-key expertp) "Read a character from command input and act accordingly. PROMPT is the displayed prompt, as a string. ALLOWED-KEYS is @@ -6163,7 +6753,7 @@ options as CDR." (while (and (setq key (read-char-exclusive prompt)) (not expertp) (memq key '(14 16 ?\s ?\d))) - (case key + (cl-case key (14 (if (not (pos-visible-in-window-p (point-max))) (ignore-errors (scroll-up 1)) (message "End of buffer") @@ -6200,8 +6790,8 @@ options as CDR." ;; Toggle options: C-b (2) C-v (22) C-s (19) C-f (6) C-a (1). ((memq key '(2 22 19 6 1)) (org-export--dispatch-ui - (let ((option (case key (2 'body) (22 'visible) (19 'subtree) - (6 'force) (1 'async)))) + (let ((option (cl-case key (2 'body) (22 'visible) (19 'subtree) + (6 'force) (1 'async)))) (if (memq option options) (remq option options) (cons option options))) first-key expertp)) @@ -6213,7 +6803,7 @@ options as CDR." ;; Publishing actions are hard-coded. Send a special ;; signal to `org-export-dispatch'. ((eq first-key ?P) - (case key + (cl-case key (?f 'publish-current-file) (?p 'publish-current-project) (?x 'publish-choose-project) @@ -6222,10 +6812,9 @@ options as CDR." ;; path. Indeed, derived backends can share the same ;; FIRST-KEY. (t (catch 'found - (mapc (lambda (entry) - (let ((match (assq key (nth 2 entry)))) - (when match (throw 'found (nth 2 match))))) - (member (assq first-key entries) entries))))) + (dolist (entry (member (assq first-key entries) entries)) + (let ((match (assq key (nth 2 entry)))) + (when match (throw 'found (nth 2 match)))))))) options)) ;; Otherwise, enter sub-menu. (t (org-export--dispatch-ui options key expertp))))) diff --git a/lisp/outline.el b/lisp/outline.el index 9ace6044e0d..fe1df766cb9 100644 --- a/lisp/outline.el +++ b/lisp/outline.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/paren.el b/lisp/paren.el index a4d9200c42f..190922ac8d1 100644 --- a/lisp/paren.el +++ b/lisp/paren.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -60,7 +60,7 @@ active, you must toggle the mode off and on again for this to take effect." (defcustom show-paren-priority 1000 "Priority of paren highlighting overlays." - :type 'integer + :type 'integer :version "21.1") (defcustom show-paren-ring-bell-on-mismatch nil @@ -247,13 +247,21 @@ It is the default value of `show-paren-data-function'." (there-beg (nth 2 data)) (there-end (nth 3 data)) (mismatch (nth 4 data)) + (highlight-expression + (or (eq show-paren-style 'expression) + (and there-beg + (eq show-paren-style 'mixed) + (let ((closest (if (< there-beg here-beg) + (1- there-end) (1+ there-beg)))) + (not (pos-visible-in-window-p closest)))))) (face - (if mismatch - (progn - (if show-paren-ring-bell-on-mismatch - (beep)) - 'show-paren-mismatch) - 'show-paren-match))) + (cond + (mismatch + (if show-paren-ring-bell-on-mismatch + (beep)) + 'show-paren-mismatch) + (highlight-expression 'show-paren-match-expression) + (t 'show-paren-match)))) ;; ;; If matching backwards, highlight the closeparen ;; before point as well as its matching open. @@ -276,11 +284,7 @@ It is the default value of `show-paren-data-function'." ;; If it's an unmatched paren, turn off any such highlighting. (if (not there-beg) (delete-overlay show-paren--overlay) - (if (or (eq show-paren-style 'expression) - (and (eq show-paren-style 'mixed) - (let ((closest (if (< there-beg here-beg) - (1- there-end) (1+ there-beg)))) - (not (pos-visible-in-window-p closest))))) + (if highlight-expression (move-overlay show-paren--overlay (if (< there-beg here-beg) here-end here-beg) (if (< there-beg here-beg) there-beg there-end) diff --git a/lisp/password-cache.el b/lisp/password-cache.el index 7be3c6fdb6f..18f30a82ffb 100644 --- a/lisp/password-cache.el +++ b/lisp/password-cache.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -66,7 +66,7 @@ Whether passwords are cached at all is controlled by `password-cache'." :type '(choice (const :tag "Never" nil) (integer :tag "Seconds"))) -(defvar password-data (make-vector 7 0)) +(defvar password-data (make-hash-table :test #'equal)) (defun password-read-from-cache (key) "Obtain passphrase for KEY from time-limited passphrase cache. @@ -74,20 +74,20 @@ Custom variables `password-cache' and `password-cache-expiry' regulate cache behavior." (and password-cache key - (symbol-value (intern-soft key password-data)))) + (gethash key password-data))) ;;;###autoload (defun password-in-cache-p (key) "Check if KEY is in the cache." (and password-cache key - (intern-soft key password-data))) + (gethash key password-data))) (defun password-read (prompt &optional key) "Read password, for use with KEY, from user, or from cache if wanted. KEY indicate the purpose of the password, so the cache can -separate passwords. The cache is not used if KEY is nil. It is -typically a string. +separate passwords. The cache is not used if KEY is nil. +KEY is typically a string but can be anything (compared via `equal'). The variable `password-cache' control whether the cache is used." (or (password-read-from-cache key) (read-passwd prompt))) @@ -115,29 +115,27 @@ but can be invoked at any time to forcefully remove passwords from the cache. This may be useful when it has been detected that a password is invalid, so that `password-read' query the user again." - (let ((sym (intern-soft key password-data))) - (when sym - (let ((password (symbol-value sym))) - (when (stringp password) - (if (fboundp 'clear-string) - (clear-string password) - (fillarray password ?_))) - (unintern key password-data))))) + (let ((password (gethash key password-data))) + (when (stringp password) + (if (fboundp 'clear-string) + (clear-string password) + (fillarray password ?_))) + (remhash key password-data))) (defun password-cache-add (key password) "Add password to cache. The password is removed by a timer after `password-cache-expiry' seconds." - (when (and password-cache-expiry (null (intern-soft key password-data))) + (when (and password-cache-expiry (null (gethash key password-data))) (run-at-time password-cache-expiry nil #'password-cache-remove key)) - (set (intern key password-data) password) + (puthash key password password-data) nil) (defun password-reset () "Clear the password cache." (interactive) - (fillarray password-data 0)) + (clrhash password-data)) (provide 'password-cache) diff --git a/lisp/pcmpl-cvs.el b/lisp/pcmpl-cvs.el index c03be64cf58..6ab962f5f08 100644 --- a/lisp/pcmpl-cvs.el +++ b/lisp/pcmpl-cvs.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/pcmpl-gnu.el b/lisp/pcmpl-gnu.el index 73a0fe507f9..78cc0018307 100644 --- a/lisp/pcmpl-gnu.el +++ b/lisp/pcmpl-gnu.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/pcmpl-linux.el b/lisp/pcmpl-linux.el index 84fb4b9e118..0e27489c91b 100644 --- a/lisp/pcmpl-linux.el +++ b/lisp/pcmpl-linux.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/pcmpl-rpm.el b/lisp/pcmpl-rpm.el index ce5f053aa30..c2083c889c2 100644 --- a/lisp/pcmpl-rpm.el +++ b/lisp/pcmpl-rpm.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/pcmpl-unix.el b/lisp/pcmpl-unix.el index 9bcce8b8855..41968bfe888 100644 --- a/lisp/pcmpl-unix.el +++ b/lisp/pcmpl-unix.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/pcmpl-x.el b/lisp/pcmpl-x.el index 7aeff54b210..1dde3245d8f 100644 --- a/lisp/pcmpl-x.el +++ b/lisp/pcmpl-x.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: diff --git a/lisp/pcomplete.el b/lisp/pcomplete.el index 6e45f3898f7..2d2a8773bfe 100644 --- a/lisp/pcomplete.el +++ b/lisp/pcomplete.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -772,7 +772,7 @@ this is `comint-dynamic-complete-functions'." (setq c (cdr c))) (setq pcomplete-stub (substring common-stub 0 len) pcomplete-autolist t) - (when (and begin (not pcomplete-show-list)) + (when (and begin (> len 0) (not pcomplete-show-list)) (delete-region begin (point)) (pcomplete-insert-entry "" pcomplete-stub)) (throw 'pcomplete-completions completions)) diff --git a/lisp/pixel-scroll.el b/lisp/pixel-scroll.el index 18c0bc85073..f64a4392b49 100644 --- a/lisp/pixel-scroll.el +++ b/lisp/pixel-scroll.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; Usage: ;; @@ -74,10 +74,13 @@ More wait will result in slow and gentle scroll.") (defvar pixel-resolution-fine-flag nil - "Set scrolling resolution to a pixel instead of a line. -After a pixel scroll, typing C-n or C-p scrolls the window to -make it fully visible, and undoes the effect of the pixel-level -scroll.") + "Set scrolling resolution to pixels instead of a line. +When it is t, scrolling resolution is number of pixels obtained +by `frame-char-height' instead of a line. When it is number, +scrolling resolution is set to number of pixels specified. In +case you need scrolling resolution of a pixel, set to 1. After a +pixel scroll, typing \\[next-line] or \\[previous-line] scrolls the window to make it +fully visible, and undoes the effect of the pixel-level scroll.") ;;;###autoload (define-minor-mode pixel-scroll-mode @@ -102,13 +105,16 @@ This is an alternative of `scroll-up'. Scope moves downward." (interactive) (or arg (setq arg 1)) (dotimes (ii arg) ; move scope downward - (if (pixel-eob-at-top-p) ; when end-of-the-buffer is close - (scroll-up 1) ; relay on robust method - (when (pixel-point-at-top-p) ; prevent too late - (vertical-motion 1)) ; move point downward - (pixel-scroll-pixel-up (if pixel-resolution-fine-flag - 1 - (pixel-line-height)))))) ; move scope downward + (let ((amt (if pixel-resolution-fine-flag + (if (integerp pixel-resolution-fine-flag) + pixel-resolution-fine-flag + (frame-char-height)) + (pixel-line-height)))) + (while (pixel-point-at-top-p amt) ; prevent too late (multi tries) + (vertical-motion 1)) ; move point downward + (if (pixel-eob-at-top-p) ; when end-of-the-buffer is close + (scroll-up 1) ; relay on robust method + (pixel-scroll-pixel-up amt))))) ; move scope downward (defun pixel-scroll-down (&optional arg) "Scroll text of selected window down ARG lines. @@ -116,48 +122,63 @@ This is and alternative of `scroll-down'. Scope moves upward." (interactive) (or arg (setq arg 1)) (dotimes (ii arg) - (if (or (pixel-bob-at-top-p) ; when beginning-of-the-buffer is seen - (pixel-eob-at-top-p)) ; for file with a long line - (scroll-down 1) ; relay on robust method - (while (pixel-point-at-bottom-p) ; prevent too late (multi tries) - (vertical-motion -1)) - (pixel-scroll-pixel-down (if pixel-resolution-fine-flag - 1 - (pixel-line-height -1)))))) - -(defun pixel-bob-at-top-p () - "Return non-nil if beginning of buffer is at top of window." - (equal (window-start) (point-min))) + (let ((amt (if pixel-resolution-fine-flag + (if (integerp pixel-resolution-fine-flag) + pixel-resolution-fine-flag + (frame-char-height)) + (pixel-line-height -1)))) + (while (pixel-point-at-bottom-p amt) ; prevent too late (multi tries) + (vertical-motion -1)) ; move point upward + (if (or (pixel-bob-at-top-p amt) ; when beginning-of-the-buffer is seen + (pixel-eob-at-top-p)) ; for file with a long line + (scroll-down 1) ; relay on robust method + (pixel-scroll-pixel-down amt))))) + +(defun pixel-bob-at-top-p (amt) + "Return non-nil if window-start is at beginning of the current buffer. +Window must be vertically scrolled by not more than AMT pixels." + (and (equal (window-start) (point-min)) + (< (window-vscroll nil t) amt))) (defun pixel-eob-at-top-p () "Return non-nil if end of buffer is at top of window." (<= (count-lines (window-start) (window-end)) 2)) ; count-screen-lines (defun pixel-posn-y-at-point () - "Return y coordinates of point in pixels of current window." - (let ((hscroll0 (window-hscroll)) - (y (cdr (posn-x-y (posn-at-point))))) - ;; when point is out of scope by hscroll - (unless y - (save-excursion - (set-window-hscroll nil (current-column)) - (setq y (cdr (posn-x-y (posn-at-point)))) - (set-window-hscroll nil hscroll0))) - y)) - -(defun pixel-point-at-top-p () - "Return if point is located at top of a window." - (let* ((y (pixel-posn-y-at-point)) - (top-margin y)) - (< top-margin (pixel-line-height)))) - -(defun pixel-point-at-bottom-p () - "Return if point is located at bottom of a window." - (let* ((y (pixel-posn-y-at-point)) - (edges (window-inside-pixel-edges)) + "Return y coordinates of point in pixels of current window. +This returns nil when horizontally scrolled." + (when (equal (window-hscroll) 0) + (save-excursion + ;; When there's an overlay string on a line, move + ;; point by (beginning-of-visual-line). + (beginning-of-visual-line) + ;; (- (cadr (pos-visible-in-window-p (point) nil t)) + ;; (line-pixel-height)) + (cdr (posn-x-y (posn-at-point)))))) + +(defun pixel-point-at-top-p (amt) + "Return if point is located at top of a window on coming scroll of AMT pixels. +When location of point was not obtained, this returns if point is at top +of window." + (let ((y (pixel-posn-y-at-point)) + top-margin) + (cond + (y + (setq top-margin y) + (< top-margin amt)) + (t + (<= (count-lines (window-start) (point)) 1))))) + +(defun pixel-point-at-bottom-p (amt) + "Return if point is located at bottom of window on coming scroll of AMT pixels. +When location of point was not obtained, this returns nil." + (let* ((edges (window-inside-pixel-edges)) (height (- (nth 3 edges) (nth 1 edges))) ; (- bottom top) - (bottom-margin (- height (+ y (line-pixel-height))))) ; bottom margin - (< bottom-margin (pixel-line-height -1)))) ; coming unseen line + (y (pixel-posn-y-at-point)) + bottom-margin) + (when y + (setq bottom-margin (- height (+ y (pixel-visual-line-height)))) + (< bottom-margin amt)))) ; coming unseen line (defun pixel-scroll-pixel-up (amt) "Scroll text of selected windows up AMT pixels. @@ -173,8 +194,12 @@ Scope moves upward." (while (> amt 0) (let ((vs (window-vscroll nil t))) (if (equal vs 0) - (pixel-scroll-down-and-set-window-vscroll - (1- (pixel-line-height -1))) + (progn + ;; On horizontal scrolling, move cursor. + (when (> (window-hscroll) 0) + (vertical-motion -1)) + (pixel-scroll-down-and-set-window-vscroll + (1- (pixel-line-height -1)))) (set-window-vscroll nil (1- vs) t)) (setq amt (1- amt)) (sit-for pixel-wait)))) @@ -189,11 +214,16 @@ Scope moves downward. This function returns number of pixels that was scrolled." (let* ((src (window-vscroll nil t)) ; EXAMPLE (initial) @0 @8 @88 (height (pixel-line-height)) ; 25 25 23 - (line (1+ (/ src height))) ; catch up + one line Ä1 Ä1 Ä4 + (line (1+ (/ src height))) ; catch up + one line 1 1 4 (dst (* line height)) ; goal @25 @25 @92 (delta (- dst src))) ; pixels to be scrolled 25 17 4 (pixel--whistlestop-pixel-up (1- delta)) ; until one less @24 @24 @91 - (scroll-up line) (sit-for pixel-wait) ; scroll 1 pixel @0 @0 @0 + (dotimes (ii line) + ;; On horizontal scrolling, move cursor. + (when (> (window-hscroll) 0) + (vertical-motion 1)) + (scroll-up 1)) + (sit-for pixel-wait) ; scroll 1 pixel @0 @0 @0 delta)) (defun pixel--whistlestop-pixel-up (n) @@ -211,9 +241,61 @@ unseen line above the first line, respectively, is provided." (or pos (setq pos (window-start))) (when (< pos 0) (setq pos (pixel-point-at-unseen-line))) - (save-excursion - (goto-char pos) - (line-pixel-height))) ; frame-char-height + (let ((vs1 (window-vscroll nil t)) + height) + (set-window-vscroll nil 0 t) + (save-excursion + (goto-char pos) + (setq height (pixel-visual-line-height))) ; line-pixel-height, frame-char-height + (set-window-vscroll nil vs1 t) + height)) + +(defun pixel-visual-line-height () + "Return height in pixels of text line where cursor is in the selected window." + (let ((pos (pixel-visible-pos-in-window))) + (cond + ;; When a char of line is shown, obtain height by + ;; (line-pixel-height). + (pos (save-excursion (goto-char pos) (line-pixel-height))) + ;; When no char of line is shown but the line is at the top, + ;; obtain height by (line-pixel-height). This is based on + ;; expected response from display engine. See following + ;; discussion. + ;; https://lists.gnu.org/r/emacs-devel/2017-10/msg00621.html + ((equal (count-lines (window-start) (point)) 1) + (line-pixel-height)) + ;; No char of line is shown and the line is not at the top, + ;; obtain height by (frame-char-height). + (t (frame-char-height))))) + +(defun pixel-visible-pos-in-window () + "Return position shown on text line where cursor is in the selected window. +This will look for positions of point and end-of-visual-line, +then positions from beginning-of-visual-line to +end-of-visual-line. When no char in a line is shown, this +returns nil." + (let* ((beginning-of-visual-line-pos (save-excursion (beginning-of-visual-line) (point))) + (end-of-visual-line-pos (save-excursion (end-of-visual-line) (point))) + (pos-list (number-sequence beginning-of-visual-line-pos end-of-visual-line-pos)) + (edges (window-inside-pixel-edges)) + (width (- (nth 2 edges) (nth 0 edges))) + posn-x + visible-pos) + ;; Optimize list of position to be surveyed. + (push end-of-visual-line-pos pos-list) + (push (point) pos-list) + (delete-dups pos-list) + ;; Find out a char with position X that is more than zero and less + ;; than width of screen. + (while (and (not visible-pos) + pos-list) + (setq posn-x (car (pos-visible-in-window-p (car pos-list) nil t))) + (if (and posn-x + (<= 0 posn-x) + (< posn-x width)) + (setq visible-pos (car pos-list)) + (setq pos-list (cdr pos-list)))) + visible-pos)) (defun pixel-point-at-unseen-line () "Return the character position of line above the selected window. diff --git a/lisp/play/5x5.el b/lisp/play/5x5.el index 312764b2f4a..dad2048ac83 100644 --- a/lisp/play/5x5.el +++ b/lisp/play/5x5.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/play/animate.el b/lisp/play/animate.el index d074a741b69..80bb746133f 100644 --- a/lisp/play/animate.el +++ b/lisp/play/animate.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/play/blackbox.el b/lisp/play/blackbox.el index d935b02e7f8..e25978cdf5d 100644 --- a/lisp/play/blackbox.el +++ b/lisp/play/blackbox.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/play/bubbles.el b/lisp/play/bubbles.el index 4c9754a689b..35abbc8bb2a 100644 --- a/lisp/play/bubbles.el +++ b/lisp/play/bubbles.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/play/cookie1.el b/lisp/play/cookie1.el index 88627d694f6..b9605dcf9e0 100644 --- a/lisp/play/cookie1.el +++ b/lisp/play/cookie1.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/play/decipher.el b/lisp/play/decipher.el index 61a63bd28dd..f68e78d160a 100644 --- a/lisp/play/decipher.el +++ b/lisp/play/decipher.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/play/dissociate.el b/lisp/play/dissociate.el index 6bd7f694050..23d78478c53 100644 --- a/lisp/play/dissociate.el +++ b/lisp/play/dissociate.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/play/doctor.el b/lisp/play/doctor.el index f90e1d044b5..e1c4d2acd73 100644 --- a/lisp/play/doctor.el +++ b/lisp/play/doctor.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/play/dunnet.el b/lisp/play/dunnet.el index 6acdf36d72c..ed1cd5e730a 100644 --- a/lisp/play/dunnet.el +++ b/lisp/play/dunnet.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/play/fortune.el b/lisp/play/fortune.el index 7b60465788a..0b83b62b292 100644 --- a/lisp/play/fortune.el +++ b/lisp/play/fortune.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; This utility allows you to automatically cut regions to a fortune diff --git a/lisp/play/gamegrid.el b/lisp/play/gamegrid.el index 0386a89b3a4..6223a01d4fa 100644 --- a/lisp/play/gamegrid.el +++ b/lisp/play/gamegrid.el @@ -1,4 +1,4 @@ -;;; gamegrid.el --- library for implementing grid-based games on Emacs +;;; gamegrid.el --- library for implementing grid-based games on Emacs -*- lexical-binding:t -*- ;; Copyright (C) 1997-1998, 2001-2017 Free Software Foundation, Inc. @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -86,49 +86,157 @@ directory will be used.") (defvar gamegrid-mono-x-face nil) (defvar gamegrid-mono-tty-face nil) -;; ;;;;;;;;;;;;; constants ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defvar gamegrid-glyph-height-mm 7.0 + "Desired glyph height in mm.") -(defconst gamegrid-glyph-height 16) +;; ;;;;;;;;;;;;; glyph generation ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defconst gamegrid-xpm "\ +(defun gamegrid-calculate-glyph-size () + "Calculate appropriate glyph size in pixels based on display resolution. +Return a multiple of 8 no less than 16." + (if (and (display-pixel-height) (display-mm-height)) + (let* ((y-pitch (/ (display-pixel-height) (float (display-mm-height)))) + (pixels (* y-pitch gamegrid-glyph-height-mm)) + (rounded (* (floor (/ (+ pixels 4) 8)) 8))) + (max 16 rounded)) + 16)) + +;; Example of glyph in XPM format: +;; +;; /* XPM */ +;; static char *noname[] = { +;; /* width height ncolors chars_per_pixel */ +;; \"16 16 3 1\", +;; /* colors */ +;; \"+ s col1\", +;; \". s col2\", +;; \"- s col3\", +;; /* pixels */ +;; \"---------------+\", +;; \"--------------++\", +;; \"--............++\", +;; \"--............++\", +;; \"--............++\", +;; \"--............++\", +;; \"--............++\", +;; \"--............++\", +;; \"--............++\", +;; \"--............++\", +;; \"--............++\", +;; \"--............++\", +;; \"--............++\", +;; \"--............++\", +;; \"-+++++++++++++++\", +;; \"++++++++++++++++\" +;; }; + +(defun gamegrid-xpm () + "Generate the XPM format image used for each square." + (let* ((glyph-pixel-count (gamegrid-calculate-glyph-size)) + (border-pixel-count (/ glyph-pixel-count 8)) + (center-pixel-count (- glyph-pixel-count (* border-pixel-count 2)))) + (with-temp-buffer + (insert (format "\ /* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ -\"16 16 3 1\", +\"%s %s 3 1\", /* colors */ \"+ s col1\", \". s col2\", \"- s col3\", /* pixels */ -\"---------------+\", -\"--------------++\", -\"--............++\", -\"--............++\", -\"--............++\", -\"--............++\", -\"--............++\", -\"--............++\", -\"--............++\", -\"--............++\", -\"--............++\", -\"--............++\", -\"--............++\", -\"--............++\", -\"-+++++++++++++++\", -\"++++++++++++++++\" -}; -" - "XPM format image used for each square") - -(defvar gamegrid-xbm "\ +" glyph-pixel-count glyph-pixel-count)) + + (dotimes (row border-pixel-count) + (let ((edge-pixel-count (+ row 1))) + (insert "\"") + (dotimes (_ (- glyph-pixel-count edge-pixel-count)) (insert "-")) + (dotimes (_ edge-pixel-count) (insert "+")) + (insert "\",\n"))) + + (let ((middle (format "\"%s%s%s\",\n" + (make-string border-pixel-count ?-) + (make-string center-pixel-count ?.) + (make-string border-pixel-count ?+)))) + (dotimes (_ center-pixel-count) (insert middle))) + + (dotimes (row border-pixel-count) + (let ((edge-pixel-count (- border-pixel-count row 1))) + (insert "\"") + (dotimes (_ edge-pixel-count) (insert "-")) + (dotimes (_ (- glyph-pixel-count edge-pixel-count)) (insert "+")) + (insert "\"") + (if (/= row (1- border-pixel-count)) + (insert ",\n") + (insert "\n};\n")))) + (buffer-string)))) + +;; Example of glyph in XBM format: +;; +;; /* gamegrid XBM */ +;; #define gamegrid_width 16 +;; #define gamegrid_height 16 +;; static unsigned char gamegrid_bits[] = { +;; 0xff, 0xff, 0xff, 0x7f, 0xff, 0x3f, 0xaf, 0x0a, 0x57, 0x15, 0xaf, 0x0a, +;; 0x57, 0x15, 0xaf, 0x0a, 0x57, 0x15, 0xaf, 0x0a, 0x57, 0x15, 0xaf, 0x0a, +;; 0x57, 0x15, 0x07, 0x00, 0x03, 0x00, 0x01, 0x00 }; + +(defun gamegrid-xbm () + "Generate XBM format image used for each square." + (let* ((glyph-pixel-count (gamegrid-calculate-glyph-size)) + (border-pixel-count (1- (/ glyph-pixel-count 4))) + (center-pixel-count (- glyph-pixel-count (* 2 border-pixel-count)))) + (with-temp-buffer + (insert (format "\ /* gamegrid XBM */ -#define gamegrid_width 16 -#define gamegrid_height 16 +#define gamegrid_width %s +#define gamegrid_height %s static unsigned char gamegrid_bits[] = { - 0xff, 0xff, 0xff, 0x7f, 0xff, 0x3f, 0xaf, 0x0a, 0x57, 0x15, 0xaf, 0x0a, - 0x57, 0x15, 0xaf, 0x0a, 0x57, 0x15, 0xaf, 0x0a, 0x57, 0x15, 0xaf, 0x0a, - 0x57, 0x15, 0x07, 0x00, 0x03, 0x00, 0x01, 0x00 };" - "XBM format image used for each square.") +" glyph-pixel-count glyph-pixel-count)) + (dotimes (row border-pixel-count) + (gamegrid-insert-xbm-bits + (concat (make-string (- glyph-pixel-count row) ?1) + (make-string row ?0))) + (insert ", \n")) + + (let* ((left-border (make-string border-pixel-count ?1)) + (right-border (make-string border-pixel-count ?0)) + (even-line (apply 'concat + (append (list left-border) + (make-list (/ center-pixel-count 2) "10") + (list right-border)))) + (odd-line (apply 'concat + (append (list left-border) + (make-list (/ center-pixel-count 2) "01") + (list right-border))))) + (dotimes (row center-pixel-count) + (gamegrid-insert-xbm-bits (if (eq (logand row 1) 1) odd-line even-line)) + (insert ", \n"))) + + (dotimes (row border-pixel-count) + (let ((edge-pixel-count (- border-pixel-count row))) + (gamegrid-insert-xbm-bits + (concat (make-string edge-pixel-count ?1) + (make-string (- glyph-pixel-count edge-pixel-count) ?0)))) + (if (/= row (1- border-pixel-count)) + (insert ", \n") + (insert " };\n"))) + (buffer-string)))) + +(defun gamegrid-insert-xbm-bits (str) + "Convert binary to hex and insert in current buffer. +STR should be a string composed of 1s and 0s and be a multiple of +8 in length. Divide it into 8 bit bytes, reverse the order of +each, convert them to hex and insert them in comma separated C +format." + (let ((byte-count (/ (length str) 8))) + (dotimes (i byte-count) + (let* ((byte (reverse (substring str (* i 8) (+ (* i 8) 8)))) + (value (string-to-number byte 2))) + (insert (format "0x%02x" value)) + (unless (= i (1- byte-count)) + (insert ", ")))))) ;; ;;;;;;;;;;;;;;;; miscellaneous functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -228,13 +336,13 @@ static unsigned char gamegrid_bits[] = { gamegrid-mono-tty-face)))) (defun gamegrid-colorize-glyph (color) - (find-image `((:type xpm :data ,gamegrid-xpm + (find-image `((:type xpm :data ,(gamegrid-xpm) :ascent center :color-symbols (("col1" . ,(gamegrid-color color 0.6)) ("col2" . ,(gamegrid-color color 0.8)) ("col3" . ,(gamegrid-color color 1.0)))) - (:type xbm :data ,gamegrid-xbm + (:type xbm :data ,(gamegrid-xbm) :ascent center :foreground ,(gamegrid-color color 1.0) :background ,(gamegrid-color color 0.5))))) @@ -376,7 +484,7 @@ static unsigned char gamegrid_bits[] = { (buffer-read-only nil)) (erase-buffer) (setq gamegrid-buffer-start (point)) - (dotimes (i height) + (dotimes (_ height) (insert line)) ;; Adjust the height of the default face to the height of the ;; images. Unlike XEmacs, Emacs doesn't allow making the default diff --git a/lisp/play/gametree.el b/lisp/play/gametree.el index 3954c1dc1fa..944205209cc 100644 --- a/lisp/play/gametree.el +++ b/lisp/play/gametree.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/play/gomoku.el b/lisp/play/gomoku.el index a2d3447dedb..2f5f36e1dbb 100644 --- a/lisp/play/gomoku.el +++ b/lisp/play/gomoku.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -263,7 +263,7 @@ Other useful commands:\n "Vector recording the actual score of the free squares.") -;; The key point point about the algorithm is that, rather than considering +;; The key point about the algorithm is that, rather than considering ;; the board as just a set of squares, we prefer to see it as a "space" of ;; internested 5-tuples of contiguous squares (called qtuples). ;; diff --git a/lisp/play/handwrite.el b/lisp/play/handwrite.el index 06b37beb555..0b572d12be6 100644 --- a/lisp/play/handwrite.el +++ b/lisp/play/handwrite.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/play/life.el b/lisp/play/life.el index c5907a9875d..a5a3f1ef054 100644 --- a/lisp/play/life.el +++ b/lisp/play/life.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/play/morse.el b/lisp/play/morse.el index 85d9db086ff..d55e0a4c9f6 100644 --- a/lisp/play/morse.el +++ b/lisp/play/morse.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/play/mpuz.el b/lisp/play/mpuz.el index 815203032ff..5fc4f2d4b11 100644 --- a/lisp/play/mpuz.el +++ b/lisp/play/mpuz.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/play/pong.el b/lisp/play/pong.el index fb826fb65ed..c5af6f15e99 100644 --- a/lisp/play/pong.el +++ b/lisp/play/pong.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/play/snake.el b/lisp/play/snake.el index d5904a48f42..d6a21418ecd 100644 --- a/lisp/play/snake.el +++ b/lisp/play/snake.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/play/solitaire.el b/lisp/play/solitaire.el index 850b80566b8..f1aa046cc10 100644 --- a/lisp/play/solitaire.el +++ b/lisp/play/solitaire.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/play/spook.el b/lisp/play/spook.el index e6727725d69..fd2e8116c82 100644 --- a/lisp/play/spook.el +++ b/lisp/play/spook.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/play/tetris.el b/lisp/play/tetris.el index d20ac0ab3a2..2b06d8f3ad1 100644 --- a/lisp/play/tetris.el +++ b/lisp/play/tetris.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/play/zone.el b/lisp/play/zone.el index a718d07caca..254b76ca27a 100644 --- a/lisp/play/zone.el +++ b/lisp/play/zone.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/plstore.el b/lisp/plstore.el index b9025433b11..da260096eaf 100644 --- a/lisp/plstore.el +++ b/lisp/plstore.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary @@ -212,7 +212,8 @@ symmetric encryption will be used." (with-current-buffer buffer (erase-buffer) (condition-case nil - (insert-file-contents-literally file) + (let ((coding-system-for-read 'raw-text)) + (insert-file-contents file)) (error)) (setq buffer-file-name (file-truename file)) (set-buffer-modified-p nil) @@ -520,7 +521,7 @@ If no one is selected, symmetric encryption will be performed. " t))) (defun plstore-mode-original () - "Show the original form of the this buffer." + "Show the original form of this buffer." (interactive) (when plstore-encoded (if (and (buffer-modified-p) @@ -532,7 +533,7 @@ If no one is selected, symmetric encryption will be performed. " (setq plstore-encoded nil))) (defun plstore-mode-decoded () - "Show the decoded form of the this buffer." + "Show the decoded form of this buffer." (interactive) (unless plstore-encoded (if (and (buffer-modified-p) diff --git a/lisp/printing.el b/lisp/printing.el index 9970b85a8ee..acfea5e9887 100644 --- a/lisp/printing.el +++ b/lisp/printing.el @@ -2,8 +2,8 @@ ;; Copyright (C) 2000-2001, 2003-2017 Free Software Foundation, Inc. -;; Author: Vinicius Jose Latorre <viniciusjl@ig.com.br> -;; Maintainer: Vinicius Jose Latorre <viniciusjl@ig.com.br> +;; Author: Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> +;; Maintainer: Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> ;; Keywords: wp, print, PostScript ;; Version: 6.9.3 ;; X-URL: http://www.emacswiki.org/cgi-bin/wiki/ViniciusJoseLatorre @@ -12,7 +12,7 @@ "printing.el, v 6.9.3 <2007/12/09 vinicius> Please send all bug fixes and enhancements to - bug-gnu-emacs@gnu.org and Vinicius Jose Latorre <viniciusjl@ig.com.br> + bug-gnu-emacs@gnu.org and Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> ") ;; This file is part of GNU Emacs. @@ -28,7 +28,7 @@ Please send all bug fixes and enhancements to ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -58,7 +58,7 @@ Please send all bug fixes and enhancements to ;; On GNU or Unix system, `printing' depends on gs and gv utilities. ;; On NT system, `printing' depends on gstools (gswin32.exe and gsview32.exe). ;; To obtain ghostscript, ghostview and GSview see the URL -;; `http://www.gnu.org/software/ghostscript/ghostscript.html'. +;; `https://www.gnu.org/software/ghostscript/ghostscript.html'. ;; ;; `printing' depends on ps-print package to generate PostScript files, to ;; spool and to despool PostScript buffer. So, `printing' provides an @@ -958,7 +958,7 @@ Please send all bug fixes and enhancements to ;; ;; * For GNU or Unix system: ;; -;; gs, gv `http://www.gnu.org/software/ghostscript/ghostscript.html' +;; gs, gv `https://www.gnu.org/software/ghostscript/ghostscript.html' ;; enscript `http://people.ssh.fi/mtr/genscript/' ;; psnup `http://www.knackered.org/angus/psutils/' ;; mpage `http://www.mesa.nl/pub/mpage/' @@ -966,7 +966,7 @@ Please send all bug fixes and enhancements to ;; * For Windows system: ;; ;; gswin32, gsview32 -;; `http://www.gnu.org/software/ghostscript/ghostscript.html' +;; `https://www.gnu.org/software/ghostscript/ghostscript.html' ;; gsprint `http://www.cs.wisc.edu/~ghost/gsview/gsprint.htm'. ;; enscript `http://people.ssh.fi/mtr/genscript/' ;; psnup `http://gnuwin32.sourceforge.net/packages/psutils.htm' @@ -2249,7 +2249,7 @@ See also `pr-path-alist'. Useful links: * GNU gv manual - `http://www.gnu.org/software/gv/manual/gv.html' + `https://www.gnu.org/software/gv/manual/gv.html' * GSview Help `http://www.cs.wisc.edu/~ghost/gsview/gsviewen.htm' diff --git a/lisp/proced.el b/lisp/proced.el index 0736ab09dc9..b4bdbb05f0d 100644 --- a/lisp/proced.el +++ b/lisp/proced.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -602,7 +602,10 @@ Important: the match ends just after the marker.") (defun proced-header-line () "Return header line for Proced buffer." - (list (propertize " " 'display '(space :align-to 0)) + (list (propertize " " + 'display + (list 'space :align-to + (line-number-display-width 'columns))) (if (<= (window-hscroll) (length proced-header-line)) (replace-regexp-in-string ;; preserve text properties "\\(%\\)" "\\1\\1" @@ -767,7 +770,7 @@ The time interval for updates is specified via `proced-auto-update-interval'." (while (not (eobp)) (cond ((looking-at mark-re) (proced-insert-mark nil)) - ((looking-at " ") + ((= (following-char) ?\s) (proced-insert-mark t)) (t (forward-line 1))))))) @@ -1436,7 +1439,7 @@ Replace newline characters by \"^J\" (two characters)." (hprops (if (nth 4 grammar) (let ((descend (if (eq key sort-key) proced-descend (nth 5 grammar)))) - `(proced-key ,key mouse-face highlight + `(proced-key ,key mouse-face header-line-highlight help-echo ,(format proced-header-help-echo (if descend "-" "+") (nth 1 grammar) @@ -1801,7 +1804,7 @@ supported but discouraged. It will be removed in a future version of Emacs." (let (failures) ;; Why not always use `signal-process'? See - ;; http://lists.gnu.org/archive/html/emacs-devel/2008-03/msg02955.html + ;; https://lists.gnu.org/r/emacs-devel/2008-03/msg02955.html (if (functionp proced-signal-function) ;; use built-in `signal-process' (let ((signal (if (stringp signal) diff --git a/lisp/profiler.el b/lisp/profiler.el index 15ff9b68ab9..0eed79eff0c 100644 --- a/lisp/profiler.el +++ b/lisp/profiler.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/progmodes/ada-mode.el b/lisp/progmodes/ada-mode.el index ab3ff3aa208..05d8038e87b 100644 --- a/lisp/progmodes/ada-mode.el +++ b/lisp/progmodes/ada-mode.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; This mode is a major mode for editing Ada code. This is a major diff --git a/lisp/progmodes/ada-prj.el b/lisp/progmodes/ada-prj.el index f1b90875044..b86982a75c8 100644 --- a/lisp/progmodes/ada-prj.el +++ b/lisp/progmodes/ada-prj.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/progmodes/ada-stmt.el b/lisp/progmodes/ada-stmt.el index 2b390688c2b..c8f70b0e4b9 100644 --- a/lisp/progmodes/ada-stmt.el +++ b/lisp/progmodes/ada-stmt.el @@ -23,7 +23,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; This file is now automatically loaded from ada-mode.el, and creates a submenu diff --git a/lisp/progmodes/ada-xref.el b/lisp/progmodes/ada-xref.el index 4e196505b6c..5f79afe01ac 100644 --- a/lisp/progmodes/ada-xref.el +++ b/lisp/progmodes/ada-xref.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/progmodes/antlr-mode.el b/lisp/progmodes/antlr-mode.el index 2d09e431f29..82ae1816270 100644 --- a/lisp/progmodes/antlr-mode.el +++ b/lisp/progmodes/antlr-mode.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -619,7 +619,7 @@ COUNT starts with 1. GEN-SEP is used to separate long variable values." '((java-mode ("%sTokenTypes.java") ("%s.java")) (c++-mode ("%sTokenTypes.hpp") ("%s.cpp" "%s.hpp"))) "Language dependent formats which specify generated files. -Each element in this list looks looks like +Each element in this list looks like (MAJOR-MODE (VOCAB-FILE-FORMAT...) (CLASS-FILE-FORMAT...)). The element whose MAJOR-MODE is equal to `antlr-language' is used to diff --git a/lisp/progmodes/asm-mode.el b/lisp/progmodes/asm-mode.el index 2a1dad69877..f6e2d78f3a7 100644 --- a/lisp/progmodes/asm-mode.el +++ b/lisp/progmodes/asm-mode.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/progmodes/autoconf.el b/lisp/progmodes/autoconf.el index 6d58faa6a66..6e591c1d657 100644 --- a/lisp/progmodes/autoconf.el +++ b/lisp/progmodes/autoconf.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/progmodes/bat-mode.el b/lisp/progmodes/bat-mode.el index 1dd2e3757ed..102c3186200 100644 --- a/lisp/progmodes/bat-mode.el +++ b/lisp/progmodes/bat-mode.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -84,11 +84,11 @@ . 'bat-label-face) ("\\_<\\(defined\\|set\\)\\_>[ \t]*\\(\\(\\sw\\|\\s_\\)+\\)" (2 font-lock-variable-name-face)) - ("%\\(\\(\\sw\\|\\s_\\)+\\)%" + ("%\\([^%~ \n]+\\)%?" (1 font-lock-variable-name-face)) - ("!\\(\\(\\sw\\|\\s_\\)+\\)!" ; delayed-expansion !variable! + ("!\\([^!%~ \n]+\\)!?" ; delayed-expansion !variable! (1 font-lock-variable-name-face)) - ("%%\\(?:~[adfnpstxz]*\\(?:\\$\\(\\(?:\\sw\\|\\s_\\)+\\):\\)?\\)?\\([]!#$&-:?-[_-{}~]\\)" + ("%%\\(?:~[adfnpstxz]*\\(?:\\$\\(\\(?:\\sw\\|\\s_\\|_\\)+\\):\\)?\\)?\\([]!#$&-:?-[_-{}~]\\)" (1 font-lock-variable-name-face nil t) ; PATH expansion (2 font-lock-variable-name-face)) ; iteration variable or positional parameter ("[ =][-/]+\\(\\w+\\)" diff --git a/lisp/progmodes/bug-reference.el b/lisp/progmodes/bug-reference.el index 210f0356084..7e004ce6a01 100644 --- a/lisp/progmodes/bug-reference.el +++ b/lisp/progmodes/bug-reference.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -44,7 +44,7 @@ map) "Keymap used by bug reference buttons.") -;; E.g., "http://gcc.gnu.org/PR%s" +;; E.g., "https://gcc.gnu.org/PR%s" (defvar bug-reference-url-format nil "Format used to turn a bug number into a URL. The bug number is supplied as a string, so this should have a single %s. @@ -73,10 +73,12 @@ so that it is considered safe, see `enable-local-variables'.") "Regular expression matching bug references. The second subexpression should match the bug reference (usually a number)." :type 'string - :safe 'stringp :version "24.3" ; previously defconst :group 'bug-reference) +;;;###autoload +(put 'bug-reference-bug-regexp 'safe-local-variable 'stringp) + (defun bug-reference-set-overlay-properties () "Set properties of bug reference overlays." (put 'bug-reference 'evaporate t) diff --git a/lisp/progmodes/cc-align.el b/lisp/progmodes/cc-align.el index 0f7e4b598dc..4b326026b80 100644 --- a/lisp/progmodes/cc-align.el +++ b/lisp/progmodes/cc-align.el @@ -26,7 +26,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -159,7 +159,7 @@ Works with: topmost-intro-cont." (c-safe-position (or containing-sexp (point)) c-state-cache) containing-sexp)))) -(defun c-lineup-arglist (langelem) +(defun c-lineup-arglist (_langelem) "Line up the current argument line under the first argument. As a special case, if the indented line is inside a brace block @@ -265,7 +265,7 @@ Works with: arglist-cont, arglist-cont-nonempty." (c-forward-syntactic-ws)) (c-lineup-argcont-scan other-match))))) -(defun c-lineup-arglist-intro-after-paren (langelem) +(defun c-lineup-arglist-intro-after-paren (_langelem) "Line up a line to just after the open paren of the surrounding paren or brace block. @@ -483,7 +483,7 @@ Works with: func-decl-cont." (vector (+ (current-column) c-basic-offset))) c-basic-offset)))) -(defun c-indent-one-line-block (langelem) +(defun c-indent-one-line-block (_langelem) "Indent a one line block `c-basic-offset' extra. E.g.: @@ -506,7 +506,7 @@ Work with: Almost all syntactic symbols, but most useful on *-open." c-basic-offset nil)))) -(defun c-indent-multi-line-block (langelem) +(defun c-indent-multi-line-block (_langelem) "Indent a multi line block `c-basic-offset' extra. E.g.: @@ -642,7 +642,7 @@ Works with: The `c' syntactic symbol." (goto-char (c-langelem-pos langelem))))) (vector (current-column))))))) -(defun c-lineup-comment (langelem) +(defun c-lineup-comment (_langelem) "Line up a comment start according to `c-comment-only-line-offset'. If the comment is lined up with a comment starter on the previous line, that alignment is preserved. @@ -667,7 +667,7 @@ Works with: comment-intro." -1000)) ;jam it against the left side )))) -(defun c-lineup-knr-region-comment (langelem) +(defun c-lineup-knr-region-comment (_langelem) "Line up a comment in the \"K&R region\" with the declaration. That is the region between the function or class header and the beginning of the block. E.g.: @@ -836,7 +836,7 @@ arglist-cont-nonempty." (vector col)))))) -(defun c-lineup-string-cont (langelem) +(defun c-lineup-string-cont (_langelem) "Line up a continued string under the one it continues. A continued string in this sense is where a string literal follows directly after another one. E.g.: @@ -861,7 +861,7 @@ arglist-cont-nonempty." (goto-char pos) (vector (current-column))))))) -(defun c-lineup-template-args (langelem) +(defun c-lineup-template-args (_langelem) "Line up template argument lines under the first argument. To allow this function to be used in a list expression, nil is returned if there's no template argument on the first line. @@ -992,7 +992,7 @@ Works with: objc-method-args-cont." (+ curcol (- prev-col-column (current-column))) c-basic-offset))))) -(defun c-lineup-inexpr-block (langelem) +(defun c-lineup-inexpr-block (_langelem) "Line up the block for constructs that use a block inside an expression, e.g. anonymous classes in Java and lambda functions in Pike. The body is aligned with the start of the header, e.g. with the \"new\" or @@ -1020,7 +1020,7 @@ Works with: inlambda, inexpr-statement, inexpr-class." (goto-char (cdr res)) (vector (current-column)))))) -(defun c-lineup-whitesmith-in-block (langelem) +(defun c-lineup-whitesmith-in-block (_langelem) "Line up lines inside a block in Whitesmith style. It's done in a way that works both when the opening brace hangs and when it doesn't. E.g.: @@ -1084,7 +1084,7 @@ arglist-cont." (vector (+ (current-column) c-basic-offset)))) (vector 0))))) -(defun c-lineup-cpp-define (langelem) +(defun c-lineup-cpp-define (_langelem) "Line up macro continuation lines according to the indentation of the construct preceding the macro. E.g.: @@ -1231,9 +1231,9 @@ Works with: Any syntactic symbol which has an anchor position." (save-excursion (goto-char (c-langelem-pos langelem)) (vector (current-column)))) - -(defun c-lineup-dont-change (langelem) + +(defun c-lineup-dont-change (_langelem) "Do not change the indentation of the current line. Works with: Any syntactic symbol." @@ -1241,7 +1241,7 @@ Works with: Any syntactic symbol." (back-to-indentation) (vector (current-column)))) -(defun c-lineup-respect-col-0 (langelem) +(defun c-lineup-respect-col-0 (_langelem) "If the current line starts at column 0, return [0]. Otherwise return nil. This can be used for comments (in conjunction with, say, @@ -1254,7 +1254,7 @@ anchored there, but reindent other comments." nil))) -(defun c-snug-do-while (syntax pos) +(defun c-snug-do-while (syntax _pos) "Dynamically calculate brace hanginess for do-while statements. Using this function, `while' clauses that end a `do-while' block will remain on the same line as the brace that closes that block. @@ -1272,7 +1272,7 @@ ACTION associated with `block-close' syntax." '(before) '(before after))))) -(defun c-snug-1line-defun-close (syntax pos) +(defun c-snug-1line-defun-close (_syntax pos) "Determine the brace hanginess for an AWK defun-close. If the action/function being closed is a one-liner, keep it so. Otherwise put the closing brace on its own line." diff --git a/lisp/progmodes/cc-awk.el b/lisp/progmodes/cc-awk.el index 736f1de2094..488b93eb574 100644 --- a/lisp/progmodes/cc-awk.el +++ b/lisp/progmodes/cc-awk.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -592,7 +592,7 @@ ;; starts at a `while' token. (not (c-get-char-property (c-point 'eol) 'c-awk-NL-prop))) -(defun c-awk-clear-NL-props (beg end) +(defun c-awk-clear-NL-props (beg _end) ;; This function is run from before-change-hooks. It clears the ;; c-awk-NL-prop text property from beg to the end of the buffer (The END ;; parameter is ignored). This ensures that the indentation engine will @@ -847,7 +847,7 @@ ;; Just beyond logical line following the region which is about to be changed. ;; Set in c-awk-record-region-clear-NL and used in c-awk-after-change. -(defun c-awk-record-region-clear-NL (beg end) +(defun c-awk-record-region-clear-NL (_beg end) ;; This function is called exclusively from the before-change-functions hook. ;; It does two things: Finds the end of the (logical) line on which END lies, ;; and clears c-awk-NL-prop text properties from this point onwards. BEG is diff --git a/lisp/progmodes/cc-bytecomp.el b/lisp/progmodes/cc-bytecomp.el index e98b3dfa9df..d4bce32f175 100644 --- a/lisp/progmodes/cc-bytecomp.el +++ b/lisp/progmodes/cc-bytecomp.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -86,6 +86,7 @@ (defvar cc-bytecomp-environment-set nil) (defmacro cc-bytecomp-debug-msg (&rest args) + (ignore args) ;;`(message ,@args) ) @@ -252,7 +253,7 @@ perhaps a `cc-bytecomp-restore-environment' is forgotten somewhere")) (cc-bytecomp-debug-msg "cc-bytecomp-restore-environment: Done")))) -(defun cc-bytecomp-load (cc-part) +(defun cc-bytecomp-load (_cc-part) ;; A dummy function which will immediately be overwritten by the ;; following at load time. This should suppress the byte compiler ;; error that the function is "not known to be defined". diff --git a/lisp/progmodes/cc-cmds.el b/lisp/progmodes/cc-cmds.el index c05200b3898..471560e19d4 100644 --- a/lisp/progmodes/cc-cmds.el +++ b/lisp/progmodes/cc-cmds.el @@ -26,7 +26,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -51,6 +51,8 @@ ;; Indentation / Display syntax functions (defvar c-fix-backslashes t) +(defvar c-syntactic-context) + (defun c-indent-line (&optional syntax quiet ignore-point-pos) "Indent the current line according to the syntactic context, if `c-syntactic-indentation' is non-nil. Optional SYNTAX is the @@ -1635,7 +1637,6 @@ defun." (c-save-buffer-state (beginning-of-defun-function end-of-defun-function - (start (point)) (paren-state (c-parse-state)) (orig-point-min (point-min)) (orig-point-max (point-max)) lim ; Position of { which has been widened to. @@ -1759,7 +1760,6 @@ the open-parenthesis that starts a defun; see `beginning-of-defun'." (c-save-buffer-state (beginning-of-defun-function end-of-defun-function - (start (point)) (paren-state (c-parse-state)) (orig-point-min (point-min)) (orig-point-max (point-max)) lim @@ -1821,7 +1821,6 @@ the open-parenthesis that starts a defun; see `beginning-of-defun'." "Return the name of the current defun, or NIL if there isn't one. \"Defun\" here means a function, or other top level construct with a brace block." - (interactive) (c-save-buffer-state (beginning-of-defun-function end-of-defun-function where pos name-end case-fold-search) @@ -1843,19 +1842,33 @@ with a brace block." (unless (eq where 'at-header) (c-backward-to-nth-BOF-{ 1 where) (c-beginning-of-decl-1)) + (when (looking-at c-typedef-key) + (goto-char (match-end 0)) + (c-forward-syntactic-ws)) ;; Pick out the defun name, according to the type of defun. (cond ;; struct, union, enum, or similar: - ((and (looking-at c-type-prefix-key) - (progn (c-forward-token-2 2) ; over "struct foo " - (or (eq (char-after) ?\{) - (looking-at c-symbol-key)))) ; "struct foo bar ..." - (save-match-data (c-forward-token-2)) - (when (eq (char-after) ?\{) - (c-backward-token-2) - (looking-at c-symbol-key)) - (match-string-no-properties 0)) + ((save-excursion + (and + (looking-at c-type-prefix-key) + (consp (c-forward-decl-or-cast-1 (c-point 'bosws) 'top nil)) + (or (not (or (eq (char-after) ?{) + (and c-recognize-knr-p + (c-in-knr-argdecl)))) + (progn (c-backward-syntactic-ws) + (not (eq (char-before) ?\))))))) + (let ((key-pos (point))) + (c-forward-over-token-and-ws) ; over "struct ". + (cond + ((looking-at c-symbol-key) ; "struct foo { ..." + (buffer-substring-no-properties key-pos (match-end 0))) + ((eq (char-after) ?{) ; "struct { ... } foo" + (when (c-go-list-forward) + (c-forward-syntactic-ws) + (when (looking-at c-symbol-key) ; a bit bogus - there might + ; be several identifiers. + (match-string-no-properties 0))))))) ((looking-at "DEFUN\\s-*(") ;"DEFUN\\_>") think of XEmacs! ;; DEFUN ("file-name-directory", Ffile_name_directory, Sfile_name_directory, ...) ==> Ffile_name_directory @@ -1892,15 +1905,24 @@ with a brace block." (t ;; Normal function or initializer. - (when (c-syntactic-re-search-forward "[{(]" nil t) - (backward-char) + (when + (and + (consp (c-forward-decl-or-cast-1 (c-point 'bosws) 'top nil)) + (or (eq (char-after) ?{) + (and c-recognize-knr-p + (c-in-knr-argdecl))) + (progn + (c-backward-syntactic-ws) + (eq (char-before) ?\))) + (c-go-list-backward)) (c-backward-syntactic-ws) (when (eq (char-before) ?\=) ; struct foo bar = {0, 0} ; (c-backward-token-2) (c-backward-syntactic-ws)) (setq name-end (point)) (c-back-over-compound-identifier) - (buffer-substring-no-properties (point) name-end))))))))) + (and (looking-at c-symbol-start) + (buffer-substring-no-properties (point) name-end)))))))))) (defun c-declaration-limits (near) ;; Return a cons of the beginning and end positions of the current @@ -1915,7 +1937,7 @@ with a brace block." (save-restriction (let ((start (point)) (paren-state (c-parse-state)) - lim pos end-pos encl-decl-block where) + lim pos end-pos where) ;; Narrow enclosing brace blocks out, as required by the values of ;; `c-defun-tactic', `near', and the position of point. (when (eq c-defun-tactic 'go-outward) @@ -2041,6 +2063,23 @@ with a brace block." (eq (char-after) ?\{) (cons (point-min) (point-max)))))))) +(defun c-display-defun-name (&optional arg) + "Display the name of the current CC mode defun and the position in it. +With a prefix arg, push the name onto the kill ring too." + (interactive "P") + (save-restriction + (widen) + (c-save-buffer-state ((name (c-defun-name)) + (limits (c-declaration-limits t)) + (point-bol (c-point 'bol))) + (when name + (message "%s. Line %s/%s." name + (1+ (count-lines (car limits) point-bol)) + (count-lines (car limits) (cdr limits))) + (if arg (kill-new name)) + (sit-for 3 t))))) +(put 'c-display-defun-name 'isearch-scroll t) + (defun c-mark-function () "Put mark at end of the current top-level declaration or macro, point at beginning. If point is not inside any then the closest following one is @@ -2085,7 +2124,6 @@ function does not require the declaration to contain a brace block." (defun c-cpp-define-name () "Return the name of the current CPP macro, or NIL if we're not in one." - (interactive) (let (case-fold-search) (save-excursion (and c-opt-cpp-macro-define-start diff --git a/lisp/progmodes/cc-defs.el b/lisp/progmodes/cc-defs.el index dd8f8afc6a3..bff1c9eb65d 100644 --- a/lisp/progmodes/cc-defs.el +++ b/lisp/progmodes/cc-defs.el @@ -26,7 +26,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -44,19 +44,12 @@ (load "cc-bytecomp" nil t))) (eval-and-compile - (defvar c--mapcan-status - (cond ((and (fboundp 'mapcan) - (subrp (symbol-function 'mapcan))) - ;; XEmacs - 'mapcan) - ((locate-file "cl-lib.elc" load-path) - ;; Emacs >= 24.3 - 'cl-mapcan) - (t - ;; Emacs <= 24.2 - nil)))) - -(cc-external-require (if (eq c--mapcan-status 'cl-mapcan) 'cl-lib 'cl)) + (defvar c--cl-library + (if (locate-library "cl-lib") + 'cl-lib + 'cl))) + +(cc-external-require c--cl-library) ; was (cc-external-require 'cl). ACM 2005/11/29. ; Changed from (eval-when-compile (require 'cl)) back to ; cc-external-require, 2015-08-12. @@ -182,9 +175,12 @@ This variant works around bugs in `eval-when-compile' in various ;; The motivation for this macro is to avoid the irritating message ;; "function `mapcan' from cl package called at runtime" produced by Emacs. (cond - ((eq c--mapcan-status 'mapcan) + ((and (fboundp 'mapcan) + (subrp (symbol-function 'mapcan))) + ;; XEmacs and Emacs >= 26. `(mapcan ,fun ,liszt)) - ((eq c--mapcan-status 'cl-mapcan) + ((eq c--cl-library 'cl-lib) + ;; Emacs >= 24.3, < 26. `(cl-mapcan ,fun ,liszt)) (t ;; Emacs <= 24.2. It would be nice to be able to distinguish between @@ -193,13 +189,13 @@ This variant works around bugs in `eval-when-compile' in various (defmacro c--set-difference (liszt1 liszt2 &rest other-args) ;; Macro to smooth out the renaming of `set-difference' in Emacs 24.3. - (if (eq c--mapcan-status 'cl-mapcan) + (if (eq c--cl-library 'cl-lib) `(cl-set-difference ,liszt1 ,liszt2 ,@other-args) `(set-difference ,liszt1 ,liszt2 ,@other-args))) (defmacro c--intersection (liszt1 liszt2 &rest other-args) ;; Macro to smooth out the renaming of `intersection' in Emacs 24.3. - (if (eq c--mapcan-status 'cl-mapcan) + (if (eq c--cl-library 'cl-lib) `(cl-intersection ,liszt1 ,liszt2 ,@other-args) `(intersection ,liszt1 ,liszt2 ,@other-args))) @@ -212,7 +208,7 @@ This variant works around bugs in `eval-when-compile' in various (defmacro c--delete-duplicates (cl-seq &rest cl-keys) ;; Macro to smooth out the renaming of `delete-duplicates' in Emacs 24.3. - (if (eq c--mapcan-status 'cl-mapcan) + (if (eq c--cl-library 'cl-lib) `(cl-delete-duplicates ,cl-seq ,@cl-keys) `(delete-duplicates ,cl-seq ,@cl-keys)))) @@ -371,6 +367,8 @@ to it is returned. This function does not modify the point or the mark." (t (error "Unknown buffer position requested: %s" position)))) (point)))) +(defvar lookup-syntax-properties) ;XEmacs. + (eval-and-compile ;; Constant to decide at compilation time whether to use category ;; properties. Currently (2010-03) they're available only on GNU Emacs. @@ -419,6 +417,17 @@ to it is returned. This function does not modify the point or the mark." ;; Emacs. `(setq mark-active ,activate))) +(defmacro c-set-keymap-parent (map parent) + (cond + ;; XEmacs + ((cc-bytecomp-fboundp 'set-keymap-parents) + `(set-keymap-parents ,map ,parent)) + ;; Emacs + ((cc-bytecomp-fboundp 'set-keymap-parent) + `(set-keymap-parent ,map ,parent)) + ;; incompatible + (t (error "CC Mode is incompatible with this version of Emacs")))) + (defmacro c-delete-and-extract-region (start end) "Delete the text between START and END and return it." (if (cc-bytecomp-fboundp 'delete-and-extract-region) @@ -1175,6 +1184,86 @@ been put there by c-put-char-property. POINT remains unchanged." nil ,from ,to ,value nil -property-)) ;; GNU Emacs `(c-clear-char-property-with-value-function ,from ,to ,property ,value))) + +(defmacro c-search-forward-char-property-with-value-on-char + (property value char &optional limit) + "Search forward for a text-property PROPERTY having value VALUE on a +character with value CHAR. +LIMIT bounds the search. The value comparison is done with `equal'. +PROPERTY must be a constant. + +Leave point just after the character, and set the match data on +this character, and return point. If the search fails, return +nil; point is then left undefined." + `(let ((char-skip (concat "^" (char-to-string ,char))) + (-limit- ,limit) + (-value- ,value)) + (while + (and + (progn (skip-chars-forward char-skip -limit-) + (< (point) -limit-)) + (not (equal (c-get-char-property (point) ,property) -value-))) + (forward-char)) + (when (< (point) -limit-) + (search-forward-regexp ".") ; to set the match-data. + (point)))) + +(defun c-clear-char-property-with-value-on-char-function (from to property + value char) + "Remove all text-properties PROPERTY with value VALUE on +characters with value CHAR from the region [FROM, TO), as tested +by `equal'. These properties are assumed to be over individual +characters, having been put there by c-put-char-property. POINT +remains unchanged." + (let ((place from) + ) + (while ; loop round occurrences of (PROPERTY VALUE) + (progn + (while ; loop round changes in PROPERTY till we find VALUE + (and + (< place to) + (not (equal (get-text-property place property) value))) + (setq place (c-next-single-property-change place property nil to))) + (< place to)) + (if (eq (char-after place) char) + (remove-text-properties place (1+ place) (cons property nil))) + ;; Do we have to do anything with stickiness here? + (setq place (1+ place))))) + +(defmacro c-clear-char-property-with-value-on-char (from to property value char) + "Remove all text-properties PROPERTY with value VALUE on +characters with value CHAR from the region [FROM, TO), as tested +by `equal'. These properties are assumed to be over individual +characters, having been put there by c-put-char-property. POINT +remains unchanged." + (if c-use-extents + ;; XEmacs + `(let ((-property- ,property) + (-char- ,char)) + (map-extents (lambda (ext val) + (if (and (equal (extent-property ext -property-) val) + (eq (char-after + (extent-start-position ext)) + -char-)) + (delete-extent ext))) + nil ,from ,to ,value nil -property-)) + ;; Gnu Emacs + `(c-clear-char-property-with-value-on-char-function ,from ,to ,property + ,value ,char))) + +(defmacro c-put-char-properties-on-char (from to property value char) + ;; This needs to be a macro because `property' passed to + ;; `c-put-char-property' must be a constant. + "Put the text property PROPERTY with value VALUE on characters +with value CHAR in the region [FROM to)." + `(let ((skip-string (concat "^" (list ,char))) + (-to- ,to)) + (save-excursion + (goto-char ,from) + (while (progn (skip-chars-forward skip-string -to-) + (< (point) -to-)) + (c-put-char-property (point) ,property ,value) + (forward-char))))) ;; Macros to put overlays (Emacs) or extents (XEmacs) on buffer text. ;; For our purposes, these are characterized by being possible to @@ -1211,6 +1300,7 @@ been put there by c-put-char-property. POINT remains unchanged." (def-edebug-spec cc-eval-when-compile (&rest def-form)) (def-edebug-spec c-point t) (def-edebug-spec c-set-region-active t) +(def-edebug-spec c-set-keymap-parent t) (def-edebug-spec c-safe t) (def-edebug-spec c-save-buffer-state let*) (def-edebug-spec c-tentative-buffer-changes t) @@ -1232,6 +1322,8 @@ been put there by c-put-char-property. POINT remains unchanged." (def-edebug-spec c-put-char-property t) (def-edebug-spec c-get-char-property t) (def-edebug-spec c-clear-char-property t) +(def-edebug-spec c-clear-char-property-with-value-on-char t) +(def-edebug-spec c-put-char-properties-on-char t) (def-edebug-spec c-clear-char-properties t) (def-edebug-spec c-put-overlay t) (def-edebug-spec c-delete-overlay t) @@ -1777,8 +1869,6 @@ non-nil, a caret is prepended to invert the set." (cc-bytecomp-defvar open-paren-in-column-0-is-defun-start) -(defvar lookup-syntax-properties) ;XEmacs. - (defconst c-emacs-features (let (list) diff --git a/lisp/progmodes/cc-engine.el b/lisp/progmodes/cc-engine.el index aa84ade083c..ab0204cb961 100644 --- a/lisp/progmodes/cc-engine.el +++ b/lisp/progmodes/cc-engine.el @@ -26,7 +26,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -132,7 +132,7 @@ ;; ;; 'c-not-decl ;; Put on the brace which introduces a brace list and on the commas -;; which separate the element within it. +;; which separate the elements within it. ;; ;; 'c-awk-NL-prop ;; Used in AWK mode to mark the various kinds of newlines. See @@ -241,14 +241,14 @@ ;; Either nil, or the last character of the macro currently represented by ;; `c-macro-cache' which isn't in a comment. */ -(defun c-invalidate-macro-cache (beg end) +(defun c-invalidate-macro-cache (beg _end) ;; Called from a before-change function. If the change region is before or ;; in the macro characterized by `c-macro-cache' etc., nullify it ;; appropriately. BEG and END are the standard before-change-functions ;; parameters. END isn't used. (cond ((null c-macro-cache)) - ((< beg (car c-macro-cache)) + ((<= beg (car c-macro-cache)) (setq c-macro-cache nil c-macro-cache-start-pos nil c-macro-cache-syntactic nil @@ -834,7 +834,7 @@ comment at the start of cc-engine.el for more info." (c-stmt-delim-chars (if comma-delim c-stmt-delim-chars-with-comma c-stmt-delim-chars)) - c-in-literal-cache c-maybe-labelp after-case:-pos saved + c-maybe-labelp after-case:-pos saved ;; Current position. pos ;; Position of last stmt boundary character (e.g. ;). @@ -1680,6 +1680,7 @@ comment at the start of cc-engine.el for more info." ; (not (eobp))))))) (defmacro c-debug-sws-msg (&rest args) + (ignore args) ;;`(message ,@args) ) @@ -1719,7 +1720,7 @@ comment at the start of cc-engine.el for more info." `((c-debug-remove-face beg end 'c-debug-is-sws-face) (c-debug-remove-face beg end 'c-debug-in-sws-face))))) -;; The type of literal position `end' is in in a `before-change-functions' +;; The type of literal position `end' is in a `before-change-functions' ;; function - one of `c', `c++', `pound', or nil (but NOT `string'). (defvar c-sws-lit-type nil) ;; A cons (START . STOP) of the bounds of the comment or CPP construct @@ -1979,17 +1980,10 @@ comment at the start of cc-engine.el for more info." (end-of-line)) (setq macro-end (point)) ;; Check for an open block comment at the end of the macro. - (goto-char macro-start) - (let (s in-block-comment) - (while - (progn - (setq s (parse-partial-sexp (point) macro-end - nil nil s 'syntax-table)) - (< (point) macro-end)) - (setq in-block-comment - (and (elt s 4) ; in a comment - (null (elt s 7))))) ; a block comment - (if in-block-comment (setq safe-start nil))) + (let ((s (parse-partial-sexp macro-start macro-end))) + (if (and (elt s 4) ; in a comment + (null (elt s 7))) ; a block comment + (setq safe-start nil))) (forward-line 1) ;; Don't cache at eob in case the buffer is narrowed. (not (eobp))) @@ -2790,7 +2784,7 @@ comment at the start of cc-engine.el for more info." (setq pos npos) (setq c-state-nonlit-pos-cache (cons pos c-state-nonlit-pos-cache))) - ;; Add one extra element above HERE so as to to avoid the previous + ;; Add one extra element above HERE so as to avoid the previous ;; expensive calculation when the next call is close to the current ;; one. This is especially useful when inside a large macro. (when npos @@ -3307,7 +3301,6 @@ comment at the start of cc-engine.el for more info." paren+1s ; A list of `paren+1's; used to determine a ; good-pos. bra+1 ; just after L bra-ce. - bra+1s ; list of OLD values of bra+1. mstart) ; start of a macro. (save-excursion @@ -3345,7 +3338,7 @@ comment at the start of cc-engine.el for more info." ;; Insert the opening brace/bracket/paren position. (setq c-state-cache (cons (1- pa+1) c-state-cache)) ;; Clear admin stuff for the next more nested part of the scan. - (setq ren+1 pa+1 pa+1 nil bra+1 nil bra+1s nil) + (setq ren+1 pa+1 pa+1 nil bra+1 nil) t) ; Carry on the loop ;; All open p/b/b's at this nesting level, if any, have probably @@ -3429,7 +3422,7 @@ comment at the start of cc-engine.el for more info." upper-lim ; ,beyond which `c-state-cache' entries are removed scan-back-pos cons-separated - pair-beg pps-point-state target-depth) + pair-beg target-depth) ;; Remove entries beyond HERE. Also remove any entries inside ;; a macro, unless HERE is in the same macro. @@ -3485,9 +3478,6 @@ comment at the start of cc-engine.el for more info." target-depth nil pps-state)) - (if (= (point) pps-point) - (setq pps-point-state pps-state)) - (when (eq (car pps-state) target-depth) (setq pos (point)) ; POS is now just after an R-paren/brace. (cond @@ -3732,11 +3722,10 @@ comment at the start of cc-engine.el for more info." ;; brace pair. (let ((here-bol (c-point 'bol here)) too-high-pa ; recorded {/(/[ next above or just below here, or nil. - dropped-cons ; was the last removed element a brace pair? - pa) + dropped-cons) ; was the last removed element a brace pair? ;; The easy bit - knock over-the-top bits off `c-state-cache'. (while (and c-state-cache - (>= (setq pa (c-state-cache-top-paren)) here)) + (>= (c-state-cache-top-paren) here)) (setq dropped-cons (consp (car c-state-cache)) too-high-pa (c-state-cache-top-lparen) c-state-cache (cdr c-state-cache))) @@ -4308,6 +4297,47 @@ comment at the start of cc-engine.el for more info." "\\w\\|\\s_\\|\\s\"\\|\\s|" "\\w\\|\\s_\\|\\s\"")) +(defun c-forward-over-token-and-ws (&optional balanced) + "Move forward over a token and any following whitespace +Return t if we moved, nil otherwise (i.e. we were at EOB, or a +non-token or BALANCED is non-nil and we can't move). If we +are at syntactic whitespace, move over this in place of a token. + +If BALANCED is non-nil move over any balanced parens we are at, and never move +out of an enclosing paren. + +This function differs from `c-forward-token-2' in that it will move forward +over the final token in a buffer, up to EOB." + (let ((jump-syntax (if balanced + c-jump-syntax-balanced + c-jump-syntax-unbalanced)) + (here (point))) + (when + (condition-case nil + (cond + ((/= (point) + (progn (c-forward-syntactic-ws) (point))) + ;; If we're at whitespace, count this as the token. + t) + ((eobp) nil) + ((looking-at jump-syntax) + (goto-char (scan-sexps (point) 1)) + t) + ((looking-at c-nonsymbol-token-regexp) + (goto-char (match-end 0)) + t) + ((save-restriction + (widen) + (looking-at c-nonsymbol-token-regexp)) + nil) + (t + (forward-char) + t)) + (error (goto-char here) + nil)) + (c-forward-syntactic-ws) + t))) + (defun c-forward-token-2 (&optional count balanced limit) "Move forward by tokens. A token is defined as all symbols and identifiers which aren't @@ -4337,15 +4367,11 @@ comment at the start of cc-engine.el for more info." (if (< count 0) (- (c-backward-token-2 (- count) balanced limit)) - (let ((jump-syntax (if balanced - c-jump-syntax-balanced - c-jump-syntax-unbalanced)) - (last (point)) - (prev (point))) - - (if (zerop count) - ;; If count is zero we should jump if in the middle of a token. - (c-end-of-current-token)) + (let ((here (point)) + (last (point))) + (when (zerop count) + ;; If count is zero we should jump if in the middle of a token. + (c-end-of-current-token)) (save-restriction (if limit (narrow-to-region (point-min) limit)) @@ -4359,43 +4385,15 @@ comment at the start of cc-engine.el for more info." ;; Moved out of bounds. Make sure the returned count isn't zero. (progn (if (zerop count) (setq count 1)) - (goto-char last)) - - ;; Use `condition-case' to avoid having the limit tests - ;; inside the loop. - (condition-case nil - (while (and - (> count 0) - (progn - (setq last (point)) - (cond ((looking-at jump-syntax) - (goto-char (scan-sexps (point) 1)) - t) - ((looking-at c-nonsymbol-token-regexp) - (goto-char (match-end 0)) - t) - ;; `c-nonsymbol-token-regexp' above should always - ;; match if there are correct tokens. Try to - ;; widen to see if the limit was set in the - ;; middle of one, else fall back to treating - ;; the offending thing as a one character token. - ((and limit - (save-restriction - (widen) - (looking-at c-nonsymbol-token-regexp))) - nil) - (t - (forward-char) - t)))) - (c-forward-syntactic-ws) - (setq prev last - count (1- count))) - (error (goto-char last))) - - (when (eobp) - (goto-char prev) - (setq count (1+ count))))) - + (goto-char here)) + (while (and + (> count 0) + (c-forward-over-token-and-ws balanced) + (not (eobp))) + (setq last (point) + count (1- count))) + (if (eobp) + (goto-char last)))) count))) (defun c-backward-token-2 (&optional count balanced limit) @@ -4809,7 +4807,6 @@ comment at the start of cc-engine.el for more info." (c-self-bind-state-cache (let ((start (point)) - state-2 ;; A list of syntactically relevant positions in descending ;; order. It's used to avoid scanning repeatedly over ;; potentially large regions with `parse-partial-sexp' to verify @@ -5028,7 +5025,7 @@ comment at the start of cc-engine.el for more info." ;; Tools for handling comments and string literals. -(defun c-in-literal (&optional lim detect-cpp) +(defun c-in-literal (&optional _lim detect-cpp) "Return the type of literal point is in, if any. The return value is `c' if in a C-style comment, `c++' if in a C++ style comment, `string' if in a string literal, `pound' if DETECT-CPP @@ -5036,9 +5033,6 @@ is non-nil and in a preprocessor line, or nil if somewhere else. Optional LIM is used as the backward limit of the search. If omitted, or nil, `c-beginning-of-defun' is used. -The last point calculated is cached if the cache is enabled, i.e. if -`c-in-literal-cache' is bound to a two element vector. - Note that this function might do hidden buffer changes. See the comment at the start of cc-engine.el for more info." (save-restriction @@ -5195,16 +5189,25 @@ comment at the start of cc-engine.el for more info." ;; Get a "safe place" approximately TRY-SIZE characters before START. ;; This defsubst doesn't preserve point. (let* ((pos (max (- start try-size) (point-min))) - (s (c-state-semi-pp-to-literal pos))) - (or (car (cddr s)) pos))) + (s (c-state-semi-pp-to-literal pos)) + (cand (or (car (cddr s)) pos))) + (if (>= cand (point-min)) + cand + (parse-partial-sexp pos start nil nil (car s) 'syntax-table) + (point)))) (defun c-determine-limit (how-far-back &optional start try-size) - ;; Return a buffer position HOW-FAR-BACK non-literal characters from START - ;; (default point). This is done by going back further in the buffer then - ;; searching forward for literals. The position found won't be in a - ;; literal. We start searching for the sought position TRY-SIZE (default - ;; twice HOW-FAR-BACK) bytes back from START. This function must be fast. - ;; :-) + ;; Return a buffer position HOW-FAR-BACK non-literal characters from + ;; START (default point). The starting position, either point or + ;; START may not be in a comment or string. + ;; + ;; The position found will not be before POINT-MIN and won't be in a + ;; literal. + ;; + ;; We start searching for the sought position TRY-SIZE (default + ;; twice HOW-FAR-BACK) bytes back from START. + ;; + ;; This function must be fast. :-) (save-excursion (let* ((start (or start (point))) (try-size (or try-size (* 2 how-far-back))) @@ -5260,6 +5263,8 @@ comment at the start of cc-engine.el for more info." (+ (car elt) (- count how-far-back))) ((eq base (point-min)) (point-min)) + ((> base (- start try-size)) ; Can only happen if we hit point-min. + (car elt)) (t (c-determine-limit (- how-far-back count) base try-size)))))) @@ -5418,15 +5423,14 @@ comment at the start of cc-engine.el for more info." (min c-bs-cache-limit pos))) (defun c-update-brace-stack (stack from to) - ;; Give a brace-stack which has the value STACK at position FROM, update it - ;; to it's value at position TO, where TO is after (or equal to) FROM. + ;; Given a brace-stack which has the value STACK at position FROM, update it + ;; to its value at position TO, where TO is after (or equal to) FROM. ;; Return a cons of either TO (if it is outside a literal) and this new ;; value, or of the next position after TO outside a literal and the new ;; value. (let (match kwd-sym (prev-match-pos 1) (s (cdr stack)) - (bound-<> (car stack)) - ) + (bound-<> (car stack))) (save-excursion (cond ((and bound-<> (<= to bound-<>)) @@ -5487,6 +5491,9 @@ comment at the start of cc-engine.el for more info." (setq s (cdr s)))) ((c-keyword-member kwd-sym 'c-flat-decl-block-kwds) (push 0 s)))) + ;; The failing `c-syntactic-re-search-forward' may have left us in the + ;; middle of a token, which might be a significant token. Fix this! + (c-beginning-of-current-token) (cons (point) (cons bound-<> s))))) @@ -5662,11 +5669,13 @@ comment at the start of cc-engine.el for more info." ;; Call CFD-FUN for each possible spot for a declaration, cast or ;; label from the point to CFD-LIMIT. ;; - ;; CFD-FUN is called with point at the start of the spot. It's passed two + ;; CFD-FUN is called with point at the start of the spot. It's passed three ;; arguments: The first is the end position of the token preceding the spot, ;; or 0 for the implicit match at bob. The second is a flag that is t when - ;; the match is inside a macro. Point should be moved forward by at least - ;; one token. + ;; the match is inside a macro. The third is a flag that is t when the + ;; match is at "top level", i.e. outside any brace block, or directly inside + ;; a class or namespace, etc. Point should be moved forward by at least one + ;; token. ;; ;; If CFD-FUN adds `c-decl-end' properties somewhere below the current spot, ;; it should return non-nil to ensure that the next search will find them. @@ -6053,6 +6062,8 @@ comment at the start of cc-engine.el for more info." (setq cfd-macro-end 0) nil)))) ; end of when condition + (when (> cfd-macro-end 0) + (setq cfd-top-level nil)) ; In a macro is "never" at top level. (c-debug-put-decl-spot-faces cfd-match-pos (point)) (if (funcall cfd-fun cfd-match-pos (/= cfd-macro-end 0) cfd-top-level) (setq cfd-prop-match nil)) @@ -6097,7 +6108,8 @@ comment at the start of cc-engine.el for more info." (defsubst c-clear-found-types () ;; Clears `c-found-types'. - (setq c-found-types (make-vector 53 0))) + (setq c-found-types + (make-hash-table :test #'equal :weakness nil))) (defun c-add-type (from to) ;; Add the given region as a type in `c-found-types'. If the region @@ -6111,36 +6123,34 @@ comment at the start of cc-engine.el for more info." ;; ;; This function might do hidden buffer changes. (let ((type (c-syntactic-content from to c-recognize-<>-arglists))) - (unless (intern-soft type c-found-types) - (unintern (substring type 0 -1) c-found-types) - (intern type c-found-types)))) + (unless (gethash type c-found-types) + (remhash (substring type 0 -1) c-found-types) + (puthash type t c-found-types)))) (defun c-unfind-type (name) ;; Remove the "NAME" from c-found-types, if present. - (unintern name c-found-types)) + (remhash name c-found-types)) (defsubst c-check-type (from to) ;; Return non-nil if the given region contains a type in ;; `c-found-types'. ;; ;; This function might do hidden buffer changes. - (intern-soft (c-syntactic-content from to c-recognize-<>-arglists) - c-found-types)) + (gethash (c-syntactic-content from to c-recognize-<>-arglists) c-found-types)) (defun c-list-found-types () ;; Return all the types in `c-found-types' as a sorted list of ;; strings. (let (type-list) - (mapatoms (lambda (type) - (setq type-list (cons (symbol-name type) - type-list))) + (maphash (lambda (type _) + (setq type-list (cons type type-list))) c-found-types) (sort type-list 'string-lessp))) ;; Shut up the byte compiler. (defvar c-maybe-stale-found-type) -(defun c-trim-found-types (beg end old-len) +(defun c-trim-found-types (beg end _old-len) ;; An after change function which, in conjunction with the info in ;; c-maybe-stale-found-type (set in c-before-change), removes a type ;; from `c-found-types', should this type have become stale. For @@ -6410,6 +6420,9 @@ comment at the start of cc-engine.el for more info." (c-clear-<>-pair-props) (forward-char))))))) +(defvar c-restricted-<>-arglists) ;FIXME: Move definition here? +(defvar c-parse-and-markup-<>-arglists) ;FIXME: Move definition here? + (defun c-restore-<>-properties (_beg _end _old-len) ;; This function is called as an after-change function. It restores the ;; category/syntax-table properties on template/generic <..> pairs between @@ -6431,7 +6444,8 @@ comment at the start of cc-engine.el for more info." (not (eq (c-get-char-property (point) 'c-type) 'c-decl-arg-start))))))) (or (c-forward-<>-arglist nil) - (forward-char))))) + (c-forward-over-token-and-ws) + (goto-char c-new-END))))) ;; Functions to handle C++ raw strings. @@ -6716,7 +6730,7 @@ comment at the start of cc-engine.el for more info." (c-put-char-property open-paren 'syntax-table '(1))) (goto-char bound)))) -(defun c-after-change-re-mark-raw-strings (beg end old-len) +(defun c-after-change-re-mark-raw-strings (_beg _end _old-len) ;; This function applies `syntax-table' text properties to C++ raw strings ;; beginning in the region (c-new-BEG c-new-END). BEG, END, and OLD-LEN are ;; the standard arguments supplied to any after-change function. @@ -6937,7 +6951,7 @@ comment at the start of cc-engine.el for more info." ;; recognized are those specified by `c-type-list-kwds', ;; `c-ref-list-kwds', `c-colon-type-list-kwds', ;; `c-paren-nontype-kwds', `c-paren-type-kwds', `c-<>-type-kwds', - ;; and `c-<>-arglist-kwds'. + ;; `c-<>-arglist-kwds', and `c-protection-kwds'. ;; ;; This function records identifier ranges on ;; `c-record-type-identifiers' and `c-record-ref-identifiers' if @@ -7007,6 +7021,17 @@ comment at the start of cc-engine.el for more info." (not (looking-at c-symbol-start)) (c-safe (c-forward-sexp) t)) (c-forward-syntactic-ws) + (setq safe-pos (point))) + + ((and (c-keyword-member kwd-sym 'c-protection-kwds) + (or (null c-post-protection-token) + (and (looking-at c-post-protection-token) + (save-excursion + (goto-char (match-end 0)) + (not (c-end-of-current-token)))))) + (if c-post-protection-token + (goto-char (match-end 0))) + (c-forward-syntactic-ws) (setq safe-pos (point)))) (when (c-keyword-member kwd-sym 'c-colon-type-list-kwds) @@ -7064,6 +7089,7 @@ comment at the start of cc-engine.el for more info." ;; This function might do hidden buffer changes. (let ((start (point)) + (old-found-types (copy-hash-table c-found-types)) ;; If `c-record-type-identifiers' is set then activate ;; recording of any found types that constitute an argument in ;; the arglist. @@ -7079,6 +7105,7 @@ comment at the start of cc-engine.el for more info." (nconc c-record-found-types c-record-type-identifiers))) t) + (setq c-found-types old-found-types) (goto-char start) nil))) @@ -7136,7 +7163,7 @@ comment at the start of cc-engine.el for more info." (let ((c-promote-possible-types t) (c-record-found-types t)) (c-forward-type)) - (c-forward-token-2)))) + (c-forward-over-token-and-ws)))) (c-forward-syntactic-ws) @@ -7398,7 +7425,12 @@ comment at the start of cc-engine.el for more info." (setq pos (point) res subres)))) - ((looking-at c-identifier-start) + ((and (looking-at c-identifier-start) + (or (not (looking-at + c-ambiguous-overloadable-or-identifier-prefix-re)) + (save-excursion + (and (eq (c-forward-token-2) 0) + (not (eq (char-after) ?\()))))) ;; Got a cast operator. (when (c-forward-type) (setq pos (point) @@ -7809,8 +7841,7 @@ comment at the start of cc-engine.el for more info." ;; looking (in C++) like this "FQN::of::base::Class". Move to the start of ;; this construct and return t. If the parsing fails, return nil, leaving ;; point unchanged. - (let ((here (point)) - end) + (let (end) (if (not (c-on-identifier)) nil (c-simple-skip-symbol-backward) @@ -8092,12 +8123,14 @@ comment at the start of cc-engine.el for more info." ;; initializing brace lists. (let (found) (while - (and (progn + (and (< (point) limit) + (progn ;; In the next loop, we keep searching forward whilst ;; we find ":"s which aren't single colons inside C++ ;; "for" statements. (while (and + (< (point) limit) (setq found (c-syntactic-re-search-forward "[;:,]\\|\\s)\\|\\(=\\|\\s(\\)" @@ -8119,7 +8152,7 @@ comment at the start of cc-engine.el for more info." (c-go-up-list-forward)) (setq brackets-after-id t)) (when found (backward-char)) - t)) + (<= (point) limit))) (list id-start id-end brackets-after-id (match-beginning 1) decorated) (goto-char here) @@ -8241,10 +8274,6 @@ comment at the start of cc-engine.el for more info." ;; If `backup-at-type' is nil then the other variables have ;; undefined values. backup-at-type backup-type-start backup-id-start - ;; This stores `kwd-sym' of the symbol before the current one. - ;; This is needed to distinguish the C++11 version of "auto" from - ;; the pre C++11 meaning. - backup-kwd-sym ;; Set if we've found a specifier (apart from "typedef") that makes ;; the defined identifier(s) types. at-type-decl @@ -8352,7 +8381,6 @@ comment at the start of cc-engine.el for more info." (setq backup-at-type at-type backup-type-start type-start backup-id-start id-start - backup-kwd-sym kwd-sym at-type found-type type-start start id-start (point) @@ -8576,7 +8604,13 @@ comment at the start of cc-engine.el for more info." (looking-at c-noise-macro-with-parens-name-re)) (c-forward-noise-clause)) - ((looking-at c-type-decl-suffix-key) + ((and (looking-at c-type-decl-suffix-key) + ;; We avoid recognizing foo(bar) or foo() at top level as a + ;; construct here in C, since we want to recognize this as a + ;; typeless function declaration. + (not (and (c-major-mode-is 'c-mode) + (eq context 'top) + (eq (char-after) ?\))))) (if (eq (char-after) ?\)) (when (> paren-depth 0) (setq paren-depth (1- paren-depth)) @@ -8619,7 +8653,12 @@ comment at the start of cc-engine.el for more info." (save-excursion (goto-char after-paren-pos) (c-forward-syntactic-ws) - (c-forward-type))))) + (or (c-forward-type) + ;; Recognize a top-level typeless + ;; function declaration in C. + (and (c-major-mode-is 'c-mode) + (eq context 'top) + (eq (char-after) ?\)))))))) (setq pos (c-up-list-forward (point))) (eq (char-before pos) ?\))) (c-fdoc-shift-type-backward) @@ -8906,9 +8945,9 @@ comment at the start of cc-engine.el for more info." ;; uncommon (e.g. some placements of "const" in C++) it's not worth ;; the effort to look for them.) -;;; 2008-04-16: commented out the next form, to allow the function to recognize -;;; "foo (int bar)" in CC (an implicit type (in class foo) without a semicolon) -;;; as a(n almost complete) declaration, enabling it to be fontified. +;;; 2008-04-16: commented out the next form, to allow the function to recognize +;;; "foo (int bar)" in CC (an implicit type (in class foo) without a semicolon) +;;; as a(n almost complete) declaration, enabling it to be fontified. ;; CASE 13 ;; (unless (or at-decl-end (looking-at "=[^=]")) ;; If this is a declaration it should end here or its initializer(*) @@ -9036,9 +9075,12 @@ comment at the start of cc-engine.el for more info." ;; (in at least C++) that anything that can be parsed as a declaration ;; is a declaration. Now we're being more defensive and prefer to ;; highlight things like "foo (bar);" as a declaration only if we're - ;; inside an arglist that contains declarations. - ;; CASE 19 - (eq context 'decl)))) + ;; inside an arglist that contains declarations. Update (2017-09): We + ;; now recognize a top-level "foo(bar);" as a declaration in C. + ;; CASE 19 + (or (eq context 'decl) + (and (c-major-mode-is 'c-mode) + (eq context 'top)))))) ;; The point is now after the type decl expression. @@ -9546,6 +9588,7 @@ Note that this function might do hidden buffer changes. See the comment at the start of cc-engine.el for more info." ;; Note to maintainers: this function consumes a great mass of CPU cycles. ;; Its use should thus be minimized as far as possible. + ;; Consider instead using `c-bs-at-toplevel-p'. (let ((paren-state (c-parse-state))) (or (not (c-most-enclosing-brace paren-state)) (c-search-uplist-for-classkey paren-state)))) @@ -9575,8 +9618,15 @@ comment at the start of cc-engine.el for more info." (not (and (c-major-mode-is 'objc-mode) (c-forward-objc-directive))) + ;; Don't confuse #if .... defined(foo) for a function arglist. + (not (and (looking-at c-cpp-expr-functions-key) + (save-excursion + (save-restriction + (widen) + (c-beginning-of-macro lim))))) (setq id-start (car-safe (c-forward-decl-or-cast-1 (c-point 'bosws) 'top nil))) + (numberp id-start) (< id-start beg) ;; There should not be a '=' or ',' between beg and the @@ -9695,8 +9745,8 @@ comment at the start of cc-engine.el for more info." ;; identifiers? (progn (goto-char before-lparen) - (c-forward-token-2) ; to first token inside parens (and + (c-forward-over-token-and-ws) ; to first token inside parens (setq id-start (c-on-identifier)) ; Must be at least one. (catch 'id-list (while @@ -9708,7 +9758,7 @@ comment at the start of cc-engine.el for more info." ids) (c-forward-syntactic-ws) (eq (char-after) ?\,)) - (c-forward-token-2) + (c-forward-over-token-and-ws) (unless (setq id-start (c-on-identifier)) (throw 'id-list nil))) (eq (char-after) ?\))))) @@ -10040,7 +10090,7 @@ comment at the start of cc-engine.el for more info." (c-syntactic-re-search-forward ";" nil 'move t)))) nil))) -(defun c-looking-at-decl-block (containing-sexp goto-start &optional limit) +(defun c-looking-at-decl-block (_containing-sexp goto-start &optional limit) ;; Assuming the point is at an open brace, check if it starts a ;; block that contains another declaration level, i.e. that isn't a ;; statement block or a brace list, and if so return non-nil. @@ -10181,8 +10231,16 @@ comment at the start of cc-engine.el for more info." ;; Could be more restrictive wrt invalid keywords, ;; but that'd only occur in invalid code so there's ;; no use spending effort on it. - (let ((end (match-end 0))) - (unless (c-forward-keyword-clause 0) + (let ((end (match-end 0)) + (kwd-sym (c-keyword-sym (match-string 0)))) + (unless + (and kwd-sym + ;; Moving over a protection kwd and the following + ;; ":" (in C++ Mode) to the next token could take + ;; us all the way up to `kwd-start', leaving us + ;; no chance to update `first-specifier-pos'. + (not (c-keyword-member kwd-sym 'c-protection-kwds)) + (c-forward-keyword-clause 0)) (goto-char end) (c-forward-syntactic-ws))) @@ -10313,7 +10371,7 @@ comment at the start of cc-engine.el for more info." ;; We're at a "{". Move back to the enum-like keyword that starts this ;; declaration and return t, otherwise don't move and return nil. (let ((here (point)) - up-sexp-pos before-identifier) + before-identifier) (when c-recognize-post-brace-list-type-p (c-backward-typed-enum-colon)) (while @@ -10349,16 +10407,20 @@ comment at the start of cc-engine.el for more info." (defun c-looking-at-or-maybe-in-bracelist (&optional containing-sexp lim) ;; Point is at an open brace. If this starts a brace list, return a list ;; whose car is the buffer position of the start of the construct which - ;; introduces the list, and whose cdr is t if we have parsed a keyword - ;; matching `c-opt-inexpr-brace-list-key' (e.g. Java's "new"), nil - ;; otherwise. Otherwise, if point might be inside an enclosing brace list, - ;; return t. If point is definitely neither at nor in a brace list, return - ;; nil. + ;; introduces the list, and whose cdr is the symbol `in-paren' if the brace + ;; is directly enclosed in a parenthesis form (i.e. an arglist), t if we + ;; have parsed a keyword matching `c-opt-inexpr-brace-list-key' (e.g. Java's + ;; "new"), nil otherwise. Otherwise, if point might be inside an enclosing + ;; brace list, return t. If point is definitely neither at nor in a brace + ;; list, return nil. ;; ;; CONTAINING-SEXP is the position of the brace/paren/bracket enclosing ;; POINT, or nil if there is no such position, or we do not know it. LIM is ;; a backward search limit. ;; + ;; The determination of whether the brace starts a brace list is solely by + ;; the context of the brace, not by its contents. + ;; ;; Here, "brace list" does not include the body of an enum. (save-excursion (let ((start (point)) @@ -10368,17 +10430,20 @@ comment at the start of cc-engine.el for more info." (and (c-major-mode-is 'pike-mode) c-decl-block-key)) (braceassignp 'dontknow) - inexpr-brace-list bufpos macro-start res pos after-type-id-pos) + inexpr-brace-list bufpos macro-start res pos after-type-id-pos + in-paren) (setq res (c-backward-token-2 1 t lim)) ;; Checks to do only on the first sexp before the brace. ;; Have we a C++ initialization, without an "="? (if (and (c-major-mode-is 'c++-mode) (cond - ((and (not (eq res 0)) + ((and (or (not (eq res 0)) + (eq (char-after) ?,)) (c-go-up-list-backward nil lim) ; FIXME!!! Check ; `lim' 2016-07-12. (eq (char-after) ?\()) - (setq braceassignp 'c++-noassign)) + (setq braceassignp 'c++-noassign + in-paren 'in-paren)) ((looking-at c-pre-id-bracelist-key)) ((looking-at c-return-key)) ((and (looking-at c-symbol-start) @@ -10387,9 +10452,11 @@ comment at the start of cc-engine.el for more info." (t nil)) (save-excursion (cond - ((not (eq res 0)) + ((or (not (eq res 0)) + (eq (char-after) ?,)) (and (c-go-up-list-backward nil lim) ; FIXME!!! Check `lim' 2016-07-12. - (eq (char-after) ?\())) + (eq (char-after) ?\() + (setq in-paren 'in-paren))) ((looking-at c-pre-id-bracelist-key)) ((looking-at c-return-key)) (t (setq after-type-id-pos (point)) @@ -10428,7 +10495,7 @@ comment at the start of cc-engine.el for more info." (c-backward-syntactic-ws) (eq (char-before) ?\())) ;; Single identifier between '(' and '{'. We have a bracelist. - (cons after-type-id-pos nil)) + (cons after-type-id-pos 'in-paren)) (t (goto-char pos) @@ -10486,14 +10553,14 @@ comment at the start of cc-engine.el for more info." (braceassignp ;; We've hit the beginning of the aggregate list. (c-beginning-of-statement-1 containing-sexp) - (cons (point) inexpr-brace-list)) + (cons (point) (or in-paren inexpr-brace-list))) ((and after-type-id-pos (save-excursion (when (eq (char-after) ?\;) - (c-forward-token-2 1 t)) + (c-forward-over-token-and-ws t)) (setq bufpos (point)) (when (looking-at c-opt-<>-sexp-key) - (c-forward-token-2) + (c-forward-over-token-and-ws) (when (and (eq (char-after) ?<) (c-get-char-property (point) 'syntax-table)) (c-go-list-forward nil after-type-id-pos) @@ -10511,7 +10578,7 @@ comment at the start of cc-engine.el for more info." nil nil)) (and (consp res) (eq (car res) after-type-id-pos)))))) - (cons bufpos inexpr-brace-list)) + (cons bufpos (or in-paren inexpr-brace-list))) ((eq (char-after) ?\;) ;; Brace lists can't contain a semicolon, so we're done. ;; (setq containing-sexp nil) @@ -10535,12 +10602,16 @@ comment at the start of cc-engine.el for more info." (t t)))) ;; The caller can go up one level. ))) -(defun c-inside-bracelist-p (containing-sexp paren-state) +(defun c-inside-bracelist-p (containing-sexp paren-state accept-in-paren) ;; return the buffer position of the beginning of the brace list ;; statement if we're inside a brace list, otherwise return nil. ;; CONTAINING-SEXP is the buffer pos of the innermost containing ;; paren. PAREN-STATE is the remainder of the state of enclosing - ;; braces + ;; braces. ACCEPT-IN-PAREN is non-nil iff we will accept as a brace + ;; list a brace directly enclosed in a parenthesis. + ;; + ;; The "brace list" here is recognized solely by its context, not by + ;; its contents. ;; ;; N.B.: This algorithm can potentially get confused by cpp macros ;; placed in inconvenient locations. It's a trade-off we make for @@ -10555,17 +10626,11 @@ comment at the start of cc-engine.el for more info." ;; this will pick up array/aggregate init lists, even if they are nested. (save-excursion (let ((bufpos t) - lim next-containing) + next-containing) (while (and (eq bufpos t) containing-sexp) (when paren-state - (if (consp (car paren-state)) - (setq lim (cdr (car paren-state)) - paren-state (cdr paren-state)) - (setq lim (car paren-state))) - (when paren-state - (setq next-containing (car paren-state) - paren-state (cdr paren-state)))) + (setq next-containing (c-pull-open-brace paren-state))) (goto-char containing-sexp) (if (c-looking-at-inexpr-block next-containing next-containing) @@ -10574,16 +10639,18 @@ comment at the start of cc-engine.el for more info." ;; containing sexp, so that c-looking-at-inexpr-block ;; doesn't check for an identifier before it. (setq bufpos nil) - (when (or (not (eq (char-after) ?{)) - (eq (setq bufpos (c-looking-at-or-maybe-in-bracelist - next-containing lim)) - t)) - (setq containing-sexp next-containing - lim nil - next-containing nil)))) - (and (consp bufpos) (car bufpos)))))) - -(defun c-looking-at-special-brace-list (&optional lim) + (if (not (eq (char-after) ?{)) + (setq bufpos nil) + (when (eq (setq bufpos (c-looking-at-or-maybe-in-bracelist + next-containing next-containing)) + t) + (setq containing-sexp next-containing + next-containing nil))))) + (and (consp bufpos) + (or accept-in-paren (not (eq (cdr bufpos) 'in-paren))) + (car bufpos)))))) + +(defun c-looking-at-special-brace-list (&optional _lim) ;; If we're looking at the start of a pike-style list, i.e., `({ })', ;; `([ ])', `(< >)', etc., a cons of a cons of its starting and ending ;; positions and its entry in c-special-brace-lists is returned, nil @@ -10646,7 +10713,7 @@ comment at the start of cc-engine.el for more info." (cons (list beg) type))))) (error nil)))) -(defun c-looking-at-bos (&optional lim) +(defun c-looking-at-bos (&optional _lim) ;; Return non-nil if between two statements or declarations, assuming ;; point is not inside a literal or comment. ;; @@ -10659,26 +10726,37 @@ comment at the start of cc-engine.el for more info." (defun c-looking-at-statement-block () ;; Point is at an opening brace. If this is a statement block (i.e. the - ;; elements in it are terminated by semicolons) return t. Otherwise, return - ;; nil. + ;; elements in the block are terminated by semicolons, or the block is + ;; empty, or the block contains a keyword) return t. Otherwise, return nil. (let ((here (point))) (prog1 (if (c-go-list-forward) (let ((there (point))) (backward-char) - (c-syntactic-skip-backward - "^;," here t) + (c-syntactic-skip-backward "^;," here t) (cond ((eq (char-before) ?\;) t) ((eq (char-before) ?,) nil) - (t (goto-char here) - (forward-char) - (and (c-syntactic-re-search-forward "{" there t t) - (progn (backward-char) - (c-looking-at-statement-block)))))) + (t ; We're at (1+ here). + (cond + ((progn (c-forward-syntactic-ws) + (eq (point) (1- there)))) + ((c-syntactic-re-search-forward c-keywords-regexp there t)) + ((c-syntactic-re-search-forward "{" there t t) + (backward-char) + (c-looking-at-statement-block)) + (t nil))))) (forward-char) - (and (c-syntactic-re-search-forward "[;,]" nil t t) - (eq (char-before) ?\;))) + (cond + ((c-syntactic-re-search-forward "[;,]" nil t t) + (eq (char-before) ?\;)) + ((progn (c-forward-syntactic-ws) + (eobp))) + ((c-syntactic-re-search-forward c-keywords-regexp nil t t)) + ((c-syntactic-re-search-forward "{" nil t t) + (backward-char) + (c-looking-at-statement-block)) + (t nil))) (goto-char here)))) (defun c-looking-at-inexpr-block (lim containing-sexp &optional check-at-end) @@ -11208,7 +11286,7 @@ comment at the start of cc-engine.el for more info." containing-decl-open containing-decl-start containing-decl-kwd - paren-state) + _paren-state) ;; The inclass and class-close syntactic symbols are added in ;; several places and some work is needed to fix everything. ;; Therefore it's collected here. @@ -11424,7 +11502,7 @@ comment at the start of cc-engine.el for more info." ;; following result clauses, and most of this function is a ;; single gigantic cond. :P literal char-before-ip before-ws-ip char-after-ip macro-start - in-macro-expr c-syntactic-context placeholder c-in-literal-cache + in-macro-expr c-syntactic-context placeholder step-type tmpsymbol keyword injava-inher special-brace-list tmp-pos containing-< ;; The following record some positions for the containing @@ -11448,6 +11526,7 @@ comment at the start of cc-engine.el for more info." ;; The paren state outside `containing-sexp', or at ;; `indent-point' if `containing-sexp' is nil. (paren-state (c-parse-state)) + (state-cache (copy-tree paren-state)) ;; There's always at most one syntactic element which got ;; an anchor pos. It's stored in syntactic-relpos. syntactic-relpos @@ -11610,7 +11689,7 @@ comment at the start of cc-engine.el for more info." (not (c-at-vsemi-p before-ws-ip)) (not (memq char-after-ip '(?\) ?\] ?,))) (or (not (eq char-before-ip ?})) - (c-looking-at-inexpr-block-backward c-state-cache)) + (c-looking-at-inexpr-block-backward state-cache)) (> (point) (progn ;; Ought to cache the result from the @@ -11688,7 +11767,7 @@ comment at the start of cc-engine.el for more info." (if containing-sexp (progn (goto-char containing-sexp) - (setq lim (c-most-enclosing-brace c-state-cache + (setq lim (c-most-enclosing-brace state-cache containing-sexp)) (c-backward-to-block-anchor lim) (c-add-stmt-syntax 'case-label nil t lim paren-state)) @@ -11714,7 +11793,7 @@ comment at the start of cc-engine.el for more info." (containing-sexp (goto-char containing-sexp) - (setq lim (c-most-enclosing-brace c-state-cache + (setq lim (c-most-enclosing-brace state-cache containing-sexp)) (save-excursion (setq tmpsymbol @@ -11758,7 +11837,7 @@ comment at the start of cc-engine.el for more info." (goto-char (cdr placeholder)) (back-to-indentation) (c-add-stmt-syntax tmpsymbol nil t - (c-most-enclosing-brace c-state-cache (point)) + (c-most-enclosing-brace state-cache (point)) paren-state) (unless (eq (point) (cdr placeholder)) (c-add-syntax (car placeholder)))) @@ -11811,7 +11890,7 @@ comment at the start of cc-engine.el for more info." (cond ((c-backward-over-enum-header) (setq placeholder (c-point 'boi))) - ((consp (setq placeholder + ((consp (setq placeholder (c-looking-at-or-maybe-in-bracelist containing-sexp lim))) (setq tmpsymbol (and (cdr placeholder) 'topmost-intro-cont)) @@ -12181,11 +12260,11 @@ comment at the start of cc-engine.el for more info." (and (eq (char-before) ?}) (save-excursion (let ((start (point))) - (if (and c-state-cache - (consp (car c-state-cache)) - (eq (cdar c-state-cache) (point))) + (if (and state-cache + (consp (car state-cache)) + (eq (cdar state-cache) (point))) ;; Speed up the backward search a bit. - (goto-char (caar c-state-cache))) + (goto-char (caar state-cache))) (c-beginning-of-decl-1 containing-sexp) ; Can't use `lim' here. (setq placeholder (point)) (if (= start (point)) @@ -12342,7 +12421,8 @@ comment at the start of cc-engine.el for more info." ((and (eq char-after-ip ?{) (progn (setq placeholder (c-inside-bracelist-p (point) - paren-state)) + paren-state + nil)) (if placeholder (setq tmpsymbol '(brace-list-open . inexpr-class)) (setq tmpsymbol '(block-open . inexpr-statement) @@ -12424,7 +12504,7 @@ comment at the start of cc-engine.el for more info." (skip-chars-forward " \t")) (goto-char placeholder)) (c-add-stmt-syntax 'arglist-cont-nonempty (list containing-sexp) t - (c-most-enclosing-brace c-state-cache (point)) + (c-most-enclosing-brace state-cache (point)) paren-state)) ;; CASE 7G: we are looking at just a normal arglist @@ -12465,7 +12545,11 @@ comment at the start of cc-engine.el for more info." (save-excursion (goto-char containing-sexp) (c-looking-at-special-brace-list))) - (c-inside-bracelist-p containing-sexp paren-state)))) + (c-inside-bracelist-p containing-sexp paren-state t) + (save-excursion + (goto-char containing-sexp) + (and (eq (char-after) ?{) + (not (c-looking-at-statement-block))))))) (cond ;; CASE 9A: In the middle of a special brace list opener. @@ -12513,7 +12597,7 @@ comment at the start of cc-engine.el for more info." (= (point) containing-sexp))) (if (eq (point) (c-point 'boi)) (c-add-syntax 'brace-list-close (point)) - (setq lim (c-most-enclosing-brace c-state-cache (point))) + (setq lim (c-most-enclosing-brace state-cache (point))) (c-beginning-of-statement-1 lim nil nil t) (c-add-stmt-syntax 'brace-list-close nil t lim paren-state))) @@ -12539,7 +12623,7 @@ comment at the start of cc-engine.el for more info." (goto-char containing-sexp)) (if (eq (point) (c-point 'boi)) (c-add-syntax 'brace-list-intro (point)) - (setq lim (c-most-enclosing-brace c-state-cache (point))) + (setq lim (c-most-enclosing-brace state-cache (point))) (c-beginning-of-statement-1 lim) (c-add-stmt-syntax 'brace-list-intro nil t lim paren-state))) @@ -12561,7 +12645,7 @@ comment at the start of cc-engine.el for more info." ((and (not (memq char-before-ip '(?\; ?:))) (not (c-at-vsemi-p before-ws-ip)) (or (not (eq char-before-ip ?})) - (c-looking-at-inexpr-block-backward c-state-cache)) + (c-looking-at-inexpr-block-backward state-cache)) (> (point) (save-excursion (c-beginning-of-statement-1 containing-sexp) @@ -12695,7 +12779,7 @@ comment at the start of cc-engine.el for more info." (skip-chars-forward " \t")) (goto-char placeholder)) (c-add-stmt-syntax 'template-args-cont (list containing-<) t - (c-most-enclosing-brace c-state-cache (point)) + (c-most-enclosing-brace state-cache (point)) paren-state)) ;; CASE 17: Statement or defun catchall. @@ -12769,7 +12853,7 @@ comment at the start of cc-engine.el for more info." (goto-char (cdr placeholder)) (back-to-indentation) (c-add-stmt-syntax tmpsymbol nil t - (c-most-enclosing-brace c-state-cache (point)) + (c-most-enclosing-brace state-cache (point)) paren-state) (if (/= (point) (cdr placeholder)) (c-add-syntax (car placeholder)))) diff --git a/lisp/progmodes/cc-fonts.el b/lisp/progmodes/cc-fonts.el index 9bae7d9aa2f..d352e5b08c9 100644 --- a/lisp/progmodes/cc-fonts.el +++ b/lisp/progmodes/cc-fonts.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -292,12 +292,17 @@ nil))))) res)))) - (defun c-make-font-lock-search-form (regexp highlights) + (defun c-make-font-lock-search-form (regexp highlights &optional check-point) ;; Return a lisp form which will fontify every occurrence of REGEXP ;; (a regular expression, NOT a function) between POINT and `limit' ;; with HIGHLIGHTS, a list of highlighters as specified on page - ;; "Search-based Fontification" in the elisp manual. - `(while (re-search-forward ,regexp limit t) + ;; "Search-based Fontification" in the elisp manual. If CHECK-POINT + ;; is non-nil, we will check (< (point) limit) in the main loop. + `(while + ,(if check-point + `(and (< (point) limit) + (re-search-forward ,regexp limit t)) + `(re-search-forward ,regexp limit t)) (unless (progn (goto-char (match-beginning 0)) (c-skip-comments-and-strings limit)) @@ -476,7 +481,9 @@ ,(c-make-font-lock-search-form regexp highlights))))) state-stanzas) - ,(c-make-font-lock-search-form (car normal) (cdr normal)) + ;; In the next form, check that point hasn't been moved beyond + ;; `limit' in any of the above stanzas. + ,(c-make-font-lock-search-form (car normal) (cdr normal) t) nil)))) ; (eval-after-load "edebug" ; 2006-07-09: def-edebug-spec is now in subr.el. @@ -702,6 +709,36 @@ stuff. Used on level 1 and higher." t) (c-put-font-lock-face start (1+ start) 'font-lock-warning-face))))) +(defun c-font-lock-invalid-single-quotes (limit) + ;; This function will be called from font-lock for a region bounded by POINT + ;; and LIMIT, as though it were to identify a keyword for + ;; font-lock-keyword-face. It always returns NIL to inhibit this and + ;; prevent a repeat invocation. See elisp/lispref page "Search-based + ;; Fontification". + ;; + ;; This function fontifies invalid single quotes with + ;; `font-lock-warning-face'. These are the single quotes which + ;; o - aren't inside a literal; + ;; o - are marked with a syntax-table text property value '(1); and + ;; o - are NOT marked with a non-null c-digit-separator property. + (let ((limits (c-literal-limits)) + state beg end) + (if limits + (goto-char (cdr limits))) ; Even for being in a ' ' + (while (< (point) limit) + (setq beg (point)) + (setq state (parse-partial-sexp (point) limit nil nil nil 'syntax-table)) + (setq end (point)) + (goto-char beg) + (while (progn (skip-chars-forward "^'" end) + (< (point) end)) + (if (and (equal (c-get-char-property (point) 'syntax-table) '(1)) + (not (c-get-char-property (point) 'c-digit-separator))) + (c-put-font-lock-face (point) (1+ (point)) font-lock-warning-face)) + (forward-char)) + (parse-partial-sexp end limit nil nil state 'syntax-table))) + nil) + (c-lang-defconst c-basic-matchers-before "Font lock matchers for basic keywords, labels, references and various other easily recognizable things that should be fontified before generic @@ -723,6 +760,9 @@ casts and declarations are fontified. Used on level 2 and higher." (concat ".\\(" c-string-limit-regexp "\\)") '((c-font-lock-invalid-string))) + ;; Invalid single quotes. + c-font-lock-invalid-single-quotes + ;; Fontify C++ raw strings. ,@(when (c-major-mode-is 'c++-mode) '(c-font-lock-raw-strings)) @@ -777,7 +817,8 @@ casts and declarations are fontified. Used on level 2 and higher." (c-backward-syntactic-ws) (setq id-end (point)) (< (skip-chars-backward - ,(c-lang-const c-symbol-chars)) 0)) + ,(c-lang-const c-symbol-chars)) + 0)) (not (get-text-property (point) 'face))) (c-put-font-lock-face (point) id-end c-reference-face-name) @@ -992,7 +1033,8 @@ casts and declarations are fontified. Used on level 2 and higher." (goto-char pos))))) nil) -(defun c-font-lock-declarators (limit list types not-top) +(defun c-font-lock-declarators (limit list types not-top + &optional template-class) ;; Assuming the point is at the start of a declarator in a declaration, ;; fontify the identifier it declares. (If TYPES is set, it does this via ;; the macro `c-fontify-types-and-refs'.) @@ -1006,6 +1048,11 @@ casts and declarations are fontified. Used on level 2 and higher." ;; non-nil, we are not at the top-level ("top-level" includes being directly ;; inside a class or namespace, etc.). ;; + ;; TEMPLATE-CLASS is non-nil when the declaration is in template delimiters + ;; and was introduced by, e.g. "typename" or "class", such that if there is + ;; a default (introduced by "="), it will be fontified as a type. + ;; E.g. "<class X = Y>". + ;; ;; Nil is always returned. The function leaves point at the delimiter after ;; the last declarator it processes. ;; @@ -1013,18 +1060,16 @@ casts and declarations are fontified. Used on level 2 and higher." ;;(message "c-font-lock-declarators from %s to %s" (point) limit) (c-fontify-types-and-refs - ((pos (point)) next-pos id-start id-end + ((pos (point)) next-pos id-start decl-res - paren-depth id-face got-type got-init c-last-identifier-range - (separator-prop (if types 'c-decl-type-start 'c-decl-id-start)) - brackets-after-id) + (separator-prop (if types 'c-decl-type-start 'c-decl-id-start))) ;; The following `while' fontifies a single declarator id each time round. ;; It loops only when LIST is non-nil. (while - (and pos (setq decl-res (c-forward-declarator limit))) + (and pos (setq decl-res (c-forward-declarator))) (setq next-pos (point) id-start (car decl-res) id-face (if (and (eq (char-after) ?\() @@ -1036,7 +1081,7 @@ casts and declarations are fontified. Used on level 2 and higher." (forward-char) (c-forward-syntactic-ws) (looking-at "[*&]"))) - (not (car (cddr decl-res))) ; brackets-after-id + (not (car (cddr decl-res))) (or (not (c-major-mode-is 'c++-mode)) (save-excursion (let (c-last-identifier-range) @@ -1053,7 +1098,7 @@ casts and declarations are fontified. Used on level 2 and higher." (throw 'is-function nil)) ((not (eq got-type 'maybe)) (throw 'is-function t))) - (c-forward-declarator limit t) + (c-forward-declarator nil t) (eq (char-after) ?,)) (forward-char) (c-forward-syntactic-ws)) @@ -1080,6 +1125,13 @@ casts and declarations are fontified. Used on level 2 and higher." (goto-char next-pos) (setq pos nil) ; So as to terminate the enclosing `while' form. + (if (and template-class + (eq got-init ?=) ; C++ "<class X = Y>"? + (c-forward-token-2 1 nil limit) ; Over "=" + (let ((c-promote-possible-types t)) + (c-forward-type t))) ; Over "Y" + (setq list nil)) ; Shouldn't be needed. We can't have a list, here. + (when list ;; Jump past any initializer or function prototype to see if ;; there's a ',' to continue at. @@ -1150,10 +1202,15 @@ casts and declarations are fontified. Used on level 2 and higher." (goto-char match-pos) (backward-char) (c-backward-token-2) - (or (looking-at c-block-stmt-2-key) - (looking-at c-block-stmt-1-2-key) - (looking-at c-typeof-key)))) - (cons nil t)) + (cond + ((looking-at c-paren-stmt-key) + ;; Allow comma separated <> arglists in for statements. + (cons nil nil)) + ((or (looking-at c-block-stmt-2-key) + (looking-at c-block-stmt-1-2-key) + (looking-at c-typeof-key)) + (cons nil t)) + (t nil))))) ;; Near BOB. ((<= match-pos (point-min)) (cons 'arglist t)) @@ -1194,13 +1251,16 @@ casts and declarations are fontified. Used on level 2 and higher." ;; Got a cached hit in some other type of arglist. (type (cons 'arglist t)) - (not-front-decl + ((and not-front-decl ;; The point is within the range of a previously ;; encountered type decl expression, so the arglist ;; is probably one that contains declarations. ;; However, if `c-recognize-paren-inits' is set it ;; might also be an initializer arglist. - ;; + (or (not c-recognize-paren-inits) + (save-excursion + (goto-char match-pos) + (not (c-back-over-member-initializers))))) ;; The result of this check is cached with a char ;; property on the match token, so that we can look ;; it up again when refontifying single lines in a @@ -1211,17 +1271,21 @@ casts and declarations are fontified. Used on level 2 and higher." ;; Got an open paren preceded by an arith operator. ((and (eq (char-before match-pos) ?\() (save-excursion + (goto-char match-pos) (and (zerop (c-backward-token-2 2)) (looking-at c-arithmetic-op-regexp)))) (cons nil nil)) ;; In a C++ member initialization list. ((and (eq (char-before match-pos) ?,) (c-major-mode-is 'c++-mode) - (save-excursion (c-back-over-member-initializers))) + (save-excursion + (goto-char match-pos) + (c-back-over-member-initializers))) (c-put-char-property (1- match-pos) 'c-type 'c-not-decl) (cons 'not-decl nil)) ;; At start of a declaration inside a declaration paren. ((save-excursion + (goto-char match-pos) (and (memq (char-before match-pos) '(?\( ?\,)) (c-go-up-list-backward match-pos) (eq (char-after) ?\() @@ -1296,8 +1360,12 @@ casts and declarations are fontified. Used on level 2 and higher." (c-backward-syntactic-ws) (and (c-simple-skip-symbol-backward) (looking-at c-paren-stmt-key)))) - t))) - + t)) + (template-class (and (eq context '<>) + (save-excursion + (goto-char match-pos) + (c-forward-syntactic-ws) + (looking-at c-template-typename-key))))) ;; Fix the `c-decl-id-start' or `c-decl-type-start' property ;; before the first declarator if it's a list. ;; `c-font-lock-declarators' handles the rest. @@ -1309,10 +1377,9 @@ casts and declarations are fontified. Used on level 2 and higher." (if (cadr decl-or-cast) 'c-decl-type-start 'c-decl-id-start))))) - (c-font-lock-declarators (min limit (point-max)) decl-list - (cadr decl-or-cast) (not toplev))) + (cadr decl-or-cast) (not toplev) template-class)) ;; A declaration has been successfully identified, so do all the ;; fontification of types and refs that've been recorded. @@ -1375,7 +1442,6 @@ casts and declarations are fontified. Used on level 2 and higher." ;; it finds any. That's necessary so that we later will ;; stop inside them to fontify types there. (c-parse-and-markup-<>-arglists t) - lbrace ; position of some {. ;; The font-lock package in Emacs is known to clobber ;; `parse-sexp-lookup-properties' (when it exists). (parse-sexp-lookup-properties @@ -1607,7 +1673,8 @@ casts and declarations are fontified. Used on level 2 and higher." ;; font-lock-keyword-face. It always returns NIL to inhibit this and ;; prevent a repeat invocation. See elisp/lispref page "Search-based ;; fontification". - (let ((decl-search-lim (c-determine-limit 1000)) + (let ((here (point)) + (decl-search-lim (c-determine-limit 1000)) paren-state encl-pos token-end context decl-or-cast start-pos top-level c-restricted-<>-arglists c-recognize-knr-p) ; Strictly speaking, bogus, but it @@ -1624,26 +1691,27 @@ casts and declarations are fontified. Used on level 2 and higher." (when (or (bobp) (memq (char-before) '(?\; ?{ ?}))) (setq token-end (point)) - (c-forward-syntactic-ws) - ;; We're now putatively at the declaration. - (setq start-pos (point)) - (setq paren-state (c-parse-state)) - ;; At top level or inside a "{"? - (if (or (not (setq encl-pos - (c-most-enclosing-brace paren-state))) - (eq (char-after encl-pos) ?\{)) - (progn - (setq top-level (c-at-toplevel-p)) - (let ((got-context (c-get-fontification-context - token-end nil top-level))) - (setq context (car got-context) - c-restricted-<>-arglists (cdr got-context))) - (setq decl-or-cast - (c-forward-decl-or-cast-1 token-end context nil)) - (when (consp decl-or-cast) - (goto-char start-pos) - (c-font-lock-single-decl limit decl-or-cast token-end - context top-level))))))) + (c-forward-syntactic-ws here) + (when (< (point) here) + ;; We're now putatively at the declaration. + (setq start-pos (point)) + (setq paren-state (c-parse-state)) + ;; At top level or inside a "{"? + (if (or (not (setq encl-pos + (c-most-enclosing-brace paren-state))) + (eq (char-after encl-pos) ?\{)) + (progn + (setq top-level (c-at-toplevel-p)) + (let ((got-context (c-get-fontification-context + token-end nil top-level))) + (setq context (car got-context) + c-restricted-<>-arglists (cdr got-context))) + (setq decl-or-cast + (c-forward-decl-or-cast-1 token-end context nil)) + (when (consp decl-or-cast) + (goto-char start-pos) + (c-font-lock-single-decl limit decl-or-cast token-end + context top-level)))))))) nil)) (defun c-font-lock-enclosing-decls (limit) @@ -1667,18 +1735,16 @@ casts and declarations are fontified. Used on level 2 and higher." (eq (char-after ps-elt) ?\{)) (goto-char ps-elt) (c-syntactic-skip-backward "^;{}" decl-search-lim) - (when (or (bobp) - (memq (char-before) '(?\; ?}))) - (c-forward-syntactic-ws) - (setq in-typedef (looking-at c-typedef-key)) - (if in-typedef (c-forward-token-2)) - (when (and c-opt-block-decls-with-vars-key - (looking-at c-opt-block-decls-with-vars-key)) - (goto-char ps-elt) - (when (c-safe (c-forward-sexp)) - (c-forward-syntactic-ws) - (c-font-lock-declarators limit t in-typedef - (not (c-bs-at-toplevel-p (point))))))))))) + (c-forward-syntactic-ws) + (setq in-typedef (looking-at c-typedef-key)) + (if in-typedef (c-forward-over-token-and-ws)) + (when (and c-opt-block-decls-with-vars-key + (looking-at c-opt-block-decls-with-vars-key)) + (goto-char ps-elt) + (when (c-safe (c-forward-sexp)) + (c-forward-syntactic-ws) + (c-font-lock-declarators limit t in-typedef + (not (c-bs-at-toplevel-p (point)))))))))) (defun c-font-lock-raw-strings (limit) ;; Fontify C++ raw strings. @@ -1955,85 +2021,6 @@ on level 2 only and so aren't combined with `c-complex-decl-matchers'." 2 font-lock-type-face) `(,(concat "\\<\\(" re "\\)\\>") 1 'font-lock-type-face))) - - ;; Fontify types preceded by `c-type-prefix-kwds' (e.g. "struct"). - ,@(when (c-lang-const c-type-prefix-kwds) - `((,(byte-compile - `(lambda (limit) - (c-fontify-types-and-refs - ((c-promote-possible-types t) - ;; The font-lock package in Emacs is known to clobber - ;; `parse-sexp-lookup-properties' (when it exists). - (parse-sexp-lookup-properties - (cc-eval-when-compile - (boundp 'parse-sexp-lookup-properties)))) - (save-restriction - ;; Narrow to avoid going past the limit in - ;; `c-forward-type'. - (narrow-to-region (point) limit) - (while (re-search-forward - ,(concat "\\<\\(" - (c-make-keywords-re nil - (c-lang-const c-type-prefix-kwds)) - "\\)\\>") - limit t) - (unless (c-skip-comments-and-strings limit) - (c-forward-syntactic-ws) - ;; Handle prefix declaration specifiers. - (while - (or - (when (or (looking-at c-prefix-spec-kwds-re) - (and (c-major-mode-is 'java-mode) - (looking-at "@[A-Za-z0-9]+"))) - (c-forward-keyword-clause 1) - t) - (when (and c-opt-cpp-prefix - (looking-at - c-noise-macro-with-parens-name-re)) - (c-forward-noise-clause) - t))) - ,(if (c-major-mode-is 'c++-mode) - `(when (and (c-forward-type) - (eq (char-after) ?=)) - ;; In C++ we additionally check for a "class - ;; X = Y" construct which is used in - ;; templates, to fontify Y as a type. - (forward-char) - (c-forward-syntactic-ws) - (c-forward-type)) - `(c-forward-type)) - ))))))))) - - ;; Fontify symbols after closing braces as declaration - ;; identifiers under the assumption that they are part of - ;; declarations like "class Foo { ... } foo;". It's too - ;; expensive to check this accurately by skipping past the - ;; brace block, so we use the heuristic that it's such a - ;; declaration if the first identifier is on the same line as - ;; the closing brace. `c-font-lock-declarations' will later - ;; override it if it turns out to be an new declaration, but - ;; it will be wrong if it's an expression (see the test - ;; decls-8.cc). -;; ,@(when (c-lang-const c-opt-block-decls-with-vars-key) -;; `((,(c-make-font-lock-search-function -;; (concat "}" -;; (c-lang-const c-single-line-syntactic-ws) -;; "\\(" ; 1 + c-single-line-syntactic-ws-depth -;; (c-lang-const c-type-decl-prefix-key) -;; "\\|" -;; (c-lang-const c-symbol-key) -;; "\\)") -;; `((c-font-lock-declarators limit t nil) ; That nil says use `font-lock-variable-name-face'; -;; ; t would mean `font-lock-function-name-face'. -;; (progn -;; (c-put-char-property (match-beginning 0) 'c-type -;; 'c-decl-id-start) -;; ; 'c-decl-type-start) -;; (goto-char (match-beginning -;; ,(1+ (c-lang-const -;; c-single-line-syntactic-ws-depth))))) -;; (goto-char (match-end 0))))))) - ;; Fontify the type in C++ "new" expressions. ,@(when (c-major-mode-is 'c++-mode) ;; This pattern is a probably a "(MATCHER . ANCHORED-HIGHLIGHTER)" @@ -2503,7 +2490,7 @@ need for `c++-font-lock-extra-types'.") limit "[-+]" nil - (lambda (match-pos inside-macro &optional top-level) + (lambda (_match-pos _inside-macro &optional _top-level) (forward-char) (c-font-lock-objc-method)))) nil) diff --git a/lisp/progmodes/cc-guess.el b/lisp/progmodes/cc-guess.el index 153b3a31e56..00d8bf08175 100644 --- a/lisp/progmodes/cc-guess.el +++ b/lisp/progmodes/cc-guess.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -494,8 +494,7 @@ is called with one argument, the guessed style." ;; If an entry in `c-offsets-alist' holds a guessed value, move it to ;; front in the field. In addition alphabetical sort by entry name is done. (setq style (copy-tree style)) - (let ((offsets-alist-cell (assq 'c-offsets-alist style)) - (guessed-syntactic-symbols (c-guess-guessed-syntactic-symbols))) + (let ((offsets-alist-cell (assq 'c-offsets-alist style))) (setcdr offsets-alist-cell (sort (cdr offsets-alist-cell) (lambda (a b) diff --git a/lisp/progmodes/cc-langs.el b/lisp/progmodes/cc-langs.el index a9d5ac34ad4..227b3e16485 100644 --- a/lisp/progmodes/cc-langs.el +++ b/lisp/progmodes/cc-langs.el @@ -26,7 +26,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -115,7 +115,7 @@ ;; For Emacs < 22.2. (eval-and-compile - (unless (fboundp 'declare-function) (defmacro declare-function (&rest r)))) + (unless (fboundp 'declare-function) (defmacro declare-function (&rest _)))) (eval-when-compile (let ((load-path @@ -130,7 +130,7 @@ ;; This file is not always loaded. See note above. -(cc-external-require (if (eq c--mapcan-status 'cl-mapcan) 'cl-lib 'cl)) +(cc-external-require (if (eq c--cl-library 'cl-lib) 'cl-lib 'cl)) ;;; Setup for the `c-lang-defvar' system. @@ -245,12 +245,12 @@ the evaluated constant value at compile time." (unless (listp (car-safe ops)) (setq ops (list ops))) (cond ((eq opgroup-filter t) - (setq opgroup-filter (lambda (opgroup) t))) + (setq opgroup-filter (lambda (_opgroup) t))) ((not (functionp opgroup-filter)) (setq opgroup-filter `(lambda (opgroup) (memq opgroup ',opgroup-filter))))) (cond ((eq op-filter t) - (setq op-filter (lambda (op) t))) + (setq op-filter (lambda (_op) t))) ((stringp op-filter) (setq op-filter `(lambda (op) (string-match ,op-filter op))))) @@ -474,18 +474,19 @@ so that all identifiers are recognized as words.") ;; The value here may be a list of functions or a single function. t nil c++ '(c-extend-region-for-CPP -; c-before-after-change-extend-region-for-lambda-capture ; doesn't seem needed. c-before-change-check-raw-strings c-before-change-check-<>-operators c-depropertize-CPP - c-before-after-change-digit-quote c-invalidate-macro-cache - c-truncate-bs-cache) + c-truncate-bs-cache + c-parse-quotes-before-change) (c objc) '(c-extend-region-for-CPP c-depropertize-CPP c-invalidate-macro-cache - c-truncate-bs-cache) - ;; java 'c-before-change-check-<>-operators + c-truncate-bs-cache + c-parse-quotes-before-change) + java 'c-parse-quotes-before-change + ;; 'c-before-change-check-<>-operators awk 'c-awk-record-region-clear-NL) (c-lang-defvar c-get-state-before-change-functions (let ((fs (c-lang-const c-get-state-before-change-functions))) @@ -515,18 +516,19 @@ parameters \(point-min) and \(point-max).") t '(c-depropertize-new-text c-change-expand-fl-region) (c objc) '(c-depropertize-new-text + c-parse-quotes-after-change c-extend-font-lock-region-for-macros c-neutralize-syntax-in-and-mark-CPP c-change-expand-fl-region) c++ '(c-depropertize-new-text + c-parse-quotes-after-change c-extend-font-lock-region-for-macros -; c-before-after-change-extend-region-for-lambda-capture ; doesn't seem needed. - c-before-after-change-digit-quote c-after-change-re-mark-raw-strings c-neutralize-syntax-in-and-mark-CPP c-restore-<>-properties c-change-expand-fl-region) java '(c-depropertize-new-text + c-parse-quotes-after-change c-restore-<>-properties c-change-expand-fl-region) awk '(c-depropertize-new-text @@ -609,6 +611,12 @@ EOL terminated statements." (c c++ objc) t) (c-lang-defvar c-has-bitfields (c-lang-const c-has-bitfields)) +(c-lang-defconst c-has-quoted-numbers + "Whether the language has numbers quoted like 4'294'967'295." + t nil + c++ t) +(c-lang-defvar c-has-quoted-numbers (c-lang-const c-has-quoted-numbers)) + (c-lang-defconst c-modified-constant "Regexp that matches a “modified” constant literal such as \"L\\='a\\='\", a “long character”. In particular, this recognizes forms of constant @@ -944,6 +952,11 @@ expression, or nil if there aren't any in the language." '("defined")) pike '("defined" "efun" "constant")) +(c-lang-defconst c-cpp-expr-functions-key + ;; Matches a function in a cpp expression. + t (c-make-keywords-re t (c-lang-const c-cpp-expr-functions))) +(c-lang-defvar c-cpp-expr-functions-key (c-lang-const c-cpp-expr-functions-key)) + (c-lang-defconst c-assignment-operators "List of all assignment operators." t '("=" "*=" "/=" "%=" "+=" "-=" ">>=" "<<=" "&=" "^=" "|=") @@ -1177,6 +1190,24 @@ This regexp is assumed to not match any non-operator identifier." (make-obsolete-variable 'c-opt-op-identitier-prefix 'c-opt-op-identifier-prefix "CC Mode 5.31.4, 2006-04-14") +(c-lang-defconst c-ambiguous-overloadable-or-identifier-prefixes + ;; A list of strings which can be either overloadable operators or + ;; identifier prefixes. + t (c--intersection + (c-filter-ops (c-lang-const c-identifier-ops) + '(prefix) + t) + (c-lang-const c-overloadable-operators) + :test 'string-equal)) + +(c-lang-defconst c-ambiguous-overloadable-or-identifier-prefix-re + ;; A regexp matching strings which can be either overloadable operators + ;; or identifier prefixes. + t (c-make-keywords-re + t (c-lang-const c-ambiguous-overloadable-or-identifier-prefixes))) +(c-lang-defvar c-ambiguous-overloadable-or-identifier-prefix-re + (c-lang-const c-ambiguous-overloadable-or-identifier-prefix-re)) + (c-lang-defconst c-other-op-syntax-tokens "List of the tokens made up of characters in the punctuation or parenthesis syntax classes that have uses other than as expression @@ -1865,6 +1896,17 @@ the type of that expression." t (c-make-keywords-re t (c-lang-const c-typeof-kwds))) (c-lang-defvar c-typeof-key (c-lang-const c-typeof-key)) +(c-lang-defconst c-template-typename-kwds + "Keywords which, within a template declaration, can introduce a +declaration with a type as a default value. This is used only in +C++ Mode, e.g. \"<typename X = Y>\"." + t nil + c++ '("class" "typename")) + +(c-lang-defconst c-template-typename-key + t (c-make-keywords-re t (c-lang-const c-template-typename-kwds))) +(c-lang-defvar c-template-typename-key (c-lang-const c-template-typename-key)) + (c-lang-defconst c-type-prefix-kwds "Keywords where the following name - if any - is a type name, and where the keyword together with the symbol works as a type in @@ -2258,6 +2300,18 @@ one of `c-type-list-kwds', `c-ref-list-kwds', c++ '("private" "protected" "public") objc '("@private" "@protected" "@public")) +(c-lang-defconst c-protection-key + ;; A regexp match an element of `c-protection-kwds' cleanly. + t (c-make-keywords-re t (c-lang-const c-protection-kwds))) +(c-lang-defvar c-protection-key (c-lang-const c-protection-key)) + +(c-lang-defconst c-post-protection-token + "The token which (may) follow a protection keyword, +e.g. the \":\" in C++ Mode's \"public:\". nil if there is no such token." + t nil + c++ ":") +(c-lang-defvar c-post-protection-token (c-lang-const c-post-protection-token)) + (c-lang-defconst c-block-decls-with-vars "Keywords introducing declarations that can contain a block which might be followed by variable declarations, e.g. like \"foo\" in @@ -2844,14 +2898,7 @@ Note that Java specific rules are currently applied to tell this from left-assoc right-assoc right-assoc-sequence) - t)) - - (unambiguous-prefix-ops (c--set-difference nonkeyword-prefix-ops - in-or-postfix-ops - :test 'string-equal)) - (ambiguous-prefix-ops (c--intersection nonkeyword-prefix-ops - in-or-postfix-ops - :test 'string-equal))) + t))) (concat "\\(" diff --git a/lisp/progmodes/cc-menus.el b/lisp/progmodes/cc-menus.el index 126b419128c..7dae8297fd3 100644 --- a/lisp/progmodes/cc-menus.el +++ b/lisp/progmodes/cc-menus.el @@ -25,7 +25,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -117,7 +117,7 @@ A sample value might look like: `\\(_P\\|_PROTO\\)'.") ,(concat "^\\<" ; line MUST start with word char ;; \n added to prevent overflow in regexp matcher. - ;; http://lists.gnu.org/archive/html/emacs-pretest-bug/2007-02/msg00021.html + ;; https://lists.gnu.org/r/emacs-pretest-bug/2007-02/msg00021.html "[^()\n]*" ; no parentheses before "[^" c-alnum "_:<>~]" ; match any non-identifier char "\\([" c-alpha "_][" c-alnum "_:<>~]*\\)" ; match function name diff --git a/lisp/progmodes/cc-mode.el b/lisp/progmodes/cc-mode.el index a501ebba256..22dea039cd1 100644 --- a/lisp/progmodes/cc-mode.el +++ b/lisp/progmodes/cc-mode.el @@ -25,7 +25,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -225,18 +225,7 @@ control). See \"cc-mode.el\" for more info." (defun c-make-inherited-keymap () (let ((map (make-sparse-keymap))) - ;; Necessary to use `cc-bytecomp-fboundp' below since this - ;; function is called from top-level forms that are evaluated - ;; while cc-bytecomp is active when one does M-x eval-buffer. - (cond - ;; Emacs - ((cc-bytecomp-fboundp 'set-keymap-parent) - (set-keymap-parent map c-mode-base-map)) - ;; XEmacs - ((fboundp 'set-keymap-parents) - (set-keymap-parents map c-mode-base-map)) - ;; incompatible - (t (error "CC Mode is incompatible with this version of Emacs"))) + (c-set-keymap-parent map c-mode-base-map) map)) (defun c-define-abbrev-table (name defs &optional doc) @@ -276,6 +265,8 @@ control). See \"cc-mode.el\" for more info." nil (setq c-mode-base-map (make-sparse-keymap)) + (when (boundp 'prog-mode-map) + (c-set-keymap-parent c-mode-base-map prog-mode-map)) ;; Separate M-BS from C-M-h. The former should remain ;; backward-kill-word. @@ -398,7 +389,8 @@ control). See \"cc-mode.el\" for more info." ;;(define-key c-mode-base-map "\C-c\C-v" 'c-version) ;; (define-key c-mode-base-map "\C-c\C-y" 'c-toggle-hungry-state) Commented out by ACM, 2005-11-22. (define-key c-mode-base-map "\C-c\C-w" 'c-subword-mode) - (define-key c-mode-base-map "\C-c\C-k" 'c-toggle-comment-style)) + (define-key c-mode-base-map "\C-c\C-k" 'c-toggle-comment-style) + (define-key c-mode-base-map "\C-c\C-z" 'c-display-defun-name)) ;; We don't require the outline package, but we configure it a bit anyway. (cc-bytecomp-defvar outline-level) @@ -446,27 +438,36 @@ preferably use the `c-mode-menu' language constant directly." t)))) (defun c-unfind-coalesced-tokens (beg end) - ;; unless the non-empty region (beg end) is entirely WS and there's at - ;; least one character of WS just before or after this region, remove - ;; the tokens which touch the region from `c-found-types' should they - ;; be present. - (or (c-partial-ws-p beg end) - (save-excursion - (progn - (goto-char beg) - (or (eq beg (point-min)) - (c-skip-ws-backward (1- beg)) - (/= (point) beg) - (= (c-backward-token-2) 1) - (c-unfind-type (buffer-substring-no-properties - (point) beg))) - (goto-char end) - (or (eq end (point-max)) - (c-skip-ws-forward (1+ end)) - (/= (point) end) - (progn (forward-char) (c-end-of-current-token) nil) - (c-unfind-type (buffer-substring-no-properties - end (point)))))))) + ;; If removing the region (beg end) would coalesce an identifier ending at + ;; beg with an identifier (fragment) beginning at end, or an identifier + ;; fragment ending at beg with an identifier beginning at end, remove the + ;; pertinent identifier(s) from `c-found-types'. + (save-excursion + (when (< beg end) + (goto-char beg) + (when + (and (not (bobp)) + (progn (c-backward-syntactic-ws) (eq (point) beg)) + (/= (skip-chars-backward c-symbol-chars (1- (point))) 0) + (progn (goto-char beg) (c-forward-syntactic-ws) (<= (point) end)) + (> (point) beg) + (goto-char end) + (looking-at c-symbol-char-key)) + (goto-char beg) + (c-simple-skip-symbol-backward) + (c-unfind-type (buffer-substring-no-properties (point) beg))) + + (goto-char end) + (when + (and (not (eobp)) + (progn (c-forward-syntactic-ws) (eq (point) end)) + (looking-at c-symbol-char-key) + (progn (c-backward-syntactic-ws) (>= (point) beg)) + (< (point) end) + (/= (skip-chars-backward c-symbol-chars (1- (point))) 0)) + (goto-char (1+ end)) + (c-end-of-current-token) + (c-unfind-type (buffer-substring-no-properties end (point))))))) ;; c-maybe-stale-found-type records a place near the region being ;; changed where an element of `found-types' might become stale. It @@ -927,7 +928,7 @@ Note that the style variables are always made local to the buffer." (c-clear-char-property-with-value m-beg (point) 'syntax-table '(1))))) -(defun c-extend-region-for-CPP (beg end) +(defun c-extend-region-for-CPP (_beg _end) ;; Adjust `c-new-BEG', `c-new-END' respectively to the beginning and end of ;; any preprocessor construct they may be in. ;; @@ -951,7 +952,7 @@ Note that the style variables are always made local to the buffer." (when (> (point) c-new-END) (setq c-new-END (min (point) (c-determine-+ve-limit 500 c-new-END))))) -(defun c-depropertize-new-text (beg end old-len) +(defun c-depropertize-new-text (beg end _old-len) ;; Remove from the new text in (BEG END) any and all text properties which ;; might interfere with CC Mode's proper working. ;; @@ -970,7 +971,7 @@ Note that the style variables are always made local to the buffer." (c-clear-char-properties beg end 'c-type) (c-clear-char-properties beg end 'c-awk-NL-prop)))) -(defun c-extend-font-lock-region-for-macros (begg endd old-len) +(defun c-extend-font-lock-region-for-macros (_begg endd _old-len) ;; Extend the region (c-new-BEG c-new-END) to cover all (possibly changed) ;; preprocessor macros; The return value has no significance. ;; @@ -1015,7 +1016,7 @@ Note that the style variables are always made local to the buffer." t) (t nil))))))) -(defun c-neutralize-syntax-in-and-mark-CPP (begg endd old-len) +(defun c-neutralize-syntax-in-and-mark-CPP (_begg _endd _old-len) ;; (i) "Neutralize" every preprocessor line wholly or partially in the ;; changed region. "Restore" lines which were CPP lines before the change ;; and are no longer so. @@ -1083,101 +1084,234 @@ Note that the style variables are always made local to the buffer." (forward-line)) ; no infinite loop with, e.g., "#//" ))))) -(defun c-before-after-change-digit-quote (beg end &optional old-len) - ;; This function either removes or applies the punctuation value ('(1)) of - ;; the `syntax-table' text property on single quote marks which are - ;; separator characters in long integer literals, e.g. "4'294'967'295". It - ;; applies to both decimal/octal and hex literals. (FIXME (2016-06-10): it - ;; should also apply to binary literals.) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Parsing of quotes. +;; +;; Valid digit separators in numbers will get the syntax-table "punctuation" +;; property, '(1), and also the text property `c-digit-separator' value t. +;; +;; Invalid other quotes (i.e. those not validly bounding a single character, +;; or escaped character) will get the syntax-table "punctuation" property, +;; '(1), too. +;; +;; Note that, for convenience, these properties are applied even inside +;; comments and strings. + +(defconst c-maybe-quoted-number-head + (concat + "\\(0\\(" + "\\([Xx]\\([0-9a-fA-F]\\('[0-9a-fA-F]\\|[0-9a-fA-F]\\)*'?\\)?\\)" + "\\|" + "\\([Bb]\\([01]\\('[01]\\|[01]\\)*'?\\)?\\)" + "\\|" + "\\('[0-7]\\|[0-7]\\)*'?" + "\\)" + "\\|" + "[1-9]\\('[0-9]\\|[0-9]\\)*'?" + "\\)") + "Regexp matching the head of a numeric literal, including with digit separators.") + +(defun c-quoted-number-head-before-point () + ;; Return non-nil when the head of a possibly quoted number is found + ;; immediately before point. The value returned in this case is the buffer + ;; position of the start of the head. That position is also in + ;; (match-beginning 0). + (when c-has-quoted-numbers + (save-excursion + (let ((here (point)) + found) + (skip-chars-backward "0-9a-fA-F'") + (if (and (memq (char-before) '(?x ?X)) + (eq (char-before (1- (point))) ?0)) + (backward-char 2)) + (while + (and + (setq found + (search-forward-regexp c-maybe-quoted-number-head here t)) + (< found here))) + (and (eq found here) (match-beginning 0)))))) + +(defconst c-maybe-quoted-number-tail + (concat + "\\(" + "\\([xX']?[0-9a-fA-F]\\('[0-9a-fA-F]\\|[0-9a-fA-F]\\)*\\)" + "\\|" + "\\([bB']?[01]\\('[01]\\|[01]\\)*\\)" + "\\|" + "\\('?[0-9]\\('[0-9]\\|[0-9]\\)*\\)" + "\\)") + "Regexp matching the tail of a numeric literal, including with digit separators. +Note that this is a strict tail, so won't match, e.g. \"0x....\".") + +(defun c-quoted-number-tail-after-point () + ;; Return non-nil when a proper tail of a possibly quoted number is found + ;; immediately after point. The value returned in this case is the buffer + ;; position of the end of the tail. That position is also in (match-end 0). + (when c-has-quoted-numbers + (and (looking-at c-maybe-quoted-number-tail) + (match-end 0)))) + +(defconst c-maybe-quoted-number + (concat + "\\(0\\(" + "\\([Xx][0-9a-fA-F]\\('[0-9a-fA-F]\\|[0-9a-fA-F]\\)*\\)" + "\\|" + "\\([Bb][01]\\('[01]\\|[01]\\)*\\)" + "\\|" + "\\('[0-7]\\|[0-7]\\)*" + "\\)" + "\\|" + "[1-9]\\('[0-9]\\|[0-9]\\)*" + "\\)") + "Regexp matching a numeric literal, including with digit separators.") + +(defun c-quoted-number-straddling-point () + ;; Return non-nil if a definitely quoted number starts before point and ends + ;; after point. In this case the number is bounded by (match-beginning 0) + ;; and (match-end 0). + (when c-has-quoted-numbers + (save-excursion + (let ((here (point)) + (bound (progn (skip-chars-forward "0-9a-fA-F'") (point)))) + (goto-char here) + (when (< (skip-chars-backward "0-9a-fA-F'") 0) + (if (and (memq (char-before) '(?x ?X)) + (eq (char-before (1- (point))) ?0)) + (backward-char 2)) + (while (and (search-forward-regexp c-maybe-quoted-number bound t) + (<= (match-end 0) here))) + (and (< (match-beginning 0) here) + (> (match-end 0) here) + (save-match-data + (goto-char (match-beginning 0)) + (save-excursion (search-forward "'" (match-end 0) t))))))))) + +(defun c-parse-quotes-before-change (beg end) + ;; This function analyzes 's near the region (c-new-BEG c-new-END), amending + ;; those two variables as needed to include 's into that region when they + ;; might be syntactically relevant to the change in progress. ;; - ;; In both uses of the function, the `syntax-table' properties are - ;; removed/applied only on quote marks which appear to be digit separators. + ;; Having amended that region, the function removes pertinent text + ;; properties (syntax-table properties with value '(1) and c-digit-separator + ;; props with value t) from 's in it. This operation is performed even + ;; within strings and comments. ;; - ;; Point is undefined on both entry and exit to this function, and the - ;; return value has no significance. The function is called solely as a - ;; before-change function (see `c-get-state-before-change-functions') and as - ;; an after change function (see `c-before-font-lock-functions', with the - ;; parameters BEG, END, and (optionally) OLD-LEN being given the standard - ;; values for before/after-change functions. - (c-save-buffer-state ((num-begin c-new-BEG) digit-re try-end) + ;; This function is called exclusively as a before-change function via the + ;; variable `c-get-state-before-change-functions'. + (c-save-buffer-state () + (goto-char c-new-BEG) + ;; We need to scan for 's from the BO (logical) line. + (beginning-of-line) + (while (eq (char-before (1- (point))) ?\\) + (beginning-of-line 0)) + (while (and (< (point) c-new-BEG) + (search-forward "'" c-new-BEG t)) + (cond + ((c-quoted-number-straddling-point) + (goto-char (match-end 0)) + (if (> (match-end 0) c-new-BEG) + (setq c-new-BEG (match-beginning 0)))) + ((c-quoted-number-head-before-point) + (if (>= (point) c-new-BEG) + (setq c-new-BEG (match-beginning 0)))) + ((looking-at "\\([^'\\]\\|\\\\.\\)'") + (goto-char (match-end 0)) + (if (> (match-end 0) c-new-BEG) + (setq c-new-BEG (1- (match-beginning 0))))) + ((or (>= (point) (1- c-new-BEG)) + (and (eq (point) (- c-new-BEG 2)) + (eq (char-after) ?\\))) + (setq c-new-BEG (1- (point)))) + (t nil))) + + (goto-char c-new-END) + ;; We will scan from the BO (logical) line. + (beginning-of-line) + (while (eq (char-before (1- (point))) ?\\) + (beginning-of-line 0)) + (while (and (< (point) c-new-END) + (search-forward "'" c-new-END t)) + (cond + ((c-quoted-number-straddling-point) + (goto-char (match-end 0)) + (if (> (match-end 0) c-new-END) + (setq c-new-END (match-end 0)))) + ((c-quoted-number-tail-after-point) + (goto-char (match-end 0)) + (if (> (match-end 0) c-new-END) + (setq c-new-END (match-end 0)))) + ((looking-at "\\([^'\\]\\|\\\\.\\)'") + (goto-char (match-end 0)) + (if (> (match-end 0) c-new-END) + (setq c-new-END (match-end 0)))) + (t nil))) + ;; Having reached c-new-END, handle any 's after it whose context may be + ;; changed by the current buffer change. (goto-char c-new-END) - (when (looking-at "\\(x\\)?[0-9a-fA-F']+") + (cond + ((c-quoted-number-tail-after-point) (setq c-new-END (match-end 0))) - (goto-char c-new-BEG) - (when (looking-at "\\(x?\\)[0-9a-fA-F']") - (if (re-search-backward "\\(0x\\)?[0-9a-fA-F]*\\=" nil t) - (setq c-new-BEG (point)))) + ((looking-at + "\\(\\\\.\\|.\\)?\\('\\([^'\\]\\|\\\\.\\)\\)*'") + (setq c-new-END (match-end 0)))) - (while - (re-search-forward "[0-9a-fA-F]'[0-9a-fA-F]" c-new-END t) - (setq try-end (1- (point))) - (re-search-backward "[^0-9a-fA-F']" num-begin t) - (setq digit-re - (cond - ((and (not (bobp)) (eq (char-before) ?0) (memq (char-after) '(?x ?X))) - "[0-9a-fA-F]") - ((and (eq (char-after (1+ (point))) ?0) - (memq (char-after (+ 2 (point))) '(?b ?B))) - "[01]") - ((memq (char-after (1+ (point))) '(?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9)) - "[0-9]") - (t nil))) - (when digit-re - (cond ((eq (char-after) ?x) (forward-char)) - ((looking-at ".?0[Bb]") (goto-char (match-end 0))) - ((looking-at digit-re)) - (t (forward-char))) - (when (not (c-in-literal)) - (let ((num-end ; End of valid sequence of digits/quotes. - (save-excursion - (re-search-forward - (concat "\\=\\(" digit-re "+'\\)*" digit-re "+") nil t) - (point)))) - (setq try-end ; End of sequence of digits/quotes - (save-excursion - (re-search-forward - (concat "\\=\\(" digit-re "\\|'\\)+") nil t) - (point))) - (while (re-search-forward - (concat digit-re "\\('\\)" digit-re) num-end t) - (if old-len ; i.e. are we in an after-change function? - (c-put-char-property (match-beginning 1) 'syntax-table '(1)) - (c-clear-char-property (match-beginning 1) 'syntax-table)) - (backward-char))))) - (goto-char try-end) - (setq num-begin (point))))) - -;; The following doesn't seem needed at the moment (2016-08-15). -;; (defun c-before-after-change-extend-region-for-lambda-capture -;; (_beg _end &optional _old-len) -;; ;; In C++ Mode, extend the region (c-new-BEG c-new-END) to cover any lambda -;; ;; function capture lists we happen to be inside. This function is expected -;; ;; to be called both as a before-change and after change function. -;; ;; -;; ;; Note that these things _might_ be nested, with a capture list looking -;; ;; like: -;; ;; -;; ;; [ ...., &foo = [..](){...}(..), ... ] -;; ;; -;; ;; . What a wonderful language is C++. ;-) -;; (c-save-buffer-state (paren-state pos) -;; (goto-char c-new-BEG) -;; (setq paren-state (c-parse-state)) -;; (while (setq pos (c-pull-open-brace paren-state)) -;; (goto-char pos) -;; (when (c-looking-at-c++-lambda-capture-list) -;; (setq c-new-BEG (min c-new-BEG pos)) -;; (if (c-go-list-forward) -;; (setq c-new-END (max c-new-END (point)))))) - -;; (goto-char c-new-END) -;; (setq paren-state (c-parse-state)) -;; (while (setq pos (c-pull-open-brace paren-state)) -;; (goto-char pos) -;; (when (c-looking-at-c++-lambda-capture-list) -;; (setq c-new-BEG (min c-new-BEG pos)) -;; (if (c-go-list-forward) -;; (setq c-new-END (max c-new-END (point)))))))) + ;; Remove the '(1) syntax-table property from any "'"s within (c-new-BEG + ;; c-new-END). + (goto-char c-new-BEG) + (when (c-search-forward-char-property-with-value-on-char + 'syntax-table '(1) ?\' c-new-END) + (c-invalidate-state-cache (1- (point))) + (c-truncate-semi-nonlit-pos-cache (1- (point))) + (c-clear-char-property-with-value-on-char + (1- (point)) c-new-END + 'syntax-table '(1) + ?') + ;; Remove the c-digit-separator text property from the same "'"s. + (when c-has-quoted-numbers + (c-clear-char-property-with-value-on-char + (1- (point)) c-new-END + 'c-digit-separator t + ?'))))) + +(defun c-parse-quotes-after-change (beg end old-len) + ;; This function applies syntax-table properties (value '(1)) and + ;; c-digit-separator properties as needed to 's within the range (c-new-BEG + ;; c-new-END). This operation is performed even within strings and + ;; comments. + ;; + ;; This function is called exclusively as an after-change function via the + ;; variable `c-before-font-lock-functions'. + (c-save-buffer-state (num-beg num-end) + ;; Apply the needed syntax-table and c-digit-separator text properties to + ;; quotes. + (save-restriction + (goto-char c-new-BEG) + (while (and (< (point) c-new-END) + (search-forward "'" c-new-END 'limit)) + (cond ((and (eq (char-before (1- (point))) ?\\) + ;; Check we've got an odd number of \s, here. + (save-excursion + (backward-char) + (eq (logand (skip-chars-backward "\\\\") 1) 1)))) ; not a real '. + ((c-quoted-number-straddling-point) + (setq num-beg (match-beginning 0) + num-end (match-end 0)) + (c-invalidate-state-cache num-beg) + (c-truncate-semi-nonlit-pos-cache num-beg) + (c-put-char-properties-on-char num-beg num-end + 'syntax-table '(1) ?') + (c-put-char-properties-on-char num-beg num-end + 'c-digit-separator t ?') + (goto-char num-end)) + ((looking-at "\\([^\\']\\|\\\\.\\)'") ; balanced quoted expression. + (goto-char (match-end 0))) + (t + (c-invalidate-state-cache (1- (point))) + (c-truncate-semi-nonlit-pos-cache (1- (point))) + (c-put-char-property (1- (point)) 'syntax-table '(1)))) + ;; Prevent the next `c-quoted-number-straddling-point' getting + ;; confused by already processed single quotes. + (narrow-to-region (point) (point-max)))))) (defun c-before-change (beg end) ;; Function to be put on `before-change-functions'. Primarily, this calls @@ -1393,14 +1527,17 @@ Note that the style variables are always made local to the buffer." (> (point) bod-lim) (progn (c-forward-syntactic-ws) (setq bo-decl (point)) - ;; Are we looking at a keyword such as "template" or - ;; "typedef" which can decorate a type, or the type itself? - (when (or (looking-at c-prefix-spec-kwds-re) - (c-forward-type t)) - ;; We've found another candidate position. - (setq new-pos (min new-pos bo-decl)) - (goto-char bo-decl)) - t) + (or (not (looking-at c-protection-key)) + (c-forward-keyword-clause 1))) + (progn + ;; Are we looking at a keyword such as "template" or + ;; "typedef" which can decorate a type, or the type itself? + (when (or (looking-at c-prefix-spec-kwds-re) + (c-forward-type t)) + ;; We've found another candidate position. + (setq new-pos (min new-pos bo-decl)) + (goto-char bo-decl)) + t) ;; Try and go out a level to search again. (progn (c-backward-syntactic-ws bod-lim) @@ -1421,6 +1558,26 @@ Note that the style variables are always made local to the buffer." (setq new-pos capture-opener)) (and (/= new-pos pos) new-pos))) +(defun c-fl-decl-end (pos) + ;; If POS is inside a declarator, return the end of the token that follows + ;; the declarator, otherwise return nil. + (goto-char pos) + (let ((lit-start (c-literal-start)) + pos1) + (if lit-start (goto-char lit-start)) + (c-backward-syntactic-ws) + (when (setq pos1 (c-on-identifier)) + (goto-char pos1) + (let ((lim (save-excursion + (and (c-beginning-of-macro) + (progn (c-end-of-macro) (point)))))) + (when (and (c-forward-declarator lim) + (or (not (eq (char-after) ?\()) + (c-go-list-forward nil lim)) + (eq (c-forward-token-2 1 nil lim) 0)) + (c-backward-syntactic-ws) + (point)))))) + (defun c-change-expand-fl-region (_beg _end _old-len) ;; Expand the region (c-new-BEG c-new-END) to an after-change font-lock ;; region. This will usually be the smallest sequence of whole lines @@ -1434,18 +1591,16 @@ Note that the style variables are always made local to the buffer." (setq c-new-BEG (or (c-fl-decl-start c-new-BEG) (c-point 'bol c-new-BEG)) c-new-END - (save-excursion - (goto-char c-new-END) - (if (bolp) - (point) - (c-point 'bonl c-new-END)))))) + (or (c-fl-decl-end c-new-END) + (c-point 'bonl c-new-END))))) (defun c-context-expand-fl-region (beg end) ;; Return a cons (NEW-BEG . NEW-END), where NEW-BEG is the beginning of a ;; "local" declaration containing BEG (see `c-fl-decl-start') or BOL BEG is ;; in. NEW-END is beginning of the line after the one END is in. - (cons (or (c-fl-decl-start beg) (c-point 'bol beg)) - (c-point 'bonl end))) + (c-save-buffer-state () + (cons (or (c-fl-decl-start beg) (c-point 'bol beg)) + (or (c-fl-decl-end end) (c-point 'bonl (1- end)))))) (defun c-before-context-fl-expand-region (beg end) ;; Expand the region (BEG END) as specified by @@ -1704,7 +1859,7 @@ Key bindings: ;;;###autoload (defun c-or-c++-mode () - "Analyse buffer and enable either C or C++ mode. + "Analyze buffer and enable either C or C++ mode. Some people and projects use .h extension for C++ header files which is also the one used for C header files. This makes diff --git a/lisp/progmodes/cc-styles.el b/lisp/progmodes/cc-styles.el index b3848a74f97..1a8d90bacd3 100644 --- a/lisp/progmodes/cc-styles.el +++ b/lisp/progmodes/cc-styles.el @@ -26,7 +26,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -47,6 +47,7 @@ ;; `c-add-style' often contains references to functions defined there. ;; Silence the compiler. +(cc-bytecomp-defun c-guess-basic-syntax) (cc-bytecomp-defvar adaptive-fill-first-line-regexp) ; Emacs diff --git a/lisp/progmodes/cc-vars.el b/lisp/progmodes/cc-vars.el index ccd4fd29940..c4213797636 100644 --- a/lisp/progmodes/cc-vars.el +++ b/lisp/progmodes/cc-vars.el @@ -26,7 +26,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -87,7 +87,7 @@ use c-constant-symbol instead." :value nil :tag "Symbol" :format "%t: %v\n%d" - :match (lambda (widget value) (symbolp value)) + :match (lambda (_widget value) (symbolp value)) :value-to-internal (lambda (widget value) (let ((s (if (symbolp value) @@ -98,7 +98,7 @@ use c-constant-symbol instead." (setq s (concat s (make-string (- l (length s)) ?\ )))) s)) :value-to-external - (lambda (widget value) + (lambda (_widget value) (if (stringp value) (intern (progn (string-match "\\`[^ ]*" value) @@ -109,14 +109,14 @@ use c-constant-symbol instead." "An integer or the value nil." :value nil :tag "Optional integer" - :match (lambda (widget value) (or (integerp value) (null value)))) + :match (lambda (_widget value) (or (integerp value) (null value)))) (define-widget 'c-symbol-list 'sexp "A single symbol or a list of symbols." :tag "Symbols separated by spaces" :validate 'widget-field-validate :match - (lambda (widget value) + (lambda (_widget value) (or (symbolp value) (catch 'ok (while (listp value) @@ -125,7 +125,7 @@ use c-constant-symbol instead." (setq value (cdr value))) (null value)))) :value-to-internal - (lambda (widget value) + (lambda (_widget value) (cond ((null value) "") ((symbolp value) @@ -138,7 +138,7 @@ use c-constant-symbol instead." (t value))) :value-to-external - (lambda (widget value) + (lambda (_widget value) (if (stringp value) (let (list end) (while (string-match "\\S +" value end) @@ -167,7 +167,7 @@ use c-constant-symbol instead." (defmacro defcustom-c-stylevar (name val doc &rest args) "Define a style variable NAME with VAL and DOC. More precisely, convert the given `:type FOO', mined out of ARGS, -to an aggregate `:type (radio STYLE (PREAMBLE FOO))', append some +to an aggregate `:type (radio STYLE (PREAMBLE FOO))', append some boilerplate documentation to DOC, arrange for the fallback value of NAME to be VAL, and call `custom-declare-variable' to do the rest of the work. @@ -1227,8 +1227,8 @@ As described below, each cons cell in this list has the form: When a line is indented, CC Mode first determines the syntactic context of it by generating a list of symbols called syntactic -elements. The global variable `c-syntactic-context' is bound to the -that list. Each element in the list is in turn a list where the first +elements. The global variable `c-syntactic-context' is bound to that +list. Each element in the list is in turn a list where the first element is a syntactic symbol which tells what kind of construct the indentation point is located within. More elements in the syntactic element lists are optional. If there is one more and it isn't nil, diff --git a/lisp/progmodes/cfengine.el b/lisp/progmodes/cfengine.el index 5bc7b660633..10881cda527 100644 --- a/lisp/progmodes/cfengine.el +++ b/lisp/progmodes/cfengine.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/progmodes/cmacexp.el b/lisp/progmodes/cmacexp.el index cafd5acb37a..883515e8fc2 100644 --- a/lisp/progmodes/cmacexp.el +++ b/lisp/progmodes/cmacexp.el @@ -27,7 +27,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/progmodes/compile.el b/lisp/progmodes/compile.el index 31ec5a67d03..4cce47e5d8c 100644 --- a/lisp/progmodes/compile.el +++ b/lisp/progmodes/compile.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -127,7 +127,21 @@ and a string describing how the process finished.") (defvar compilation-arguments nil "Arguments that were given to `compilation-start'.") -(defvar compilation-num-errors-found) +(defvar compilation-num-errors-found 0) +(defvar compilation-num-warnings-found 0) +(defvar compilation-num-infos-found 0) + +(defconst compilation-mode-line-errors + '(" [" (:propertize (:eval (int-to-string compilation-num-errors-found)) + face compilation-error + help-echo "Number of errors so far") + " " (:propertize (:eval (int-to-string compilation-num-warnings-found)) + face compilation-warning + help-echo "Number of warnings so far") + " " (:propertize (:eval (int-to-string compilation-num-infos-found)) + face compilation-info + help-echo "Number of informational messages so far") + "]")) ;; If you make any changes to `compilation-error-regexp-alist-alist', ;; be sure to run the ERT test in test/lisp/progmodes/compile-tests.el. @@ -886,10 +900,20 @@ from a different message." :group 'compilation :version "22.1") +(defun compilation-type (type) + (or (and (car type) (match-end (car type)) 1) + (and (cdr type) (match-end (cdr type)) 0) + 2)) + (defun compilation-face (type) - (or (and (car type) (match-end (car type)) compilation-warning-face) - (and (cdr type) (match-end (cdr type)) compilation-info-face) - compilation-error-face)) + (let ((typ (compilation-type type))) + (cond + ((eq typ 1) + compilation-warning-face) + ((eq typ 0) + compilation-info-face) + ((eq typ 2) + compilation-error-face)))) ;; LOC (or location) is a list of (COLUMN LINE FILE-STRUCTURE nil nil) @@ -1334,6 +1358,14 @@ FMTS is a list of format specs for transforming the file name. (compilation-parse-errors start end))) +(defun compilation--note-type (type) + "Note that a new message with severity TYPE was seen. +This updates the appropriate variable used by the mode-line." + (cl-case type + (0 (cl-incf compilation-num-infos-found)) + (1 (cl-incf compilation-num-warnings-found)) + (2 (cl-incf compilation-num-errors-found)))) + (defun compilation-parse-errors (start end &rest rules) "Parse errors between START and END. The errors recognized are the ones specified in RULES which default @@ -1397,14 +1429,17 @@ to `compilation-error-regexp-alist' if RULES is nil." file line end-line col end-col (or type 2) fmt)) (when (integerp file) + (setq type (if (consp type) + (compilation-type type) + (or type 2))) + (compilation--note-type type) + (compilation--put-prop file 'font-lock-face - (if (consp type) - (compilation-face type) - (symbol-value (aref [compilation-info-face - compilation-warning-face - compilation-error-face] - (or type 2)))))) + (symbol-value (aref [compilation-info-face + compilation-warning-face + compilation-error-face] + type)))) (compilation--put-prop line 'font-lock-face compilation-line-face) @@ -1705,7 +1740,7 @@ Returns the compilation buffer created." (setq thisdir default-directory)) (set-buffer-modified-p nil)) ;; Pop up the compilation buffer. - ;; http://lists.gnu.org/archive/html/emacs-devel/2007-11/msg01638.html + ;; https://lists.gnu.org/r/emacs-devel/2007-11/msg01638.html (setq outwin (display-buffer outbuf '(nil (allow-no-window . t)))) (with-current-buffer outbuf (let ((process-environment @@ -1768,7 +1803,8 @@ Returns the compilation buffer created." outbuf command)))) ;; Make the buffer's mode line show process state. (setq mode-line-process - '(:propertize ":%s" face compilation-mode-line-run)) + '((:propertize ":%s" face compilation-mode-line-run) + compilation-mode-line-errors)) ;; Set the process as killable without query by default. ;; This allows us to start a new compilation without @@ -1797,7 +1833,8 @@ Returns the compilation buffer created." (message "Executing `%s'..." command) ;; Fake mode line display as if `start-process' were run. (setq mode-line-process - '(:propertize ":run" face compilation-mode-line-run)) + '((:propertize ":run" face compilation-mode-line-run) + compilation-mode-line-errors)) (force-mode-line-update) (sit-for 0) ; Force redisplay (save-excursion @@ -2106,6 +2143,9 @@ Optional argument MINOR indicates this is called from (make-local-variable 'compilation-messages-start) (make-local-variable 'compilation-error-screen-columns) (make-local-variable 'overlay-arrow-position) + (setq-local compilation-num-errors-found 0) + (setq-local compilation-num-warnings-found 0) + (setq-local compilation-num-infos-found 0) (set (make-local-variable 'overlay-arrow-string) "") (setq next-error-overlay-arrow-position nil) (add-hook 'kill-buffer-hook @@ -2195,16 +2235,18 @@ commands of Compilation major mode are available. See (add-text-properties omax (point) (append '(compilation-handle-exit t) nil)) (setq mode-line-process - (let ((out-string (format ":%s [%s]" process-status (cdr status))) - (msg (format "%s %s" mode-name - (replace-regexp-in-string "\n?$" "" - (car status))))) - (message "%s" msg) - (propertize out-string - 'help-echo msg - 'face (if (> exit-status 0) - 'compilation-mode-line-fail - 'compilation-mode-line-exit)))) + (list + (let ((out-string (format ":%s [%s]" process-status (cdr status))) + (msg (format "%s %s" mode-name + (replace-regexp-in-string "\n?$" "" + (car status))))) + (message "%s" msg) + (propertize out-string + 'help-echo msg + 'face (if (> exit-status 0) + 'compilation-mode-line-fail + 'compilation-mode-line-exit))) + compilation-mode-line-errors)) ;; Force mode line redisplay soon. (force-mode-line-update) (if (and opoint (< opoint omax)) @@ -2286,7 +2328,7 @@ and runs `compilation-filter-hook'." (while (,< n 0) (setq opt pt) (or (setq pt (,property-change pt 'compilation-message)) - ;; Handle the case where where the first error message is + ;; Handle the case where the first error message is ;; at the start of the buffer, and n < 0. (if (or (eq (get-text-property ,limit 'compilation-message) (get-text-property opt 'compilation-message)) @@ -2813,7 +2855,7 @@ TRUE-DIRNAME is the `file-truename' of DIRNAME, if given." ;; The gethash used to not use spec-directory, but ;; this leads to errors when files in different ;; directories have the same name: - ;; http://lists.gnu.org/archive/html/emacs-devel/2007-08/msg00463.html + ;; https://lists.gnu.org/r/emacs-devel/2007-08/msg00463.html (or (gethash (cons filename spec-directory) compilation-locs) (puthash (cons filename spec-directory) (compilation--make-file-struct diff --git a/lisp/progmodes/cperl-mode.el b/lisp/progmodes/cperl-mode.el index c0f1aaf39d4..e6ab8c4ea60 100644 --- a/lisp/progmodes/cperl-mode.el +++ b/lisp/progmodes/cperl-mode.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Corrections made by Ilya Zakharevich ilyaz@cpan.org @@ -480,7 +480,7 @@ Font for POD headers." (defcustom cperl-highlight-variables-indiscriminately nil "Non-nil means perform additional highlighting on variables. Currently only changes how scalar variables are highlighted. -Note that that variable is only read at initialization time for +Note that the variable is only read at initialization time for the variable `cperl-font-lock-keywords-2', so changing it after you've entered CPerl mode the first time will have no effect." :type 'boolean @@ -701,24 +701,7 @@ This way enabling/disabling of menu items is more correct." ;;; Short extra-docs. (defvar cperl-tips 'please-ignore-this-line - "Get maybe newer version of this package from - http://ilyaz.org/software/emacs -Subdirectory `cperl-mode' may contain yet newer development releases and/or -patches to related files. - -For best results apply to an older Emacs the patches from - ftp://ftp.math.ohio-state.edu/pub/users/ilya/cperl-mode/patches -\(this upgrades syntax-parsing abilities of Emacsen v19.34 and -v20.2 up to the level of Emacs v20.3 - a must for a good Perl -mode.) As of beginning of 2003, XEmacs may provide a similar ability. - -Get support packages choose-color.el (or font-lock-extra.el before -19.30), imenu-go.el from the same place. \(Look for other files there -too... ;-). Get a patch for imenu.el in 19.29. Note that for 19.30 and -later you should use choose-color.el *instead* of font-lock-extra.el -\(and you will not get smart highlighting in C :-(). - -Note that to enable Compile choices in the menu you need to install + "Note that to enable Compile choices in the menu you need to install mode-compile.el. If your Emacs does not default to `cperl-mode' on Perl files, and you @@ -1913,7 +1896,9 @@ or as help on variables `cperl-tips', `cperl-problems', (if cperl-pod-here-scan (or cperl-syntaxify-by-font-lock (progn (or cperl-faces-init (cperl-init-faces-weak)) - (cperl-find-pods-heres))))) + (cperl-find-pods-heres)))) + ;; Setup Flymake + (add-hook 'flymake-diagnostic-functions 'perl-flymake nil t)) ;; Fix for perldb - make default reasonable (defun cperl-db () @@ -2331,7 +2316,7 @@ to nil." nil t)))) ; Only one (progn (forward-word-strictly 1) - (setq name (file-name-base) + (setq name (file-name-base (buffer-file-name)) p (point)) (insert " NAME\n\n" name " - \n\n=head1 SYNOPSIS\n\n\n\n" @@ -3734,7 +3719,7 @@ the sections using `cperl-pod-head-face', `cperl-pod-face', "\\(\\`\n?\\|^\n\\)=" ; POD "\\|" ;; One extra () before this: - "<<" ; HERE-DOC + "<<~?" ; HERE-DOC "\\(" ; 1 + 1 ;; First variant "BLAH" or just ``. "[ \t]*" ; Yes, whitespace is allowed! @@ -4000,7 +3985,7 @@ the sections using `cperl-pod-head-face', `cperl-pod-face', (setq b (point)) ;; We do not search to max, since we may be called from ;; some hook of fontification, and max is random - (or (and (re-search-forward (concat "^" qtag "$") + (or (and (re-search-forward (concat "^[ \t]*" qtag "$") stop-point 'toend) ;;;(eq (following-char) ?\n) ; XXXX WHY??? ) diff --git a/lisp/progmodes/cpp.el b/lisp/progmodes/cpp.el index e35a76e38cd..f49c8e934a5 100644 --- a/lisp/progmodes/cpp.el +++ b/lisp/progmodes/cpp.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -568,6 +568,14 @@ You can also use the keyboard accelerators indicated like this: [K]ey." (set-window-start nil start) (goto-char pos))) +(defun cpp-locate-user-emacs-file (file) + (locate-user-emacs-file + ;; Remove initial '.' from file. + (if (eq (aref file 0) ?.) + (substring file 1) + file) + file)) + (defun cpp-edit-load () "Load cpp configuration." (interactive) @@ -576,8 +584,8 @@ You can also use the keyboard accelerators indicated like this: [K]ey." nil) ((file-readable-p cpp-config-file) (load-file cpp-config-file)) - ((file-readable-p (concat "~/" cpp-config-file)) - (load-file cpp-config-file))) + ((file-readable-p (cpp-locate-user-emacs-file cpp-config-file)) + (load-file (cpp-locate-user-emacs-file cpp-config-file)))) (if (derived-mode-p 'cpp-edit-mode) (cpp-edit-reset))) @@ -586,7 +594,10 @@ You can also use the keyboard accelerators indicated like this: [K]ey." (interactive) (require 'pp) (with-current-buffer cpp-edit-buffer - (let ((buffer (find-file-noselect cpp-config-file))) + (let* ((config-file (if (file-writable-p cpp-config-file) + cpp-config-file + (cpp-locate-user-emacs-file cpp-config-file))) + (buffer (find-file-noselect config-file))) (set-buffer buffer) (erase-buffer) (pp (list 'setq 'cpp-known-face @@ -601,7 +612,7 @@ You can also use the keyboard accelerators indicated like this: [K]ey." (list 'quote cpp-unknown-writable)) buffer) (pp (list 'setq 'cpp-edit-list (list 'quote cpp-edit-list)) buffer) - (write-file cpp-config-file)))) + (write-file config-file)))) (defun cpp-edit-home () "Switch back to original buffer." diff --git a/lisp/progmodes/cwarn.el b/lisp/progmodes/cwarn.el index 1c6905a38fe..4b28d5a82aa 100644 --- a/lisp/progmodes/cwarn.el +++ b/lisp/progmodes/cwarn.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/progmodes/dcl-mode.el b/lisp/progmodes/dcl-mode.el index eb0850e4ec2..6681af55858 100644 --- a/lisp/progmodes/dcl-mode.el +++ b/lisp/progmodes/dcl-mode.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/progmodes/ebnf-abn.el b/lisp/progmodes/ebnf-abn.el index a3780eb70f4..937f9881ce9 100644 --- a/lisp/progmodes/ebnf-abn.el +++ b/lisp/progmodes/ebnf-abn.el @@ -2,8 +2,8 @@ ;; Copyright (C) 2001-2017 Free Software Foundation, Inc. -;; Author: Vinicius Jose Latorre <viniciusjl@ig.com.br> -;; Maintainer: Vinicius Jose Latorre <viniciusjl@ig.com.br> +;; Author: Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> +;; Maintainer: Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> ;; Keywords: wp, ebnf, PostScript ;; Old-Version: 1.2 ;; Package: ebnf2ps @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/progmodes/ebnf-bnf.el b/lisp/progmodes/ebnf-bnf.el index a257d391bf5..9cad4e5f2b6 100644 --- a/lisp/progmodes/ebnf-bnf.el +++ b/lisp/progmodes/ebnf-bnf.el @@ -2,8 +2,8 @@ ;; Copyright (C) 1999-2017 Free Software Foundation, Inc. -;; Author: Vinicius Jose Latorre <viniciusjl@ig.com.br> -;; Maintainer: Vinicius Jose Latorre <viniciusjl@ig.com.br> +;; Author: Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> +;; Maintainer: Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> ;; Keywords: wp, ebnf, PostScript ;; Old-Version: 1.10 ;; Package: ebnf2ps @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/progmodes/ebnf-dtd.el b/lisp/progmodes/ebnf-dtd.el index 2bea9547a1f..ee9f7b14e9b 100644 --- a/lisp/progmodes/ebnf-dtd.el +++ b/lisp/progmodes/ebnf-dtd.el @@ -2,8 +2,8 @@ ;; Copyright (C) 2001-2017 Free Software Foundation, Inc. -;; Author: Vinicius Jose Latorre <viniciusjl@ig.com.br> -;; Maintainer: Vinicius Jose Latorre <viniciusjl@ig.com.br> +;; Author: Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> +;; Maintainer: Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> ;; Keywords: wp, ebnf, PostScript ;; Old-Version: 1.1 ;; Package: ebnf2ps @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/progmodes/ebnf-ebx.el b/lisp/progmodes/ebnf-ebx.el index 84c67df63fa..6d1e761a1a5 100644 --- a/lisp/progmodes/ebnf-ebx.el +++ b/lisp/progmodes/ebnf-ebx.el @@ -2,8 +2,8 @@ ;; Copyright (C) 2001-2017 Free Software Foundation, Inc. -;; Author: Vinicius Jose Latorre <viniciusjl@ig.com.br> -;; Maintainer: Vinicius Jose Latorre <viniciusjl@ig.com.br> +;; Author: Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> +;; Maintainer: Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> ;; Keywords: wp, ebnf, PostScript ;; Old-Version: 1.2 ;; Package: ebnf2ps @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/progmodes/ebnf-iso.el b/lisp/progmodes/ebnf-iso.el index 8847c401508..61a3479a5c3 100644 --- a/lisp/progmodes/ebnf-iso.el +++ b/lisp/progmodes/ebnf-iso.el @@ -2,8 +2,8 @@ ;; Copyright (C) 1999-2017 Free Software Foundation, Inc. -;; Author: Vinicius Jose Latorre <viniciusjl@ig.com.br> -;; Maintainer: Vinicius Jose Latorre <viniciusjl@ig.com.br> +;; Author: Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> +;; Maintainer: Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> ;; Keywords: wp, ebnf, PostScript ;; Old-Version: 1.9 ;; Package: ebnf2ps @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/progmodes/ebnf-otz.el b/lisp/progmodes/ebnf-otz.el index 31dfd95e941..f77959e4ca2 100644 --- a/lisp/progmodes/ebnf-otz.el +++ b/lisp/progmodes/ebnf-otz.el @@ -2,8 +2,8 @@ ;; Copyright (C) 1999-2017 Free Software Foundation, Inc. -;; Author: Vinicius Jose Latorre <viniciusjl@ig.com.br> -;; Maintainer: Vinicius Jose Latorre <viniciusjl@ig.com.br> +;; Author: Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> +;; Maintainer: Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> ;; Keywords: wp, ebnf, PostScript ;; Old-Version: 1.0 ;; Package: ebnf2ps @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/progmodes/ebnf-yac.el b/lisp/progmodes/ebnf-yac.el index 3aa02a8e0fa..d8916ee4c0d 100644 --- a/lisp/progmodes/ebnf-yac.el +++ b/lisp/progmodes/ebnf-yac.el @@ -2,8 +2,8 @@ ;; Copyright (C) 1999-2017 Free Software Foundation, Inc. -;; Author: Vinicius Jose Latorre <viniciusjl@ig.com.br> -;; Maintainer: Vinicius Jose Latorre <viniciusjl@ig.com.br> +;; Author: Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> +;; Maintainer: Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> ;; Keywords: wp, ebnf, PostScript ;; Old-Version: 1.4 ;; Package: ebnf2ps @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/progmodes/ebnf2ps.el b/lisp/progmodes/ebnf2ps.el index a8229df4aeb..e40104353ac 100644 --- a/lisp/progmodes/ebnf2ps.el +++ b/lisp/progmodes/ebnf2ps.el @@ -1,9 +1,9 @@ -;;; ebnf2ps.el --- translate an EBNF to a syntactic chart on PostScript +;;; ebnf2ps.el --- translate an EBNF to a syntactic chart on PostScript -*- lexical-binding:t -*- ;; Copyright (C) 1999-2017 Free Software Foundation, Inc. -;; Author: Vinicius Jose Latorre <viniciusjl@ig.com.br> -;; Maintainer: Vinicius Jose Latorre <viniciusjl@ig.com.br> +;; Author: Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> +;; Maintainer: Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> ;; Keywords: wp, ebnf, PostScript ;; Version: 4.4 ;; X-URL: http://www.emacswiki.org/cgi-bin/wiki/ViniciusJoseLatorre @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. (defconst ebnf-version "4.4" "ebnf2ps.el, v 4.4 <2007/02/12 vinicius> @@ -30,8 +30,7 @@ Vinicius's last change version. When reporting bugs, please also report the version of Emacs, if any, that ebnf2ps was running with. Please send all bug fixes and enhancements to - Vinicius Jose Latorre <viniciusjl@ig.com.br>. -") + Vinicius Jose Latorre <viniciusjl.gnu@gmail.com>.") ;;; Commentary: @@ -1136,7 +1135,7 @@ Please send all bug fixes and enhancements to ;; Thanks to Drew Adams <drew.adams@oracle.com> for suggestions: ;; - `ebnf-arrow-extra-width', `ebnf-arrow-scale', ;; `ebnf-production-name-p', `ebnf-stop-on-error', -;; `ebnf-file-suffix-regexp'and `ebnf-special-show-delimiter' variables. +;; `ebnf-file-suffix-regexp' and `ebnf-special-show-delimiter' variables. ;; - `ebnf-delete-style', `ebnf-eps-file' and `ebnf-eps-directory' ;; commands. ;; - some docs fix. @@ -1154,6 +1153,7 @@ Please send all bug fixes and enhancements to (require 'ps-print) +(eval-when-compile (require 'cl-lib)) (and (string< ps-print-version "5.2.3") (error "`ebnf2ps' requires `ps-print' package version 5.2.3 or later")) @@ -2047,8 +2047,7 @@ It must be a float between 0.0 (top) and 1.0 (bottom)." (defcustom ebnf-default-width 0.6 - "Specify additional border width over default terminal, non-terminal or -special." + "Additional border width over default terminal, non-terminal or special." :type 'number :version "20" :group 'ebnf2ps) @@ -2252,7 +2251,7 @@ See also `ebnf-print-buffer'." (defun ebnf-print-buffer (&optional filename) "Generate and print a PostScript syntactic chart image of the buffer. -When called with a numeric prefix argument (C-u), prompts the user for +When called with a numeric prefix argument (\\[universal-argument]), prompts the user for the name of a file to save the PostScript image in, instead of sending it to the printer. @@ -2383,6 +2382,7 @@ WARNING: This function does *NOT* ask any confirmation to override existing (ebnf-log-header "(ebnf-eps-buffer)") (ebnf-eps-region (point-min) (point-max))) +(defvar ebnf-eps-executing) ;;;###autoload (defun ebnf-eps-region (from to) @@ -2411,7 +2411,7 @@ WARNING: This function does *NOT* ask any confirmation to override existing ;;;###autoload -(defalias 'ebnf-despool 'ps-despool) +(defalias 'ebnf-despool #'ps-despool) ;;;###autoload @@ -2611,7 +2611,8 @@ See also `ebnf-syntax-buffer'." (defvar ebnf-stack-style nil - "Used in functions `ebnf-reset-style', `ebnf-push-style' and + "Stack of styles. +Used in functions `ebnf-reset-style', `ebnf-push-style' and `ebnf-pop-style'.") @@ -3999,7 +4000,7 @@ See documentation for `ebnf-terminal-shape', `ebnf-non-terminal-shape' and % === end EBNF engine " - "EBNF PostScript prologue") + "EBNF PostScript prologue.") (defconst ebnf-eps-prologue @@ -4276,7 +4277,7 @@ See documentation for `ebnf-terminal-shape', `ebnf-non-terminal-shape' and }bind def " - "EBNF EPS prologue") + "EBNF EPS prologue.") (defconst ebnf-eps-begin @@ -4292,14 +4293,14 @@ end %%EndProlog " - "EBNF EPS begin") + "EBNF EPS begin.") (defconst ebnf-eps-end "#ebnf2ps#end %%EOF " - "EBNF EPS end") + "EBNF EPS end.") ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -4329,14 +4330,16 @@ end ;; hacked fom `ps-output-string-prim' (ps-print.el) (defun ebnf-eps-string (string) - (let* ((str (string-as-unibyte string)) + (let* ((str string) (len (length str)) (index 0) (new "(") ; insert start-string delimiter start special) ;; Find and quote special characters as necessary for PS - ;; This skips everything except control chars, non-ASCII chars, (, ) and \. - (while (setq start (string-match "[^]-~ -'*-[]" str index)) + ;; This skips everything except control chars, non-ASCII chars, + ;; (, ), \, and DEL. + (while (setq start (string-match "[[:cntrl:][:nonascii:]\177()\\]" + str index)) (setq special (aref str start) new (concat new (substring str index start) @@ -4536,26 +4539,25 @@ end ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; PostScript generation +(defvar ebnf-tree) -(defun ebnf-generate-eps (ebnf-tree) - (let* ((ps-color-p (and ebnf-color-p (ps-color-device))) +(defun ebnf-generate-eps (tree) + (let* ((ebnf-tree tree) + (ps-color-p (and ebnf-color-p (ps-color-device))) (ps-print-color-scale (if ps-color-p (float (car (ps-color-values "white"))) 1.0)) (ebnf-total (length ebnf-tree)) (ebnf-nprod 0) - (old-ps-output (symbol-function 'ps-output)) - (old-ps-output-string (symbol-function 'ps-output-string)) (eps-buffer (get-buffer-create ebnf-eps-buffer-name)) - ebnf-debug-ps error-msg horizontal + ebnf-debug-ps horizontal prod prod-name prod-width prod-height prod-list file-list) - ;; redefines `ps-output' and `ps-output-string' - (defalias 'ps-output 'ebnf-eps-output) - (defalias 'ps-output-string 'ps-output-string-prim) ;; generate EPS file - (save-excursion - (condition-case data - (progn + (unwind-protect + ;; redefines `ps-output' and `ps-output-string' + (cl-letf (((symbol-function 'ps-output) #'ebnf-eps-output) + ((symbol-function 'ps-output-string) #'ps-output-string-prim)) + (save-excursion (while ebnf-tree (setq prod (car ebnf-tree) prod-name (ebnf-node-name prod) @@ -4573,8 +4575,9 @@ end (if (setq prod-list (cdr (assoc prod-name ebnf-eps-production-list))) ;; insert EPS buffer in all buffer associated with production - (ebnf-eps-production-list prod-list 'file-list horizontal - prod-width prod-height eps-buffer) + (ebnf-eps-production-list + prod-list (gv-ref file-list) horizontal + prod-width prod-height eps-buffer) ;; write EPS file for production (ebnf-eps-finish-and-write eps-buffer (ebnf-eps-filename prod-name))) @@ -4584,17 +4587,10 @@ end (setq ebnf-tree (cdr ebnf-tree))) ;; write and kill temporary buffers (ebnf-eps-write-kill-temp file-list t) - (setq file-list nil)) - ;; handler - ((quit error) - (setq error-msg (error-message-string data))))) - ;; restore `ps-output' and `ps-output-string' - (defalias 'ps-output old-ps-output) - (defalias 'ps-output-string old-ps-output-string) - ;; kill temporary buffers - (kill-buffer eps-buffer) - (ebnf-eps-write-kill-temp file-list nil) - (and error-msg (error error-msg)) + (setq file-list nil))) + ;; kill temporary buffers + (kill-buffer eps-buffer) + (ebnf-eps-write-kill-temp file-list nil)) (message " "))) @@ -4610,10 +4606,10 @@ end ;; insert EPS buffer in all buffer associated with production -(defun ebnf-eps-production-list (prod-list file-list-sym horizontal +(defun ebnf-eps-production-list (prod-list file-list-ref horizontal prod-width prod-height eps-buffer) (while prod-list - (add-to-list file-list-sym (car prod-list)) + (cl-pushnew (car prod-list) (gv-deref file-list-ref) :test #'equal) (with-current-buffer (get-buffer-create (concat " *" (car prod-list) "*")) (goto-char (point-max)) (cond @@ -4647,8 +4643,9 @@ end (setq prod-list (cdr prod-list)))) -(defun ebnf-generate (ebnf-tree) - (let* ((ps-color-p (and ebnf-color-p (ps-color-device))) +(defun ebnf-generate (tree) + (let* ((ebnf-tree tree) + (ps-color-p (and ebnf-color-p (ps-color-device))) (ps-print-color-scale (if ps-color-p (float (car (ps-color-values "white"))) 1.0)) @@ -4658,14 +4655,13 @@ end ps-print-begin-page-hook ps-print-begin-column-hook) (ps-generate (current-buffer) (point-min) (point-max) - 'ebnf-generate-postscript))) + #'ebnf-generate-postscript))) -(defvar ebnf-tree nil) (defvar ebnf-direction "R") -(defun ebnf-generate-postscript (from to) +(defun ebnf-generate-postscript (_from _to) (ebnf-begin-file) (if ebnf-horizontal-max-height (ebnf-generate-with-max-height) @@ -5314,9 +5310,9 @@ killed after process termination." "\n%%DocumentNeededResources: font " (or ebnf-fonts-required (setq ebnf-fonts-required - (mapconcat 'identity + (mapconcat #'identity (ps-remove-duplicates - (mapcar 'ebnf-font-name-select + (mapcar #'ebnf-font-name-select (list ebnf-production-font ebnf-terminal-font ebnf-non-terminal-font @@ -5545,7 +5541,7 @@ killed after process termination." (ebnf-log "(ebnf-dimensions tree)") (let ((ebnf-total (length tree)) (ebnf-nprod 0)) - (mapc 'ebnf-production-dimension tree)) + (mapc #'ebnf-production-dimension tree)) tree) @@ -5925,7 +5921,7 @@ killed after process termination." )))) -(defun ebnf-justify (node seq seq-width width last-p) +(defun ebnf-justify (_node seq seq-width width last-p) (let ((term (car (if last-p (last seq) seq)))) (cond ;; adjust empty term diff --git a/lisp/progmodes/ebrowse.el b/lisp/progmodes/ebrowse.el index 0b5d7aa11bf..6ea939de661 100644 --- a/lisp/progmodes/ebrowse.el +++ b/lisp/progmodes/ebrowse.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -3034,7 +3034,7 @@ the first derived class." :help "Show the base class of this class" :active t] ["Down" ebrowse-switch-member-buffer-to-derived-class - :help "Show a derived class class of this class" + :help "Show a derived class of this class" :active t] ["Next Sibling" ebrowse-switch-member-buffer-to-next-sibling-class :help "Show the next sibling class" diff --git a/lisp/progmodes/elisp-mode.el b/lisp/progmodes/elisp-mode.el index b3f452ca5b9..3b24a23b893 100644 --- a/lisp/progmodes/elisp-mode.el +++ b/lisp/progmodes/elisp-mode.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -231,16 +231,21 @@ Blank lines separate paragraphs. Semicolons start comments. (defvar project-vc-external-roots-function) (lisp-mode-variables nil nil 'elisp) (add-hook 'after-load-functions #'elisp--font-lock-flush-elisp-buffers) - (setq-local electric-pair-text-pairs - (append '((?\` . ?\') (?‘ . ?’)) electric-pair-text-pairs)) - (setq-local electric-quote-string t) + (unless noninteractive + (require 'elec-pair) + (defvar electric-pair-text-pairs) + (setq-local electric-pair-text-pairs + (append '((?\` . ?\') (?‘ . ?’)) electric-pair-text-pairs)) + (setq-local electric-quote-string t)) (setq imenu-case-fold-search nil) (add-function :before-until (local 'eldoc-documentation-function) #'elisp-eldoc-documentation-function) (add-hook 'xref-backend-functions #'elisp--xref-backend nil t) (setq-local project-vc-external-roots-function #'elisp-load-path-roots) (add-hook 'completion-at-point-functions - #'elisp-completion-at-point nil 'local)) + #'elisp-completion-at-point nil 'local) + (add-hook 'flymake-diagnostic-functions #'elisp-flymake-checkdoc nil t) + (add-hook 'flymake-diagnostic-functions #'elisp-flymake-byte-compile nil t)) ;; Font-locking support. @@ -807,7 +812,7 @@ non-nil result supercedes the xrefs produced by (apply #'nconc (let (lst) (dolist (sym (apropos-internal regexp)) - (push (elisp--xref-find-definitions sym) lst)) + (push (elisp--xref-find-definitions sym) lst)) (nreverse lst)))) (defvar elisp--xref-identifier-completion-table @@ -894,10 +899,11 @@ Semicolons start comments. ;;; Emacs Lisp Byte-Code mode (eval-and-compile - (defconst emacs-list-byte-code-comment-re + (defconst emacs-lisp-byte-code-comment-re (concat "\\(#\\)@\\([0-9]+\\) " ;; Make sure it's a docstring and not a lazy-loaded byte-code. - "\\(?:[^(]\\|([^\"]\\)"))) + "\\(?:[^(]\\|([^\"]\\)") + "Regular expression matching a dynamic doc string comment.")) (defun elisp--byte-code-comment (end &optional _point) "Try to syntactically mark the #@NNN ....^_ docstrings in byte-code files." @@ -906,7 +912,7 @@ Semicolons start comments. (eq (char-after (nth 8 ppss)) ?#)) (let* ((n (save-excursion (goto-char (nth 8 ppss)) - (when (looking-at emacs-list-byte-code-comment-re) + (when (looking-at emacs-lisp-byte-code-comment-re) (string-to-number (match-string 2))))) ;; `maxdiff' tries to make sure the loop below terminates. (maxdiff n)) @@ -932,7 +938,7 @@ Semicolons start comments. (elisp--byte-code-comment end (point)) (funcall (syntax-propertize-rules - (emacs-list-byte-code-comment-re + (emacs-lisp-byte-code-comment-re (1 (prog1 "< b" (elisp--byte-code-comment end (point)))))) start end)) @@ -1106,7 +1112,7 @@ If CHAR is not a character, return nil." ;; interactive call would use it. ;; FIXME: Is it really the right place for this? (when (eq (car-safe expr) 'interactive) - (setq expr + (setq expr `(call-interactively (lambda (&rest args) ,expr args)))) expr))))) @@ -1171,7 +1177,7 @@ POS specifies the starting position where EXP was found and defaults to point." (and (not (special-variable-p var)) (save-excursion (zerop (car (syntax-ppss (match-beginning 0))))) - (push var vars)))) + (push var vars)))) `(progn ,@(mapcar (lambda (v) `(defvar ,v)) vars) ,exp))))) (defun eval-last-sexp (eval-last-sexp-arg-internal) @@ -1376,7 +1382,7 @@ or elsewhere, return a 1-line docstring." (t (help-function-arglist sym))))) ;; Stringify, and store before highlighting, downcasing, etc. (elisp--last-data-store sym (elisp-function-argstring args) - 'function)))))) + 'function)))))) ;; Highlight, truncate. (if argstring (elisp--highlight-function-argument @@ -1394,13 +1400,14 @@ In the absence of INDEX, just call `eldoc-docstring-format-sym-doc'." ;; FIXME: This should probably work on the list representation of `args' ;; rather than its string representation. ;; FIXME: This function is much too long, we need to split it up! - (let ((start nil) - (end 0) - (argument-face 'eldoc-highlight-function-argument) - (args-lst (mapcar (lambda (x) - (replace-regexp-in-string - "\\`[(]\\|[)]\\'" "" x)) - (split-string args)))) + (let* ((start nil) + (end 0) + (argument-face 'eldoc-highlight-function-argument) + (args-lst (mapcar (lambda (x) + (replace-regexp-in-string + "\\`[(]\\|[)]\\'" "" x)) + (split-string args))) + (args-lst-ak (cdr (member "&key" args-lst)))) ;; Find the current argument in the argument string. We need to ;; handle `&rest' and informal `...' properly. ;; @@ -1412,12 +1419,12 @@ In the absence of INDEX, just call `eldoc-docstring-format-sym-doc'." ;; When `&key' is used finding position based on `index' ;; would be wrong, so find the arg at point and determine ;; position in ARGS based on this current arg. - (when (string-match "&key" args) + (when (and args-lst-ak + (>= index (- (length args-lst) (length args-lst-ak)))) (let* (case-fold-search key-have-value (sym-name (symbol-name sym)) - (cur-w (current-word)) - (args-lst-ak (cdr (member "&key" args-lst))) + (cur-w (current-word t)) (limit (save-excursion (when (re-search-backward sym-name nil t) (match-end 0)))) @@ -1425,7 +1432,7 @@ In the absence of INDEX, just call `eldoc-docstring-format-sym-doc'." (substring cur-w 1) (save-excursion (let (split) - (when (re-search-backward ":\\([^()\n]*\\)" limit t) + (when (re-search-backward ":\\([^ ()\n]*\\)" limit t) (setq split (split-string (match-string 1) " " t)) (prog1 (car split) (when (cdr split) @@ -1437,7 +1444,7 @@ In the absence of INDEX, just call `eldoc-docstring-format-sym-doc'." args-lst-ak (not (member (upcase cur-a) args-lst-ak)) (upcase (car (last args-lst-ak)))))) - (unless (string= cur-w sym-name) + (unless (or (null cur-w) (string= cur-w sym-name)) ;; The last keyword have already a value ;; i.e :foo a b and cursor is at b. ;; If signature have also `&rest' @@ -1584,5 +1591,157 @@ ARGLIST is either a string, or a list of strings or symbols." (replace-match "(" t t str) str))) +;;; Flymake support + +;; Don't require checkdoc, but forward declare these checkdoc special +;; variables. Autoloading them on `checkdoc-current-buffer' is too +;; late, they won't be bound dynamically. +(defvar checkdoc-create-error-function) +(defvar checkdoc-autofix-flag) +(defvar checkdoc-generate-compile-warnings-flag) +(defvar checkdoc-diagnostic-buffer) + +;;;###autoload +(defun elisp-flymake-checkdoc (report-fn &rest _args) + "A Flymake backend for `checkdoc'. +Calls REPORT-FN directly." + (let (collected) + (let* ((checkdoc-create-error-function + (lambda (text start end &optional unfixable) + (push (list text start end unfixable) collected) + nil)) + (checkdoc-autofix-flag nil) + (checkdoc-generate-compile-warnings-flag nil) + (checkdoc-diagnostic-buffer + (generate-new-buffer " *checkdoc-temp*"))) + (unwind-protect + (save-excursion + ;; checkdoc-current-buffer can error if there are + ;; unbalanced parens, for example, but this shouldn't + ;; disable the backend (bug#29176). + (ignore-errors + (checkdoc-current-buffer t))) + (kill-buffer checkdoc-diagnostic-buffer))) + (funcall report-fn + (cl-loop for (text start end _unfixable) in + collected + collect + (flymake-make-diagnostic + (current-buffer) + start end :note text))) + collected)) + +(defun elisp-flymake--byte-compile-done (report-fn + source-buffer + output-buffer) + (with-current-buffer + source-buffer + (save-excursion + (save-restriction + (widen) + (funcall + report-fn + (cl-loop with data = + (with-current-buffer output-buffer + (goto-char (point-min)) + (search-forward ":elisp-flymake-output-start") + (read (point-marker))) + for (string pos _fill level) in data + do (goto-char pos) + for beg = (if (< (point) (point-max)) + (point) + (line-beginning-position)) + for end = (min + (line-end-position) + (or (cdr + (bounds-of-thing-at-point 'sexp)) + (point-max))) + collect (flymake-make-diagnostic + (current-buffer) + (if (= beg end) (1- beg) beg) + end + level + string))))))) + +(defvar-local elisp-flymake--byte-compile-process nil + "Buffer-local process started for byte-compiling the buffer.") + +;;;###autoload +(defun elisp-flymake-byte-compile (report-fn &rest _args) + "A Flymake backend for elisp byte compilation. +Spawn an Emacs process that byte-compiles a file representing the +current buffer state and calls REPORT-FN when done." + (when elisp-flymake--byte-compile-process + (when (process-live-p elisp-flymake--byte-compile-process) + (kill-process elisp-flymake--byte-compile-process))) + (let ((temp-file (make-temp-file "elisp-flymake-byte-compile")) + (source-buffer (current-buffer))) + (save-restriction + (widen) + (write-region (point-min) (point-max) temp-file nil 'nomessage)) + (let* ((output-buffer (generate-new-buffer " *elisp-flymake-byte-compile*"))) + (setq + elisp-flymake--byte-compile-process + (make-process + :name "elisp-flymake-byte-compile" + :buffer output-buffer + :command (list (expand-file-name invocation-name invocation-directory) + "-Q" + "--batch" + ;; "--eval" "(setq load-prefer-newer t)" ; for testing + "-L" default-directory + "-f" "elisp-flymake--batch-compile-for-flymake" + temp-file) + :connection-type 'pipe + :sentinel + (lambda (proc _event) + (when (eq (process-status proc) 'exit) + (unwind-protect + (cond + ((not (eq proc (with-current-buffer source-buffer + elisp-flymake--byte-compile-process))) + (flymake-log :warning "byte-compile process %s obsolete" proc)) + ((zerop (process-exit-status proc)) + (elisp-flymake--byte-compile-done report-fn + source-buffer + output-buffer)) + (t + (funcall report-fn + :panic + :explanation + (format "byte-compile process %s died" proc)))) + (ignore-errors (delete-file temp-file)) + (kill-buffer output-buffer)))))) + :stderr null-device + :noquery t))) + +(defun elisp-flymake--batch-compile-for-flymake (&optional file) + "Helper for `elisp-flymake-byte-compile'. +Runs in a batch-mode Emacs. Interactively use variable +`buffer-file-name' for FILE." + (interactive (list buffer-file-name)) + (let* ((file (or file + (car command-line-args-left))) + (dummy-elc-file) + (byte-compile-log-buffer + (generate-new-buffer " *dummy-byte-compile-log-buffer*")) + (byte-compile-dest-file-function + (lambda (source) + (setq dummy-elc-file (make-temp-file (file-name-nondirectory source))))) + (collected) + (byte-compile-log-warning-function + (lambda (string &optional position fill level) + (push (list string position fill level) + collected) + t))) + (unwind-protect + (byte-compile-file file) + (ignore-errors + (delete-file dummy-elc-file) + (kill-buffer byte-compile-log-buffer))) + (prin1 :elisp-flymake-output-start) + (terpri) + (pp collected))) + (provide 'elisp-mode) ;;; elisp-mode.el ends here diff --git a/lisp/progmodes/etags.el b/lisp/progmodes/etags.el index 8d635cb6d4d..9b21ee67ed1 100644 --- a/lisp/progmodes/etags.el +++ b/lisp/progmodes/etags.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -274,12 +274,9 @@ buffer-local and set them to nil." (run-hook-with-args-until-success 'tags-table-format-functions)) ;;;###autoload -(defun tags-table-mode () +(define-derived-mode tags-table-mode special-mode "Tags Table" "Major mode for tags table file buffers." - (interactive) - (setq major-mode 'tags-table-mode ;FIXME: Use define-derived-mode. - mode-name "Tags Table" - buffer-undo-list t) + (setq buffer-undo-list t) (initialize-new-tags-table)) ;;;###autoload @@ -439,25 +436,25 @@ Returns non-nil if it is a valid table." (progn (set-buffer (get-file-buffer file)) (or verify-tags-table-function (tags-table-mode)) - (if (or (verify-visited-file-modtime (current-buffer)) - ;; Decide whether to revert the file. - ;; revert-without-query can say to revert - ;; or the user can say to revert. - (not (or (let ((tail revert-without-query) - (found nil)) - (while tail - (if (string-match (car tail) buffer-file-name) - (setq found t)) - (setq tail (cdr tail))) - found) - tags-revert-without-query - (yes-or-no-p - (format "Tags file %s has changed, read new contents? " - file))))) - (and verify-tags-table-function - (funcall verify-tags-table-function)) + (unless (or (verify-visited-file-modtime (current-buffer)) + ;; Decide whether to revert the file. + ;; revert-without-query can say to revert + ;; or the user can say to revert. + (not (or (let ((tail revert-without-query) + (found nil)) + (while tail + (if (string-match (car tail) buffer-file-name) + (setq found t)) + (setq tail (cdr tail))) + found) + tags-revert-without-query + (yes-or-no-p + (format "Tags file %s has changed, read new contents? " + file))))) (revert-buffer t t) - (tags-table-mode))) + (tags-table-mode)) + (and verify-tags-table-function + (funcall verify-tags-table-function))) (when (file-exists-p file) (let* ((buf (find-file-noselect file)) (newfile (buffer-file-name buf))) @@ -470,7 +467,9 @@ Returns non-nil if it is a valid table." ;; Only change buffer now that we're done using potentially ;; buffer-local variables. (set-buffer buf) - (tags-table-mode))))) + (tags-table-mode) + (and verify-tags-table-function + (funcall verify-tags-table-function)))))) ;; Subroutine of visit-tags-table-buffer. Search the current tags tables ;; for one that has tags for THIS-FILE (or that includes a table that @@ -599,12 +598,13 @@ Returns t if it visits a tags table, or nil if there are no more in the list." ;; be frobnicated, and CONT will be set non-nil so we don't ;; do it below. (and buffer-file-name - (or - ;; First check only tables already in buffers. - (tags-table-including buffer-file-name t) - ;; Since that didn't find any, now do the - ;; expensive version: reading new files. - (tags-table-including buffer-file-name nil))) + (save-current-buffer + (or + ;; First check only tables already in buffers. + (tags-table-including buffer-file-name t) + ;; Since that didn't find any, now do the + ;; expensive version: reading new files. + (tags-table-including buffer-file-name nil)))) ;; Fourth, use the user variable tags-file-name, if it is ;; not already in the current list. (and tags-file-name @@ -2059,7 +2059,7 @@ see the doc of that variable if you want to add names to the list." (define-derived-mode select-tags-table-mode special-mode "Select Tags Table" "Major mode for choosing a current tags table among those already loaded." - (setq buffer-read-only t)) + ) (defun select-tags-table-select (button) "Select the tags table named on this line." diff --git a/lisp/progmodes/executable.el b/lisp/progmodes/executable.el index da148bd39aa..00c898d261c 100644 --- a/lisp/progmodes/executable.el +++ b/lisp/progmodes/executable.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -83,13 +83,21 @@ When this is `function', only ask when called non-interactively." :type 'regexp :group 'executable) - (defcustom executable-prefix "#!" - "Interpreter magic number prefix inserted when there was no magic number." - :version "24.3" ; "#! " -> "#!" + "Interpreter magic number prefix inserted when there was no magic number. +Use of `executable-prefix-env' is preferable to this option." + :version "26.1" ; deprecated :type 'string :group 'executable) +(defcustom executable-prefix-env nil + "If non-nil, use \"/usr/bin/env\" in interpreter magic number. +If this variable is non-nil, the interpreter magic number inserted +by `executable-set-magic' will be \"#!/usr/bin/env INTERPRETER\", +otherwise it will be \"#!/path/to/INTERPRETER\"." + :version "26.1" + :type 'boolean + :group 'executable) (defcustom executable-chmod 73 "After saving, if the file is not executable, set this mode. @@ -199,7 +207,7 @@ command to find the next error. The buffer is also in `comint-mode' and (defun executable-set-magic (interpreter &optional argument no-query-flag insert-flag) "Set this buffer's interpreter to INTERPRETER with optional ARGUMENT. -The variables `executable-magicless-file-regexp', `executable-prefix', +The variables `executable-magicless-file-regexp', `executable-prefix-env', `executable-insert', `executable-query' and `executable-chmod' control when and how magic numbers are inserted or replaced and scripts made executable." @@ -220,6 +228,14 @@ executable." (and argument (string< "" argument) " ") argument)) + ;; For backward compatibility, allow `executable-prefix-env' to be + ;; overridden by custom `executable-prefix'. + (if (string-match "#!\\([ \t]*/usr/bin/env[ \t]*\\)?$" executable-prefix) + (if executable-prefix-env + (setq argument (concat "/usr/bin/env " + (file-name-nondirectory argument)))) + (setq argument (concat (substring executable-prefix 2) argument))) + (or buffer-read-only (if buffer-file-name (string-match executable-magicless-file-regexp @@ -241,15 +257,13 @@ executable." ;; Make buffer visible before question. (switch-to-buffer (current-buffer)) (y-or-n-p (format-message - "Replace magic number by `%s%s'? " - executable-prefix argument)))) + "Replace magic number by `#!%s'? " + argument)))) (progn (replace-match argument t t nil 1) - (message "Magic number changed to `%s'" - (concat executable-prefix argument))))) - (insert executable-prefix argument ?\n) - (message "Magic number changed to `%s'" - (concat executable-prefix argument))))) + (message "Magic number changed to `#!%s'" argument)))) + (insert "#!" argument ?\n) + (message "Magic number changed to `#!%s'" argument)))) interpreter) diff --git a/lisp/progmodes/f90.el b/lisp/progmodes/f90.el index b3661bfe3f1..0cd665ca24b 100644 --- a/lisp/progmodes/f90.el +++ b/lisp/progmodes/f90.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -133,7 +133,7 @@ ;; f90-indent-region (can be called by calling indent-region) ;; f90-indent-subprogram ;; f90-break-line f90-join-lines -;; f90-fill-region +;; f90-fill-region f90-fill-paragraph ;; f90-insert-end ;; f90-upcase-keywords f90-upcase-region-keywords ;; f90-downcase-keywords f90-downcase-region-keywords @@ -784,6 +784,7 @@ Can be overridden by the value of `font-lock-maximum-decoration'.") ["Indent Region" f90-indent-region :active mark-active] ["Fill Region" f90-fill-region :active mark-active :help "Fill long lines in the region"] + ["Fill Statement/Comment" fill-paragraph :active t] "--" ["Break Line at Point" f90-break-line :active t :help "Break the current line at point"] @@ -909,6 +910,8 @@ Can be overridden by the value of `font-lock-maximum-decoration'.") [ \t]*\\(\\(?:\\sw\\|\\s_\\)+\\)" "Regexp matching the definition of a derived type.") +;; Maybe this should include "class default", but the constant is no +;; longer used. (defconst f90-typeis-re "\\_<\\(class\\|type\\)[ \t]*is[ \t]*(" "Regexp matching a CLASS/TYPE IS statement.") @@ -955,10 +958,14 @@ Used in the F90 entry in `hs-special-modes-alist'.") ;; Avoid F2003 "type is" in "select type", ;; and also variables of derived type "type (foo)". ;; "type, foo" must be a block (?). + ;; And a partial effort to avoid "class default". "\\(?:type\\|class\\)[ \t,]\\(" - "[^i(!\n\"& \t]\\|" ; not-i( + "[^id(!\n\"& \t]\\|" ; not-id( "i[^s!\n\"& \t]\\|" ; i not-s - "is\\(?:\\sw\\|\\s_\\)\\)\\|" + "d[^e!\n\"& \t]\\|" ; d not-e + "de[^f!\n\"& \t]\\|" ; de not-f + "def[^a!\n\"& \t]\\|" ; def not-a + "\\(?:is\\|default\\)\\(?:\\sw\\|\\s_\\)\\)\\|" ;; "abstract interface" is F2003; "submodule" is F2008. "program\\|\\(?:abstract[ \t]*\\)?interface\\|\\(?:sub\\)?module\\|" ;; "enum", but not "enumerator". @@ -1179,6 +1186,7 @@ with no args, if that value is non-nil." (set (make-local-variable 'abbrev-all-caps) t) (set (make-local-variable 'normal-auto-fill-function) 'f90-do-auto-fill) (setq indent-tabs-mode nil) ; auto buffer local + (set (make-local-variable 'fill-paragraph-function) 'f90-fill-paragraph) (set (make-local-variable 'font-lock-defaults) '((f90-font-lock-keywords f90-font-lock-keywords-1 f90-font-lock-keywords-2 @@ -1454,7 +1462,7 @@ if all else fails." (not (or (looking-at "end") (looking-at "\\(do\\|if\\|else\\(if\\|where\\)?\ \\|select[ \t]*\\(case\\|type\\)\\|case\\|where\\|forall\\|\ -\\(?:class\\|type\\)[ \t]*is\\|\ +\\(?:class\\|type\\)[ \t]*is\\|class[ \t]*default\\|\ block\\|critical\\|enum\\|associate\\)\\_>") (looking-at "\\(program\\|\\(?:sub\\)?module\\|\ \\(?:abstract[ \t]*\\)?interface\\|block[ \t]*data\\)\\_>") @@ -1880,8 +1888,8 @@ after indenting." ;; FIXME This means f90-calculate-indent gives different answers ;; for comments and preprocessor lines to this function. ;; Better to make f90-calculate-indent return the correct answer? - (cond ((looking-at "!") (setq indent (f90-comment-indent))) - ((looking-at "#") (setq indent 0)) + (cond ((= (following-char) ?!) (setq indent (f90-comment-indent))) + ((= (following-char) ?#) (setq indent 0)) (t (and f90-smart-end (looking-at "end") (f90-match-end)) @@ -2152,6 +2160,20 @@ Like `join-line', but handles F90 syntax." (if (featurep 'xemacs) (zmacs-deactivate-region) (deactivate-mark)))) + +(defun f90-fill-paragraph (&optional justify) + "In a comment, fill it as a paragraph, else fill the current statement. +For use as the value of `fill-paragraph-function'. +Passes optional argument JUSTIFY to `fill-comment-paragraph'. +Always returns non-nil (to prevent `fill-paragraph' being called)." + (interactive "*P") + (or (fill-comment-paragraph justify) + (save-excursion + (f90-next-statement) + (let ((end (if (bobp) (point) (1- (point))))) + (f90-previous-statement) + (f90-fill-region (point) end))) + t)) (defconst f90-end-block-optional-name '("program" "module" "subroutine" "function" "type") diff --git a/lisp/progmodes/flymake-proc.el b/lisp/progmodes/flymake-proc.el new file mode 100644 index 00000000000..e207de5da6c --- /dev/null +++ b/lisp/progmodes/flymake-proc.el @@ -0,0 +1,1208 @@ +;;; flymake-proc.el --- Flymake backend for external tools -*- lexical-binding: t; -*- + +;; Copyright (C) 2003-2017 Free Software Foundation, Inc. + +;; Author: Pavel Kobyakov <pk_at_work@yahoo.com> +;; Maintainer: Leo Liu <sdl.web@gmail.com> +;; Version: 0.3 +;; Keywords: c languages tools + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: +;; +;; Flymake is a minor Emacs mode performing on-the-fly syntax checks. +;; +;; This file contains a significant part of the original flymake's +;; implementation, a buffer-checking mechanism that parses the output +;; of an external syntax check tool with regular expressions. +;; +;; That work has been adapted into a flymake "backend" function, +;; `flymake-proc-legacy-flymake' suitable for adding to the +;; `flymake-diagnostic-functions' variable. +;; +;;; Bugs/todo: + +;; - Only uses "Makefile", not "makefile" or "GNUmakefile" +;; (from http://bugs.debian.org/337339). + +;;; Code: + +(require 'cl-lib) + +(require 'flymake) + +(defcustom flymake-proc-compilation-prevents-syntax-check t + "If non-nil, don't start syntax check if compilation is running." + :group 'flymake + :type 'boolean) + +(defcustom flymake-proc-xml-program + (if (executable-find "xmlstarlet") "xmlstarlet" "xml") + "Program to use for XML validation." + :type 'file + :group 'flymake + :version "24.4") + +(defcustom flymake-proc-master-file-dirs '("." "./src" "./UnitTest") + "Dirs where to look for master files." + :group 'flymake + :type '(repeat (string))) + +(defcustom flymake-proc-master-file-count-limit 32 + "Max number of master files to check." + :group 'flymake + :type 'integer) + +(defcustom flymake-proc-ignored-file-name-regexps '() + "Files syntax checking is forbidden for. +Overrides `flymake-proc-allowed-file-name-masks'." + :group 'flymake + :type '(repeat (regexp)) + :version "27.1") + +(defcustom flymake-proc-allowed-file-name-masks + '(("\\.\\(?:c\\(?:pp\\|xx\\|\\+\\+\\)?\\|CC\\)\\'" + flymake-proc-simple-make-init + nil + flymake-proc-real-file-name-considering-includes) + ("\\.xml\\'" flymake-proc-xml-init) + ("\\.html?\\'" flymake-proc-xml-init) + ("\\.cs\\'" flymake-proc-simple-make-init) + ;; ("\\.p[ml]\\'" flymake-proc-perl-init) + ("\\.php[345]?\\'" flymake-proc-php-init) + ("\\.h\\'" flymake-proc-master-make-header-init flymake-proc-master-cleanup) + ("\\.java\\'" flymake-proc-simple-make-java-init flymake-proc-simple-java-cleanup) + ("[0-9]+\\.tex\\'" flymake-proc-master-tex-init flymake-proc-master-cleanup) + ("\\.tex\\'" flymake-proc-simple-tex-init) + ("\\.idl\\'" flymake-proc-simple-make-init) + ;; ("\\.cpp\\'" 1) + ;; ("\\.java\\'" 3) + ;; ("\\.h\\'" 2 ("\\.cpp\\'" "\\.c\\'") + ;; ("[ \t]*#[ \t]*include[ \t]*\"\\([\w0-9/\\_\.]*[/\\]*\\)\\(%s\\)\"" 1 2)) + ;; ("\\.idl\\'" 1) + ;; ("\\.odl\\'" 1) + ;; ("[0-9]+\\.tex\\'" 2 ("\\.tex\\'") + ;; ("[ \t]*\\input[ \t]*{\\(.*\\)\\(%s\\)}" 1 2 )) + ;; ("\\.tex\\'" 1) + ) + "Files syntax checking is allowed for. +Variable `flymake-proc-ignored-file-name-regexps' overrides this variable. +This is an alist with elements of the form: + REGEXP INIT [CLEANUP [NAME]] +REGEXP is a regular expression that matches a file name. +INIT is the init function to use. +CLEANUP is the cleanup function to use, default `flymake-proc-simple-cleanup'. +NAME is the file name function to use, default `flymake-proc-get-real-file-name'." + :group 'flymake + :type '(alist :key-type (regexp :tag "File regexp") + :value-type + (list :tag "Handler functions" + (function :tag "Init function") + (choice :tag "Cleanup function" + (const :tag "flymake-proc-simple-cleanup" nil) + function) + (choice :tag "Name function" + (const :tag "flymake-proc-get-real-file-name" nil) + function)))) + +(defvar-local flymake-proc--current-process nil + "Currently active Flymake process for a buffer, if any.") + +(defvar flymake-proc--report-fn nil + "If bound, function used to report back to Flymake's UI.") + +(defun flymake-proc-reformat-err-line-patterns-from-compile-el (original-list) + "Grab error line patterns from ORIGINAL-LIST in compile.el format. +Convert it to Flymake internal format." + (let* ((converted-list '())) + (dolist (item original-list) + (setq item (cdr item)) + (let ((regexp (nth 0 item)) + (file (nth 1 item)) + (line (nth 2 item)) + (col (nth 3 item))) + (if (consp file) (setq file (car file))) + (if (consp line) (setq line (car line))) + (if (consp col) (setq col (car col))) + + (when (not (functionp line)) + (setq converted-list (cons (list regexp file line col) converted-list))))) + converted-list)) + +(defvar flymake-proc-err-line-patterns ; regexp file-idx line-idx col-idx (optional) text-idx(optional), match-end to end of string is error text + (append + '( + ;; MS Visual C++ 6.0 + ("\\(\\([a-zA-Z]:\\)?[^:(\t\n]+\\)(\\([0-9]+\\)) : \\(\\(error\\|warning\\|fatal error\\) \\(C[0-9]+\\):[ \t\n]*\\(.+\\)\\)" + 1 3 nil 4) + ;; jikes + ("\\(\\([a-zA-Z]:\\)?[^:(\t\n]+\\):\\([0-9]+\\):[0-9]+:[0-9]+:[0-9]+: \\(\\(Error\\|Warning\\|Caution\\|Semantic Error\\):[ \t\n]*\\(.+\\)\\)" + 1 3 nil 4) + ;; MS midl + ("midl[ ]*:[ ]*\\(command line error .*\\)" + nil nil nil 1) + ;; MS C# + ("\\(\\([a-zA-Z]:\\)?[^:(\t\n]+\\)(\\([0-9]+\\),[0-9]+): \\(\\(error\\|warning\\|fatal error\\) \\(CS[0-9]+\\):[ \t\n]*\\(.+\\)\\)" + 1 3 nil 4) + ;; perl + ("\\(.*\\) at \\([^ \n]+\\) line \\([0-9]+\\)[,.\n]" 2 3 nil 1) + ;; PHP + ("\\(?:Parse\\|Fatal\\) error: \\(.*\\) in \\(.*\\) on line \\([0-9]+\\)" 2 3 nil 1) + ;; LaTeX warnings (fileless) ("\\(LaTeX \\(Warning\\|Error\\): .*\\) on input line \\([0-9]+\\)" 20 3 nil 1) + ;; ant/javac. Note this also matches gcc warnings! + (" *\\(\\[javac\\] *\\)?\\(\\([a-zA-Z]:\\)?[^:(\t\n]+\\):\\([0-9]+\\)\\(?::\\([0-9]+\\)\\)?:[ \t\n]*\\(.+\\)" + 2 4 5 6)) + ;; compilation-error-regexp-alist) + (flymake-proc-reformat-err-line-patterns-from-compile-el compilation-error-regexp-alist-alist)) + "Patterns for matching error/warning lines. Each pattern has the form +\(REGEXP FILE-IDX LINE-IDX COL-IDX ERR-TEXT-IDX). +Use `flymake-proc-reformat-err-line-patterns-from-compile-el' to add patterns +from compile.el") + +(define-obsolete-variable-alias 'flymake-warning-re 'flymake-proc-diagnostic-type-pred "26.1") +(defvar flymake-proc-diagnostic-type-pred + 'flymake-proc-default-guess + "Predicate matching against diagnostic text to detect its type. +Takes a single argument, the diagnostic's text and should return +a value suitable for indexing +`flymake-diagnostic-types-alist' (which see). If the returned +value is nil, a type of `:error' is assumed. For some backward +compatibility, if a non-nil value is returned that doesn't +index that alist, a type of `:warning' is assumed. + +Instead of a function, it can also be a string, a regular +expression. A match indicates `:warning' type, otherwise +`:error'") + +(defun flymake-proc-default-guess (text) + "Guess if TEXT means a warning, a note or an error." + (cond ((string-match "^[wW]arning" text) + :warning) + ((string-match "^[nN]ote" text) + :note) + (t + :error))) + +(defun flymake-proc--get-file-name-mode-and-masks (file-name) + "Return the corresponding entry from `flymake-proc-allowed-file-name-masks'. +If the FILE-NAME matches a regexp from `flymake-proc-ignored-file-name-regexps', +`flymake-proc-allowed-file-name-masks' is not searched." + (unless (stringp file-name) + (error "Invalid file-name")) + (if (cl-find file-name flymake-proc-ignored-file-name-regexps + :test (lambda (fn rex) (string-match rex fn))) + (flymake-log 3 "file %s ignored") + (let ((fnm flymake-proc-allowed-file-name-masks) + (mode-and-masks nil)) + (while (and (not mode-and-masks) fnm) + (if (string-match (car (car fnm)) file-name) + (setq mode-and-masks (cdr (car fnm)))) + (setq fnm (cdr fnm))) + (flymake-log 3 "file %s, init=%s" file-name (car mode-and-masks)) + mode-and-masks))) + +(defun flymake-proc--get-init-function (file-name) + "Return init function to be used for the file." + (let* ((init-f (nth 0 (flymake-proc--get-file-name-mode-and-masks file-name)))) + ;;(flymake-log 0 "calling %s" init-f) + ;;(funcall init-f (current-buffer)) + init-f)) + +(defun flymake-proc--get-cleanup-function (file-name) + "Return cleanup function to be used for the file." + (or (nth 1 (flymake-proc--get-file-name-mode-and-masks file-name)) + 'flymake-proc-simple-cleanup)) + +(defun flymake-proc--get-real-file-name-function (file-name) + (or (nth 2 (flymake-proc--get-file-name-mode-and-masks file-name)) + 'flymake-proc-get-real-file-name)) + +(defvar flymake-proc--find-buildfile-cache (make-hash-table :test #'equal)) + +(defun flymake-proc--get-buildfile-from-cache (dir-name) + "Look up DIR-NAME in cache and return its associated value. +If DIR-NAME is not found, return nil." + (gethash dir-name flymake-proc--find-buildfile-cache)) + +(defun flymake-proc--add-buildfile-to-cache (dir-name buildfile) + "Associate DIR-NAME with BUILDFILE in the buildfile cache." + (puthash dir-name buildfile flymake-proc--find-buildfile-cache)) + +(defun flymake-proc--clear-buildfile-cache () + "Clear the buildfile cache." + (clrhash flymake-proc--find-buildfile-cache)) + +(defun flymake-proc--find-buildfile (buildfile-name source-dir-name) + "Find buildfile starting from current directory. +Buildfile includes Makefile, build.xml etc. +Return its file name if found, or nil if not found." + (or (flymake-proc--get-buildfile-from-cache source-dir-name) + (let* ((file (locate-dominating-file source-dir-name buildfile-name))) + (if file + (progn + (flymake-log 3 "found buildfile at %s" file) + (flymake-proc--add-buildfile-to-cache source-dir-name file) + file) + (progn + (flymake-log 3 "buildfile for %s not found" source-dir-name) + nil))))) + +(defun flymake-proc--fix-file-name (name) + "Replace all occurrences of `\\' with `/'." + (when name + (setq name (expand-file-name name)) + (setq name (abbreviate-file-name name)) + (setq name (directory-file-name name)) + name)) + +(defun flymake-proc--same-files (file-name-one file-name-two) + "Check if FILE-NAME-ONE and FILE-NAME-TWO point to same file. +Return t if so, nil if not." + (equal (flymake-proc--fix-file-name file-name-one) + (flymake-proc--fix-file-name file-name-two))) + +;; This is bound dynamically to pass a parameter to a sort predicate below +(defvar flymake-proc--included-file-name) + +(defun flymake-proc--find-possible-master-files (file-name master-file-dirs masks) + "Find (by name and location) all possible master files. +Name is specified by FILE-NAME and location is specified by +MASTER-FILE-DIRS. Master files include .cpp and .c for .h. +Files are searched for starting from the .h directory and max +max-level parent dirs. File contents are not checked." + (let* ((dirs master-file-dirs) + (files nil) + (done nil)) + + (while (and (not done) dirs) + (let* ((dir (expand-file-name (car dirs) (file-name-directory file-name))) + (masks masks)) + (while (and (file-exists-p dir) (not done) masks) + (let* ((mask (car masks)) + (dir-files (directory-files dir t mask))) + + (flymake-log 3 "dir %s, %d file(s) for mask %s" + dir (length dir-files) mask) + (while (and (not done) dir-files) + (when (not (file-directory-p (car dir-files))) + (setq files (cons (car dir-files) files)) + (when (>= (length files) flymake-proc-master-file-count-limit) + (flymake-log 3 "master file count limit (%d) reached" flymake-proc-master-file-count-limit) + (setq done t))) + (setq dir-files (cdr dir-files)))) + (setq masks (cdr masks)))) + (setq dirs (cdr dirs))) + (when files + (let ((flymake-proc--included-file-name (file-name-nondirectory file-name))) + (setq files (sort files 'flymake-proc--master-file-compare)))) + (flymake-log 3 "found %d possible master file(s)" (length files)) + files)) + +(defun flymake-proc--master-file-compare (file-one file-two) + "Compare two files specified by FILE-ONE and FILE-TWO. +This function is used in sort to move most possible file names +to the beginning of the list (File.h -> File.cpp moved to top)." + (and (equal (file-name-sans-extension flymake-proc--included-file-name) + (file-name-base file-one)) + (not (equal file-one file-two)))) + +(defvar flymake-proc-check-file-limit 8192 + "Maximum number of chars to look at when checking possible master file. +Nil means search the entire file.") + +(defun flymake-proc--check-patch-master-file-buffer + (master-file-temp-buffer + master-file-name patched-master-file-name + source-file-name patched-source-file-name + include-dirs regexp) + "Check if MASTER-FILE-NAME is a master file for SOURCE-FILE-NAME. +If yes, patch a copy of MASTER-FILE-NAME to include PATCHED-SOURCE-FILE-NAME +instead of SOURCE-FILE-NAME. + +For example, foo.cpp is a master file if it includes foo.h. + +When a buffer for MASTER-FILE-NAME exists, use it as a source +instead of reading master file from disk." + (let* ((source-file-nondir (file-name-nondirectory source-file-name)) + (source-file-extension (file-name-extension source-file-nondir)) + (source-file-nonext (file-name-sans-extension source-file-nondir)) + (found nil) + (inc-name nil) + (search-limit flymake-proc-check-file-limit)) + (setq regexp + (format regexp ; "[ \t]*#[ \t]*include[ \t]*\"\\(.*%s\\)\"" + ;; Hack for tex files, where \include often excludes .tex. + ;; Maybe this is safe generally. + (if (and (> (length source-file-extension) 1) + (string-equal source-file-extension "tex")) + (format "%s\\(?:\\.%s\\)?" + (regexp-quote source-file-nonext) + (regexp-quote source-file-extension)) + (regexp-quote source-file-nondir)))) + (unwind-protect + (with-current-buffer master-file-temp-buffer + (if (or (not search-limit) + (> search-limit (point-max))) + (setq search-limit (point-max))) + (flymake-log 3 "checking %s against regexp %s" + master-file-name regexp) + (goto-char (point-min)) + (while (and (< (point) search-limit) + (re-search-forward regexp search-limit t)) + (let ((match-beg (match-beginning 1)) + (match-end (match-end 1))) + + (flymake-log 3 "found possible match for %s" source-file-nondir) + (setq inc-name (match-string 1)) + (and (> (length source-file-extension) 1) + (string-equal source-file-extension "tex") + (not (string-match (format "\\.%s\\'" source-file-extension) + inc-name)) + (setq inc-name (concat inc-name "." source-file-extension))) + (when (eq t (compare-strings + source-file-nondir nil nil + inc-name (- (length inc-name) + (length source-file-nondir)) nil)) + (flymake-log 3 "inc-name=%s" inc-name) + (when (flymake-proc--check-include source-file-name inc-name + include-dirs) + (setq found t) + ;; replace-match is not used here as it fails in + ;; XEmacs with 'last match not a buffer' error as + ;; check-includes calls replace-in-string + (flymake-proc--replace-region + match-beg match-end + (file-name-nondirectory patched-source-file-name)))) + (forward-line 1))) + (when found + (flymake-proc--save-buffer-in-file patched-master-file-name))) + ;;+(flymake-log 3 "killing buffer %s" + ;; (buffer-name master-file-temp-buffer)) + (kill-buffer master-file-temp-buffer)) + ;;+(flymake-log 3 "check-patch master file %s: %s" master-file-name found) + (when found + (flymake-log 2 "found master file %s" master-file-name)) + found)) + +;;; XXX: remove +(defun flymake-proc--replace-region (beg end rep) + "Replace text in BUFFER in region (BEG END) with REP." + (save-excursion + (goto-char end) + ;; Insert before deleting, so as to better preserve markers's positions. + (insert rep) + (delete-region beg end))) + +(defun flymake-proc--read-file-to-temp-buffer (file-name) + "Insert contents of FILE-NAME into newly created temp buffer." + (let* ((temp-buffer (get-buffer-create (generate-new-buffer-name (concat "flymake:" (file-name-nondirectory file-name)))))) + (with-current-buffer temp-buffer + (insert-file-contents file-name)) + temp-buffer)) + +(defun flymake-proc--copy-buffer-to-temp-buffer (buffer) + "Copy contents of BUFFER into newly created temp buffer." + (with-current-buffer + (get-buffer-create (generate-new-buffer-name + (concat "flymake:" (buffer-name buffer)))) + (insert-buffer-substring buffer) + (current-buffer))) + +(defun flymake-proc--check-include (source-file-name inc-name include-dirs) + "Check if SOURCE-FILE-NAME can be found in include path. +Return t if it can be found via include path using INC-NAME." + (if (file-name-absolute-p inc-name) + (flymake-proc--same-files source-file-name inc-name) + (while (and include-dirs + (not (flymake-proc--same-files + source-file-name + (concat (file-name-directory source-file-name) + "/" (car include-dirs) + "/" inc-name)))) + (setq include-dirs (cdr include-dirs))) + include-dirs)) + +(defun flymake-proc--find-buffer-for-file (file-name) + "Check if there exists a buffer visiting FILE-NAME. +Return t if so, nil if not." + (let ((buffer-name (get-file-buffer file-name))) + (if buffer-name + (get-buffer buffer-name)))) + +(defun flymake-proc--create-master-file (source-file-name patched-source-file-name get-incl-dirs-f create-temp-f masks include-regexp) + "Save SOURCE-FILE-NAME with a different name. +Find master file, patch and save it." + (let* ((possible-master-files (flymake-proc--find-possible-master-files source-file-name flymake-proc-master-file-dirs masks)) + (master-file-count (length possible-master-files)) + (idx 0) + (temp-buffer nil) + (master-file-name nil) + (patched-master-file-name nil) + (found nil)) + + (while (and (not found) (< idx master-file-count)) + (setq master-file-name (nth idx possible-master-files)) + (setq patched-master-file-name (funcall create-temp-f master-file-name "flymake_master")) + (if (flymake-proc--find-buffer-for-file master-file-name) + (setq temp-buffer (flymake-proc--copy-buffer-to-temp-buffer (flymake-proc--find-buffer-for-file master-file-name))) + (setq temp-buffer (flymake-proc--read-file-to-temp-buffer master-file-name))) + (setq found + (flymake-proc--check-patch-master-file-buffer + temp-buffer + master-file-name + patched-master-file-name + source-file-name + patched-source-file-name + (funcall get-incl-dirs-f (file-name-directory master-file-name)) + include-regexp)) + (setq idx (1+ idx))) + (if found + (list master-file-name patched-master-file-name) + (progn + (flymake-log 3 "none of %d master file(s) checked includes %s" master-file-count + (file-name-nondirectory source-file-name)) + nil)))) + +(defun flymake-proc--save-buffer-in-file (file-name) + "Save the entire buffer contents into file FILE-NAME. +Create parent directories as needed." + (make-directory (file-name-directory file-name) 1) + (write-region nil nil file-name nil 566) + (flymake-log 3 "saved buffer %s in file %s" (buffer-name) file-name)) + +(defun flymake-proc--diagnostics-for-pattern (proc pattern) + (cl-flet ((guess-type + (pred message) + (cond ((null message) + :error) + ((stringp pred) + (if (string-match pred message) + :warning + :error)) + ((functionp pred) + (let ((probe (funcall pred message))) + (cond ((assoc-default probe + flymake-diagnostic-types-alist) + probe) + (probe + :warning) + (t + :error))))))) + (condition-case-unless-debug err + (cl-loop + with (regexp file-idx line-idx col-idx message-idx) = pattern + while (and + (search-forward-regexp regexp nil t) + ;; If the preceding search spanned more than one line, + ;; move to the start of the line we ended up in. This + ;; preserves the usefulness of the patterns in + ;; `flymake-proc-err-line-patterns', which were + ;; written primarily for flymake's original + ;; line-by-line parsing and thus never spanned + ;; multiple lines. + (if (/= (line-number-at-pos (match-beginning 0)) + (line-number-at-pos)) + (goto-char (line-beginning-position)) + t)) + for fname = (and file-idx (match-string file-idx)) + for message = (and message-idx (match-string message-idx)) + for line-string = (and line-idx (match-string line-idx)) + for line-number = (or (and line-string + (string-to-number line-string)) + 1) + for col-string = (and col-idx (match-string col-idx)) + for col-number = (and col-string + (string-to-number col-string)) + for full-file = (with-current-buffer (process-buffer proc) + (and fname + (funcall + (flymake-proc--get-real-file-name-function + fname) + fname))) + for buffer = (and full-file + (find-buffer-visiting full-file)) + if (and (eq buffer (process-buffer proc)) message) + collect (pcase-let ((`(,beg . ,end) + (flymake-diag-region buffer line-number col-number))) + (flymake-make-diagnostic + buffer beg end + (with-current-buffer buffer + (guess-type flymake-proc-diagnostic-type-pred message)) + message)) + else + do (flymake-log 2 "Reference to file %s is out of scope" fname)) + (error + (flymake-log 1 "Error parsing process output for pattern %s: %s" + pattern err) + nil)))) + +(defun flymake-proc--process-filter (proc string) + "Parse STRING and collect diagnostics info." + (flymake-log 3 "received %d byte(s) of output from process %d" + (length string) (process-id proc)) + (let ((output-buffer (process-get proc 'flymake-proc--output-buffer))) + (when (and (buffer-live-p (process-buffer proc)) + output-buffer) + (with-current-buffer output-buffer + (let ((moving (= (point) (process-mark proc))) + (inhibit-read-only t) + (unprocessed-mark + (or (process-get proc 'flymake-proc--unprocessed-mark) + (set-marker (make-marker) (point-min))))) + (save-excursion + ;; Insert the text, advancing the process marker. + (goto-char (process-mark proc)) + (insert string) + (set-marker (process-mark proc) (point))) + (if moving (goto-char (process-mark proc))) + + ;; check for new diagnostics + ;; + (save-excursion + (goto-char unprocessed-mark) + (dolist (pattern flymake-proc-err-line-patterns) + (let ((new (flymake-proc--diagnostics-for-pattern proc pattern))) + (process-put + proc + 'flymake-proc--collected-diagnostics + (append new + (process-get proc + 'flymake-proc--collected-diagnostics))))) + (process-put proc 'flymake-proc--unprocessed-mark + (point-marker)))))))) + +(defun flymake-proc--process-sentinel (proc _event) + "Sentinel for syntax check buffers." + (let (debug + (pid (process-id proc)) + (source-buffer (process-buffer proc))) + (unwind-protect + (when (buffer-live-p source-buffer) + (with-current-buffer source-buffer + (cond ((process-get proc 'flymake-proc--obsolete) + (flymake-log 3 "proc %s considered obsolete" + pid)) + ((process-get proc 'flymake-proc--interrupted) + (flymake-log 3 "proc %s interrupted by user" + pid)) + ((not (process-live-p proc)) + (let* ((exit-status (process-exit-status proc)) + (command (process-command proc)) + (diagnostics (process-get + proc + 'flymake-proc--collected-diagnostics))) + (flymake-log 2 "process %d exited with code %d" + pid exit-status) + (cond + ((equal 0 exit-status) + (funcall flymake-proc--report-fn diagnostics + :explanation (format "a gift from %s" (process-id proc)) + )) + (diagnostics + ;; non-zero exit but some diagnostics is quite + ;; normal... + (funcall flymake-proc--report-fn diagnostics + :explanation (format "a gift from %s" (process-id proc)))) + ((null diagnostics) + ;; ...but no diagnostics is strange, so panic. + (setq debug debug-on-error) + (flymake-proc--panic + :configuration-error + (format "Command %s errored, but no diagnostics" + command))))))))) + (let ((output-buffer (process-get proc 'flymake-proc--output-buffer))) + (cond (debug + (flymake-log 3 "Output buffer %s kept alive for debugging" + output-buffer)) + (t + (when (buffer-live-p source-buffer) + (with-current-buffer source-buffer + (let ((cleanup-f (flymake-proc--get-cleanup-function + (buffer-file-name)))) + (flymake-log 3 "cleaning up using %s" cleanup-f) + (funcall cleanup-f)))) + (kill-buffer output-buffer))))))) + +(defun flymake-proc--panic (problem explanation) + "Tell Flymake UI about a fatal PROBLEM with this backend. +May only be called in a dynamic environment where +`flymake-proc--report-fn' is bound." + (flymake-log 1 "%s: %s" problem explanation) + (if (and (boundp 'flymake-proc--report-fn) + flymake-proc--report-fn) + (funcall flymake-proc--report-fn :panic + :explanation (format "%s: %s" problem explanation)) + (flymake-error "Trouble telling flymake-ui about problem %s(%s)" + problem explanation))) + +(require 'compile) + +(defun flymake-proc-get-project-include-dirs-imp (basedir) + "Include dirs for the project current file belongs to." + (if (flymake-proc--get-project-include-dirs-from-cache basedir) + (progn + (flymake-proc--get-project-include-dirs-from-cache basedir)) + ;;else + (let* ((command-line (concat "make -C " + (shell-quote-argument basedir) + " DUMPVARS=INCLUDE_DIRS dumpvars")) + (output (shell-command-to-string command-line)) + (lines (split-string output "\n" t)) + (count (length lines)) + (idx 0) + (inc-dirs nil)) + (while (and (< idx count) (not (string-match "^INCLUDE_DIRS=.*" (nth idx lines)))) + (setq idx (1+ idx))) + (when (< idx count) + (let* ((inc-lines (split-string (nth idx lines) " *-I" t)) + (inc-count (length inc-lines))) + (while (> inc-count 0) + (when (not (string-match "^INCLUDE_DIRS=.*" (nth (1- inc-count) inc-lines))) + (push (replace-regexp-in-string "\"" "" (nth (1- inc-count) inc-lines)) inc-dirs)) + (setq inc-count (1- inc-count))))) + (flymake-proc--add-project-include-dirs-to-cache basedir inc-dirs) + inc-dirs))) + +(defvar flymake-proc-get-project-include-dirs-function #'flymake-proc-get-project-include-dirs-imp + "Function used to get project include dirs, one parameter: basedir name.") + +(defun flymake-proc--get-project-include-dirs (basedir) + (funcall flymake-proc-get-project-include-dirs-function basedir)) + +(defun flymake-proc--get-system-include-dirs () + "System include dirs - from the `INCLUDE' env setting." + (let* ((includes (getenv "INCLUDE"))) + (if includes (split-string includes path-separator t) nil))) + +(defvar flymake-proc--project-include-dirs-cache (make-hash-table :test #'equal)) + +(defun flymake-proc--get-project-include-dirs-from-cache (base-dir) + (gethash base-dir flymake-proc--project-include-dirs-cache)) + +(defun flymake-proc--add-project-include-dirs-to-cache (base-dir include-dirs) + (puthash base-dir include-dirs flymake-proc--project-include-dirs-cache)) + +(defun flymake-proc--clear-project-include-dirs-cache () + (clrhash flymake-proc--project-include-dirs-cache)) + +(defun flymake-proc-get-include-dirs (base-dir) + "Get dirs to use when resolving local file names." + (let* ((include-dirs (append '(".") (flymake-proc--get-project-include-dirs base-dir) (flymake-proc--get-system-include-dirs)))) + include-dirs)) + +;; (defun flymake-proc--restore-formatting () +;; "Remove any formatting made by flymake." +;; ) + +;; (defun flymake-proc--get-program-dir (buffer) +;; "Get dir to start program in." +;; (unless (bufferp buffer) +;; (error "Invalid buffer")) +;; (with-current-buffer buffer +;; default-directory)) + +(defun flymake-proc--safe-delete-file (file-name) + (when (and file-name (file-exists-p file-name)) + (delete-file file-name) + (flymake-log 2 "deleted file %s" file-name))) + +(defun flymake-proc--safe-delete-directory (dir-name) + (condition-case-unless-debug nil + (progn + (delete-directory dir-name) + (flymake-log 2 "deleted dir %s" dir-name)) + (error + (flymake-log 1 "Failed to delete dir %s, error ignored" dir-name)))) + + +(defun flymake-proc-legacy-flymake (report-fn &rest args) + "Flymake backend based on the original Flymake implementation. +This function is suitable for inclusion in +`flymake-diagnostic-functions'. For backward compatibility, it +can also be executed interactively independently of +`flymake-mode'." + ;; Interactively, behave as if flymake had invoked us through its + ;; `flymake-diagnostic-functions' with a suitable ID so flymake can + ;; clean up consistently + (interactive (list + (lambda (diags &rest args) + (apply (flymake-make-report-fn 'flymake-proc-legacy-flymake) + diags + (append args '(:force t)))) + :interactive t)) + (let ((interactive (plist-get args :interactive)) + (proc flymake-proc--current-process) + (flymake-proc--report-fn report-fn)) + (when (processp proc) + (process-put proc 'flymake-proc--obsolete t) + (flymake-log 3 "marking %s obsolete" (process-id proc)) + (when (process-live-p proc) + (when interactive + (user-error + "There's already a Flymake process running in this buffer") + (kill-process proc)))) + (when + ;; This particular situation make us not want to error right + ;; away (and disable ourselves), in case the situation changes + ;; in the near future. + (and (or (not flymake-proc-compilation-prevents-syntax-check) + (not (flymake-proc--compilation-is-running)))) + (let ((init-f + (and + buffer-file-name + ;; Since we write temp files in current dir, there's no point + ;; trying if the directory is read-only (bug#8954). + (file-writable-p (file-name-directory buffer-file-name)) + (flymake-proc--get-init-function buffer-file-name)))) + (unless init-f (error "Can't find a suitable init function")) + (flymake-proc--clear-buildfile-cache) + (flymake-proc--clear-project-include-dirs-cache) + + (let* ((cleanup-f (flymake-proc--get-cleanup-function buffer-file-name)) + (cmd-and-args (funcall init-f)) + (cmd (nth 0 cmd-and-args)) + (args (nth 1 cmd-and-args)) + (dir (nth 2 cmd-and-args)) + (success nil)) + (unwind-protect + (cond + ((not cmd-and-args) + (flymake-log 1 "init function %s for %s failed, cleaning up" + init-f buffer-file-name)) + (t + (setq proc + (let ((default-directory (or dir default-directory))) + (when dir + (flymake-log 3 "starting process on dir %s" dir)) + (make-process + :name "flymake-proc" + :buffer (current-buffer) + :command (cons cmd args) + :noquery t + :filter + (lambda (proc string) + (let ((flymake-proc--report-fn report-fn)) + (flymake-proc--process-filter proc string))) + :sentinel + (lambda (proc event) + (let ((flymake-proc--report-fn report-fn)) + (flymake-proc--process-sentinel proc event)))))) + (process-put proc 'flymake-proc--output-buffer + (generate-new-buffer + (format " *flymake output for %s*" (current-buffer)))) + (setq flymake-proc--current-process proc) + (flymake-log 2 "started process %d, command=%s, dir=%s" + (process-id proc) (process-command proc) + default-directory) + (setq success t))) + (unless success + (funcall cleanup-f)))))))) + +(define-obsolete-function-alias 'flymake-start-syntax-check + 'flymake-proc-legacy-flymake "26.1") + +(defun flymake-proc-stop-all-syntax-checks (&optional reason) + "Kill all syntax check processes." + (interactive (list "Interrupted by user")) + (dolist (buf (buffer-list)) + (with-current-buffer buf + (let (p flymake-proc--current-process) + (when (process-live-p p) + (kill-process p) + (process-put p 'flymake-proc--interrupted reason) + (flymake-log 2 "killed process %d" (process-id p))))))) + +(defun flymake-proc--compilation-is-running () + (and (boundp 'compilation-in-progress) + compilation-in-progress)) + +(defun flymake-proc-compile () + "Kill all Flymake syntax checks, start compilation." + (interactive) + (flymake-proc-stop-all-syntax-checks "Stopping for proper compilation") + (call-interactively 'compile)) + +;;;; general init-cleanup and helper routines +(defun flymake-proc-create-temp-inplace (file-name prefix) + (unless (stringp file-name) + (error "Invalid file-name")) + (or prefix + (setq prefix "flymake")) + (let* ((ext (file-name-extension file-name)) + (temp-name (file-truename + (concat (file-name-sans-extension file-name) + "_" prefix + (and ext (concat "." ext)))))) + (flymake-log 3 "create-temp-inplace: file=%s temp=%s" file-name temp-name) + temp-name)) + +(defun flymake-proc-create-temp-with-folder-structure (file-name _prefix) + (unless (stringp file-name) + (error "Invalid file-name")) + + (let* ((dir (file-name-directory file-name)) + ;; Not sure what this slash-pos is all about, but I guess it's just + ;; trying to remove the leading / of absolute file names. + (slash-pos (string-match "/" dir)) + (temp-dir (expand-file-name (substring dir (1+ slash-pos)) + temporary-file-directory))) + + (file-truename (expand-file-name (file-name-nondirectory file-name) + temp-dir)))) + +(defun flymake-proc--delete-temp-directory (dir-name) + "Attempt to delete temp dir created by `flymake-proc-create-temp-with-folder-structure', do not fail on error." + (let* ((temp-dir temporary-file-directory) + (suffix (substring dir-name (1+ (length temp-dir))))) + + (while (> (length suffix) 0) + (setq suffix (directory-file-name suffix)) + ;;+(flymake-log 0 "suffix=%s" suffix) + (flymake-proc--safe-delete-directory + (file-truename (expand-file-name suffix temp-dir))) + (setq suffix (file-name-directory suffix))))) + +(defvar-local flymake-proc--temp-source-file-name nil) +(defvar-local flymake-proc--master-file-name nil) +(defvar-local flymake-proc--temp-master-file-name nil) +(defvar-local flymake-proc--base-dir nil) + +(defun flymake-proc-init-create-temp-buffer-copy (create-temp-f) + "Make a temporary copy of the current buffer, save its name in buffer data and return the name." + (let* ((source-file-name buffer-file-name) + (temp-source-file-name (funcall create-temp-f source-file-name "flymake"))) + + (flymake-proc--save-buffer-in-file temp-source-file-name) + (setq flymake-proc--temp-source-file-name temp-source-file-name) + temp-source-file-name)) + +(defun flymake-proc-simple-cleanup () + "Do cleanup after `flymake-proc-init-create-temp-buffer-copy'. +Delete temp file." + (flymake-proc--safe-delete-file flymake-proc--temp-source-file-name)) + +(defun flymake-proc-get-real-file-name (file-name-from-err-msg) + "Translate file name from error message to \"real\" file name. +Return full-name. Names are real, not patched." + (let* ((real-name nil) + (source-file-name buffer-file-name) + (master-file-name flymake-proc--master-file-name) + (temp-source-file-name flymake-proc--temp-source-file-name) + (temp-master-file-name flymake-proc--temp-master-file-name) + (base-dirs + (list flymake-proc--base-dir + (file-name-directory source-file-name) + (if master-file-name (file-name-directory master-file-name)))) + (files (list (list source-file-name source-file-name) + (list temp-source-file-name source-file-name) + (list master-file-name master-file-name) + (list temp-master-file-name master-file-name)))) + + (when (equal 0 (length file-name-from-err-msg)) + (setq file-name-from-err-msg source-file-name)) + + (setq real-name (flymake-proc--get-full-patched-file-name file-name-from-err-msg base-dirs files)) + ;; if real-name is nil, than file name from err msg is none of the files we've patched + (if (not real-name) + (setq real-name (flymake-proc--get-full-nonpatched-file-name file-name-from-err-msg base-dirs))) + (if (not real-name) + (setq real-name file-name-from-err-msg)) + (setq real-name (flymake-proc--fix-file-name real-name)) + (flymake-log 3 "get-real-file-name: file-name=%s real-name=%s" file-name-from-err-msg real-name) + real-name)) + +(defun flymake-proc--get-full-patched-file-name (file-name-from-err-msg base-dirs files) + (let* ((base-dirs-count (length base-dirs)) + (file-count (length files)) + (real-name nil)) + + (while (and (not real-name) (> base-dirs-count 0)) + (setq file-count (length files)) + (while (and (not real-name) (> file-count 0)) + (let* ((this-dir (nth (1- base-dirs-count) base-dirs)) + (this-file (nth 0 (nth (1- file-count) files))) + (this-real-name (nth 1 (nth (1- file-count) files)))) + ;;+(flymake-log 0 "this-dir=%s this-file=%s this-real=%s msg-file=%s" this-dir this-file this-real-name file-name-from-err-msg) + (when (and this-dir this-file (flymake-proc--same-files + (expand-file-name file-name-from-err-msg this-dir) + this-file)) + (setq real-name this-real-name))) + (setq file-count (1- file-count))) + (setq base-dirs-count (1- base-dirs-count))) + real-name)) + +(defun flymake-proc--get-full-nonpatched-file-name (file-name-from-err-msg base-dirs) + (let* ((real-name nil)) + (if (file-name-absolute-p file-name-from-err-msg) + (setq real-name file-name-from-err-msg) + (let* ((base-dirs-count (length base-dirs))) + (while (and (not real-name) (> base-dirs-count 0)) + (let* ((full-name (expand-file-name file-name-from-err-msg + (nth (1- base-dirs-count) base-dirs)))) + (if (file-exists-p full-name) + (setq real-name full-name)) + (setq base-dirs-count (1- base-dirs-count)))))) + real-name)) + +(defun flymake-proc--init-find-buildfile-dir (source-file-name buildfile-name) + "Find buildfile, store its dir in buffer data and return its dir, if found." + (let* ((buildfile-dir + (flymake-proc--find-buildfile buildfile-name + (file-name-directory source-file-name)))) + (if buildfile-dir + (setq flymake-proc--base-dir buildfile-dir) + (flymake-proc--panic + "NOMK" (format "No buildfile (%s) found for %s" + buildfile-name source-file-name))))) + +(defun flymake-proc--init-create-temp-source-and-master-buffer-copy (get-incl-dirs-f create-temp-f master-file-masks include-regexp) + "Find master file (or buffer), create its copy along with a copy of the source file." + (let* ((source-file-name buffer-file-name) + (temp-source-file-name (flymake-proc-init-create-temp-buffer-copy create-temp-f)) + (master-and-temp-master (flymake-proc--create-master-file + source-file-name temp-source-file-name + get-incl-dirs-f create-temp-f + master-file-masks include-regexp))) + + (if (not master-and-temp-master) + (progn + (flymake-proc--panic + "NOMASTER" + (format-message "cannot find master file for %s" + source-file-name)) + nil) + (setq flymake-proc--master-file-name (nth 0 master-and-temp-master)) + (setq flymake-proc--temp-master-file-name (nth 1 master-and-temp-master))))) + +(defun flymake-proc-master-cleanup () + (flymake-proc-simple-cleanup) + (flymake-proc--safe-delete-file flymake-proc--temp-master-file-name)) + +;;;; make-specific init-cleanup routines +(defun flymake-proc--get-syntax-check-program-args (source-file-name base-dir use-relative-base-dir use-relative-source get-cmd-line-f) + "Create a command line for syntax check using GET-CMD-LINE-F." + (funcall get-cmd-line-f + (if use-relative-source + (file-relative-name source-file-name base-dir) + source-file-name) + (if use-relative-base-dir + (file-relative-name base-dir + (file-name-directory source-file-name)) + base-dir))) + +(defun flymake-proc-get-make-cmdline (source base-dir) + (list "make" + (list "-s" + "-C" + base-dir + (concat "CHK_SOURCES=" source) + "SYNTAX_CHECK_MODE=1" + "check-syntax"))) + +(defun flymake-proc-get-ant-cmdline (source base-dir) + (list "ant" + (list "-buildfile" + (concat base-dir "/" "build.xml") + (concat "-DCHK_SOURCES=" source) + "check-syntax"))) + +(defun flymake-proc-simple-make-init-impl (create-temp-f use-relative-base-dir use-relative-source build-file-name get-cmdline-f) + "Create syntax check command line for a directly checked source file. +Use CREATE-TEMP-F for creating temp copy." + (let* ((args nil) + (source-file-name buffer-file-name) + (buildfile-dir (flymake-proc--init-find-buildfile-dir source-file-name build-file-name))) + (if buildfile-dir + (let* ((temp-source-file-name (flymake-proc-init-create-temp-buffer-copy create-temp-f))) + (setq args (flymake-proc--get-syntax-check-program-args temp-source-file-name buildfile-dir + use-relative-base-dir use-relative-source + get-cmdline-f)))) + args)) + +(defun flymake-proc-simple-make-init () + (flymake-proc-simple-make-init-impl 'flymake-proc-create-temp-inplace t t "Makefile" 'flymake-proc-get-make-cmdline)) + +(defun flymake-proc-master-make-init (get-incl-dirs-f master-file-masks include-regexp) + "Create make command line for a source file checked via master file compilation." + (let* ((make-args nil) + (temp-master-file-name (flymake-proc--init-create-temp-source-and-master-buffer-copy + get-incl-dirs-f 'flymake-proc-create-temp-inplace + master-file-masks include-regexp))) + (when temp-master-file-name + (let* ((buildfile-dir (flymake-proc--init-find-buildfile-dir temp-master-file-name "Makefile"))) + (if buildfile-dir + (setq make-args (flymake-proc--get-syntax-check-program-args + temp-master-file-name buildfile-dir nil nil 'flymake-proc-get-make-cmdline))))) + make-args)) + +(defun flymake-proc--find-make-buildfile (source-dir) + (flymake-proc--find-buildfile "Makefile" source-dir)) + +;;;; .h/make specific +(defun flymake-proc-master-make-header-init () + (flymake-proc-master-make-init + 'flymake-proc-get-include-dirs + '("\\.\\(?:c\\(?:pp\\|xx\\|\\+\\+\\)?\\|CC\\)\\'") + "[ \t]*#[ \t]*include[ \t]*\"\\([[:word:]0-9/\\_.]*%s\\)\"")) + +(defun flymake-proc-real-file-name-considering-includes (scraped) + (flymake-proc-get-real-file-name + (let ((case-fold-search t)) + (replace-regexp-in-string "^in file included from[ \t*]" + "" + scraped)))) + +;;;; .java/make specific +(defun flymake-proc-simple-make-java-init () + (flymake-proc-simple-make-init-impl 'flymake-proc-create-temp-with-folder-structure nil nil "Makefile" 'flymake-proc-get-make-cmdline)) + +(defun flymake-proc-simple-ant-java-init () + (flymake-proc-simple-make-init-impl 'flymake-proc-create-temp-with-folder-structure nil nil "build.xml" 'flymake-proc-get-ant-cmdline)) + +(defun flymake-proc-simple-java-cleanup () + "Cleanup after `flymake-proc-simple-make-java-init' -- delete temp file and dirs." + (flymake-proc--safe-delete-file flymake-proc--temp-source-file-name) + (when flymake-proc--temp-source-file-name + (flymake-proc--delete-temp-directory + (file-name-directory flymake-proc--temp-source-file-name)))) + +;;;; perl-specific init-cleanup routines +(defun flymake-proc-perl-init () + (let* ((temp-file (flymake-proc-init-create-temp-buffer-copy + 'flymake-proc-create-temp-inplace)) + (local-file (file-relative-name + temp-file + (file-name-directory buffer-file-name)))) + (list "perl" (list "-wc " local-file)))) + +;;;; php-specific init-cleanup routines +(defun flymake-proc-php-init () + (let* ((temp-file (flymake-proc-init-create-temp-buffer-copy + 'flymake-proc-create-temp-inplace)) + (local-file (file-relative-name + temp-file + (file-name-directory buffer-file-name)))) + (list "php" (list "-f" local-file "-l")))) + +;;;; tex-specific init-cleanup routines +(defun flymake-proc--get-tex-args (file-name) + ;;(list "latex" (list "-c-style-errors" file-name)) + (list "texify" (list "--pdf" "--tex-option=-c-style-errors" file-name))) + +(defun flymake-proc-simple-tex-init () + (flymake-proc--get-tex-args (flymake-proc-init-create-temp-buffer-copy 'flymake-proc-create-temp-inplace))) + +;; Perhaps there should be a buffer-local variable flymake-master-file +;; that people can set to override this stuff. Could inherit from +;; the similar AUCTeX variable. +(defun flymake-proc-master-tex-init () + (let* ((temp-master-file-name (flymake-proc--init-create-temp-source-and-master-buffer-copy + 'flymake-proc-get-include-dirs-dot 'flymake-proc-create-temp-inplace + '("\\.tex\\'") + "[ \t]*\\in\\(?:put\\|clude\\)[ \t]*{\\(.*%s\\)}"))) + (when temp-master-file-name + (flymake-proc--get-tex-args temp-master-file-name)))) + +(defun flymake-proc--get-include-dirs-dot (_base-dir) + '(".")) + +;;;; xml-specific init-cleanup routines +(defun flymake-proc-xml-init () + (list flymake-proc-xml-program + (list "val" (flymake-proc-init-create-temp-buffer-copy + 'flymake-proc-create-temp-inplace)))) + + +;;;; Hook onto flymake-ui +(add-hook 'flymake-diagnostic-functions 'flymake-proc-legacy-flymake) + + +;;;; + +(progn + (define-obsolete-variable-alias 'flymake-compilation-prevents-syntax-check + 'flymake-proc-compilation-prevents-syntax-check "26.1") + (define-obsolete-variable-alias 'flymake-xml-program + 'flymake-proc-xml-program "26.1") + (define-obsolete-variable-alias 'flymake-master-file-dirs + 'flymake-proc-master-file-dirs "26.1") + (define-obsolete-variable-alias 'flymake-master-file-count-limit + 'flymake-proc-master-file-count-limit "26.1" + "Max number of master files to check.") + (define-obsolete-variable-alias 'flymake-allowed-file-name-masks + 'flymake-proc-allowed-file-name-masks "26.1") + (define-obsolete-variable-alias 'flymake-check-file-limit + 'flymake-proc-check-file-limit "26.1") + (define-obsolete-function-alias 'flymake-reformat-err-line-patterns-from-compile-el + 'flymake-proc-reformat-err-line-patterns-from-compile-el "26.1") + (define-obsolete-variable-alias 'flymake-err-line-patterns + 'flymake-proc-err-line-patterns "26.1") + (define-obsolete-function-alias 'flymake-parse-line + 'flymake-proc-parse-line "26.1") + (define-obsolete-function-alias 'flymake-get-include-dirs + 'flymake-proc-get-include-dirs "26.1") + (define-obsolete-function-alias 'flymake-stop-all-syntax-checks + 'flymake-proc-stop-all-syntax-checks "26.1") + (define-obsolete-function-alias 'flymake-compile + 'flymake-proc-compile "26.1") + (define-obsolete-function-alias 'flymake-create-temp-inplace + 'flymake-proc-create-temp-inplace "26.1") + (define-obsolete-function-alias 'flymake-create-temp-with-folder-structure + 'flymake-proc-create-temp-with-folder-structure "26.1") + (define-obsolete-function-alias 'flymake-init-create-temp-buffer-copy + 'flymake-proc-init-create-temp-buffer-copy "26.1") + (define-obsolete-function-alias 'flymake-simple-cleanup + 'flymake-proc-simple-cleanup "26.1") + (define-obsolete-function-alias 'flymake-get-real-file-name + 'flymake-proc-get-real-file-name "26.1") + (define-obsolete-function-alias 'flymake-master-cleanup + 'flymake-proc-master-cleanup "26.1") + (define-obsolete-function-alias 'flymake-get-make-cmdline + 'flymake-proc-get-make-cmdline "26.1") + (define-obsolete-function-alias 'flymake-get-ant-cmdline + 'flymake-proc-get-ant-cmdline "26.1") + (define-obsolete-function-alias 'flymake-simple-make-init-impl + 'flymake-proc-simple-make-init-impl "26.1") + (define-obsolete-function-alias 'flymake-simple-make-init + 'flymake-proc-simple-make-init "26.1") + (define-obsolete-function-alias 'flymake-master-make-init + 'flymake-proc-master-make-init "26.1") + (define-obsolete-function-alias 'flymake-find-make-buildfile + 'flymake-proc--find-make-buildfile "26.1") + (define-obsolete-function-alias 'flymake-master-make-header-init + 'flymake-proc-master-make-header-init "26.1") + (define-obsolete-function-alias 'flymake-simple-make-java-init + 'flymake-proc-simple-make-java-init "26.1") + (define-obsolete-function-alias 'flymake-simple-ant-java-init + 'flymake-proc-simple-ant-java-init "26.1") + (define-obsolete-function-alias 'flymake-simple-java-cleanup + 'flymake-proc-simple-java-cleanup "26.1") + (define-obsolete-function-alias 'flymake-perl-init + 'flymake-proc-perl-init "26.1") + (define-obsolete-function-alias 'flymake-php-init + 'flymake-proc-php-init "26.1") + (define-obsolete-function-alias 'flymake-simple-tex-init + 'flymake-proc-simple-tex-init "26.1") + (define-obsolete-function-alias 'flymake-master-tex-init + 'flymake-proc-master-tex-init "26.1") + (define-obsolete-function-alias 'flymake-xml-init + 'flymake-proc-xml-init "26.1")) + + + +(provide 'flymake-proc) +;;; flymake-proc.el ends here diff --git a/lisp/progmodes/flymake.el b/lisp/progmodes/flymake.el index ed34d9aaa52..15a36175970 100644 --- a/lisp/progmodes/flymake.el +++ b/lisp/progmodes/flymake.el @@ -1,4 +1,4 @@ -;;; flymake.el --- a universal on-the-fly syntax checker -*- lexical-binding: t; -*- +;;; flymake.el --- A universal on-the-fly syntax checker -*- lexical-binding: t; -*- ;; Copyright (C) 2003-2017 Free Software Foundation, Inc. @@ -20,22 +20,36 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; -;; Flymake is a minor Emacs mode performing on-the-fly syntax checks -;; using the external syntax check tool (for C/C++ this is usually the -;; compiler). - -;;; Bugs/todo: - -;; - Only uses "Makefile", not "makefile" or "GNUmakefile" -;; (from http://bugs.debian.org/337339). - +;; Flymake is a minor Emacs mode performing on-the-fly syntax checks. +;; +;; Flymake collects diagnostic information for multiple sources, +;; called backends, and visually annotates the relevant portions in +;; the buffer. +;; +;; This file contains the UI for displaying and interacting with the +;; results produced by these backends, as well as entry points for +;; backends to hook on to. +;; +;; The main entry points are `flymake-mode' and `flymake-start' +;; +;; The docstrings of these variables are relevant to understanding how +;; Flymake works for both the user and the backend programmer: +;; +;; * `flymake-diagnostic-functions' +;; * `flymake-diagnostic-types-alist' +;; ;;; Code: -(eval-when-compile (require 'cl-lib)) +(require 'cl-lib) +(require 'thingatpt) ; end-of-thing +(require 'warnings) ; warning-numeric-level, display-warning +(require 'compile) ; for some faces +;; when-let*, if-let*, hash-table-keys, hash-table-values: +(eval-when-compile (require 'subr-x)) (defgroup flymake nil "Universal on-the-fly syntax checker." @@ -43,7 +57,8 @@ :link '(custom-manual "(flymake) Top") :group 'tools) -(defcustom flymake-error-bitmap '(exclamation-mark error) +(defcustom flymake-error-bitmap '(flymake-double-exclamation-mark + compilation-error) "Bitmap (a symbol) used in the fringe for indicating errors. The value may also be a list of two elements where the second element specifies the face for the bitmap. For possible bitmap @@ -51,14 +66,13 @@ symbols, see `fringe-bitmaps'. See also `flymake-warning-bitmap'. The option `flymake-fringe-indicator-position' controls how and where this is used." - :group 'flymake :version "24.3" :type '(choice (symbol :tag "Bitmap") (list :tag "Bitmap and face" (symbol :tag "Bitmap") (face :tag "Face")))) -(defcustom flymake-warning-bitmap 'question-mark +(defcustom flymake-warning-bitmap '(exclamation-mark compilation-warning) "Bitmap (a symbol) used in the fringe for indicating warnings. The value may also be a list of two elements where the second element specifies the face for the bitmap. For possible bitmap @@ -66,1176 +80,763 @@ symbols, see `fringe-bitmaps'. See also `flymake-error-bitmap'. The option `flymake-fringe-indicator-position' controls how and where this is used." - :group 'flymake :version "24.3" :type '(choice (symbol :tag "Bitmap") (list :tag "Bitmap and face" (symbol :tag "Bitmap") (face :tag "Face")))) +(defcustom flymake-note-bitmap '(exclamation-mark compilation-info) + "Bitmap (a symbol) used in the fringe for indicating info notes. +The value may also be a list of two elements where the second +element specifies the face for the bitmap. For possible bitmap +symbols, see `fringe-bitmaps'. See also `flymake-error-bitmap'. + +The option `flymake-fringe-indicator-position' controls how and where +this is used." + :version "26.1" + :type '(choice (symbol :tag "Bitmap") + (list :tag "Bitmap and face" + (symbol :tag "Bitmap") + (face :tag "Face")))) + (defcustom flymake-fringe-indicator-position 'left-fringe - "The position to put flymake fringe indicator. + "The position to put Flymake fringe indicator. The value can be nil (do not use indicators), `left-fringe' or `right-fringe'. See `flymake-error-bitmap' and `flymake-warning-bitmap'." - :group 'flymake :version "24.3" :type '(choice (const left-fringe) (const right-fringe) (const :tag "No fringe indicators" nil))) -(defcustom flymake-compilation-prevents-syntax-check t - "If non-nil, don't start syntax check if compilation is running." - :group 'flymake - :type 'boolean) - (defcustom flymake-start-syntax-check-on-newline t "Start syntax check if newline char was added/removed from the buffer." - :group 'flymake :type 'boolean) (defcustom flymake-no-changes-timeout 0.5 - "Time to wait after last change before starting compilation." - :group 'flymake + "Time to wait after last change before automatically checking buffer. +If nil, never start checking buffer automatically like this." :type 'number) (defcustom flymake-gui-warnings-enabled t "Enables/disables GUI warnings." - :group 'flymake :type 'boolean) (make-obsolete-variable 'flymake-gui-warnings-enabled "it no longer has any effect." "26.1") -(defcustom flymake-start-syntax-check-on-find-file t - "Start syntax check on find file." - :group 'flymake +(defcustom flymake-start-on-flymake-mode t + "Start syntax check when `flymake-mode' is enabled. +Specifically, start it when the buffer is actually displayed." :type 'boolean) +(define-obsolete-variable-alias 'flymake-start-syntax-check-on-find-file + 'flymake-start-on-flymake-mode "26.1") + (defcustom flymake-log-level -1 - "Logging level, only messages with level lower or equal will be logged. --1 = NONE, 0 = ERROR, 1 = WARNING, 2 = INFO, 3 = DEBUG" - :group 'flymake + "Obsolete and ignored variable." :type 'integer) +(make-obsolete-variable 'flymake-log-level + "it is superseded by `warning-minimum-log-level.'" + "26.1") -(defcustom flymake-xml-program - (if (executable-find "xmlstarlet") "xmlstarlet" "xml") - "Program to use for XML validation." - :type 'file - :group 'flymake - :version "24.4") - -(defcustom flymake-master-file-dirs '("." "./src" "./UnitTest") - "Dirs where to look for master files." - :group 'flymake - :type '(repeat (string))) - -(defcustom flymake-master-file-count-limit 32 - "Max number of master files to check." - :group 'flymake - :type 'integer) +(defcustom flymake-wrap-around t + "If non-nil, moving to errors wraps around buffer boundaries." + :type 'boolean) -(defcustom flymake-allowed-file-name-masks - '(("\\.\\(?:c\\(?:pp\\|xx\\|\\+\\+\\)?\\|CC\\)\\'" flymake-simple-make-init) - ("\\.xml\\'" flymake-xml-init) - ("\\.html?\\'" flymake-xml-init) - ("\\.cs\\'" flymake-simple-make-init) - ("\\.p[ml]\\'" flymake-perl-init) - ("\\.php[345]?\\'" flymake-php-init) - ("\\.h\\'" flymake-master-make-header-init flymake-master-cleanup) - ("\\.java\\'" flymake-simple-make-java-init flymake-simple-java-cleanup) - ("[0-9]+\\.tex\\'" flymake-master-tex-init flymake-master-cleanup) - ("\\.tex\\'" flymake-simple-tex-init) - ("\\.idl\\'" flymake-simple-make-init) - ;; ("\\.cpp\\'" 1) - ;; ("\\.java\\'" 3) - ;; ("\\.h\\'" 2 ("\\.cpp\\'" "\\.c\\'") - ;; ("[ \t]*#[ \t]*include[ \t]*\"\\([\w0-9/\\_\.]*[/\\]*\\)\\(%s\\)\"" 1 2)) - ;; ("\\.idl\\'" 1) - ;; ("\\.odl\\'" 1) - ;; ("[0-9]+\\.tex\\'" 2 ("\\.tex\\'") - ;; ("[ \t]*\\input[ \t]*{\\(.*\\)\\(%s\\)}" 1 2 )) - ;; ("\\.tex\\'" 1) - ) - "Files syntax checking is allowed for. -This is an alist with elements of the form: - REGEXP INIT [CLEANUP [NAME]] -REGEXP is a regular expression that matches a file name. -INIT is the init function to use. -CLEANUP is the cleanup function to use, default `flymake-simple-cleanup'. -NAME is the file name function to use, default `flymake-get-real-file-name'." - :group 'flymake - :type '(alist :key-type (regexp :tag "File regexp") - :value-type - (list :tag "Handler functions" - (function :tag "Init function") - (choice :tag "Cleanup function" - (const :tag "flymake-simple-cleanup" nil) - function) - (choice :tag "Name function" - (const :tag "flymake-get-real-file-name" nil) - function)))) - -(defvar-local flymake-is-running nil - "If t, flymake syntax check process is running for the current buffer.") +(when (fboundp 'define-fringe-bitmap) + (define-fringe-bitmap 'flymake-double-exclamation-mark + (vector #b00000000 + #b00000000 + #b00000000 + #b00000000 + #b01100110 + #b01100110 + #b01100110 + #b01100110 + #b01100110 + #b01100110 + #b01100110 + #b01100110 + #b00000000 + #b01100110 + #b00000000 + #b00000000 + #b00000000))) (defvar-local flymake-timer nil "Timer for starting syntax check.") -(defvar-local flymake-last-change-time nil - "Time of last buffer change.") - (defvar-local flymake-check-start-time nil "Time at which syntax check was started.") -(defvar-local flymake-check-was-interrupted nil - "Non-nil if syntax check was killed by `flymake-compile'.") - -(defvar-local flymake-err-info nil - "Sorted list of line numbers and lists of err info in the form (file, err-text).") - -(defvar-local flymake-new-err-info nil - "Same as `flymake-err-info', effective when a syntax check is in progress.") - -(defun flymake-log (level text &rest args) - "Log a message at level LEVEL. -If LEVEL is higher than `flymake-log-level', the message is -ignored. Otherwise, it is printed using `message'. -TEXT is a format control string, and the remaining arguments ARGS -are the string substitutions (see the function `format')." - (if (<= level flymake-log-level) - (let* ((msg (apply #'format-message text args))) - (message "%s" msg)))) - -(defun flymake-ins-after (list pos val) - "Insert VAL into LIST after position POS. -POS counts from zero." - (let ((tmp (copy-sequence list))) - (setcdr (nthcdr pos tmp) (cons val (nthcdr (1+ pos) tmp))) - tmp)) - -(defun flymake-set-at (list pos val) - "Set VAL at position POS in LIST. -POS counts from zero." - (let ((tmp (copy-sequence list))) - (setcar (nthcdr pos tmp) val) - tmp)) - -(defvar flymake-processes nil - "List of currently active flymake processes.") - -(defvar-local flymake-output-residual nil) - -(defun flymake-get-file-name-mode-and-masks (file-name) - "Return the corresponding entry from `flymake-allowed-file-name-masks'." - (unless (stringp file-name) - (error "Invalid file-name")) - (let ((fnm flymake-allowed-file-name-masks) - (mode-and-masks nil)) - (while (and (not mode-and-masks) fnm) - (if (string-match (car (car fnm)) file-name) - (setq mode-and-masks (cdr (car fnm)))) - (setq fnm (cdr fnm))) - (flymake-log 3 "file %s, init=%s" file-name (car mode-and-masks)) - mode-and-masks)) - -(defun flymake-can-syntax-check-file (file-name) - "Determine whether we can syntax check FILE-NAME. -Return nil if we cannot, non-nil if we can." - (if (flymake-get-init-function file-name) t nil)) - -(defun flymake-get-init-function (file-name) - "Return init function to be used for the file." - (let* ((init-f (nth 0 (flymake-get-file-name-mode-and-masks file-name)))) - ;;(flymake-log 0 "calling %s" init-f) - ;;(funcall init-f (current-buffer)) - init-f)) - -(defun flymake-get-cleanup-function (file-name) - "Return cleanup function to be used for the file." - (or (nth 1 (flymake-get-file-name-mode-and-masks file-name)) - 'flymake-simple-cleanup)) - -(defun flymake-get-real-file-name-function (file-name) - (or (nth 2 (flymake-get-file-name-mode-and-masks file-name)) - 'flymake-get-real-file-name)) - -(defvar flymake-find-buildfile-cache (make-hash-table :test #'equal)) - -(defun flymake-get-buildfile-from-cache (dir-name) - "Look up DIR-NAME in cache and return its associated value. -If DIR-NAME is not found, return nil." - (gethash dir-name flymake-find-buildfile-cache)) - -(defun flymake-add-buildfile-to-cache (dir-name buildfile) - "Associate DIR-NAME with BUILDFILE in the buildfile cache." - (puthash dir-name buildfile flymake-find-buildfile-cache)) - -(defun flymake-clear-buildfile-cache () - "Clear the buildfile cache." - (clrhash flymake-find-buildfile-cache)) - -(defun flymake-find-buildfile (buildfile-name source-dir-name) - "Find buildfile starting from current directory. -Buildfile includes Makefile, build.xml etc. -Return its file name if found, or nil if not found." - (or (flymake-get-buildfile-from-cache source-dir-name) - (let* ((file (locate-dominating-file source-dir-name buildfile-name))) - (if file - (progn - (flymake-log 3 "found buildfile at %s" file) - (flymake-add-buildfile-to-cache source-dir-name file) - file) - (progn - (flymake-log 3 "buildfile for %s not found" source-dir-name) - nil))))) - -(defun flymake-fix-file-name (name) - "Replace all occurrences of `\\' with `/'." - (when name - (setq name (expand-file-name name)) - (setq name (abbreviate-file-name name)) - (setq name (directory-file-name name)) - name)) - -(defun flymake-same-files (file-name-one file-name-two) - "Check if FILE-NAME-ONE and FILE-NAME-TWO point to same file. -Return t if so, nil if not." - (equal (flymake-fix-file-name file-name-one) - (flymake-fix-file-name file-name-two))) - -;; This is bound dynamically to pass a parameter to a sort predicate below -(defvar flymake-included-file-name) - -(defun flymake-find-possible-master-files (file-name master-file-dirs masks) - "Find (by name and location) all possible master files. - -Name is specified by FILE-NAME and location is specified by -MASTER-FILE-DIRS. Master files include .cpp and .c for .h. -Files are searched for starting from the .h directory and max -max-level parent dirs. File contents are not checked." - (let* ((dirs master-file-dirs) - (files nil) - (done nil)) - - (while (and (not done) dirs) - (let* ((dir (expand-file-name (car dirs) (file-name-directory file-name))) - (masks masks)) - (while (and (file-exists-p dir) (not done) masks) - (let* ((mask (car masks)) - (dir-files (directory-files dir t mask))) - - (flymake-log 3 "dir %s, %d file(s) for mask %s" - dir (length dir-files) mask) - (while (and (not done) dir-files) - (when (not (file-directory-p (car dir-files))) - (setq files (cons (car dir-files) files)) - (when (>= (length files) flymake-master-file-count-limit) - (flymake-log 3 "master file count limit (%d) reached" flymake-master-file-count-limit) - (setq done t))) - (setq dir-files (cdr dir-files)))) - (setq masks (cdr masks)))) - (setq dirs (cdr dirs))) - (when files - (let ((flymake-included-file-name (file-name-nondirectory file-name))) - (setq files (sort files 'flymake-master-file-compare)))) - (flymake-log 3 "found %d possible master file(s)" (length files)) - files)) - -(defun flymake-master-file-compare (file-one file-two) - "Compare two files specified by FILE-ONE and FILE-TWO. -This function is used in sort to move most possible file names -to the beginning of the list (File.h -> File.cpp moved to top)." - (and (equal (file-name-sans-extension flymake-included-file-name) - (file-name-base file-one)) - (not (equal file-one file-two)))) - -(defvar flymake-check-file-limit 8192 - "Maximum number of chars to look at when checking possible master file. -Nil means search the entire file.") - -(defun flymake-check-patch-master-file-buffer - (master-file-temp-buffer - master-file-name patched-master-file-name - source-file-name patched-source-file-name - include-dirs regexp) - "Check if MASTER-FILE-NAME is a master file for SOURCE-FILE-NAME. -If yes, patch a copy of MASTER-FILE-NAME to include PATCHED-SOURCE-FILE-NAME -instead of SOURCE-FILE-NAME. - -For example, foo.cpp is a master file if it includes foo.h. - -When a buffer for MASTER-FILE-NAME exists, use it as a source -instead of reading master file from disk." - (let* ((source-file-nondir (file-name-nondirectory source-file-name)) - (source-file-extension (file-name-extension source-file-nondir)) - (source-file-nonext (file-name-sans-extension source-file-nondir)) - (found nil) - (inc-name nil) - (search-limit flymake-check-file-limit)) - (setq regexp - (format regexp ; "[ \t]*#[ \t]*include[ \t]*\"\\(.*%s\\)\"" - ;; Hack for tex files, where \include often excludes .tex. - ;; Maybe this is safe generally. - (if (and (> (length source-file-extension) 1) - (string-equal source-file-extension "tex")) - (format "%s\\(?:\\.%s\\)?" - (regexp-quote source-file-nonext) - (regexp-quote source-file-extension)) - (regexp-quote source-file-nondir)))) - (unwind-protect - (with-current-buffer master-file-temp-buffer - (if (or (not search-limit) - (> search-limit (point-max))) - (setq search-limit (point-max))) - (flymake-log 3 "checking %s against regexp %s" - master-file-name regexp) - (goto-char (point-min)) - (while (and (< (point) search-limit) - (re-search-forward regexp search-limit t)) - (let ((match-beg (match-beginning 1)) - (match-end (match-end 1))) - - (flymake-log 3 "found possible match for %s" source-file-nondir) - (setq inc-name (match-string 1)) - (and (> (length source-file-extension) 1) - (string-equal source-file-extension "tex") - (not (string-match (format "\\.%s\\'" source-file-extension) - inc-name)) - (setq inc-name (concat inc-name "." source-file-extension))) - (when (eq t (compare-strings - source-file-nondir nil nil - inc-name (- (length inc-name) - (length source-file-nondir)) nil)) - (flymake-log 3 "inc-name=%s" inc-name) - (when (flymake-check-include source-file-name inc-name - include-dirs) - (setq found t) - ;; replace-match is not used here as it fails in - ;; XEmacs with 'last match not a buffer' error as - ;; check-includes calls replace-in-string - (flymake-replace-region - match-beg match-end - (file-name-nondirectory patched-source-file-name)))) - (forward-line 1))) - (when found - (flymake-save-buffer-in-file patched-master-file-name))) - ;;+(flymake-log 3 "killing buffer %s" - ;; (buffer-name master-file-temp-buffer)) - (kill-buffer master-file-temp-buffer)) - ;;+(flymake-log 3 "check-patch master file %s: %s" master-file-name found) - (when found - (flymake-log 2 "found master file %s" master-file-name)) - found)) - -;;; XXX: remove -(defun flymake-replace-region (beg end rep) - "Replace text in BUFFER in region (BEG END) with REP." - (save-excursion - (goto-char end) - ;; Insert before deleting, so as to better preserve markers's positions. - (insert rep) - (delete-region beg end))) - -(defun flymake-read-file-to-temp-buffer (file-name) - "Insert contents of FILE-NAME into newly created temp buffer." - (let* ((temp-buffer (get-buffer-create (generate-new-buffer-name (concat "flymake:" (file-name-nondirectory file-name)))))) - (with-current-buffer temp-buffer - (insert-file-contents file-name)) - temp-buffer)) - -(defun flymake-copy-buffer-to-temp-buffer (buffer) - "Copy contents of BUFFER into newly created temp buffer." - (with-current-buffer - (get-buffer-create (generate-new-buffer-name - (concat "flymake:" (buffer-name buffer)))) - (insert-buffer-substring buffer) - (current-buffer))) - -(defun flymake-check-include (source-file-name inc-name include-dirs) - "Check if SOURCE-FILE-NAME can be found in include path. -Return t if it can be found via include path using INC-NAME." - (if (file-name-absolute-p inc-name) - (flymake-same-files source-file-name inc-name) - (while (and include-dirs - (not (flymake-same-files - source-file-name - (concat (file-name-directory source-file-name) - "/" (car include-dirs) - "/" inc-name)))) - (setq include-dirs (cdr include-dirs))) - include-dirs)) - -(defun flymake-find-buffer-for-file (file-name) - "Check if there exists a buffer visiting FILE-NAME. -Return t if so, nil if not." - (let ((buffer-name (get-file-buffer file-name))) - (if buffer-name - (get-buffer buffer-name)))) - -(defun flymake-create-master-file (source-file-name patched-source-file-name get-incl-dirs-f create-temp-f masks include-regexp) - "Save SOURCE-FILE-NAME with a different name. -Find master file, patch and save it." - (let* ((possible-master-files (flymake-find-possible-master-files source-file-name flymake-master-file-dirs masks)) - (master-file-count (length possible-master-files)) - (idx 0) - (temp-buffer nil) - (master-file-name nil) - (patched-master-file-name nil) - (found nil)) - - (while (and (not found) (< idx master-file-count)) - (setq master-file-name (nth idx possible-master-files)) - (setq patched-master-file-name (funcall create-temp-f master-file-name "flymake_master")) - (if (flymake-find-buffer-for-file master-file-name) - (setq temp-buffer (flymake-copy-buffer-to-temp-buffer (flymake-find-buffer-for-file master-file-name))) - (setq temp-buffer (flymake-read-file-to-temp-buffer master-file-name))) - (setq found - (flymake-check-patch-master-file-buffer - temp-buffer - master-file-name - patched-master-file-name - source-file-name - patched-source-file-name - (funcall get-incl-dirs-f (file-name-directory master-file-name)) - include-regexp)) - (setq idx (1+ idx))) - (if found - (list master-file-name patched-master-file-name) - (progn - (flymake-log 3 "none of %d master file(s) checked includes %s" master-file-count - (file-name-nondirectory source-file-name)) - nil)))) - -(defun flymake-save-buffer-in-file (file-name) - "Save the entire buffer contents into file FILE-NAME. -Create parent directories as needed." - (make-directory (file-name-directory file-name) 1) - (write-region nil nil file-name nil 566) - (flymake-log 3 "saved buffer %s in file %s" (buffer-name) file-name)) - -(defun flymake-process-filter (process output) - "Parse OUTPUT and highlight error lines. -It's flymake process filter." - (let ((source-buffer (process-buffer process))) - - (flymake-log 3 "received %d byte(s) of output from process %d" - (length output) (process-id process)) - (when (buffer-live-p source-buffer) - (with-current-buffer source-buffer - (flymake-parse-output-and-residual output))))) - -(defun flymake-process-sentinel (process _event) - "Sentinel for syntax check buffers." - (when (memq (process-status process) '(signal exit)) - (let* ((exit-status (process-exit-status process)) - (command (process-command process)) - (source-buffer (process-buffer process)) - (cleanup-f (flymake-get-cleanup-function (buffer-file-name source-buffer)))) - - (flymake-log 2 "process %d exited with code %d" - (process-id process) exit-status) - (condition-case err - (progn - (flymake-log 3 "cleaning up using %s" cleanup-f) - (when (buffer-live-p source-buffer) - (with-current-buffer source-buffer - (funcall cleanup-f))) - - (delete-process process) - (setq flymake-processes (delq process flymake-processes)) - - (when (buffer-live-p source-buffer) - (with-current-buffer source-buffer - - (flymake-parse-residual) - (flymake-post-syntax-check exit-status command) - (setq flymake-is-running nil)))) - (error - (let ((err-str (format "Error in process sentinel for buffer %s: %s" - source-buffer (error-message-string err)))) - (flymake-log 0 err-str) - (with-current-buffer source-buffer - (setq flymake-is-running nil)))))))) - -(defun flymake-post-syntax-check (exit-status command) +(defun flymake--log-1 (level sublog msg &rest args) + "Do actual work for `flymake-log'." + (let (;; never popup the log buffer + (warning-minimum-level :emergency) + (warning-type-format + (format " [%s %s]" + (or sublog 'flymake) + (current-buffer)))) + (display-warning (list 'flymake sublog) + (apply #'format-message msg args) + (if (numberp level) + (or (nth level + '(:emergency :error :warning :debug :debug) ) + :error) + level) + "*Flymake log*"))) + +(defun flymake-switch-to-log-buffer () + "Go to the *Flymake log* buffer." + (interactive) + (switch-to-buffer "*Flymake log*")) + +;;;###autoload +(defmacro flymake-log (level msg &rest args) + "Log, at level LEVEL, the message MSG formatted with ARGS. +LEVEL is passed to `display-warning', which is used to display +the warning. If this form is included in a byte-compiled file, +the generated warning contains an indication of the file that +generated it." + (let* ((compile-file (and (boundp 'byte-compile-current-file) + (symbol-value 'byte-compile-current-file))) + (sublog (if (and + compile-file + (not load-file-name)) + (intern + (file-name-nondirectory + (file-name-sans-extension compile-file)))))) + `(flymake--log-1 ,level ',sublog ,msg ,@args))) + +(defun flymake-error (text &rest args) + "Format TEXT with ARGS and signal an error for Flymake." + (let ((msg (apply #'format-message text args))) + (flymake-log :error msg) + (error (concat "[Flymake] " msg)))) + +(cl-defstruct (flymake--diag + (:constructor flymake--diag-make)) + buffer beg end type text backend) + +;;;###autoload +(defun flymake-make-diagnostic (buffer + beg + end + type + text) + "Make a Flymake diagnostic for BUFFER's region from BEG to END. +TYPE is a key to `flymake-diagnostic-types-alist' and TEXT is a +description of the problem detected in this region." + (flymake--diag-make :buffer buffer :beg beg :end end :type type :text text)) + +;;;###autoload +(defun flymake-diagnostics (&optional beg end) + "Get Flymake diagnostics in region determined by BEG and END. + +If neither BEG or END is supplied, use the whole buffer, +otherwise if BEG is non-nil and END is nil, consider only +diagnostics at BEG." + (mapcar (lambda (ov) (overlay-get ov 'flymake-diagnostic)) + (flymake--overlays :beg beg :end end))) + +(defmacro flymake--diag-accessor (public internal thing) + "Make PUBLIC an alias for INTERNAL, add doc using THING." + `(defsubst ,public (diag) + ,(format "Get Flymake diagnostic DIAG's %s." (symbol-name thing)) + (,internal diag))) + +(flymake--diag-accessor flymake-diagnostic-buffer flymake--diag-buffer buffer) +(flymake--diag-accessor flymake-diagnostic-text flymake--diag-text text) +(flymake--diag-accessor flymake-diagnostic-type flymake--diag-type type) +(flymake--diag-accessor flymake-diagnostic-beg flymake--diag-beg beg) +(flymake--diag-accessor flymake-diagnostic-end flymake--diag-end end) +(flymake--diag-accessor flymake-diagnostic-backend flymake--diag-backend backend) + +(cl-defun flymake--overlays (&key beg end filter compare key) + "Get flymake-related overlays. +If BEG is non-nil and END is nil, consider only `overlays-at' +BEG. Otherwise consider `overlays-in' the region comprised by BEG +and END, defaulting to the whole buffer. Remove all that do not +verify FILTER, a function, and sort them by COMPARE (using KEY)." (save-restriction (widen) - (setq flymake-err-info flymake-new-err-info) - (setq flymake-new-err-info nil) - (setq flymake-err-info - (flymake-fix-line-numbers - flymake-err-info 1 (count-lines (point-min) (point-max)))) - (flymake-delete-own-overlays) - (flymake-highlight-err-lines flymake-err-info) - (let (err-count warn-count) - (setq err-count (flymake-get-err-count flymake-err-info "e")) - (setq warn-count (flymake-get-err-count flymake-err-info "w")) - (flymake-log 2 "%s: %d error(s), %d warning(s) in %.2f second(s)" - (buffer-name) err-count warn-count - (- (float-time) flymake-check-start-time)) - (setq flymake-check-start-time nil) - - (if (and (equal 0 err-count) (equal 0 warn-count)) - (if (equal 0 exit-status) - (flymake-report-status "" "") ; PASSED - (if (not flymake-check-was-interrupted) - (flymake-report-fatal-status "CFGERR" - (format "Configuration error has occurred while running %s" command)) - (flymake-report-status nil ""))) ; "STOPPED" - (flymake-report-status (format "%d/%d" err-count warn-count) ""))))) - -(defun flymake-parse-output-and-residual (output) - "Split OUTPUT into lines, merge in residual if necessary." - (let* ((buffer-residual flymake-output-residual) - (total-output (if buffer-residual (concat buffer-residual output) output)) - (lines-and-residual (flymake-split-output total-output)) - (lines (nth 0 lines-and-residual)) - (new-residual (nth 1 lines-and-residual))) - (setq flymake-output-residual new-residual) - (setq flymake-new-err-info - (flymake-parse-err-lines - flymake-new-err-info lines)))) - -(defun flymake-parse-residual () - "Parse residual if it's non empty." - (when flymake-output-residual - (setq flymake-new-err-info - (flymake-parse-err-lines - flymake-new-err-info - (list flymake-output-residual))) - (setq flymake-output-residual nil))) - -(defun flymake-er-make-er (line-no line-err-info-list) - (list line-no line-err-info-list)) - -(defun flymake-er-get-line (err-info) - (nth 0 err-info)) - -(defun flymake-er-get-line-err-info-list (err-info) - (nth 1 err-info)) - -(cl-defstruct (flymake-ler - (:constructor nil) - (:constructor flymake-ler-make-ler (file line type text &optional full-file))) - file line type text full-file) - -(defun flymake-ler-set-file (line-err-info file) - (flymake-ler-make-ler file - (flymake-ler-line line-err-info) - (flymake-ler-type line-err-info) - (flymake-ler-text line-err-info) - (flymake-ler-full-file line-err-info))) - -(defun flymake-ler-set-full-file (line-err-info full-file) - (flymake-ler-make-ler (flymake-ler-file line-err-info) - (flymake-ler-line line-err-info) - (flymake-ler-type line-err-info) - (flymake-ler-text line-err-info) - full-file)) - -(defun flymake-ler-set-line (line-err-info line) - (flymake-ler-make-ler (flymake-ler-file line-err-info) - line - (flymake-ler-type line-err-info) - (flymake-ler-text line-err-info) - (flymake-ler-full-file line-err-info))) - -(defun flymake-get-line-err-count (line-err-info-list type) - "Return number of errors of specified TYPE. -Value of TYPE is either \"e\" or \"w\"." - (let* ((idx 0) - (count (length line-err-info-list)) - (err-count 0)) - - (while (< idx count) - (when (equal type (flymake-ler-type (nth idx line-err-info-list))) - (setq err-count (1+ err-count))) - (setq idx (1+ idx))) - err-count)) - -(defun flymake-get-err-count (err-info-list type) - "Return number of errors of specified TYPE for ERR-INFO-LIST." - (let* ((idx 0) - (count (length err-info-list)) - (err-count 0)) - (while (< idx count) - (setq err-count (+ err-count (flymake-get-line-err-count (nth 1 (nth idx err-info-list)) type))) - (setq idx (1+ idx))) - err-count)) - -(defun flymake-fix-line-numbers (err-info-list min-line max-line) - "Replace line numbers with fixed value. -If line-numbers is less than MIN-LINE, set line numbers to MIN-LINE. -If line numbers is greater than MAX-LINE, set line numbers to MAX-LINE. -The reason for this fix is because some compilers might report -line number outside the file being compiled." - (let* ((count (length err-info-list)) - (err-info nil) - (line 0)) - (while (> count 0) - (setq err-info (nth (1- count) err-info-list)) - (setq line (flymake-er-get-line err-info)) - (when (or (< line min-line) (> line max-line)) - (setq line (if (< line min-line) min-line max-line)) - (setq err-info-list (flymake-set-at err-info-list (1- count) - (flymake-er-make-er line - (flymake-er-get-line-err-info-list err-info))))) - (setq count (1- count)))) - err-info-list) - -(defun flymake-highlight-err-lines (err-info-list) - "Highlight error lines in BUFFER using info from ERR-INFO-LIST." - (save-excursion - (dolist (err err-info-list) - (flymake-highlight-line (car err) (nth 1 err))))) - -(defun flymake-overlay-p (ov) - "Determine whether overlay OV was created by flymake." - (and (overlayp ov) (overlay-get ov 'flymake-overlay))) - -(defun flymake-make-overlay (beg end tooltip-text face bitmap) - "Allocate a flymake overlay in range BEG and END." - (when (not (flymake-region-has-flymake-overlays beg end)) - (let ((ov (make-overlay beg end nil t)) - (fringe (and flymake-fringe-indicator-position - (propertize "!" 'display - (cons flymake-fringe-indicator-position - (if (listp bitmap) - bitmap - (list bitmap))))))) - (overlay-put ov 'face face) - (overlay-put ov 'help-echo tooltip-text) - (overlay-put ov 'flymake-overlay t) - (overlay-put ov 'priority 100) - (overlay-put ov 'evaporate t) - (overlay-put ov 'before-string fringe) - ;;+(flymake-log 3 "created overlay %s" ov) - ov) - (flymake-log 3 "created an overlay at (%d-%d)" beg end))) - -(defun flymake-delete-own-overlays () - "Delete all flymake overlays in BUFFER." - (dolist (ol (overlays-in (point-min) (point-max))) - (when (flymake-overlay-p ol) - (delete-overlay ol) - ;;+(flymake-log 3 "deleted overlay %s" ol) - ))) - -(defun flymake-region-has-flymake-overlays (beg end) - "Check if region specified by BEG and END has overlay. -Return t if it has at least one flymake overlay, nil if no overlay." - (let ((ov (overlays-in beg end)) - (has-flymake-overlays nil)) - (while (consp ov) - (when (flymake-overlay-p (car ov)) - (setq has-flymake-overlays t)) - (setq ov (cdr ov))) - has-flymake-overlays)) - -(defface flymake-errline + (let ((ovs (cl-remove-if-not + (lambda (ov) + (and (overlay-get ov 'flymake-diagnostic) + (or (not filter) + (funcall filter ov)))) + (if (and beg (null end)) + (overlays-at beg t) + (overlays-in (or beg (point-min)) + (or end (point-max))))))) + (if compare + (cl-sort ovs compare :key (or key + #'identity)) + ovs)))) + +(defun flymake-delete-own-overlays (&optional filter) + "Delete all Flymake overlays in BUFFER." + (mapc #'delete-overlay (flymake--overlays :filter filter))) + +(defface flymake-error '((((supports :underline (:style wave))) :underline (:style wave :color "Red1")) (t :inherit error)) - "Face used for marking error lines." - :version "24.4" - :group 'flymake) + "Face used for marking error regions." + :version "24.4") -(defface flymake-warnline +(defface flymake-warning '((((supports :underline (:style wave))) - :underline (:style wave :color "DarkOrange")) + :underline (:style wave :color "deep sky blue")) (t :inherit warning)) - "Face used for marking warning lines." - :version "24.4" - :group 'flymake) - -(defun flymake-highlight-line (line-no line-err-info-list) - "Highlight line LINE-NO in current buffer. -Perhaps use text from LINE-ERR-INFO-LIST to enhance highlighting." - (goto-char (point-min)) - (forward-line (1- line-no)) - (pcase-let* ((beg (progn (back-to-indentation) (point))) - (end (progn - (end-of-line) - (skip-chars-backward " \t\f\t\n" beg) - (if (eq (point) beg) - (line-beginning-position 2) - (point)))) - (tooltip-text (mapconcat #'flymake-ler-text line-err-info-list "\n")) - (`(,face ,bitmap) - (if (> (flymake-get-line-err-count line-err-info-list "e") 0) - (list 'flymake-errline flymake-error-bitmap) - (list 'flymake-warnline flymake-warning-bitmap)))) - (flymake-make-overlay beg end tooltip-text face bitmap))) - -(defun flymake-parse-err-lines (err-info-list lines) - "Parse err LINES, store info in ERR-INFO-LIST." - (let* ((count (length lines)) - (idx 0) - (line-err-info nil) - (real-file-name nil) - (source-file-name buffer-file-name) - (get-real-file-name-f (flymake-get-real-file-name-function source-file-name))) - - (while (< idx count) - (setq line-err-info (flymake-parse-line (nth idx lines))) - (when line-err-info - (setq real-file-name (funcall get-real-file-name-f - (flymake-ler-file line-err-info))) - (setq line-err-info (flymake-ler-set-full-file line-err-info real-file-name)) - - (when (flymake-same-files real-file-name source-file-name) - (setq line-err-info (flymake-ler-set-file line-err-info nil)) - (setq err-info-list (flymake-add-err-info err-info-list line-err-info)))) - (flymake-log 3 "parsed `%s', %s line-err-info" (nth idx lines) (if line-err-info "got" "no")) - (setq idx (1+ idx))) - err-info-list)) - -(defun flymake-split-output (output) - "Split OUTPUT into lines. -Return last one as residual if it does not end with newline char. -Returns ((LINES) RESIDUAL)." - (when (and output (> (length output) 0)) - (let* ((lines (split-string output "[\n\r]+" t)) - (complete (equal "\n" (char-to-string (aref output (1- (length output)))))) - (residual nil)) - (when (not complete) - (setq residual (car (last lines))) - (setq lines (butlast lines))) - (list lines residual)))) - -(defun flymake-reformat-err-line-patterns-from-compile-el (original-list) - "Grab error line patterns from ORIGINAL-LIST in compile.el format. -Convert it to flymake internal format." - (let* ((converted-list '())) - (dolist (item original-list) - (setq item (cdr item)) - (let ((regexp (nth 0 item)) - (file (nth 1 item)) - (line (nth 2 item)) - (col (nth 3 item))) - (if (consp file) (setq file (car file))) - (if (consp line) (setq line (car line))) - (if (consp col) (setq col (car col))) - - (when (not (functionp line)) - (setq converted-list (cons (list regexp file line col) converted-list))))) - converted-list)) - -(require 'compile) - -(defvar flymake-err-line-patterns ; regexp file-idx line-idx col-idx (optional) text-idx(optional), match-end to end of string is error text - (append - '( - ;; MS Visual C++ 6.0 - ("\\(\\([a-zA-Z]:\\)?[^:(\t\n]+\\)(\\([0-9]+\\)) : \\(\\(error\\|warning\\|fatal error\\) \\(C[0-9]+\\):[ \t\n]*\\(.+\\)\\)" - 1 3 nil 4) - ;; jikes - ("\\(\\([a-zA-Z]:\\)?[^:(\t\n]+\\):\\([0-9]+\\):[0-9]+:[0-9]+:[0-9]+: \\(\\(Error\\|Warning\\|Caution\\|Semantic Error\\):[ \t\n]*\\(.+\\)\\)" - 1 3 nil 4) - ;; MS midl - ("midl[ ]*:[ ]*\\(command line error .*\\)" - nil nil nil 1) - ;; MS C# - ("\\(\\([a-zA-Z]:\\)?[^:(\t\n]+\\)(\\([0-9]+\\),[0-9]+): \\(\\(error\\|warning\\|fatal error\\) \\(CS[0-9]+\\):[ \t\n]*\\(.+\\)\\)" - 1 3 nil 4) - ;; perl - ("\\(.*\\) at \\([^ \n]+\\) line \\([0-9]+\\)[,.\n]" 2 3 nil 1) - ;; PHP - ("\\(?:Parse\\|Fatal\\) error: \\(.*\\) in \\(.*\\) on line \\([0-9]+\\)" 2 3 nil 1) - ;; LaTeX warnings (fileless) ("\\(LaTeX \\(Warning\\|Error\\): .*\\) on input line \\([0-9]+\\)" 20 3 nil 1) - ;; ant/javac. Note this also matches gcc warnings! - (" *\\(\\[javac\\] *\\)?\\(\\([a-zA-Z]:\\)?[^:(\t\n]+\\):\\([0-9]+\\)\\(?::[0-9]+\\)?:[ \t\n]*\\(.+\\)" - 2 4 nil 5)) - ;; compilation-error-regexp-alist) - (flymake-reformat-err-line-patterns-from-compile-el compilation-error-regexp-alist-alist)) - "Patterns for matching error/warning lines. Each pattern has the form -\(REGEXP FILE-IDX LINE-IDX COL-IDX ERR-TEXT-IDX). -Use `flymake-reformat-err-line-patterns-from-compile-el' to add patterns -from compile.el") - -(define-obsolete-variable-alias 'flymake-warning-re 'flymake-warning-predicate "24.4") -(defvar flymake-warning-predicate "^[wW]arning" - "Predicate matching against error text to detect a warning. -Takes a single argument, the error's text and should return non-nil -if it's a warning. -Instead of a function, it can also be a regular expression.") - -(defun flymake-parse-line (line) - "Parse LINE to see if it is an error or warning. -Return its components if so, nil otherwise." - (let ((raw-file-name nil) - (line-no 0) - (err-type "e") - (err-text nil) - (patterns flymake-err-line-patterns) - (matched nil)) - (while (and patterns (not matched)) - (when (string-match (car (car patterns)) line) - (let* ((file-idx (nth 1 (car patterns))) - (line-idx (nth 2 (car patterns)))) - - (setq raw-file-name (if file-idx (match-string file-idx line) nil)) - (setq line-no (if line-idx (string-to-number - (match-string line-idx line)) 0)) - (setq err-text (if (> (length (car patterns)) 4) - (match-string (nth 4 (car patterns)) line) - (flymake-patch-err-text - (substring line (match-end 0))))) - (if (null err-text) - (setq err-text "<no error text>") - (when (cond ((stringp flymake-warning-predicate) - (string-match flymake-warning-predicate err-text)) - ((functionp flymake-warning-predicate) - (funcall flymake-warning-predicate err-text))) - (setq err-type "w"))) - (flymake-log - 3 "parse line: file-idx=%s line-idx=%s file=%s line=%s text=%s" - file-idx line-idx raw-file-name line-no err-text) - (setq matched t))) - (setq patterns (cdr patterns))) - (if matched - (flymake-ler-make-ler raw-file-name line-no err-type err-text) - ()))) - -(defun flymake-find-err-info (err-info-list line-no) - "Find (line-err-info-list pos) for specified LINE-NO." - (if err-info-list - (let* ((line-err-info-list nil) - (pos 0) - (count (length err-info-list))) - - (while (and (< pos count) (< (car (nth pos err-info-list)) line-no)) - (setq pos (1+ pos))) - (when (and (< pos count) (equal (car (nth pos err-info-list)) line-no)) - (setq line-err-info-list (flymake-er-get-line-err-info-list (nth pos err-info-list)))) - (list line-err-info-list pos)) - '(nil 0))) - -(defun flymake-line-err-info-is-less-or-equal (line-one line-two) - (or (string< (flymake-ler-type line-one) (flymake-ler-type line-two)) - (and (string= (flymake-ler-type line-one) (flymake-ler-type line-two)) - (not (flymake-ler-file line-one)) (flymake-ler-file line-two)) - (and (string= (flymake-ler-type line-one) (flymake-ler-type line-two)) - (or (and (flymake-ler-file line-one) (flymake-ler-file line-two)) - (and (not (flymake-ler-file line-one)) (not (flymake-ler-file line-two))))))) - -(defun flymake-add-line-err-info (line-err-info-list line-err-info) - "Update LINE-ERR-INFO-LIST with the error LINE-ERR-INFO. -For the format of LINE-ERR-INFO, see `flymake-ler-make-ler'. -The new element is inserted in the proper position, according to -the predicate `flymake-line-err-info-is-less-or-equal'. -The updated value of LINE-ERR-INFO-LIST is returned." - (if (not line-err-info-list) - (list line-err-info) - (let* ((count (length line-err-info-list)) - (idx 0)) - (while (and (< idx count) (flymake-line-err-info-is-less-or-equal (nth idx line-err-info-list) line-err-info)) - (setq idx (1+ idx))) - (cond ((equal 0 idx) (setq line-err-info-list (cons line-err-info line-err-info-list))) - (t (setq line-err-info-list (flymake-ins-after line-err-info-list (1- idx) line-err-info)))) - line-err-info-list))) - -(defun flymake-add-err-info (err-info-list line-err-info) - "Update ERR-INFO-LIST with the error LINE-ERR-INFO, preserving sort order. -Returns the updated value of ERR-INFO-LIST. -For the format of ERR-INFO-LIST, see `flymake-err-info'. -For the format of LINE-ERR-INFO, see `flymake-ler-make-ler'." - (let* ((line-no (if (flymake-ler-file line-err-info) 1 (flymake-ler-line line-err-info))) - (info-and-pos (flymake-find-err-info err-info-list line-no)) - (exists (car info-and-pos)) - (pos (nth 1 info-and-pos)) - (line-err-info-list nil) - (err-info nil)) - - (if exists - (setq line-err-info-list (flymake-er-get-line-err-info-list (car (nthcdr pos err-info-list))))) - (setq line-err-info-list (flymake-add-line-err-info line-err-info-list line-err-info)) - - (setq err-info (flymake-er-make-er line-no line-err-info-list)) - (cond (exists (setq err-info-list (flymake-set-at err-info-list pos err-info))) - ((equal 0 pos) (setq err-info-list (cons err-info err-info-list))) - (t (setq err-info-list (flymake-ins-after err-info-list (1- pos) err-info)))) - err-info-list)) - -(defun flymake-get-project-include-dirs-imp (basedir) - "Include dirs for the project current file belongs to." - (if (flymake-get-project-include-dirs-from-cache basedir) - (progn - (flymake-get-project-include-dirs-from-cache basedir)) - ;;else - (let* ((command-line (concat "make -C " - (shell-quote-argument basedir) - " DUMPVARS=INCLUDE_DIRS dumpvars")) - (output (shell-command-to-string command-line)) - (lines (split-string output "\n" t)) - (count (length lines)) - (idx 0) - (inc-dirs nil)) - (while (and (< idx count) (not (string-match "^INCLUDE_DIRS=.*" (nth idx lines)))) - (setq idx (1+ idx))) - (when (< idx count) - (let* ((inc-lines (split-string (nth idx lines) " *-I" t)) - (inc-count (length inc-lines))) - (while (> inc-count 0) - (when (not (string-match "^INCLUDE_DIRS=.*" (nth (1- inc-count) inc-lines))) - (push (replace-regexp-in-string "\"" "" (nth (1- inc-count) inc-lines)) inc-dirs)) - (setq inc-count (1- inc-count))))) - (flymake-add-project-include-dirs-to-cache basedir inc-dirs) - inc-dirs))) - -(defvar flymake-get-project-include-dirs-function #'flymake-get-project-include-dirs-imp - "Function used to get project include dirs, one parameter: basedir name.") - -(defun flymake-get-project-include-dirs (basedir) - (funcall flymake-get-project-include-dirs-function basedir)) - -(defun flymake-get-system-include-dirs () - "System include dirs - from the `INCLUDE' env setting." - (let* ((includes (getenv "INCLUDE"))) - (if includes (split-string includes path-separator t) nil))) - -(defvar flymake-project-include-dirs-cache (make-hash-table :test #'equal)) - -(defun flymake-get-project-include-dirs-from-cache (base-dir) - (gethash base-dir flymake-project-include-dirs-cache)) - -(defun flymake-add-project-include-dirs-to-cache (base-dir include-dirs) - (puthash base-dir include-dirs flymake-project-include-dirs-cache)) - -(defun flymake-clear-project-include-dirs-cache () - (clrhash flymake-project-include-dirs-cache)) - -(defun flymake-get-include-dirs (base-dir) - "Get dirs to use when resolving local file names." - (let* ((include-dirs (append '(".") (flymake-get-project-include-dirs base-dir) (flymake-get-system-include-dirs)))) - include-dirs)) - -;; (defun flymake-restore-formatting () -;; "Remove any formatting made by flymake." -;; ) - -;; (defun flymake-get-program-dir (buffer) -;; "Get dir to start program in." -;; (unless (bufferp buffer) -;; (error "Invalid buffer")) -;; (with-current-buffer buffer -;; default-directory)) - -(defun flymake-safe-delete-file (file-name) - (when (and file-name (file-exists-p file-name)) - (delete-file file-name) - (flymake-log 1 "deleted file %s" file-name))) - -(defun flymake-safe-delete-directory (dir-name) - (condition-case nil - (progn - (delete-directory dir-name) - (flymake-log 1 "deleted dir %s" dir-name)) - (error - (flymake-log 1 "Failed to delete dir %s, error ignored" dir-name)))) - -(defun flymake-start-syntax-check () - "Start syntax checking for current buffer." - (interactive) - (flymake-log 3 "flymake is running: %s" flymake-is-running) - (when (and (not flymake-is-running) - (flymake-can-syntax-check-file buffer-file-name)) - (when (or (not flymake-compilation-prevents-syntax-check) - (not (flymake-compilation-is-running))) ;+ (flymake-rep-ort-status buffer "COMP") - (flymake-clear-buildfile-cache) - (flymake-clear-project-include-dirs-cache) - - (setq flymake-check-was-interrupted nil) - - (let* ((source-file-name buffer-file-name) - (init-f (flymake-get-init-function source-file-name)) - (cleanup-f (flymake-get-cleanup-function source-file-name)) - (cmd-and-args (funcall init-f)) - (cmd (nth 0 cmd-and-args)) - (args (nth 1 cmd-and-args)) - (dir (nth 2 cmd-and-args))) - (if (not cmd-and-args) - (progn - (flymake-log 0 "init function %s for %s failed, cleaning up" init-f source-file-name) - (funcall cleanup-f)) - (progn - (setq flymake-last-change-time nil) - (flymake-start-syntax-check-process cmd args dir))))))) - -(defun flymake-start-syntax-check-process (cmd args dir) - "Start syntax check process." - (condition-case err - (let* ((process - (let ((default-directory (or dir default-directory))) - (when dir - (flymake-log 3 "starting process on dir %s" dir)) - (apply 'start-file-process - "flymake-proc" (current-buffer) cmd args)))) - (set-process-sentinel process 'flymake-process-sentinel) - (set-process-filter process 'flymake-process-filter) - (set-process-query-on-exit-flag process nil) - (push process flymake-processes) - - (setq flymake-is-running t) - (setq flymake-last-change-time nil) - (setq flymake-check-start-time (float-time)) - - (flymake-report-status nil "*") - (flymake-log 2 "started process %d, command=%s, dir=%s" - (process-id process) (process-command process) - default-directory) - process) - (error - (let* ((err-str - (format-message - "Failed to launch syntax check process `%s' with args %s: %s" - cmd args (error-message-string err))) - (source-file-name buffer-file-name) - (cleanup-f (flymake-get-cleanup-function source-file-name))) - (flymake-log 0 err-str) - (funcall cleanup-f) - (flymake-report-fatal-status "PROCERR" err-str))))) - -(defun flymake-kill-process (proc) - "Kill process PROC." - (kill-process proc) - (let* ((buf (process-buffer proc))) - (when (buffer-live-p buf) - (with-current-buffer buf - (setq flymake-check-was-interrupted t)))) - (flymake-log 1 "killed process %d" (process-id proc))) - -(defun flymake-stop-all-syntax-checks () - "Kill all syntax check processes." - (interactive) - (while flymake-processes - (flymake-kill-process (pop flymake-processes)))) + "Face used for marking warning regions." + :version "24.4") -(defun flymake-compilation-is-running () - (and (boundp 'compilation-in-progress) - compilation-in-progress)) +(defface flymake-note + '((((supports :underline (:style wave))) + :underline (:style wave :color "yellow green")) + (t + :inherit warning)) + "Face used for marking note regions." + :version "26.1") -(defun flymake-compile () - "Kill all flymake syntax checks, start compilation." - (interactive) - (flymake-stop-all-syntax-checks) - (call-interactively 'compile)) - -(defun flymake-on-timer-event (buffer) - "Start a syntax check for buffer BUFFER if necessary." - (when (buffer-live-p buffer) - (with-current-buffer buffer - (when (and (not flymake-is-running) - flymake-last-change-time - (> (- (float-time) flymake-last-change-time) - flymake-no-changes-timeout)) - - (setq flymake-last-change-time nil) - (flymake-log 3 "starting syntax check as more than 1 second passed since last change") - (flymake-start-syntax-check))))) - -(define-obsolete-function-alias 'flymake-display-err-menu-for-current-line - 'flymake-popup-current-error-menu "24.4") - -(defun flymake-popup-current-error-menu (&optional event) - "Pop up a menu with errors/warnings for current line." - (interactive (list last-nonmenu-event)) - (let* ((line-no (line-number-at-pos)) - (errors (or (car (flymake-find-err-info flymake-err-info line-no)) - (user-error "No errors for current line"))) - (menu (mapcar (lambda (x) - (if (flymake-ler-file x) - (cons (format "%s - %s(%d)" - (flymake-ler-text x) - (flymake-ler-file x) - (flymake-ler-line x)) - x) - (list (flymake-ler-text x)))) - errors)) - (event (if (mouse-event-p event) - event - (list 'mouse-1 (posn-at-point)))) - (title (format "Line %d: %d error(s), %d warning(s)" - line-no - (flymake-get-line-err-count errors "e") - (flymake-get-line-err-count errors "w"))) - (choice (x-popup-menu event (list title (cons "" menu))))) - (flymake-log 3 "choice=%s" choice) - (when choice - (flymake-goto-file-and-line (flymake-ler-full-file choice) - (flymake-ler-line choice))))) - -(defun flymake-goto-file-and-line (file line) - "Try to get buffer for FILE and goto line LINE in it." - (if (not (file-exists-p file)) - (flymake-log 1 "File %s does not exist" file) - (find-file file) - (goto-char (point-min)) - (forward-line (1- line)))) - -;; flymake minor mode declarations -(defvar-local flymake-mode-line nil) -(defvar-local flymake-mode-line-e-w nil) -(defvar-local flymake-mode-line-status nil) - -(defun flymake-report-status (e-w &optional status) - "Show status in mode line." - (when e-w - (setq flymake-mode-line-e-w e-w)) - (when status - (setq flymake-mode-line-status status)) - (let* ((mode-line " Flymake")) - (when (> (length flymake-mode-line-e-w) 0) - (setq mode-line (concat mode-line ":" flymake-mode-line-e-w))) - (setq mode-line (concat mode-line flymake-mode-line-status)) - (setq flymake-mode-line mode-line) - (force-mode-line-update))) - -;; Nothing in flymake uses this at all any more, so this is just for +(define-obsolete-face-alias 'flymake-warnline 'flymake-warning "26.1") +(define-obsolete-face-alias 'flymake-errline 'flymake-error "26.1") + +;;;###autoload +(defun flymake-diag-region (buffer line &optional col) + "Compute BUFFER's region (BEG . END) corresponding to LINE and COL. +If COL is nil, return a region just for LINE. Return nil if the +region is invalid." + (condition-case-unless-debug _err + (with-current-buffer buffer + (let ((line (min (max line 1) + (line-number-at-pos (point-max) 'absolute)))) + (save-excursion + (goto-char (point-min)) + (forward-line (1- line)) + (cl-flet ((fallback-bol + () + (back-to-indentation) + (if (eobp) + (line-beginning-position 0) + (point))) + (fallback-eol + (beg) + (progn + (end-of-line) + (skip-chars-backward " \t\f\t\n" beg) + (if (eq (point) beg) + (line-beginning-position 2) + (point))))) + (if (and col (cl-plusp col)) + (let* ((beg (progn (forward-char (1- col)) + (point))) + (sexp-end (ignore-errors (end-of-thing 'sexp))) + (end (or (and sexp-end + (not (= sexp-end beg)) + sexp-end) + (and (< (goto-char (1+ beg)) (point-max)) + (point))))) + (if end + (cons beg end) + (cons (setq beg (fallback-bol)) + (fallback-eol beg)))) + (let* ((beg (fallback-bol)) + (end (fallback-eol beg))) + (cons beg end))))))) + (error (flymake-log :warning "Invalid region line=%s col=%s" line col) + nil))) + +(defvar flymake-diagnostic-functions nil + "Special hook of Flymake backends that check a buffer. + +The functions in this hook diagnose problems in a buffer's +contents and provide information to the Flymake user interface +about where and how to annotate problems diagnosed in a buffer. + +Each backend function must be prepared to accept an arbitrary +number of arguments: + +* the first argument is always REPORT-FN, a callback function + detailed below; + +* the remaining arguments are keyword-value pairs in the + form (:KEY VALUE :KEY2 VALUE2...). Currently, Flymake provides + no such arguments, but backend functions must be prepared to + accept and possibly ignore any number of them. + +Whenever Flymake or the user decides to re-check the buffer, +backend functions are called as detailed above and are expected +to initiate this check, but aren't required to complete it before +exiting: if the computation involved is expensive, especially for +large buffers, that task can be scheduled for the future using +asynchronous processes or other asynchronous mechanisms. + +In any case, backend functions are expected to return quickly or +signal an error, in which case the backend is disabled. Flymake +will not try disabled backends again for any future checks of +this buffer. Certain commands, like turning `flymake-mode' off +and on again, reset the list of disabled backends. + +If the function returns, Flymake considers the backend to be +\"running\". If it has not done so already, the backend is +expected to call the function REPORT-FN with a single argument +REPORT-ACTION also followed by an optional list of keyword-value +pairs in the form (:REPORT-KEY VALUE :REPORT-KEY2 VALUE2...). + +Currently accepted values for REPORT-ACTION are: + +* A (possibly empty) list of diagnostic objects created with + `flymake-make-diagnostic', causing Flymake to annotate the + buffer with this information. + + A backend may call REPORT-FN repeatedly in this manner, but + only until Flymake considers that the most recently requested + buffer check is now obsolete because, say, buffer contents have + changed in the meantime. The backend is only given notice of + this via a renewed call to the backend function. Thus, to + prevent making obsolete reports and wasting resources, backend + functions should first cancel any ongoing processing from + previous calls. + +* The symbol `:panic', signaling that the backend has encountered + an exceptional situation and should be disabled. + +Currently accepted REPORT-KEY arguments are: + +* `:explanation' value should give user-readable details of + the situation encountered, if any. + +* `:force': value should be a boolean suggesting that Flymake + consider the report even if it was somehow unexpected.") + +(put 'flymake-diagnostic-functions 'safe-local-variable #'null) + +(defvar flymake-diagnostic-types-alist + `((:error + . ((flymake-category . flymake-error))) + (:warning + . ((flymake-category . flymake-warning))) + (:note + . ((flymake-category . flymake-note)))) + "Alist ((KEY . PROPS)*) of properties of Flymake diagnostic types. +KEY designates a kind of diagnostic can be anything passed as +`:type' to `flymake-make-diagnostic'. + +PROPS is an alist of properties that are applied, in order, to +the diagnostics of the type designated by KEY. The recognized +properties are: + +* Every property pertaining to overlays, except `category' and + `evaporate' (see Info Node `(elisp)Overlay Properties'), used + to affect the appearance of Flymake annotations. + +* `bitmap', an image displayed in the fringe according to + `flymake-fringe-indicator-position'. The value actually + follows the syntax of `flymake-error-bitmap' (which see). It + is overridden by any `before-string' overlay property. + +* `severity', a non-negative integer specifying the diagnostic's + severity. The higher, the more serious. If the overlay + property `priority' is not specified, `severity' is used to set + it and help sort overlapping overlays. + +* `flymake-category', a symbol whose property list is considered + a default for missing values of any other properties. This is + useful to backend authors when creating new diagnostic types + that differ from an existing type by only a few properties.") + +(put 'flymake-error 'face 'flymake-error) +(put 'flymake-error 'bitmap 'flymake-error-bitmap) +(put 'flymake-error 'severity (warning-numeric-level :error)) +(put 'flymake-error 'mode-line-face 'compilation-error) + +(put 'flymake-warning 'face 'flymake-warning) +(put 'flymake-warning 'bitmap 'flymake-warning-bitmap) +(put 'flymake-warning 'severity (warning-numeric-level :warning)) +(put 'flymake-warning 'mode-line-face 'compilation-warning) + +(put 'flymake-note 'face 'flymake-note) +(put 'flymake-note 'bitmap 'flymake-note-bitmap) +(put 'flymake-note 'severity (warning-numeric-level :debug)) +(put 'flymake-note 'mode-line-face 'compilation-info) + +(defun flymake--lookup-type-property (type prop &optional default) + "Look up PROP for TYPE in `flymake-diagnostic-types-alist'. +If TYPE doesn't declare PROP in either +`flymake-diagnostic-types-alist' or in the symbol of its +associated `flymake-category' return DEFAULT." + (let ((alist-probe (assoc type flymake-diagnostic-types-alist))) + (cond (alist-probe + (let* ((alist (cdr alist-probe)) + (prop-probe (assoc prop alist))) + (if prop-probe + (cdr prop-probe) + (if-let* ((cat (assoc-default 'flymake-category alist)) + (plist (and (symbolp cat) + (symbol-plist cat))) + (cat-probe (plist-member plist prop))) + (cadr cat-probe) + default)))) + (t + default)))) + +(defun flymake--fringe-overlay-spec (bitmap &optional recursed) + (if (and (symbolp bitmap) + (boundp bitmap) + (not recursed)) + (flymake--fringe-overlay-spec + (symbol-value bitmap) t) + (and flymake-fringe-indicator-position + bitmap + (propertize "!" 'display + (cons flymake-fringe-indicator-position + (if (listp bitmap) + bitmap + (list bitmap))))))) + +(defun flymake--highlight-line (diagnostic) + "Highlight buffer with info in DIAGNOSTIC." + (when-let* ((ov (make-overlay + (flymake--diag-beg diagnostic) + (flymake--diag-end diagnostic)))) + ;; First set `category' in the overlay, then copy over every other + ;; property. + ;; + (let ((alist (assoc-default (flymake--diag-type diagnostic) + flymake-diagnostic-types-alist))) + (overlay-put ov 'category (assoc-default 'flymake-category alist)) + (cl-loop for (k . v) in alist + unless (eq k 'category) + do (overlay-put ov k v))) + ;; Now ensure some essential defaults are set + ;; + (cl-flet ((default-maybe + (prop value) + (unless (or (plist-member (overlay-properties ov) prop) + (let ((cat (overlay-get ov + 'flymake-category))) + (and cat + (plist-member (symbol-plist cat) prop)))) + (overlay-put ov prop value)))) + (default-maybe 'bitmap 'flymake-error-bitmap) + (default-maybe 'face 'flymake-error) + (default-maybe 'before-string + (flymake--fringe-overlay-spec + (overlay-get ov 'bitmap))) + (default-maybe 'help-echo + (lambda (window _ov pos) + (with-selected-window window + (mapconcat + #'flymake--diag-text + (flymake-diagnostics pos) + "\n")))) + (default-maybe 'severity (warning-numeric-level :error)) + (default-maybe 'priority (+ 100 (overlay-get ov 'severity)))) + ;; Some properties can't be overridden. + ;; + (overlay-put ov 'evaporate t) + (overlay-put ov 'flymake-diagnostic diagnostic))) + +;; Nothing in Flymake uses this at all any more, so this is just for ;; third-party compatibility. (define-obsolete-function-alias 'flymake-display-warning 'message-box "26.1") -(defun flymake-report-fatal-status (status warning) - "Display a warning and switch flymake mode off." - ;; This first message was always shown by default, and flymake-log - ;; does nothing by default, hence the use of message. - ;; Another option is display-warning. - (if (< flymake-log-level 0) - (message "Flymake: %s. Flymake will be switched OFF" warning)) - (flymake-mode 0) - (flymake-log 0 "switched OFF Flymake mode for buffer %s due to fatal status %s, warning %s" - (buffer-name) status warning)) +(defvar-local flymake--backend-state nil + "Buffer-local hash table of a Flymake backend's state. +The keys to this hash table are functions as found in +`flymake-diagnostic-functions'. The values are structures +of the type `flymake--backend-state', with these slots: + +`running', a symbol to keep track of a backend's replies via its +REPORT-FN argument. A backend is running if this key is +present. If nil, Flymake isn't expecting any replies from the +backend. + +`diags', a (possibly empty) list of recent diagnostic objects +created by the backend with `flymake-make-diagnostic'. + +`reported-p', a boolean indicating if the backend has replied +since it last was contacted. + +`disabled', a string with the explanation for a previous +exceptional situation reported by the backend, nil if the +backend is operating normally.") + +(cl-defstruct (flymake--backend-state + (:constructor flymake--make-backend-state)) + running reported-p disabled diags) + +(defmacro flymake--with-backend-state (backend state-var &rest body) + "Bind BACKEND's STATE-VAR to its state, run BODY." + (declare (indent 2) (debug (sexp sexp &rest form))) + (let ((b (make-symbol "b"))) + `(let* ((,b ,backend) + (,state-var + (or (gethash ,b flymake--backend-state) + (puthash ,b (flymake--make-backend-state) + flymake--backend-state)))) + ,@body))) + +(defun flymake-is-running () + "Tell if Flymake has running backends in this buffer" + (flymake-running-backends)) + +(cl-defun flymake--handle-report (backend token report-action + &key explanation force + &allow-other-keys) + "Handle reports from BACKEND identified by TOKEN. +BACKEND, REPORT-ACTION and EXPLANATION, and FORCE conform to the calling +convention described in `flymake-diagnostic-functions' (which +see). Optional FORCE says to handle a report even if TOKEN was +not expected." + (let* ((state (gethash backend flymake--backend-state)) + (first-report (not (flymake--backend-state-reported-p state)))) + (setf (flymake--backend-state-reported-p state) t) + (let (expected-token + new-diags) + (cond + ((null state) + (flymake-error + "Unexpected report from unknown backend %s" backend)) + ((flymake--backend-state-disabled state) + (flymake-error + "Unexpected report from disabled backend %s" backend)) + ((progn + (setq expected-token (flymake--backend-state-running state)) + (null expected-token)) + ;; should never happen + (flymake-error "Unexpected report from stopped backend %s" backend)) + ((not (or (eq expected-token token) + force)) + (flymake-error "Obsolete report from backend %s with explanation %s" + backend explanation)) + ((eq :panic report-action) + (flymake--disable-backend backend explanation)) + ((not (listp report-action)) + (flymake--disable-backend backend + (format "Unknown action %S" report-action)) + (flymake-error "Expected report, but got unknown key %s" report-action)) + (t + (setq new-diags report-action) + (save-restriction + (widen) + ;; only delete overlays if this is the first report + (when first-report + (flymake-delete-own-overlays + (lambda (ov) + (eq backend + (flymake--diag-backend + (overlay-get ov 'flymake-diagnostic)))))) + (mapc (lambda (diag) + (flymake--highlight-line diag) + (setf (flymake--diag-backend diag) backend)) + new-diags) + (setf (flymake--backend-state-diags state) + (append new-diags (flymake--backend-state-diags state))) + (when flymake-check-start-time + (flymake-log :debug "backend %s reported %d diagnostics in %.2f second(s)" + backend + (length new-diags) + (- (float-time) flymake-check-start-time))) + (when (and (get-buffer (flymake--diagnostics-buffer-name)) + (get-buffer-window (flymake--diagnostics-buffer-name)) + (null (cl-set-difference (flymake-running-backends) + (flymake-reporting-backends)))) + (flymake-show-diagnostics-buffer)))))))) + +(defun flymake-make-report-fn (backend &optional token) + "Make a suitable anonymous report function for BACKEND. +BACKEND is used to help Flymake distinguish different diagnostic +sources. If provided, TOKEN helps Flymake distinguish between +different runs of the same backend." + (let ((buffer (current-buffer))) + (lambda (&rest args) + (when (buffer-live-p buffer) + (with-current-buffer buffer + (apply #'flymake--handle-report backend token args)))))) + +(defun flymake--collect (fn &optional message-prefix) + "Collect Flymake backends matching FN. +If MESSAGE-PREFIX, echo a message using that prefix" + (unless flymake--backend-state + (user-error "Flymake is not initialized")) + (let (retval) + (maphash (lambda (backend state) + (when (funcall fn state) (push backend retval))) + flymake--backend-state) + (when message-prefix + (message "%s%s" + message-prefix + (mapconcat (lambda (s) (format "%s" s)) + retval ", "))) + retval)) + +(defun flymake-running-backends () + "Compute running Flymake backends in current buffer." + (interactive) + (flymake--collect #'flymake--backend-state-running + (and (called-interactively-p 'interactive) + "Running backends: "))) + +(defun flymake-disabled-backends () + "Compute disabled Flymake backends in current buffer." + (interactive) + (flymake--collect #'flymake--backend-state-disabled + (and (called-interactively-p 'interactive) + "Disabled backends: "))) + +(defun flymake-reporting-backends () + "Compute reporting Flymake backends in current buffer." + (interactive) + (flymake--collect #'flymake--backend-state-reported-p + (and (called-interactively-p 'interactive) + "Reporting backends: "))) + +(defun flymake--disable-backend (backend &optional explanation) + "Disable BACKEND because EXPLANATION. +If it is running also stop it." + (flymake-log :warning "Disabling backend %s because %s" backend explanation) + (flymake--with-backend-state backend state + (setf (flymake--backend-state-running state) nil + (flymake--backend-state-disabled state) explanation + (flymake--backend-state-reported-p state) t))) + +(defun flymake--run-backend (backend) + "Run the backend BACKEND, reenabling if necessary." + (flymake-log :debug "Running backend %s" backend) + (let ((run-token (cl-gensym "backend-token"))) + (flymake--with-backend-state backend state + (setf (flymake--backend-state-running state) run-token + (flymake--backend-state-disabled state) nil + (flymake--backend-state-diags state) nil + (flymake--backend-state-reported-p state) nil)) + ;; FIXME: Should use `condition-case-unless-debug' here, but don't + ;; for two reasons: (1) that won't let me catch errors from inside + ;; `ert-deftest' where `debug-on-error' appears to be always + ;; t. (2) In cases where the user is debugging elisp somewhere + ;; else, and using flymake, the presence of a frequently + ;; misbehaving backend in the global hook (most likely the legacy + ;; backend) will trigger an annoying backtrace. + ;; + (condition-case err + (funcall backend + (flymake-make-report-fn backend run-token)) + (error + (flymake--disable-backend backend err))))) + +(defun flymake-start (&optional deferred force) + "Start a syntax check for the current buffer. +DEFERRED is a list of symbols designating conditions to wait for +before actually starting the check. If it is nil (the list is +empty), start it immediately, else defer the check to when those +conditions are met. Currently recognized conditions are +`post-command', for waiting until the current command is over, +`on-display', for waiting until the buffer is actually displayed +in a window. If DEFERRED is t, wait for all known conditions. + +With optional FORCE run even disabled backends. + +Interactively, with a prefix arg, FORCE is t." + (interactive (list nil current-prefix-arg)) + (let ((deferred (if (eq t deferred) + '(post-command on-display) + deferred)) + (buffer (current-buffer))) + (cl-labels + ((start-post-command + () + (remove-hook 'post-command-hook #'start-post-command + nil) + ;; The buffer may have disappeared already, e.g. because of + ;; code like `(with-temp-buffer (python-mode) ...)'. + (when (buffer-live-p buffer) + (with-current-buffer buffer + (flymake-start (remove 'post-command deferred) force)))) + (start-on-display + () + (remove-hook 'window-configuration-change-hook #'start-on-display + 'local) + (flymake-start (remove 'on-display deferred) force))) + (cond ((and (memq 'post-command deferred) + this-command) + (add-hook 'post-command-hook + #'start-post-command + 'append nil)) + ((and (memq 'on-display deferred) + (not (get-buffer-window (current-buffer)))) + (add-hook 'window-configuration-change-hook + #'start-on-display + 'append 'local)) + (t + (setq flymake-check-start-time (float-time)) + (run-hook-wrapped + 'flymake-diagnostic-functions + (lambda (backend) + (cond + ((and (not force) + (flymake--with-backend-state backend state + (flymake--backend-state-disabled state))) + (flymake-log :debug "Backend %s is disabled, not starting" + backend)) + (t + (flymake--run-backend backend))) + nil))))))) + +(defvar flymake-mode-map + (let ((map (make-sparse-keymap))) map) + "Keymap for `flymake-mode'") ;;;###autoload -(define-minor-mode flymake-mode nil - :group 'flymake :lighter flymake-mode-line +(define-minor-mode flymake-mode + "Toggle Flymake mode on or off. +With a prefix argument ARG, enable Flymake mode if ARG is +positive, and disable it otherwise. If called from Lisp, enable +the mode if ARG is omitted or nil, and toggle it if ARG is `toggle'. + +Flymake is an Emacs minor mode for on-the-fly syntax checking. +Flymake collects diagnostic information from multiple sources, +called backends, and visually annotates the buffer with the +results. + +Flymake performs these checks while the user is editing. The +customization variables `flymake-start-on-flymake-mode', +`flymake-no-changes-timeout' and +`flymake-start-syntax-check-on-newline' determine the exact +circumstances whereupon Flymake decides to initiate a check of +the buffer. + +The commands `flymake-goto-next-error' and +`flymake-goto-prev-error' can be used to navigate among Flymake +diagnostics annotated in the buffer. + +The visual appearance of each type of diagnostic can be changed +in the variable `flymake-diagnostic-types-alist'. + +Activation or deactivation of backends used by Flymake in each +buffer happens via the special hook +`flymake-diagnostic-functions'. + +Some backends may take longer than others to respond or complete, +and some may decide to disable themselves if they are not +suitable for the current buffer. The commands +`flymake-running-backends', `flymake-disabled-backends' and +`flymake-reporting-backends' summarize the situation, as does the +special *Flymake log* buffer." :group 'flymake :lighter + flymake--mode-line-format :keymap flymake-mode-map (cond - ;; Turning the mode ON. (flymake-mode - (cond - ((not buffer-file-name) - (message "Flymake unable to run without a buffer file name")) - ((not (flymake-can-syntax-check-file buffer-file-name)) - (flymake-log 2 "flymake cannot check syntax in buffer %s" (buffer-name))) - (t - (add-hook 'after-change-functions 'flymake-after-change-function nil t) - (add-hook 'after-save-hook 'flymake-after-save-hook nil t) - (add-hook 'kill-buffer-hook 'flymake-kill-buffer-hook nil t) - ;;+(add-hook 'find-file-hook 'flymake-find-file-hook) - - (flymake-report-status "" "") - - (setq flymake-timer - (run-at-time nil 1 'flymake-on-timer-event (current-buffer))) - - (when (and flymake-start-syntax-check-on-find-file - ;; Since we write temp files in current dir, there's no point - ;; trying if the directory is read-only (bug#8954). - (file-writable-p (file-name-directory buffer-file-name))) - (with-demoted-errors - (flymake-start-syntax-check)))))) + (add-hook 'after-change-functions 'flymake-after-change-function nil t) + (add-hook 'after-save-hook 'flymake-after-save-hook nil t) + (add-hook 'kill-buffer-hook 'flymake-kill-buffer-hook nil t) + + (setq flymake--backend-state (make-hash-table)) + + (when flymake-start-on-flymake-mode (flymake-start t))) ;; Turning the mode OFF. (t @@ -1248,402 +849,365 @@ For the format of LINE-ERR-INFO, see `flymake-ler-make-ler'." (when flymake-timer (cancel-timer flymake-timer) - (setq flymake-timer nil)) - - (setq flymake-is-running nil)))) + (setq flymake-timer nil))))) + +(defun flymake--schedule-timer-maybe () + "(Re)schedule an idle timer for checking the buffer. +Do it only if `flymake-no-changes-timeout' is non-nil." + (when flymake-timer (cancel-timer flymake-timer)) + (when flymake-no-changes-timeout + (setq + flymake-timer + (run-with-idle-timer + (seconds-to-time flymake-no-changes-timeout) + nil + (lambda (buffer) + (when (buffer-live-p buffer) + (with-current-buffer buffer + (when (and flymake-mode + flymake-no-changes-timeout) + (flymake-log + :debug "starting syntax check after idle for %s seconds" + flymake-no-changes-timeout) + (flymake-start t)) + (setq flymake-timer nil)))) + (current-buffer))))) ;;;###autoload (defun flymake-mode-on () - "Turn flymake mode on." - (flymake-mode 1) - (flymake-log 1 "flymake mode turned ON for buffer %s" (buffer-name))) + "Turn Flymake mode on." + (flymake-mode 1)) ;;;###autoload (defun flymake-mode-off () - "Turn flymake mode off." - (flymake-mode 0) - (flymake-log 1 "flymake mode turned OFF for buffer %s" (buffer-name))) + "Turn Flymake mode off." + (flymake-mode 0)) + +(make-obsolete 'flymake-mode-on 'flymake-mode "26.1") +(make-obsolete 'flymake-mode-off 'flymake-mode "26.1") (defun flymake-after-change-function (start stop _len) "Start syntax check for current buffer if it isn't already running." - ;;+(flymake-log 0 "setting change time to %s" (float-time)) (let((new-text (buffer-substring start stop))) (when (and flymake-start-syntax-check-on-newline (equal new-text "\n")) - (flymake-log 3 "starting syntax check as new-line has been seen") - (flymake-start-syntax-check)) - (setq flymake-last-change-time (float-time)))) + (flymake-log :debug "starting syntax check as new-line has been seen") + (flymake-start t)) + (flymake--schedule-timer-maybe))) (defun flymake-after-save-hook () - (if (local-variable-p 'flymake-mode (current-buffer)) ; (???) other way to determine whether flymake is active in buffer being saved? - (progn - (flymake-log 3 "starting syntax check as buffer was saved") - (flymake-start-syntax-check)))) ; no more mode 3. cannot start check if mode 3 (to temp copies) is active - (???) + (when flymake-mode + (flymake-log :debug "starting syntax check as buffer was saved") + (flymake-start t))) (defun flymake-kill-buffer-hook () (when flymake-timer (cancel-timer flymake-timer) (setq flymake-timer nil))) -;;;###autoload (defun flymake-find-file-hook () - ;;+(when flymake-start-syntax-check-on-find-file - ;;+ (flymake-log 3 "starting syntax check on file open") - ;;+ (flymake-start-syntax-check) - ;;+) - (when (and (not (local-variable-p 'flymake-mode (current-buffer))) - (flymake-can-syntax-check-file buffer-file-name)) + (unless (or flymake-mode + (null flymake-diagnostic-functions)) (flymake-mode) - (flymake-log 3 "automatically turned ON flymake mode"))) - -(defun flymake-get-first-err-line-no (err-info-list) - "Return first line with error." - (when err-info-list - (flymake-er-get-line (car err-info-list)))) - -(defun flymake-get-last-err-line-no (err-info-list) - "Return last line with error." - (when err-info-list - (flymake-er-get-line (nth (1- (length err-info-list)) err-info-list)))) - -(defun flymake-get-next-err-line-no (err-info-list line-no) - "Return next line with error." - (when err-info-list - (let* ((count (length err-info-list)) - (idx 0)) - (while (and (< idx count) (>= line-no (flymake-er-get-line (nth idx err-info-list)))) - (setq idx (1+ idx))) - (if (< idx count) - (flymake-er-get-line (nth idx err-info-list)))))) - -(defun flymake-get-prev-err-line-no (err-info-list line-no) - "Return previous line with error." - (when err-info-list - (let* ((count (length err-info-list))) - (while (and (> count 0) (<= line-no (flymake-er-get-line (nth (1- count) err-info-list)))) - (setq count (1- count))) - (if (> count 0) - (flymake-er-get-line (nth (1- count) err-info-list)))))) - -(defun flymake-skip-whitespace () - "Move forward until non-whitespace is reached." - (while (looking-at "[ \t]") - (forward-char))) - -(defun flymake-goto-line (line-no) - "Go to line LINE-NO, then skip whitespace." - (goto-char (point-min)) - (forward-line (1- line-no)) - (flymake-skip-whitespace)) - -(defun flymake-goto-next-error () - "Go to next error in err ring." + (flymake-log :warning "Turned on in `flymake-find-file-hook'"))) + +(defun flymake-goto-next-error (&optional n filter interactive) + "Go to Nth next Flymake diagnostic that matches FILTER. +Interactively, always move to the next diagnostic. With a prefix +arg, skip any diagnostics with a severity less than `:warning'. + +If `flymake-wrap-around' is non-nil and no more next diagnostics, +resumes search from top. + +FILTER is a list of diagnostic types found in +`flymake-diagnostic-types-alist', or nil, if no filter is to be +applied." + ;; TODO: let filter be a number, a severity below which diags are + ;; skipped. + (interactive (list 1 + (if current-prefix-arg + '(:error :warning)) + t)) + (let* ((n (or n 1)) + (ovs (flymake--overlays :filter + (lambda (ov) + (let ((diag (overlay-get + ov + 'flymake-diagnostic))) + (and diag + (or (not filter) + (memq (flymake--diag-type diag) + filter))))) + :compare (if (cl-plusp n) #'< #'>) + :key #'overlay-start)) + (tail (cl-member-if (lambda (ov) + (if (cl-plusp n) + (> (overlay-start ov) + (point)) + (< (overlay-start ov) + (point)))) + ovs)) + (chain (if flymake-wrap-around + (if tail + (progn (setcdr (last tail) ovs) tail) + (and ovs (setcdr (last ovs) ovs))) + tail)) + (target (nth (1- n) chain))) + (cond (target + (goto-char (overlay-start target)) + (when interactive + (message + "%s" + (funcall (overlay-get target 'help-echo) + (selected-window) target (point))))) + (interactive + (user-error "No more Flymake errors%s" + (if filter + (format " of types %s" filter) + "")))))) + +(defun flymake-goto-prev-error (&optional n filter interactive) + "Go to Nth previous Flymake diagnostic that matches FILTER. +Interactively, always move to the previous diagnostic. With a +prefix arg, skip any diagnostics with a severity less than +`:warning'. + +If `flymake-wrap-around' is non-nil and no more previous +diagnostics, resumes search from bottom. + +FILTER is a list of diagnostic types found in +`flymake-diagnostic-types-alist', or nil, if no filter is to be +applied." + (interactive (list 1 (if current-prefix-arg + '(:error :warning)) + t)) + (flymake-goto-next-error (- (or n 1)) filter interactive)) + + +;;; Mode-line and menu +;;; +(easy-menu-define flymake-menu flymake-mode-map "Flymake" + `("Flymake" + [ "Go to next problem" flymake-goto-next-error t ] + [ "Go to previous problem" flymake-goto-prev-error t ] + [ "Check now" flymake-start t ] + [ "List all problems" flymake-show-diagnostics-buffer t ] + "--" + [ "Go to log buffer" flymake-switch-to-log-buffer t ] + [ "Turn off Flymake" flymake-mode t ])) + +(defvar flymake--mode-line-format `(:eval (flymake--mode-line-format))) + +(put 'flymake--mode-line-format 'risky-local-variable t) + +(defun flymake--mode-line-format () + "Produce a pretty minor mode indicator." + (let* ((known (hash-table-keys flymake--backend-state)) + (running (flymake-running-backends)) + (disabled (flymake-disabled-backends)) + (reported (flymake-reporting-backends)) + (diags-by-type (make-hash-table)) + (all-disabled (and disabled (null running))) + (some-waiting (cl-set-difference running reported))) + (maphash (lambda (_b state) + (mapc (lambda (diag) + (push diag + (gethash (flymake--diag-type diag) + diags-by-type))) + (flymake--backend-state-diags state))) + flymake--backend-state) + `((:propertize " Flymake" + mouse-face mode-line-highlight + help-echo + ,(concat (format "%s known backends\n" (length known)) + (format "%s running\n" (length running)) + (format "%s disabled\n" (length disabled)) + "mouse-1: Display minor mode menu\n" + "mouse-2: Show help for minor mode") + keymap + ,(let ((map (make-sparse-keymap))) + (define-key map [mode-line down-mouse-1] + flymake-menu) + (define-key map [mode-line mouse-2] + (lambda () + (interactive) + (describe-function 'flymake-mode))) + map)) + ,@(pcase-let ((`(,ind ,face ,explain) + (cond ((null known) + `("?" mode-line "No known backends")) + (some-waiting + `("Wait" compilation-mode-line-run + ,(format "Waiting for %s running backend(s)" + (length some-waiting)))) + (all-disabled + `("!" compilation-mode-line-run + "All backends disabled")) + (t + `(nil nil nil))))) + (when ind + `((":" + (:propertize ,ind + face ,face + help-echo ,explain + keymap + ,(let ((map (make-sparse-keymap))) + (define-key map [mode-line mouse-1] + 'flymake-switch-to-log-buffer) + map)))))) + ,@(unless (or all-disabled + (null known)) + (cl-loop + for (type . severity) + in (cl-sort (mapcar (lambda (type) + (cons type (flymake--lookup-type-property + type + 'severity + (warning-numeric-level :error)))) + (cl-union (hash-table-keys diags-by-type) + '(:error :warning))) + #'> + :key #'cdr) + for diags = (gethash type diags-by-type) + for face = (flymake--lookup-type-property type + 'mode-line-face + 'compilation-error) + when (or diags + (>= severity (warning-numeric-level :warning))) + collect `(:propertize + ,(format "%d" (length diags)) + face ,face + mouse-face mode-line-highlight + keymap + ,(let ((map (make-sparse-keymap)) + (type type)) + (define-key map (vector 'mode-line + mouse-wheel-down-event) + (lambda (event) + (interactive "e") + (with-selected-window (posn-window (event-start event)) + (flymake-goto-prev-error 1 (list type) t)))) + (define-key map (vector 'mode-line + mouse-wheel-up-event) + (lambda (event) + (interactive "e") + (with-selected-window (posn-window (event-start event)) + (flymake-goto-next-error 1 (list type) t)))) + map) + help-echo + ,(concat (format "%s diagnostics of type %s\n" + (propertize (format "%d" + (length diags)) + 'face face) + (propertize (format "%s" type) + 'face face)) + (format "%s/%s: previous/next of this type" + mouse-wheel-down-event + mouse-wheel-up-event))) + into forms + finally return + `((:propertize "[") + ,@(cl-loop for (a . rest) on forms by #'cdr + collect a when rest collect + '(:propertize " ")) + (:propertize "]"))))))) + +;;; Diagnostics buffer + +(defvar-local flymake--diagnostics-buffer-source nil) + +(defvar flymake-diagnostics-buffer-mode-map + (let ((map (make-sparse-keymap))) + (define-key map (kbd "RET") 'flymake-goto-diagnostic) + (define-key map (kbd "SPC") 'flymake-show-diagnostic) + map)) + +(defun flymake-show-diagnostic (pos &optional other-window) + "Show location of diagnostic at POS." + (interactive (list (point) t)) + (let* ((id (or (tabulated-list-get-id pos) + (user-error "Nothing at point"))) + (diag (plist-get id :diagnostic))) + (with-current-buffer (flymake--diag-buffer diag) + (with-selected-window + (display-buffer (current-buffer) other-window) + (goto-char (flymake--diag-beg diag)) + (pulse-momentary-highlight-region (flymake--diag-beg diag) + (flymake--diag-end diag) + 'highlight)) + (current-buffer)))) + +(defun flymake-goto-diagnostic (pos) + "Show location of diagnostic at POS. +POS can be a buffer position or a button" + (interactive "d") + (pop-to-buffer + (flymake-show-diagnostic (if (button-type pos) (button-start pos) pos)))) + +(defun flymake--diagnostics-buffer-entries () + (with-current-buffer flymake--diagnostics-buffer-source + (cl-loop for diag in + (cl-sort (flymake-diagnostics) #'< :key #'flymake-diagnostic-beg) + for (line . col) = + (save-excursion + (goto-char (flymake--diag-beg diag)) + (cons (line-number-at-pos) + (- (point) + (line-beginning-position)))) + for type = (flymake--diag-type diag) + collect + (list (list :diagnostic diag + :line line + :severity (flymake--lookup-type-property + type + 'severity (warning-numeric-level :error))) + `[,(format "%s" line) + ,(format "%s" col) + ,(propertize (format "%s" type) + 'face (flymake--lookup-type-property + type 'mode-line-face 'flymake-error)) + (,(format "%s" (flymake--diag-text diag)) + mouse-face highlight + help-echo "mouse-2: visit this diagnostic" + face nil + action flymake-goto-diagnostic + mouse-action flymake-goto-diagnostic)])))) + +(define-derived-mode flymake-diagnostics-buffer-mode tabulated-list-mode + "Flymake diagnostics" + "A mode for listing Flymake diagnostics." + (setq tabulated-list-format + `[("Line" 5 (lambda (l1 l2) + (< (plist-get (car l1) :line) + (plist-get (car l2) :line))) + :right-align t) + ("Col" 3 nil :right-align t) + ("Type" 8 (lambda (l1 l2) + (< (plist-get (car l1) :severity) + (plist-get (car l2) :severity)))) + ("Message" 0 t)]) + (setq tabulated-list-entries + 'flymake--diagnostics-buffer-entries) + (tabulated-list-init-header)) + +(defun flymake--diagnostics-buffer-name () + (format "*Flymake diagnostics for %s*" (current-buffer))) + +(defun flymake-show-diagnostics-buffer () + "Show a list of Flymake diagnostics for current buffer." (interactive) - (let ((line-no (flymake-get-next-err-line-no flymake-err-info (line-number-at-pos)))) - (when (not line-no) - (setq line-no (flymake-get-first-err-line-no flymake-err-info)) - (flymake-log 1 "passed end of file")) - (if line-no - (flymake-goto-line line-no) - (flymake-log 1 "no errors in current buffer")))) - -(defun flymake-goto-prev-error () - "Go to previous error in err ring." - (interactive) - (let ((line-no (flymake-get-prev-err-line-no flymake-err-info (line-number-at-pos)))) - (when (not line-no) - (setq line-no (flymake-get-last-err-line-no flymake-err-info)) - (flymake-log 1 "passed beginning of file")) - (if line-no - (flymake-goto-line line-no) - (flymake-log 1 "no errors in current buffer")))) - -(defun flymake-patch-err-text (string) - (if (string-match "^[\n\t :0-9]*\\(.*\\)$" string) - (match-string 1 string) - string)) - -;;;; general init-cleanup and helper routines -(defun flymake-create-temp-inplace (file-name prefix) - (unless (stringp file-name) - (error "Invalid file-name")) - (or prefix - (setq prefix "flymake")) - (let* ((ext (file-name-extension file-name)) - (temp-name (file-truename - (concat (file-name-sans-extension file-name) - "_" prefix - (and ext (concat "." ext)))))) - (flymake-log 3 "create-temp-inplace: file=%s temp=%s" file-name temp-name) - temp-name)) - -(defun flymake-create-temp-with-folder-structure (file-name _prefix) - (unless (stringp file-name) - (error "Invalid file-name")) - - (let* ((dir (file-name-directory file-name)) - ;; Not sure what this slash-pos is all about, but I guess it's just - ;; trying to remove the leading / of absolute file names. - (slash-pos (string-match "/" dir)) - (temp-dir (expand-file-name (substring dir (1+ slash-pos)) - temporary-file-directory))) - - (file-truename (expand-file-name (file-name-nondirectory file-name) - temp-dir)))) - -(defun flymake-delete-temp-directory (dir-name) - "Attempt to delete temp dir created by `flymake-create-temp-with-folder-structure', do not fail on error." - (let* ((temp-dir temporary-file-directory) - (suffix (substring dir-name (1+ (length temp-dir))))) - - (while (> (length suffix) 0) - (setq suffix (directory-file-name suffix)) - ;;+(flymake-log 0 "suffix=%s" suffix) - (flymake-safe-delete-directory - (file-truename (expand-file-name suffix temp-dir))) - (setq suffix (file-name-directory suffix))))) - -(defvar-local flymake-temp-source-file-name nil) -(defvar-local flymake-master-file-name nil) -(defvar-local flymake-temp-master-file-name nil) -(defvar-local flymake-base-dir nil) - -(defun flymake-init-create-temp-buffer-copy (create-temp-f) - "Make a temporary copy of the current buffer, save its name in buffer data and return the name." - (let* ((source-file-name buffer-file-name) - (temp-source-file-name (funcall create-temp-f source-file-name "flymake"))) - - (flymake-save-buffer-in-file temp-source-file-name) - (setq flymake-temp-source-file-name temp-source-file-name) - temp-source-file-name)) - -(defun flymake-simple-cleanup () - "Do cleanup after `flymake-init-create-temp-buffer-copy'. -Delete temp file." - (flymake-safe-delete-file flymake-temp-source-file-name) - (setq flymake-last-change-time nil)) - -(defun flymake-get-real-file-name (file-name-from-err-msg) - "Translate file name from error message to \"real\" file name. -Return full-name. Names are real, not patched." - (let* ((real-name nil) - (source-file-name buffer-file-name) - (master-file-name flymake-master-file-name) - (temp-source-file-name flymake-temp-source-file-name) - (temp-master-file-name flymake-temp-master-file-name) - (base-dirs - (list flymake-base-dir - (file-name-directory source-file-name) - (if master-file-name (file-name-directory master-file-name)))) - (files (list (list source-file-name source-file-name) - (list temp-source-file-name source-file-name) - (list master-file-name master-file-name) - (list temp-master-file-name master-file-name)))) - - (when (equal 0 (length file-name-from-err-msg)) - (setq file-name-from-err-msg source-file-name)) - - (setq real-name (flymake-get-full-patched-file-name file-name-from-err-msg base-dirs files)) - ;; if real-name is nil, than file name from err msg is none of the files we've patched - (if (not real-name) - (setq real-name (flymake-get-full-nonpatched-file-name file-name-from-err-msg base-dirs))) - (if (not real-name) - (setq real-name file-name-from-err-msg)) - (setq real-name (flymake-fix-file-name real-name)) - (flymake-log 3 "get-real-file-name: file-name=%s real-name=%s" file-name-from-err-msg real-name) - real-name)) - -(defun flymake-get-full-patched-file-name (file-name-from-err-msg base-dirs files) - (let* ((base-dirs-count (length base-dirs)) - (file-count (length files)) - (real-name nil)) - - (while (and (not real-name) (> base-dirs-count 0)) - (setq file-count (length files)) - (while (and (not real-name) (> file-count 0)) - (let* ((this-dir (nth (1- base-dirs-count) base-dirs)) - (this-file (nth 0 (nth (1- file-count) files))) - (this-real-name (nth 1 (nth (1- file-count) files)))) - ;;+(flymake-log 0 "this-dir=%s this-file=%s this-real=%s msg-file=%s" this-dir this-file this-real-name file-name-from-err-msg) - (when (and this-dir this-file (flymake-same-files - (expand-file-name file-name-from-err-msg this-dir) - this-file)) - (setq real-name this-real-name))) - (setq file-count (1- file-count))) - (setq base-dirs-count (1- base-dirs-count))) - real-name)) - -(defun flymake-get-full-nonpatched-file-name (file-name-from-err-msg base-dirs) - (let* ((real-name nil)) - (if (file-name-absolute-p file-name-from-err-msg) - (setq real-name file-name-from-err-msg) - (let* ((base-dirs-count (length base-dirs))) - (while (and (not real-name) (> base-dirs-count 0)) - (let* ((full-name (expand-file-name file-name-from-err-msg - (nth (1- base-dirs-count) base-dirs)))) - (if (file-exists-p full-name) - (setq real-name full-name)) - (setq base-dirs-count (1- base-dirs-count)))))) - real-name)) - -(defun flymake-init-find-buildfile-dir (source-file-name buildfile-name) - "Find buildfile, store its dir in buffer data and return its dir, if found." - (let* ((buildfile-dir - (flymake-find-buildfile buildfile-name - (file-name-directory source-file-name)))) - (if buildfile-dir - (setq flymake-base-dir buildfile-dir) - (flymake-log 1 "no buildfile (%s) for %s" buildfile-name source-file-name) - (flymake-report-fatal-status - "NOMK" (format "No buildfile (%s) found for %s" - buildfile-name source-file-name))))) - -(defun flymake-init-create-temp-source-and-master-buffer-copy (get-incl-dirs-f create-temp-f master-file-masks include-regexp) - "Find master file (or buffer), create its copy along with a copy of the source file." - (let* ((source-file-name buffer-file-name) - (temp-source-file-name (flymake-init-create-temp-buffer-copy create-temp-f)) - (master-and-temp-master (flymake-create-master-file - source-file-name temp-source-file-name - get-incl-dirs-f create-temp-f - master-file-masks include-regexp))) - - (if (not master-and-temp-master) - (progn - (flymake-log 1 "cannot find master file for %s" source-file-name) - (flymake-report-status "!" "") ; NOMASTER - nil) - (setq flymake-master-file-name (nth 0 master-and-temp-master)) - (setq flymake-temp-master-file-name (nth 1 master-and-temp-master))))) - -(defun flymake-master-cleanup () - (flymake-simple-cleanup) - (flymake-safe-delete-file flymake-temp-master-file-name)) - -;;;; make-specific init-cleanup routines -(defun flymake-get-syntax-check-program-args (source-file-name base-dir use-relative-base-dir use-relative-source get-cmd-line-f) - "Create a command line for syntax check using GET-CMD-LINE-F." - (funcall get-cmd-line-f - (if use-relative-source - (file-relative-name source-file-name base-dir) - source-file-name) - (if use-relative-base-dir - (file-relative-name base-dir - (file-name-directory source-file-name)) - base-dir))) - -(defun flymake-get-make-cmdline (source base-dir) - (list "make" - (list "-s" - "-C" - base-dir - (concat "CHK_SOURCES=" source) - "SYNTAX_CHECK_MODE=1" - "check-syntax"))) - -(defun flymake-get-ant-cmdline (source base-dir) - (list "ant" - (list "-buildfile" - (concat base-dir "/" "build.xml") - (concat "-DCHK_SOURCES=" source) - "check-syntax"))) - -(defun flymake-simple-make-init-impl (create-temp-f use-relative-base-dir use-relative-source build-file-name get-cmdline-f) - "Create syntax check command line for a directly checked source file. -Use CREATE-TEMP-F for creating temp copy." - (let* ((args nil) - (source-file-name buffer-file-name) - (buildfile-dir (flymake-init-find-buildfile-dir source-file-name build-file-name))) - (if buildfile-dir - (let* ((temp-source-file-name (flymake-init-create-temp-buffer-copy create-temp-f))) - (setq args (flymake-get-syntax-check-program-args temp-source-file-name buildfile-dir - use-relative-base-dir use-relative-source - get-cmdline-f)))) - args)) - -(defun flymake-simple-make-init () - (flymake-simple-make-init-impl 'flymake-create-temp-inplace t t "Makefile" 'flymake-get-make-cmdline)) - -(defun flymake-master-make-init (get-incl-dirs-f master-file-masks include-regexp) - "Create make command line for a source file checked via master file compilation." - (let* ((make-args nil) - (temp-master-file-name (flymake-init-create-temp-source-and-master-buffer-copy - get-incl-dirs-f 'flymake-create-temp-inplace - master-file-masks include-regexp))) - (when temp-master-file-name - (let* ((buildfile-dir (flymake-init-find-buildfile-dir temp-master-file-name "Makefile"))) - (if buildfile-dir - (setq make-args (flymake-get-syntax-check-program-args - temp-master-file-name buildfile-dir nil nil 'flymake-get-make-cmdline))))) - make-args)) - -(defun flymake-find-make-buildfile (source-dir) - (flymake-find-buildfile "Makefile" source-dir)) - -;;;; .h/make specific -(defun flymake-master-make-header-init () - (flymake-master-make-init - 'flymake-get-include-dirs - '("\\.\\(?:c\\(?:pp\\|xx\\|\\+\\+\\)?\\|CC\\)\\'") - "[ \t]*#[ \t]*include[ \t]*\"\\([[:word:]0-9/\\_.]*%s\\)\"")) - -;;;; .java/make specific -(defun flymake-simple-make-java-init () - (flymake-simple-make-init-impl 'flymake-create-temp-with-folder-structure nil nil "Makefile" 'flymake-get-make-cmdline)) - -(defun flymake-simple-ant-java-init () - (flymake-simple-make-init-impl 'flymake-create-temp-with-folder-structure nil nil "build.xml" 'flymake-get-ant-cmdline)) - -(defun flymake-simple-java-cleanup () - "Cleanup after `flymake-simple-make-java-init' -- delete temp file and dirs." - (flymake-safe-delete-file flymake-temp-source-file-name) - (when flymake-temp-source-file-name - (flymake-delete-temp-directory - (file-name-directory flymake-temp-source-file-name)))) - -;;;; perl-specific init-cleanup routines -(defun flymake-perl-init () - (let* ((temp-file (flymake-init-create-temp-buffer-copy - 'flymake-create-temp-inplace)) - (local-file (file-relative-name - temp-file - (file-name-directory buffer-file-name)))) - (list "perl" (list "-wc " local-file)))) - -;;;; php-specific init-cleanup routines -(defun flymake-php-init () - (let* ((temp-file (flymake-init-create-temp-buffer-copy - 'flymake-create-temp-inplace)) - (local-file (file-relative-name - temp-file - (file-name-directory buffer-file-name)))) - (list "php" (list "-f" local-file "-l")))) - -;;;; tex-specific init-cleanup routines -(defun flymake-get-tex-args (file-name) - ;;(list "latex" (list "-c-style-errors" file-name)) - (list "texify" (list "--pdf" "--tex-option=-c-style-errors" file-name))) - -(defun flymake-simple-tex-init () - (flymake-get-tex-args (flymake-init-create-temp-buffer-copy 'flymake-create-temp-inplace))) - -;; Perhaps there should be a buffer-local variable flymake-master-file -;; that people can set to override this stuff. Could inherit from -;; the similar AUCTeX variable. -(defun flymake-master-tex-init () - (let* ((temp-master-file-name (flymake-init-create-temp-source-and-master-buffer-copy - 'flymake-get-include-dirs-dot 'flymake-create-temp-inplace - '("\\.tex\\'") - "[ \t]*\\in\\(?:put\\|clude\\)[ \t]*{\\(.*%s\\)}"))) - (when temp-master-file-name - (flymake-get-tex-args temp-master-file-name)))) - -(defun flymake-get-include-dirs-dot (_base-dir) - '(".")) - -;;;; xml-specific init-cleanup routines -(defun flymake-xml-init () - (list flymake-xml-program - (list "val" (flymake-init-create-temp-buffer-copy - 'flymake-create-temp-inplace)))) + (let* ((name (flymake--diagnostics-buffer-name)) + (source (current-buffer)) + (target (or (get-buffer name) + (with-current-buffer (get-buffer-create name) + (flymake-diagnostics-buffer-mode) + (setq flymake--diagnostics-buffer-source source) + (current-buffer))))) + (with-current-buffer target + (revert-buffer) + (display-buffer (current-buffer))))) (provide 'flymake) + +(require 'flymake-proc) + ;;; flymake.el ends here diff --git a/lisp/progmodes/fortran.el b/lisp/progmodes/fortran.el index b15da92a5c1..b73ee2525fd 100644 --- a/lisp/progmodes/fortran.el +++ b/lisp/progmodes/fortran.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/progmodes/gdb-mi.el b/lisp/progmodes/gdb-mi.el index cc9205c0d8a..58552759b95 100644 --- a/lisp/progmodes/gdb-mi.el +++ b/lisp/progmodes/gdb-mi.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Credits: @@ -400,14 +400,22 @@ valid signal handlers.") (const :tag "Unlimited" nil)) :version "22.1") -(defcustom gdb-non-stop-setting t - "When in non-stop mode, stopped threads can be examined while +(defcustom gdb-non-stop-setting (not (eq system-type 'windows-nt)) + "If non-nil, GDB sessions are expected to support the non-stop mode. +When in the non-stop mode, stopped threads can be examined while other threads continue to execute. +If this is non-nil, GDB will be sent the \"set non-stop 1\" command, +and if that results in an error, the non-stop setting will be +turned off automatically. + +On MS-Windows, this is off by default, because MS-Windows targets +don't support the non-stop mode. + GDB session needs to be restarted for this setting to take effect." :type 'boolean :group 'gdb-non-stop - :version "23.2") + :version "26.1") ;; TODO Some commands can't be called with --all (give a notice about ;; it in setting doc) @@ -2188,7 +2196,10 @@ a GDB/MI reply message." (defun gdbmi-bnf-console-stream-output (c-string) "Handler for the console-stream-output GDB/MI output grammar rule." - (gdb-console c-string)) + (gdb-console c-string) + ;; We've written to the GUD console, so we should print the prompt + ;; after the next result-class or async-class. + (setq gdb-first-done-or-error t)) (defun gdbmi-bnf-target-stream-output (_c-string) "Handler for the target-stream-output GDB/MI output grammar rule." @@ -2374,7 +2385,7 @@ file names include non-ASCII characters." ;; sequences are not split between chunks of output of the GDB process ;; due to buffering, and arrive together. Finally, if some string ;; included literal \nnn strings (as opposed to non-ASCII characters -;; converted by by GDB/MI to octal escapes), this decoding will mangle +;; converted by GDB/MI to octal escapes), this decoding will mangle ;; those strings. When/if GDB acquires the ability to not ;; escape-protect non-ASCII characters in its MI output, this kludge ;; should be removed. diff --git a/lisp/progmodes/glasses.el b/lisp/progmodes/glasses.el index f476ac0a566..699ef2eee82 100644 --- a/lisp/progmodes/glasses.el +++ b/lisp/progmodes/glasses.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/progmodes/grep.el b/lisp/progmodes/grep.el index b3d8a51ceeb..c2d80223541 100644 --- a/lisp/progmodes/grep.el +++ b/lisp/progmodes/grep.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -31,7 +31,6 @@ (require 'compile) - (defgroup grep nil "Run `grep' and display the results." :group 'tools @@ -47,8 +46,8 @@ to avoid computing them again.") (defun grep-apply-setting (symbol value) "Set SYMBOL to VALUE, and update `grep-host-defaults-alist'. SYMBOL should be one of `grep-command', `grep-template', -`grep-use-null-device', `grep-find-command', -`grep-find-template', `grep-find-use-xargs', or +`grep-use-null-device', `grep-find-command' `grep-find-template', +`grep-find-use-xargs', `grep-use-null-filename-separator', or `grep-highlight-matches'." (when grep-host-defaults-alist (let* ((host-id @@ -160,6 +159,15 @@ Customize or call the function `grep-apply-setting'." :set 'grep-apply-setting :group 'grep) +(defcustom grep-use-null-filename-separator 'auto-detect + "If non-nil, use `grep's `--null' option. +This is done to disambiguate file names in `grep's output." + :type '(choice (const :tag "Do Not Use `--null'" nil) + (const :tag "Use `--null'" t) + (other :tag "Not Set" auto-detect)) + :set 'grep-apply-setting + :group 'grep) + ;;;###autoload (defcustom grep-find-command nil "The default find command for \\[grep-find]. @@ -359,31 +367,42 @@ Notice that using \\[next-error] or \\[compile-goto-error] modifies ;;;###autoload (defconst grep-regexp-alist - '( - ;; Use a tight regexp to handle weird file names (with colons - ;; in them) as well as possible. E.g., use [1-9][0-9]* rather - ;; than [0-9]+ so as to accept ":034:" in file names. - ("^\\(.*?[^/\n]\\):[ \t]*\\([1-9][0-9]*\\)[ \t]*:" + `((,(concat "^\\(?:" + ;; Parse using NUL characters when `--null' is used. + ;; Note that we must still assume no newlines in + ;; filenames due to "foo: Is a directory." type + ;; messages. + "\\(?1:[^\0\n]+\\)\\(?3:\0\\)\\(?2:[0-9]+\\):" + "\\|" + ;; Fallback if `--null' is not used, use a tight regexp + ;; to handle weird file names (with colons in them) as + ;; well as possible. E.g., use [1-9][0-9]* rather than + ;; [0-9]+ so as to accept ":034:" in file names. + "\\(?1:[^\n:]+?[^\n/:]\\):[\t ]*\\(?2:[1-9][0-9]*\\)[\t ]*:" + "\\)") 1 2 ;; Calculate column positions (col . end-col) of first grep match on a line - ((lambda () - (when grep-highlight-matches - (let* ((beg (match-end 0)) - (end (save-excursion (goto-char beg) (line-end-position))) - (mbeg (text-property-any beg end 'font-lock-face grep-match-face))) - (when mbeg - (- mbeg beg))))) + (,(lambda () + (when grep-highlight-matches + (let* ((beg (match-end 0)) + (end (save-excursion (goto-char beg) (line-end-position))) + (mbeg (text-property-any beg end 'font-lock-face 'grep-match-face))) + (when mbeg + (- mbeg beg))))) . - (lambda () - (when grep-highlight-matches - (let* ((beg (match-end 0)) - (end (save-excursion (goto-char beg) (line-end-position))) - (mbeg (text-property-any beg end 'font-lock-face grep-match-face)) - (mend (and mbeg (next-single-property-change mbeg 'font-lock-face nil end)))) - (when mend - (- mend beg))))))) + ,(lambda () + (when grep-highlight-matches + (let* ((beg (match-end 0)) + (end (save-excursion (goto-char beg) (line-end-position))) + (mbeg (text-property-any beg end 'font-lock-face 'grep-match-face)) + (mend (and mbeg (next-single-property-change mbeg 'font-lock-face nil end)))) + (when mend + (- mend beg)))))) + nil nil + (3 '(face nil display ":"))) ("^Binary file \\(.+\\) matches$" 1 nil nil 0 1)) - "Regexp used to match grep hits. See `compilation-error-regexp-alist'.") + "Regexp used to match grep hits. +See `compilation-error-regexp-alist' for format details.") (defvar grep-first-column 0 ; bug#10594 "Value to use for `compilation-first-column' in grep buffers.") @@ -422,7 +441,9 @@ Notice that using \\[next-error] or \\[compile-goto-error] modifies (2 grep-error-face nil t)) ;; "filename-linenumber-" format is used for context lines in GNU grep, ;; "filename=linenumber=" for lines with function names in "git grep -p". - ("^.+?[-=][0-9]+[-=].*\n" (0 grep-context-face))) + ("^.+?\\([-=\0]\\)[0-9]+\\([-=]\\).*\n" (0 grep-context-face) + (1 (if (eq (char-after (match-beginning 1)) ?\0) + `(face nil display ,(match-string 2)))))) "Additional things to highlight in grep output. This gets tacked on the end of the generated expressions.") @@ -538,6 +559,8 @@ This function is called from `compilation-filter-hook'." (grep-use-null-device ,grep-use-null-device) (grep-find-command ,grep-find-command) (grep-find-template ,grep-find-template) + (grep-use-null-filename-separator + ,grep-use-null-filename-separator) (grep-find-use-xargs ,grep-find-use-xargs) (grep-highlight-matches ,grep-highlight-matches))))) (let* ((host-id @@ -550,7 +573,8 @@ This function is called from `compilation-filter-hook'." ;; computed for every host once. (dolist (setting '(grep-command grep-template grep-use-null-device grep-find-command - grep-find-template grep-find-use-xargs + grep-use-null-filename-separator + grep-find-template grep-find-use-xargs grep-highlight-matches)) (set setting (cadr (or (assq setting host-defaults) @@ -576,6 +600,21 @@ This function is called from `compilation-filter-hook'." (concat (regexp-quote hello-file) ":[0-9]+:English"))))))))) + (when (eq grep-use-null-filename-separator 'auto-detect) + (setq grep-use-null-filename-separator + (with-temp-buffer + (let* ((hello-file (expand-file-name "HELLO" data-directory)) + (args `("--null" "-ne" "^English" ,hello-file))) + (if grep-use-null-device + (setq args (append args (list null-device))) + (push "-H" args)) + (and (grep-probe grep-program `(nil t nil ,@args)) + (progn + (goto-char (point-min)) + (looking-at + (concat (regexp-quote hello-file) + "\0[0-9]+:English")))))))) + (when (eq grep-highlight-matches 'auto-detect) (setq grep-highlight-matches (with-temp-buffer @@ -591,6 +630,7 @@ This function is called from `compilation-filter-hook'." grep-template grep-find-template) (let ((grep-options (concat (if grep-use-null-device "-n" "-nH") + (if grep-use-null-filename-separator " --null") (if (grep-probe grep-program `(nil nil nil "-e" "foo" ,null-device) nil 1) @@ -863,7 +903,10 @@ substitution string. Note dynamic scoping of variables.") (read-regexp "Search for" 'grep-tag-default 'grep-regexp-history)) (defun grep-read-files (regexp) - "Read files arg for interactive grep." + "Read a file-name pattern arg for interactive grep. +The pattern can include shell wildcards. As whitespace triggers +completion when entering a pattern, including it requires +quoting, e.g. `\\[quoted-insert]<space>'." (let* ((bn (or (buffer-file-name) (replace-regexp-in-string "<[0-9]+>\\'" "" (buffer-name)))) (fn (and bn @@ -896,7 +939,7 @@ substitution string. Note dynamic scoping of variables.") (car (car grep-files-aliases)))) (files (completing-read (concat "Search for \"" regexp - "\" in files" + "\" in files matching wildcard" (if default (concat " (default " default ")")) ": ") 'read-file-name-internal @@ -913,7 +956,9 @@ substitution string. Note dynamic scoping of variables.") "Run grep, searching for REGEXP in FILES in directory DIR. The search is limited to file names matching shell pattern FILES. FILES may use abbreviations defined in `grep-files-aliases', e.g. -entering `ch' is equivalent to `*.[ch]'. +entering `ch' is equivalent to `*.[ch]'. As whitespace triggers +completion when entering a pattern, including it requires +quoting, e.g. `\\[quoted-insert]<space>'. With \\[universal-argument] prefix, you can edit the constructed shell command line before it is executed. @@ -991,7 +1036,9 @@ This command shares argument histories with \\[rgrep] and \\[grep]." "Recursively grep for REGEXP in FILES in directory tree rooted at DIR. The search is limited to file names matching shell pattern FILES. FILES may use abbreviations defined in `grep-files-aliases', e.g. -entering `ch' is equivalent to `*.[ch]'. +entering `ch' is equivalent to `*.[ch]'. As whitespace triggers +completion when entering a pattern, including it requires +quoting, e.g. `\\[quoted-insert]<space>'. With \\[universal-argument] prefix, you can edit the constructed shell command line before it is executed. diff --git a/lisp/progmodes/gud.el b/lisp/progmodes/gud.el index e9ca7eade36..7d044b294da 100644 --- a/lisp/progmodes/gud.el +++ b/lisp/progmodes/gud.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -1830,7 +1830,7 @@ and source-file directory for your debugger." ;; ;; Type M-n to step over the current line and M-s to step into it. That, ;; along with the JDB 'help' command should get you started. The 'quit' -;; JDB command will get out out of the debugger. There is some truly +;; JDB command will get out of the debugger. There is some truly ;; pathetic JDB documentation available at: ;; ;; http://java.sun.com/products/jdk/1.1/debugging/ diff --git a/lisp/progmodes/hideif.el b/lisp/progmodes/hideif.el index b34ea1c4ae1..b1a2a35d55f 100644 --- a/lisp/progmodes/hideif.el +++ b/lisp/progmodes/hideif.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -1657,8 +1657,8 @@ first arg will be `hif-etc'." ;; The original version of hideif evaluates the macro early and store the ;; final values for the defined macro into the symbol database (aka -;; `hide-ifdef-env'). The evaluation process is "strings -> tokens -> parsed -;; tree -> [value]". (The square bracket refers to what's stored in in our +;; `hide-ifdef-env'). The evaluation process is "strings -> tokens -> parsed +;; tree -> [value]". (The square bracket refers to what's stored in our ;; `hide-ifdef-env'.) ;; ;; This forbids the evaluation of an argumented macro since the parameters diff --git a/lisp/progmodes/hideshow.el b/lisp/progmodes/hideshow.el index 5328526abd9..f3abf373d4e 100644 --- a/lisp/progmodes/hideshow.el +++ b/lisp/progmodes/hideshow.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/progmodes/icon.el b/lisp/progmodes/icon.el index 92a89fef70b..a164b703f18 100644 --- a/lisp/progmodes/icon.el +++ b/lisp/progmodes/icon.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/progmodes/idlw-complete-structtag.el b/lisp/progmodes/idlw-complete-structtag.el index d2758ccd62e..a7e49b6ea44 100644 --- a/lisp/progmodes/idlw-complete-structtag.el +++ b/lisp/progmodes/idlw-complete-structtag.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -148,9 +148,9 @@ an up-to-date completion list." (not (equal start idlwave-current-tags-completion-pos))) (idlwave-prepare-structure-tag-completion var)) (setq idlwave-current-tags-completion-pos start) - (setq idlwave-completion-help-info + (setq idlwave-completion-help-info (list 'idlwave-complete-structure-tag-help)) - (idlwave-complete-in-buffer 'structtag 'structtag + (idlwave-complete-in-buffer 'structtag 'structtag idlwave-current-struct-tags nil "Select a structure tag" "structure tag") t) ; we did the completion: return t to skip other completions @@ -169,7 +169,7 @@ an up-to-date completion list." (if (derived-mode-p 'idlwave-shell-mode) ;; OK, we are in the shell, do it dynamically (progn - (message "preparing shell tags") + (message "preparing shell tags") ;; The following call puts the tags into `idlwave-current-struct-tags' (idlwave-complete-structure-tag-query-shell var) ;; initialize @@ -191,7 +191,7 @@ an up-to-date completion list." ;; Find possible definitions of the structure. (while (idlwave-find-structure-definition var nil 'all) (let ((tags (idlwave-struct-tags))) - (when tags + (when tags ;; initialize (setq idlwave-sint-structtags nil idlwave-current-tags-buffer (current-buffer) diff --git a/lisp/progmodes/idlw-help.el b/lisp/progmodes/idlw-help.el index e82ed06164d..244e2b38436 100644 --- a/lisp/progmodes/idlw-help.el +++ b/lisp/progmodes/idlw-help.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/progmodes/idlw-shell.el b/lisp/progmodes/idlw-shell.el index e7497e8e4fd..39d24d4f9d9 100644 --- a/lisp/progmodes/idlw-shell.el +++ b/lisp/progmodes/idlw-shell.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/progmodes/idlw-toolbar.el b/lisp/progmodes/idlw-toolbar.el index 2fda49d91f4..c53e5e5989a 100644 --- a/lisp/progmodes/idlw-toolbar.el +++ b/lisp/progmodes/idlw-toolbar.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/progmodes/idlwave.el b/lisp/progmodes/idlwave.el index f070000c867..92a42b1cb94 100644 --- a/lisp/progmodes/idlwave.el +++ b/lisp/progmodes/idlwave.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -5240,7 +5240,7 @@ Can run from `after-save-hook'." class (cond ((not (boundp 'idlwave-scanning-lib)) (list 'buffer (buffer-file-name))) -; ((string= (downcase (file-name-base)) +; ((string= (downcase (file-name-base (buffer-file-name)) ; (downcase name)) ; (list 'lib)) ; (t (cons 'lib (file-name-nondirectory (buffer-file-name)))) diff --git a/lisp/progmodes/inf-lisp.el b/lisp/progmodes/inf-lisp.el index 7de3a796ae1..e398c3ed64e 100644 --- a/lisp/progmodes/inf-lisp.el +++ b/lisp/progmodes/inf-lisp.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/progmodes/js.el b/lisp/progmodes/js.el index bae9e52bf0f..1f86909362e 100644 --- a/lisp/progmodes/js.el +++ b/lisp/progmodes/js.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary @@ -475,6 +475,11 @@ This applies to function movement, marking, and so on." :type 'boolean :group 'js) +(defcustom js-indent-align-list-continuation t + "Align continuation of non-empty ([{ lines in `js-mode'." + :type 'boolean + :group 'js) + (defcustom js-comment-lineup-func #'c-lineup-C-comments "Lineup function for `cc-mode-style', for C comments in `js-mode'." :type 'function @@ -1829,10 +1834,15 @@ This performs fontification according to `js--class-styles'." (save-excursion (back-to-indentation) (if (js--looking-at-operator-p) - (or (not (memq (char-after) '(?- ?+))) - (progn - (forward-comment (- (point))) - (not (memq (char-before) '(?, ?\[ ?\())))) + (if (eq (char-after) ?/) + (prog1 + (not (nth 3 (syntax-ppss (1+ (point))))) + (forward-char -1)) + (or + (not (memq (char-after) '(?- ?+))) + (progn + (forward-comment (- (point))) + (not (memq (char-before) '(?, ?\[ ?\()))))) (and (js--find-newline-backward) (progn (skip-chars-backward " \t") @@ -1967,8 +1977,12 @@ statement spanning multiple lines; otherwise, return nil." (save-excursion (back-to-indentation) (when (not (looking-at js--declaration-keyword-re)) - (when (looking-at js--indent-operator-re) - (goto-char (match-end 0))) + (let ((pt (point))) + (when (looking-at js--indent-operator-re) + (goto-char (match-end 0))) + ;; The "operator" is probably a regexp literal opener. + (when (nth 3 (syntax-ppss)) + (goto-char pt))) (while (and (not at-opening-bracket) (not (bobp)) (let ((pos (point))) @@ -2092,7 +2106,8 @@ indentation is aligned to that column." (switch-keyword-p (looking-at "default\\_>\\|case\\_>[^:]")) (continued-expr-p (js--continued-expression-p))) (goto-char (nth 1 parse-status)) ; go to the opening char - (if (looking-at "[({[]\\s-*\\(/[/*]\\|$\\)") + (if (or (not js-indent-align-list-continuation) + (looking-at "[({[]\\s-*\\(/[/*]\\|$\\)")) (progn ; nothing following the opening paren/bracket (skip-syntax-backward " ") (when (eq (char-before) ?\)) (backward-list)) @@ -2374,6 +2389,10 @@ i.e., customize JSX element indentation with `sgml-basic-offset', (fill-paragraph-function #'c-fill-paragraph)) (c-fill-paragraph justify))) +(defun js-do-auto-fill () + (let ((js--filling-paragraph t)) + (c-do-auto-fill))) + ;;; Type database and Imenu ;; We maintain a cache of semantic information, i.e., the classes and @@ -3857,6 +3876,7 @@ If one hasn't been set, or if it's stale, prompt for a new one." (setq-local comment-start "// ") (setq-local comment-end "") (setq-local fill-paragraph-function #'js-c-fill-paragraph) + (setq-local normal-auto-fill-function #'js-do-auto-fill) ;; Parse cache (add-hook 'before-change-functions #'js--flush-caches t t) diff --git a/lisp/progmodes/ld-script.el b/lisp/progmodes/ld-script.el index 389ddfca6b1..980ef9014c7 100644 --- a/lisp/progmodes/ld-script.el +++ b/lisp/progmodes/ld-script.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -85,10 +85,12 @@ ;; 3.4.5 Other Linker Script Commands "ASSERT" "EXTERN" "FORCE_COMMON_ALLOCATION" "INHIBIT_COMMON_ALLOCATION" "INSERT" "AFTER" "BEFORE" - "NOCROSSREFS" "OUTPUT_ARCH" "LD_FEATURE" - ;; 3.5.2 PROVIDE + "NOCROSSREFS" "NOCROSSREFS_TO" "OUTPUT_ARCH" "LD_FEATURE" + ;; 3.5.2 HIDDEN + "HIDDEN" + ;; 3.5.3 PROVIDE "PROVIDE" - ;; 3.5.3 PROVIDE_HIDDEN + ;; 3.5.4 PROVIDE_HIDDEN "PROVIDE_HIDDEN" ;; 3.6 SECTIONS Command "SECTIONS" @@ -142,6 +144,7 @@ "DEFINED" "LENGTH" "len" "l" "LOADADDR" + "LOG2CEIL" "MAX" "MIN" "NEXT" diff --git a/lisp/progmodes/m4-mode.el b/lisp/progmodes/m4-mode.el index b48654ff41b..ebb66fa05ac 100644 --- a/lisp/progmodes/m4-mode.el +++ b/lisp/progmodes/m4-mode.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/progmodes/make-mode.el b/lisp/progmodes/make-mode.el index 5cda7bb219c..4c926f4de95 100644 --- a/lisp/progmodes/make-mode.el +++ b/lisp/progmodes/make-mode.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/progmodes/mantemp.el b/lisp/progmodes/mantemp.el index 7a3c0fb0357..93119b1e8d0 100644 --- a/lisp/progmodes/mantemp.el +++ b/lisp/progmodes/mantemp.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/progmodes/meta-mode.el b/lisp/progmodes/meta-mode.el index 33772263884..a47ae28a4af 100644 --- a/lisp/progmodes/meta-mode.el +++ b/lisp/progmodes/meta-mode.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/progmodes/mixal-mode.el b/lisp/progmodes/mixal-mode.el index f884de1fcca..6d2d64af960 100644 --- a/lisp/progmodes/mixal-mode.el +++ b/lisp/progmodes/mixal-mode.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; Major mode for the mix asm language. @@ -30,7 +30,7 @@ ;; For optimal use, also use GNU MDK. Compiling needs mixasm, running ;; and debugging needs mixvm and mixvm.el from GNU MDK. You can get ;; GNU MDK from `https://savannah.gnu.org/projects/mdk/' and -;; `ftp://ftp.gnu.org/pub/gnu/mdk'. +;; `https://ftp.gnu.org/pub/gnu/mdk'. ;; ;; To use this mode, place the following in your init file: ;; `(load-file "/PATH-TO-FILE/mixal-mode.el")'. diff --git a/lisp/progmodes/octave.el b/lisp/progmodes/octave.el index ac9ba630c4e..dc6bba44f32 100644 --- a/lisp/progmodes/octave.el +++ b/lisp/progmodes/octave.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -37,7 +37,7 @@ (defgroup octave nil "Editing Octave code." :link '(custom-manual "(octave-mode)Top") - :link '(url-link "http://www.gnu.org/s/octave") + :link '(url-link "https://www.gnu.org/s/octave") :link '(custom-group-link :tag "Font Lock Faces group" font-lock-faces) :group 'languages) @@ -612,7 +612,7 @@ Key bindings: (defcustom inferior-octave-prompt ;; For Octave >= 3.8, default is always 'octave', see - ;; http://hg.savannah.gnu.org/hgweb/octave/rev/708173343c50 + ;; https://hg.savannah.gnu.org/hgweb/octave/rev/708173343c50 "\\(?:^octave\\(?:.bin\\|.exe\\)?\\(?:-[.0-9]+\\)?\\(?::[0-9]+\\)?\\|^debug\\|^\\)>+ " "Regexp to match prompts for the inferior Octave process." :type 'regexp) @@ -839,7 +839,7 @@ startup file, `~/.emacs-octave'." (inferior-octave-send-list-and-digest (list "more off;\n" (unless (equal inferior-octave-output-string ">> ") - ;; See http://hg.savannah.gnu.org/hgweb/octave/rev/708173343c50 + ;; See https://hg.savannah.gnu.org/hgweb/octave/rev/708173343c50 "PS1 ('octave> ');\n") (when (and inferior-octave-startup-file (file-exists-p inferior-octave-startup-file)) @@ -867,7 +867,7 @@ startup file, `~/.emacs-octave'." (defun inferior-octave-completion-at-point () "Return the data to complete the Octave symbol at point." - ;; http://debbugs.gnu.org/14300 + ;; https://debbugs.gnu.org/14300 (unless (string-match-p "/" (or (comint--match-partial-filename) "")) (let ((beg (save-excursion (skip-syntax-backward "w_" (comint-line-beginning-position)) @@ -1497,7 +1497,7 @@ current buffer file unless called with a prefix arg \\[universal-argument]." (string (buffer-substring-no-properties beg end)) line) (with-current-buffer inferior-octave-buffer - ;; http://lists.gnu.org/archive/html/emacs-devel/2013-10/msg00095.html + ;; https://lists.gnu.org/r/emacs-devel/2013-10/msg00095.html (compilation-forget-errors) (setq inferior-octave-output-list nil) (while (not (string-equal string "")) diff --git a/lisp/progmodes/opascal.el b/lisp/progmodes/opascal.el index 6a61564b446..12353c4fafd 100644 --- a/lisp/progmodes/opascal.el +++ b/lisp/progmodes/opascal.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/progmodes/pascal.el b/lisp/progmodes/pascal.el index a7d0624a74a..5f893b87c2e 100644 --- a/lisp/progmodes/pascal.el +++ b/lisp/progmodes/pascal.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/progmodes/perl-mode.el b/lisp/progmodes/perl-mode.el index 3def37a2ea8..f3cb8109133 100644 --- a/lisp/progmodes/perl-mode.el +++ b/lisp/progmodes/perl-mode.el @@ -23,7 +23,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -135,7 +135,7 @@ '(;; Functions (nil "^[ \t]*sub\\s-+\\([-[:alnum:]+_:]+\\)" 1) ;;Variables - ("Variables" "^\\(?:my\\|our\\)\\s-+\\([$@%][-[:alnum:]+_:]+\\)\\s-*=" 1) + ("Variables" "^[ \t]*\\(?:anon\\|argument\\|has\\|local\\|my\\|our\\|state\\|supersede\\)\\s-+\\([$@%][-[:alnum:]+_:]+\\)\\s-*=" 1) ("Packages" "^[ \t]*package\\s-+\\([-[:alnum:]+_:]+\\);" 1) ("Doc sections" "^=head[0-9][ \t]+\\(.*\\)" 1)) "Imenu generic expression for Perl mode. See `imenu-generic-expression'.") @@ -179,8 +179,9 @@ "BEGIN" "END" "return" "exec" "eval") t) "\\>") ;; - ;; Fontify local and my keywords as types. - ("\\<\\(local\\|my\\)\\>" . font-lock-type-face) + ;; Fontify declarators and prefixes as types. + ("\\<\\(anon\\|argument\\|has\\|local\\|my\\|our\\|state\\|supersede\\)\\>" . font-lock-type-face) ; declarators + ("\\<\\(let\\|temp\\)\\>" . font-lock-type-face) ; prefixes ;; ;; Fontify function, variable and file name references. ("&\\(\\sw+\\(::\\sw+\\)*\\)" 1 font-lock-function-name-face) @@ -213,25 +214,6 @@ (regexp-opt perl--syntax-exp-intro-keywords) "\\|[-?:.,;|&+*=!~({[]\\|\\(^\\)\\)[ \t\n]*"))) -;; FIXME: handle here-docs and regexps. -;; <<EOF <<"EOF" <<'EOF' (no space) -;; see `man perlop' -;; ?...? -;; /.../ -;; m [...] -;; m /.../ -;; q /.../ = '...' -;; qq /.../ = "..." -;; qx /.../ = `...` -;; qr /.../ = precompiled regexp =~=~ m/.../ -;; qw /.../ -;; s /.../.../ -;; s <...> /.../ -;; s '...'...' -;; tr /.../.../ -;; y /.../.../ -;; -;; <file*glob> (defun perl-syntax-propertize-function (start end) (let ((case-fold-search nil)) (goto-char start) @@ -324,23 +306,25 @@ ((concat "\\(?:" ;; << "EOF", << 'EOF', or << \EOF - "<<[ \t]*\\('[^'\n]*'\\|\"[^\"\n]*\"\\|\\\\[[:alpha:]][[:alnum:]]*\\)" + "<<\\(~\\)?[ \t]*\\('[^'\n]*'\\|\"[^\"\n]*\"\\|\\\\[[:alpha:]][[:alnum:]]*\\)" ;; The <<EOF case which needs perl--syntax-exp-intro-regexp, to ;; disambiguate with the left-bitshift operator. - "\\|" perl--syntax-exp-intro-regexp "<<\\(?1:\\sw+\\)\\)" + "\\|" perl--syntax-exp-intro-regexp "<<\\(?2:\\sw+\\)\\)" ".*\\(\n\\)") - (3 (let* ((st (get-text-property (match-beginning 3) 'syntax-table)) - (name (match-string 1))) - (goto-char (match-end 1)) + (4 (let* ((st (get-text-property (match-beginning 4) 'syntax-table)) + (name (match-string 2)) + (indented (match-beginning 1))) + (goto-char (match-end 2)) (if (save-excursion (nth 8 (syntax-ppss (match-beginning 0)))) ;; Leave the property of the newline unchanged. st (cons (car (string-to-syntax "< c")) ;; Remember the names of heredocs found on this line. - (cons (pcase (aref name 0) - (`?\\ (substring name 1)) - ((or `?\" `?\' `?\`) (substring name 1 -1)) - (_ name)) + (cons (cons (pcase (aref name 0) + (`?\\ (substring name 1)) + ((or `?\" `?\' `?\`) (substring name 1 -1)) + (_ name)) + indented) (cdr st))))))) ;; We don't call perl-syntax-propertize-special-constructs directly ;; from the << rule, because there might be other elements (between @@ -383,7 +367,9 @@ (goto-char (nth 8 state))) (while (and names (re-search-forward - (concat "^" (regexp-quote (pop names)) "\n") + (pcase-let ((`(,name . ,indented) (pop names))) + (concat "^" (if indented "[ \t]*") + (regexp-quote name) "\n")) limit 'move)) (unless names (put-text-property (1- (point)) (point) 'syntax-table @@ -595,6 +581,73 @@ create a new comment." (match-string-no-properties 1)))) +;;; Flymake support +(defcustom perl-flymake-command '("perl" "-w" "-c") + "External tool used to check Perl source code. +This is a non empty list of strings, the checker tool possibly +followed by required arguments. Once launched it will receive +the Perl source to be checked as its standard input." + :group 'perl + :type '(repeat string)) + +(defvar-local perl--flymake-proc nil) + +;;;###autoload +(defun perl-flymake (report-fn &rest _args) + "Perl backend for Flymake. Launches +`perl-flymake-command' (which see) and passes to its standard +input the contents of the current buffer. The output of this +command is analyzed for error and warning messages." + (unless (executable-find (car perl-flymake-command)) + (error "Cannot find a suitable checker")) + + (when (process-live-p perl--flymake-proc) + (kill-process perl--flymake-proc)) + + (let ((source (current-buffer))) + (save-restriction + (widen) + (setq + perl--flymake-proc + (make-process + :name "perl-flymake" :noquery t :connection-type 'pipe + :buffer (generate-new-buffer " *perl-flymake*") + :command perl-flymake-command + :sentinel + (lambda (proc _event) + (when (eq 'exit (process-status proc)) + (unwind-protect + (if (with-current-buffer source (eq proc perl--flymake-proc)) + (with-current-buffer (process-buffer proc) + (goto-char (point-min)) + (cl-loop + while (search-forward-regexp + "^\\(.+\\) at - line \\([0-9]+\\)" + nil t) + for msg = (match-string 1) + for (beg . end) = (flymake-diag-region + source + (string-to-number (match-string 2))) + for type = + (if (string-match + "\\(Scalar value\\|Useless use\\|Unquoted string\\)" + msg) + :warning + :error) + collect (flymake-make-diagnostic source + beg + end + type + msg) + into diags + finally (funcall report-fn diags))) + (flymake-log :debug "Canceling obsolete check %s" + proc)) + (kill-buffer (process-buffer proc))))))) + (process-send-region perl--flymake-proc (point-min) (point-max)) + (process-send-eof perl--flymake-proc)))) + + (defvar perl-mode-hook nil "Normal hook to run when entering Perl mode.") @@ -679,7 +732,9 @@ Turning on Perl mode runs the normal hook `perl-mode-hook'." ;; Setup outline-minor-mode. (setq-local outline-regexp perl-outline-regexp) (setq-local outline-level 'perl-outline-level) - (setq-local add-log-current-defun-function #'perl-current-defun-name)) + (setq-local add-log-current-defun-function #'perl-current-defun-name) + ;; Setup Flymake + (add-hook 'flymake-diagnostic-functions #'perl-flymake nil t)) ;; This is used by indent-for-comment ;; to decide how much to indent a comment in Perl code @@ -692,7 +747,9 @@ Turning on Perl mode runs the normal hook `perl-mode-hook'." (define-obsolete-function-alias 'electric-perl-terminator 'perl-electric-terminator "22.1") (defun perl-electric-noindent-p (_char) - (unless (eolp) 'no-indent)) + ;; To reproduce the old behavior, ;, {, }, and : are made electric, but + ;; we only want them to be electric at EOL. + (unless (or (bolp) (eolp)) 'no-indent)) (defun perl-electric-terminator (arg) "Insert character and maybe adjust indentation. diff --git a/lisp/progmodes/prog-mode.el b/lisp/progmodes/prog-mode.el index 8f66f1c9541..f727e458b2b 100644 --- a/lisp/progmodes/prog-mode.el +++ b/lisp/progmodes/prog-mode.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -225,11 +225,11 @@ on the symbol." (apply #'font-lock-flush prettify-symbols--current-symbol-bounds) (setq prettify-symbols--current-symbol-bounds nil)) ;; Unprettify the current symbol. - (when-let ((c (get-prop-as-list 'composition)) - (s (get-prop-as-list 'prettify-symbols-start)) - (e (get-prop-as-list 'prettify-symbols-end)) - (s (apply #'min s)) - (e (apply #'max e))) + (when-let* ((c (get-prop-as-list 'composition)) + (s (get-prop-as-list 'prettify-symbols-start)) + (e (get-prop-as-list 'prettify-symbols-end)) + (s (apply #'min s)) + (e (apply #'max e))) (with-silent-modifications (setq prettify-symbols--current-symbol-bounds (list s e)) (remove-text-properties s e '(composition)))))) diff --git a/lisp/progmodes/project.el b/lisp/progmodes/project.el index ed1d564752c..93a945edaa4 100644 --- a/lisp/progmodes/project.el +++ b/lisp/progmodes/project.el @@ -15,7 +15,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -307,7 +307,11 @@ DIRS must contain directory names." (defun project-find-regexp (regexp) "Find all matches for REGEXP in the current project's roots. With \\[universal-argument] prefix, you can specify the directory -to search in, and the file name pattern to search for." +to search in, and the file name pattern to search for. The +pattern may use abbreviations defined in `grep-files-aliases', +e.g. entering `ch' is equivalent to `*.[ch]'. As whitespace +triggers completion when entering a pattern, including it +requires quoting, e.g. `\\[quoted-insert]<space>'." (interactive (list (project--read-regexp))) (let* ((pr (project-current t)) (dirs (if current-prefix-arg diff --git a/lisp/progmodes/prolog.el b/lisp/progmodes/prolog.el index c234cca3ff9..13cd6be9f7d 100644 --- a/lisp/progmodes/prolog.el +++ b/lisp/progmodes/prolog.el @@ -26,7 +26,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; Original author: Masanobu UMEDA <umerin(at)mse(dot)kyutech(dot)ac(dot)jp> ;; Parts of this file was taken from a modified version of the original @@ -358,13 +358,15 @@ The version numbers are of the format (Major . Minor)." (defcustom prolog-indent-width 4 "The indentation width used by the editing buffer." :group 'prolog-indentation - :type 'integer) + :type 'integer + :safe 'integerp) (defcustom prolog-left-indent-regexp "\\(;\\|\\*?->\\)" "Regexp for `prolog-electric-if-then-else-flag'." :version "24.1" :group 'prolog-indentation - :type 'regexp) + :type 'regexp + :safe 'stringp) (defcustom prolog-paren-indent-p nil "If non-nil, increase indentation for parenthesis expressions. @@ -374,14 +376,16 @@ right (if this variable is non-nil) or in the same way as for compound terms (if this variable is nil, default)." :version "24.1" :group 'prolog-indentation - :type 'boolean) + :type 'boolean + :safe 'booleanp) (defcustom prolog-paren-indent 4 "The indentation increase for parenthesis expressions. Only used in ( If -> Then ; Else) and ( Disj1 ; Disj2 ) style expressions." :version "24.1" :group 'prolog-indentation - :type 'integer) + :type 'integer + :safe 'integerp) (defcustom prolog-parse-mode 'beg-of-clause "The parse mode used (decides from which point parsing is done). diff --git a/lisp/progmodes/ps-mode.el b/lisp/progmodes/ps-mode.el index 7e2b7fdf79f..69ea3a70f56 100644 --- a/lisp/progmodes/ps-mode.el +++ b/lisp/progmodes/ps-mode.el @@ -28,7 +28,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el index 464b931cffc..9e09bfc5941 100644 --- a/lisp/progmodes/python.el +++ b/lisp/progmodes/python.el @@ -23,7 +23,7 @@ ;; General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -80,7 +80,7 @@ ;; Using the "console" subcommand to start IPython in server-client ;; mode is known to fail intermittently due a bug on IPython itself -;; (see URL `http://debbugs.gnu.org/cgi/bugreport.cgi?bug=18052#27'). +;; (see URL `https://debbugs.gnu.org/cgi/bugreport.cgi?bug=18052#27'). ;; There seems to be a race condition in the IPython server (A.K.A ;; kernel) when code is sent while it is still initializing, sometimes ;; causing the shell to get stalled. With that said, if an IPython @@ -97,7 +97,7 @@ ;; Missing or delayed output used to happen due to differences between ;; Operating Systems' pipe buffering (e.g. CPython 3.3.4 in Windows 7. -;; See URL `http://debbugs.gnu.org/cgi/bugreport.cgi?bug=17304'). To +;; See URL `https://debbugs.gnu.org/cgi/bugreport.cgi?bug=17304'). To ;; avoid this, the `python-shell-unbuffered' defaults to non-nil and ;; controls whether `python-shell-calculate-process-environment' ;; should set the "PYTHONUNBUFFERED" environment variable on startup: @@ -273,7 +273,7 @@ (autoload 'help-function-arglist "help-fns") ;;;###autoload -(add-to-list 'auto-mode-alist (cons (purecopy "\\.pyw?\\'") 'python-mode)) +(add-to-list 'auto-mode-alist (cons (purecopy "\\.py[iw]?\\'") 'python-mode)) ;;;###autoload (add-to-list 'interpreter-mode-alist (cons (purecopy "python[0-9.]*") 'python-mode)) @@ -640,10 +640,14 @@ The type returned can be `comment', `string' or `paren'." ((python-rx string-delimiter) (0 (ignore (python-syntax-stringify)))))) -(defconst python--prettify-symbols-alist +(defvar python-prettify-symbols-alist '(("lambda" . ?λ) ("and" . ?∧) - ("or" . ?∨))) + ("or" . ?∨)) + "Value for `prettify-symbols-alist' in `python-mode'.") + +(define-obsolete-variable-alias 'python--prettify-symbols-alist + 'python-prettify-symbols-alist "26.1") (defsubst python-syntax-count-quotes (quote-char &optional point limit) "Count number of quotes around point (max is 3). @@ -1253,7 +1257,11 @@ This function is intended to be added to `post-self-insert-hook.' If a line renders a paren alone, after adding a char before it, the line will be re-indented automatically if needed." (when (and electric-indent-mode - (eq (char-before) last-command-event)) + (eq (char-before) last-command-event) + (not (python-syntax-context 'string)) + (save-excursion + (beginning-of-line) + (not (python-syntax-context 'string (syntax-ppss))))) (cond ;; Electric indent inside parens ((and @@ -2109,20 +2117,25 @@ remote host, the returned value is intended for (defun python-shell-calculate-exec-path () "Calculate `exec-path'. Prepends `python-shell-exec-path' and adds the binary directory -for virtualenv if `python-shell-virtualenv-root' is set. If -`default-directory' points to a remote host, the returned value -appends `python-shell-remote-exec-path' instead of `exec-path'." +for virtualenv if `python-shell-virtualenv-root' is set - this +will use the python interpreter from inside the virtualenv when +starting the shell. If `default-directory' points to a remote host, +the returned value appends `python-shell-remote-exec-path' instead +of `exec-path'." (let ((new-path (copy-sequence (if (file-remote-p default-directory) python-shell-remote-exec-path - exec-path)))) + exec-path))) + + ;; Windows and POSIX systems use different venv directory structures + (virtualenv-bin-dir (if (eq system-type 'windows-nt) "Scripts" "bin"))) (python-shell--add-to-path-with-priority new-path python-shell-exec-path) (if (not python-shell-virtualenv-root) new-path (python-shell--add-to-path-with-priority new-path - (list (expand-file-name "bin" python-shell-virtualenv-root))) + (list (expand-file-name virtualenv-bin-dir python-shell-virtualenv-root))) new-path))) (defun python-shell-tramp-refresh-remote-path (vec paths) @@ -2212,6 +2225,11 @@ machine then modifies `tramp-remote-process-environment' and Do not set this variable directly, instead use `python-shell-prompt-set-calculated-regexps'.") +(defvar python-shell--block-prompt nil + "Input block prompt for inferior python shell. +Do not set this variable directly, instead use +`python-shell-prompt-set-calculated-regexps'.") + (defvar python-shell--prompt-calculated-output-regexp nil "Calculated output prompt regexp for inferior python shell. Do not set this variable directly, instead use @@ -2245,7 +2263,11 @@ detection and just returns nil." ;; `condition-case' and displaying the error message to ;; the user in the no-prompts warning. (ignore-errors - (let ((code-file (python-shell--save-temp-file code))) + (let ((code-file + ;; Python 2.x on Windows does not handle + ;; carriage returns in unbuffered mode. + (let ((inhibit-eol-conversion (getenv "PYTHONUNBUFFERED"))) + (python-shell--save-temp-file code)))) ;; Use `process-file' as it is remote-host friendly. (process-file interpreter @@ -2362,6 +2384,7 @@ and `python-shell-output-prompt-regexp' using the values from (dolist (prompt (butlast detected-prompts)) (setq prompt (regexp-quote prompt)) (cl-pushnew prompt input-prompts :test #'string=)) + (setq python-shell--block-prompt (nth 1 detected-prompts)) (cl-pushnew (regexp-quote (car (last detected-prompts))) output-prompts :test #'string=)) @@ -2722,6 +2745,7 @@ variable. (set (make-local-variable 'python-shell-interpreter-args) (or python-shell--interpreter-args python-shell-interpreter-args)) (set (make-local-variable 'python-shell--prompt-calculated-input-regexp) nil) + (set (make-local-variable 'python-shell--block-prompt) nil) (set (make-local-variable 'python-shell--prompt-calculated-output-regexp) nil) (python-shell-prompt-set-calculated-regexps) (setq comint-prompt-regexp python-shell--prompt-calculated-input-regexp) @@ -3289,8 +3313,9 @@ the full statement in the case of imports." (defcustom python-shell-completion-native-disabled-interpreters ;; PyPy's readline cannot handle some escape sequences yet. Native ;; completion was found to be non-functional for IPython (see - ;; Bug#25067). - (list "pypy" "ipython") + ;; Bug#25067). Native completion doesn't work on w32 (Bug#28580). + (if (eq system-type 'windows-nt) '("") + '("pypy" "ipython")) "List of disabled interpreters. When a match is found, native completion is disabled." :version "25.1" @@ -3431,6 +3456,8 @@ def __PYTHON_EL_native_completion_setup(): instance.rlcomplete = new_completer if readline.__doc__ and 'libedit' in readline.__doc__: + raise Exception('''libedit based readline is known not to work, + see etc/PROBLEMS under \"In Inferior Python mode, input is echoed\".''') readline.parse_and_bind('bind ^I rl_complete') else: readline.parse_and_bind('tab: complete') @@ -3439,7 +3466,9 @@ def __PYTHON_EL_native_completion_setup(): print ('python.el: native completion setup loaded') except: - print ('python.el: native completion setup failed') + import sys + print ('python.el: native completion setup failed, %s: %s' + % sys.exc_info()[:2]) __PYTHON_EL_native_completion_setup()" process) (when (and @@ -3628,7 +3657,14 @@ using that one instead of current buffer's process." ;; Also, since pdb interaction is single-line ;; based, this is enough. (string-match-p python-shell-prompt-pdb-regexp prompt)) - #'python-shell-completion-get-completions) + (if (or (equal python-shell--block-prompt prompt) + (string-match-p + python-shell-prompt-block-regexp prompt)) + ;; The non-native completion mechanism sends + ;; newlines to the interpreter, so we can't use + ;; it during a multiline statement (Bug#28051). + #'ignore + #'python-shell-completion-get-completions)) (t #'python-shell-completion-native-get-completions))))) (list start end (completion-table-dynamic @@ -4253,8 +4289,10 @@ See `python-check-command' for the default." import inspect try: str_type = basestring + argspec_function = inspect.getargspec except NameError: str_type = str + argspec_function = inspect.getfullargspec if isinstance(obj, str_type): obj = eval(obj, globals()) doc = inspect.getdoc(obj) @@ -4267,9 +4305,7 @@ See `python-check-command' for the default." target = obj objtype = 'def' if target: - args = inspect.formatargspec( - *inspect.getargspec(target) - ) + args = inspect.formatargspec(*argspec_function(target)) name = obj.__name__ doc = '{objtype} {name}{args}'.format( objtype=objtype, name=name, args=args @@ -5115,6 +5151,138 @@ returned as is." (ignore-errors (string-match regexp "") t)) +;;; Flymake integration + +(defgroup python-flymake nil + "Integration between Python and Flymake." + :group 'python + :link '(custom-group-link :tag "Flymake" flymake) + :version "26.1") + +(defcustom python-flymake-command '("pyflakes") + "The external tool that will be used to perform the syntax check. +This is a non empty list of strings, the checker tool possibly followed by +required arguments. Once launched it will receive the Python source to be +checked as its standard input. +To use `flake8' you would set this to (\"flake8\" \"-\")." + :group 'python-flymake + :type '(repeat string)) + +;; The default regexp accomodates for older pyflakes, which did not +;; report the column number, and at the same time it's compatible with +;; flake8 output, although it may be redefined to explicitly match the +;; TYPE +(defcustom python-flymake-command-output-pattern + (list + "^\\(?:<?stdin>?\\):\\(?1:[0-9]+\\):\\(?:\\(?2:[0-9]+\\):\\)? \\(?3:.*\\)$" + 1 2 nil 3) + "Specify how to parse the output of `python-flymake-command'. +The value has the form (REGEXP LINE COLUMN TYPE MESSAGE): if +REGEXP matches, the LINE'th subexpression gives the line number, +the COLUMN'th subexpression gives the column number on that line, +the TYPE'th subexpression gives the type of the message and the +MESSAGE'th gives the message text itself. + +If COLUMN or TYPE are nil or that index didn't match, that +information is not present on the matched line and a default will +be used." + :group 'python-flymake + :type '(list regexp + (integer :tag "Line's index") + (choice + (const :tag "No column" nil) + (integer :tag "Column's index")) + (choice + (const :tag "No type" nil) + (integer :tag "Type's index")) + (integer :tag "Message's index"))) + +(defcustom python-flymake-msg-alist + '(("\\(^redefinition\\|.*unused.*\\|used$\\)" . :warning)) + "Alist used to associate messages to their types. +Each element should be a cons-cell (REGEXP . TYPE), where TYPE must be +one defined in the variable `flymake-diagnostic-types-alist'. +For example, when using `flake8' a possible configuration could be: + + ((\"\\(^redefinition\\|.*unused.*\\|used$\\)\" . :warning) + (\"^E999\" . :error) + (\"^[EW][0-9]+\" . :note)) + +By default messages are considered errors." + :group 'python-flymake + :type `(alist :key-type (regexp) + :value-type (symbol))) + +(defvar-local python--flymake-proc nil) + +(defun python--flymake-parse-output (source proc report-fn) + "Collect diagnostics parsing checker tool's output line by line." + (let ((rx (nth 0 python-flymake-command-output-pattern)) + (lineidx (nth 1 python-flymake-command-output-pattern)) + (colidx (nth 2 python-flymake-command-output-pattern)) + (typeidx (nth 3 python-flymake-command-output-pattern)) + (msgidx (nth 4 python-flymake-command-output-pattern))) + (with-current-buffer (process-buffer proc) + (goto-char (point-min)) + (cl-loop + while (search-forward-regexp rx nil t) + for msg = (match-string msgidx) + for (beg . end) = (flymake-diag-region + source + (string-to-number + (match-string lineidx)) + (and colidx + (match-string colidx) + (string-to-number + (match-string colidx)))) + for type = (or (and typeidx + (match-string typeidx) + (assoc-default + (match-string typeidx) + python-flymake-msg-alist + #'string-match)) + (assoc-default msg + python-flymake-msg-alist + #'string-match) + :error) + collect (flymake-make-diagnostic + source beg end type msg) + into diags + finally (funcall report-fn diags))))) + +(defun python-flymake (report-fn &rest _args) + "Flymake backend for Python. +This backend uses `python-flymake-command' (which see) to launch a process +that is passed the current buffer's content via stdin. +REPORT-FN is Flymake's callback function." + (unless (executable-find (car python-flymake-command)) + (error "Cannot find a suitable checker")) + + (when (process-live-p python--flymake-proc) + (kill-process python--flymake-proc)) + + (let ((source (current-buffer))) + (save-restriction + (widen) + (setq python--flymake-proc + (make-process + :name "python-flymake" + :noquery t + :connection-type 'pipe + :buffer (generate-new-buffer " *python-flymake*") + :command python-flymake-command + :sentinel + (lambda (proc _event) + (when (eq 'exit (process-status proc)) + (unwind-protect + (when (with-current-buffer source + (eq proc python--flymake-proc)) + (python--flymake-parse-output source proc report-fn)) + (kill-buffer (process-buffer proc))))))) + (process-send-region python--flymake-proc (point-min) (point-max)) + (process-send-eof python--flymake-proc)))) + + (defun python-electric-pair-string-delimiter () (when (and electric-pair-mode (memq last-command-event '(?\" ?\')) @@ -5228,7 +5396,9 @@ returned as is." (make-local-variable 'python-shell-internal-buffer) (when python-indent-guess-indent-offset - (python-indent-guess-indent-offset))) + (python-indent-guess-indent-offset)) + + (add-hook 'flymake-diagnostic-functions #'python-flymake nil t)) (provide 'python) diff --git a/lisp/progmodes/ruby-mode.el b/lisp/progmodes/ruby-mode.el index 6f431ecd302..dc1b0f8e2da 100644 --- a/lisp/progmodes/ruby-mode.el +++ b/lisp/progmodes/ruby-mode.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -2253,6 +2253,139 @@ See `font-lock-syntax-table'.") (progn (set-match-data value) t)) (ruby-match-expression-expansion limit))))) +;;; Flymake support +(defvar-local ruby--flymake-proc nil) + +(defun ruby-flymake-simple (report-fn &rest _args) + "`ruby -wc' backend for Flymake." + (unless (executable-find "ruby") + (error "Cannot find the ruby executable")) + + (ruby-flymake--helper + "ruby-flymake" + '("ruby" "-w" "-c") + (lambda (_proc source) + (goto-char (point-min)) + (cl-loop + while (search-forward-regexp + "^\\(?:.*.rb\\|-\\):\\([0-9]+\\): \\(.*\\)$" + nil t) + for msg = (match-string 2) + for (beg . end) = (flymake-diag-region + source + (string-to-number (match-string 1))) + for type = (if (string-match "^warning" msg) + :warning + :error) + collect (flymake-make-diagnostic source + beg + end + type + msg) + into diags + finally (funcall report-fn diags))))) + +(defun ruby-flymake--helper (process-name command parser-fn) + (when (process-live-p ruby--flymake-proc) + (kill-process ruby--flymake-proc)) + + (let ((source (current-buffer))) + (save-restriction + (widen) + (setq + ruby--flymake-proc + (make-process + :name process-name :noquery t :connection-type 'pipe + :buffer (generate-new-buffer (format " *%s*" process-name)) + :command command + :sentinel + (lambda (proc _event) + (when (eq 'exit (process-status proc)) + (unwind-protect + (if (with-current-buffer source (eq proc ruby--flymake-proc)) + (with-current-buffer (process-buffer proc) + (funcall parser-fn proc source)) + (flymake-log :debug "Canceling obsolete check %s" + proc)) + (kill-buffer (process-buffer proc))))))) + (process-send-region ruby--flymake-proc (point-min) (point-max)) + (process-send-eof ruby--flymake-proc)))) + +(defcustom ruby-flymake-use-rubocop-if-available t + "Non-nil to use the Rubocop Flymake backend. +Only takes effect if Rubocop is installed." + :type 'boolean + :group 'ruby + :safe 'booleanp) + +(defcustom ruby-rubocop-config ".rubocop.yml" + "Configuration file for `ruby-flymake-rubocop'." + :type 'string + :group 'ruby + :safe 'stringp) + +(defun ruby-flymake-rubocop (report-fn &rest _args) + "Rubocop backend for Flymake." + (unless (executable-find "rubocop") + (error "Cannot find the rubocop executable")) + + (let ((command (list "rubocop" "--stdin" buffer-file-name "--format" "emacs" + "--cache" "false" ; Work around a bug in old version. + "--display-cop-names")) + config-dir) + (when buffer-file-name + (setq config-dir (locate-dominating-file buffer-file-name + ruby-rubocop-config)) + (when config-dir + (setq command (append command (list "--config" + (expand-file-name ruby-rubocop-config + config-dir))))) + + (ruby-flymake--helper + "rubocop-flymake" + command + (lambda (proc source) + ;; Finding the executable is no guarantee of + ;; rubocop working, especially in the presence + ;; of rbenv shims (which cross ruby versions). + (when (eq (process-exit-status proc) 127) + ;; Not sure what to do in this case. Maybe ideally we'd + ;; switch back to ruby-flymake-simple. + (flymake-log :warning "Rubocop returned status 127: %s" + (buffer-string))) + (goto-char (point-min)) + (cl-loop + while (search-forward-regexp + "^\\(?:.*.rb\\|-\\):\\([0-9]+\\):\\([0-9]+\\): \\(.*\\)$" + nil t) + for msg = (match-string 3) + for (beg . end) = (flymake-diag-region + source + (string-to-number (match-string 1)) + (string-to-number (match-string 2))) + for type = (cond + ((string-match "^[EF]: " msg) + :error) + ((string-match "^W: " msg) + :warning) + (t :note)) + collect (flymake-make-diagnostic source + beg + end + type + (substring msg 3)) + into diags + finally (funcall report-fn diags))))))) + +(defun ruby-flymake-auto (report-fn &rest args) + (apply + (if (and ruby-flymake-use-rubocop-if-available + (executable-find "rubocop")) + #'ruby-flymake-rubocop + #'ruby-flymake-simple) + report-fn + args)) + ;;;###autoload (define-derived-mode ruby-mode prog-mode "Ruby" "Major mode for editing Ruby code." @@ -2265,6 +2398,7 @@ See `font-lock-syntax-table'.") (add-hook 'after-save-hook 'ruby-mode-set-encoding nil 'local) (add-hook 'electric-indent-functions 'ruby--electric-indent-p nil 'local) + (add-hook 'flymake-diagnostic-functions 'ruby-flymake-auto nil 'local) (setq-local font-lock-defaults '((ruby-font-lock-keywords) nil nil)) (setq-local font-lock-keywords ruby-font-lock-keywords) diff --git a/lisp/progmodes/scheme.el b/lisp/progmodes/scheme.el index 0dcf9b47b84..bb75595cb4d 100644 --- a/lisp/progmodes/scheme.el +++ b/lisp/progmodes/scheme.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/progmodes/sh-script.el b/lisp/progmodes/sh-script.el index 35b555e6879..2a867bb3655 100644 --- a/lisp/progmodes/sh-script.el +++ b/lisp/progmodes/sh-script.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -419,44 +419,6 @@ name symbol." (define-abbrev-table 'sh-mode-abbrev-table ()) -;; I turned off this feature because it doesn't permit typing commands -;; in the usual way without help. -;;(defvar sh-abbrevs -;; '((csh sh-abbrevs shell -;; "switch" 'sh-case -;; "getopts" 'sh-while-getopts) - -;; (es sh-abbrevs shell -;; "function" 'sh-function) - -;; (ksh88 sh-abbrevs sh -;; "select" 'sh-select) - -;; (rc sh-abbrevs shell -;; "case" 'sh-case -;; "function" 'sh-function) - -;; (sh sh-abbrevs shell -;; "case" 'sh-case -;; "function" 'sh-function -;; "until" 'sh-until -;; "getopts" 'sh-while-getopts) - -;; ;; The next entry is only used for defining the others -;; (shell "for" sh-for -;; "loop" sh-indexed-loop -;; "if" sh-if -;; "tmpfile" sh-tmp-file -;; "while" sh-while) - -;; (zsh sh-abbrevs ksh88 -;; "repeat" 'sh-repeat)) -;; "Abbrev-table used in Shell-Script mode. See `sh-feature'. -;;;Due to the internal workings of abbrev tables, the shell name symbol is -;;;actually defined as the table for the like of \\[edit-abbrevs].") - - - (defun sh-mode-syntax-table (table &rest list) "Copy TABLE and set syntax for successive CHARs according to strings S." (setq table (copy-syntax-table table)) @@ -631,11 +593,7 @@ sign. See `sh-feature'." (sexp :format "Evaluate: %v")))) :group 'sh-script) - -(defcustom sh-indentation 4 - "The width for further indentation in Shell-Script mode." - :type 'integer - :group 'sh-script) +(define-obsolete-variable-alias 'sh-indentation 'sh-basic-offset "26.1") (put 'sh-indentation 'safe-local-variable 'integerp) (defcustom sh-remember-variable-min 3 @@ -747,9 +705,7 @@ removed when closing the here document." ;; The next entry is only used for defining the others (shell "cd" "echo" "eval" "set" "shift" "umask" "unset" "wait") - (wksh sh-append ksh88 - ;; FIXME: This looks too much like a regexp. --Stef - "Xt[A-Z][A-Za-z]*") + (wksh sh-append ksh88) (zsh sh-append ksh88 "autoload" "bindkey" "builtin" "chdir" "compctl" "declare" "dirs" @@ -1178,7 +1134,7 @@ subshells can nest." (syntax-propertize-rules (sh-here-doc-open-re (2 (sh-font-lock-open-heredoc - (match-beginning 0) (match-string 1) (match-beginning 2)))) + (1+ (match-beginning 0)) (match-string 1) (match-beginning 2)))) ("\\s|" (0 (prog1 nil (sh-syntax-propertize-here-doc end)))) ;; A `#' begins a comment when it is unquoted and at the ;; beginning of a word. In the shell, words are separated by @@ -1657,7 +1613,7 @@ with your script for an edit-interpret-debug cycle." (setq-local skeleton-pair-alist '((?` _ ?`))) (setq-local skeleton-pair-filter-function 'sh-quoted-p) (setq-local skeleton-further-elements - '((< '(- (min sh-indentation (current-column)))))) + '((< '(- (min sh-basic-offset (current-column)))))) (setq-local skeleton-filter-function 'sh-feature) (setq-local skeleton-newline-indent-rigidly t) (setq-local defun-prompt-regexp @@ -1683,6 +1639,7 @@ with your script for an edit-interpret-debug cycle." ((string-match "[.]sh\\>" buffer-file-name) "sh") ((string-match "[.]bash\\>" buffer-file-name) "bash") ((string-match "[.]ksh\\>" buffer-file-name) "ksh") + ((string-match "[.]mkshrc\\>" buffer-file-name) "mksh") ((string-match "[.]t?csh\\(rc\\)?\\>" buffer-file-name) "csh") ((string-match "[.]zsh\\(rc\\|env\\)?\\>" buffer-file-name) "zsh") ((equal (file-name-nondirectory buffer-file-name) ".profile") "sh") @@ -2051,7 +2008,7 @@ May return nil if the line should not be treated as continued." (forward-line -1) (if (sh-smie--looking-back-at-continuation-p) (current-indentation) - (+ (current-indentation) sh-indentation)))) + (+ (current-indentation) sh-basic-offset)))) (t ;; Just make sure a line-continuation is indented deeper. (save-excursion @@ -2072,13 +2029,13 @@ May return nil if the line should not be treated as continued." ;; check the line before that one. (> ci indent)) (t ;Previous line is the beginning of the continued line. - (setq indent (min (+ ci sh-indentation) max)) + (setq indent (min (+ ci sh-basic-offset) max)) nil))))) indent)))))) (defun sh-smie-sh-rules (kind token) (pcase (cons kind token) - (`(:elem . basic) sh-indentation) + (`(:elem . basic) sh-basic-offset) (`(:after . "case-)") (- (sh-var-value 'sh-indent-for-case-alt) (sh-var-value 'sh-indent-for-case-label))) (`(:before . ,(or `"(" `"{" `"[" "while" "if" "for" "case")) @@ -2287,8 +2244,8 @@ Point should be before the newline." (defun sh-smie-rc-rules (kind token) (pcase (cons kind token) - (`(:elem . basic) sh-indentation) - ;; (`(:after . "case") (or sh-indentation smie-indent-basic)) + (`(:elem . basic) sh-basic-offset) + ;; (`(:after . "case") (or sh-basic-offset smie-indent-basic)) (`(:after . ";") (if (smie-rule-parent-p "case") (smie-rule-parent (sh-var-value 'sh-indent-after-case)))) @@ -2511,39 +2468,6 @@ the value thus obtained, and the result is used instead." -;; I commented this out because nobody calls it -- rms. -;;(defun sh-abbrevs (ancestor &rest list) -;; "If it isn't, define the current shell as abbrev table and fill that. -;;Abbrev table will inherit all abbrevs from ANCESTOR, which is either an abbrev -;;table or a list of (NAME1 EXPANSION1 ...). In addition it will define abbrevs -;;according to the remaining arguments NAMEi EXPANSIONi ... -;;EXPANSION may be either a string or a skeleton command." -;; (or (if (boundp sh-shell) -;; (symbol-value sh-shell)) -;; (progn -;; (if (listp ancestor) -;; (nconc list ancestor)) -;; (define-abbrev-table sh-shell ()) -;; (if (vectorp ancestor) -;; (mapatoms (lambda (atom) -;; (or (eq atom 0) -;; (define-abbrev (symbol-value sh-shell) -;; (symbol-name atom) -;; (symbol-value atom) -;; (symbol-function atom)))) -;; ancestor)) -;; (while list -;; (define-abbrev (symbol-value sh-shell) -;; (car list) -;; (if (stringp (car (cdr list))) -;; (car (cdr list)) -;; "") -;; (if (symbolp (car (cdr list))) -;; (car (cdr list)))) -;; (setq list (cdr (cdr list))))) -;; (symbol-value sh-shell))) - - (defun sh-append (ancestor &rest list) "Return list composed of first argument (a list) physically appended to rest." (nconc list ancestor)) @@ -2562,7 +2486,7 @@ the value thus obtained, and the result is used instead." (defun sh-basic-indent-line () "Indent a line for Sh mode (shell script mode). -Indent as far as preceding non-empty line, then by steps of `sh-indentation'. +Indent as far as preceding non-empty line, then by steps of `sh-basic-offset'. Lines containing only comments are considered empty." (interactive) (let ((previous (save-excursion @@ -2586,9 +2510,9 @@ Lines containing only comments are considered empty." (delete-region (point) (progn (beginning-of-line) (point))) (if (eolp) - (max previous (* (1+ (/ current sh-indentation)) - sh-indentation)) - (* (1+ (/ current sh-indentation)) sh-indentation)))))) + (max previous (* (1+ (/ current sh-basic-offset)) + sh-basic-offset)) + (* (1+ (/ current sh-basic-offset)) sh-basic-offset)))))) (if (< (current-column) (current-indentation)) (skip-chars-forward " \t")))) @@ -3452,7 +3376,7 @@ If INFO is supplied it is used, else it is calculated from current line." (if msg (message "%s" msg) (message nil)))) (defun sh-show-indent (arg) - "Show the how the current line would be indented. + "Show how the current line would be indented. This tells you which variable, if any, controls the indentation of this line. If optional arg ARG is non-null (called interactively with a prefix), @@ -3666,6 +3590,10 @@ so that `occur-next' and `occur-prev' will work." (defun sh-learn-buffer-indent (&optional arg) "Learn how to indent the buffer the way it currently is. +If `sh-use-smie' is non-nil, call `smie-config-guess'. +Otherwise, run the sh-script specific indent learning command, as +described below. + Output in buffer \"*indent*\" shows any lines which have conflicting values of a variable, and the final value of all variables learned. When called interactively, pop to this buffer automatically if @@ -3682,8 +3610,7 @@ to the value of variable `sh-learn-basic-offset'. Abnormal hook `sh-learned-buffer-hook' if non-nil is called when the function completes. The function is abnormal because it is called -with an alist of variables learned. This feature may be changed or -removed in the future. +with an alist of variables learned. This command can often take a long time to run." (interactive "P") @@ -3881,7 +3808,6 @@ This command can often take a long time to run." " has" "s have") (if (zerop num-diffs) "." ":")))))) - ;; Are abnormal hooks considered bad form? (run-hook-with-args 'sh-learned-buffer-hook learned-var-list) (and (called-interactively-p 'any) (or sh-popup-occur-buffer (> num-diffs 0)) diff --git a/lisp/progmodes/simula.el b/lisp/progmodes/simula.el index 505a2ea43c0..6f98d68d047 100644 --- a/lisp/progmodes/simula.el +++ b/lisp/progmodes/simula.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/progmodes/sql.el b/lisp/progmodes/sql.el index 68ca37207ef..db88563a3e7 100644 --- a/lisp/progmodes/sql.el +++ b/lisp/progmodes/sql.el @@ -4,9 +4,9 @@ ;; Author: Alex Schroeder <alex@gnu.org> ;; Maintainer: Michael Mauger <michael@mauger.com> -;; Version: 3.5 +;; Version: 3.6 ;; Keywords: comm languages processes -;; URL: http://savannah.gnu.org/projects/emacs/ +;; URL: https://savannah.gnu.org/projects/emacs/ ;; This file is part of GNU Emacs. @@ -21,14 +21,14 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; Please send bug reports and bug fixes to the mailing list at ;; help-gnu-emacs@gnu.org. If you want to subscribe to the mailing ;; list, see the web page at -;; http://lists.gnu.org/mailman/listinfo/help-gnu-emacs for +;; https://lists.gnu.org/mailman/listinfo/help-gnu-emacs for ;; instructions. I monitor this list actively. If you send an e-mail ;; to Alex Schroeder it usually makes it to me when Alex has a chance ;; to forward them along (Thanks, Alex). @@ -156,7 +156,7 @@ ;; (sql-set-product-feature 'xyz ;; :sqli-options 'my-sql-xyz-options)) -;; (defun my-sql-comint-xyz (product options) +;; (defun my-sql-comint-xyz (product options &optional buf-name) ;; "Connect ti XyzDB in a comint buffer." ;; ;; ;; Do something with `sql-user', `sql-password', @@ -172,7 +172,7 @@ ;; (if (not (string= "" sql-server)) ;; (list "-S" sql-server)) ;; options))) -;; (sql-comint product params))) +;; (sql-comint product params buf-name))) ;; ;; (sql-set-product-feature 'xyz ;; :sqli-comint-func 'my-sql-comint-xyz) @@ -220,6 +220,7 @@ ;; incorrectly enabled by default ;; Roman Scherer <roman.scherer@nugg.ad> -- Connection documentation ;; Mark Wilkinson <wilkinsonmr@gmail.com> -- file-local variables ignored +;; Simen Heggestøyl <simenheg@gmail.com> -- Postgres database completion ;; @@ -317,6 +318,7 @@ file. Since that is a plaintext file, this could be dangerous." (list :tag "completion" (const :format "" server) (const :format "" :completion) + (const :format "" :must-match) (restricted-sexp :match-alternatives (listp stringp)))) (choice :tag "database" @@ -332,9 +334,10 @@ file. Since that is a plaintext file, this could be dangerous." regexp) (list :tag "completion" (const :format "" database) - (const :format "" :completion) - (restricted-sexp - :match-alternatives (listp stringp)))) + (const :format "" :completion) + (const :format "" :must-match) + (restricted-sexp + :match-alternatives (listp stringp)))) (const port))) ;; SQL Product support @@ -936,7 +939,8 @@ Starts `sql-interactive-mode' after doing some setup." :version "20.8" :group 'SQL) -(defcustom sql-sqlite-login-params '((database :file nil)) +(defcustom sql-sqlite-login-params '((database :file nil + :must-match confirm)) "List of login parameters needed to connect to SQLite." :type 'sql-login-params :version "26.1" @@ -1079,7 +1083,8 @@ add your name with a \"-U\" prefix (such as \"-Umark\") to the list." `((user :default ,(user-login-name)) (database :default ,(user-login-name) :completion ,(completion-table-dynamic - (lambda (_) (sql-postgres-list-databases)))) + (lambda (_) (sql-postgres-list-databases))) + :must-match confirm) server) "List of login parameters needed to connect to Postgres." :type 'sql-login-params @@ -1090,9 +1095,10 @@ add your name with a \"-U\" prefix (such as \"-Umark\") to the list." "Return a list of available PostgreSQL databases." (when (executable-find sql-postgres-program) (let ((res '())) - (dolist (row (process-lines sql-postgres-program "-ltX")) - (when (string-match "^ \\([[:alnum:]-_]+\\) +|.*" row) - (push (match-string 1 row) res))) + (ignore-errors + (dolist (row (process-lines sql-postgres-program "-ltX")) + (when (string-match "^ \\([[:alnum:]-_]+\\) +|.*" row) + (push (match-string 1 row) res)))) (nreverse res)))) ;; Customization for Interbase @@ -2957,7 +2963,9 @@ value. (The property value is used as the PREDICATE argument to ((plist-member plist :file) (let ((file-name (read-file-name prompt - (file-name-directory last-value) default 'confirm + (file-name-directory last-value) + default + (plist-get plist :must-match) (file-name-nondirectory last-value) (when (plist-get plist :file) `(lambda (f) @@ -2971,8 +2979,13 @@ value. (The property value is used as the PREDICATE argument to (expand-file-name file-name)))) ((plist-member plist :completion) - (completing-read prompt-def (plist-get plist :completion) nil t - last-value history-var default)) + (completing-read prompt-def + (plist-get plist :completion) + nil + (plist-get plist :must-match) + last-value + history-var + default)) ((plist-get plist :number) (read-number prompt (or default last-value 0))) @@ -4034,7 +4047,7 @@ Sentinels will always get the two parameters PROCESS and EVENT." nil t initial 'sql-connection-history default))) ;;;###autoload -(defun sql-connect (connection &optional new-name) +(defun sql-connect (connection &optional buf-name) "Connect to an interactive session using CONNECTION settings. See `sql-connection-alist' to see how to define connections and @@ -4046,7 +4059,7 @@ is specified in the connection settings." ;; Prompt for the connection from those defined in the alist (interactive (if sql-connection-alist - (list (sql-read-connection "Connection: " nil '(nil)) + (list (sql-read-connection "Connection: ") current-prefix-arg) (user-error "No SQL Connections defined"))) @@ -4055,16 +4068,16 @@ is specified in the connection settings." ;; Was one selected (when connection ;; Get connection settings - (let ((connect-set (assoc-string connection sql-connection-alist t))) + (let ((connect-set (cdr (assoc-string connection sql-connection-alist t)))) ;; Settings are defined (if connect-set ;; Set the desired parameters - (let (param-var login-params set-params rem-params) + (let (param-var login-params set-vars rem-vars) ;; Set the parameters and start the interactive session - (mapc - (lambda (vv) - (set-default (car vv) (eval (cadr vv)))) - (cdr connect-set)) + (dolist (vv connect-set) + (let ((var (car vv)) + (val (cadr vv))) + (set-default var (eval val)))) (setq-default sql-connection connection) ;; :sqli-login params variable @@ -4072,32 +4085,33 @@ is specified in the connection settings." (sql-get-product-feature sql-product :sqli-login nil t)) ;; :sqli-login params value - (setq login-params - (sql-get-product-feature sql-product :sqli-login)) + (setq login-params (symbol-value param-var)) - ;; Params in the connection - (setq set-params + ;; Params set in the connection + (setq set-vars (mapcar (lambda (v) - (pcase (car v) - (`sql-user 'user) - (`sql-password 'password) - (`sql-server 'server) - (`sql-database 'database) - (`sql-port 'port) - (s s))) - (cdr connect-set))) + (pcase (car v) + (`sql-user 'user) + (`sql-password 'password) + (`sql-server 'server) + (`sql-database 'database) + (`sql-port 'port) + (s s))) + connect-set)) ;; the remaining params (w/o the connection params) - (setq rem-params + (setq rem-vars (sql-for-each-login login-params - (lambda (token plist) - (unless (member token set-params) - (if plist (cons token plist) token))))) + (lambda (var vals) + (unless (member var set-vars) + (if vals (cons var vals) var))))) ;; Start the SQLi session with revised list of login parameters - (eval `(let ((,param-var ',rem-params)) - (sql-product-interactive ',sql-product ',new-name)))) + (eval `(let ((,param-var ',rem-vars)) + (sql-product-interactive + ',sql-product + ',(or buf-name (format "<%s>" connection)))))) (user-error "SQL Connection <%s> does not exist" connection) nil))) @@ -4241,7 +4255,10 @@ the call to \\[sql-product-interactive] with default-directory))) (funcall (sql-get-product-feature product :sqli-comint-func) product - (sql-get-product-feature product :sqli-options))) + (sql-get-product-feature product :sqli-options) + (if (and new-name (string-prefix-p "SQL" new-name t)) + new-name + (concat "SQL: " new-name)))) ;; Set SQLi mode. (let ((sql-interactive-product product)) @@ -4249,8 +4266,6 @@ the call to \\[sql-product-interactive] with ;; Set the new buffer name (setq new-sqli-buffer (current-buffer)) - (when new-name - (sql-rename-buffer new-name)) (set (make-local-variable 'sql-buffer) (buffer-name new-sqli-buffer)) @@ -4284,29 +4299,41 @@ the call to \\[sql-product-interactive] with (current-buffer))))) (user-error "No default SQL product defined. Set `sql-product'."))) -(defun sql-comint (product params) +(defun sql-comint (product params &optional buf-name) "Set up a comint buffer to run the SQL processor. PRODUCT is the SQL product. PARAMS is a list of strings which are -passed as command line arguments." - (let ((program (sql-get-product-feature product :sqli-program)) - (buf-name "SQL")) +passed as command line arguments. BUF-NAME is the name of the new +buffer. If nil, a name is chosen for it." + + (let ((program (sql-get-product-feature product :sqli-program))) ;; Make sure we can find the program. `executable-find' does not ;; work for remote hosts; we suppress the check there. (unless (or (file-remote-p default-directory) (executable-find program)) (error "Unable to locate SQL program `%s'" program)) + ;; Make sure buffer name is unique. - (when (sql-buffer-live-p (format "*%s*" buf-name)) - (setq buf-name (format "SQL-%s" product)) - (when (sql-buffer-live-p (format "*%s*" buf-name)) - (let ((i 1)) - (while (sql-buffer-live-p - (format "*%s*" - (setq buf-name (format "SQL-%s%d" product i)))) - (setq i (1+ i)))))) - (set-buffer - (apply #'make-comint buf-name program nil params)))) + ;; if not specified, try *SQL* then *SQL-product*, then *SQL-product1*, ... + ;; otherwise, use *buf-name* + (if buf-name + (unless (string-match-p "\\`[*].*[*]\\'" buf-name) + (setq buf-name (concat "*" buf-name "*"))) + (setq buf-name "*SQL*") + (when (sql-buffer-live-p buf-name) + (setq buf-name (format "*SQL-%s*" product))) + (let ((i 1)) + (while (sql-buffer-live-p buf-name) + (setq buf-name (format "*SQL-%s%d*" product i) + i (1+ i))))) + (set-text-properties 0 (length buf-name) nil buf-name) + + ;; Start the command interpreter in the buffer + ;; PROC-NAME is BUF-NAME without enclosing asterisks + (let ((proc-name (replace-regexp-in-string "\\`[*]\\(.*\\)[*]\\'" "\\1" buf-name))) + (set-buffer + (apply #'make-comint-in-buffer + proc-name buf-name program nil params))))) ;;;###autoload (defun sql-oracle (&optional buffer) @@ -4340,7 +4367,7 @@ The default comes from `process-coding-system-alist' and (interactive "P") (sql-product-interactive 'oracle buffer)) -(defun sql-comint-oracle (product options) +(defun sql-comint-oracle (product options &optional buf-name) "Create comint buffer and connect to Oracle." ;; Produce user/password@database construct. Password without user ;; is meaningless; database without user/password is meaningless, @@ -4357,7 +4384,7 @@ The default comes from `process-coding-system-alist' and (if parameter (setq parameter (append options (list parameter))) (setq parameter options)) - (sql-comint product parameter) + (sql-comint product parameter buf-name) ;; Set process coding system to agree with the interpreter (setq nlslang (or (getenv "NLS_LANG") "") coding (dolist (cs @@ -4454,20 +4481,25 @@ The default comes from `process-coding-system-alist' and ;; Restore the changed settings (sql-redirect sqlbuf saved-settings)) +(defun sql-oracle--list-object-name (obj-name) + (format "CASE WHEN REGEXP_LIKE (%s, q'/^[A-Z0-9_#$]+$/','c') THEN %s ELSE '\"'|| %s ||'\"' END " + obj-name obj-name obj-name)) + (defun sql-oracle-list-all (sqlbuf outbuf enhanced _table-name) ;; Query from USER_OBJECTS or ALL_OBJECTS (let ((settings (sql-oracle-save-settings sqlbuf)) (simple-sql (concat "SELECT INITCAP(x.object_type) AS SQL_EL_TYPE " - ", x.object_name AS SQL_EL_NAME " + ", " (sql-oracle--list-object-name "x.object_name") " AS SQL_EL_NAME " "FROM user_objects x " "WHERE x.object_type NOT LIKE '%% BODY' " "ORDER BY 2, 1;")) (enhanced-sql (concat "SELECT INITCAP(x.object_type) AS SQL_EL_TYPE " - ", x.owner ||'.'|| x.object_name AS SQL_EL_NAME " + ", " (sql-oracle--list-object-name "x.owner") + " ||'.'|| " (sql-oracle--list-object-name "x.object_name") " AS SQL_EL_NAME " "FROM all_objects x " "WHERE x.object_type NOT LIKE '%% BODY' " "AND x.owner <> 'SYS' " @@ -4524,9 +4556,15 @@ See the distinct values in ALL_OBJECTS.OBJECT_TYPE for possible values." (concat "SELECT CHR(1)||" (if schema - (format "owner||'.'||object_name AS o FROM all_objects WHERE owner = %s AND " - (sql-str-literal (upcase schema))) - "object_name AS o FROM user_objects WHERE ") + (concat "CASE WHEN REGEXP_LIKE (owner, q'/^[A-Z0-9_#$]+$/','c') THEN owner ELSE '\"'|| owner ||'\"' END " + "||'.'||" + "CASE WHEN REGEXP_LIKE (object_name, q'/^[A-Z0-9_#$]+$/','c') THEN object_name ELSE '\"'|| object_name ||'\"' END " + " AS o FROM all_objects " + (format "WHERE owner = %s AND " + (sql-str-literal (if (string-match "^[\"]\\(.+\\)[\"]$" schema) + (match-string 1 schema) (upcase schema))))) + (concat "CASE WHEN REGEXP_LIKE (object_name, q'/^[A-Z0-9_#$]+$/','c') THEN object_name ELSE '\"'|| object_name ||'\"' END " + " AS o FROM user_objects WHERE ")) "temporary = 'N' AND generated = 'N' AND secondary = 'N' AND " "object_type IN (" (mapconcat (function sql-str-literal) sql-oracle-completion-types ",") @@ -4566,7 +4604,7 @@ The default comes from `process-coding-system-alist' and (interactive "P") (sql-product-interactive 'sybase buffer)) -(defun sql-comint-sybase (product options) +(defun sql-comint-sybase (product options &optional buf-name) "Create comint buffer and connect to Sybase." ;; Put all parameters to the program (if defined) in a list and call ;; make-comint. @@ -4581,7 +4619,7 @@ The default comes from `process-coding-system-alist' and (if (not (string= "" sql-server)) (list "-S" sql-server)) options))) - (sql-comint product params))) + (sql-comint product params buf-name))) @@ -4615,7 +4653,7 @@ The default comes from `process-coding-system-alist' and (interactive "P") (sql-product-interactive 'informix buffer)) -(defun sql-comint-informix (product options) +(defun sql-comint-informix (product options &optional buf-name) "Create comint buffer and connect to Informix." ;; username and password are ignored. (let ((db (if (string= "" sql-database) @@ -4623,7 +4661,7 @@ The default comes from `process-coding-system-alist' and (if (string= "" sql-server) sql-database (concat sql-database "@" sql-server))))) - (sql-comint product (append `(,db "-") options)))) + (sql-comint product (append `(,db "-") options) buf-name))) @@ -4661,7 +4699,7 @@ The default comes from `process-coding-system-alist' and (interactive "P") (sql-product-interactive 'sqlite buffer)) -(defun sql-comint-sqlite (product options) +(defun sql-comint-sqlite (product options &optional buf-name) "Create comint buffer and connect to SQLite." ;; Put all parameters to the program (if defined) in a list and call ;; make-comint. @@ -4669,7 +4707,7 @@ The default comes from `process-coding-system-alist' and (append options (if (not (string= "" sql-database)) `(,(expand-file-name sql-database)))))) - (sql-comint product params))) + (sql-comint product params buf-name))) (defun sql-sqlite-completion-object (sqlbuf _schema) (sql-redirect-value sqlbuf ".tables" "\\sw\\(?:\\sw\\|\\s_\\)*" 0)) @@ -4710,7 +4748,7 @@ The default comes from `process-coding-system-alist' and (interactive "P") (sql-product-interactive 'mysql buffer)) -(defun sql-comint-mysql (product options) +(defun sql-comint-mysql (product options &optional buf-name) "Create comint buffer and connect to MySQL." ;; Put all parameters to the program (if defined) in a list and call ;; make-comint. @@ -4727,7 +4765,7 @@ The default comes from `process-coding-system-alist' and (list (concat "--host=" sql-server))) (if (not (string= "" sql-database)) (list sql-database))))) - (sql-comint product params))) + (sql-comint product params buf-name))) @@ -4762,7 +4800,7 @@ The default comes from `process-coding-system-alist' and (interactive "P") (sql-product-interactive 'solid buffer)) -(defun sql-comint-solid (product options) +(defun sql-comint-solid (product options &optional buf-name) "Create comint buffer and connect to Solid." ;; Put all parameters to the program (if defined) in a list and call ;; make-comint. @@ -4775,7 +4813,7 @@ The default comes from `process-coding-system-alist' and (string= "" sql-password))) (list sql-user sql-password)) options))) - (sql-comint product params))) + (sql-comint product params buf-name))) @@ -4809,14 +4847,15 @@ The default comes from `process-coding-system-alist' and (interactive "P") (sql-product-interactive 'ingres buffer)) -(defun sql-comint-ingres (product options) +(defun sql-comint-ingres (product options &optional buf-name) "Create comint buffer and connect to Ingres." ;; username and password are ignored. (sql-comint product - (append (if (string= "" sql-database) - nil - (list sql-database)) - options))) + (append (if (string= "" sql-database) + nil + (list sql-database)) + options) + buf-name)) @@ -4852,7 +4891,7 @@ The default comes from `process-coding-system-alist' and (interactive "P") (sql-product-interactive 'ms buffer)) -(defun sql-comint-ms (product options) +(defun sql-comint-ms (product options &optional buf-name) "Create comint buffer and connect to Microsoft SQL Server." ;; Put all parameters to the program (if defined) in a list and call ;; make-comint. @@ -4875,7 +4914,7 @@ The default comes from `process-coding-system-alist' and ;; If -P is passed to ISQL as the last argument without a ;; password, it's considered null. `(,@params "-P")))) - (sql-comint product params))) + (sql-comint product params buf-name))) @@ -4916,7 +4955,7 @@ Try to set `comint-output-filter-functions' like this: (interactive "P") (sql-product-interactive 'postgres buffer)) -(defun sql-comint-postgres (product options) +(defun sql-comint-postgres (product options &optional buf-name) "Create comint buffer and connect to Postgres." ;; username and password are ignored. Mark Stosberg suggests to add ;; the database at the end. Jason Beegan suggests using --pset and @@ -4934,7 +4973,7 @@ Try to set `comint-output-filter-functions' like this: options (if (not (string= "" sql-database)) (list sql-database))))) - (sql-comint product params))) + (sql-comint product params buf-name))) (defun sql-postgres-completion-object (sqlbuf schema) (sql-redirect sqlbuf "\\t on") @@ -5004,7 +5043,7 @@ The default comes from `process-coding-system-alist' and (interactive "P") (sql-product-interactive 'interbase buffer)) -(defun sql-comint-interbase (product options) +(defun sql-comint-interbase (product options &optional buf-name) "Create comint buffer and connect to Interbase." ;; Put all parameters to the program (if defined) in a list and call ;; make-comint. @@ -5017,7 +5056,7 @@ The default comes from `process-coding-system-alist' and (if (not (string= "" sql-user)) (list "-u" sql-user)) options))) - (sql-comint product params))) + (sql-comint product params buf-name))) @@ -5056,11 +5095,11 @@ The default comes from `process-coding-system-alist' and (interactive "P") (sql-product-interactive 'db2 buffer)) -(defun sql-comint-db2 (product options) +(defun sql-comint-db2 (product options &optional buf-name) "Create comint buffer and connect to DB2." ;; Put all parameters to the program (if defined) in a list and call ;; make-comint. - (sql-comint product options)) + (sql-comint product options buf-name)) ;;;###autoload (defun sql-linter (&optional buffer) @@ -5094,7 +5133,7 @@ buffer. (interactive "P") (sql-product-interactive 'linter buffer)) -(defun sql-comint-linter (product options) +(defun sql-comint-linter (product options &optional buf-name) "Create comint buffer and connect to Linter." ;; Put all parameters to the program (if defined) in a list and call ;; make-comint. @@ -5109,7 +5148,7 @@ buffer. options))) (cl-letf (((getenv "LINTER_MBX") (unless (string= "" sql-database) sql-database))) - (sql-comint product params)))) + (sql-comint product params buf-name)))) @@ -5132,7 +5171,7 @@ The default value disables the internal pager." :type 'sql-login-params :group 'SQL) -(defun sql-comint-vertica (product options) +(defun sql-comint-vertica (product options &optional buf-name) "Create comint buffer and connect to Vertica." (sql-comint product (nconc @@ -5144,7 +5183,8 @@ The default value disables the internal pager." (list "-w" sql-password)) (and (not (string= "" sql-user)) (list "-U" sql-user)) - options))) + options) + buf-name)) ;;;###autoload (defun sql-vertica (&optional buffer) diff --git a/lisp/progmodes/subword.el b/lisp/progmodes/subword.el index ede2f420735..6428b56f9dc 100644 --- a/lisp/progmodes/subword.el +++ b/lisp/progmodes/subword.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/progmodes/tcl.el b/lisp/progmodes/tcl.el index 902a5aace08..dbb71efdfb4 100644 --- a/lisp/progmodes/tcl.el +++ b/lisp/progmodes/tcl.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; BEFORE USE: ;; @@ -353,8 +353,6 @@ information): Quotes all \"#\" characters that don't correspond to actual Tcl comments. (Useful when editing code not originally created with this mode). - `tcl-auto-fill-mode' - Auto-filling of Tcl comments. Add functions to the hook with `add-hook': @@ -1413,6 +1411,9 @@ Prefix argument means switch to the Tcl buffer afterwards." (defun tcl-auto-fill-mode (&optional arg) "Like `auto-fill-mode', but sets `comment-auto-fill-only-comments'." + (declare + (obsolete + "Use `auto-fill-mode' with `comment-auto-fill-only-comments'." "26.1")) (interactive "P") (auto-fill-mode arg) (if auto-fill-function diff --git a/lisp/progmodes/vera-mode.el b/lisp/progmodes/vera-mode.el index 066360023d7..05d1a5f5f31 100644 --- a/lisp/progmodes/vera-mode.el +++ b/lisp/progmodes/vera-mode.el @@ -32,7 +32,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Commentary: diff --git a/lisp/progmodes/verilog-mode.el b/lisp/progmodes/verilog-mode.el index 2760c4d276e..e2bd89ec46c 100644 --- a/lisp/progmodes/verilog-mode.el +++ b/lisp/progmodes/verilog-mode.el @@ -4,7 +4,6 @@ ;; Author: Michael McNamara <mac@verilog.com> ;; Wilson Snyder <wsnyder@wsnyder.org> -;; X-URL: http://www.verilog.com ;; X-URL: http://www.veripool.org ;; Created: 3 Jan 1996 ;; Keywords: languages @@ -33,7 +32,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -70,7 +69,7 @@ ;; default. ;; You can get step by step help in installing this file by going to -;; <http://www.verilog.com/emacs_install.html> +;; <http://www.veripool.com/verilog-mode> ;; The short list of installation instructions are: To set up ;; automatic Verilog mode, put this file in your load path, and put @@ -123,7 +122,7 @@ ;; ;; This variable will always hold the version number of the mode -(defconst verilog-mode-version "2017-05-08-b240c8f-vpo-GNU" +(defconst verilog-mode-version "2017-08-07-c085e50-vpo-GNU" "Version of this Verilog mode.") (defconst verilog-mode-release-emacs t "If non-nil, this version of Verilog mode was released with Emacs itself.") @@ -285,7 +284,7 @@ STRING should be given if the last search was by `string-match' on STRING." ;; This function is lifted directly from emacs's subr.el ;; so that it can be used by xemacs. ;; The idea for this was borrowed from org-mode via this link: -;; https://lists.gnu.org/archive/html/emacs-orgmode/2009-12/msg00032.html +;; https://lists.gnu.org/r/emacs-orgmode/2009-12/msg00032.html (eval-and-compile (cond ((fboundp 'looking-back) @@ -345,6 +344,12 @@ wherever possible, since it is slow." (unless (fboundp 'buffer-chars-modified-tick) ; Emacs 22 added (defmacro buffer-chars-modified-tick () (buffer-modified-tick))) (error nil)) + ;; Added in Emacs 23.1 + (condition-case nil + (unless (fboundp 'ignore-errors) + (defmacro ignore-errors (&rest body) + (declare (debug t) (indent 0)) + `(condition-case nil (progn ,@body) (error nil))))) ;; Added in Emacs 24.1 (condition-case nil (unless (fboundp 'prog-mode) @@ -961,7 +966,8 @@ Only used in XEmacs; GNU Emacs uses `verilog-error-regexp-emacs-alist'.") These arguments are used to find files for `verilog-auto', and match the flags accepted by a standard Verilog-XL simulator. - -f filename Reads more `verilog-library-flags' from the filename. + -f filename Reads absolute `verilog-library-flags' from the filename. + -F filename Reads relative `verilog-library-flags' from the filename. +incdir+dir Adds the directory to `verilog-library-directories'. -Idir Adds the directory to `verilog-library-directories'. -y dir Adds the directory to `verilog-library-directories'. @@ -4034,7 +4040,7 @@ With optional ARG, remove existing end of line comments." (progn (if (or (eq 'all verilog-auto-lineup) (eq 'assignments verilog-auto-lineup)) - (verilog-pretty-expr t "\\(<\\|:\\)?=" )) + (verilog-pretty-expr :quiet)) (newline)) (forward-line 1)) ;; Indent next line @@ -5790,11 +5796,9 @@ Return a list of two elements: (INDENT-TYPE INDENT-LEVEL)." (goto-char here) ; or is clocking, starts a new block (throw 'nesting 'block))))) - ;; need to consider typedef struct here... ((looking-at "\\<class\\|struct\\|function\\|task\\>") ;; *sigh* These words have an optional prefix: ;; extern {virtual|protected}? function a(); - ;; typedef class foo; ;; and we don't want to confuse this with ;; function a(); ;; property @@ -5804,7 +5808,11 @@ Return a list of two elements: (INDENT-TYPE INDENT-LEVEL)." (cond ((looking-at verilog-dpi-import-export-re) (throw 'continue 'foo)) - ((looking-at "\\<pure\\>\\s-+\\<virtual\\>\\s-+\\(?:\\<\\(local\\|protected\\|static\\)\\>\\s-+\\)?\\<\\(function\\|task\\)\\>\\s-+") + ((or + (looking-at "\\<pure\\>\\s-+\\<virtual\\>\\s-+\\(?:\\<\\(local\\|protected\\|static\\)\\>\\s-+\\)?\\<\\(function\\|task\\)\\>\\s-+") + ;; Do not throw 'defun for class typedefs like + ;; typedef class foo; + (looking-at "\\<typedef\\>\\s-+\\(?:\\<virtual\\>\\s-+\\)?\\<class\\>\\s-+")) (throw 'nesting 'statement)) ((looking-at verilog-beg-block-re-ordered) (throw 'nesting 'block)) @@ -6660,7 +6668,7 @@ Only look at a few lines to determine indent level." (let ((val)) (verilog-beg-of-statement-1) (if (and (< (point) here) - (verilog-re-search-forward "=[ \\t]*" here 'move) + (verilog-re-search-forward "=[ \t]*" here 'move) ;; not at a |=>, #=#, or [=n] operator (not (string-match "\\[=.\\|#=#\\||=>" (or (buffer-substring (- (point) 2) (1+ (point))) @@ -6974,106 +6982,97 @@ Be verbose about progress unless optional QUIET set." (forward-line 1)) (unless quiet (message ""))))))) -(defun verilog-pretty-expr (&optional quiet _myre) - "Line up expressions around point, optionally QUIET with regexp _MYRE ignored." +(defun verilog-pretty-expr (&optional quiet) + "Line up expressions around point. +If QUIET is non-nil, do not print messages showing the progress of line-up." (interactive) - (if (not (verilog-in-comment-or-string-p)) - (save-excursion - (let ( (rexp (concat "^\\s-*" verilog-complete-reg)) - (rexp1 (concat "^\\s-*" verilog-basic-complete-re))) - (beginning-of-line) - (if (and (not (looking-at rexp )) + (unless (verilog-in-comment-or-string-p) + (save-excursion + (let ((regexp (concat "^\\s-*" verilog-complete-reg)) + (regexp1 (concat "^\\s-*" verilog-basic-complete-re))) + (beginning-of-line) + (when (and (not (looking-at regexp)) (looking-at verilog-assignment-operation-re) (save-excursion (goto-char (match-end 2)) (and (not (verilog-in-attribute-p)) (not (verilog-in-parameter-p)) (not (verilog-in-comment-or-string-p))))) - (let* ((here (point)) - (e) (r) - (start - (progn - (beginning-of-line) - (setq e (point)) - (verilog-backward-syntactic-ws) - (beginning-of-line) - (while (and (not (looking-at rexp1)) - (looking-at verilog-assignment-operation-re) - (not (bobp)) - ) - (setq e (point)) - (verilog-backward-syntactic-ws) + (let* ((start (save-excursion ; BOL of the first line of the assignment block (beginning-of-line) - ) ;Ack, need to grok `define - e)) - (end - (progn - (goto-char here) + (let ((pt (point))) + (verilog-backward-syntactic-ws) + (beginning-of-line) + (while (and (not (looking-at regexp1)) + (looking-at verilog-assignment-operation-re) + (not (bobp))) + (setq pt (point)) + (verilog-backward-syntactic-ws) + (beginning-of-line)) ; Ack, need to grok `define + pt))) + (end (save-excursion ; EOL of the last line of the assignment block (end-of-line) - (setq e (point)) ;Might be on last line - (verilog-forward-syntactic-ws) - (beginning-of-line) - (while (and - (not (looking-at rexp1 )) - (looking-at verilog-assignment-operation-re) - (progn - (end-of-line) - (not (eq e (point))))) - (setq e (point)) + (let ((pt (point))) ; Might be on last line (verilog-forward-syntactic-ws) (beginning-of-line) - ) - e)) - (endpos (set-marker (make-marker) end)) - (ind) - ) - (goto-char start) - (verilog-do-indent (verilog-calculate-indent)) - (if (and (not quiet) - (> (- end start) 100)) - (message "Lining up expressions..(please stand by)")) - - ;; Set indent to minimum throughout region - (while (< (point) (marker-position endpos)) - (beginning-of-line) - (verilog-just-one-space verilog-assignment-operation-re) - (beginning-of-line) - (verilog-do-indent (verilog-calculate-indent)) - (end-of-line) - (verilog-forward-syntactic-ws) - ) - - ;; Now find biggest prefix - (setq ind (verilog-get-lineup-indent-2 verilog-assignment-operation-re start endpos)) - - ;; Now indent each line. - (goto-char start) - (while (progn (setq e (marker-position endpos)) - (setq r (- e (point))) - (> r 0)) - (setq e (point)) - (if (not quiet) (message "%d" r)) - (cond - ((looking-at verilog-assignment-operation-re) - (goto-char (match-beginning 2)) - (if (not (or (verilog-in-parenthesis-p) ; leave attributes and comparisons alone - (verilog-in-coverage-p))) - (if (eq (char-after) ?=) - (indent-to (1+ ind)) ; line up the = of the <= with surrounding = - (indent-to ind) - )) - ) - ((verilog-continued-line-1 start) - (goto-char e) - (indent-line-to ind)) - (t ; Must be comment or white space - (goto-char e) - (verilog-forward-ws&directives) - (forward-line -1)) - ) - (forward-line 1)) - (unless quiet (message "")) - )))))) + (while (and + (not (looking-at regexp1)) + (looking-at verilog-assignment-operation-re) + (progn + (end-of-line) + (not (eq pt (point))))) + (setq pt (point)) + (verilog-forward-syntactic-ws) + (beginning-of-line)) + pt))) + (contains-2-char-operator (string-match "<=" (buffer-substring-no-properties start end))) + (endmark (set-marker (make-marker) end))) + (goto-char start) + (verilog-do-indent (verilog-calculate-indent)) + (when (and (not quiet) + (> (- end start) 100)) + (message "Lining up expressions.. (please stand by)")) + + ;; Set indent to minimum throughout region + ;; Rely on mark rather than on point as the indentation changes can + ;; make the older point reference obsolete + (while (< (point) (marker-position endmark)) + (beginning-of-line) + (save-excursion + (verilog-just-one-space verilog-assignment-operation-re)) + (verilog-do-indent (verilog-calculate-indent)) + (end-of-line) + (verilog-forward-syntactic-ws)) + + (let ((ind (verilog-get-lineup-indent-2 verilog-assignment-operation-re start (marker-position endmark))) ; Find the biggest prefix + e) + ;; Now indent each line. + (goto-char start) + (while (progn + (setq e (marker-position endmark)) + (> e (point))) + (unless quiet + (message " verilog-pretty-expr: %d" (- e (point)))) + (setq e (point)) + (cond + ((looking-at verilog-assignment-operation-re) + (goto-char (match-beginning 2)) + (unless (or (verilog-in-parenthesis-p) ; Leave attributes and comparisons alone + (verilog-in-coverage-p)) + (if (and contains-2-char-operator + (eq (char-after) ?=)) + (indent-to (1+ ind)) ; Line up the = of the <= with surrounding = + (indent-to ind)))) + ((verilog-continued-line-1 start) + (goto-char e) + (indent-line-to ind)) + (t ; Must be comment or white space + (goto-char e) + (verilog-forward-ws&directives) + (forward-line -1))) + (forward-line 1)) + (unless quiet + (message ""))))))))) (defun verilog-just-one-space (myre) "Remove extra spaces around regular expression MYRE." @@ -7180,30 +7179,30 @@ Region is defined by B and EDPOS." ;;(skip-chars-backward " \t") (1+ (current-column)))))) -(defun verilog-get-lineup-indent-2 (myre b edpos) - "Return the indent level that will line up several lines within the region." +(defun verilog-get-lineup-indent-2 (regexp beg end) + "Return the indent level that will line up several lines. +The lineup string is searched using REGEXP within the region between points +BEG and END." (save-excursion - (let ((ind 0) e) - (goto-char b) + (let ((ind 0)) + (goto-char beg) ;; Get rightmost position - (while (progn (setq e (marker-position edpos)) - (< (point) e)) - (if (and (verilog-re-search-forward myre e 'move) - (not (verilog-in-attribute-p))) ; skip attribute exprs - (progn - (goto-char (match-beginning 2)) - (verilog-backward-syntactic-ws) - (if (> (current-column) ind) - (setq ind (current-column))) - (goto-char (match-end 0))) - )) - (if (> ind 0) - (1+ ind) - ;; No lineup-string found - (goto-char b) - (end-of-line) - (skip-chars-backward " \t") - (1+ (current-column)))))) + (while (< (point) end) + (when (and (verilog-re-search-forward regexp end 'move) + (not (verilog-in-attribute-p))) ; skip attribute exprs + (goto-char (match-beginning 2)) + (verilog-backward-syntactic-ws) + (if (> (current-column) ind) + (setq ind (current-column))) + (goto-char (match-end 0)))) + (setq ind (if (> ind 0) + (1+ ind) + ;; No lineup-string found + (goto-char beg) + (end-of-line) + (skip-chars-backward " \t") + (1+ (current-column)))) + ind))) (defun verilog-comment-depth (type val) "A useful mode debugging aide. TYPE and VAL are comments for insertion." @@ -9344,7 +9343,7 @@ Returns REGEXP and list of ( (signal_name connection_name)... )." ;; Regexp form?? ((looking-at ;; Regexp bug in XEmacs disallows ][ inside [], and wants + last - "\\s-*\\.\\(\\([a-zA-Z0-9`_$+@^.*?|---]+\\|[][]\\|\\\\[()|]\\)+\\)\\s-*(\\(.*\\))\\s-*\\(,\\|)\\s-*;\\)") + "\\s-*\\.\\(\\([a-zA-Z0-9`_$+@^.*?|---]\\|[][]\\|\\\\[()|]\\)+\\)\\s-*(\\(.*\\))\\s-*\\(,\\|)\\s-*;\\)") (setq rep (match-string-no-properties 3)) (goto-char (match-end 0)) (setq tpl-wild-list @@ -9619,8 +9618,9 @@ Some macros and such are also found and included. For dinotrace.el." ;; Argument file parsing ;; -(defun verilog-getopt (arglist) - "Parse -f, -v etc arguments in ARGLIST list or string." +(defun verilog-getopt (arglist &optional default-dir) + "Parse -f, -v etc arguments in ARGLIST list or string. +Use DEFAULT-DIR to anchor paths if non-nil." (unless (listp arglist) (setq arglist (list arglist))) (let ((space-args '()) arg next-param) @@ -9638,6 +9638,8 @@ Some macros and such are also found and included. For dinotrace.el." space-args (cdr space-args)) (cond ;; Need another arg + ((equal arg "-F") + (setq next-param arg)) ((equal arg "-f") (setq next-param arg)) ((equal arg "-v") @@ -9661,32 +9663,37 @@ Some macros and such are also found and included. For dinotrace.el." ((or (string-match "^\\+incdir\\+\\(.*\\)" arg) ; +incdir+dir (string-match "^-I\\(.*\\)" arg)) ; -Idir (verilog-add-list-unique `verilog-library-directories - (match-string 1 (substitute-in-file-name arg)))) + (substitute-in-file-name (match-string 1 arg)))) ;; Ignore ((equal "+librescan" arg)) ((string-match "^-U\\(.*\\)" arg)) ; -Udefine ;; Second parameters + ((equal next-param "-F") + (setq next-param nil) + (verilog-getopt-file (verilog-substitute-file-name-path arg default-dir) + (file-name-directory (verilog-substitute-file-name-path arg default-dir)))) ((equal next-param "-f") (setq next-param nil) - (verilog-getopt-file (substitute-in-file-name arg))) + (verilog-getopt-file (verilog-substitute-file-name-path arg default-dir) nil)) ((equal next-param "-v") (setq next-param nil) (verilog-add-list-unique `verilog-library-files - (substitute-in-file-name arg))) + (verilog-substitute-file-name-path arg default-dir))) ((equal next-param "-y") (setq next-param nil) (verilog-add-list-unique `verilog-library-directories - (substitute-in-file-name arg))) + (verilog-substitute-file-name-path arg default-dir))) ;; Filename ((string-match "^[^-+]" arg) (verilog-add-list-unique `verilog-library-files - (substitute-in-file-name arg))) + (verilog-substitute-file-name-path arg default-dir))) ;; Default - ignore; no warning )))) ;;(verilog-getopt (list "+libext+.a+.b" "+incdir+foodir" "+define+a+aval" "-f" "otherf" "-v" "library" "-y" "dir")) -(defun verilog-getopt-file (filename) - "Read Verilog options from the specified FILENAME." +(defun verilog-getopt-file (filename &optional default-dir) + "Read Verilog options from the specified FILENAME. +Use DEFAULT-DIR to anchor paths if non-nil." (save-excursion (let ((fns (verilog-library-filenames filename (buffer-file-name))) (orig-buffer (current-buffer)) @@ -9702,7 +9709,7 @@ Some macros and such are also found and included. For dinotrace.el." (when (string-match "//" line) (setq line (substring line 0 (match-beginning 0)))) (with-current-buffer orig-buffer ; Variables are buffer-local, so need right context. - (verilog-getopt line)))))) + (verilog-getopt line default-dir)))))) (defun verilog-getopt-flags () "Convert `verilog-library-flags' into standard library variables." @@ -9719,6 +9726,13 @@ Some macros and such are also found and included. For dinotrace.el." ;; Allow user to customize (verilog-run-hooks 'verilog-getopt-flags-hook)) +(defun verilog-substitute-file-name-path (filename default-dir) + "Return FILENAME with environment variables substituted. +Use DEFAULT-DIR to anchor paths if non-nil." + (if default-dir + (expand-file-name (substitute-in-file-name filename) default-dir) + (substitute-in-file-name filename))) + (defun verilog-add-list-unique (varref object) "Append to VARREF list the given OBJECT, unless it is already a member of the variable's list." @@ -9898,42 +9912,44 @@ Or, just the existing dirnames themselves if there are no wildcards." (interactive) (unless dirnames (error "`verilog-library-directories' should include at least `.'")) - (setq dirnames (reverse dirnames)) ; not nreverse - (let ((dirlist nil) - pattern dirfile dirfiles dirname root filename rest basefile) - (while dirnames - (setq dirname (substitute-in-file-name (car dirnames)) - dirnames (cdr dirnames)) - (cond ((string-match (concat "^\\(\\|[/\\]*[^*?]*[/\\]\\)" ; root - "\\([^/\\]*[*?][^/\\]*\\)" ; filename with *? - "\\(.*\\)") ; rest - dirname) - (setq root (match-string 1 dirname) - filename (match-string 2 dirname) - rest (match-string 3 dirname) - pattern filename) - ;; now replace those * and ? with .+ and . - ;; use ^ and /> to get only whole file names - (setq pattern (verilog-string-replace-matches "[*]" ".+" nil nil pattern) - pattern (verilog-string-replace-matches "[?]" "." nil nil pattern) - pattern (concat "^" pattern "$") - dirfiles (verilog-dir-files root)) - (while dirfiles - (setq basefile (car dirfiles) - dirfile (expand-file-name (concat root basefile rest)) - dirfiles (cdr dirfiles)) - (if (and (string-match pattern basefile) - ;; Don't allow abc/*/rtl to match abc/rtl via .. - (not (equal basefile ".")) - (not (equal basefile "..")) - (file-directory-p dirfile)) - (setq dirlist (cons dirfile dirlist))))) - ;; Defaults - (t - (if (file-directory-p dirname) - (setq dirlist (cons dirname dirlist)))))) - dirlist)) -;;(verilog-expand-dirnames (list "." ".." "nonexist" "../*" "/home/wsnyder/*/v")) + (save-match-data + (setq dirnames (reverse dirnames)) ; not nreverse + (let ((dirlist nil) + pattern dirfile dirfiles dirname root filename rest basefile) + (setq dirnames (mapcar 'substitute-in-file-name dirnames)) + (while dirnames + (setq dirname (car dirnames) + dirnames (cdr dirnames)) + (cond ((string-match (concat "^\\(\\|[/\\]*[^*?]*[/\\]\\)" ; root + "\\([^/\\]*[*?][^/\\]*\\)" ; filename with *? + "\\(.*\\)") ; rest + dirname) + (setq root (match-string 1 dirname) + filename (match-string 2 dirname) + rest (match-string 3 dirname) + pattern filename) + ;; now replace those * and ? with .+ and . + ;; use ^ and /> to get only whole file names + (setq pattern (verilog-string-replace-matches "[*]" ".+" nil nil pattern) + pattern (verilog-string-replace-matches "[?]" "." nil nil pattern) + pattern (concat "^" pattern "$") + dirfiles (verilog-dir-files root)) + (while dirfiles + (setq basefile (car dirfiles) + dirfile (expand-file-name (concat root basefile rest)) + dirfiles (cdr dirfiles)) + (when (and (string-match pattern basefile) + ;; Don't allow abc/*/rtl to match abc/rtl via .. + (not (equal basefile ".")) + (not (equal basefile ".."))) + ;; Might have more wildcards, so process again + (setq dirnames (cons dirfile dirnames))))) + ;; Defaults + (t + (if (file-directory-p dirname) + (setq dirlist (cons dirname dirlist)))))) + dirlist))) +;;(verilog-expand-dirnames (list "." ".." "nonexist" "../*" "/home/wsnyder/*/v" "../*/*")) (defun verilog-library-filenames (filename &optional current check-ext) "Return a search path to find the given FILENAME or module name. @@ -12074,7 +12090,7 @@ This is currently equivalent to: with the below at the bottom of the file // Local Variables: - // verilog-auto-logic-type:\"logic\" + // verilog-auto-wire-type:\"logic\" // End: In the future AUTOLOGIC may declare additional identifiers, @@ -13223,10 +13239,12 @@ Typing \\[verilog-auto] will make this into: Replace the /*AUTOTIEOFF*/ comment with code to wire-tie all unused output signals to deasserted. -/*AUTOTIEOFF*/ is used to make stub modules; modules that have the same -input/output list as another module, but no internals. Specifically, it -finds all outputs in the module, and if that input is not otherwise declared -as a register or wire, creates a tieoff. +/*AUTOTIEOFF*/ is used to make stub modules; modules that have +the same input/output list as another module, but no internals. +Specifically, it finds all outputs in the module, and if that +input is not otherwise declared as a register or wire, nor comes +from a AUTOINST submodule's output, creates a tieoff. AUTOTIEOFF +does not examine assignments to determine what is already driven. AUTORESET ties signals to deasserted, which is presumed to be zero. Signals that match `verilog-active-low-regexp' will be deasserted by tying @@ -14420,7 +14438,7 @@ Files are checked based on `verilog-library-flags'." (with-output-to-temp-buffer "*verilog-mode help*" (princ (format "You are using verilog-mode %s\n" verilog-mode-version)) (princ "\n") - (princ "For new releases, see http://www.verilog.com\n") + (princ "For new releases, see http://www.veripool.com/verilog-mode\n") (princ "\n") (princ "For frequently asked questions, see http://www.veripool.org/verilog-mode-faq.html\n") (princ "\n") diff --git a/lisp/progmodes/vhdl-mode.el b/lisp/progmodes/vhdl-mode.el index 06ffd54d2df..3f2d7e11ec9 100644 --- a/lisp/progmodes/vhdl-mode.el +++ b/lisp/progmodes/vhdl-mode.el @@ -32,7 +32,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Commentary: @@ -17897,7 +17897,7 @@ references: [3] European Space Agency. \"VHDL Modelling Guidelines\". - ftp://ftp.estec.esa.nl/pub/vhdl/doc/ModelGuide.{pdf,ps} + https://amstel.estec.esa.int/tecedm/website/docs_generic/ModelGuide.pdf Use user options `vhdl-highlight-special-words' and `vhdl-special-syntax-alist' to visually support naming conventions.") diff --git a/lisp/progmodes/which-func.el b/lisp/progmodes/which-func.el index 41513340e12..adfe7b3bf1c 100644 --- a/lisp/progmodes/which-func.el +++ b/lisp/progmodes/which-func.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/progmodes/xref.el b/lisp/progmodes/xref.el index b8ec50f14ae..db025d40aa3 100644 --- a/lisp/progmodes/xref.el +++ b/lisp/progmodes/xref.el @@ -15,7 +15,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -102,7 +102,7 @@ This is typically the filename.") ;;;; Commonly needed location classes are defined here: ;; FIXME: might be useful to have an optional "hint" i.e. a string to -;; search for in case the line number is sightly out of date. +;; search for in case the line number is slightly out of date. (defclass xref-file-location (xref-location) ((file :type string :initarg :file) (line :type fixnum :initarg :line :reader xref-location-line) @@ -254,8 +254,7 @@ find a search tool; by default, this uses \"find | grep\" in the (project-external-roots pr))))) (cl-defgeneric xref-backend-apropos (backend pattern) - "Find all symbols that match PATTERN. -PATTERN is a regexp") + "Find all symbols that match regexp PATTERN.") (cl-defgeneric xref-backend-identifier-at-point (_backend) "Return the relevant identifier at point. @@ -449,43 +448,74 @@ If SELECT is non-nil, select the target window." (when xref-w (set-window-dedicated-p xref-w xref-w-dedicated))))) -(defun xref--show-pos-in-buf (pos buf select) - (let ((xref-buf (current-buffer)) - win) +(defvar-local xref--original-window-intent nil + "Original window-switching intent before xref buffer creation.") + +(defvar-local xref--original-window nil + "The original window this xref buffer was created from.") + +(defun xref--show-pos-in-buf (pos buf) + "Goto and display position POS of buffer BUF in a window. +Honor `xref--original-window-intent', run `xref-after-jump-hook' +and finally return the window." + (let* ((xref-buf (current-buffer)) + (pop-up-frames + (or (eq xref--original-window-intent 'frame) + pop-up-frames)) + (action + (cond ((memq + xref--original-window-intent + '(window frame)) + t) + ((and + (window-live-p xref--original-window) + (or (not (window-dedicated-p xref--original-window)) + (eq (window-buffer xref--original-window) buf))) + `(,(lambda (buf _alist) + (set-window-buffer xref--original-window buf) + xref--original-window)))))) (with-selected-window - (xref--with-dedicated-window - (display-buffer buf)) + (with-selected-window + ;; Just before `display-buffer', place ourselves in the + ;; original window to suggest preserving it. Of course, if + ;; user has deleted the original window, all bets are off, + ;; just use the selected one. + (or (and (window-live-p xref--original-window) + xref--original-window) + (selected-window)) + (display-buffer buf action)) (xref--goto-char pos) (run-hooks 'xref-after-jump-hook) (let ((buf (current-buffer))) - (setq win (selected-window)) (with-current-buffer xref-buf - (setq-local other-window-scroll-buffer buf)))) - (when select - (select-window win)))) + (setq-local other-window-scroll-buffer buf))) + (selected-window)))) (defun xref--show-location (location &optional select) + "Help `xref-show-xref' and `xref-goto-xref' do their job. +Go to LOCATION and if SELECT is non-nil select its window. If +SELECT is `quit', also quit the *xref* window." (condition-case err (let* ((marker (xref-location-marker location)) - (buf (marker-buffer marker))) - (xref--show-pos-in-buf marker buf select)) + (buf (marker-buffer marker)) + (xref-buffer (current-buffer))) + (cond (select + (if (eq select 'quit) (quit-window nil nil)) + (with-current-buffer xref-buffer + (select-window (xref--show-pos-in-buf marker buf)))) + (t + (save-selected-window + (xref--with-dedicated-window + (xref--show-pos-in-buf marker buf)))))) (user-error (message (error-message-string err))))) -(defvar-local xref--window nil - "The original window this xref buffer was created from.") - (defun xref-show-location-at-point () "Display the source of xref at point in the appropriate window, if any." (interactive) (let* ((xref (xref--item-at-point)) (xref--current-item xref)) (when xref - ;; Try to avoid the window the current xref buffer was - ;; originally created from. - (if (window-live-p xref--window) - (with-selected-window xref--window - (xref--show-location (xref-item-location xref))) - (xref--show-location (xref-item-location xref)))))) + (xref--show-location (xref-item-location xref))))) (defun xref-next-line () "Move to the next xref and display its source in the appropriate window." @@ -504,12 +534,19 @@ If SELECT is non-nil, select the target window." (back-to-indentation) (get-text-property (point) 'xref-item))) -(defun xref-goto-xref () - "Jump to the xref on the current line and select its window." +(defun xref-goto-xref (&optional quit) + "Jump to the xref on the current line and select its window. +Non-interactively, non-nil QUIT means to first quit the *xref* +buffer." (interactive) (let ((xref (or (xref--item-at-point) (user-error "No reference at point")))) - (xref--show-location (xref-item-location xref) t))) + (xref--show-location (xref-item-location xref) (if quit 'quit t)))) + +(defun xref-quit-and-goto-xref () + "Quit *xref* buffer, then jump to xref on current line." + (interactive) + (xref-goto-xref t)) (defun xref-query-replace-in-results (from to) "Perform interactive replacement of FROM with TO in all displayed xrefs. @@ -633,6 +670,7 @@ references displayed in the current *xref* buffer." (define-key map (kbd "p") #'xref-prev-line) (define-key map (kbd "r") #'xref-query-replace-in-results) (define-key map (kbd "RET") #'xref-goto-xref) + (define-key map (kbd "TAB") #'xref-quit-and-goto-xref) (define-key map (kbd "C-o") #'xref-show-location-at-point) ;; suggested by Johan Claesson "to further reduce finger movement": (define-key map (kbd ".") #'xref-next-line) @@ -727,7 +765,8 @@ Return an alist of the form ((FILENAME . (XREF ...)) ...)." (xref--xref-buffer-mode) (pop-to-buffer (current-buffer)) (goto-char (point-min)) - (setq xref--window (assoc-default 'window alist)) + (setq xref--original-window (assoc-default 'window alist) + xref--original-window-intent (assoc-default 'display-action alist)) (current-buffer))))) @@ -754,7 +793,8 @@ Return an alist of the form ((FILENAME . (XREF ...)) ...)." (t (xref-push-marker-stack) (funcall xref-show-xrefs-function xrefs - `((window . ,(selected-window))))))) + `((window . ,(selected-window)) + (display-action . ,display-action)))))) (defun xref--prompt-p (command) (or (eq xref-prompt-for-identifier t) @@ -917,22 +957,25 @@ IGNORES is a list of glob patterns." (grep-compute-defaults) (defvar grep-find-template) (defvar grep-highlight-matches) - (let* ((grep-find-template (replace-regexp-in-string "<C>" "<C> -E" - grep-find-template t t)) - (grep-highlight-matches nil) - ;; TODO: Sanitize the regexp to remove Emacs-specific terms, - ;; so that Grep can search for the "relaxed" version. Can we - ;; do that reliably enough, without creating false negatives? - (command (xref--rgrep-command (xref--regexp-to-extended regexp) - files - (expand-file-name dir) - ignores)) - (buf (get-buffer-create " *xref-grep*")) - (grep-re (caar grep-regexp-alist)) - status - hits) + (pcase-let* + ((grep-find-template (replace-regexp-in-string "<C>" "<C> -E" + grep-find-template t t)) + (grep-highlight-matches nil) + ;; TODO: Sanitize the regexp to remove Emacs-specific terms, + ;; so that Grep can search for the "relaxed" version. Can we + ;; do that reliably enough, without creating false negatives? + (command (xref--rgrep-command (xref--regexp-to-extended regexp) + files + (expand-file-name dir) + ignores)) + (def default-directory) + (buf (get-buffer-create " *xref-grep*")) + (`(,grep-re ,file-group ,line-group . ,_) (car grep-regexp-alist)) + (status nil) + (hits nil)) (with-current-buffer buf (erase-buffer) + (setq default-directory def) (setq status (call-process-shell-command command nil t)) (goto-char (point-min)) @@ -944,8 +987,8 @@ IGNORES is a list of glob patterns." (not (looking-at grep-re))) (user-error "Search failed with status %d: %s" status (buffer-string))) (while (re-search-forward grep-re nil t) - (push (list (string-to-number (match-string 2)) - (match-string 1) + (push (list (string-to-number (match-string line-group)) + (match-string file-group) (buffer-substring-no-properties (point) (line-end-position))) hits))) (xref--convert-hits (nreverse hits) regexp))) diff --git a/lisp/progmodes/xscheme.el b/lisp/progmodes/xscheme.el index bdfe30af505..16bf01eeaa8 100644 --- a/lisp/progmodes/xscheme.el +++ b/lisp/progmodes/xscheme.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -85,8 +85,7 @@ reading-type received an altmode but nothing else reading-string reading prompt string") (defvar-local xscheme-allow-output-p t - "This variable, if nil, prevents output from the scheme process -from being inserted into the process-buffer.") + "Non-nil stops scheme process output being inserted in the process buffer.") (defvar-local xscheme-prompt "" "The current scheme prompt string.") @@ -300,7 +299,7 @@ With argument, asks for a command line." (defun scheme-interaction-mode (&optional preserve) "Major mode for interacting with an inferior MIT Scheme process. -Like scheme-mode except that: +Like `scheme-mode' except that: \\[xscheme-send-previous-expression] sends the expression before point to the Scheme process as input \\[xscheme-yank-pop] yanks an expression previously sent to Scheme @@ -315,7 +314,7 @@ in the minibuffer. If an error occurs, the process buffer will automatically pop up to show you the error message. While the Scheme process is running, the mode lines of all buffers in -scheme-mode are modified to show the state of the process. The +`scheme-mode' are modified to show the state of the process. The possible states and their meanings are: input waiting for input @@ -353,13 +352,13 @@ Some possible command interpreter types and their meanings are: Starting with release 6.2 of Scheme, the latter two types of command interpreters will change the major mode of the Scheme process buffer -to scheme-debugger-mode , in which the evaluation commands are +to `scheme-debugger-mode', in which the evaluation commands are disabled, and the keys which normally self insert instead send themselves to the Scheme process. The command character ? will list the available commands. -For older releases of Scheme, the major mode will be be -scheme-interaction-mode , and the command characters must be sent as +For older releases of Scheme, the major mode will be +`scheme-interaction-mode', and the command characters must be sent as if they were expressions. Commands: @@ -367,10 +366,8 @@ Delete converts tabs to spaces as it moves back. Blank lines separate paragraphs. Semicolons start comments. \\{scheme-interaction-mode-map} -Entry to this mode calls the value of scheme-interaction-mode-hook -with no args, if that value is non-nil. - Likewise with the value of scheme-mode-hook. - scheme-interaction-mode-hook is called after scheme-mode-hook." +Entry to this mode runs `scheme-mode-hook' and then +`scheme-interaction-mode-hook'." ;; FIXME: Use define-derived-mode. (interactive "P") (if (not preserve) @@ -456,7 +453,7 @@ with no args, if that value is non-nil. (defun scheme-debugger-mode () "Major mode for executing the Scheme debugger. -Like scheme-mode except that the evaluation commands +Like `scheme-mode' except that the evaluation commands are disabled, and characters that would normally be self inserting are sent to the Scheme process instead. Typing ? will show you which characters perform useful functions. @@ -593,7 +590,7 @@ See also the commands \\[xscheme-yank-pop] and \\[xscheme-yank-push]." "Insert or replace a just-yanked expression with an older expression. If the previous command was not a yank, it yanks. Otherwise, the region contains a stretch of reinserted -expression. yank-pop deletes that text and inserts in its +expression. `yank-pop' deletes that text and inserts in its place a different expression. With no argument, the next older expression is inserted. @@ -620,7 +617,7 @@ comes the newest one." "Insert or replace a just-yanked expression with a more recent expression. If the previous command was not a yank, it yanks. Otherwise, the region contains a stretch of reinserted -expression. yank-pop deletes that text and inserts in its +expression. `yank-pop' deletes that text and inserts in its place a different expression. With no argument, the next more recent expression is inserted. diff --git a/lisp/ps-bdf.el b/lisp/ps-bdf.el index f49cbd7c589..04e69a307f8 100644 --- a/lisp/ps-bdf.el +++ b/lisp/ps-bdf.el @@ -27,7 +27,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/ps-def.el b/lisp/ps-def.el index ea51c2a09b1..a23ca53a831 100644 --- a/lisp/ps-def.el +++ b/lisp/ps-def.el @@ -2,10 +2,10 @@ ;; Copyright (C) 2007-2017 Free Software Foundation, Inc. -;; Author: Vinicius Jose Latorre <viniciusjl@ig.com.br> +;; Author: Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> ;; Kenichi Handa <handa@m17n.org> (multi-byte characters) ;; Maintainer: Kenichi Handa <handa@m17n.org> (multi-byte characters) -;; Vinicius Jose Latorre <viniciusjl@ig.com.br> +;; Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> ;; Keywords: wp, print, PostScript ;; X-URL: http://www.emacswiki.org/cgi-bin/wiki/ViniciusJoseLatorre ;; Package: ps-print @@ -23,7 +23,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/ps-mule.el b/lisp/ps-mule.el index 0a590105b20..0d850f1e520 100644 --- a/lisp/ps-mule.el +++ b/lisp/ps-mule.el @@ -2,10 +2,10 @@ ;; Copyright (C) 1998-2017 Free Software Foundation, Inc. -;; Author: Vinicius Jose Latorre <viniciusjl@ig.com.br> +;; Author: Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> ;; Kenichi Handa <handa@m17n.org> (multi-byte characters) ;; Maintainer: Kenichi Handa <handa@m17n.org> (multi-byte characters) -;; Vinicius Jose Latorre <viniciusjl@ig.com.br> +;; Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> ;; Keywords: wp, print, PostScript, multibyte, mule ;; Package: ps-print @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/ps-print.el b/lisp/ps-print.el index 7476ab3bb12..8571f2287ac 100644 --- a/lisp/ps-print.el +++ b/lisp/ps-print.el @@ -4,10 +4,10 @@ ;; Author: Jim Thompson (was <thompson@wg2.waii.com>) ;; Jacques Duthen (was <duthen@cegelec-red.fr>) -;; Vinicius Jose Latorre <viniciusjl@ig.com.br> +;; Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> ;; Kenichi Handa <handa@m17n.org> (multi-byte characters) ;; Maintainer: Kenichi Handa <handa@m17n.org> (multi-byte characters) -;; Vinicius Jose Latorre <viniciusjl@ig.com.br> +;; Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> ;; Keywords: wp, print, PostScript ;; Version: 7.3.5 ;; X-URL: http://www.emacswiki.org/cgi-bin/wiki/ViniciusJoseLatorre @@ -20,7 +20,7 @@ Emacs without changes to the version number. When reporting bugs, please also report the version of Emacs, if any, that ps-print was distributed with. Please send all bug fixes and enhancements to - bug-gnu-emacs@gnu.org and Vinicius Jose Latorre <viniciusjl@ig.com.br>.") + bug-gnu-emacs@gnu.org and Vinicius Jose Latorre <viniciusjl.gnu@gmail.com>.") ;; This file is part of GNU Emacs. @@ -35,7 +35,7 @@ Please send all bug fixes and enhancements to ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -1216,7 +1216,7 @@ Please send all bug fixes and enhancements to ;; New since version 2.8 ;; --------------------- ;; -;; [vinicius] Vinicius Jose Latorre <viniciusjl@ig.com.br> +;; [vinicius] Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> ;; ;; 2007-10-27 ;; `ps-fg-validate-p', `ps-fg-list' @@ -1274,7 +1274,7 @@ Please send all bug fixes and enhancements to ;; ;; `ps-print-region-function' ;; -;; [vinicius] Vinicius Jose Latorre <viniciusjl@ig.com.br> +;; [vinicius] Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> ;; ;; 1999-03-01 ;; PostScript tumble and setpagedevice. @@ -1287,7 +1287,7 @@ Please send all bug fixes and enhancements to ;; ;; Multi-byte buffer handling. ;; -;; [vinicius] Vinicius Jose Latorre <viniciusjl@ig.com.br> +;; [vinicius] Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> ;; ;; 1998-03-06 ;; Skip invisible text. diff --git a/lisp/ps-samp.el b/lisp/ps-samp.el index f86e5269388..7507eee8f64 100644 --- a/lisp/ps-samp.el +++ b/lisp/ps-samp.el @@ -4,10 +4,10 @@ ;; Author: Jim Thompson (was <thompson@wg2.waii.com>) ;; Jacques Duthen (was <duthen@cegelec-red.fr>) -;; Vinicius Jose Latorre <viniciusjl@ig.com.br> +;; Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> ;; Kenichi Handa <handa@m17n.org> (multi-byte characters) ;; Maintainer: Kenichi Handa <handa@m17n.org> (multi-byte characters) -;; Vinicius Jose Latorre <viniciusjl@ig.com.br> +;; Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> ;; Keywords: wp, print, PostScript ;; X-URL: http://www.emacswiki.org/cgi-bin/wiki/ViniciusJoseLatorre ;; Package: ps-print @@ -25,7 +25,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/recentf.el b/lisp/recentf.el index 462ccb6db5e..d78d7ce71da 100644 --- a/lisp/recentf.el +++ b/lisp/recentf.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/rect.el b/lisp/rect.el index a85101fddfa..a62ed95b715 100644 --- a/lisp/rect.el +++ b/lisp/rect.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -450,6 +450,10 @@ With a prefix (or a FILL) argument, also fill too short lines." "Replace rectangle contents with STRING on each line. The length of STRING need not be the same as the rectangle width. +When called interactively and option `rectangle-preview' is +non-nil, display the result as the user enters the string into +the minibuffer. + Called from a program, takes three args; START, END and STRING." (interactive (progn diff --git a/lisp/register.el b/lisp/register.el index 7cc3ccd870c..23eefd08b88 100644 --- a/lisp/register.el +++ b/lisp/register.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -164,6 +164,10 @@ display such a window regardless." help-chars) (unless (get-buffer-window buffer) (register-preview buffer 'show-empty))) + (when (or (eq ?\C-g last-input-event) + (eq 'escape last-input-event) + (eq ?\C-\[ last-input-event)) + (keyboard-quit)) (if (characterp last-input-event) last-input-event (error "Non-character input-event"))) (and (timerp timer) (cancel-timer timer)) @@ -178,8 +182,11 @@ Use \\[jump-to-register] to go to that location or restore that configuration. Argument is a character, naming the register. Interactively, reads the register using `register-read-with-preview'." - (interactive (list (register-read-with-preview "Point to register: ") - current-prefix-arg)) + (interactive (list (register-read-with-preview + (if current-prefix-arg + "Frame configuration to register: " + "Point to register: ")) + current-prefix-arg)) ;; Turn the marker into a file-ref if the buffer is killed. (add-hook 'kill-buffer-hook 'register-swap-out nil t) (set-register register diff --git a/lisp/registry.el b/lisp/registry.el index 27664dc09ec..17dc23d68e8 100644 --- a/lisp/registry.el +++ b/lisp/registry.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/repeat.el b/lisp/repeat.el index c55a50a8343..f75d9d0d66b 100644 --- a/lisp/repeat.el +++ b/lisp/repeat.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/replace.el b/lisp/replace.el index 64dfe7da22d..80e584517ce 100644 --- a/lisp/replace.el +++ b/lisp/replace.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -28,6 +28,7 @@ ;;; Code: +(require 'text-mode) (eval-when-compile (require 'cl-lib)) (defcustom case-replace t @@ -1395,6 +1396,11 @@ invoke `occur'." "Show all lines in the current buffer containing a match for REGEXP. If a match spreads across multiple lines, all those lines are shown. +Each match is extended to include complete lines. Only non-overlapping +matches are considered. (Note that extending matches to complete +lines could cause some of the matches to overlap; if so, they will not +be shown as separate matches.) + Each line is displayed with NLINES lines before and after, or -NLINES before if NLINES is negative. NLINES defaults to `list-matching-lines-default-context-lines'. @@ -1637,175 +1643,185 @@ See also `multi-occur'." (inhibit-field-text-motion t) (headerpt (with-current-buffer out-buf (point)))) (with-current-buffer buf - (or coding - ;; Set CODING only if the current buffer locally - ;; binds buffer-file-coding-system. - (not (local-variable-p 'buffer-file-coding-system)) - (setq coding buffer-file-coding-system)) - (save-excursion - (goto-char (point-min)) ;; begin searching in the buffer - (while (not (eobp)) - (setq origpt (point)) - (when (setq endpt (re-search-forward regexp nil t)) - (setq lines (1+ lines)) ;; increment matching lines count - (setq matchbeg (match-beginning 0)) - ;; Get beginning of first match line and end of the last. - (save-excursion - (goto-char matchbeg) - (setq begpt (line-beginning-position)) - (goto-char endpt) - (setq endpt (line-end-position))) - ;; Sum line numbers up to the first match line. - (setq curr-line (+ curr-line (count-lines origpt begpt))) - (setq marker (make-marker)) - (set-marker marker matchbeg) - (setq curstring (occur-engine-line begpt endpt keep-props)) - ;; Highlight the matches - (let ((len (length curstring)) - (start 0)) - ;; Count empty lines that don't use next loop (Bug#22062). - (when (zerop len) - (setq matches (1+ matches))) - (while (and (< start len) - (string-match regexp curstring start)) - (setq matches (1+ matches)) - (add-text-properties - (match-beginning 0) (match-end 0) - '(occur-match t) curstring) - (when match-face - ;; Add `match-face' to faces copied from the buffer. - (add-face-text-property + ;; The following binding is for when case-fold-search + ;; has a local binding in the original buffer, in which + ;; case we cannot bind it globally and let that have + ;; effect in every buffer we search. + (let ((case-fold-search case-fold)) + (or coding + ;; Set CODING only if the current buffer locally + ;; binds buffer-file-coding-system. + (not (local-variable-p 'buffer-file-coding-system)) + (setq coding buffer-file-coding-system)) + (save-excursion + (goto-char (point-min)) ;; begin searching in the buffer + (while (not (eobp)) + (setq origpt (point)) + (when (setq endpt (re-search-forward regexp nil t)) + (setq lines (1+ lines)) ;; increment matching lines count + (setq matchbeg (match-beginning 0)) + ;; Get beginning of first match line and end of the last. + (save-excursion + (goto-char matchbeg) + (setq begpt (line-beginning-position)) + (goto-char endpt) + (setq endpt (line-end-position))) + ;; Sum line numbers up to the first match line. + (setq curr-line (+ curr-line (count-lines origpt begpt))) + (setq marker (make-marker)) + (set-marker marker matchbeg) + (setq curstring (occur-engine-line begpt endpt keep-props)) + ;; Highlight the matches + (let ((len (length curstring)) + (start 0)) + ;; Count empty lines that don't use next loop (Bug#22062). + (when (zerop len) + (setq matches (1+ matches))) + (while (and (< start len) + (string-match regexp curstring start)) + (setq matches (1+ matches)) + (add-text-properties (match-beginning 0) (match-end 0) - match-face nil curstring)) - ;; Avoid infloop (Bug#7593). - (let ((end (match-end 0))) - (setq start (if (= start end) (1+ start) end))))) - ;; Generate the string to insert for this match - (let* ((match-prefix - ;; Using 7 digits aligns tabs properly. - (apply #'propertize (format "%7d:" curr-line) - (append - (when prefix-face - `(font-lock-face ,prefix-face)) - `(occur-prefix t mouse-face (highlight) - ;; Allow insertion of text at - ;; the end of the prefix (for - ;; Occur Edit mode). - front-sticky t rear-nonsticky t - occur-target ,marker follow-link t - help-echo "mouse-2: go to this occurrence")))) - (match-str - ;; We don't put `mouse-face' on the newline, - ;; because that loses. And don't put it - ;; on context lines to reduce flicker. - (propertize curstring 'mouse-face (list 'highlight) - 'occur-target marker - 'follow-link t - 'help-echo - "mouse-2: go to this occurrence")) - (out-line - (concat - match-prefix - ;; Add non-numeric prefix to all non-first lines - ;; of multi-line matches. - (replace-regexp-in-string - "\n" - (if prefix-face - (propertize "\n :" 'font-lock-face prefix-face) - "\n :") - match-str) - ;; Add marker at eol, but no mouse props. - (propertize "\n" 'occur-target marker))) - (data - (if (= nlines 0) - ;; The simple display style - out-line - ;; The complex multi-line display style. - (setq ret (occur-context-lines - out-line nlines keep-props begpt endpt - curr-line prev-line prev-after-lines - prefix-face)) - ;; Set first elem of the returned list to `data', - ;; and the second elem to `prev-after-lines'. - (setq prev-after-lines (nth 1 ret)) - (nth 0 ret)))) - ;; Actually insert the match display data - (with-current-buffer out-buf - (when (and list-matching-lines-jump-to-current-line - (not multi-occur-p) - (not orig-line-shown-p) - (>= curr-line orig-line)) - (insert - (concat - (propertize - (format "%7d:%s" orig-line orig-line-str) - 'face list-matching-lines-current-line-face - 'mouse-face 'mode-line-highlight - 'help-echo "Current line") "\n")) - (setq orig-line-shown-p t finalpt (point))) - (insert data))) - (goto-char endpt)) - (if endpt - (progn - ;; Sum line numbers between first and last match lines. - (setq curr-line (+ curr-line (count-lines begpt endpt) - ;; Add 1 for empty last match line since - ;; count-lines returns 1 line less. - (if (and (bolp) (eolp)) 1 0))) - ;; On to the next match... - (forward-line 1)) - (goto-char (point-max))) - (setq prev-line (1- curr-line))) - ;; Insert original line if haven't done yet. - (when (and list-matching-lines-jump-to-current-line - (not multi-occur-p) - (not orig-line-shown-p)) - (with-current-buffer out-buf - (insert - (concat - (propertize - (format "%7d:%s" orig-line orig-line-str) - 'face list-matching-lines-current-line-face - 'mouse-face 'mode-line-highlight - 'help-echo "Current line") "\n")))) - ;; Flush remaining context after-lines. - (when prev-after-lines - (with-current-buffer out-buf - (insert (apply #'concat (occur-engine-add-prefix - prev-after-lines prefix-face))))))) - (when (not (zerop lines)) ;; is the count zero? - (setq global-lines (+ global-lines lines) - global-matches (+ global-matches matches)) - (with-current-buffer out-buf - (goto-char headerpt) - (let ((beg (point)) - end) - (insert (propertize - (format "%d match%s%s%s in buffer: %s%s\n" - matches (if (= matches 1) "" "es") - ;; Don't display the same number of lines - ;; and matches in case of 1 match per line. - (if (= lines matches) - "" (format " in %d line%s" - lines (if (= lines 1) "" "s"))) - ;; Don't display regexp for multi-buffer. - (if (> (length buffers) 1) - "" (occur-regexp-descr regexp)) - (buffer-name buf) - (if in-region-p - (format " within region: %d-%d" - occur--region-start - occur--region-end) - "")) - 'read-only t)) - (setq end (point)) - (add-text-properties beg end `(occur-title ,buf)) - (when title-face - (add-face-text-property beg end title-face)) - (goto-char (if finalpt - (setq occur--final-pos - (cl-incf finalpt (- end beg))) - (point-min))))))))) + '(occur-match t) curstring) + (when match-face + ;; Add `match-face' to faces copied from the buffer. + (add-face-text-property + (match-beginning 0) (match-end 0) + match-face nil curstring)) + ;; Avoid infloop (Bug#7593). + (let ((end (match-end 0))) + (setq start (if (= start end) (1+ start) end))))) + ;; Generate the string to insert for this match + (let* ((match-prefix + ;; Using 7 digits aligns tabs properly. + (apply #'propertize (format "%7d:" curr-line) + (append + (when prefix-face + `(font-lock-face ,prefix-face)) + `(occur-prefix t mouse-face (highlight) + ;; Allow insertion of text + ;; at the end of the prefix + ;; (for Occur Edit mode). + front-sticky t + rear-nonsticky t + occur-target ,marker + follow-link t + help-echo "mouse-2: go to this occurrence")))) + (match-str + ;; We don't put `mouse-face' on the newline, + ;; because that loses. And don't put it + ;; on context lines to reduce flicker. + (propertize curstring 'mouse-face (list 'highlight) + 'occur-target marker + 'follow-link t + 'help-echo + "mouse-2: go to this occurrence")) + (out-line + (concat + match-prefix + ;; Add non-numeric prefix to all non-first lines + ;; of multi-line matches. + (replace-regexp-in-string + "\n" + (if prefix-face + (propertize + "\n :" 'font-lock-face prefix-face) + "\n :") + match-str) + ;; Add marker at eol, but no mouse props. + (propertize "\n" 'occur-target marker))) + (data + (if (= nlines 0) + ;; The simple display style + out-line + ;; The complex multi-line display style. + (setq ret (occur-context-lines + out-line nlines keep-props begpt + endpt curr-line prev-line + prev-after-lines prefix-face)) + ;; Set first elem of the returned list to `data', + ;; and the second elem to `prev-after-lines'. + (setq prev-after-lines (nth 1 ret)) + (nth 0 ret)))) + ;; Actually insert the match display data + (with-current-buffer out-buf + (when (and list-matching-lines-jump-to-current-line + (not multi-occur-p) + (not orig-line-shown-p) + (>= curr-line orig-line)) + (insert + (concat + (propertize + (format "%7d:%s" orig-line orig-line-str) + 'face list-matching-lines-current-line-face + 'mouse-face 'mode-line-highlight + 'help-echo "Current line") "\n")) + (setq orig-line-shown-p t finalpt (point))) + (insert data))) + (goto-char endpt)) + (if endpt + (progn + ;; Sum line numbers between first and last match lines. + (setq curr-line (+ curr-line (count-lines begpt endpt) + ;; Add 1 for empty last match line + ;; since count-lines returns one + ;; line less. + (if (and (bolp) (eolp)) 1 0))) + ;; On to the next match... + (forward-line 1)) + (goto-char (point-max))) + (setq prev-line (1- curr-line))) + ;; Insert original line if haven't done yet. + (when (and list-matching-lines-jump-to-current-line + (not multi-occur-p) + (not orig-line-shown-p)) + (with-current-buffer out-buf + (insert + (concat + (propertize + (format "%7d:%s" orig-line orig-line-str) + 'face list-matching-lines-current-line-face + 'mouse-face 'mode-line-highlight + 'help-echo "Current line") "\n")))) + ;; Flush remaining context after-lines. + (when prev-after-lines + (with-current-buffer out-buf + (insert (apply #'concat (occur-engine-add-prefix + prev-after-lines prefix-face))))))) + (when (not (zerop lines)) ;; is the count zero? + (setq global-lines (+ global-lines lines) + global-matches (+ global-matches matches)) + (with-current-buffer out-buf + (goto-char headerpt) + (let ((beg (point)) + end) + (insert (propertize + (format "%d match%s%s%s in buffer: %s%s\n" + matches (if (= matches 1) "" "es") + ;; Don't display the same number of lines + ;; and matches in case of 1 match per line. + (if (= lines matches) + "" (format " in %d line%s" + lines + (if (= lines 1) "" "s"))) + ;; Don't display regexp for multi-buffer. + (if (> (length buffers) 1) + "" (occur-regexp-descr regexp)) + (buffer-name buf) + (if in-region-p + (format " within region: %d-%d" + occur--region-start + occur--region-end) + "")) + 'read-only t)) + (setq end (point)) + (add-text-properties beg end `(occur-title ,buf)) + (when title-face + (add-face-text-property beg end title-face)) + (goto-char (if finalpt + (setq occur--final-pos + (cl-incf finalpt (- end beg))) + (point-min)))))))))) ;; Display total match count and regexp for multi-buffer. (when (and (not (zerop global-lines)) (> (length buffers) 1)) (goto-char (point-min)) @@ -2216,6 +2232,26 @@ It is called with three arguments, as if it were ;; Close overlays opened by `isearch-range-invisible' in `perform-replace'. (isearch-clean-overlays)) +;; A macro because we push STACK, i.e. a local var in `perform-replace'. +(defmacro replace--push-stack (replaced search-str next-replace stack) + (declare (indent 0) (debug (form form form gv-place))) + `(push (list (point) ,replaced +;;; If the replacement has already happened, all we need is the +;;; current match start and end. We could get this with a trivial +;;; match like +;;; (save-excursion (goto-char (match-beginning 0)) +;;; (search-forward (match-string 0)) +;;; (match-data t)) +;;; if we really wanted to avoid manually constructing match data. +;;; Adding current-buffer is necessary so that match-data calls can +;;; return markers which are appropriate for editing. + (if ,replaced + (list + (match-beginning 0) (match-end 0) (current-buffer)) + (match-data t)) + ,search-str ,next-replace) + ,stack)) + (defun perform-replace (from-string replacements query-flag regexp-flag delimited-flag &optional repeat-count map start end backward region-noncontiguous-p) @@ -2259,6 +2295,8 @@ It must return a string." (next-replacement-replaced nil) ; replacement string ; (substituted regexp) (last-was-undo) + (last-was-act-and-show) + (update-stack t) (replace-count 0) (skip-read-only-count 0) (skip-filtered-count 0) @@ -2542,7 +2580,7 @@ It must return a string." next-replacement) (while (and (< stack-idx stack-len) stack - (null replaced)) + (or (null replaced) last-was-act-and-show)) (let* ((elt (nth stack-idx stack))) (setq stack-idx (1+ stack-idx) @@ -2552,10 +2590,11 @@ It must return a string." search-string (nth (if replaced 4 3) elt) next-replacement (nth (if replaced 3 4) elt) search-string-replaced search-string - next-replacement-replaced next-replacement) + next-replacement-replaced next-replacement + last-was-act-and-show nil) (when (and (= stack-idx stack-len) - (null replaced) + (and (null replaced) (not last-was-act-and-show)) (zerop num-replacements)) (message "Nothing to undo") (ding 'no-terminate) @@ -2595,7 +2634,7 @@ It must return a string." "replacements")) (ding 'no-terminate) (sit-for 1))) - (setq replaced nil last-was-undo t))) + (setq replaced nil last-was-undo t last-was-act-and-show nil))) ((eq def 'act) (or replaced (setq noedit @@ -2603,7 +2642,7 @@ It must return a string." next-replacement nocasify literal noedit real-match-data backward) replace-count (1+ replace-count))) - (setq done t replaced t)) + (setq done t replaced t update-stack (not last-was-act-and-show))) ((eq def 'act-and-exit) (or replaced (setq noedit @@ -2614,7 +2653,7 @@ It must return a string." (setq keep-going nil) (setq done t replaced t)) ((eq def 'act-and-show) - (if (not replaced) + (unless replaced (setq noedit (replace-match-maybe-edit next-replacement nocasify literal @@ -2622,7 +2661,11 @@ It must return a string." replace-count (1+ replace-count) real-match-data (replace-match-data t real-match-data) - replaced t))) + replaced t last-was-act-and-show t) + (replace--push-stack + replaced + search-string-replaced + next-replacement-replaced stack))) ((or (eq def 'automatic) (eq def 'automatic-all)) (or replaced (setq noedit @@ -2633,7 +2676,7 @@ It must return a string." (setq done t query-flag nil replaced t) (if (eq def 'automatic-all) (setq multi-buffer t))) ((eq def 'skip) - (setq done t)) + (setq done t update-stack (not last-was-act-and-show))) ((eq def 'recenter) ;; `this-command' has the value `query-replace', ;; so we need to bind it to `recenter-top-bottom' @@ -2703,27 +2746,14 @@ It must return a string." ;; Record previous position for ^ when we move on. ;; Change markers to numbers in the match data ;; since lots of markers slow down editing. - (push (list (point) replaced -;;; If the replacement has already happened, all we need is the -;;; current match start and end. We could get this with a trivial -;;; match like -;;; (save-excursion (goto-char (match-beginning 0)) -;;; (search-forward (match-string 0)) -;;; (match-data t)) -;;; if we really wanted to avoid manually constructing match data. -;;; Adding current-buffer is necessary so that match-data calls can -;;; return markers which are appropriate for editing. - (if replaced - (list - (match-beginning 0) - (match-end 0) - (current-buffer)) - (match-data t)) - search-string-replaced - next-replacement-replaced) - stack) + (when update-stack + (replace--push-stack + replaced + search-string-replaced + next-replacement-replaced stack)) (setq next-replacement-replaced nil - search-string-replaced nil)))))) + search-string-replaced nil + last-was-act-and-show nil)))))) (replace-dehighlight)) (or unread-command-events (message "Replaced %d occurrence%s%s" diff --git a/lisp/reposition.el b/lisp/reposition.el index ce24d29e5f2..833b65ac52b 100644 --- a/lisp/reposition.el +++ b/lisp/reposition.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/reveal.el b/lisp/reveal.el index 66f5bc47554..1b6cd335d77 100644 --- a/lisp/reveal.el +++ b/lisp/reveal.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/rfn-eshadow.el b/lisp/rfn-eshadow.el index f5df7f80f91..66204125d58 100644 --- a/lisp/rfn-eshadow.el +++ b/lisp/rfn-eshadow.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/rot13.el b/lisp/rot13.el index 20a0dbed462..886085b8265 100644 --- a/lisp/rot13.el +++ b/lisp/rot13.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/rtree.el b/lisp/rtree.el index b4c9d48b83c..9db03c474d3 100644 --- a/lisp/rtree.el +++ b/lisp/rtree.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/ruler-mode.el b/lisp/ruler-mode.el index 7b0588dfead..cac91e421e0 100644 --- a/lisp/ruler-mode.el +++ b/lisp/ruler-mode.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -304,7 +304,15 @@ or remove a tab stop. \\[ruler-mode-toggle-show-tab-stops] or (defsubst ruler-mode-window-col (n) "Return a column number relative to the selected window. -N is a column number relative to selected frame." +N is a column number relative to selected frame. +If required, account for screen estate taken by `display-line-numbers'." + (if display-line-numbers + ;; FIXME: ruler-mode relies on N being an integer, so if the + ;; 'line-number' face is customized to use a font that is larger + ;; or smaller than that of the default face, the alignment might + ;; be off by up to half a column, unless the font width is an + ;; integral multiple or divisor of the default face's font. + (setq n (- n (round (line-number-display-width 'columns))))) (- n (or (car (window-margins)) 0) (fringe-columns 'left) @@ -665,7 +673,12 @@ Optional argument PROPS specifies other text properties to apply." (let* ((w (ruler-mode-text-scaled-window-width)) (m (window-margins)) (f (window-fringes)) - (i 0) + (i (if display-line-numbers + ;; FIXME: ruler-mode relies on I being an integer, so + ;; the column numbers might be slightly off if the + ;; line-number face is customized. + (round (line-number-display-width 'columns)) + 0)) (j (ruler-mode-text-scaled-window-hscroll)) ;; Setup the scrollbar, fringes, and margins areas. (lf (ruler-mode-space @@ -696,8 +709,18 @@ Optional argument PROPS specifies other text properties to apply." ;; Create an "clean" ruler. (ruler (propertize - (string-to-multibyte - (make-string w ruler-mode-basic-graduation-char)) + ;; Make the part of header-line corresponding to the + ;; line-number display be blank, not filled with + ;; ruler-mode-basic-graduation-char. + (if display-line-numbers + (let* ((lndw (round (line-number-display-width 'columns))) + ;; We need a multibyte string here so we could + ;; later use aset to insert multibyte characters + ;; into that string. + (s (make-string lndw ?\s t))) + (concat s (make-string (- w lndw) + ruler-mode-basic-graduation-char t))) + (make-string w ruler-mode-basic-graduation-char t)) 'face 'ruler-mode-default 'local-map ruler-mode-map 'help-echo (cond diff --git a/lisp/savehist.el b/lisp/savehist.el index 9a3c5cfc4d6..c1f17f76617 100644 --- a/lisp/savehist.el +++ b/lisp/savehist.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/saveplace.el b/lisp/saveplace.el index 603ab65d717..54599c7e11f 100644 --- a/lisp/saveplace.el +++ b/lisp/saveplace.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/sb-image.el b/lisp/sb-image.el index b94978a8df2..6faa66d1528 100644 --- a/lisp/sb-image.el +++ b/lisp/sb-image.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/scroll-all.el b/lisp/scroll-all.el index 59efe8c11b4..90365fae3fb 100644 --- a/lisp/scroll-all.el +++ b/lisp/scroll-all.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; This mode allows multiple buffers to be 'locked' so that scrolling diff --git a/lisp/scroll-bar.el b/lisp/scroll-bar.el index 58352740447..8f02f2f3e9d 100644 --- a/lisp/scroll-bar.el +++ b/lisp/scroll-bar.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/scroll-lock.el b/lisp/scroll-lock.el index 50868e7257c..837189c2129 100644 --- a/lisp/scroll-lock.el +++ b/lisp/scroll-lock.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/select.el b/lisp/select.el index 4849d7d515e..54acb5292e6 100644 --- a/lisp/select.el +++ b/lisp/select.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -98,7 +98,7 @@ if applicable (i.e. under X11)." 'select-enable-clipboard "25.1") (defcustom select-enable-primary nil - "Non-nil means cutting and pasting uses the primary selection + "Non-nil means cutting and pasting uses the primary selection. The existence of a primary selection depends on the underlying GUI you use. E.g. it doesn't exist under MS-Windows." :type 'boolean @@ -475,6 +475,9 @@ two markers or an overlay. Otherwise, it is nil." (t (error "Unknown selection type: %S" type))))) + ;; Most programs are unable to handle NUL bytes in strings. + (setq str (replace-regexp-in-string "\0" "\\0" str t t)) + (setq next-selection-coding-system nil) (cons type str)))) diff --git a/lisp/server.el b/lisp/server.el index 209bfaaf701..0e225f723ea 100644 --- a/lisp/server.el +++ b/lisp/server.el @@ -23,7 +23,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -525,30 +525,38 @@ Creates the directory if necessary and makes sure: ;; Check that it's safe for use. (let* ((uid (nth 2 attrs)) (w32 (eq system-type 'windows-nt)) - (safe (cond - ((not (eq t (car attrs))) nil) ; is a dir? - ((and w32 (zerop uid)) ; on FAT32? - (display-warning - 'server - (format-message "\ + (unsafe (cond + ((not (eq t (car attrs))) + (if (null attrs) "its attributes can't be checked" + (format "it is a %s" + (if (stringp (car attrs)) + "symlink" "file")))) + ((and w32 (zerop uid)) ; on FAT32? + (display-warning + 'server + (format-message "\ Using `%s' to store Emacs-server authentication files. Directories on FAT32 filesystems are NOT secure against tampering. See variable `server-auth-dir' for details." - (file-name-as-directory dir)) - :warning) - t) - ((and (/= uid (user-uid)) ; is the dir ours? - (or (not w32) - ;; Files created on Windows by Administrator - ;; (RID=500) have the Administrators (RID=544) - ;; group recorded as the owner. - (/= uid 544) (/= (user-uid) 500))) - nil) - (w32 t) ; on NTFS? - (t ; else, check permissions - (zerop (logand ?\077 (file-modes dir))))))) - (unless safe - (error "The directory `%s' is unsafe" dir))))) + (file-name-as-directory dir)) + :warning) + nil) + ((and (/= uid (user-uid)) ; is the dir ours? + (or (not w32) + ;; Files created on Windows by Administrator + ;; (RID=500) have the Administrators (RID=544) + ;; group recorded as the owner. + (/= uid 544) (/= (user-uid) 500))) + (format "it is not owned by you (owner = %s (%d))" + (user-full-name uid) uid)) + (w32 nil) ; on NTFS? + ((/= 0 (logand ?\077 (file-modes dir))) + (format "it is accessible by others (%03o)" + (file-modes dir))) + (t nil)))) + (when unsafe + (error "`%s' is not a safe directory because %s" + (expand-file-name dir) unsafe))))) (defun server-generate-key () "Generate and return a random authentication key. diff --git a/lisp/ses.el b/lisp/ses.el index fd7174d383d..4c19c70c5da 100644 --- a/lisp/ses.el +++ b/lisp/ses.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -167,12 +167,32 @@ Each function is called with ARG=1." ["Export values" ses-export-tsv t] ["Export formulas" ses-export-tsf t])) +(defconst ses-completion-keys '("\M-\C-i" "\C-i") + "List for keys that can be used for completion while editing.") + +(defvar ses--completion-table nil + "Set globally to what completion table to use depending on type + of completion (local printers, cells, etc.). We need to go + through a local variable to pass the SES buffer local variable + to completing function while the current buffer is the + minibuffer.") + +(defvar ses--list-orig-buffer nil + "Calling buffer for SES listing help. Used for listing local + printers or renamed cells.") + + (defconst ses-mode-edit-map (let ((keys '("\C-c\C-r" ses-insert-range "\C-c\C-s" ses-insert-ses-range [S-mouse-3] ses-insert-range-click [C-S-mouse-3] ses-insert-ses-range-click - "\M-\C-i" lisp-complete-symbol)) ; FIXME obsolete + "\C-h\C-p" ses-list-local-printers + "\C-h\C-n" ses-list-named-cells + "\M-\C-i" lisp-complete-symbol)) ; redefined + ; dynamically in + ; editing + ; functions (newmap (make-sparse-keymap))) (set-keymap-parent newmap minibuffer-local-map) (while keys @@ -437,7 +457,7 @@ is nil if SYM is not a symbol that names a cell." (declare (debug t)) `(let ((rc (and (symbolp ,sym) (get ,sym 'ses-cell)))) (if (eq rc :ses-named) - (gethash ,sym ses--named-cell-hashmap) + (and ses--named-cell-hashmap (gethash ,sym ses--named-cell-hashmap)) rc))) (defun ses-cell-p (cell) @@ -868,27 +888,39 @@ means Emacs will crash if FORMULA contains a circular list." (oldref (ses-formula-references old)) (newref (ses-formula-references formula)) (inhibit-quit t) + not-a-cell-ref-list x xrow xcol) (cl-pushnew sym ses--deferred-recalc) ;;Delete old references from this cell. Skip the ones that are also ;;in the new list. (dolist (ref oldref) (unless (memq ref newref) - (setq x (ses-sym-rowcol ref) - xrow (car x) - xcol (cdr x)) - (ses-set-cell xrow xcol 'references - (delq sym (ses-cell-references xrow xcol))))) + ;; because we do not cancel edit when the user provides a + ;; false reference in it, then we need to check that ref + ;; points to a cell that is within the spreadsheet. + (setq x (ses-sym-rowcol ref)) + (and x + (< (setq xrow (car x)) ses--numrows) + (< (setq xcol (cdr x)) ses--numcols) + (ses-set-cell xrow xcol 'references + (delq sym (ses-cell-references xrow xcol)))))) ;;Add new ones. Skip ones left over from old list (dolist (ref newref) - (setq x (ses-sym-rowcol ref) - xrow (car x) - xcol (cdr x) - x (ses-cell-references xrow xcol)) - (or (memq sym x) - (ses-set-cell xrow xcol 'references (cons sym x)))) + (setq x (ses-sym-rowcol ref)) + ;;Do not trust the user, the reference may be outside the spreadsheet + (if (and + x + (< (setq xrow (car x)) ses--numrows) + (< (setq xcol (cdr x)) ses--numcols)) + (progn + (setq x (ses-cell-references xrow xcol)) + (or (memq sym x) + (ses-set-cell xrow xcol 'references (cons sym x)))) + (cl-pushnew ref not-a-cell-ref-list))) (ses-formula-record formula) - (ses-set-cell row col 'formula formula)))) + (ses-set-cell row col 'formula formula) + (and not-a-cell-ref-list + (error "Found in formula cells not in spreadsheet: %S" not-a-cell-ref-list))))) (defun ses-repair-cell-reference-all () @@ -1222,8 +1254,7 @@ preceding cell has spilled over." ((< len width) ;; Fill field to length with spaces. (setq len (make-string (- width len) ?\s) - text (if (or (stringp value) - (eq ses-call-printer-return t)) + text (if (eq ses-call-printer-return t) (concat text len) (concat len text)))) ((> len width) @@ -1529,7 +1560,13 @@ by (ROWINCR,COLINCR)." ;;Relocate this variable, unless it is a named cell (if (eq (get sym 'ses-cell) :ses-named) sym - (ses-create-cell-symbol row col)) + ;; otherwise, we create the relocated cell symbol because + ;; ses-cell-symbol gives the old symbols, however since + ;; renamed cell are not relocated we keep the relocated + ;; cell old symbol in this case. + (if (eq (get (setq sym (ses-cell-symbol row col)) 'ses-cell) :ses-named) + sym + (ses-create-cell-symbol row col))) ;;Delete reference to a deleted cell nil)))) @@ -1697,7 +1734,7 @@ to each symbol." (set (make-local-variable sym) nil) (put sym 'ses-cell (cons row col)))))) ))) ;; Relocate the cell values. - (let (oldval myrow mycol xrow xcol) + (let (oldval myrow mycol xrow xcol sym) (cond ((and (<= rowincr 0) (<= colincr 0)) ;; Deletion of rows and/or columns. @@ -1707,16 +1744,16 @@ to each symbol." (dotimes (col (- ses--numcols mincol)) (setq mycol (+ col mincol) xrow (- myrow rowincr) - xcol (- mycol colincr)) - (let ((sym (ses-cell-symbol myrow mycol))) - ;; We don't need to relocate value for renamed cells, as they keep the same - ;; symbol. - (unless (eq (get sym 'ses-cell) :ses-named) - (ses-set-cell myrow mycol 'value - (if (and (< xrow ses--numrows) (< xcol ses--numcols)) - (ses-cell-value xrow xcol) - ;; Cell is off the end of the array. - (symbol-value (ses-create-cell-symbol xrow xcol)))))))) + xcol (- mycol colincr) + sym (ses-cell-symbol myrow mycol)) + ;; We don't need to relocate value for renamed cells, as they keep the same + ;; symbol. + (unless (eq (get sym 'ses-cell) :ses-named) + (ses-set-cell myrow mycol 'value + (if (and (< xrow ses--numrows) (< xcol ses--numcols)) + (ses-cell-value xrow xcol) + ;; Cell is off the end of the array. + (symbol-value (ses-create-cell-symbol xrow xcol))))))) (when ses--in-killing-named-cell-list (message "Unbinding killed named cell symbols...") (setq ses-start-time (float-time)) @@ -1736,13 +1773,17 @@ to each symbol." (dotimes (col (- ses--numcols mincol)) (setq mycol (- distx col) xrow (- myrow rowincr) - xcol (- mycol colincr)) - (if (or (< xrow minrow) (< xcol mincol)) - ;; Newly-inserted value. - (setq oldval nil) - ;; Transfer old value. - (setq oldval (ses-cell-value xrow xcol))) - (ses-set-cell myrow mycol 'value oldval))) + xcol (- mycol colincr) + sym (ses-cell-symbol myrow mycol)) + ;; We don't need to relocate value for renamed cells, as they keep the same + ;; symbol. + (unless (eq (get sym 'ses-cell) :ses-named) + (if (or (< xrow minrow) (< xcol mincol)) + ;; Newly-inserted value. + (setq oldval nil) + ;; Transfer old value. + (setq oldval (ses-cell-value xrow xcol))) + (ses-set-cell myrow mycol 'value oldval)))) t)) ; Make testcover happy by returning non-nil here. (t (error "ROWINCR and COLINCR must have the same sign")))) @@ -1862,7 +1903,7 @@ Does not execute cell formulas or print functions." (setq ses--numlocprn 0) (dotimes (_ numlocprn) (let ((x (read (current-buffer)))) - (or (and (looking-at-p "\n") + (or (and (= (following-char) ?\n) (eq (car-safe x) 'ses-local-printer) (apply #'ses--local-printer (cdr x))) (error "local printer-def error")) @@ -1872,7 +1913,7 @@ Does not execute cell formulas or print functions." (dotimes (col ses--numcols) (let* ((x (read (current-buffer))) (sym (car-safe (cdr-safe x)))) - (or (and (looking-at-p "\n") + (or (and (= (following-char) ?\n) (eq (car-safe x) 'ses-cell) (ses-create-cell-variable sym row col)) (error "Cell-def error")) @@ -2337,7 +2378,8 @@ to are recalculated first." "Recalculate and reprint all cells." (interactive "*") (let ((startcell (ses--cell-at-pos (point))) - (ses--curcell (cons 'A1 (ses-cell-symbol (1- ses--numrows) + (ses--curcell (cons (ses-cell-symbol 0 0) + (ses-cell-symbol (1- ses--numrows) (1- ses--numcols))))) (ses-recalculate-cell ses--curcell) (ses-jump-safe startcell))) @@ -2424,6 +2466,42 @@ to are recalculated first." ;;---------------------------------------------------------------------------- ;; Input of cell formulas ;;---------------------------------------------------------------------------- +(defun ses-edit-cell-complete-symbol () + (interactive) + (let ((completion-at-point-functions (cons 'ses--edit-cell-completion-at-point-function + completion-at-point-functions))) + (completion-at-point))) + +(defun ses--edit-cell-completion-at-point-function () + (and + ses--completion-table + (let* ((bol (save-excursion (move-beginning-of-line nil) (point))) + start end collection + (prefix + (save-excursion + (setq end (point)) + (backward-sexp) + (if (< (point) bol) + (progn + (setq start bol) + (buffer-substring start end)) + (setq start (point)) + (forward-sexp) + (if (>= (point) end) + (progn + (setq end (point)) + (buffer-substring start end)) + nil)))) + prefix-length) + (when (and prefix (null (string= prefix ""))) + (setq prefix-length (length prefix)) + (maphash (lambda (key val) + (let ((key-name (symbol-name key))) + (when (and (>= (length key-name) prefix-length) + (string= prefix (substring key-name 0 prefix-length))) + (push key-name collection)))) + ses--completion-table) + (and collection (list start end collection)))))) (defun ses-edit-cell (row col newval) "Display current cell contents in minibuffer, for editing. Returns nil if @@ -2445,6 +2523,10 @@ cell formula was unsafe and user declined confirmation." (if (stringp formula) ;; Position cursor inside close-quote. (setq initial (cons initial (length initial)))) + (dolist (key ses-completion-keys) + (define-key ses-mode-edit-map key 'ses-edit-cell-complete-symbol)) + ;; make it globally visible, so that it can be visible from the minibuffer. + (setq ses--completion-table ses--named-cell-hashmap) (list row col (read-from-minibuffer (format "Cell %s: " ses--curcell) initial @@ -2539,6 +2621,40 @@ cells." ;;---------------------------------------------------------------------------- ;; Input of cell-printer functions ;;---------------------------------------------------------------------------- +(defun ses-read-printer-complete-symbol () + (interactive) + (let ((completion-at-point-functions (cons 'ses--read-printer-completion-at-point-function + completion-at-point-functions))) + (completion-at-point))) + +(defun ses--read-printer-completion-at-point-function () + (let* ((bol (save-excursion (move-beginning-of-line nil) (point))) + start end collection + (prefix + (save-excursion + (setq end (point)) + (backward-sexp) + (if (< (point) bol) + (progn + (setq start bol) + (buffer-substring start end)) + (setq start (point)) + (forward-sexp) + (if (>= (point) end) + (progn + (setq end (point)) + (buffer-substring start end)) + nil)))) + prefix-length) + (when prefix + (setq prefix-length (length prefix)) + (maphash (lambda (key val) + (let ((key-name (symbol-name key))) + (when (and (>= (length key-name) prefix-length) + (string= prefix (substring key-name 0 prefix-length))) + (push key-name collection)))) + ses--completion-table) + (and collection (list start end collection))))) (defun ses-read-printer (prompt default) "Common code for functions `ses-read-cell-printer', `ses-read-column-printer', @@ -2551,6 +2667,10 @@ canceled." (setq prompt (format "%s (default %S): " (substring prompt 0 -2) default))) + (dolist (key ses-completion-keys) + (define-key ses-mode-edit-map key 'ses-read-printer-complete-symbol)) + ;; make it globally visible, so that it can be visible from the minibuffer. + (setq ses--completion-table ses--local-printer-hashmap) (let ((new (read-from-minibuffer prompt nil ; Initial contents. ses-mode-edit-map @@ -3259,6 +3379,78 @@ is non-nil. Newlines and tabs in the export text are escaped." (setq result (apply #'concat (nreverse result))) (kill-new result))) +;;---------------------------------------------------------------------------- +;; Interactive help on symbols +;;---------------------------------------------------------------------------- + +(defun ses-list-local-printers (&optional local-printer-hashmap) + "List local printers in a help buffer. Can be called either +during editing a printer or a formula, or while in the SES +buffer." + (interactive + (list (cond + ((derived-mode-p 'ses-mode) ses--local-printer-hashmap) + ((minibufferp) ses--completion-table) + ((derived-mode-p 'help-mode) nil) + (t (error "Not in a SES buffer"))))) + (when local-printer-hashmap + (let ((ses--list-orig-buffer (or ses--list-orig-buffer (current-buffer)))) + (help-setup-xref + (list (lambda (local-printer-hashmap buffer) + (let ((ses--list-orig-buffer + (if (buffer-live-p buffer) buffer))) + (ses-list-local-printers local-printer-hashmap))) + local-printer-hashmap ses--list-orig-buffer) + (called-interactively-p 'interactive)) + + (save-excursion + (with-help-window (help-buffer) + (if (= 0 (hash-table-count local-printer-hashmap)) + (princ "No local printers defined.") + (princ "List of local printers definitions:\n") + (maphash (lambda (key val) + (princ key) + (princ " as ") + (prin1 (ses--locprn-def val)) + (princ "\n")) + local-printer-hashmap)) + (with-current-buffer standard-output + (buffer-string))))))) + +(defun ses-list-named-cells (&optional named-cell-hashmap) + "List named cells in a help buffer. Can be called either +during editing a printer or a formula, or while in the SES +buffer." + (interactive + (list (cond + ((derived-mode-p 'ses-mode) ses--named-cell-hashmap) + ((minibufferp) ses--completion-table) + ((derived-mode-p 'help-mode) nil) + (t (error "Not in a SES buffer"))))) + (when named-cell-hashmap + (let ((ses--list-orig-buffer (or ses--list-orig-buffer (current-buffer)))) + (help-setup-xref + (list (lambda (named-cell-hashmap buffer) + (let ((ses--list-orig-buffer + (if (buffer-live-p buffer) buffer))) + (ses-list-named-cells named-cell-hashmap))) + named-cell-hashmap ses--list-orig-buffer) + (called-interactively-p 'interactive)) + + (save-excursion + (with-help-window (help-buffer) + (if (= 0 (hash-table-count named-cell-hashmap)) + (princ "No cell was renamed.") + (princ "List of named cells definitions:\n") + (maphash (lambda (key val) + (princ key) + (princ " for ") + (prin1 (ses-create-cell-symbol (car val) (cdr val))) + (princ "\n")) + named-cell-hashmap)) + (with-current-buffer standard-output + (buffer-string))))))) + ;;---------------------------------------------------------------------------- ;; Other user commands @@ -3441,8 +3633,12 @@ highlighted range in the spreadsheet." (defun ses-replace-name-in-formula (formula old-name new-name) (let ((new-formula formula)) - (unless (and (consp formula) - (eq (car-safe formula) 'quote)) + (cond + ((eq (car-safe formula) 'quote)) + ((symbolp formula) + (if (eq formula old-name) + (setq new-formula new-name))) + ((consp formula) (while formula (let ((elt (car-safe formula))) (cond @@ -3451,8 +3647,8 @@ highlighted range in the spreadsheet." ((and (symbolp elt) (eq (car-safe formula) old-name)) (setcar formula new-name)))) - (setq formula (cdr formula)))) - new-formula)) + (setq formula (cdr formula))))) + new-formula)) (defun ses-rename-cell (new-name &optional cell) "Rename current cell." @@ -3477,9 +3673,10 @@ highlighted range in the spreadsheet." (rowcol (ses-sym-rowcol sym)) (row (car rowcol)) (col (cdr rowcol)) - new-rowcol old-name) + new-rowcol old-name old-value) (setq cell (or cell (ses-get-cell row col)) old-name (ses-cell-symbol cell) + old-value (symbol-value old-name) new-rowcol (ses-decode-cell-symbol (symbol-name new-name))) ;; when ses-rename-cell is called interactively, then 'sym' is the ;; 'cursor-intangible' property of text at cursor position, while @@ -3499,10 +3696,12 @@ highlighted range in the spreadsheet." (put new-name 'ses-cell :ses-named) (puthash new-name rowcol ses--named-cell-hashmap)) (push `(ses-rename-cell ,old-name ,cell) buffer-undo-list) + (cl-pushnew rowcol ses--deferred-write :test #'equal) ;; Replace name by new name in formula of cells refering to renamed cell. (dolist (ref (ses-cell-references cell)) (let* ((x (ses-sym-rowcol ref)) (xcell (ses-get-cell (car x) (cdr x)))) + (cl-pushnew x ses--deferred-write :test #'equal) (setf (ses-cell-formula xcell) (ses-replace-name-in-formula (ses-cell-formula xcell) @@ -3513,11 +3712,14 @@ highlighted range in the spreadsheet." (dolist (ref (ses-formula-references (ses-cell-formula cell))) (let* ((x (ses-sym-rowcol ref)) (xcell (ses-get-cell (car x) (cdr x)))) + (cl-pushnew x ses--deferred-write :test #'equal) (setf (ses-cell-references xcell) (cons new-name (delq old-name (ses-cell-references xcell)))))) (set (make-local-variable new-name) (symbol-value sym)) (setf (ses-cell--symbol cell) new-name) + ;; set new name to value + (set new-name old-value) ;; Unbind old name (if (eq (get old-name 'ses-cell) :ses-named) (ses--unbind-cell-name old-name) diff --git a/lisp/shadowfile.el b/lisp/shadowfile.el index 21d0f0a40bd..53718ab082a 100644 --- a/lisp/shadowfile.el +++ b/lisp/shadowfile.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/shell.el b/lisp/shell.el index c5e5cbbee7e..9c837629243 100644 --- a/lisp/shell.el +++ b/lisp/shell.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -264,7 +264,9 @@ see the function `dirtrack-mode'." :group 'shell-directories) (defcustom explicit-shell-file-name nil - "If non-nil, is file name to use for explicitly requested inferior shell." + "If non-nil, is file name to use for explicitly requested inferior shell. +When nil, such interactive shell sessions fallback to using either +the shell specified in $ESHELL or in `shell-file-name'." :type '(choice (const :tag "None" nil) file) :group 'shell) diff --git a/lisp/simple.el b/lisp/simple.el index a5565ab6e73..24ecf6929d9 100644 --- a/lisp/simple.el +++ b/lisp/simple.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -39,11 +39,11 @@ (defcustom shell-command-dont-erase-buffer nil "If non-nil, output buffer is not erased between shell commands. -Also, a non-nil value set the point in the output buffer -once the command complete. -The value `beg-last-out' set point at the beginning of the output, -`end-last-out' set point at the end of the buffer, `save-point' -restore the buffer position before the command." +Also, a non-nil value sets the point in the output buffer +once the command completes. +The value `beg-last-out' sets point at the beginning of the output, +`end-last-out' sets point at the end of the buffer, `save-point' +restores the buffer position before the command." :type '(choice (const :tag "Erase buffer" nil) (const :tag "Set point to beginning of last output" beg-last-out) @@ -53,9 +53,10 @@ restore the buffer position before the command." :version "26.1") (defvar shell-command-saved-pos nil - "Point position in the output buffer after command complete. -It is an alist (BUFFER . POS), where BUFFER is the output -buffer, and POS is the point position in BUFFER once the command finish. + "Record of point positions in output buffers after command completion. +The value is an alist whose elements are of the form (BUFFER . POS), +where BUFFER is the output buffer, and POS is the point position +in BUFFER once the command finishes. This variable is used when `shell-command-dont-erase-buffer' is non-nil.") (defcustom idle-update-delay 0.5 @@ -278,23 +279,28 @@ To control which errors are matched, customize the variable `compilation-error-regexp-alist'." (interactive "P") (if (consp arg) (setq reset t arg nil)) - (when (setq next-error-last-buffer (next-error-find-buffer)) - ;; we know here that next-error-function is a valid symbol we can funcall - (with-current-buffer next-error-last-buffer - (funcall next-error-function (prefix-numeric-value arg) reset) - (when next-error-recenter - (recenter next-error-recenter)) - (run-hooks 'next-error-hook)))) + (let ((buffer (next-error-find-buffer))) + (when buffer + ;; We know here that next-error-function is a valid symbol we can funcall + (with-current-buffer buffer + (funcall next-error-function (prefix-numeric-value arg) reset) + ;; Override possible change of next-error-last-buffer in next-error-function + (setq next-error-last-buffer buffer) + (when next-error-recenter + (recenter next-error-recenter)) + (run-hooks 'next-error-hook))))) (defun next-error-internal () "Visit the source code corresponding to the `next-error' message at point." - (setq next-error-last-buffer (current-buffer)) - ;; we know here that next-error-function is a valid symbol we can funcall - (with-current-buffer next-error-last-buffer - (funcall next-error-function 0 nil) - (when next-error-recenter - (recenter next-error-recenter)) - (run-hooks 'next-error-hook))) + (let ((buffer (current-buffer))) + ;; We know here that next-error-function is a valid symbol we can funcall + (with-current-buffer buffer + (funcall next-error-function 0 nil) + ;; Override possible change of next-error-last-buffer in next-error-function + (setq next-error-last-buffer buffer) + (when next-error-recenter + (recenter next-error-recenter)) + (run-hooks 'next-error-hook)))) (defalias 'goto-next-locus 'next-error) (defalias 'next-match 'next-error) @@ -400,9 +406,18 @@ Other major modes are defined by comparison with this one." (defvar self-insert-uses-region-functions nil "Special hook to tell if `self-insert-command' will use the region. It must be called via `run-hook-with-args-until-success' with no arguments. -Any `post-self-insert-command' which consumes the region should -register a function on this hook so that things like `delete-selection-mode' -can refrain from consuming the region.") + +If any function on this hook returns a non-nil value, `delete-selection-mode' +will act on that value (see `delete-selection-helper'), and will +usually delete the region. If all the functions on this hook return +nil, it is an indiction that `self-insert-command' needs the region +untouched by `delete-selection-mode', and will itself do whatever is +appropriate with the region. +Any function on `post-self-insert-hook' which act on the region should +add a function to this hook so that `delete-selection-mode' could +refrain from deleting the region before `post-self-insert-hook' +functions are called. +This hook is run by `delete-selection-uses-region-p', which see.") (defvar hard-newline (propertize "\n" 'hard t 'rear-nonsticky '(hard)) "Propertized string representing a hard newline character.") @@ -434,10 +449,6 @@ A non-nil INTERACTIVE argument means to run the `post-self-insert-hook'." ;; Do the rest in post-self-insert-hook, because we want to do it ;; *before* other functions on that hook. (lambda () - ;; We are not going to insert any newlines if arg is - ;; non-positive. - (or (and (numberp arg) (<= arg 0)) - (cl-assert (eq ?\n (char-before)))) ;; Mark the newline(s) `hard'. (if use-hard-newlines (set-hard-newline-properties @@ -456,25 +467,22 @@ A non-nil INTERACTIVE argument means to run the `post-self-insert-hook'." ;; starts a page. (or was-page-start (move-to-left-margin nil t))))) - (unwind-protect - (if (not interactive) - ;; FIXME: For non-interactive uses, many calls actually - ;; just want (insert "\n"), so maybe we should do just - ;; that, so as to avoid the risk of filling or running - ;; abbrevs unexpectedly. - (let ((post-self-insert-hook (list postproc))) - (self-insert-command arg)) - (unwind-protect - (progn - (add-hook 'post-self-insert-hook postproc nil t) - (self-insert-command arg)) - ;; We first used let-binding to protect the hook, but that - ;; was naive since add-hook affects the symbol-default - ;; value of the variable, whereas the let-binding might - ;; only protect the buffer-local value. - (remove-hook 'post-self-insert-hook postproc t))) - (cl-assert (not (member postproc post-self-insert-hook))) - (cl-assert (not (member postproc (default-value 'post-self-insert-hook)))))) + (if (not interactive) + ;; FIXME: For non-interactive uses, many calls actually + ;; just want (insert "\n"), so maybe we should do just + ;; that, so as to avoid the risk of filling or running + ;; abbrevs unexpectedly. + (let ((post-self-insert-hook (list postproc))) + (self-insert-command arg)) + (unwind-protect + (progn + (add-hook 'post-self-insert-hook postproc nil t) + (self-insert-command arg)) + ;; We first used let-binding to protect the hook, but that + ;; was naive since add-hook affects the symbol-default + ;; value of the variable, whereas the let-binding might + ;; only protect the buffer-local value. + (remove-hook 'post-self-insert-hook postproc t)))) nil) (defun set-hard-newline-properties (from to) @@ -996,23 +1004,24 @@ instead of deleted." :version "24.1") (defvar region-extract-function - (lambda (delete) + (lambda (method) (when (region-beginning) (cond - ((eq delete 'bounds) + ((eq method 'bounds) (list (cons (region-beginning) (region-end)))) - ((eq delete 'delete-only) + ((eq method 'delete-only) (delete-region (region-beginning) (region-end))) (t - (filter-buffer-substring (region-beginning) (region-end) delete))))) + (filter-buffer-substring (region-beginning) (region-end) method))))) "Function to get the region's content. -Called with one argument DELETE. -If DELETE is `delete-only', then only delete the region and the return value -is undefined. If DELETE is nil, just return the content as a string. -If DELETE is `bounds', then don't delete, but just return the -boundaries of the region as a list of (START . END) positions. -If anything else, delete the region and return its content as a string, -after filtering it with `filter-buffer-substring'.") +Called with one argument METHOD. +If METHOD is `delete-only', then delete the region; the return value +is undefined. If METHOD is nil, then return the content as a string. +If METHOD is `bounds', then return the boundaries of the region +as a list of cons cells of the form (START . END). +If METHOD is anything else, delete the region and return its content +as a string, after filtering it with `filter-buffer-substring', which +is called with METHOD as its 3rd argument.") (defvar region-insert-function (lambda (lines) @@ -1270,18 +1279,25 @@ and the greater of them is not at the start of a line." done))) (- (buffer-size) (forward-line (buffer-size))))))) -(defun line-number-at-pos (&optional pos) - "Return (narrowed) buffer line number at position POS. +(defun line-number-at-pos (&optional pos absolute) + "Return buffer line number at position POS. If POS is nil, use current buffer location. -Counting starts at (point-min), so the value refers -to the contents of the accessible portion of the buffer." - (let ((opoint (or pos (point))) start) - (save-excursion - (goto-char (point-min)) - (setq start (point)) - (goto-char opoint) - (forward-line 0) - (1+ (count-lines start (point)))))) + +If ABSOLUTE is nil, the default, counting starts +at (point-min), so the value refers to the contents of the +accessible portion of the (potentially narrowed) buffer. If +ABSOLUTE is non-nil, ignore any narrowing and return the +absolute line number." + (save-restriction + (when absolute + (widen)) + (let ((opoint (or pos (point))) start) + (save-excursion + (goto-char (point-min)) + (setq start (point)) + (goto-char opoint) + (forward-line 0) + (1+ (count-lines start (point))))))) (defun what-cursor-position (&optional detail) "Print info on cursor position (on screen and within buffer). @@ -2563,10 +2579,10 @@ Return what remains of the list." (setq did-apply t))) ;; Element (STRING . POS) means STRING was deleted. (`(,(and string (pred stringp)) . ,(and pos (pred integerp))) - (when (let ((apos (abs pos))) - (or (< apos (point-min)) (> apos (point-max)))) - (error "Changes to be undone are outside visible portion of buffer")) - (let (valid-marker-adjustments) + (let ((valid-marker-adjustments nil) + (apos (abs pos))) + (when (or (< apos (point-min)) (> apos (point-max))) + (error "Changes to be undone are outside visible portion of buffer")) ;; Check that marker adjustments which were recorded ;; with the (STRING . POS) record are still valid, ie ;; the markers haven't moved. We check their validity @@ -2577,7 +2593,7 @@ Return what remains of the list." (let* ((marker-adj (pop list)) (m (car marker-adj))) (and (eq (marker-buffer m) (current-buffer)) - (= pos m) + (= apos m) (push marker-adj valid-marker-adjustments)))) ;; Insert string and adjust point (if (< pos 0) @@ -3271,6 +3287,17 @@ output buffer and running a new command in the default buffer, :group 'shell :version "24.3") +(defcustom async-shell-command-display-buffer t + "Whether to display the command buffer immediately. +If t, display the buffer immediately; if nil, wait until there +is output." + :type '(choice (const :tag "Display buffer immediately" + t) + (const :tag "Display buffer on output" + nil)) + :group 'shell + :version "26.1") + (defun shell-command--save-pos-or-erase () "Store a buffer position or erase the buffer. See `shell-command-dont-erase-buffer'." @@ -3380,10 +3407,10 @@ The optional second argument OUTPUT-BUFFER, if non-nil, says to put the output in some other buffer. If OUTPUT-BUFFER is a buffer or buffer name, erase that buffer and insert the output there; a non-nil value of -`shell-command-dont-erase-buffer' prevent to erase the buffer. -If OUTPUT-BUFFER is not a buffer and not nil, insert the output -in current buffer after point leaving mark after it. -This cannot be done asynchronously. +`shell-command-dont-erase-buffer' prevents the buffer from being +erased. If OUTPUT-BUFFER is not a buffer and not nil, insert the +output in current buffer after point leaving mark after it. This +cannot be done asynchronously. If the command terminates without error, but generates output, and you did not specify \"insert it in the current buffer\", @@ -3391,7 +3418,7 @@ the output can be displayed in the echo area or in its buffer. If the output is short enough to display in the echo area \(determined by the variable `max-mini-window-height' if `resize-mini-windows' is non-nil), it is shown there. -Otherwise,the buffer containing the output is displayed. +Otherwise, the buffer containing the output is displayed. If there is output and an error, and you did not specify \"insert it in the current buffer\", a message about the error goes at the end @@ -3474,10 +3501,11 @@ the use of a shell (with its need to quote arguments)." (save-match-data (if (string-match "[ \t]*&[ \t]*\\'" command) ;; Command ending with ampersand means asynchronous. - (let ((buffer (get-buffer-create - (or output-buffer "*Async Shell Command*"))) - (directory default-directory) - proc) + (let* ((buffer (get-buffer-create + (or output-buffer "*Async Shell Command*"))) + (bname (buffer-name buffer)) + (directory default-directory) + proc) ;; Remove the ampersand. (setq command (substring command 0 (match-beginning 0))) ;; Ask the user what to do with already running process. @@ -3492,32 +3520,25 @@ the use of a shell (with its need to quote arguments)." ((eq async-shell-command-buffer 'confirm-new-buffer) ;; If will create a new buffer, query first. (if (yes-or-no-p "A command is running in the default buffer. Use a new buffer? ") - (setq buffer (generate-new-buffer - (or (and (bufferp output-buffer) (buffer-name output-buffer)) - output-buffer "*Async Shell Command*"))) + (setq buffer (generate-new-buffer bname)) (error "Shell command in progress"))) ((eq async-shell-command-buffer 'new-buffer) ;; It will create a new buffer. - (setq buffer (generate-new-buffer - (or (and (bufferp output-buffer) (buffer-name output-buffer)) - output-buffer "*Async Shell Command*")))) + (setq buffer (generate-new-buffer bname))) ((eq async-shell-command-buffer 'confirm-rename-buffer) ;; If will rename the buffer, query first. (if (yes-or-no-p "A command is running in the default buffer. Rename it? ") (progn (with-current-buffer buffer (rename-uniquely)) - (setq buffer (get-buffer-create - (or output-buffer "*Async Shell Command*")))) + (setq buffer (get-buffer-create bname))) (error "Shell command in progress"))) ((eq async-shell-command-buffer 'rename-buffer) ;; It will rename the buffer. (with-current-buffer buffer (rename-uniquely)) - (setq buffer (get-buffer-create - (or output-buffer "*Async Shell Command*")))))) + (setq buffer (get-buffer-create bname))))) (with-current-buffer buffer - (display-buffer buffer '(nil (allow-no-window . t))) (shell-command--save-pos-or-erase) (setq default-directory directory) (setq proc (start-process "Shell" buffer shell-file-name @@ -3525,10 +3546,18 @@ the use of a shell (with its need to quote arguments)." (setq mode-line-process '(":%s")) (require 'shell) (shell-mode) (set-process-sentinel proc 'shell-command-sentinel) - ;; Use the comint filter for proper handling of carriage motion - ;; (see `comint-inhibit-carriage-motion'),. + ;; Use the comint filter for proper handling of + ;; carriage motion (see comint-inhibit-carriage-motion). (set-process-filter proc 'comint-output-filter) - )) + (if async-shell-command-display-buffer + (display-buffer buffer '(nil (allow-no-window . t))) + (add-function :before (process-filter proc) + (lambda (process _string) + (let ((buf (process-buffer process))) + (when (and (zerop (buffer-size buf)) + (string= (buffer-name buf) + bname)) + (display-buffer buf)))))))) ;; Otherwise, command is executed synchronously. (shell-command-on-region (point) (point) command output-buffer nil error-buffer))))))) @@ -3901,8 +3930,7 @@ support pty association, if PROGRAM is nil." ("Command" 0 t)]) (make-local-variable 'process-menu-query-only) (setq tabulated-list-sort-key (cons "Process" nil)) - (add-hook 'tabulated-list-revert-hook 'list-processes--refresh nil t) - (tabulated-list-init-header)) + (add-hook 'tabulated-list-revert-hook 'list-processes--refresh nil t)) (defun process-menu-delete-process () "Kill process at point in a `list-processes' buffer." @@ -3963,7 +3991,8 @@ Also, delete any process that is exited or signaled." ""))))) (mapconcat 'identity (process-command p) " ")))) (push (list p (vector name pid status buf-label tty cmd)) - tabulated-list-entries)))))) + tabulated-list-entries))))) + (tabulated-list-init-header)) (defun process-menu-visit-buffer (button) (display-buffer (button-get button 'process-buffer))) @@ -5453,7 +5482,8 @@ also checks the value of `use-empty-active-region'." (progn (cl-assert (mark)) t))) (defun region-bounds () - "Return the boundaries of the region as a list of (START . END) positions." + "Return the boundaries of the region as a pair of positions. +Value is a list of cons cells of the form (START . END)." (funcall region-extract-function 'bounds)) (defun region-noncontiguous-p () @@ -5935,11 +5965,15 @@ columns by which window is scrolled from left margin. When the `track-eol' feature is doing its job, the value is `most-positive-fixnum'.") +(defvar last--line-number-width 0 + "Last value of width used for displaying line numbers. +Used internally by `line-move-visual'.") + (defcustom line-move-ignore-invisible t "Non-nil means commands that move by lines ignore invisible newlines. When this option is non-nil, \\[next-line], \\[previous-line], \\[move-end-of-line], and \\[move-beginning-of-line] behave as if newlines that are invisible didn't exist, and count -only visible newlines. Thus, moving across across 2 newlines +only visible newlines. Thus, moving across 2 newlines one of which is invisible will be counted as a one-line move. Also, a non-nil value causes invisible text to be ignored when counting columns for the purposes of keeping point in the same @@ -6099,7 +6133,7 @@ The value is a floating-point number." (or (null rbot) (= rbot 0))) nil) ;; If cursor is not in the bottom scroll margin, and the - ;; current line is is not too tall, move forward. + ;; current line is not too tall, move forward. ((and (or (null this-height) (<= this-height winh)) vpos (> vpos 0) @@ -6205,6 +6239,7 @@ not vscroll." If NOERROR, don't signal an error if we can't move that many lines." (let ((opoint (point)) (hscroll (window-hscroll)) + (lnum-width (line-number-display-width t)) target-hscroll) ;; Check if the previous command was a line-motion command, or if ;; we were called from some other command. @@ -6212,16 +6247,27 @@ If NOERROR, don't signal an error if we can't move that many lines." (memq last-command `(next-line previous-line ,this-command))) ;; If so, there's no need to reset `temporary-goal-column', ;; but we may need to hscroll. - (if (or (/= (cdr temporary-goal-column) hscroll) - (> (cdr temporary-goal-column) 0)) - (setq target-hscroll (cdr temporary-goal-column))) + (progn + (if (or (/= (cdr temporary-goal-column) hscroll) + (> (cdr temporary-goal-column) 0)) + (setq target-hscroll (cdr temporary-goal-column))) + ;; Update the COLUMN part of temporary-goal-column if the + ;; line-number display changed its width since the last + ;; time. + (setq temporary-goal-column + (cons (+ (car temporary-goal-column) + (/ (float (- lnum-width last--line-number-width)) + (frame-char-width))) + (cdr temporary-goal-column))) + (setq last--line-number-width lnum-width)) ;; Otherwise, we should reset `temporary-goal-column'. (let ((posn (posn-at-point)) x-pos) (cond - ;; Handle the `overflow-newline-into-fringe' case: - ((eq (nth 1 posn) 'right-fringe) - (setq temporary-goal-column (cons (- (window-width) 1) hscroll))) + ;; Handle the `overflow-newline-into-fringe' case + ;; (left-fringe is for the R2L case): + ((memq (nth 1 posn) '(right-fringe left-fringe)) + (setq temporary-goal-column (cons (window-width) hscroll))) ((car (posn-x-y posn)) (setq x-pos (car (posn-x-y posn))) ;; In R2L lines, the X pixel coordinate is measured from the @@ -6580,6 +6626,8 @@ which are part of the text that the image rests on.) With argument ARG not nil or 1, move forward ARG - 1 lines first. If point reaches the beginning or end of buffer, it stops there. +\(But if the buffer doesn't end in a newline, it stops at the +beginning of the last line.) To ignore intangibility, bind `inhibit-point-motion-hooks' to t." (interactive "^p") (or arg (setq arg 1)) @@ -6668,6 +6716,8 @@ To ignore intangibility, bind `inhibit-point-motion-hooks' to t." "Move point to beginning of current visual line. With argument N not nil or 1, move forward N - 1 visual lines first. If point reaches the beginning or end of buffer, it stops there. +\(But if the buffer doesn't end in a newline, it stops at the +beginning of the last visual line.) To ignore intangibility, bind `inhibit-point-motion-hooks' to t." (interactive "^p") (or n (setq n 1)) @@ -6780,10 +6830,13 @@ other purposes." (defvar visual-line--saved-state nil) (define-minor-mode visual-line-mode - "Toggle visual line based editing (Visual Line mode). -With a prefix argument ARG, enable Visual Line mode if ARG is -positive, and disable it otherwise. If called from Lisp, enable -the mode if ARG is omitted or nil. + "Toggle visual line based editing (Visual Line mode) in the current buffer. +Interactively, with a prefix argument, enable +Visual Line mode if the prefix argument is positive, +and disable it otherwise. If called from Lisp, toggle +the mode if ARG is `toggle', disable the mode if ARG is +a non-positive integer, and enable the mode otherwise +\(including if ARG is omitted or nil or a positive integer). When Visual Line mode is enabled, `word-wrap' is turned on in this buffer, and simple editing commands are redefined to act on @@ -7101,18 +7154,18 @@ Returns t if it really did any work." (setq fill-prefix prefix)))) (while (and (not give-up) (> (current-column) fc)) - ;; Determine where to split the line. - (let* (after-prefix - (fill-point - (save-excursion - (beginning-of-line) - (setq after-prefix (point)) - (and fill-prefix - (looking-at (regexp-quote fill-prefix)) - (setq after-prefix (match-end 0))) - (move-to-column (1+ fc)) - (fill-move-to-break-point after-prefix) - (point)))) + ;; Determine where to split the line. + (let ((fill-point + (save-excursion + (beginning-of-line) + ;; Don't split earlier in the line than the length of the + ;; fill prefix, since the resulting line would be longer. + (when fill-prefix + (move-to-column (string-width fill-prefix))) + (let ((after-prefix (point))) + (move-to-column (1+ fc)) + (fill-move-to-break-point after-prefix) + (point))))) ;; See whether the place we found is any good. (if (save-excursion @@ -7120,9 +7173,6 @@ Returns t if it really did any work." (or (bolp) ;; There is no use breaking at end of line. (save-excursion (skip-chars-forward " ") (eolp)) - ;; It is futile to split at the end of the prefix - ;; since we would just insert the prefix again. - (and after-prefix (<= (point) after-prefix)) ;; Don't split right after a comment starter ;; since we would just make another comment starter. (and comment-start-skip @@ -7196,6 +7246,13 @@ unless optional argument SOFT is non-nil." ;; If we're not inside a comment, just try to indent. (t (indent-according-to-mode)))))) +(defun internal-auto-fill () + "The function called by `self-insert-command' to perform auto-filling." + (when (or (not comment-start) + (not comment-auto-fill-only-comments) + (nth 4 (syntax-ppss))) + (funcall auto-fill-function))) + (defvar normal-auto-fill-function 'do-auto-fill "The function to use for `auto-fill-function' if Auto Fill mode is turned on. Some major modes set this.") @@ -7208,9 +7265,12 @@ Some major modes set this.") (define-minor-mode auto-fill-mode "Toggle automatic line breaking (Auto Fill mode). -With a prefix argument ARG, enable Auto Fill mode if ARG is -positive, and disable it otherwise. If called from Lisp, enable -the mode if ARG is omitted or nil. +Interactively, with a prefix argument, enable +Auto Fill mode if the prefix argument is positive, +and disable it otherwise. If called from Lisp, toggle +the mode if ARG is `toggle', disable the mode if ARG is +a non-positive integer, and enable the mode otherwise +\(including if ARG is omitted or nil or a positive integer). When Auto Fill mode is enabled, inserting a space at a column beyond `current-fill-column' automatically breaks the line at a @@ -7804,7 +7864,7 @@ buffer buried." (eq mail-user-agent 'message-user-agent) (let (warn-vars) (dolist (var '(mail-mode-hook mail-send-hook mail-setup-hook - mail-yank-hooks mail-archive-file-name + mail-citation-hook mail-archive-file-name mail-default-reply-to mail-mailing-lists mail-self-blind)) (and (boundp var) @@ -8465,13 +8525,16 @@ after it has been set up properly in other respects." ;; Set up other local variables. (mapc (lambda (v) - (condition-case () ;in case var is read-only + (condition-case () (if (symbolp v) (makunbound v) (set (make-local-variable (car v)) (cdr v))) - (error nil))) + (setting-constant nil))) ;E.g. for enable-multibyte-characters. lvars) + (setq mark-ring (mapcar (lambda (mk) (copy-marker (marker-position mk))) + mark-ring)) + ;; Run any hooks (typically set up by the major mode ;; for cloning to work properly). (run-hooks 'clone-buffer-hook)) @@ -8762,7 +8825,7 @@ If it does not exist, create and it switch it to `messages-buffer-mode'." ;; rms says this should be done by specifying symbols that define ;; versions together with bad values. This is therefore not as ;; flexible as it could be. See the thread: -;; http://lists.gnu.org/archive/html/emacs-devel/2007-08/msg00300.html +;; https://lists.gnu.org/r/emacs-devel/2007-08/msg00300.html (defconst bad-packages-alist ;; Not sure exactly which semantic versions have problems. ;; Definitely 2.0pre3, probably all 2.0pre's before this. diff --git a/lisp/skeleton.el b/lisp/skeleton.el index dbfa87e207d..d182bdff300 100644 --- a/lisp/skeleton.el +++ b/lisp/skeleton.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/sort.el b/lisp/sort.el index 88a784fbb85..1dee6ef6c56 100644 --- a/lisp/sort.el +++ b/lisp/sort.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -406,7 +406,7 @@ the sort order." ;;;###autoload (defun sort-regexp-fields (reverse record-regexp key-regexp beg end) - "Sort the text in the region region lexicographically. + "Sort the text in the region lexicographically. If called interactively, prompt for two regular expressions, RECORD-REGEXP and KEY-REGEXP. diff --git a/lisp/soundex.el b/lisp/soundex.el index e0d83303e34..0903b80abe5 100644 --- a/lisp/soundex.el +++ b/lisp/soundex.el @@ -1,4 +1,4 @@ -;;; soundex.el --- implement Soundex algorithm +;;; soundex.el --- implement Soundex algorithm -*- lexical-binding: t -*- ;; Copyright (C) 1993, 2001-2017 Free Software Foundation, Inc. @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -29,7 +29,7 @@ ;;; Code: -(defvar soundex-alist +(defconst soundex-alist '((?B . "1") (?F . "1") (?P . "1") (?V . "1") (?C . "2") (?G . "2") (?J . "2") (?K . "2") (?Q . "2") (?S . "2") (?X . "2") (?Z . "2") (?D . "3") (?T . "3") (?L . "4") (?M . "5") @@ -60,15 +60,6 @@ and Searching\", Addison-Wesley (1973), pp. 391-392." (substring (concat key "000") 0 4) key))) -;(defvar soundex-test -; '("Euler" "Gauss" "Hilbert" "Knuth" "Lloyd" "Lukasiewicz" -; "Ellery" "Ghosh" "Heilbronn" "Kant" "Ladd" "Lissajous") -; "\n Knuth's names to demonstrate the Soundex algorithm.") -; -;(mapcar 'soundex soundex-test) -;("E460" "G200" "H416" "K530" "L300" "L222" -; "E460" "G200" "H416" "K530" "L300" "L222") - (provide 'soundex) ;;; soundex.el ends here diff --git a/lisp/speedbar.el b/lisp/speedbar.el index 31134711830..c66cc89dda2 100644 --- a/lisp/speedbar.el +++ b/lisp/speedbar.el @@ -25,7 +25,7 @@ this version is not backward compatible to 0.14 or earlier.") ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/startup.el b/lisp/startup.el index bc60bbd08b8..a39c8f0fe76 100644 --- a/lisp/startup.el +++ b/lisp/startup.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -513,7 +513,7 @@ It is the default value of the variable `top-level'." (let ((default-directory dir)) (load (expand-file-name "subdirs.el") t t t)) ;; Do not scan standard directories that won't contain a leim-list.el. - ;; http://lists.gnu.org/archive/html/emacs-devel/2009-10/msg00502.html + ;; https://lists.gnu.org/r/emacs-devel/2009-10/msg00502.html ;; (Except the preloaded one in lisp/leim.) (or (string-prefix-p lispdir dir) (let ((default-directory dir)) @@ -1371,7 +1371,7 @@ the `--debug-init' option to view a complete error backtrace." ;; trying to load gnus could load the wrong file. ;; OK, it would not matter if .emacs.d were at the end of load-path. ;; but for the sake of simplicity, we discourage it full-stop. - ;; Ref eg http://lists.gnu.org/archive/html/emacs-devel/2012-03/msg00056.html + ;; Ref eg https://lists.gnu.org/r/emacs-devel/2012-03/msg00056.html ;; ;; A bad element could come from user-emacs-file, the command line, ;; or EMACSLOADPATH, so we basically always have to check. @@ -1432,6 +1432,7 @@ settings will be marked as \"CHANGED outside of Customize\"." (let ((no-vals '("no" "off" "false" "0")) (settings '(("menuBar" "MenuBar" menu-bar-mode nil) ("toolBar" "ToolBar" tool-bar-mode nil) + ("scrollBar" "ScrollBar" scroll-bar-mode nil) ("cursorBlink" "CursorBlink" no-blinking-cursor t)))) (dolist (x settings) (if (member (x-get-resource (nth 0 x) (nth 1 x)) no-vals) @@ -1462,18 +1463,18 @@ If this is nil, no message will be displayed." `((:face (variable-pitch font-lock-comment-face) "Welcome to " :link ("GNU Emacs" - ,(lambda (_button) (browse-url "http://www.gnu.org/software/emacs/")) - "Browse http://www.gnu.org/software/emacs/") + ,(lambda (_button) (browse-url "https://www.gnu.org/software/emacs/")) + "Browse https://www.gnu.org/software/emacs/") ", one component of the " :link ,(lambda () (if (eq system-type 'gnu/linux) `("GNU/Linux" - ,(lambda (_button) (browse-url "http://www.gnu.org/gnu/linux-and-gnu.html")) - "Browse http://www.gnu.org/gnu/linux-and-gnu.html") + ,(lambda (_button) (browse-url "https://www.gnu.org/gnu/linux-and-gnu.html")) + "Browse https://www.gnu.org/gnu/linux-and-gnu.html") `("GNU" ,(lambda (_button) - (browse-url "http://www.gnu.org/gnu/thegnuproject.html")) - "Browse http://www.gnu.org/gnu/thegnuproject.html"))) + (browse-url "https://www.gnu.org/gnu/thegnuproject.html")) + "Browse https://www.gnu.org/gnu/thegnuproject.html"))) " operating system.\n\n" :face variable-pitch :link ("Emacs Tutorial" ,(lambda (_button) (help-with-tutorial))) @@ -1505,8 +1506,8 @@ If this is nil, no message will be displayed." "\n" :link ("Emacs Guided Tour" ,(lambda (_button) - (browse-url "http://www.gnu.org/software/emacs/tour/")) - "Browse http://www.gnu.org/software/emacs/tour/") + (browse-url "https://www.gnu.org/software/emacs/tour/")) + "Browse https://www.gnu.org/software/emacs/tour/") "\tOverview of Emacs features at gnu.org\n" :link ("View Emacs Manual" ,(lambda (_button) (info-emacs-manual))) "\tView the Emacs manual using Info\n" @@ -1528,16 +1529,16 @@ Each element in the list should be a list of strings or pairs `((:face (variable-pitch font-lock-comment-face) "This is " :link ("GNU Emacs" - ,(lambda (_button) (browse-url "http://www.gnu.org/software/emacs/")) - "Browse http://www.gnu.org/software/emacs/") + ,(lambda (_button) (browse-url "https://www.gnu.org/software/emacs/")) + "Browse https://www.gnu.org/software/emacs/") ", one component of the " :link ,(lambda () (if (eq system-type 'gnu/linux) `("GNU/Linux" ,(lambda (_button) - (browse-url "http://www.gnu.org/gnu/linux-and-gnu.html")) - "Browse http://www.gnu.org/gnu/linux-and-gnu.html") + (browse-url "https://www.gnu.org/gnu/linux-and-gnu.html")) + "Browse https://www.gnu.org/gnu/linux-and-gnu.html") `("GNU" ,(lambda (_button) (describe-gnu-project)) "Display info on the GNU project."))) " operating system.\n" @@ -1596,8 +1597,8 @@ Each element in the list should be a list of strings or pairs "\n" :link ("Emacs Guided Tour" ,(lambda (_button) - (browse-url "http://www.gnu.org/software/emacs/tour/")) - "Browse http://www.gnu.org/software/emacs/tour/") + (browse-url "https://www.gnu.org/software/emacs/tour/")) + "Browse https://www.gnu.org/software/emacs/tour/") "\tSee an overview of Emacs features at gnu.org")) "A list of texts to show in the middle part of the About screen. Each element in the list should be a list of strings or pairs @@ -1705,8 +1706,8 @@ a face or button specification." ;; Insert the image with a help-echo and a link. (make-button (prog1 (point) (insert-image img)) (point) 'face 'default - 'help-echo "mouse-2, RET: Browse http://www.gnu.org/" - 'action (lambda (_button) (browse-url "http://www.gnu.org/")) + 'help-echo "mouse-2, RET: Browse https://www.gnu.org/" + 'action (lambda (_button) (browse-url "https://www.gnu.org/")) 'follow-link t) (insert "\n\n"))))) diff --git a/lisp/strokes.el b/lisp/strokes.el index a70c3f58f49..33a2ea6b244 100644 --- a/lisp/strokes.el +++ b/lisp/strokes.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/subr.el b/lisp/subr.el index ef00286b341..7ec727ef19c 100644 --- a/lisp/subr.el +++ b/lisp/subr.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; Beware: while this file has tag `utf-8', before it's compiled, it gets ;; loaded as "raw-text", so non-ASCII chars won't work right during bootstrap. @@ -78,8 +78,8 @@ If FORM does return, signal an error." (defmacro 1value (form) "Evaluate FORM, expecting a constant return value. -This is the global do-nothing version. There is also `testcover-1value' -that complains if FORM ever does return differing values." +If FORM returns differing values when running under Testcover, +Testcover will raise an error." (declare (debug t)) form) @@ -110,8 +110,7 @@ BODY should be a list of Lisp expressions. \(fn ARGS [DOCSTRING] [INTERACTIVE] BODY)" (declare (doc-string 2) (indent defun) - (debug (&define lambda-list - [&optional stringp] + (debug (&define lambda-list lambda-doc [&optional ("interactive" interactive)] def-body))) ;; Note that this definition should not use backquotes; subr.el should not @@ -121,6 +120,7 @@ BODY should be a list of Lisp expressions. (defmacro setq-local (var val) "Set variable VAR to value VAL in current buffer." ;; Can't use backquote here, it's too early in the bootstrap. + (declare (debug (symbolp form))) (list 'set (list 'make-local-variable (list 'quote var)) val)) (defmacro defvar-local (var val &optional docstring) @@ -279,6 +279,17 @@ without silencing all errors." ;;;; Basic Lisp functions. +(defvar gensym-counter 0 + "Number used to construct the name of the next symbol created by `gensym'.") + +(defun gensym (&optional prefix) + "Return a new uninterned symbol. +The name is made by appending `gensym-counter' to PREFIX. +PREFIX is a string, and defaults to \"g\"." + (let ((num (prog1 gensym-counter + (setq gensym-counter (1+ gensym-counter))))) + (make-symbol (format "%s%d" (or prefix "g") num)))) + (defun ignore (&rest _ignore) "Do nothing and return nil. This function accepts any number of arguments, but ignores them." @@ -566,7 +577,7 @@ one is kept." (setq tail (cdr tail)))))) list) -;; See http://lists.gnu.org/archive/html/emacs-devel/2013-05/msg00204.html +;; See https://lists.gnu.org/r/emacs-devel/2013-05/msg00204.html (defun delete-consecutive-dups (list &optional circular) "Destructively remove `equal' consecutive duplicates from LIST. First and last elements are considered consecutive if CIRCULAR is @@ -724,15 +735,18 @@ Elements of ALIST that are not conses are ignored." (setq tail tail-cdr)))) alist) -(defun alist-get (key alist &optional default remove) - "Return the value associated with KEY in ALIST, using `assq'. +(defun alist-get (key alist &optional default remove testfn) + "Return the value associated with KEY in ALIST. If KEY is not found in ALIST, return DEFAULT. +Use TESTFN to lookup in the alist if non-nil. Otherwise, use `assq'. This is a generalized variable suitable for use with `setf'. When using it to set a value, optional argument REMOVE non-nil means to remove KEY from ALIST if the new value is `eql' to DEFAULT." (ignore remove) ;;Silence byte-compiler. - (let ((x (assq key alist))) + (let ((x (if (not testfn) + (assq key alist) + (assoc key alist testfn)))) (if x (cdr x) default))) (defun remove (elt seq) @@ -770,8 +784,9 @@ This is the same format used for saving keyboard macros (see "Beep to tell the user this binding is undefined." (interactive) (ding) - (message "%s is undefined" (key-description (this-single-command-keys))) - (setq defining-kbd-macro nil) + (if defining-kbd-macro + (error "%s is undefined" (key-description (this-single-command-keys))) + (message "%s is undefined" (key-description (this-single-command-keys)))) (force-mode-line-update) ;; If this is a down-mouse event, don't reset prefix-arg; ;; pass it to the command run by the up event. @@ -1255,6 +1270,11 @@ See `event-start' for a description of the value returned." "Return the multi-click count of EVENT, a click or drag event. The return value is a positive integer." (if (and (consp event) (integerp (nth 2 event))) (nth 2 event) 1)) + +(defsubst event-line-count (event) + "Return the line count of EVENT, a mousewheel event. +The return value is a positive integer." + (if (and (consp event) (integerp (nth 3 event))) (nth 3 event) 1)) ;;;; Extracting fields of the positions in an event. @@ -1459,10 +1479,6 @@ be a list of the form returned by `event-start' and `event-end'." (make-obsolete-variable 'command-debug-status "expect it to be removed in a future version." "25.2") -;; Lisp manual only updated in 22.1. -(define-obsolete-variable-alias 'executing-macro 'executing-kbd-macro - "before 19.34") - (define-obsolete-variable-alias 'x-lost-selection-hooks 'x-lost-selection-functions "22.1") (define-obsolete-variable-alias 'x-sent-selection-hooks @@ -1474,6 +1490,8 @@ be a list of the form returned by `event-start' and `event-end'." ;; but Stefan insists to mark it so. (make-obsolete-variable 'translation-table-for-input nil "23.1") +(make-obsolete-variable 'x-gtk-use-window-move nil "26.1") + (defvaralias 'messages-buffer-max-lines 'message-log-max) ;;;; Alternate names for functions - these are not being phased out. @@ -1785,7 +1803,8 @@ Return the new history list. If MAXELT is non-nil, it specifies the maximum length of the history. Otherwise, the maximum history length is the value of the `history-length' property on symbol HISTORY-VAR, if set, or the value of the `history-length' -variable. +variable. The possible values of maximum length have the same meaning as +the values of `history-length'. Remove duplicates of NEWELT if `history-delete-duplicates' is non-nil. If optional fourth arg KEEP-ALL is non-nil, add NEWELT to history even if it is empty or a duplicate." @@ -1994,6 +2013,25 @@ If TOGGLE has a `:menu-tag', that is used for the menu item's label." ;; "Return the name of the file from which AUTOLOAD will be loaded. ;; \n\(fn AUTOLOAD)") +(defun define-symbol-prop (symbol prop val) + "Define the property PROP of SYMBOL to be VAL. +This is to `put' what `defalias' is to `fset'." + ;; Can't use `cl-pushnew' here (nor `push' on (cdr foo)). + ;; (cl-pushnew symbol (alist-get prop + ;; (alist-get 'define-symbol-props + ;; current-load-list))) + (let ((sps (assq 'define-symbol-props current-load-list))) + (unless sps + (setq sps (list 'define-symbol-props)) + (push sps current-load-list)) + (let ((ps (assq prop sps))) + (unless ps + (setq ps (list prop)) + (setcdr sps (cons ps (cdr sps)))) + (unless (member symbol (cdr ps)) + (setcdr ps (cons symbol (cdr ps)))))) + (put symbol prop val)) + (defun symbol-file (symbol &optional type) "Return the name of the file that defined SYMBOL. The value is normally an absolute file name. It can also be nil, @@ -2003,47 +2041,30 @@ file name without extension. If TYPE is nil, then any kind of definition is acceptable. If TYPE is `defun', `defvar', or `defface', that specifies function -definition, variable definition, or face definition only." +definition, variable definition, or face definition only. +Otherwise TYPE is assumed to be a symbol property." (if (and (or (null type) (eq type 'defun)) (symbolp symbol) (autoloadp (symbol-function symbol))) (nth 1 (symbol-function symbol)) - (let ((files load-history) - file match) - (while files - (if (if type - (if (eq type 'defvar) - ;; Variables are present just as their names. - (member symbol (cdr (car files))) - ;; Other types are represented as (TYPE . NAME). - (member (cons type symbol) (cdr (car files)))) - ;; We accept all types, so look for variable def - ;; and then for any other kind. - (or (member symbol (cdr (car files))) - (and (setq match (rassq symbol (cdr (car files)))) - (not (eq 'require (car match)))))) - (setq file (car (car files)) files nil)) - (setq files (cdr files))) - file))) - -(defun method-files (method) - "Return a list of files where METHOD is defined by `cl-defmethod'. -The list will have entries of the form (FILE . (METHOD ...)) -where (METHOD ...) contains the qualifiers and specializers of -the method and is a suitable argument for -`find-function-search-for-symbol'. Filenames are absolute." - (let ((files load-history) - result) - (while files - (let ((defs (cdr (car files)))) - (while defs - (let ((def (car defs))) - (if (and (eq (car-safe def) 'cl-defmethod) - (eq (cadr def) method)) - (push (cons (car (car files)) (cdr def)) result))) - (setq defs (cdr defs)))) - (setq files (cdr files))) - result)) + (catch 'found + (pcase-dolist (`(,file . ,elems) load-history) + (when (if type + (if (eq type 'defvar) + ;; Variables are present just as their names. + (member symbol elems) + ;; Many other types are represented as (TYPE . NAME). + (or (member (cons type symbol) elems) + (memq symbol (alist-get type + (alist-get 'define-symbol-props + elems))))) + ;; We accept all types, so look for variable def + ;; and then for any other kind. + (or (member symbol elems) + (let ((match (rassq symbol elems))) + (and match + (not (eq 'require (car match))))))) + (throw 'found file)))))) (defun locate-library (library &optional nosuffix path interactive-call) "Show the precise file name of Emacs library LIBRARY. @@ -2405,7 +2426,7 @@ in milliseconds; this was useful when Emacs was built without floating point support." (declare (advertised-calling-convention (seconds &optional nodisp) "22.1")) ;; This used to be implemented in C until the following discussion: - ;; http://lists.gnu.org/archive/html/emacs-devel/2006-07/msg00401.html + ;; https://lists.gnu.org/r/emacs-devel/2006-07/msg00401.html ;; Then it was moved here using an implementation based on an idle timer, ;; which was then replaced by the use of read-event. (if (numberp nodisp) @@ -2420,7 +2441,7 @@ floating point support." nil) ((or (<= seconds 0) ;; We are going to call read-event below, which will record - ;; the the next key as part of the macro, even if that key + ;; the next key as part of the macro, even if that key ;; invokes kmacro-end-macro, so if we are recording a macro, ;; the macro will recursively call itself. In addition, when ;; that key is removed from unread-command-events, it will be @@ -2444,7 +2465,7 @@ floating point support." (read-event nil t seconds)))) (or (null read) (progn - ;; https://lists.gnu.org/archive/html/emacs-devel/2006-10/msg00394.html + ;; https://lists.gnu.org/r/emacs-devel/2006-10/msg00394.html ;; We want `read' appear in the next command's this-command-event ;; but not in the current one. ;; By pushing (cons t read), we indicate that `read' has not @@ -3077,7 +3098,7 @@ Do nothing if FACE is nil." (put-text-property start end 'face face))) ;; This removes `mouse-face' properties in *Help* buffer buttons: -;; http://lists.gnu.org/archive/html/emacs-devel/2002-04/msg00648.html +;; https://lists.gnu.org/r/emacs-devel/2002-04/msg00648.html (defun yank-handle-category-property (category start end) "Apply property category CATEGORY's properties between START and END." (when category @@ -4192,7 +4213,7 @@ Used from `delayed-warnings-hook' (which see)." (setq delayed-warnings-list (nreverse collapsed)))) ;; At present this is only used for Emacs internals. -;; Ref http://lists.gnu.org/archive/html/emacs-devel/2012-02/msg00085.html +;; Ref https://lists.gnu.org/r/emacs-devel/2012-02/msg00085.html (defvar delayed-warnings-hook '(collapse-delayed-warnings display-delayed-warnings) "Normal hook run to process and display delayed warnings. @@ -4513,7 +4534,8 @@ EVALD, FUNC, ARGS, FLAGS are as in `mapbacktrace'." (defun backtrace () "Print a trace of Lisp function calls currently active. Output stream used is value of `standard-output'." - (let ((print-level (or print-level 8))) + (let ((print-level (or print-level 8)) + (print-escape-control-characters t)) (mapbacktrace #'backtrace--print-frame 'backtrace))) (defun backtrace-frames (&optional base) @@ -4794,10 +4816,9 @@ CURRENT-VALUE and MIN-CHANGE do not have any effect if MIN-VALUE and/or MAX-VALUE are nil. Optional MIN-TIME specifies the minimum interval time between -echo area updates (default is 0.2 seconds.) If the function -`float-time' is not present, time is not tracked at all. If the -OS is not capable of measuring fractions of seconds, this -parameter is effectively rounded up." +echo area updates (default is 0.2 seconds.) If the OS is not +capable of measuring fractions of seconds, this parameter is +effectively rounded up." (when (string-match "[[:alnum:]]\\'" message) (setq message (concat message "..."))) (unless min-time @@ -4805,8 +4826,7 @@ parameter is effectively rounded up." (let ((reporter ;; Force a call to `message' now (cons (or min-value 0) - (vector (if (and (fboundp 'float-time) - (>= min-time 0.02)) + (vector (if (>= min-time 0.02) (float-time) nil) min-value max-value @@ -5203,7 +5223,7 @@ or \"gnus-article-toto-\".") ;; The following statement ought to be in print.c, but `provide' can't ;; be used there. -;; http://lists.gnu.org/archive/html/emacs-devel/2009-08/msg00236.html +;; https://lists.gnu.org/r/emacs-devel/2009-08/msg00236.html (when (hash-table-p (car (read-from-string (prin1-to-string (make-hash-table))))) (provide 'hashtable-print-readable)) diff --git a/lisp/svg.el b/lisp/svg.el index fc1a6d60e1a..ae7f1c57c02 100644 --- a/lisp/svg.el +++ b/lisp/svg.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -157,7 +157,27 @@ otherwise. IMAGE-TYPE should be a MIME image type, like (dom-node 'text `(,@(svg--arguments svg args)) - text))) + (svg--encode-text text)))) + +(defun svg--encode-text (text) + ;; Apparently the SVG renderer needs to have all non-ASCII + ;; characters encoded, and only certain special characters. + (with-temp-buffer + (insert text) + (dolist (substitution '(("&" . "&") + ("<" . "<") + (">" . ">"))) + (goto-char (point-min)) + (while (search-forward (car substitution) nil t) + (replace-match (cdr substitution) t t nil))) + (goto-char (point-min)) + (while (not (eobp)) + (let ((char (following-char))) + (if (< char 128) + (forward-char 1) + (delete-char 1) + (insert "&#" (format "%d" char) ";")))) + (buffer-string))) (defun svg--append (svg node) (let ((old (and (dom-attr node 'id) @@ -264,10 +284,10 @@ If the SVG is later changed, the image will also be updated." (defun svg-remove (svg id) "Remove the element identified by ID from SVG." - (when-let ((node (car (dom-by-id - svg - (concat "\\`" (regexp-quote id) - "\\'"))))) + (when-let* ((node (car (dom-by-id + svg + (concat "\\`" (regexp-quote id) + "\\'"))))) (dom-remove-node svg node))) (provide 'svg) diff --git a/lisp/t-mouse.el b/lisp/t-mouse.el index 2ed2fcb466e..75e88045132 100644 --- a/lisp/t-mouse.el +++ b/lisp/t-mouse.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/tabify.el b/lisp/tabify.el index 75ff61d3272..93a0fc27d15 100644 --- a/lisp/tabify.el +++ b/lisp/tabify.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/talk.el b/lisp/talk.el index f35f9344f8d..a471a500617 100644 --- a/lisp/talk.el +++ b/lisp/talk.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/tar-mode.el b/lisp/tar-mode.el index f25b1a45ba1..21fccc4fcce 100644 --- a/lisp/tar-mode.el +++ b/lisp/tar-mode.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -469,7 +469,7 @@ checksum before doing the check." (concat " " (substring str 4 16) (format-time-string " %Y" time)))) (defun tar-grind-file-mode (mode) - "Construct a `-rw--r--r--' string indicating MODE. + "Construct a `rw-r--r--' string indicating MODE. MODE should be an integer which is a file mode value." (string (if (zerop (logand 256 mode)) ?- ?r) @@ -1118,7 +1118,7 @@ for this to be permanent." (save-excursion (goto-char (point-min)) (while (not (eobp)) - (if (looking-at "D") + (if (= (following-char) ?D) (progn (tar-expunge-internal) (setq n (1+ n))) (forward-line 1))) diff --git a/lisp/tempo.el b/lisp/tempo.el index e4c50038fdc..3470d48e244 100644 --- a/lisp/tempo.el +++ b/lisp/tempo.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/term.el b/lisp/term.el index 063a6ea592f..2046578368c 100644 --- a/lisp/term.el +++ b/lisp/term.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; Marck 13 2001 ;; Fixes for CJK support by Yong Lu <lyongu@yahoo.com>. @@ -427,6 +427,8 @@ by moving term-home-marker. It is set to t if there is a (defvar term-old-mode-line-format) ; Saves old mode-line-format while paging. (defvar term-pager-old-local-map nil "Saves old keymap while paging.") (defvar term-pager-old-filter) ; Saved process-filter while paging. +(defvar-local term-line-mode-buffer-read-only nil + "The `buffer-read-only' state to set in `term-line-mode'.") (defcustom explicit-shell-file-name nil "If non-nil, is file name to use for explicitly requested inferior shell." @@ -487,6 +489,41 @@ This variable is buffer-local, and is a good thing to set in mode hooks." :type 'boolean :group 'term) +(defcustom term-char-mode-buffer-read-only t + "If non-nil, only the process filter may modify the buffer in char mode. + +A non-nil value makes the buffer read-only in `term-char-mode', +which prevents editing commands from making the buffer state +inconsistent with the state of the terminal understood by the +inferior process. Only the process filter is allowed to make +changes to the buffer. + +Customize this option to nil if you want the previous behaviour." + :version "26.1" + :type 'boolean + :group 'term) + +(defcustom term-char-mode-point-at-process-mark t + "If non-nil, keep point at the process mark in char mode. + +A non-nil value causes point to be moved to the current process +mark after each command in `term-char-mode' (provided that the +pre-command point position was also at the process mark). This +prevents commands that move point from making the buffer state +inconsistent with the state of the terminal understood by the +inferior process. + +Mouse events are not affected, so moving point and selecting text +is still possible in char mode via the mouse, after which other +commands can be invoked on the mouse-selected point or region, +until the process filter (or user) moves point to the process +mark once again. + +Customize this option to nil if you want the previous behaviour." + :version "26.1" + :type 'boolean + :group 'term) + (defcustom term-scroll-to-bottom-on-output nil "Controls whether interpreter output causes window to scroll. If nil, then do not scroll. If t or `all', scroll all windows showing buffer. @@ -1007,7 +1044,7 @@ Entry to this mode runs the hooks on `term-mode-hook'." (setq indent-tabs-mode nil) (setq buffer-display-table term-display-table) (set (make-local-variable 'term-home-marker) (copy-marker 0)) - (set (make-local-variable 'term-height) (1- (window-height))) + (set (make-local-variable 'term-height) (window-text-height)) (set (make-local-variable 'term-width) (window-max-chars-per-line)) (set (make-local-variable 'term-last-input-start) (make-marker)) (set (make-local-variable 'term-last-input-end) (make-marker)) @@ -1105,6 +1142,8 @@ Entry to this mode runs the hooks on `term-mode-hook'." (term-reset-size (cdr size) (car size))) size)) + (add-hook 'read-only-mode-hook #'term-line-mode-buffer-read-only-update nil t) + (easy-menu-add term-terminal-menu) (easy-menu-add term-signals-menu) (or term-input-ring @@ -1246,6 +1285,13 @@ intervention from Emacs, except for the escape character (usually C-c)." (easy-menu-add term-terminal-menu) (easy-menu-add term-signals-menu) + ;; Don't allow changes to the buffer or to point which are not + ;; caused by the process filter. + (when term-char-mode-buffer-read-only + (setq buffer-read-only t)) + (add-hook 'pre-command-hook #'term-set-goto-process-mark nil t) + (add-hook 'post-command-hook #'term-goto-process-mark-maybe nil t) + ;; Send existing partial line to inferior (without newline). (let ((pmark (process-mark (get-buffer-process (current-buffer)))) (save-input-sender term-input-sender)) @@ -1265,9 +1311,20 @@ This means that Emacs editing commands work as normally, until you type \\[term-send-input] which sends the current line to the inferior." (interactive) (when (term-in-char-mode) + (when term-char-mode-buffer-read-only + (setq buffer-read-only term-line-mode-buffer-read-only)) + (remove-hook 'pre-command-hook #'term-set-goto-process-mark t) + (remove-hook 'post-command-hook #'term-goto-process-mark-maybe t) (use-local-map term-old-mode-map) (term-update-mode-line))) +(defun term-line-mode-buffer-read-only-update () + "Update the user-set state of `buffer-read-only' in `term-line-mode'. + +Called as a buffer-local `read-only-mode-hook' function." + (when (term-in-line-mode) + (setq term-line-mode-buffer-read-only buffer-read-only))) + (defun term-update-mode-line () (let ((term-mode (if (term-in-char-mode) @@ -1354,8 +1411,7 @@ commands to use in that buffer. (interactive (list (read-from-minibuffer "Run program: " (or explicit-shell-file-name (getenv "ESHELL") - (getenv "SHELL") - "/bin/sh")))) + shell-file-name)))) (set-buffer (make-term "terminal" program)) (term-mode) (term-char-mode) @@ -2712,6 +2768,7 @@ See `term-prompt-regexp'." count-bytes ; number of bytes decoded-substring save-point save-marker old-point temp win + (inhibit-read-only t) (buffer-undo-list t) (selected (selected-window)) last-win @@ -3110,6 +3167,46 @@ See `term-prompt-regexp'." (when (get-buffer-window (current-buffer)) (redisplay)))) +(defvar-local term-goto-process-mark t + "Whether to reset point to the current process mark after this command. + +Set in `pre-command-hook' in char mode by `term-set-goto-process-mark'.") + +(defun term-set-goto-process-mark () + "Sets `term-goto-process-mark'. + +Always set to nil if `term-char-mode-point-at-process-mark' is nil. + +Called as a buffer-local `pre-command-hook' function in +`term-char-mode' so that when point is equal to the process mark +at the pre-command stage, we know to restore point to the process +mark at the post-command stage. + +See also `term-goto-process-mark-maybe'." + (setq term-goto-process-mark + (and term-char-mode-point-at-process-mark + (eq (point) (marker-position (term-process-mark)))))) + +(defun term-goto-process-mark-maybe () + "Move point to the term buffer's process mark upon keyboard input. + +Called as a buffer-local `post-command-hook' function in +`term-char-mode' to prevent commands from putting the buffer into +an inconsistent state by unexpectedly moving point. + +Mouse events are ignored so that mouse selection is unimpeded. + +Only acts when the pre-command position of point was equal to the +process mark, and the `term-char-mode-point-at-process-mark' +option is enabled. See `term-set-goto-process-mark'." + (when term-goto-process-mark + (unless (mouse-event-p last-command-event) + (goto-char (term-process-mark))))) + +(defun term-process-mark () + "The current `process-mark' for the term buffer process." + (process-mark (get-buffer-process (current-buffer)))) + (defun term-handle-deferred-scroll () (let ((count (- (term-current-row) term-height))) (when (>= count 0) @@ -4149,8 +4246,7 @@ the process. Any more args are arguments to PROGRAM." (interactive (list (read-from-minibuffer "Run program: " (or explicit-shell-file-name (getenv "ESHELL") - (getenv "SHELL") - "/bin/sh")))) + shell-file-name)))) ;; Pick the name of the new buffer. (setq term-ansi-buffer-name diff --git a/lisp/term/AT386.el b/lisp/term/AT386.el index ac027747b77..62a6c58cafe 100644 --- a/lisp/term/AT386.el +++ b/lisp/term/AT386.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/term/README b/lisp/term/README index 9cb844b7619..25b9e5db0cd 100644 --- a/lisp/term/README +++ b/lisp/term/README @@ -262,4 +262,4 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License -along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. diff --git a/lisp/term/common-win.el b/lisp/term/common-win.el index cce84588a5a..2cf1e84768e 100644 --- a/lisp/term/common-win.el +++ b/lisp/term/common-win.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/term/internal.el b/lisp/term/internal.el index 00a908a4598..f16189e0587 100644 --- a/lisp/term/internal.el +++ b/lisp/term/internal.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/term/iris-ansi.el b/lisp/term/iris-ansi.el index d3ddb19c0fb..24a5642b0f2 100644 --- a/lisp/term/iris-ansi.el +++ b/lisp/term/iris-ansi.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/term/konsole.el b/lisp/term/konsole.el new file mode 100644 index 00000000000..45c701f33bc --- /dev/null +++ b/lisp/term/konsole.el @@ -0,0 +1,12 @@ +;;; konsole.el --- terminal initialization for konsole +;; Copyright (C) 2017 Free Software Foundation, Inc. + +(require 'term/xterm) + +(defun terminal-init-konsole () + "Terminal initialization function for konsole." + (tty-run-terminal-initialization (selected-frame) "xterm")) + +(provide 'term/konsole) + +;; konsole.el ends here diff --git a/lisp/term/news.el b/lisp/term/news.el index 241db338494..1c23f1cfce1 100644 --- a/lisp/term/news.el +++ b/lisp/term/news.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/term/ns-win.el b/lisp/term/ns-win.el index 4df5f0abe21..e895d09bb4f 100644 --- a/lisp/term/ns-win.el +++ b/lisp/term/ns-win.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -124,6 +124,8 @@ The properties returned may include `top', `left', `height', and `width'." (define-key global-map [?\s-g] 'isearch-repeat-forward) (define-key global-map [?\s-h] 'ns-do-hide-emacs) (define-key global-map [?\s-H] 'ns-do-hide-others) +(define-key global-map [?\M-\s-h] 'ns-do-hide-others) +(define-key key-translation-map [?\M-\s-\u02D9] [?\M-\s-h]) (define-key global-map [?\s-j] 'exchange-point-and-mark) (define-key global-map [?\s-k] 'kill-current-buffer) (define-key global-map [?\s-l] 'goto-line) @@ -592,7 +594,7 @@ the last file dropped is selected." (declare-function tool-bar-mode "tool-bar" (&optional arg)) ;; Based on a function by David Reitter <dreitter@inf.ed.ac.uk> ; -;; see http://lists.gnu.org/archive/html/emacs-devel/2005-09/msg00681.html . +;; see https://lists.gnu.org/r/emacs-devel/2005-09/msg00681.html . (defun ns-toggle-toolbar (&optional frame) "Switches the tool bar on and off in frame FRAME. If FRAME is nil, the change applies to the selected frame." @@ -734,6 +736,27 @@ See the documentation of `create-fontset-from-fontset-spec' for the format.") (global-unset-key [horizontal-scroll-bar drag-mouse-1]) +;;;; macOS-like defaults for trackpad and mouse wheel scrolling on +;;;; macOS 10.7+. + +;; FIXME: This doesn't look right. Is there a better way to do this +;; that keeps customize happy? +(when (featurep 'cocoa) + (let ((appkit-version + (progn (string-match "^appkit-\\([^\s-]*\\)" ns-version-string) + (string-to-number (match-string 1 ns-version-string))))) + ;; Appkit 1138 ~= macOS 10.7. + (when (>= appkit-version 1138) + (setq mouse-wheel-scroll-amount '(1 ((shift) . 5) ((control)))) + (put 'mouse-wheel-scroll-amount 'customized-value + (list (custom-quote (symbol-value 'mouse-wheel-scroll-amount)))) + + (setq mouse-wheel-progressive-speed nil) + (put 'mouse-wheel-progressive-speed 'customized-value + (list (custom-quote + (symbol-value 'mouse-wheel-progressive-speed))))))) + + ;;;; Color support. ;; Functions for color panel + drag @@ -774,7 +797,7 @@ See the documentation of `create-fontset-from-fontset-spec' for the format.") (defun ns-suspend-error () ;; Don't allow suspending if any of the frames are NS frames. (if (memq 'ns (mapcar 'window-system (frame-list))) - (error "Cannot suspend Emacs while running under NS"))) + (error "Cannot suspend Emacs while an NS GUI frame exists"))) ;; Set some options to be as Nextstep-like as possible. @@ -855,7 +878,7 @@ See the documentation of `create-fontset-from-fontset-spec' for the format.") ;; Mac OS X Lion introduces PressAndHold, which is unsupported by this port. ;; See this thread for more details: - ;; http://lists.gnu.org/archive/html/emacs-devel/2011-06/msg00505.html + ;; https://lists.gnu.org/r/emacs-devel/2011-06/msg00505.html (ns-set-resource nil "ApplePressAndHoldEnabled" "NO") (x-apply-session-resources) diff --git a/lisp/term/pc-win.el b/lisp/term/pc-win.el index b6f2acc2978..0355350da72 100644 --- a/lisp/term/pc-win.el +++ b/lisp/term/pc-win.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/term/rxvt.el b/lisp/term/rxvt.el index d80bb78804c..d88b12b799f 100644 --- a/lisp/term/rxvt.el +++ b/lisp/term/rxvt.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/term/sun.el b/lisp/term/sun.el index 88e63d2c9ea..64c67ae8122 100644 --- a/lisp/term/sun.el +++ b/lisp/term/sun.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/term/tty-colors.el b/lisp/term/tty-colors.el index 9cfe30a4630..3b86aa7c9b9 100644 --- a/lisp/term/tty-colors.el +++ b/lisp/term/tty-colors.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/term/tvi970.el b/lisp/term/tvi970.el index 862cd7978cb..1ce82200b38 100644 --- a/lisp/term/tvi970.el +++ b/lisp/term/tvi970.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -36,7 +36,7 @@ ;; ("\eOR" [kp-f3]) ;; ("\eOS" [kp-f4]) - ;; These might bre set by terminfo. + ;; These might be set by terminfo. ("\e[H" [home]) ("\e[Z" [backtab]) ("\e[i" [print]) @@ -45,13 +45,13 @@ ("\e[M" [deleteline]) ("\e[U" [next]) ;; actually the `page' key - ;; These won't be set up by either + ;; These won't be set up by either. ("\eOm" [kp-subtract]) ("\eOl" [kp-separator]) ("\eOn" [kp-decimal]) ("\eOM" [kp-enter]) - ;; These won't be set up by either either + ;; These won't be set up by either. ("\e[K" [key_eol]) ;; Not an X keysym ("\e[J" [key_eos]) ;; Not an X keysym ("\e[2J" [key_clear]) ;; Not an X keysym @@ -73,7 +73,7 @@ ("\e5" [S-send]) ;; Not an X keysym )) (define-key map (car key-binding) (nth 1 key-binding))) - + ;; The numeric keypad keys. (dotimes (i 10) diff --git a/lisp/term/vt100.el b/lisp/term/vt100.el index bc171381cc2..44bee803aa0 100644 --- a/lisp/term/vt100.el +++ b/lisp/term/vt100.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/term/w32-win.el b/lisp/term/w32-win.el index fda93884c40..4e0e54ae179 100644 --- a/lisp/term/w32-win.el +++ b/lisp/term/w32-win.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -275,7 +275,8 @@ See the documentation of `create-fontset-from-fontset-spec' for the format.") '(gnutls "libgnutls-30.dll") '(gnutls "libgnutls-28.dll" "libgnutls-26.dll")) '(libxml2 "libxml2-2.dll" "libxml2.dll") - '(zlib "zlib1.dll" "libz-1.dll"))) + '(zlib "zlib1.dll" "libz-1.dll") + '(lcms2 "liblcms2-2.dll"))) ;;; multi-tty support (defvar w32-initialized nil @@ -396,7 +397,7 @@ See the documentation of `create-fontset-from-fontset-spec' for the format.") ;;; Fix interface to (X-specific) mouse.el (defun w32--set-selection (type value) (if (eq type 'CLIPBOARD) - (w32-set-clipboard-data value) + (w32-set-clipboard-data (replace-regexp-in-string "\0" "\\0" value t t)) (put 'x-selections (or type 'PRIMARY) value))) (defun w32--get-selection (&optional type data-type) diff --git a/lisp/term/w32console.el b/lisp/term/w32console.el index f8b8b3c1b43..b6e04669c38 100644 --- a/lisp/term/w32console.el +++ b/lisp/term/w32console.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/term/wyse50.el b/lisp/term/wyse50.el index a6b2e7cc437..f6abc79c5e5 100644 --- a/lisp/term/wyse50.el +++ b/lisp/term/wyse50.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/term/x-win.el b/lisp/term/x-win.el index 532d0395cf4..e7b1e08b038 100644 --- a/lisp/term/x-win.el +++ b/lisp/term/x-win.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -1182,7 +1182,7 @@ as returned by `x-server-vendor'." This returns an error if any Emacs frames are X frames." ;; Don't allow suspending if any of the frames are X frames. (if (memq 'x (mapcar #'window-system (frame-list))) - (error "Cannot suspend Emacs while running under X"))) + (error "Cannot suspend Emacs while an X GUI frame exists"))) (defvar x-initialized nil "Non-nil if the X window system has been initialized.") @@ -1287,7 +1287,7 @@ This returns an error if any Emacs frames are X frames." ;; During initialization, we defer sending size hints to the window ;; manager, because that can induce a race condition: - ;; http://lists.gnu.org/archive/html/emacs-devel/2008-10/msg00033.html + ;; https://lists.gnu.org/r/emacs-devel/2008-10/msg00033.html ;; Send the size hints once initialization is done. (add-hook 'after-init-hook 'x-wm-set-size-hint) diff --git a/lisp/term/xterm.el b/lisp/term/xterm.el index e6d224dd3de..b7d0cfb4792 100644 --- a/lisp/term/xterm.el +++ b/lisp/term/xterm.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -68,6 +68,11 @@ string bytes that can be copied is 3/4 of this value." :version "25.1" :type 'integer) +(defcustom xterm-set-window-title nil + "Whether Emacs should set window titles to an Emacs frame in an XTerm." + :version "27.1" + :type 'boolean) + (defconst xterm-paste-ending-sequence "\e[201~" "Characters send by the terminal to end a bracketed paste.") @@ -610,7 +615,7 @@ Return the pasted text as a string." ;; Set up colors, for those versions of xterm that support it. (defvar xterm-standard-colors ;; The names in the comments taken from XTerm-col.ad in the xterm - ;; distribution, see ftp://dickey.his.com/xterm/. RGB values are + ;; distribution, see https://invisible-island.net/xterm/. RGB values are ;; from rgb.txt. '(("black" 0 ( 0 0 0)) ; black ("red" 1 (205 0 0)) ; red3 @@ -802,6 +807,8 @@ We run the first FUNCTION whose STRING matches the input events." (when (memq 'setSelection xterm-extra-capabilities) (xterm--init-activate-set-selection))) + (when xterm-set-window-title + (xterm--init-frame-title)) ;; Unconditionally enable bracketed paste mode: terminals that don't ;; support it just ignore the sequence. (xterm--init-bracketed-paste-mode) @@ -828,6 +835,34 @@ We run the first FUNCTION whose STRING matches the input events." "Terminal initialization for `gui-set-selection'." (set-terminal-parameter nil 'xterm--set-selection t)) +(defun xterm--init-frame-title () + "Terminal initialization for XTerm frame titles." + (xterm-set-window-title) + (add-hook 'after-make-frame-functions 'xterm-set-window-title-flag) + (add-hook 'window-configuration-change-hook 'xterm-unset-window-title-flag) + (add-hook 'post-command-hook 'xterm-set-window-title) + (add-hook 'minibuffer-exit-hook 'xterm-set-window-title)) + +(defvar xterm-window-title-flag nil + "Whether a new frame has been created, calling for a title update.") + +(defun xterm-set-window-title-flag (_frame) + "Set `xterm-window-title-flag'. +See `xterm--init-frame-title'" + (setq xterm-window-title-flag t)) + +(defun xterm-unset-window-title-flag () + (when xterm-window-title-flag + (setq xterm-window-title-flag nil) + (xterm-set-window-title))) + +(defun xterm-set-window-title (&optional terminal) + "Set the window title of the Xterm TERMINAL. +The title is constructed from `frame-title-format'." + (send-string-to-terminal + (format "\e]2;%s\a" (format-mode-line frame-title-format)) + terminal)) + (defun xterm--selection-char (type) (pcase type ('PRIMARY "p") diff --git a/lisp/textmodes/artist.el b/lisp/textmodes/artist.el index 596570ca4e2..10e788145a6 100644 --- a/lisp/textmodes/artist.el +++ b/lisp/textmodes/artist.el @@ -27,7 +27,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -4889,7 +4889,7 @@ If optional argument STATE is positive, turn borders on." (select-window (posn-window (event-start last-input-event))) (list last-input-event (if (display-popup-menus-p) - (x-popup-menu last-nonmenu-event artist-popup-menu-table) + (x-popup-menu t artist-popup-menu-table) 'no-popup-menus)))) (if (eq op 'no-popup-menus) diff --git a/lisp/textmodes/bib-mode.el b/lisp/textmodes/bib-mode.el index 2f3c17b3b29..d6bb636a9b7 100644 --- a/lisp/textmodes/bib-mode.el +++ b/lisp/textmodes/bib-mode.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/textmodes/bibtex-style.el b/lisp/textmodes/bibtex-style.el index 393bbd1c3af..bd36b9738d0 100644 --- a/lisp/textmodes/bibtex-style.el +++ b/lisp/textmodes/bibtex-style.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/textmodes/bibtex.el b/lisp/textmodes/bibtex.el index 2128e50797d..b6b12e6a9c9 100644 --- a/lisp/textmodes/bibtex.el +++ b/lisp/textmodes/bibtex.el @@ -25,7 +25,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/textmodes/conf-mode.el b/lisp/textmodes/conf-mode.el index 054d8dbb8b2..33dc3722aa6 100644 --- a/lisp/textmodes/conf-mode.el +++ b/lisp/textmodes/conf-mode.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -175,6 +175,16 @@ not align (only setting space according to `conf-assignment-space')." table) "Syntax table in use in Xdefaults style `conf-mode' buffers.") +(defvar conf-toml-mode-syntax-table + (let ((table (make-syntax-table conf-mode-syntax-table))) + (modify-syntax-entry ?\" "\"" table) + (modify-syntax-entry ?' "\"" table) + (modify-syntax-entry ?\\ "\\" table) + (modify-syntax-entry ?# "<" table) + ;; override + (modify-syntax-entry ?\; "." table) + table) + "Syntax table in use in TOML style `conf-mode' buffers.") (defvar conf-font-lock-keywords '(;; [section] (do this first because it may look like a parameter) @@ -242,6 +252,22 @@ This variable is best set in the file local variables, or through ("^[ \t]*\\([^:\n]+\\)[ \t\n]*{[^{}]*?$" 1 'font-lock-type-face prepend)) "Keywords to highlight in Conf Colon mode.") +(defvar conf-toml-font-lock-keywords + '(;; [section] (do this first because it may look like a parameter) + (conf-toml-recognize-section 0 'font-lock-type-face prepend) + ;; var=val or var[index]=val + ("^\\s-*\\(.+?\\)\\(?:\\[\\(.*?\\)\\]\\)?\\s-*=" + (1 'font-lock-variable-name-face) + (2 'font-lock-constant-face nil t)) + ("\\_<false\\|true\\_>" 0 'font-lock-keyword-face)) + "Keywords to highlight in Conf TOML mode.") + +(defvar conf-desktop-font-lock-keywords + `(,@conf-font-lock-keywords + ("\\_<false\\|true\\_>" 0 'font-lock-constant-face) + ("\\_<%[uUfFick%]\\_>" 0 'font-lock-constant-face)) + "Keywords to highlight in Conf Desktop mode.") + (defvar conf-assignment-sign ?= "Sign used for assignments (char or string).") @@ -429,16 +455,7 @@ The optional arg FONT-LOCK is the value for FONT-LOCK-KEYWORDS." ;;;###autoload (define-derived-mode conf-unix-mode conf-mode "Conf[Unix]" "Conf Mode starter for Unix style Conf files. -Comments start with `#'. -For details see `conf-mode'. Example: - -# Conf mode font-locks this right on Unix and with \\[conf-unix-mode] - -[Desktop Entry] - Encoding=UTF-8 - Name=The GIMP - Name[ca]=El GIMP - Name[cs]=GIMP" +Comments start with `#'. For details see `conf-mode'." (conf-mode-initialize "#")) ;;;###autoload @@ -617,6 +634,61 @@ For details see `conf-mode'. Example: *foreground: black" (conf-mode-initialize "!")) +(defun conf-toml-recognize-section (limit) + "Font-lock helper function for conf-toml-mode. +Handles recognizing TOML section names, like [section], +\[[section]], or [something.\"else\".section]." + (save-excursion + ;; Skip any number of "[" to handle things like [[section]]. + (when (re-search-forward "^\\s-*\\[+" limit t) + (let ((start (point))) + (backward-char) + (let ((end (min limit + (condition-case nil + (progn + (forward-list) + (1- (point))) + (scan-error + (end-of-line) + (point)))))) + ;; If there is a comma in the text, then we assume this is + ;; an array and not a section. (This could be refined to + ;; look only for unquoted commas if necessary.) + (save-excursion + (goto-char start) + (unless (search-forward "," end t) + (set-match-data (list start end)) + t))))))) + +;;;###autoload +(define-derived-mode conf-toml-mode conf-mode "Conf[TOML]" + "Conf Mode starter for TOML files. +Comments start with `#' and \"assignments\" are with `='. +For details see `conf-mode'. Example: + +# Conf mode font-locks this right with \\[conf-toml-mode] + +\[entry] +value = \"some string\"" + (conf-mode-initialize "#" 'conf-toml-font-lock-keywords) + (setq-local conf-assignment-column 0) + (setq-local conf-assignment-sign ?=)) + +;;;###autoload +(define-derived-mode conf-desktop-mode conf-unix-mode "Conf[Desktop]" + "Conf Mode started for freedesktop.org Desktop files. +Comments start with `#' and \"assignments\" are with `='. +For details see `conf-mode'. + +# Conf mode font-locks this correctly with \\[conf-desktop-mode] + [Desktop Entry] + Name=GNU Image Manipulation Program + Name[oc]=Editor d'imatge GIMP + Exec=gimp-2.8 %U + Terminal=false" + (conf-mode-initialize "#" 'conf-desktop-font-lock-keywords) + (conf-quote-normal nil)) + (provide 'conf-mode) ;;; conf-mode.el ends here diff --git a/lisp/textmodes/css-mode.el b/lisp/textmodes/css-mode.el index 4952533e834..93ca36b08aa 100644 --- a/lisp/textmodes/css-mode.el +++ b/lisp/textmodes/css-mode.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -764,7 +764,6 @@ cannot be completed sensibly: `custom-ident', "Self inserting keys which should trigger re-indentation." :version "22.2" :type '(repeat character) - :options '((?\} ?\;)) :group 'css) (defvar css-mode-syntax-table @@ -836,7 +835,7 @@ cannot be completed sensibly: `custom-ident', (defface css-selector '((t :inherit font-lock-function-name-face)) "Face to use for selectors." :group 'css) -(defface css-property '((t :inherit font-lock-variable-name-face)) +(defface css-property '((t :inherit font-lock-keyword-face)) "Face to use for properties." :group 'css) (defface css-proprietary-property '((t :inherit (css-property italic))) @@ -897,7 +896,7 @@ cannot be completed sensibly: `custom-ident', ;; No face. nil))) ;; Variables. - (,(concat "--" css-ident-re) (0 font-lock-variable-name-face)) + (,(concat (rx symbol-start) "--" css-ident-re) (0 font-lock-variable-name-face)) ;; Properties. Again, we don't limit ourselves to css-property-ids. (,(concat "\\(?:[{;]\\|^\\)[ \t]*\\(" "\\(?:\\(" css-proprietary-nmstart-re "\\)\\|" @@ -1046,7 +1045,7 @@ This function simply drops any transparency." "Check whether STR, seen at point, is CSS named color. Returns STR if it is a valid color. Special care is taken to exclude some SCSS constructs." - (when-let ((color (assoc str css--color-map))) + (when-let* ((color (assoc str css--color-map))) (save-excursion (goto-char start-point) (forward-comment (- (point))) @@ -1150,12 +1149,12 @@ This function is intended to be good enough to help SMIE during tokenization, but should not be regarded as a reliable function for determining whether point is within a selector." (save-excursion - (re-search-forward "[{};)]" nil t) + (re-search-forward "[{};]" nil t) (eq (char-before) ?\{))) (defun css--colon-inside-funcall () "Return t if point is inside a function call." - (when-let (opening-paren-pos (nth 1 (syntax-ppss))) + (when-let* ((opening-paren-pos (nth 1 (syntax-ppss)))) (save-excursion (goto-char opening-paren-pos) (eq (char-after) ?\()))) @@ -1206,9 +1205,12 @@ for determining whether point is within a selector." (`(:before . "{") (when (or (smie-rule-hanging-p) (smie-rule-bolp)) (smie-backward-sexp ";") - (smie-indent-virtual))) - (`(:before . ,(or "{" "(")) - (if (smie-rule-hanging-p) (smie-rule-parent 0))) + (unless (eq (char-after) ?\{) + (smie-indent-virtual)))) + (`(:before . "(") + (cond + ((smie-rule-hanging-p) (smie-rule-parent 0)) + ((not (smie-rule-bolp)) 0))) (`(:after . ":-property") (when (smie-rule-hanging-p) css-indent-offset)))) @@ -1373,6 +1375,7 @@ tags, classes and IDs." :exit-function ,(lambda (string status) (and (eq status 'finished) + (eolp) prop-table (test-completion string prop-table) (not (and sel-table @@ -1576,7 +1579,7 @@ to look up will be substituted there." (goto-char (point-min)) (let ((window (get-buffer-window (current-buffer) 'visible))) (when window - (when (re-search-forward "^Summary" nil 'move) + (when (re-search-forward "^\\(Summary\\|Syntax\\)" nil 'move) (beginning-of-line) (set-window-start window (point)))))) @@ -1657,14 +1660,13 @@ on what is seen near point." (setq symbol (concat ":" symbol))) (let ((url (format css-lookup-url-format symbol)) (buffer (get-buffer-create "*MDN CSS*"))) - (save-selected-window - ;; Make sure to display the buffer before calling `eww', as - ;; that calls `pop-to-buffer-same-window'. - (switch-to-buffer-other-window buffer) - (with-current-buffer buffer - (eww-mode) - (add-hook 'eww-after-render-hook #'css--mdn-after-render nil t) - (eww url)))))) + ;; Make sure to display the buffer before calling `eww', as that + ;; calls `pop-to-buffer-same-window'. + (switch-to-buffer-other-window buffer) + (with-current-buffer buffer + (eww-mode) + (add-hook 'eww-after-render-hook #'css--mdn-after-render nil t) + (eww url))))) (provide 'css-mode) ;;; css-mode.el ends here diff --git a/lisp/textmodes/dns-mode.el b/lisp/textmodes/dns-mode.el index 72eb66b571e..6b668a62674 100644 --- a/lisp/textmodes/dns-mode.el +++ b/lisp/textmodes/dns-mode.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -35,6 +35,7 @@ ;; RFC 5155, "DNS Security (DNSSEC) Hashed Authenticated Denial of Existence" ;; RFC 6698, "The DNS-Based Authentication of Named Entities (DANE) ;; Transport Layer Security (TLS) Protocol: TLSA" +;; RFC 6844, "DNS Certification Authority Authorization (CAA) Resource Record" ;;; Release history: @@ -62,7 +63,7 @@ "A6" "DNAME" "SINK" "OPT" "APL" "DS" "SSHFP" "RRSIG" "NSEC" "DNSKEY" "UINFO" "UID" "GID" "UNSPEC" "TKEY" "TSIG" "IXFR" "AXFR" "MAILB" - "MAILA" "TLSA" "NSEC3") + "MAILA" "TLSA" "NSEC3" "CAA") "List of strings with known DNS types.") (defface dns-mode-control-entity '((t :inherit font-lock-keyword-face)) diff --git a/lisp/textmodes/enriched.el b/lisp/textmodes/enriched.el index 7ace2a50486..be5cd6b7310 100644 --- a/lisp/textmodes/enriched.el +++ b/lisp/textmodes/enriched.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -147,6 +147,22 @@ them and their old values to `enriched-old-bindings'." :type 'hook :group 'enriched) +(defcustom enriched-allow-eval-in-display-props nil + "If non-nil allow to evaluate arbitrary forms in display properties. + +Enriched mode recognizes display properties of text stored using +an extension command to the text/enriched format, \"x-display\". +These properties must not, by default, include evaluation of +Lisp forms, otherwise they are not applied. Customize this option +to t to turn off this safety feature, and allow Enriched mode to +apply display properties which evaluate arbitrary Lisp forms. +Note, however, that applying unsafe display properties could +execute malicious Lisp code, if that code came from an external source." + :risky t + :type 'boolean + :version "26.1" + :group 'enriched) + (defvar enriched-old-bindings nil "Store old variable values that we change when entering mode. The value is a list of \(VAR VALUE VAR VALUE...).") @@ -503,6 +519,8 @@ the range of text to assign text property SYMBOL with value VALUE." (error nil))))) (unless prop (message "Warning: invalid <x-display> parameter %s" param)) - (list start end 'display prop))) + (if enriched-allow-eval-in-display-props + (list start end 'display prop) + (list start end 'display (list 'disable-eval prop))))) ;;; enriched.el ends here diff --git a/lisp/textmodes/fill.el b/lisp/textmodes/fill.el index b640adb7a7b..30e10d70aa8 100644 --- a/lisp/textmodes/fill.el +++ b/lisp/textmodes/fill.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/textmodes/flyspell.el b/lisp/textmodes/flyspell.el index ecf729d15b7..dc6da4aab29 100644 --- a/lisp/textmodes/flyspell.el +++ b/lisp/textmodes/flyspell.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -231,7 +231,7 @@ Ispell's ultimate default dictionary." "Non-nil means check even inside TeX math environment. TeX math environments are discovered by `texmathp', implemented inside AUCTeX package. That package may be found at -URL `http://www.gnu.org/software/auctex/'" +URL `https://www.gnu.org/software/auctex/'" :group 'flyspell :type 'boolean) diff --git a/lisp/textmodes/ispell.el b/lisp/textmodes/ispell.el index 773023a34a6..6a169622f52 100644 --- a/lisp/textmodes/ispell.el +++ b/lisp/textmodes/ispell.el @@ -1,10 +1,8 @@ -;;; ispell.el --- interface to International Ispell Versions 3.1 and 3.2 -*- lexical-binding:t -*- +;;; ispell.el --- interface to spell checkers -*- lexical-binding:t -*- ;; Copyright (C) 1994-1995, 1997-2017 Free Software Foundation, Inc. ;; Author: Ken Stevens <k.stevens@ieee.org> -;; Status : Release with 3.1.12+ and 3.2.0+ ispell. -;; Keywords: unix wp ;; This file is part of GNU Emacs. @@ -19,25 +17,13 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. - -;; Note: version numbers and time stamp are not updated -;; when this file is edited for release with GNU Emacs. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; INSTRUCTIONS -;; This code contains a section of user-settable variables that you -;; should inspect prior to installation. Look past the end of the history -;; list. Set them up for your locale and the preferences of the majority -;; of the users. Otherwise the users may need to set a number of variables -;; themselves. -;; You particularly may want to change the default dictionary for your -;; country and language. -;; Most dictionary changes should be made in this file so all users can -;; enjoy them. Local or modified dictionaries are supported in your .emacs -;; file. Use the variable `ispell-local-dictionary-alist' to specify +;; Use the variable `ispell-local-dictionary-alist' to specify ;; your own dictionaries. ;; Depending on the mail system you use, you may want to include these: @@ -112,7 +98,7 @@ ;; Need a way to select between different character mappings without separate ;; dictionary entries. ;; Multi-byte characters if not defined by current dictionary may result in the -;; evil "misalignment error" in some versions of MULE Emacs. +;; evil "misalignment error" in some versions of Emacs. ;; On some versions of Emacs, growing the minibuffer fails. ;; see `ispell-help-in-bufferp'. ;; Recursive edits (?C-r or ?R) inside a keyboard text replacement check (?r) @@ -121,6 +107,7 @@ ;;; Code: (eval-when-compile (require 'cl-lib)) +(eval-when-compile (require 'subr-x)) (defvar mail-yank-prefix) @@ -208,6 +195,10 @@ Must be greater than 1." :type 'integer :group 'ispell) +;; XXX Add enchant to this list once enchant >= 2.1.0 is widespread. +;; Before that, adding it is useless, as if it is found, it will just +;; cause an error; and one of the other spelling engines below is +;; almost certainly installed in any case, for enchant to use. (defcustom ispell-program-name (or (executable-find "aspell") (executable-find "ispell") @@ -605,6 +596,8 @@ english.aff). Aspell and Hunspell don't have this limitation.") "Non-nil if we can use Aspell extensions.") (defvar ispell-really-hunspell nil "Non-nil if we can use Hunspell extensions.") +(defvar ispell-really-enchant nil + "Non-nil if we can use Enchant extensions.") (defvar ispell-encoding8-command nil "Command line option prefix to select encoding if supported, nil otherwise. If setting the encoding is supported by spellchecker and is selectable from @@ -739,17 +732,26 @@ Otherwise returns the library directory name, if that is defined." (and (search-forward-regexp "(but really Hunspell \\([0-9]+\\.[0-9\\.-]+\\)?)" nil t) + (match-string 1))) + (setq ispell-really-enchant + (and (search-forward-regexp + "(but really Enchant \\([0-9]+\\.[0-9\\.-]+\\)?)" + nil t) (match-string 1))))) (let* ((aspell8-minver "0.60") (ispell-minver "3.1.12") (hunspell8-minver "1.1.6") + (enchant-minver "2.1.0") (minver (cond ((not (version<= ispell-minver ispell-program-version)) ispell-minver) ((and ispell-really-aspell (not (version<= aspell8-minver ispell-really-aspell))) - aspell8-minver)))) + aspell8-minver) + ((and ispell-really-enchant + (not (version<= enchant-minver ispell-really-enchant))) + enchant-minver)))) (if minver (error "%s release %s or greater is required" @@ -1183,6 +1185,49 @@ dictionary from that list was found." (list dict)) ispell-hunspell-dictionary-alist :test #'equal)))) +;; Make ispell.el work better with enchant. + +(defvar ispell-enchant-dictionary-alist nil + "An alist of parsed Enchant dicts and associated parameters. +Internal use.") + +(defun ispell--call-enchant-lsmod (&rest args) + "Call enchant-lsmod with ARGS and return the output as string." + (with-output-to-string + (with-current-buffer + standard-output + (apply 'ispell-call-process + (concat ispell-program-name "-lsmod") nil t nil args)))) + +(defun ispell--get-extra-word-characters (&optional lang) + "Get the extra word characters for LANG as a character class. +If LANG is omitted, get the extra word characters for the default language." + (concat "[" (string-trim-right (apply 'ispell--call-enchant-lsmod + (append '("-word-chars") (if lang `(,lang))))) "]")) + +(defun ispell-find-enchant-dictionaries () + "Find Enchant's dictionaries, and record in `ispell-enchant-dictionary-alist'." + (let* ((dictionaries + (split-string + (ispell--call-enchant-lsmod "-list-dicts" (buffer-string)) " ([^)]+)\n")) + (found + (mapcar #'(lambda (lang) + `(,lang "[[:alpha:]]" "[^[:alpha:]]" + ,(ispell--get-extra-word-characters) t nil nil utf-8)) + dictionaries))) + ;; Merge into FOUND any elements from the standard ispell-dictionary-base-alist + ;; which have no element in FOUND at all. + (dolist (dict ispell-dictionary-base-alist) + (unless (assoc (car dict) found) + (setq found (nconc found (list dict))))) + (setq ispell-enchant-dictionary-alist found) + ;; Add a default entry + (let ((default-dict + `(nil "[[:alpha:]]" "[^[:alpha:]]" + ,(ispell--get-extra-word-characters) + t nil nil utf-8))) + (push default-dict ispell-enchant-dictionary-alist)))) + ;; Set params according to the selected spellchecker (defvar ispell-last-program-name nil @@ -1208,7 +1253,7 @@ aspell is used along with Emacs).") (setq ispell-library-directory (ispell-check-version)) t) (error nil)) - ispell-encoding8-command) + (or ispell-encoding8-command ispell-really-enchant)) ;; auto-detection will only be used if spellchecker is not ;; ispell and supports a way to set communication to UTF-8. (if ispell-really-aspell @@ -1216,11 +1261,14 @@ aspell is used along with Emacs).") (ispell-find-aspell-dictionaries)) (if ispell-really-hunspell (or ispell-hunspell-dictionary-alist - (ispell-find-hunspell-dictionaries))))) + (ispell-find-hunspell-dictionaries)) + (if ispell-really-enchant + (or ispell-enchant-dictionary-alist + (ispell-find-enchant-dictionaries)))))) ;; Substitute ispell-dictionary-alist with the list of ;; dictionaries corresponding to the given spellchecker. - ;; If a recent aspell or hunspell, use the list of really + ;; With programs that support it, use the list of really ;; installed dictionaries and add to it elements of the original ;; list that are not present there. Allow distro info. (let ((found-dicts-alist @@ -1229,17 +1277,19 @@ aspell is used along with Emacs).") ispell-aspell-dictionary-alist (if ispell-really-hunspell ispell-hunspell-dictionary-alist)) - nil)) + (if ispell-really-enchant + ispell-enchant-dictionary-alist + nil))) (ispell-dictionary-base-alist ispell-dictionary-base-alist) ispell-base-dicts-override-alist ; Override only base-dicts-alist all-dicts-alist) ;; While ispell and aspell (through aliases) use the traditional - ;; dict naming originally expected by ispell.el, hunspell - ;; uses locale based names with no alias. We need to map + ;; dict naming originally expected by ispell.el, hunspell & Enchant + ;; use locale-based names with no alias. We need to map ;; standard names to locale based names to make default dict - ;; definitions available for hunspell. - (if ispell-really-hunspell + ;; definitions available to these programs. + (if (or ispell-really-hunspell ispell-really-enchant) (let (tmp-dicts-alist) (dolist (adict ispell-dictionary-base-alist) (let* ((dict-name (nth 0 adict)) @@ -1264,7 +1314,7 @@ aspell is used along with Emacs).") (setq ispell-args (nconc ispell-args (list "-d" dict-equiv))) (message - "ispell-set-spellchecker-params: Missing Hunspell equiv for \"%s\". Skipping." + "ispell-set-spellchecker-params: Missing equivalent for \"%s\". Skipping." dict-name) (setq skip-dict t))) @@ -1306,7 +1356,7 @@ aspell is used along with Emacs).") (nth 4 adict) ; many-otherchars-p (nth 5 adict) ; ispell-args (nth 6 adict) ; extended-character-mode - (if ispell-encoding8-command + (if (or ispell-encoding8-command ispell-really-enchant) 'utf-8 (nth 7 adict))) adict) @@ -1435,25 +1485,17 @@ used as key in `ispell-local-dictionary-alist' and `ispell-dictionary-alist'.") "The name of the current personal dictionary, or nil for the default. This is passed to the Ispell process using the `-p' switch.") -(defun ispell-decode-string (str) - "Decodes multibyte character strings." - (decode-coding-string str (ispell-get-coding-system))) - ;; Return a string decoded from Nth element of the current dictionary. (defun ispell-get-decoded-string (n) "Get the decoded string in slot N of the descriptor of the current dict." (let* ((slot (or (assoc ispell-current-dictionary ispell-local-dictionary-alist) (assoc ispell-current-dictionary ispell-dictionary-alist) - (error "No data for dictionary \"%s\", neither in `ispell-local-dictionary-alist' nor in `ispell-dictionary-alist'" + (error "No data for dictionary \"%s\" in `ispell-local-dictionary-alist' or `ispell-dictionary-alist'" ispell-current-dictionary))) - (str (nth n slot))) - (when (and (> (length str) 0) - (not (multibyte-string-p str))) - (setq str (ispell-decode-string str)) - (or (multibyte-string-p str) - (setq str (string-to-multibyte str)))) - str)) + (str (nth n slot))) + (if (stringp str) + (decode-coding-string str (ispell-get-coding-system) t)))) (defun ispell-get-casechars () (ispell-get-decoded-string 1)) @@ -1742,9 +1784,10 @@ and pass it the output of the last Ispell invocation." (erase-buffer))))))) (defun ispell-send-replacement (misspelled replacement) - "Notify Aspell that MISSPELLED should be spelled REPLACEMENT. -This allows improving the suggestion list based on actual misspellings." - (and ispell-really-aspell + "Notify spell checker that MISSPELLED should be spelled REPLACEMENT. +This allows improving the suggestion list based on actual misspellings. +Only works for Aspell and Enchant." + (and (or ispell-really-aspell ispell-really-enchant) (ispell-send-string (concat "$$ra " misspelled "," replacement "\n")))) @@ -3460,17 +3503,9 @@ Returns the sum SHIFT due to changes in word replacements." (setq ispell-filter recheck-region recheck-region nil replace replace-word))))) + (setq shift (+ shift (- (length replace) word-len))))) - (setq shift (+ shift (- (length replace) word-len))) - - ;; Move line-start across word... - ;; new shift function does this now... - ;;(set-marker line-start (+ line-start - ;; (- (length replace) - ;; (length (car poss))))) - )) (if (not ispell-quit) - ;; FIXME: remove redundancy with identical code above. (let (message-log-max) (message "Continuing spelling check using %s with %s dictionary..." diff --git a/lisp/textmodes/less-css-mode.el b/lisp/textmodes/less-css-mode.el new file mode 100644 index 00000000000..d31414e3a4b --- /dev/null +++ b/lisp/textmodes/less-css-mode.el @@ -0,0 +1,232 @@ +;;; less-css-mode.el --- Major mode for editing Less CSS files -*- lexical-binding: t; -*- + +;; Copyright (C) 2011-2017 Free Software Foundation, Inc. + +;; Author: Steve Purcell <steve@sanityinc.com> +;; Maintainer: Simen Heggestøyl <simenheg@gmail.com> +;; Keywords: hypermedia + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; This mode provides syntax highlighting for Less CSS files +;; (http://lesscss.org/), plus optional support for compilation of +;; .less files to .css files at the time they are saved: use +;; `less-css-compile-at-save' to enable this. +;; +;; Command line utility "lessc" is required if setting +;; `less-css-compile-at-save' to t. To install "lessc" using the +;; Node.js package manager, run "npm install less". +;; +;; Also make sure the "lessc" executable is in Emacs' PATH, example: +;; (push (expand-file-name "~/.gem/ruby/1.8/bin") exec-path) +;; or customize `less-css-lessc-command' to point to your "lessc" +;; executable. +;; +;; We target lessc >= 1.4.0, and thus use the `--no-color' flag by +;; default. You may want to adjust `less-css-lessc-options' for +;; compatibility with older versions. +;; +;; `less-css-mode' is derived from `css-mode', and indentation of +;; nested blocks may not work correctly with versions of `css-mode' +;; other than that bundled with recent Emacs. +;; +;; You can specify per-file values for `less-css-compile-at-save', +;; `less-css-output-file-name' or `less-css-output-directory' using a +;; variables header at the top of your .less file, e.g.: +;; +;; // -*- less-css-compile-at-save: t; less-css-output-directory: "../css" -*- +;; +;; Alternatively, you can use directory local variables to set the +;; default value of `less-css-output-directory' for your project. +;; +;; In the case of files which are included in other .less files, you +;; may want to trigger the compilation of a "master" .less file on +;; save: you can accomplish this with `less-css-input-file-name', +;; which is probably best set using directory local variables. +;; +;; If you don't need CSS output but would like to be warned of any +;; syntax errors in your .less source, consider using `flymake-less': +;; https://github.com/purcell/flymake-less. + +;;; Credits + +;; The original code for this mode was, in large part, written using +;; Anton Johansson's scss-mode as a template -- thanks Anton! +;; https://github.com/antonj + +;;; Code: + +(require 'compile) +(require 'css-mode) +(require 'derived) +(eval-when-compile (require 'subr-x)) + +(defgroup less-css nil + "Less CSS mode." + :prefix "less-css-" + :group 'css) + +(defcustom less-css-lessc-command "lessc" + "Command used to compile Less files. +Should be \"lessc\" or the complete path to your lessc +executable, e.g.: \"~/.gem/ruby/1.8/bin/lessc\"." + :type 'file) + +(defcustom less-css-compile-at-save nil + "If non-nil, Less buffers are compiled to CSS after each save." + :type 'boolean) +;;;###autoload +(put 'less-css-compile-at-save 'safe-local-variable 'booleanp) + +(defcustom less-css-lessc-options '("--no-color") + "Command line options for Less executable. +Use \"-x\" to minify output." + :type '(repeat string)) +;;;###autoload +(put 'less-css-lessc-options 'safe-local-variable t) + +(defcustom less-css-output-directory nil + "Directory in which to save CSS, or nil to use the Less file's directory. +This path is expanded relative to the directory of the Less file +using `expand-file-name', so both relative and absolute paths +will work as expected." + :type 'directory) +;;;###autoload +(put 'less-css-output-directory 'safe-local-variable 'stringp) + +(defcustom less-css-output-file-name nil + "File name in which to save CSS, or nil to use <name>.css for <name>.less. +This can be also be set to a full path, or a relative path. If +the path is relative, it will be relative to the value of +`less-css-output-dir', if set, or the current directory by +default." + :type 'file) +(make-variable-buffer-local 'less-css-output-file-name) + +(defcustom less-css-input-file-name nil + "File name which will be compiled to CSS. +When the current buffer is saved `less-css-input-file-name' file +will be compiled to CSS instead of the current file. + +Set this in order to trigger compilation of a \"master\" .less +file which includes the current file. The best way to set this +variable in most cases is likely to be via directory local +variables. + +This can be also be set to a full path, or a relative path. If +the path is relative, it will be relative to the current +directory by default." + :type 'file) +;;;###autoload +(put 'less-css-input-file-name 'safe-local-variable 'stringp) +(make-variable-buffer-local 'less-css-input-file-name) + +(defconst less-css-default-error-regex + "^\\(?:\e\\[31m\\)?\\([^\e\n]*\\|FileError:.*\n\\)\\(?:\e\\[39m\e\\[31m\\)? in \\(?:\e\\[39m\\)?\\([^ \r\n\t\e]+\\)\\(?:\e\\[90m\\)?\\(?::\\| on line \\)\\([0-9]+\\)\\(?::\\|, column \\)\\([0-9]+\\):?\\(?:\e\\[39m\\)?") + +;;; Compilation to CSS + +(add-to-list 'compilation-error-regexp-alist-alist + (list 'less-css less-css-default-error-regex 2 3 4 nil 1)) +(add-to-list 'compilation-error-regexp-alist 'less-css) + +(defun less-css-compile-maybe () + "Run `less-css-compile' if `less-css-compile-at-save' is non-nil." + (when less-css-compile-at-save + (less-css-compile))) + +(defun less-css--output-path () + "Return the path to use for the compiled CSS file." + (expand-file-name + (or less-css-output-file-name + (concat + (file-name-nondirectory + (file-name-sans-extension buffer-file-name)) + ".css")) + (or less-css-output-directory default-directory))) + +(defun less-css-compile () + "Compile the current buffer to CSS using `less-css-lessc-command'." + (interactive) + (message "Compiling Less to CSS") + (let ((compilation-buffer-name-function + (lambda (_) "*less-css-compilation*"))) + (save-window-excursion + (with-current-buffer + (compile + (string-join + (append + (list less-css-lessc-command) + (mapcar #'shell-quote-argument less-css-lessc-options) + (list (shell-quote-argument + (or less-css-input-file-name buffer-file-name)) + (shell-quote-argument (less-css--output-path)))) + " ")) + (add-hook 'compilation-finish-functions + (lambda (buf msg) + (unless (string-match-p "^finished" msg) + (display-buffer buf))) + nil + t))))) + +;;; Major mode + +;; TODO: +;; - interpolation ("@{val}") +;; - escaped values (~"...") +;; - JS eval (~`...`) +;; - custom faces. +(defconst less-css-font-lock-keywords + '(;; Variables + ("@[a-z_-][a-z-_0-9]*" . font-lock-variable-name-face) + ("&" . font-lock-preprocessor-face) + ;; Mixins + ("\\(?:[ \t{;]\\|^\\)\\(\\.[a-z_-][a-z-_0-9]*\\)[ \t]*;" . + (1 font-lock-keyword-face)))) + +(defvar less-css-mode-syntax-table + (let ((st (make-syntax-table css-mode-syntax-table))) + ;; C++-style comments. + (modify-syntax-entry ?/ ". 124b" st) + (modify-syntax-entry ?* ". 23" st) + (modify-syntax-entry ?\n "> b" st) + ;; Special chars that sometimes come at the beginning of words. + (modify-syntax-entry ?. "'" st) + st)) + +(defvar less-css-mode-map + (let ((map (make-sparse-keymap))) + (define-key map "\C-c\C-c" 'less-css-compile) + map)) + +;;;###autoload (add-to-list 'auto-mode-alist '("\\.less\\'" . less-css-mode)) +;;;###autoload +(define-derived-mode less-css-mode css-mode "Less" + "Major mode for editing Less files (http://lesscss.org/). +Special commands: +\\{less-css-mode-map}" + (font-lock-add-keywords nil less-css-font-lock-keywords) + (setq-local comment-start "//") + (setq-local comment-end "") + (setq-local comment-continue " *") + (setq-local comment-start-skip "/[*/]+[ \t]*") + (setq-local comment-end-skip "[ \t]*\\(?:\n\\|\\*+/\\)") + (add-hook 'after-save-hook 'less-css-compile-maybe nil t)) + +(provide 'less-css-mode) +;;; less-css-mode.el ends here diff --git a/lisp/textmodes/makeinfo.el b/lisp/textmodes/makeinfo.el index 9edc759c2df..34fdb961223 100644 --- a/lisp/textmodes/makeinfo.el +++ b/lisp/textmodes/makeinfo.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/textmodes/mhtml-mode.el b/lisp/textmodes/mhtml-mode.el index 2f2257d96bd..09da155f487 100644 --- a/lisp/textmodes/mhtml-mode.el +++ b/lisp/textmodes/mhtml-mode.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: @@ -75,11 +75,11 @@ code(); (defconst mhtml--crucial-variable-prefix (regexp-opt '("comment-" "uncomment-" "electric-indent-" - "smie-" "forward-sexp-function")) + "smie-" "forward-sexp-function" "completion-" "major-mode")) "Regexp matching the prefix of \"crucial\" buffer-locals we want to capture.") (defconst mhtml--variable-prefix - (regexp-opt '("font-lock-" "indent-line-function" "major-mode")) + (regexp-opt '("font-lock-" "indent-line-function")) "Regexp matching the prefix of buffer-locals we want to capture.") (defun mhtml--construct-submode (mode &rest args) @@ -149,7 +149,12 @@ code(); (defun mhtml--submode-lighter () "Mode-line lighter indicating the current submode." - (let ((submode (get-text-property (point) 'mhtml-submode))) + ;; The end of the buffer has no text properties, so in this case + ;; back up one character, if possible. + (let* ((where (if (and (eobp) (not (bobp))) + (1- (point)) + (point))) + (submode (get-text-property where 'mhtml-submode))) (if submode (mhtml--submode-name submode) ""))) @@ -193,6 +198,12 @@ smallest." (get-text-property orig-end 'mhtml-submode)) (cl-decf font-lock-end))) + ;; Also handle the multiline property -- but handle it here, and + ;; not via font-lock-extend-region-functions, to avoid the + ;; situation where the two extension functions disagree. + ;; See bug#29159. + (font-lock-extend-region-multiline) + (or (/= font-lock-beg orig-beg) (/= font-lock-end orig-end)))) @@ -232,8 +243,8 @@ smallest." (cons 'jit-lock-bounds (cons new-beg new-end))))) (defvar-local mhtml--last-submode nil - "Record the last visited submode, so the cursor-sensor function -can function properly.") + "Record the last visited submode. +This is used by `mhtml--pre-command'.") (defvar-local mhtml--stashed-crucial-variables nil "Alist of stashed values of the crucial variables.") @@ -288,9 +299,7 @@ can function properly.") (unless (bobp) (let ((submode (get-text-property (1- (point)) 'mhtml-submode))) (if submode - ;; Don't search in a comment or string - (unless (syntax-ppss-context (syntax-ppss)) - (mhtml--syntax-propertize-submode submode end)) + (mhtml--syntax-propertize-submode submode end) ;; No submode, so do what sgml-mode does. (sgml-syntax-propertize-inside end)))) (funcall @@ -356,15 +365,13 @@ can function properly.") Code inside a <script> element is indented using the rules from `js-mode'; and code inside a <style> element is indented using the rules from `css-mode'." - (cursor-sensor-mode) (setq-local indent-line-function #'mhtml-indent-line) (setq-local parse-sexp-lookup-properties t) (setq-local syntax-propertize-function #'mhtml-syntax-propertize) (setq-local font-lock-fontify-region-function #'mhtml--submode-fontify-region) (setq-local font-lock-extend-region-functions - '(mhtml--extend-font-lock-region - font-lock-extend-region-multiline)) + '(mhtml--extend-font-lock-region)) ;; Attach this to both pre- and post- hooks just in case it ever ;; changes a key binding that might be accessed from the menu bar. diff --git a/lisp/textmodes/nroff-mode.el b/lisp/textmodes/nroff-mode.el index cea0c604baf..82cb2d4dc05 100644 --- a/lisp/textmodes/nroff-mode.el +++ b/lisp/textmodes/nroff-mode.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -195,7 +195,7 @@ Puts a full-stop before comments on a line by themselves." 9) 8)))))) ; add 9 to ensure at least two blanks (goto-char pt)))) -;; http://lists.gnu.org/archive/html/emacs-devel/2007-10/msg01869.html +;; https://lists.gnu.org/r/emacs-devel/2007-10/msg01869.html (defun nroff-insert-comment-function () "Function for `comment-insert-comment-function' in `nroff-mode'." (indent-to (nroff-comment-indent)) diff --git a/lisp/textmodes/page-ext.el b/lisp/textmodes/page-ext.el index 8542b951b3b..bf1e33bf0f6 100644 --- a/lisp/textmodes/page-ext.el +++ b/lisp/textmodes/page-ext.el @@ -1,4 +1,4 @@ -;;; page-ext.el --- extended page handling commands +;;; page-ext.el --- extended page handling commands -*- lexical-binding:t -*- ;; Copyright (C) 1990-1991, 1993-1994, 2001-2017 Free Software ;; Foundation, Inc. @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -243,18 +243,15 @@ (defcustom pages-directory-buffer-narrowing-p t "If non-nil, `pages-directory-goto' narrows pages buffer to entry." - :type 'boolean - :group 'pages) + :type 'boolean) (defcustom pages-directory-for-adding-page-narrowing-p t "If non-nil, `add-new-page' narrows page buffer to new entry." - :type 'boolean - :group 'pages) + :type 'boolean) (defcustom pages-directory-for-adding-new-page-before-current-page-p t "If non-nil, `add-new-page' inserts new page before current page." - :type 'boolean - :group 'pages) + :type 'boolean) ;;; Addresses related variables @@ -262,23 +259,19 @@ (defcustom pages-addresses-file-name "~/addresses" "Standard name for file of addresses. Entries separated by page-delimiter. Used by `pages-directory-for-addresses' function." - :type 'file - :group 'pages) + :type 'file) (defcustom pages-directory-for-addresses-goto-narrowing-p t "If non-nil, `pages-directory-goto' narrows addresses buffer to entry." - :type 'boolean - :group 'pages) + :type 'boolean) (defcustom pages-directory-for-addresses-buffer-keep-windows-p t "If nil, `pages-directory-for-addresses' deletes other windows." - :type 'boolean - :group 'pages) + :type 'boolean) (defcustom pages-directory-for-adding-addresses-narrowing-p t "If non-nil, `add-new-page' narrows addresses buffer to new entry." - :type 'boolean - :group 'pages) + :type 'boolean) ;;; Key bindings for page handling functions @@ -415,9 +408,9 @@ Point is left in the body of page." Called from a program, there are three arguments: REVERSE (non-nil means reverse order), BEG and END (region to sort)." -;;; This sort function handles ends of pages differently than -;;; `sort-pages' and works better with lists of addresses and similar -;;; files. + ;; This sort function handles ends of pages differently than + ;; `sort-pages' and works better with lists of addresses and similar + ;; files. (interactive "P\nr") (save-restriction @@ -463,25 +456,27 @@ REVERSE (non-nil means reverse order), BEG and END (region to sort)." \(This regular expression may be used to select only those pages that contain matches to the regexp.)") -(defvar pages-buffer nil +(defvar-local pages-buffer nil "The buffer for which the pages-directory function creates the directory.") (defvar pages-directory-prefix "*Directory for:" "Prefix of name of temporary buffer for pages-directory.") -(defvar pages-pos-list nil +(defvar-local pages-pos-list nil "List containing the positions of the pages in the pages-buffer.") (defvar pages-target-buffer) +(define-obsolete-variable-alias 'pages-directory-map + 'pages-directory-mode-map "26.1") (defvar pages-directory-mode-map (let ((map (make-sparse-keymap))) (define-key map "\C-c\C-c" 'pages-directory-goto) + (define-key map "\C-m" 'pages-directory-goto) (define-key map "\C-c\C-p\C-a" 'add-new-page) - (define-key map [mouse-2] 'pages-directory-goto-with-mouse) + (define-key map [mouse-2] 'pages-directory-goto) map) "Keymap for the pages-directory-buffer.") -(defvaralias 'pages-directory-map 'pages-directory-mode-map) (defvar original-page-delimiter "^\f" "Default page delimiter.") @@ -512,6 +507,9 @@ resets the page-delimiter to the original value." ;;; Pages directory main definitions +(defvar pages-buffer-original-position) +(defvar pages-buffer-original-page) + (defun pages-directory (pages-list-all-headers-p count-lines-p &optional regexp) "Display a directory of the page headers in a temporary buffer. @@ -573,7 +571,6 @@ directory for only the accessible portion of the buffer." (let ((pages-target-buffer (current-buffer)) (pages-directory-buffer (concat pages-directory-prefix " " (buffer-name))) - (linenum 1) (pages-buffer-original-position (point)) (pages-buffer-original-page 0)) @@ -583,6 +580,7 @@ directory for only the accessible portion of the buffer." (with-output-to-temp-buffer pages-directory-buffer (with-current-buffer standard-output (pages-directory-mode) + (setq buffer-read-only nil) (insert "==== Pages Directory: use `C-c C-c' to go to page under cursor. ====" ?\n) (setq pages-buffer pages-target-buffer) @@ -631,6 +629,7 @@ directory for only the accessible portion of the buffer." ))))) (set-buffer standard-output) + (setq buffer-read-only t) ;; Put positions in increasing order to go with buffer. (setq pages-pos-list (nreverse pages-pos-list)) (if (called-interactively-p 'interactive) @@ -642,10 +641,6 @@ directory for only the accessible portion of the buffer." 1 pages-buffer-original-page)))) -(defvar pages-buffer-original-position) -(defvar pages-buffer-original-page) -(defvar pages-buffer-original-page) - (defun pages-copy-header-and-position (count-lines-p) "Copy page header and its position to the Pages Directory. Only arg non-nil, count lines in page and insert before header. @@ -699,16 +694,13 @@ Used by `pages-directory' function." Move point to one of the lines in this buffer, then use \\[pages-directory-goto] to go to the same line in the pages buffer." - (make-local-variable 'pages-buffer) - (make-local-variable 'pages-pos-list) (make-local-variable 'pages-directory-buffer-narrowing-p)) -(defun pages-directory-goto () +(defun pages-directory-goto (&optional event) "Go to the corresponding line in the pages buffer." - -;;; This function is mostly a copy of `occur-mode-goto-occurrence' - - (interactive) + ;; This function is mostly a copy of `occur-mode-goto-occurrence' + (interactive "@e") + (if event (mouse-set-point event)) (if (or (not pages-buffer) (not (buffer-name pages-buffer))) (progn @@ -722,18 +714,13 @@ to the same line in the pages buffer." (narrowing-p pages-directory-buffer-narrowing-p)) (pop-to-buffer pages-buffer) (widen) - (if end-of-directory-p - (goto-char (point-max)) - (goto-char (marker-position pos))) + (goto-char (if end-of-directory-p + (point-max) + (marker-position pos))) (if narrowing-p (narrow-to-page)))) -(defun pages-directory-goto-with-mouse (event) - "Go to the corresponding line under the mouse pointer in the pages buffer." - (interactive "e") - (with-current-buffer (window-buffer (posn-window (event-end event))) - (save-excursion - (goto-char (posn-point (event-end event))) - (pages-directory-goto)))) +(define-obsolete-function-alias 'pages-directory-goto-with-mouse + #'pages-directory-goto "26.1") ;;; The `pages-directory-for-addresses' function and ancillary code diff --git a/lisp/textmodes/page.el b/lisp/textmodes/page.el index 936896c3bd8..fa2a7d1c9ad 100644 --- a/lisp/textmodes/page.el +++ b/lisp/textmodes/page.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/textmodes/paragraphs.el b/lisp/textmodes/paragraphs.el index f0671f489f8..645d3ff1a2a 100644 --- a/lisp/textmodes/paragraphs.el +++ b/lisp/textmodes/paragraphs.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/textmodes/picture.el b/lisp/textmodes/picture.el index 09d0a2f0a9a..60e9bbb5f5c 100644 --- a/lisp/textmodes/picture.el +++ b/lisp/textmodes/picture.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -66,7 +66,7 @@ (defvar picture-desired-column 0 "Desired current column for Picture mode. When a cursor is on a wide-column character (e.g. Chinese, -Japanese, Korean), this may may be different from `current-column'.") +Japanese, Korean), this may be different from `current-column'.") (defun picture-update-desired-column (adjust-to-current) diff --git a/lisp/textmodes/po.el b/lisp/textmodes/po.el index 822596c57c2..648eef56cf6 100644 --- a/lisp/textmodes/po.el +++ b/lisp/textmodes/po.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/textmodes/refbib.el b/lisp/textmodes/refbib.el index 6b721260813..62c299b86d7 100644 --- a/lisp/textmodes/refbib.el +++ b/lisp/textmodes/refbib.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/textmodes/refer.el b/lisp/textmodes/refer.el index 74dec30473c..ee182211486 100644 --- a/lisp/textmodes/refer.el +++ b/lisp/textmodes/refer.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/textmodes/refill.el b/lisp/textmodes/refill.el index f65c9ade673..e005b5806f9 100644 --- a/lisp/textmodes/refill.el +++ b/lisp/textmodes/refill.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/textmodes/reftex-auc.el b/lisp/textmodes/reftex-auc.el index 1e0a5640483..ac57ce735a0 100644 --- a/lisp/textmodes/reftex-auc.el +++ b/lisp/textmodes/reftex-auc.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/textmodes/reftex-cite.el b/lisp/textmodes/reftex-cite.el index 7f1887cbf45..9ff2d0a1769 100644 --- a/lisp/textmodes/reftex-cite.el +++ b/lisp/textmodes/reftex-cite.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/textmodes/reftex-dcr.el b/lisp/textmodes/reftex-dcr.el index 16bc621f889..894f08b15d0 100644 --- a/lisp/textmodes/reftex-dcr.el +++ b/lisp/textmodes/reftex-dcr.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/textmodes/reftex-global.el b/lisp/textmodes/reftex-global.el index 91d2b485626..d07a52816e3 100644 --- a/lisp/textmodes/reftex-global.el +++ b/lisp/textmodes/reftex-global.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/textmodes/reftex-index.el b/lisp/textmodes/reftex-index.el index 6544029ef0c..811d1477ada 100644 --- a/lisp/textmodes/reftex-index.el +++ b/lisp/textmodes/reftex-index.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -474,7 +474,7 @@ With prefix 3, restrict index to region." (interactive) - ;; Ensure access to scanning info and rescan buffer if prefix are is '(4). + ;; Ensure access to scanning info and rescan buffer if prefix arg is '(4). (let ((current-prefix-arg current-prefix-arg)) (reftex-ensure-index-support t) (reftex-access-scan-info current-prefix-arg)) diff --git a/lisp/textmodes/reftex-parse.el b/lisp/textmodes/reftex-parse.el index af2810d72e8..67a3dd26b76 100644 --- a/lisp/textmodes/reftex-parse.el +++ b/lisp/textmodes/reftex-parse.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/textmodes/reftex-ref.el b/lisp/textmodes/reftex-ref.el index dd183548d0f..8d69d8feda5 100644 --- a/lisp/textmodes/reftex-ref.el +++ b/lisp/textmodes/reftex-ref.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -134,7 +134,7 @@ This function is controlled by the settings of reftex-insert-label-flags." (interactive) - ;; Ensure access to scanning info and rescan buffer if prefix are is '(4). + ;; Ensure access to scanning info and rescan buffer if prefix arg is '(4). (reftex-access-scan-info current-prefix-arg) ;; Find out what kind of environment this is and abort if necessary. @@ -314,7 +314,7 @@ also applies `reftex-translate-to-ascii-function' to the string." (save-match-data (cond ((equal letter "f") - (file-name-base)) + (file-name-base (buffer-file-name))) ((equal letter "F") (let ((masterdir (file-name-directory (reftex-TeX-master-file))) (file (file-name-sans-extension (buffer-file-name)))) diff --git a/lisp/textmodes/reftex-sel.el b/lisp/textmodes/reftex-sel.el index a4533adec08..65720f4ecdb 100644 --- a/lisp/textmodes/reftex-sel.el +++ b/lisp/textmodes/reftex-sel.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/textmodes/reftex-toc.el b/lisp/textmodes/reftex-toc.el index 4f7c738a134..c694fafcd52 100644 --- a/lisp/textmodes/reftex-toc.el +++ b/lisp/textmodes/reftex-toc.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -216,7 +216,7 @@ When called with a raw C-u prefix, rescan the document first." (or reftex-support-index (setq reftex-toc-include-index-entries nil)) - ;; Ensure access to scanning info and rescan buffer if prefix are is '(4) + ;; Ensure access to scanning info and rescan buffer if prefix arg is '(4) (reftex-access-scan-info current-prefix-arg) (let* ((this-buf (current-buffer)) diff --git a/lisp/textmodes/reftex-vars.el b/lisp/textmodes/reftex-vars.el index 03da584e96f..528232b5254 100644 --- a/lisp/textmodes/reftex-vars.el +++ b/lisp/textmodes/reftex-vars.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -264,7 +264,7 @@ distribution. Mixed-case symbols are convenience aliases.") "LaTeX label and citation support." :tag "RefTeX" :link '(url-link :tag "Home Page" - "http://www.gnu.org/software/auctex/reftex.html") + "https://www.gnu.org/software/auctex/reftex.html") :link '(emacs-commentary-link :tag "Commentary in reftex.el" "reftex.el") :link '(custom-manual "(reftex)Top") :prefix "reftex-" diff --git a/lisp/textmodes/reftex.el b/lisp/textmodes/reftex.el index 9754d2b20ff..d9393ff25f7 100644 --- a/lisp/textmodes/reftex.el +++ b/lisp/textmodes/reftex.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -36,13 +36,13 @@ ;; ;; The documentation in various formats is also available at ;; -;; http://www.gnu.org/software/auctex/manual/reftex.index.html +;; https://www.gnu.org/software/auctex/manual/reftex.index.html ;; ;; RefTeX is bundled with Emacs and available as a plug-in package for ;; XEmacs 21.x. If you need to install it yourself, you can find a ;; distribution at ;; -;; http://www.gnu.org/software/auctex/reftex.html +;; https://www.gnu.org/software/auctex/reftex.html ;; ;; RefTeX was written by Carsten Dominik <dominik@science.uva.nl> with ;; contributions from Stephen Eglen. It is currently maintained by @@ -1496,7 +1496,8 @@ When DIE is non-nil, throw an error if file not found." (and n (setq conf-variable (nth n conf-variable))) (or (eq conf-variable t) (and (stringp conf-variable) - (string-match (concat "[" conf-variable "]") typekey)))) + (let ((case-fold-search nil)) + (string-match (concat "[" conf-variable "]") typekey))))) (defun reftex-check-recursive-edit () ;; Check if we are already in a recursive edit. Abort with helpful @@ -2368,7 +2369,7 @@ information about your RefTeX version and configuration." what in fact did happen. Check if the bug is reproducible with an up-to-date version of -RefTeX available from http://www.gnu.org/software/auctex/. +RefTeX available from https://www.gnu.org/software/auctex/. If the bug is triggered by a specific \(La)TeX file, you should try to produce a minimal sample file showing the problem and include it diff --git a/lisp/textmodes/remember.el b/lisp/textmodes/remember.el index 388e49cfdc2..7300af06f49 100644 --- a/lisp/textmodes/remember.el +++ b/lisp/textmodes/remember.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -349,7 +349,7 @@ In which case `remember-mailbox' should be the name of the mailbox. Each piece of pseudo-mail created will have an `X-Todo-Priority' field, for the purpose of appropriate splitting." (let ((who (read-string "Who is this item related to? ")) - (moment (format "%.0f" (float-time))) + (moment (format-time-string "%s")) (desc (remember-buffer-desc)) (text (buffer-string))) (with-temp-buffer @@ -402,11 +402,19 @@ exists) might be changed." :type 'string :group 'remember) +(defcustom remember-time-format "%a %b %d %H:%M:%S %Y" + "The format for time stamp, passed to `format-time-string'. +The default emulates `current-time-string' for backward compatibility." + :type 'string + :group 'remember + :version "27.1") + (defun remember-append-to-file () "Remember, with description DESC, the given TEXT." (let* ((text (buffer-string)) (desc (remember-buffer-desc)) - (remember-text (concat "\n" remember-leader-text (current-time-string) + (remember-text (concat "\n" remember-leader-text + (format-time-string remember-time-format) " (" desc ")\n\n" text (save-excursion (goto-char (point-max)) (if (bolp) nil "\n")))) diff --git a/lisp/textmodes/rst.el b/lisp/textmodes/rst.el index 490ea231096..393b679e4a1 100644 --- a/lisp/textmodes/rst.el +++ b/lisp/textmodes/rst.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -109,46 +109,9 @@ (def-edebug-spec push (&or [form symbolp] [form gv-place])) -;; Correct wrong declaration. This still doesn't support dotted destructuring -;; though. -(def-edebug-spec cl-lambda-list - (([&rest cl-macro-arg] - [&optional ["&optional" cl-&optional-arg &rest cl-&optional-arg]] - [&optional ["&rest" arg]] - [&optional ["&key" [cl-&key-arg &rest cl-&key-arg] - &optional "&allow-other-keys"]] - [&optional ["&aux" &rest - &or (symbolp &optional def-form) symbolp]] - ))) - -;; Add missing declaration. -(def-edebug-spec cl-type-spec sexp) ;; This is not exactly correct but good - ;; enough. - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Support for `testcover' -(when (and (boundp 'testcover-1value-functions) - (boundp 'testcover-compose-functions)) - ;; Below `lambda' is used in a loop with varying parameters and is thus not - ;; 1valued. - (setq testcover-1value-functions - (delq 'lambda testcover-1value-functions)) - (add-to-list 'testcover-compose-functions 'lambda)) - -(defun rst-testcover-defcustom () - "Remove all customized variables from `testcover-module-constants'. -This seems to be a bug in `testcover': `defcustom' variables are -considered constants. Revert it with this function after each `defcustom'." - (when (boundp 'testcover-module-constants) - (setq testcover-module-constants - (delq nil - (mapcar - #'(lambda (sym) - (if (not (plist-member (symbol-plist sym) 'standard-value)) - sym)) - testcover-module-constants))))) - (defun rst-testcover-add-compose (fun) "Add FUN to `testcover-compose-functions'." (when (boundp 'testcover-compose-functions) @@ -1360,7 +1323,6 @@ This inherits from Text mode.") The hook for `text-mode' is run before this one." :group 'rst :type '(hook)) -(rst-testcover-defcustom) ;; Pull in variable definitions silencing byte-compiler. (require 'newcomment) @@ -1557,7 +1519,6 @@ file." (const :tag "Underline only" simple)) (integer :tag "Indentation for overline and underline type" :value 0)))) -(rst-testcover-defcustom) ;; FIXME: Rename this to `rst-over-and-under-default-indent' and set default to ;; 0 because the effect of 1 is probably surprising in the few cases @@ -1574,7 +1535,6 @@ found in the buffer are to be used but the indentation for over-and-under adornments is inconsistent across the buffer." :group 'rst-adjust :type '(integer)) -(rst-testcover-defcustom) (defun rst-new-preferred-hdr (seen prev) ;; testcover: ok. @@ -2013,7 +1973,6 @@ b. a negative numerical argument, which generally inverts the :group 'rst-adjust :type '(hook) :package-version '(rst . "1.1.0")) -(rst-testcover-defcustom) (defcustom rst-new-adornment-down nil "Controls level of new adornment for section headers." @@ -2022,7 +1981,6 @@ b. a negative numerical argument, which generally inverts the (const :tag "Same level as previous one" nil) (const :tag "One level down relative to the previous one" t)) :package-version '(rst . "1.1.0")) -(rst-testcover-defcustom) (defun rst-adjust-adornment (pfxarg) "Call `rst-adjust-section' interactively. @@ -2445,7 +2403,6 @@ also arranged by `rst-insert-list-new-tag'." :tag (char-to-string char) char)) rst-bullets))) :package-version '(rst . "1.1.0")) -(rst-testcover-defcustom) (defun rst-insert-list-continue (ind tag tab prefer-roman) ;; testcover: ok. @@ -2682,7 +2639,6 @@ section headers at all." Also used for formatting insertion, when numbering is disabled." :type 'integer :group 'rst-toc) -(rst-testcover-defcustom) (defcustom rst-toc-insert-style 'fixed "Insertion style for table-of-contents. @@ -2697,19 +2653,16 @@ indentation style: (const aligned) (const listed)) :group 'rst-toc) -(rst-testcover-defcustom) (defcustom rst-toc-insert-number-separator " " "Separator that goes between the TOC number and the title." :type 'string :group 'rst-toc) -(rst-testcover-defcustom) (defcustom rst-toc-insert-max-level nil "If non-nil, maximum depth of the inserted TOC." :type '(choice (const nil) integer) :group 'rst-toc) -(rst-testcover-defcustom) (defconst rst-toc-link-keymap (let ((map (make-sparse-keymap))) @@ -3174,35 +3127,30 @@ These indentation widths can be customized here." "Indentation when there is no more indentation point given." :group 'rst-indent :type '(integer)) -(rst-testcover-defcustom) (defcustom rst-indent-field 3 "Indentation for first line after a field or 0 to always indent for content." :group 'rst-indent :package-version '(rst . "1.1.0") :type '(integer)) -(rst-testcover-defcustom) (defcustom rst-indent-literal-normal 3 "Default indentation for literal block after a markup on an own line." :group 'rst-indent :package-version '(rst . "1.1.0") :type '(integer)) -(rst-testcover-defcustom) (defcustom rst-indent-literal-minimized 2 "Default indentation for literal block after a minimized markup." :group 'rst-indent :package-version '(rst . "1.1.0") :type '(integer)) -(rst-testcover-defcustom) (defcustom rst-indent-comment 3 "Default indentation for first line of a comment." :group 'rst-indent :package-version '(rst . "1.1.0") :type '(integer)) -(rst-testcover-defcustom) ;; FIXME: Must consider other tabs: ;; * Line blocks @@ -3486,7 +3434,7 @@ applied to each line like this COUNT is 0 before the first paragraph and increments for every paragraph found on level IND. IN-FIRST is non-nil if this is the first line of such a paragraph. IN-SUB is non-nil if this line -is part of a sub-block while IN-SUPER is non-nil of this line is +is part of a sub-block while IN-SUPER is non-nil if this line is part of a less indented block (super-block). IN-EMPTY is non-nil if this line is empty where an empty line is considered being part of the previous block. RELIND is nil for an empty line, 0 @@ -3652,7 +3600,6 @@ Region is from BEG to END. With WITH-EMPTY prefix empty lines too." :version "24.1" :group 'rst-faces :type '(face)) -(rst-testcover-defcustom) (make-obsolete-variable 'rst-block-face "customize the face `rst-block' instead." "24.1") @@ -3667,7 +3614,6 @@ Region is from BEG to END. With WITH-EMPTY prefix empty lines too." :version "24.1" :group 'rst-faces :type '(face)) -(rst-testcover-defcustom) (make-obsolete-variable 'rst-external-face "customize the face `rst-external' instead." "24.1") @@ -3682,7 +3628,6 @@ Region is from BEG to END. With WITH-EMPTY prefix empty lines too." :version "24.1" :group 'rst-faces :type '(face)) -(rst-testcover-defcustom) (make-obsolete-variable 'rst-definition-face "customize the face `rst-definition' instead." "24.1") @@ -3699,7 +3644,6 @@ Region is from BEG to END. With WITH-EMPTY prefix empty lines too." "Directives and roles." :group 'rst-faces :type '(face)) -(rst-testcover-defcustom) (make-obsolete-variable 'rst-directive-face "customize the face `rst-directive' instead." "24.1") @@ -3714,7 +3658,6 @@ Region is from BEG to END. With WITH-EMPTY prefix empty lines too." :version "24.1" :group 'rst-faces :type '(face)) -(rst-testcover-defcustom) (make-obsolete-variable 'rst-comment-face "customize the face `rst-comment' instead." "24.1") @@ -3729,7 +3672,6 @@ Region is from BEG to END. With WITH-EMPTY prefix empty lines too." :version "24.1" :group 'rst-faces :type '(face)) -(rst-testcover-defcustom) (make-obsolete-variable 'rst-emphasis1-face "customize the face `rst-emphasis1' instead." "24.1") @@ -3743,7 +3685,6 @@ Region is from BEG to END. With WITH-EMPTY prefix empty lines too." "Double emphasis." :group 'rst-faces :type '(face)) -(rst-testcover-defcustom) (make-obsolete-variable 'rst-emphasis2-face "customize the face `rst-emphasis2' instead." "24.1") @@ -3758,7 +3699,6 @@ Region is from BEG to END. With WITH-EMPTY prefix empty lines too." :version "24.1" :group 'rst-faces :type '(face)) -(rst-testcover-defcustom) (make-obsolete-variable 'rst-literal-face "customize the face `rst-literal' instead." "24.1") @@ -3773,7 +3713,6 @@ Region is from BEG to END. With WITH-EMPTY prefix empty lines too." :version "24.1" :group 'rst-faces :type '(face)) -(rst-testcover-defcustom) (make-obsolete-variable 'rst-reference-face "customize the face `rst-reference' instead." "24.1") @@ -3856,7 +3795,6 @@ of your own." (const :tag "transitions" t) (const :tag "section title adornment" nil)) :value-type (face))) -(rst-testcover-defcustom) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -4353,7 +4291,6 @@ string)) to be used for converting the document." (string :tag "Options")))) :group 'rst-compile :package-version "1.2.0") -(rst-testcover-defcustom) ;; FIXME: Must be defcustom. (defvar rst-compile-primary-toolset 'html diff --git a/lisp/textmodes/sgml-mode.el b/lisp/textmodes/sgml-mode.el index 1f99786ae7c..7ae3036f8cf 100644 --- a/lisp/textmodes/sgml-mode.el +++ b/lisp/textmodes/sgml-mode.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -2242,8 +2242,8 @@ The result is cached in `html--buffer-classes-cache'." (classes (seq-mapcat (lambda (el) - (when-let (class-list - (cdr (assq 'class (dom-attributes el)))) + (when-let* ((class-list + (cdr (assq 'class (dom-attributes el))))) (split-string class-list))) (dom-by-class dom "")))) (setq-local html--buffer-classes-cache (cons tick classes)) @@ -2260,8 +2260,8 @@ The result is cached in `html--buffer-ids-cache'." (ids (seq-mapcat (lambda (el) - (when-let (id-list - (cdr (assq 'id (dom-attributes el)))) + (when-let* ((id-list + (cdr (assq 'id (dom-attributes el))))) (split-string id-list))) (dom-by-id dom "")))) (setq-local html--buffer-ids-cache (cons tick ids)) diff --git a/lisp/textmodes/table.el b/lisp/textmodes/table.el index 5e967b535c4..d408d206be4 100644 --- a/lisp/textmodes/table.el +++ b/lisp/textmodes/table.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/textmodes/tex-mode.el b/lisp/textmodes/tex-mode.el index ba6d696de90..5c585ea46ca 100644 --- a/lisp/textmodes/tex-mode.el +++ b/lisp/textmodes/tex-mode.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -55,6 +55,11 @@ :prefix "tex-" :group 'tex) +(defgroup tex-flymake nil + "Flymake backend for linting TeX files." + :prefix "tex-" + :group 'tex) + ;;;###autoload (defcustom tex-shell-file-name nil "If non-nil, the shell file name to run in the subshell used to run TeX." @@ -259,6 +264,17 @@ measured relative to that of the normal text." (float :tag "Superscript")) :version "23.1") +(defcustom tex-chktex-program "chktex" + "ChkTeX executable to use for linting TeX files." + :type 'string + :link '(url-link "man:chktex(1)") + :group 'tex-flymake) + +(defcustom tex-chktex-extra-flags nil + "Extra command line flags for `tex-chktex-program'." + :type '(repeat string) + :group 'tex-flymake) + (defvar tex-last-temp-file nil "Latest temporary file generated by \\[tex-region] and \\[tex-buffer]. Deleted when the \\[tex-region] or \\[tex-buffer] is next run, or when the @@ -1154,6 +1170,7 @@ subshell is initiated, `tex-shell-hook' is run." (setq-local fill-indent-according-to-mode t) (add-hook 'completion-at-point-functions #'latex-complete-data nil 'local) + (add-hook 'flymake-diagnostic-functions 'tex-chktex nil t) (setq-local outline-regexp latex-outline-regexp) (setq-local outline-level #'latex-outline-level) (setq-local forward-sexp-function #'latex-forward-sexp) @@ -1775,7 +1792,7 @@ Mark is left at original location." ;; Note this does not handle things like mismatched brackets inside ;; begin/end blocks. ;; Needs to handle escaped parens for tex-validate-*. -;; http://lists.gnu.org/archive/html/bug-gnu-emacs/2007-09/msg00038.html +;; https://lists.gnu.org/r/bug-gnu-emacs/2007-09/msg00038.html ;; Does not handle escaped parens when latex-handle-escaped-parens is nil. (defun latex-forward-sexp-1 () "Like (forward-sexp 1) but aware of multi-char elements and escaped parens." @@ -3465,6 +3482,53 @@ There might be text before point." ;; Don't compose inside verbatim blocks. (eq 2 (nth 7 (syntax-ppss)))))))) + +;;; Flymake support + +(defvar-local tex-chktex--process nil) + +(defun tex-chktex-command () + "Return a list of command arguments for invoking ChkTeX." + `(,tex-chktex-program ,@tex-chktex-extra-flags + "--quiet" "--verbosity=0" "--inputfiles")) + +(defun tex-chktex (report-fn &rest _args) + "Flymake backend for linting TeX buffers with ChkTeX." + (unless (executable-find tex-chktex-program) + (error "Cannot find a suitable TeX checker")) + (when (process-live-p tex-chktex--process) + (kill-process tex-chktex--process)) + (let ((source (current-buffer)) + (re "^stdin:\\([0-9]+\\):\\([0-9]+\\):\\([0-9]+\\):\\(.*\\)$")) + (save-restriction + (widen) + (setq tex-chktex--process + (make-process + :name "tex-chktex" + :buffer (generate-new-buffer "*tex-chktex*") + :command (tex-chktex-command) + :noquery t :connection-type 'pipe + :sentinel + (lambda (process _event) + (when (eq (process-status process) 'exit) + (unwind-protect + (when (eq process + (with-current-buffer source tex-chktex--process)) + (with-current-buffer (process-buffer process) + (goto-char (point-min)) + (cl-loop + while (search-forward-regexp re nil t) + for msg = (match-string 4) + for line = (string-to-number (match-string 1)) + for col = (string-to-number (match-string 2)) + for (beg . end) = (flymake-diag-region source line col) + collect (flymake-make-diagnostic source beg end :warning msg) + into diags + finally (funcall report-fn diags)))) + (kill-buffer (process-buffer process))))))) + (process-send-region tex-chktex--process (point-min) (point-max)) + (process-send-eof tex-chktex--process)))) + (run-hooks 'tex-mode-load-hook) (provide 'tex-mode) diff --git a/lisp/textmodes/texinfmt.el b/lisp/textmodes/texinfmt.el index 1d2a9e52ab1..51f0659bf31 100644 --- a/lisp/textmodes/texinfmt.el +++ b/lisp/textmodes/texinfmt.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -2022,7 +2022,7 @@ commands that are defined in texinfo.tex for printed output. (push (- end-of-template start-of-template) texinfo-multitable-width-list) ;; Remove carriage return from within a template, if any. - ;; This helps those those who want to use more than + ;; This helps those who want to use more than ;; one line's worth of words in @multitable line. (narrow-to-region start-of-template end-of-template) (goto-char (point-min)) diff --git a/lisp/textmodes/texinfo.el b/lisp/textmodes/texinfo.el index f962dec9f09..be8bcc55fec 100644 --- a/lisp/textmodes/texinfo.el +++ b/lisp/textmodes/texinfo.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Todo: diff --git a/lisp/textmodes/texnfo-upd.el b/lisp/textmodes/texnfo-upd.el index aa5346d01fd..46977e1411f 100644 --- a/lisp/textmodes/texnfo-upd.el +++ b/lisp/textmodes/texnfo-upd.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/textmodes/text-mode.el b/lisp/textmodes/text-mode.el index 7d63556dcc2..1661ebe8c84 100644 --- a/lisp/textmodes/text-mode.el +++ b/lisp/textmodes/text-mode.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/textmodes/tildify.el b/lisp/textmodes/tildify.el index e4920b70c1c..0d7b15dfc6b 100644 --- a/lisp/textmodes/tildify.el +++ b/lisp/textmodes/tildify.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/textmodes/two-column.el b/lisp/textmodes/two-column.el index c6203fdf9eb..293a106515a 100644 --- a/lisp/textmodes/two-column.el +++ b/lisp/textmodes/two-column.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/textmodes/underline.el b/lisp/textmodes/underline.el index f018260b7ed..9f7a6eb47b5 100644 --- a/lisp/textmodes/underline.el +++ b/lisp/textmodes/underline.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/thingatpt.el b/lisp/thingatpt.el index 7c3d73e52b7..e3a4d4d7c1e 100644 --- a/lisp/thingatpt.el +++ b/lisp/thingatpt.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -42,6 +42,9 @@ ;; beginning-op Function to call to skip to the beginning of a "thing". ;; end-op Function to call to skip to the end of a "thing". ;; +;; For simple things, defined as sequences of specific kinds of characters, +;; use macro define-thing-chars. +;; ;; Reliance on existing operators means that many `things' can be accessed ;; without further code: eg. ;; (thing-at-point 'line) @@ -237,21 +240,28 @@ The bounds of THING are determined by `bounds-of-thing-at-point'." (put 'defun 'end-op 'end-of-defun) (put 'defun 'forward-op 'end-of-defun) +;; Things defined by sets of characters + +(defmacro define-thing-chars (thing chars) + "Define THING as a sequence of CHARS. +E.g.: +\(define-thing-chars twitter-screen-name \"[:alnum:]_\")" + `(progn + (put ',thing 'end-op + (lambda () + (re-search-forward (concat "\\=[" ,chars "]*") nil t))) + (put ',thing 'beginning-op + (lambda () + (if (re-search-backward (concat "[^" ,chars "]") nil t) + (forward-char) + (goto-char (point-min))))))) + ;; Filenames (defvar thing-at-point-file-name-chars "-~/[:alnum:]_.${}#%,:" "Characters allowable in filenames.") -(put 'filename 'end-op - (lambda () - (re-search-forward (concat "\\=[" thing-at-point-file-name-chars "]*") - nil t))) -(put 'filename 'beginning-op - (lambda () - (if (re-search-backward (concat "[^" thing-at-point-file-name-chars "]") - nil t) - (forward-char) - (goto-char (point-min))))) +(define-thing-chars filename thing-at-point-file-name-chars) ;; URIs @@ -388,7 +398,7 @@ the bounds of a possible ill-formed URI (one lacking a scheme)." ;; Ensure PT is actually within BOUNDARY. Check the following ;; example with point on the beginning of the line: ;; - ;; 3,1406710489,http://gnu.org,0,"0" + ;; 3,1406710489,https://gnu.org,0,"0" (and (<= url-beg pt end) (cons url-beg end)))))) (put 'url 'thing-at-point 'thing-at-point-url-at-point) diff --git a/lisp/thumbs.el b/lisp/thumbs.el index 0665429246f..e8ef05242ef 100644 --- a/lisp/thumbs.el +++ b/lisp/thumbs.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -523,23 +523,16 @@ Open another window." (interactive "FRename to file or directory: ") (let ((files (or thumbs-marked-list (list (thumbs-current-image)))) failures) - (if (and (not (file-directory-p newfile)) - thumbs-marked-list) - (if (file-exists-p newfile) - (error "Renaming marked files to file name `%s'" newfile) - (make-directory newfile t))) + (when thumbs-marked-list + (make-directory newfile t) + (setq newfile (file-name-as-directory newfile))) (if (yes-or-no-p (format "Really rename %d files? " (length files))) (let ((thumbs-file-list (thumbs-file-alist)) (inhibit-read-only t)) (dolist (file files) (let (failure) (condition-case () - (if (file-directory-p newfile) - (rename-file file - (expand-file-name - (file-name-nondirectory file) - newfile)) - (rename-file file newfile)) + (rename-file file newfile) (file-error (setq failure t) (push file failures))) (unless failure diff --git a/lisp/time-stamp.el b/lisp/time-stamp.el index fa7621bcd46..959f0cad64f 100644 --- a/lisp/time-stamp.el +++ b/lisp/time-stamp.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/time.el b/lisp/time.el index 6a46ea68eab..6cd7320e72f 100644 --- a/lisp/time.el +++ b/lisp/time.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -160,24 +160,33 @@ LABEL is a string to display as the label of that TIMEZONE's time." :type '(repeat (list string string)) :version "23.1") -(defcustom display-time-world-list - ;; Determine if zoneinfo style timezones are supported by testing that - ;; America/New York and Europe/London return different timezones. - (let ((nyt (format-time-string "%z" nil "America/New_York")) - (gmt (format-time-string "%z" nil "Europe/London"))) - (if (string-equal nyt gmt) - legacy-style-world-list - zoneinfo-style-world-list)) +(defcustom display-time-world-list t "Alist of time zones and places for `display-time-world' to display. Each element has the form (TIMEZONE LABEL). TIMEZONE should be in a format supported by your system. See the documentation of `zoneinfo-style-world-list' and `legacy-style-world-list' for two widely used formats. LABEL is -a string to display as the label of that TIMEZONE's time." +a string to display as the label of that TIMEZONE's time. + +If the value is t instead of an alist, use the value of +`zoneinfo-style-world-list' if it works on this platform, and of +`legacy-style-world-list' otherwise." + :group 'display-time :type '(repeat (list string string)) :version "23.1") +(defun time--display-world-list () + (if (listp display-time-world-list) + display-time-world-list + ;; Determine if zoneinfo style timezones are supported by testing that + ;; America/New York and Europe/London return different timezones. + (let ((nyt (format-time-string "%z" nil "America/New_York")) + (gmt (format-time-string "%z" nil "Europe/London"))) + (if (string-equal nyt gmt) + legacy-style-world-list + zoneinfo-style-world-list)))) + (defcustom display-time-world-time-format "%A %d %B %R %Z" "Format of the time displayed, see `format-time-string'." :group 'display-time @@ -548,7 +557,7 @@ To turn off the world time display, go to that window and type `q'." (not (get-buffer display-time-world-buffer-name))) (run-at-time t display-time-world-timer-second 'display-time-world-timer)) (with-current-buffer (get-buffer-create display-time-world-buffer-name) - (display-time-world-display display-time-world-list) + (display-time-world-display (time--display-world-list)) (display-buffer display-time-world-buffer-name (cons nil '((window-height . fit-window-to-buffer)))) (display-time-world-mode))) @@ -556,7 +565,7 @@ To turn off the world time display, go to that window and type `q'." (defun display-time-world-timer () (if (get-buffer display-time-world-buffer-name) (with-current-buffer (get-buffer display-time-world-buffer-name) - (display-time-world-display display-time-world-list)) + (display-time-world-display (time--display-world-list))) ;; cancel timer (let ((list timer-list)) (while list @@ -574,7 +583,7 @@ For example, the Unix uptime command format is \"%D, %z%2h:%.2m\"." (let ((str (format-seconds (or format "%Y, %D, %H, %M, %z%S") (float-time - (time-subtract (current-time) before-init-time))))) + (time-subtract nil before-init-time))))) (if (called-interactively-p 'interactive) (message "%s" str) str))) diff --git a/lisp/timezone.el b/lisp/timezone.el index 023cc68c3cc..762147b08bb 100644 --- a/lisp/timezone.el +++ b/lisp/timezone.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/tmm.el b/lisp/tmm.el index 8755971d7ca..ca6a37d62b2 100644 --- a/lisp/tmm.el +++ b/lisp/tmm.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/tool-bar.el b/lisp/tool-bar.el index 2386fe6177a..ee01a6998b8 100644 --- a/lisp/tool-bar.el +++ b/lisp/tool-bar.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/tooltip.el b/lisp/tooltip.el index 367114b83f5..44b6938a6fd 100644 --- a/lisp/tooltip.el +++ b/lisp/tooltip.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -119,7 +119,8 @@ the value of `tooltip-y-offset' is ignored." (defcustom tooltip-frame-parameters '((name . "tooltip") (internal-border-width . 2) - (border-width . 1)) + (border-width . 1) + (no-special-glyphs . t)) "Frame parameters used for tooltips. If `left' or `top' parameters are included, they specify the absolute @@ -130,7 +131,8 @@ of the `tooltip' face are used instead." :type '(repeat (cons :format "%v" (symbol :tag "Parameter") (sexp :tag "Value"))) - :group 'tooltip) + :group 'tooltip + :version "26.1") (defface tooltip '((((class color)) @@ -153,6 +155,18 @@ This variable is obsolete; instead of setting it to t, disable (make-obsolete-variable 'tooltip-use-echo-area "disable Tooltip mode instead" "24.1" 'set) +(defcustom tooltip-resize-echo-area nil + "If non-nil, using the echo area for tooltips will resize the echo area. +By default, when the echo area is used for displaying tooltips, +the tooltip text is truncated if it exceeds a single screen line. +When this variable is non-nil, the text is not truncated; instead, +the echo area is resized as needed to accommodate the full text +of the tooltip. +This variable has effect only on GUI frames." + :type 'boolean + :group 'tooltip + :version "27.1") + ;;; Variables that are not customizable. @@ -345,7 +359,8 @@ It is also called if Tooltip mode is on, for text-only displays." (current-message)))) (setq tooltip-previous-message (current-message))) (setq tooltip-help-message help) - (let ((message-truncate-lines t) + (let ((message-truncate-lines + (or (not (display-graphic-p)) (not tooltip-resize-echo-area))) (message-log-max nil)) (message "%s" help))) ((stringp tooltip-previous-message) diff --git a/lisp/tree-widget.el b/lisp/tree-widget.el index 6baf4c47215..9599cc7f067 100644 --- a/lisp/tree-widget.el +++ b/lisp/tree-widget.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; @@ -504,7 +504,7 @@ Handle mouse button 1 click on buttons.") (define-widget 'tree-widget-end-guide 'item "End of a vertical guide line." - :tag " `" + :tag " \\=`" ;;:tag-glyph (tree-widget-find-image "end-guide") :format "%t" ) diff --git a/lisp/tutorial.el b/lisp/tutorial.el index 785dbdfd189..7823f76a793 100644 --- a/lisp/tutorial.el +++ b/lisp/tutorial.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/type-break.el b/lisp/type-break.el index 8cb81d496ee..35b0efe65b1 100644 --- a/lisp/type-break.el +++ b/lisp/type-break.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -376,7 +376,7 @@ problems." (if (and type-break-time-last-break (< (setq diff (type-break-time-difference type-break-time-last-break - (current-time))) + nil)) type-break-interval)) ;; Use the file's value. (progn @@ -563,7 +563,7 @@ as per the function `type-break-schedule'." (cond (good-interval (let ((break-secs (type-break-time-difference - start-time (current-time)))) + start-time nil))) (cond ((>= break-secs good-interval) (setq continue nil)) @@ -624,7 +624,7 @@ INTERVAL is the full length of an interval (defaults to TIME)." type-break-time-warning-intervals)) (or time - (setq time (type-break-time-difference (current-time) + (setq time (type-break-time-difference nil type-break-time-next-break))) (while (and type-break-current-time-warning-interval @@ -685,7 +685,7 @@ keystroke threshold has been exceeded." (and type-break-good-rest-interval (progn (and (> (type-break-time-difference - type-break-time-last-command (current-time)) + type-break-time-last-command nil) type-break-good-rest-interval) (progn (type-break-keystroke-reset) diff --git a/lisp/uniquify.el b/lisp/uniquify.el index c6a50edc2c7..aa1fcd99cb4 100644 --- a/lisp/uniquify.el +++ b/lisp/uniquify.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/url/ChangeLog.1 b/lisp/url/ChangeLog.1 index 75be6af62a3..5d6a68e563e 100644 --- a/lisp/url/ChangeLog.1 +++ b/lisp/url/ChangeLog.1 @@ -2403,7 +2403,7 @@ (file-symlink-p): Ditto. (url-insert-file-contents): If `visit' is non-nil then make sure we set buffer-file-name. After these changes you can visit - http://www.gnu.org/ directly from the minibuffer. + https://www.gnu.org/ directly from the minibuffer. (url-insert-file-contents): When inserting the file contents, use a save-excursion so that we behave just like the original. @@ -3084,4 +3084,4 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. diff --git a/lisp/url/url-about.el b/lisp/url/url-about.el index c98e076ffa4..f0860e570a2 100644 --- a/lisp/url/url-about.el +++ b/lisp/url/url-about.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/url/url-auth.el b/lisp/url/url-auth.el index 2885d4e12e2..65c718ea128 100644 --- a/lisp/url/url-auth.el +++ b/lisp/url/url-auth.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: diff --git a/lisp/url/url-cache.el b/lisp/url/url-cache.el index a7247dfe10a..963dfd531e2 100644 --- a/lisp/url/url-cache.el +++ b/lisp/url/url-cache.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: @@ -206,7 +206,7 @@ If `url-standalone-mode' is non-nil, cached items never expire." (time-add cache-time (seconds-to-time (or expire-time url-cache-expire-time))) - (current-time)))))) + nil))))) (defun url-cache-prune-cache (&optional directory) "Remove all expired files from the cache. diff --git a/lisp/url/url-cid.el b/lisp/url/url-cid.el index 218ec0d6544..ce160c66774 100644 --- a/lisp/url/url-cid.el +++ b/lisp/url/url-cid.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: diff --git a/lisp/url/url-cookie.el b/lisp/url/url-cookie.el index 4912db6c53b..8045050c61e 100644 --- a/lisp/url/url-cookie.el +++ b/lisp/url/url-cookie.el @@ -1,4 +1,4 @@ -;;; url-cookie.el --- URL cookie support +;;; url-cookie.el --- URL cookie support -*- lexical-binding:t -*- ;; Copyright (C) 1996-1999, 2004-2017 Free Software Foundation, Inc. @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -74,6 +74,55 @@ telling Microsoft that." ;; It's completely normal for the cookies file not to exist yet. (load (or fname url-cookie-file) t t)) +(defun url-cookie-parse-file-netscape (filename &optional long-session) + "Load cookies from FILENAME in Netscape/Mozilla format. +When LONG-SESSION is non-nil, session cookies (expiring at t=0 +i.e. 1970-1-1) are loaded as expiring one year from now instead." + (interactive "fLoad Netscape/Mozilla cookie file: ") + (let ((n 0)) + (with-temp-buffer + (insert-file-contents-literally filename) + (goto-char (point-min)) + (when (not (looking-at-p "# Netscape HTTP Cookie File\n")) + (error (format "File %s doesn't look like a netscape cookie file" filename))) + (while (not (eobp)) + (when (not (looking-at-p (rx bol (* space) "#"))) + (let* ((line (buffer-substring (point) (save-excursion (end-of-line) (point)))) + (fields (split-string line "\t"))) + (cond + ;;((>= 1 (length line) 0) + ;; (message "skipping empty line")) + ((= (length fields) 7) + (let ((dom (nth 0 fields)) + ;; (match (nth 1 fields)) + (path (nth 2 fields)) + (secure (string= (nth 3 fields) "TRUE")) + ;; session cookies (expire time = 0) are supposed + ;; to be removed when the browser is closed, but + ;; the main point of loading external cookie is to + ;; reuse a browser session, so to prevent the + ;; cookie from being detected as expired straight + ;; away, make it expire a year from now + (expires (format-time-string + "%d %b %Y %T [GMT]" + (seconds-to-time + (let ((s (string-to-number (nth 4 fields)))) + (if (and (= s 0) long-session) + (seconds-to-time (+ (* 365 24 60 60) (float-time))) + s))))) + (key (nth 5 fields)) + (val (nth 6 fields))) + (cl-incf n) + ;;(message "adding <%s>=<%s> exp=<%s> dom=<%s> path=<%s> sec=%S" key val expires dom path secure) + (url-cookie-store key val expires dom path secure) + )) + (t + (message "ignoring malformed cookie line <%s>" line))))) + (forward-line)) + (when (< 0 n) + (setq url-cookies-changed-since-last-save t)) + (message "added %d cookies from file %s" n filename)))) + (defun url-cookie-clean-up (&optional secure) (let ((var (if secure 'url-cookie-secure-storage 'url-cookie-storage)) new new-cookies) @@ -161,7 +210,7 @@ telling Microsoft that." (let ((exp (url-cookie-expires cookie))) (and (> (length exp) 0) (condition-case () - (> (float-time) (float-time (date-to-time exp))) + (time-less-p (date-to-time exp) nil) (error nil))))) (defun url-cookie-retrieve (host &optional localpart secure) @@ -227,25 +276,21 @@ telling Microsoft that." :group 'url-cookie) (defun url-cookie-host-can-set-p (host domain) - (let ((last nil) - (case-fold-search t)) - (cond - ((string= host domain) ; Apparently netscape lets you do this - t) - ((zerop (length domain)) - nil) - (t - ;; Remove the dot from wildcard domains before matching. - (when (eq ?. (aref domain 0)) - (setq domain (substring domain 1))) - (and (url-domsuf-cookie-allowed-p domain) - ;; Need to check and make sure the host is actually _in_ the - ;; domain it wants to set a cookie for though. - (string-match (concat (regexp-quote domain) "$") host)))))) + (cond + ((string= host domain) ; Apparently netscape lets you do this + t) + ((zerop (length domain)) + nil) + (t + ;; Remove the dot from wildcard domains before matching. + (when (eq ?. (aref domain 0)) + (setq domain (substring domain 1))) + (and (url-domsuf-cookie-allowed-p domain) + (string-suffix-p domain host 'ignore-case))))) (defun url-cookie-handle-set-cookie (str) (setq url-cookies-changed-since-last-save t) - (let* ((args (url-parse-args str t)) + (let* ((args (nreverse (url-parse-args str t))) (case-fold-search t) (secure (and (assoc-string "secure" args t) t)) (domain (or (cdr-safe (assoc-string "domain" args t)) @@ -253,44 +298,16 @@ telling Microsoft that." (current-url (url-view-url t)) (trusted url-cookie-trusted-urls) (untrusted url-cookie-untrusted-urls) - (expires (cdr-safe (assoc-string "expires" args t))) + (max-age (cdr-safe (assoc-string "max-age" args t))) (localpart (or (cdr-safe (assoc-string "path" args t)) (file-name-directory (url-filename url-current-object)))) - (rest nil)) - (dolist (this args) - (or (member (downcase (car this)) '("secure" "domain" "expires" "path")) - (setq rest (cons this rest)))) - - ;; Sometimes we get dates that the timezone package cannot handle very - ;; gracefully - take care of this here, instead of in url-cookie-expired-p - ;; to speed things up. - (and expires - (string-match - (concat "^[^,]+, +\\(..\\)-\\(...\\)-\\(..\\) +" - "\\(..:..:..\\) +\\[*\\([^]]+\\)\\]*$") - expires) - (setq expires (concat (match-string 1 expires) " " - (match-string 2 expires) " " - (match-string 3 expires) " " - (match-string 4 expires) " [" - (match-string 5 expires) "]"))) - - ;; This one is for older Emacs/XEmacs variants that don't - ;; understand this format without tenths of a second in it. - ;; Wednesday, 30-Dec-2037 16:00:00 GMT - ;; - vs - - ;; Wednesday, 30-Dec-2037 16:00:00.00 GMT - (and expires - (string-match - "\\([0-9]+\\)-\\([A-Za-z]+\\)-\\([0-9]+\\)[ \t]+\\([0-9]+:[0-9]+:[0-9]+\\)\\(\\.[0-9]+\\)*[ \t]+\\([-+a-zA-Z0-9]+\\)" - expires) - (setq expires (concat (match-string 1 expires) "-" ; day - (match-string 2 expires) "-" ; month - (match-string 3 expires) " " ; year - (match-string 4 expires) ".00 " ; hour:minutes:seconds - (match-string 6 expires)))) ":" ; timezone - + (expires nil)) + (if (and max-age (string-match "\\`-?[0-9]+\\'" max-age)) + (setq expires (format-time-string "%a %b %d %H:%M:%S %Y GMT" + (time-add nil (read max-age)) + t)) + (setq expires (cdr-safe (assoc-string "expires" args t)))) (while (consp trusted) (if (string-match (car trusted) current-url) (setq trusted (- (match-end 0) (match-beginning 0))) @@ -314,8 +331,9 @@ telling Microsoft that." (not trusted) (save-window-excursion (with-output-to-temp-buffer "*Cookie Warning*" - (dolist (x rest) - (princ (format "%s - %s" (car x) (cdr x))))) + (princ (format "%s=\"%s\"\n" (caar args) (cdar args))) + (dolist (x (cdr args)) + (princ (format " %s=\"%s\"\n" (car x) (cdr x))))) (prog1 (not (funcall url-confirmation-func (format "Allow %s to set these cookies? " @@ -326,8 +344,8 @@ telling Microsoft that." nil) ((url-cookie-host-can-set-p (url-host url-current-object) domain) ;; Cookie is accepted by the user, and passes our security checks. - (dolist (cur rest) - (url-cookie-store (car cur) (cdr cur) expires domain localpart secure))) + (url-cookie-store (caar args) (cdar args) + expires domain localpart secure)) (t (url-lazy-message "%s tried to set a cookie for domain %s - rejected." (url-host url-current-object) domain))))) @@ -380,8 +398,8 @@ instead delete all cookies that do not match REGEXP." "Display a buffer listing the current URL cookies, if there are any. Use \\<url-cookie-mode-map>\\[url-cookie-delete] to remove cookies." (interactive) - (when (and (null url-cookie-secure-storage) - (null url-cookie-storage)) + (unless (or url-cookie-secure-storage + url-cookie-storage) (error "No cookies are defined")) (pop-to-buffer "*url cookies*") @@ -442,20 +460,13 @@ Use \\<url-cookie-mode-map>\\[url-cookie-delete] to remove cookies." (forward-line 1) (point))))) -(defun url-cookie-quit () - "Kill the current buffer." - (interactive) - (kill-buffer (current-buffer))) - (defvar url-cookie-mode-map (let ((map (make-sparse-keymap))) - (suppress-keymap map) - (define-key map "q" 'url-cookie-quit) (define-key map [delete] 'url-cookie-delete) (define-key map [(control k)] 'url-cookie-delete) map)) -(define-derived-mode url-cookie-mode nil "URL Cookie" +(define-derived-mode url-cookie-mode special-mode "URL Cookie" "Mode for listing cookies. \\{url-cookie-mode-map}" diff --git a/lisp/url/url-dav.el b/lisp/url/url-dav.el index 59b836ca6d1..ba20d675466 100644 --- a/lisp/url/url-dav.el +++ b/lisp/url/url-dav.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; DAV is in RFC 2518. diff --git a/lisp/url/url-dired.el b/lisp/url/url-dired.el index f7ed13c45b4..53df2bf7bb4 100644 --- a/lisp/url/url-dired.el +++ b/lisp/url/url-dired.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: diff --git a/lisp/url/url-domsuf.el b/lisp/url/url-domsuf.el index b24f24531a6..e0ebba46fb9 100644 --- a/lisp/url/url-domsuf.el +++ b/lisp/url/url-domsuf.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/url/url-expand.el b/lisp/url/url-expand.el index 9ceaa025fb3..04f06c367ee 100644 --- a/lisp/url/url-expand.el +++ b/lisp/url/url-expand.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: @@ -60,7 +60,7 @@ "Convert URL to a fully specified URL, and canonicalize it. Second arg DEFAULT is a URL to start with if URL is relative. If DEFAULT is nil or missing, the current buffer's URL is used. -Path components that are `.' are removed, and +Path components that are `.' are removed, and path components followed by `..' are removed, along with the `..' itself." (if (and url (not (string-match "^#" url))) ;; Need to nuke newlines and spaces in the URL, or we open diff --git a/lisp/url/url-file.el b/lisp/url/url-file.el index 1dda1d3325d..0252896b748 100644 --- a/lisp/url/url-file.el +++ b/lisp/url/url-file.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/url/url-ftp.el b/lisp/url/url-ftp.el index da2fbde49c9..baae0a7ec47 100644 --- a/lisp/url/url-ftp.el +++ b/lisp/url/url-ftp.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/url/url-future.el b/lisp/url/url-future.el index 5394eb0e5ef..abf30041027 100644 --- a/lisp/url/url-future.el +++ b/lisp/url/url-future.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/url/url-gw.el b/lisp/url/url-gw.el index 28acde64203..c1c08259e38 100644 --- a/lisp/url/url-gw.el +++ b/lisp/url/url-gw.el @@ -19,11 +19,12 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: (require 'url-vars) +(require 'url-parse) ;; Fixme: support SSH explicitly or via a url-gateway-rlogin-program? @@ -245,8 +246,9 @@ overriding the value of `url-gateway-method'." name buffer host service :type gw-method ;; Use non-blocking socket if we can. - :nowait (featurep 'make-network-process - '(:nowait t)))) + :nowait (and (featurep 'make-network-process) + (url-asynchronous url-current-object) + '(:nowait t)))) (`socks (socks-open-network-stream name buffer host service)) (`telnet diff --git a/lisp/url/url-handlers.el b/lisp/url/url-handlers.el index ba3062308ec..55a478ad034 100644 --- a/lisp/url/url-handlers.el +++ b/lisp/url/url-handlers.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -341,7 +341,7 @@ if it had been inserted from a file named URL." (unless buffer (signal 'file-error (list url "No Data"))) (with-current-buffer buffer ;; XXX: This is HTTP/S specific and should be moved to url-http - ;; instead. See http://debbugs.gnu.org/17549. + ;; instead. See https://debbugs.gnu.org/17549. (when (bound-and-true-p url-http-response-status) ;; Don't signal an error if VISIT is non-nil, because ;; 'insert-file-contents' doesn't. This is required to @@ -354,7 +354,7 @@ if it had been inserted from a file named URL." (< url-http-response-status 300))) (let ((desc (nth 2 (assq url-http-response-status url-http-codes)))) (kill-buffer buffer) - ;; Signal file-error per http://debbugs.gnu.org/16733. + ;; Signal file-error per https://debbugs.gnu.org/16733. (signal 'file-error (list url desc)))))) (url-insert-buffer-contents buffer url visit beg end replace))) diff --git a/lisp/url/url-history.el b/lisp/url/url-history.el index 1fa085400d8..fc1b4991039 100644 --- a/lisp/url/url-history.el +++ b/lisp/url/url-history.el @@ -1,4 +1,4 @@ -;;; url-history.el --- Global history tracking for URL package +;;; url-history.el --- Global history tracking for URL package -*- lexical-binding:t -*- ;; Copyright (C) 1996-1999, 2004-2017 Free Software Foundation, Inc. @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -106,7 +106,7 @@ to run the `url-history-setup-save-timer' function manually." (defun url-history-update-url (url time) (setq url-history-changed-since-last-save t) - (puthash (if (vectorp url) (url-recreate-url url) url) time + (puthash (if (url-p url) (url-recreate-url url) url) time url-history-hash-table)) (autoload 'url-make-private-file "url-util") @@ -157,6 +157,7 @@ user for what type to save as." (gethash url url-history-hash-table nil)) (defun url-completion-function (string predicate function) + (declare (obsolete url-history-hash-table "26.1")) ;; Completion function to complete urls from the history. ;; This is obsolete since we can now pass the hash-table directly as a ;; completion table. @@ -164,7 +165,7 @@ user for what type to save as." (cond ((eq function nil) (let ((list nil)) - (maphash (lambda (key val) (push key list)) + (maphash (lambda (key _) (push key list)) url-history-hash-table) ;; Not sure why we bother reversing the list. --Stef (try-completion string (nreverse list) predicate))) @@ -172,7 +173,7 @@ user for what type to save as." (let ((stub (concat "\\`" (regexp-quote string))) (retval nil)) (maphash - (lambda (url time) + (lambda (url _) (if (string-match stub url) (push url retval))) url-history-hash-table) retval)) diff --git a/lisp/url/url-http.el b/lisp/url/url-http.el index 06d32861b2e..51f158e5c21 100644 --- a/lisp/url/url-http.el +++ b/lisp/url/url-http.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -1249,6 +1249,9 @@ The return value of this function is the retrieval buffer." (nsm-noninteractive (or url-request-noninteractive (and (boundp 'url-http-noninteractive) url-http-noninteractive))) + ;; The following binding is needed in url-open-stream, which + ;; is called from url-http-find-free-connection. + (url-current-object url) (connection (url-http-find-free-connection (url-host url) (url-port url) gateway-method)) diff --git a/lisp/url/url-imap.el b/lisp/url/url-imap.el index b8fe4ed5ff7..6384ba60fc3 100644 --- a/lisp/url/url-imap.el +++ b/lisp/url/url-imap.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/url/url-irc.el b/lisp/url/url-irc.el index 1d0a46ec2c2..5a268aa3329 100644 --- a/lisp/url/url-irc.el +++ b/lisp/url/url-irc.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/url/url-ldap.el b/lisp/url/url-ldap.el index d9a18e554fa..c23a55f353d 100644 --- a/lisp/url/url-ldap.el +++ b/lisp/url/url-ldap.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/url/url-mailto.el b/lisp/url/url-mailto.el index 8c49546aef6..c83a1d65738 100644 --- a/lisp/url/url-mailto.el +++ b/lisp/url/url-mailto.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -102,7 +102,7 @@ (goto-char (point-min)) (or (search-forward (concat "\n" mail-header-separator "\n") nil t) (goto-char (point-max))) - (insert (mapconcat + (insert (mapconcat #'(lambda (string) (replace-regexp-in-string "\r\n" "\n" string)) (cdar args) "\n"))) diff --git a/lisp/url/url-methods.el b/lisp/url/url-methods.el index 9f41f35cb84..00b2572421d 100644 --- a/lisp/url/url-methods.el +++ b/lisp/url/url-methods.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/url/url-misc.el b/lisp/url/url-misc.el index 3515febba20..a5422bbd604 100644 --- a/lisp/url/url-misc.el +++ b/lisp/url/url-misc.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: diff --git a/lisp/url/url-news.el b/lisp/url/url-news.el index 5130b0c93be..c6e056298f2 100644 --- a/lisp/url/url-news.el +++ b/lisp/url/url-news.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: diff --git a/lisp/url/url-nfs.el b/lisp/url/url-nfs.el index 898d304be64..d8e68fce035 100644 --- a/lisp/url/url-nfs.el +++ b/lisp/url/url-nfs.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/url/url-ns.el b/lisp/url/url-ns.el index ada716df60e..dcbdf6242bd 100644 --- a/lisp/url/url-ns.el +++ b/lisp/url/url-ns.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: diff --git a/lisp/url/url-parse.el b/lisp/url/url-parse.el index 4738163f0bc..4f6ab6bd955 100644 --- a/lisp/url/url-parse.el +++ b/lisp/url/url-parse.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -36,7 +36,8 @@ target attributes fullness)) (:copier nil)) type user password host portspec filename target attributes fullness - silent (use-cookies t)) + silent (use-cookies t) + (asynchronous t)) (defsubst url-port (urlobj) "Return the port number for the URL specified by URLOBJ. diff --git a/lisp/url/url-privacy.el b/lisp/url/url-privacy.el index 90dfb275132..ab9a6a6b35d 100644 --- a/lisp/url/url-privacy.el +++ b/lisp/url/url-privacy.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: diff --git a/lisp/url/url-proxy.el b/lisp/url/url-proxy.el index 790cb472b0b..706cb689e4b 100644 --- a/lisp/url/url-proxy.el +++ b/lisp/url/url-proxy.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: diff --git a/lisp/url/url-queue.el b/lisp/url/url-queue.el index dd1699bd082..84da6f60260 100644 --- a/lisp/url/url-queue.el +++ b/lisp/url/url-queue.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -177,7 +177,7 @@ The variable `url-queue-timeout' sets a timeout." (with-current-buffer (if (and (bufferp (url-queue-buffer job)) (buffer-live-p (url-queue-buffer job))) - ;; Use the (partially filled) process buffer it it exists. + ;; Use the (partially filled) process buffer if it exists. (url-queue-buffer job) ;; If not, just create a new buffer, which will probably be ;; killed again by the caller. diff --git a/lisp/url/url-tramp.el b/lisp/url/url-tramp.el index 50bfa7c499e..f4149ddb967 100644 --- a/lisp/url/url-tramp.el +++ b/lisp/url/url-tramp.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -37,33 +37,41 @@ They must also be covered by `url-handler-regexp'." :type '(repeat string)) (defun url-tramp-convert-url-to-tramp (url) - "Convert URL to a Tramp file name." - (let ((obj (url-generic-parse-url (and (stringp url) url)))) - (if (member (url-type obj) url-tramp-protocols) - (progn - (if (url-password obj) - (password-cache-add - (tramp-make-tramp-file-name - (url-type obj) (url-user obj) (url-host obj) "") - (url-password obj)) - (tramp-make-tramp-file-name - (url-type obj) (url-user obj) (url-host obj) (url-filename obj)))) - url))) + "Convert URL to a Tramp file name. +If URL contains a password, it will be added to the `password-data' cache. +In case URL is not convertible, nil is returned." + (let* ((obj (url-generic-parse-url (and (stringp url) url))) + (port + (and (natnump (url-portspec obj)) + (number-to-string (url-portspec obj))))) + (when (member (url-type obj) url-tramp-protocols) + (when (url-password obj) + (password-cache-add + (tramp-make-tramp-file-name + (url-type obj) (url-user obj) nil + (url-host obj) port "") + (url-password obj))) + (tramp-make-tramp-file-name + (url-type obj) (url-user obj) nil + (url-host obj) port (url-filename obj))))) (defun url-tramp-convert-tramp-to-url (file) - "Convert FILE, a Tramp file name, to a URL." - (let ((obj (ignore-errors (tramp-dissect-file-name file)))) - (if (member (tramp-file-name-method obj) url-tramp-protocols) - (url-recreate-url - (url-parse-make-urlobj - (tramp-file-name-method obj) - (tramp-file-name-user obj) - nil ; password. - (tramp-file-name-host obj) - nil ; port. - (tramp-file-name-localname obj) - nil nil t)) ; target attributes fullness. - file))) + "Convert FILE, a Tramp file name, to a URL. +In case FILE is not convertible, nil is returned." + (let* ((obj (ignore-errors (tramp-dissect-file-name file))) + (port + (and (stringp (tramp-file-name-port obj)) + (string-to-number (tramp-file-name-port obj))))) + (when (member (tramp-file-name-method obj) url-tramp-protocols) + (url-recreate-url + (url-parse-make-urlobj + (tramp-file-name-method obj) + (tramp-file-name-user obj) + nil ; password. + (tramp-file-name-host obj) + port + (tramp-file-name-localname obj) + nil nil t))))) ; target attributes fullness. ;;;###autoload (defun url-tramp-file-handler (operation &rest args) diff --git a/lisp/url/url-util.el b/lisp/url/url-util.el index 9897dea9c7f..1d9e386bbc3 100644 --- a/lisp/url/url-util.el +++ b/lisp/url/url-util.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -565,7 +565,7 @@ Has a preference for looking backward when not directly on a symbol." (skip-chars-forward url-get-url-filename-chars)) (setq start (point))) (setq url (buffer-substring-no-properties start (point)))) - (if (and url (string-match "^(.*)\\.?$" url)) + (if (and url (string-match "^(\\(.*\\))\\.?$" url)) (setq url (match-string 1 url))) (if (and url (string-match "^URL:" url)) (setq url (substring url 4 nil))) diff --git a/lisp/url/url-vars.el b/lisp/url/url-vars.el index f08779f6950..14c5652d6c3 100644 --- a/lisp/url/url-vars.el +++ b/lisp/url/url-vars.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Code: diff --git a/lisp/url/url.el b/lisp/url/url.el index be6377ceb3a..36cd81bd70b 100644 --- a/lisp/url/url.el +++ b/lisp/url/url.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -119,6 +119,8 @@ variable in the original buffer as a forwarding pointer.") (defvar url-retrieve-number-of-calls 0) (autoload 'url-cache-prune-cache "url-cache") +(defvar url-asynchronous t + "Bind to nil before calling `url-retrieve' to signal :nowait connections.") ;;;###autoload (defun url-retrieve (url callback &optional cbargs silent inhibit-cookies) @@ -190,6 +192,7 @@ URL-encoded before it's used." (unless (url-type url) (error "Bad url: %s" (url-recreate-url url))) (setf (url-silent url) silent) + (setf (url-asynchronous url) url-asynchronous) (setf (url-use-cookies url) (not inhibit-cookies)) ;; Once in a while, remove old entries from the URL cache. (when (zerop (% url-retrieve-number-of-calls 1000)) @@ -232,6 +235,7 @@ how long to wait for a response before giving up." (let ((retrieval-done nil) (start-time (current-time)) + (url-asynchronous nil) (asynch-buffer nil)) (setq asynch-buffer (url-retrieve url (lambda (&rest ignored) @@ -255,8 +259,7 @@ how long to wait for a response before giving up." ;; process output. (while (and (not retrieval-done) (or (not timeout) - (< (float-time (time-subtract - (current-time) start-time)) + (< (float-time (time-subtract nil start-time)) timeout))) (url-debug 'retrieval "Spinning in url-retrieve-synchronously: %S (%S)" diff --git a/lisp/userlock.el b/lisp/userlock.el index a8eb24bd100..1d45b3a4add 100644 --- a/lisp/userlock.el +++ b/lisp/userlock.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/vc/add-log.el b/lisp/vc/add-log.el index 91c69202dd5..392147b14d7 100644 --- a/lisp/vc/add-log.el +++ b/lisp/vc/add-log.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/vc/compare-w.el b/lisp/vc/compare-w.el index 11e84ae797f..f5571c6d115 100644 --- a/lisp/vc/compare-w.el +++ b/lisp/vc/compare-w.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/vc/cvs-status.el b/lisp/vc/cvs-status.el index c170809f057..770791a3c09 100644 --- a/lisp/vc/cvs-status.el +++ b/lisp/vc/cvs-status.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el index aa8d77882ec..df9627abdf0 100644 --- a/lisp/vc/diff-mode.el +++ b/lisp/vc/diff-mode.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -432,7 +432,7 @@ and the face `diff-added' for added lines.") "If non-nil, empty lines are valid in unified diffs. Some versions of diff replace all-blank context lines in unified format with empty lines. This makes the format less robust, but is tolerated. -See http://lists.gnu.org/archive/html/emacs-devel/2007-11/msg01990.html") +See https://lists.gnu.org/r/emacs-devel/2007-11/msg01990.html") (defconst diff-hunk-header-re (concat "^\\(?:" diff-hunk-header-re-unified ".*\\|\\*\\{15\\}.*\n\\*\\*\\* .+ \\*\\*\\*\\*\\|[0-9]+\\(,[0-9]+\\)?[acd][0-9]+\\(,[0-9]+\\)?\\)$")) @@ -875,51 +875,53 @@ PREFIX is only used internally: don't use it." (set (make-local-variable 'diff-remembered-defdir) default-directory) (set (make-local-variable 'diff-remembered-files-alist) nil)) (save-excursion - (unless (looking-at diff-file-header-re) - (or (ignore-errors (diff-beginning-of-file)) - (re-search-forward diff-file-header-re nil t))) - (let ((fs (diff-hunk-file-names old))) - (if prefix (setq fs (mapcar (lambda (f) (concat prefix f)) fs))) - (or - ;; use any previously used preference - (cdr (assoc fs diff-remembered-files-alist)) - ;; try to be clever and use previous choices as an inspiration - (cl-dolist (rf diff-remembered-files-alist) - (let ((newfile (diff-merge-strings (caar rf) (car fs) (cdr rf)))) - (if (and newfile (file-exists-p newfile)) (cl-return newfile)))) - ;; look for each file in turn. If none found, try again but - ;; ignoring the first level of directory, ... - (cl-do* ((files fs (delq nil (mapcar 'diff-filename-drop-dir files))) - (file nil nil)) - ((or (null files) - (setq file (cl-do* ((files files (cdr files)) - (file (car files) (car files))) - ;; Use file-regular-p to avoid - ;; /dev/null, directories, etc. - ((or (null file) (file-regular-p file)) - file)))) - file)) - ;; <foo>.rej patches implicitly apply to <foo> - (and (string-match "\\.rej\\'" (or buffer-file-name "")) - (let ((file (substring buffer-file-name 0 (match-beginning 0)))) - (when (file-exists-p file) file))) - ;; If we haven't found the file, maybe it's because we haven't paid - ;; attention to the PCL-CVS hint. - (and (not prefix) - (boundp 'cvs-pcl-cvs-dirchange-re) - (save-excursion - (re-search-backward cvs-pcl-cvs-dirchange-re nil t)) - (diff-find-file-name old noprompt (match-string 1))) - ;; if all else fails, ask the user - (unless noprompt - (let ((file (expand-file-name (or (car fs) "")))) - (setq file - (read-file-name (format "Use file %s: " file) - (file-name-directory file) file t - (file-name-nondirectory file))) - (set (make-local-variable 'diff-remembered-files-alist) - (cons (cons fs file) diff-remembered-files-alist)) - file)))))) + (save-restriction + (widen) + (unless (looking-at diff-file-header-re) + (or (ignore-errors (diff-beginning-of-file)) + (re-search-forward diff-file-header-re nil t))) + (let ((fs (diff-hunk-file-names old))) + (if prefix (setq fs (mapcar (lambda (f) (concat prefix f)) fs))) + (or + ;; use any previously used preference + (cdr (assoc fs diff-remembered-files-alist)) + ;; try to be clever and use previous choices as an inspiration + (cl-dolist (rf diff-remembered-files-alist) + (let ((newfile (diff-merge-strings (caar rf) (car fs) (cdr rf)))) + (if (and newfile (file-exists-p newfile)) (cl-return newfile)))) + ;; look for each file in turn. If none found, try again but + ;; ignoring the first level of directory, ... + (cl-do* ((files fs (delq nil (mapcar 'diff-filename-drop-dir files))) + (file nil nil)) + ((or (null files) + (setq file (cl-do* ((files files (cdr files)) + (file (car files) (car files))) + ;; Use file-regular-p to avoid + ;; /dev/null, directories, etc. + ((or (null file) (file-regular-p file)) + file)))) + file)) + ;; <foo>.rej patches implicitly apply to <foo> + (and (string-match "\\.rej\\'" (or buffer-file-name "")) + (let ((file (substring buffer-file-name 0 (match-beginning 0)))) + (when (file-exists-p file) file))) + ;; If we haven't found the file, maybe it's because we haven't paid + ;; attention to the PCL-CVS hint. + (and (not prefix) + (boundp 'cvs-pcl-cvs-dirchange-re) + (save-excursion + (re-search-backward cvs-pcl-cvs-dirchange-re nil t)) + (diff-find-file-name old noprompt (match-string 1))) + ;; if all else fails, ask the user + (unless noprompt + (let ((file (expand-file-name (or (car fs) "")))) + (setq file + (read-file-name (format "Use file %s: " file) + (file-name-directory file) file t + (file-name-nondirectory file))) + (set (make-local-variable 'diff-remembered-files-alist) + (cons (cons fs file) diff-remembered-files-alist)) + file))))))) (defun diff-ediff-patch () diff --git a/lisp/vc/diff.el b/lisp/vc/diff.el index 7fdd1037322..a267908cec9 100644 --- a/lisp/vc/diff.el +++ b/lisp/vc/diff.el @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/vc/ediff-diff.el b/lisp/vc/ediff-diff.el index b957bdce4f7..f36d0180044 100644 --- a/lisp/vc/ediff-diff.el +++ b/lisp/vc/ediff-diff.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/vc/ediff-help.el b/lisp/vc/ediff-help.el index 339d3a513b6..3df0dc72547 100644 --- a/lisp/vc/ediff-help.el +++ b/lisp/vc/ediff-help.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/vc/ediff-hook.el b/lisp/vc/ediff-hook.el index 37f8ef55a28..6a65f0d970d 100644 --- a/lisp/vc/ediff-hook.el +++ b/lisp/vc/ediff-hook.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/vc/ediff-init.el b/lisp/vc/ediff-init.el index 59d97c3cea5..21d040d1e54 100644 --- a/lisp/vc/ediff-init.el +++ b/lisp/vc/ediff-init.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/vc/ediff-merg.el b/lisp/vc/ediff-merg.el index d91d04467e3..39cf44d67d3 100644 --- a/lisp/vc/ediff-merg.el +++ b/lisp/vc/ediff-merg.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/vc/ediff-mult.el b/lisp/vc/ediff-mult.el index 5bf94a56351..4ed6661dee0 100644 --- a/lisp/vc/ediff-mult.el +++ b/lisp/vc/ediff-mult.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/vc/ediff-ptch.el b/lisp/vc/ediff-ptch.el index 6c8e925d2b9..d80db5c04ce 100644 --- a/lisp/vc/ediff-ptch.el +++ b/lisp/vc/ediff-ptch.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/vc/ediff-util.el b/lisp/vc/ediff-util.el index bcf446a64ce..3430d046c0c 100644 --- a/lisp/vc/ediff-util.el +++ b/lisp/vc/ediff-util.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/vc/ediff-vers.el b/lisp/vc/ediff-vers.el index efd8e93c4b7..134b41d41c2 100644 --- a/lisp/vc/ediff-vers.el +++ b/lisp/vc/ediff-vers.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/vc/ediff-wind.el b/lisp/vc/ediff-wind.el index 31dcf3b69f9..79ccc6d32db 100644 --- a/lisp/vc/ediff-wind.el +++ b/lisp/vc/ediff-wind.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -64,10 +64,10 @@ (defun ediff-choose-window-setup-function-automatically () (declare (obsolete ediff-setup-windows-default "24.3")) (if (ediff-window-display-p) - 'ediff-setup-windows-multiframe - 'ediff-setup-windows-plain)) + #'ediff-setup-windows-multiframe + #'ediff-setup-windows-plain)) -(defcustom ediff-window-setup-function 'ediff-setup-windows-default +(defcustom ediff-window-setup-function #'ediff-setup-windows-default "Function called to set up windows. Ediff provides a choice of three functions: (1) `ediff-setup-windows-multiframe', which sets the control panel @@ -132,7 +132,7 @@ provided functions are written." (Ancestor . ediff-window-Ancestor))) -(defcustom ediff-split-window-function 'split-window-vertically +(defcustom ediff-split-window-function #'split-window-vertically "The function used to split the main window between buffer-A and buffer-B. You can set it to a horizontal split instead of the default vertical split by setting this variable to `split-window-horizontally'. @@ -145,7 +145,7 @@ In this case, Ediff will use those frames to display these buffers." function) :group 'ediff-window) -(defcustom ediff-merge-split-window-function 'split-window-horizontally +(defcustom ediff-merge-split-window-function #'split-window-horizontally "The function used to split the main window between buffer-A and buffer-B. You can set it to a vertical split instead of the default horizontal split by setting this variable to `split-window-vertically'. @@ -181,6 +181,8 @@ In this case, Ediff will use those frames to display these buffers." '(visibility . nil) ;; make initial frame small to avoid distraction '(width . 1) '(height . 1) + ;; Fullscreen control frames don't make sense (Bug#29026). + '(fullscreen . nil) ;; this blocks queries from window manager as to where to put ;; ediff's control frame. we put the frame outside the display, ;; so the initial frame won't jump all over the screen @@ -210,7 +212,7 @@ responsibility." :type 'boolean :group 'ediff-window) -(defcustom ediff-control-frame-position-function 'ediff-make-frame-position +(defcustom ediff-control-frame-position-function #'ediff-make-frame-position "Function to call to determine the desired location for the control panel. Expects three parameters: the control buffer, the desired width and height of the control frame. It returns an association list @@ -258,7 +260,7 @@ customization of the default." display off.") (ediff-defvar-local ediff-wide-display-frame nil "Frame to be used for wide display.") -(ediff-defvar-local ediff-make-wide-display-function 'ediff-make-wide-display +(ediff-defvar-local ediff-make-wide-display-function #'ediff-make-wide-display "The value is a function that is called to create a wide display. The function is called without arguments. It should resize the frame in which buffers A, B, and C are to be displayed, and it should save the old @@ -334,11 +336,11 @@ into icons, regardless of the window manager." ;; in case user did a no-no on a tty (or (ediff-window-display-p) - (setq ediff-window-setup-function 'ediff-setup-windows-plain)) + (setq ediff-window-setup-function #'ediff-setup-windows-plain)) (or (ediff-keep-window-config control-buffer) (funcall - (ediff-with-current-buffer control-buffer ediff-window-setup-function) + (with-current-buffer control-buffer ediff-window-setup-function) buffer-A buffer-B buffer-C control-buffer)) (run-hooks 'ediff-after-setup-windows-hook)) @@ -352,7 +354,7 @@ into icons, regardless of the window manager." ;; Usually used without windowing systems ;; With windowing, we want to use dedicated frames. (defun ediff-setup-windows-plain (buffer-A buffer-B buffer-C control-buffer) - (ediff-with-current-buffer control-buffer + (with-current-buffer control-buffer (setq ediff-multiframe nil)) (if ediff-merge-job (ediff-setup-windows-plain-merge @@ -366,14 +368,14 @@ into icons, regardless of the window manager." ;; skip dedicated and unsplittable frames (ediff-destroy-control-frame control-buffer) (let ((window-min-height 1) - (with-Ancestor-p (ediff-with-current-buffer control-buffer + (with-Ancestor-p (with-current-buffer control-buffer ediff-merge-with-ancestor-job)) split-window-function merge-window-share merge-window-lines - (buf-Ancestor (ediff-with-current-buffer control-buffer + (buf-Ancestor (with-current-buffer control-buffer ediff-ancestor-buffer)) wind-A wind-B wind-C wind-Ancestor) - (ediff-with-current-buffer control-buffer + (with-current-buffer control-buffer (setq merge-window-share ediff-merge-window-share ;; this lets us have local versions of ediff-split-window-function split-window-function ediff-split-window-function)) @@ -417,7 +419,7 @@ into icons, regardless of the window manager." (switch-to-buffer buf-B) (setq wind-B (selected-window)) - (ediff-with-current-buffer control-buffer + (with-current-buffer control-buffer (setq ediff-window-A wind-A ediff-window-B wind-B ediff-window-C wind-C @@ -436,7 +438,7 @@ into icons, regardless of the window manager." split-window-function wind-width-or-height three-way-comparison wind-A-start wind-B-start wind-A wind-B wind-C) - (ediff-with-current-buffer control-buffer + (with-current-buffer control-buffer (setq wind-A-start (ediff-overlay-start (ediff-get-value-according-to-buffer-type 'A ediff-narrow-bounds)) @@ -462,7 +464,7 @@ into icons, regardless of the window manager." (setq wind-A (selected-window)) (if three-way-comparison (setq wind-width-or-height - (/ (if (eq split-window-function 'split-window-vertically) + (/ (if (eq split-window-function #'split-window-vertically) (window-height wind-A) (window-width wind-A)) 3))) @@ -487,7 +489,7 @@ into icons, regardless of the window manager." (switch-to-buffer buf-C) (setq wind-C (selected-window)))) - (ediff-with-current-buffer control-buffer + (with-current-buffer control-buffer (setq ediff-window-A wind-A ediff-window-B wind-B ediff-window-C wind-C)) @@ -506,23 +508,23 @@ into icons, regardless of the window manager." ;; dispatch an appropriate window setup function (defun ediff-setup-windows-multiframe (buf-A buf-B buf-C control-buf) - (ediff-with-current-buffer control-buf + (with-current-buffer control-buf (setq ediff-multiframe t)) (if ediff-merge-job (ediff-setup-windows-multiframe-merge buf-A buf-B buf-C control-buf) (ediff-setup-windows-multiframe-compare buf-A buf-B buf-C control-buf))) (defun ediff-setup-windows-multiframe-merge (buf-A buf-B buf-C control-buf) -;;; Algorithm: -;;; 1. Never use frames that have dedicated windows in them---it is bad to -;;; destroy dedicated windows. -;;; 2. If A and B are in the same frame but C's frame is different--- use one -;;; frame for A and B and use a separate frame for C. -;;; 3. If C's frame is non-existent, then: if the first suitable -;;; non-dedicated frame is different from A&B's, then use it for C. -;;; Otherwise, put A,B, and C in one frame. -;;; 4. If buffers A, B, C are is separate frames, use them to display these -;;; buffers. + ;; Algorithm: + ;; 1. Never use frames that have dedicated windows in them---it is bad to + ;; destroy dedicated windows. + ;; 2. If A and B are in the same frame but C's frame is different--- use one + ;; frame for A and B and use a separate frame for C. + ;; 3. If C's frame is non-existent, then: if the first suitable + ;; non-dedicated frame is different from A&B's, then use it for C. + ;; Otherwise, put A,B, and C in one frame. + ;; 4. If buffers A, B, C are is separate frames, use them to display these + ;; buffers. ;; Skip dedicated or iconified frames. ;; Unsplittable frames are taken care of later. @@ -532,7 +534,7 @@ into icons, regardless of the window manager." (wind-A (ediff-get-visible-buffer-window buf-A)) (wind-B (ediff-get-visible-buffer-window buf-B)) (wind-C (ediff-get-visible-buffer-window buf-C)) - (buf-Ancestor (ediff-with-current-buffer control-buf + (buf-Ancestor (with-current-buffer control-buf ediff-ancestor-buffer)) (wind-Ancestor (ediff-get-visible-buffer-window buf-Ancestor)) (frame-A (if wind-A (window-frame wind-A))) @@ -541,10 +543,10 @@ into icons, regardless of the window manager." (frame-Ancestor (if wind-Ancestor (window-frame wind-Ancestor))) ;; on wide display, do things in one frame (force-one-frame - (ediff-with-current-buffer control-buf ediff-wide-display-p)) + (with-current-buffer control-buf ediff-wide-display-p)) ;; this lets us have local versions of ediff-split-window-function (split-window-function - (ediff-with-current-buffer control-buf ediff-split-window-function)) + (with-current-buffer control-buf ediff-split-window-function)) (orig-wind (selected-window)) (orig-frame (selected-frame)) (use-same-frame (or force-one-frame @@ -566,11 +568,11 @@ into icons, regardless of the window manager." ;; use-same-frame-for-AB implies wind A and B are ok for display (use-same-frame-for-AB (and (not use-same-frame) (eq frame-A frame-B))) - (merge-window-share (ediff-with-current-buffer control-buf + (merge-window-share (with-current-buffer control-buf ediff-merge-window-share)) merge-window-lines designated-minibuffer-frame ; ediff-merge-with-ancestor-job - (with-Ancestor-p (ediff-with-current-buffer control-buf + (with-Ancestor-p (with-current-buffer control-buf ediff-merge-with-ancestor-job)) (done-Ancestor (not with-Ancestor-p)) done-A done-B done-C) @@ -724,7 +726,7 @@ into icons, regardless of the window manager." (switch-to-buffer buf-Ancestor) (setq wind-Ancestor (selected-window)))) - (ediff-with-current-buffer control-buf + (with-current-buffer control-buf (setq ediff-window-A wind-A ediff-window-B wind-B ediff-window-C wind-C @@ -738,21 +740,17 @@ into icons, regardless of the window manager." ;; Window setup for all comparison jobs, including 3way comparisons (defun ediff-setup-windows-multiframe-compare (buf-A buf-B buf-C control-buf) -;;; Algorithm: -;;; If a buffer is seen in a frame, use that frame for that buffer. -;;; If it is not seen, use the current frame. -;;; If both buffers are not seen, they share the current frame. If one -;;; of the buffers is not seen, it is placed in the current frame (where -;;; ediff started). If that frame is displaying the other buffer, it is -;;; shared between the two buffers. -;;; However, if we decide to put both buffers in one frame -;;; and the selected frame isn't splittable, we create a new frame and -;;; put both buffers there, event if one of this buffers is visible in -;;; another frame. - - ;; Skip dedicated or iconified frames. - ;; Unsplittable frames are taken care of later. - (ediff-skip-unsuitable-frames 'ok-unsplittable) + ;; Algorithm: + ;; If a buffer is seen in a frame, use that frame for that buffer. + ;; If it is not seen, use the current frame. + ;; If both buffers are not seen, they share the current frame. If one + ;; of the buffers is not seen, it is placed in the current frame (where + ;; ediff started). If that frame is displaying the other buffer, it is + ;; shared between the two buffers. + ;; However, if we decide to put both buffers in one frame + ;; and the selected frame isn't splittable, we create a new frame and + ;; put both buffers there, event if one of this buffers is visible in + ;; another frame. (let* ((window-min-height 1) (wind-A (ediff-get-visible-buffer-window buf-A)) @@ -761,17 +759,16 @@ into icons, regardless of the window manager." (frame-A (if wind-A (window-frame wind-A))) (frame-B (if wind-B (window-frame wind-B))) (frame-C (if wind-C (window-frame wind-C))) - (ctl-frame-exists-p (ediff-with-current-buffer control-buf + (ctl-frame-exists-p (with-current-buffer control-buf (frame-live-p ediff-control-frame))) ;; on wide display, do things in one frame (force-one-frame - (ediff-with-current-buffer control-buf ediff-wide-display-p)) + (with-current-buffer control-buf ediff-wide-display-p)) ;; this lets us have local versions of ediff-split-window-function (split-window-function - (ediff-with-current-buffer control-buf ediff-split-window-function)) + (with-current-buffer control-buf ediff-split-window-function)) (three-way-comparison - (ediff-with-current-buffer control-buf ediff-3way-comparison-job)) - (orig-wind (selected-window)) + (with-current-buffer control-buf ediff-3way-comparison-job)) (use-same-frame (or force-one-frame (eq frame-A frame-B) (not (ediff-window-ok-for-display wind-A)) @@ -790,10 +787,9 @@ into icons, regardless of the window manager." (or ctl-frame-exists-p (eq frame-B (selected-frame)))))) wind-A-start wind-B-start - designated-minibuffer-frame - done-A done-B done-C) + designated-minibuffer-frame) - (ediff-with-current-buffer control-buf + (with-current-buffer control-buf (setq wind-A-start (ediff-overlay-start (ediff-get-value-according-to-buffer-type 'A ediff-narrow-bounds)) @@ -801,30 +797,6 @@ into icons, regardless of the window manager." (ediff-get-value-according-to-buffer-type 'B ediff-narrow-bounds)))) - (if (and (window-live-p wind-A) (null use-same-frame)) ; buf-A on its own - (progn - ;; buffer buf-A is seen in live wind-A - (select-window wind-A) ; must be displaying buf-A - (delete-other-windows) - (setq wind-A (selected-window)) - (setq done-A t))) - - (if (and (window-live-p wind-B) (null use-same-frame)) ; buf B on its own - (progn - ;; buffer buf-B is seen in live wind-B - (select-window wind-B) ; must be displaying buf-B - (delete-other-windows) - (setq wind-B (selected-window)) - (setq done-B t))) - - (if (and (window-live-p wind-C) (null use-same-frame)) ; buf C on its own - (progn - ;; buffer buf-C is seen in live wind-C - (select-window wind-C) ; must be displaying buf-C - (delete-other-windows) - (setq wind-C (selected-window)) - (setq done-C t))) - (if use-same-frame (let (wind-width-or-height) ; this affects 3way setups only (if (and (eq frame-A frame-B) (frame-live-p frame-A)) @@ -838,7 +810,7 @@ into icons, regardless of the window manager." (if three-way-comparison (setq wind-width-or-height (/ - (if (eq split-window-function 'split-window-vertically) + (if (eq split-window-function #'split-window-vertically) (window-height wind-A) (window-width wind-A)) 3))) @@ -855,46 +827,57 @@ into icons, regardless of the window manager." (if (memq (selected-window) (list wind-A wind-B)) (other-window 1)) (switch-to-buffer buf-C) - (setq wind-C (selected-window)))) - (setq done-A t - done-B t - done-C t) - )) - - (or done-A ; Buf A to be set in its own frame - ;;; or it was set before because use-same-frame = 1 - (progn - ;; Buf-A was not set up yet as it wasn't visible, - ;; and use-same-frame = nil - (select-window orig-wind) - (delete-other-windows) - (switch-to-buffer buf-A) - (setq wind-A (selected-window)) - )) - (or done-B ; Buf B to be set in its own frame - ;;; or it was set before because use-same-frame = 1 - (progn - ;; Buf-B was not set up yet as it wasn't visible, - ;; and use-same-frame = nil - (select-window orig-wind) - (delete-other-windows) - (switch-to-buffer buf-B) - (setq wind-B (selected-window)) - )) - - (if three-way-comparison - (or done-C ; Buf C to be set in its own frame - ;;; or it was set before because use-same-frame = 1 + (setq wind-C (selected-window))))) + + (if (window-live-p wind-A) ; buf-A on its own + (progn + ;; buffer buf-A is seen in live wind-A + (select-window wind-A) ; must be displaying buf-A + (delete-other-windows) + (setq wind-A (selected-window))) ;FIXME: Why? + ;; Buf-A was not set up yet as it wasn't visible, + ;; and use-same-frame = nil + ;; Skip dedicated or iconified frames. + ;; Unsplittable frames are taken care of later. + (ediff-skip-unsuitable-frames 'ok-unsplittable) + (delete-other-windows) + (switch-to-buffer buf-A) + (setq wind-A (selected-window))) + + (if (window-live-p wind-B) ; buf B on its own + (progn + ;; buffer buf-B is seen in live wind-B + (select-window wind-B) ; must be displaying buf-B + (delete-other-windows) + (setq wind-B (selected-window))) ;FIXME: Why? + ;; Buf-B was not set up yet as it wasn't visible, + ;; and use-same-frame = nil + ;; Skip dedicated or iconified frames. + ;; Unsplittable frames are taken care of later. + (ediff-skip-unsuitable-frames 'ok-unsplittable) + (delete-other-windows) + (switch-to-buffer buf-B) + (setq wind-B (selected-window))) + + (if (window-live-p wind-C) ; buf C on its own + (progn + ;; buffer buf-C is seen in live wind-C + (select-window wind-C) ; must be displaying buf-C + (delete-other-windows) + (setq wind-C (selected-window))) ;FIXME: Why? + (if three-way-comparison (progn ;; Buf-C was not set up yet as it wasn't visible, ;; and use-same-frame = nil - (select-window orig-wind) + ;; Skip dedicated or iconified frames. + ;; Unsplittable frames are taken care of later. + (ediff-skip-unsuitable-frames 'ok-unsplittable) (delete-other-windows) (switch-to-buffer buf-C) (setq wind-C (selected-window)) - ))) + )))) - (ediff-with-current-buffer control-buf + (with-current-buffer control-buf (setq ediff-window-A wind-A ediff-window-B wind-B ediff-window-C wind-C) @@ -913,9 +896,9 @@ into icons, regardless of the window manager." (ediff-setup-control-frame control-buf designated-minibuffer-frame) )) -;; skip unsplittable frames and frames that have dedicated windows. -;; create a new splittable frame if none is found (defun ediff-skip-unsuitable-frames (&optional ok-unsplittable) + "Skip unsplittable frames and frames that have dedicated windows. +create a new splittable frame if none is found." (if (ediff-window-display-p) (let ((wind-frame (window-frame)) seen-windows) @@ -975,14 +958,14 @@ into icons, regardless of the window manager." ;; user-grabbed-mouse fheight fwidth adjusted-parameters) - (ediff-with-current-buffer ctl-buffer + (with-current-buffer ctl-buffer (if (and (featurep 'xemacs) (featurep 'menubar)) (set-buffer-menubar nil)) ;;(setq user-grabbed-mouse (ediff-user-grabbed-mouse)) (run-hooks 'ediff-before-setup-control-frame-hook)) - (setq old-ctl-frame (ediff-with-current-buffer ctl-buffer ediff-control-frame)) - (ediff-with-current-buffer ctl-buffer + (setq old-ctl-frame (with-current-buffer ctl-buffer ediff-control-frame)) + (with-current-buffer ctl-buffer (setq ctl-frame (if (frame-live-p old-ctl-frame) old-ctl-frame (make-frame ediff-control-frame-parameters)) @@ -1002,7 +985,7 @@ into icons, regardless of the window manager." ;; must be before ediff-setup-control-buffer ;; just a precaution--we should be in ctl-buffer already - (ediff-with-current-buffer ctl-buffer + (with-current-buffer ctl-buffer (make-local-variable 'frame-title-format) (make-local-variable 'frame-icon-title-format) ; XEmacs (make-local-variable 'icon-title-format)) ; Emacs @@ -1101,12 +1084,12 @@ into icons, regardless of the window manager." (not (eq ediff-grab-mouse t))))) (when (featurep 'xemacs) - (ediff-with-current-buffer ctl-buffer + (with-current-buffer ctl-buffer (make-local-hook 'select-frame-hook) (add-hook 'select-frame-hook - 'ediff-xemacs-select-frame-hook nil 'local))) + #'ediff-xemacs-select-frame-hook nil 'local))) - (ediff-with-current-buffer ctl-buffer + (with-current-buffer ctl-buffer (run-hooks 'ediff-after-setup-control-frame-hook)))) @@ -1126,7 +1109,7 @@ into icons, regardless of the window manager." ;; finds a good place to clip control frame (defun ediff-make-frame-position (ctl-buffer ctl-frame-width ctl-frame-height) - (ediff-with-current-buffer ctl-buffer + (with-current-buffer ctl-buffer (let* ((frame-A (window-frame ediff-window-A)) (frame-A-parameters (frame-parameters frame-A)) (frame-A-top (eval (cdr (assoc 'top frame-A-parameters)))) @@ -1380,12 +1363,4 @@ It assumes that it is called from within the control buffer." (provide 'ediff-wind) - - -;; Local Variables: -;; eval: (put 'ediff-defvar-local 'lisp-indent-hook 'defun) -;; eval: (put 'ediff-with-current-buffer 'lisp-indent-hook 1) -;; eval: (put 'ediff-with-current-buffer 'edebug-form-spec '(form body)) -;; End: - ;;; ediff-wind.el ends here diff --git a/lisp/vc/ediff.el b/lisp/vc/ediff.el index 4751bb6ddcb..0adf51328e2 100644 --- a/lisp/vc/ediff.el +++ b/lisp/vc/ediff.el @@ -29,7 +29,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -185,7 +185,9 @@ ;;;###autoload (defun ediff-files (file-A file-B &optional startup-hooks) - "Run Ediff on a pair of files, FILE-A and FILE-B." + "Run Ediff on a pair of files, FILE-A and FILE-B. +STARTUP-HOOKS is a list of functions that Emacs calls without +arguments after setting up the Ediff buffers." (interactive (let ((dir-A (if ediff-use-last-dir ediff-last-dir-A @@ -221,7 +223,9 @@ ;;;###autoload (defun ediff-files3 (file-A file-B file-C &optional startup-hooks) - "Run Ediff on three files, FILE-A, FILE-B, and FILE-C." + "Run Ediff on three files, FILE-A, FILE-B, and FILE-C. +STARTUP-HOOKS is a list of functions that Emacs calls without +arguments after setting up the Ediff buffers." (interactive (let ((dir-A (if ediff-use-last-dir ediff-last-dir-A @@ -419,7 +423,14 @@ If this file is a backup, `ediff' it with its original." ;;;###autoload (defun ediff-buffers (buffer-A buffer-B &optional startup-hooks job-name) - "Run Ediff on a pair of buffers, BUFFER-A and BUFFER-B." + "Run Ediff on a pair of buffers, BUFFER-A and BUFFER-B. +STARTUP-HOOKS is a list of functions that Emacs calls without +arguments after setting up the Ediff buffers. JOB-NAME is a +symbol describing the Ediff job type; it defaults to +`ediff-buffers', but can also be one of +`ediff-merge-files-with-ancestor', `ediff-last-dir-ancestor', +`ediff-last-dir-C', `ediff-buffers3', `ediff-merge-buffers', or +`ediff-merge-buffers-with-ancestor'." (interactive (let (bf) (list (setq bf (read-buffer "Buffer A to compare: " @@ -441,7 +452,14 @@ If this file is a backup, `ediff' it with its original." ;;;###autoload (defun ediff-buffers3 (buffer-A buffer-B buffer-C &optional startup-hooks job-name) - "Run Ediff on three buffers, BUFFER-A, BUFFER-B, and BUFFER-C." + "Run Ediff on three buffers, BUFFER-A, BUFFER-B, and BUFFER-C. +STARTUP-HOOKS is a list of functions that Emacs calls without +arguments after setting up the Ediff buffers. JOB-NAME is a +symbol describing the Ediff job type; it defaults to +`ediff-buffers3', but can also be one of +`ediff-merge-files-with-ancestor', `ediff-last-dir-ancestor', +`ediff-last-dir-C', `ediff-buffers', `ediff-merge-buffers', or +`ediff-merge-buffers-with-ancestor'." (interactive (let (bf bff) (list (setq bf (read-buffer "Buffer A to compare: " @@ -637,7 +655,8 @@ regular expression; only file names that match the regexp are considered." (defun ediff-merge-directories (dir1 dir2 regexp &optional merge-autostore-dir) "Run Ediff on a pair of directories, DIR1 and DIR2, merging files that have the same name in both. The third argument, REGEXP, is nil or a regular -expression; only file names that match the regexp are considered." +expression; only file names that match the regexp are considered. +MERGE-AUTOSTORE-DIR is the directory in which to store merged files." (interactive (let ((dir-A (ediff-get-default-directory-name)) (default-regexp (eval ediff-default-filtering-regexp)) @@ -674,7 +693,8 @@ expression; only file names that match the regexp are considered." Ediff merges files that have identical names in DIR1, DIR2. If a pair of files in DIR1 and DIR2 doesn't have an ancestor in ANCESTOR-DIR, Ediff will merge without ancestor. The fourth argument, REGEXP, is nil or a regular expression; -only file names that match the regexp are considered." +only file names that match the regexp are considered. +MERGE-AUTOSTORE-DIR is the directory in which to store merged files." (interactive (let ((dir-A (ediff-get-default-directory-name)) (default-regexp (eval ediff-default-filtering-regexp)) @@ -710,7 +730,8 @@ only file names that match the regexp are considered." &optional merge-autostore-dir) "Run Ediff on a directory, DIR1, merging its files with their revisions. The second argument, REGEXP, is a regular expression that filters the file -names. Only the files that are under revision control are taken into account." +names. Only the files that are under revision control are taken into account. +MERGE-AUTOSTORE-DIR is the directory in which to store merged files." (interactive (let ((dir-A (ediff-get-default-directory-name)) (default-regexp (eval ediff-default-filtering-regexp)) @@ -740,7 +761,8 @@ names. Only the files that are under revision control are taken into account." merge-autostore-dir) "Run Ediff on a directory, DIR1, merging its files with their revisions and ancestors. The second argument, REGEXP, is a regular expression that filters the file -names. Only the files that are under revision control are taken into account." +names. Only the files that are under revision control are taken into account. +MERGE-AUTOSTORE-DIR is the directory in which to store merged files." (interactive (let ((dir-A (ediff-get-default-directory-name)) (default-regexp (eval ediff-default-filtering-regexp)) @@ -908,7 +930,9 @@ names. Only the files that are under revision control are taken into account." With prefix argument, DUMB-MODE, or on a non-windowing display, works as follows: If WIND-A is nil, use selected window. -If WIND-B is nil, use window next to WIND-A." +If WIND-B is nil, use window next to WIND-A. +STARTUP-HOOKS is a list of functions that Emacs calls without +arguments after setting up the Ediff buffers." (interactive "P") (ediff-windows dumb-mode wind-A wind-B startup-hooks 'ediff-windows-wordwise 'word-mode)) @@ -919,7 +943,9 @@ If WIND-B is nil, use window next to WIND-A." With prefix argument, DUMB-MODE, or on a non-windowing display, works as follows: If WIND-A is nil, use selected window. -If WIND-B is nil, use window next to WIND-A." +If WIND-B is nil, use window next to WIND-A. +STARTUP-HOOKS is a list of functions that Emacs calls without +arguments after setting up the Ediff buffers." (interactive "P") (ediff-windows dumb-mode wind-A wind-B startup-hooks 'ediff-windows-linewise nil)) @@ -963,9 +989,12 @@ If WIND-B is nil, use window next to WIND-A." ;;;###autoload (defun ediff-regions-wordwise (buffer-A buffer-B &optional startup-hooks) "Run Ediff on a pair of regions in specified buffers. +BUFFER-A and BUFFER-B are the buffers to be compared. Regions (i.e., point and mark) can be set in advance or marked interactively. This function is effective only for relatively small regions, up to 200 -lines. For large regions, use `ediff-regions-linewise'." +lines. For large regions, use `ediff-regions-linewise'. +STARTUP-HOOKS is a list of functions that Emacs calls without +arguments after setting up the Ediff buffers." (interactive (let (bf) (list (setq bf (read-buffer "Region's A buffer: " @@ -1003,10 +1032,13 @@ lines. For large regions, use `ediff-regions-linewise'." ;;;###autoload (defun ediff-regions-linewise (buffer-A buffer-B &optional startup-hooks) "Run Ediff on a pair of regions in specified buffers. +BUFFER-A and BUFFER-B are the buffers to be compared. Regions (i.e., point and mark) can be set in advance or marked interactively. Each region is enlarged to contain full lines. This function is effective for large regions, over 100-200 -lines. For small regions, use `ediff-regions-wordwise'." +lines. For small regions, use `ediff-regions-wordwise'. +STARTUP-HOOKS is a list of functions that Emacs calls without +arguments after setting up the Ediff buffers." (interactive (let (bf) (list (setq bf (read-buffer "Region A's buffer: " @@ -1127,7 +1159,11 @@ lines. For small regions, use `ediff-regions-wordwise'." ;; MERGE-BUFFER-FILE is the file to be ;; associated with the merge buffer &optional startup-hooks merge-buffer-file) - "Merge two files without ancestor." + "Merge two files without ancestor. +FILE-A and FILE-B are the names of the files to be merged. +STARTUP-HOOKS is a list of functions that Emacs calls without +arguments after setting up the Ediff buffers. MERGE-BUFFER-FILE +is the name of the file to be associated with the merge buffer.." (interactive (let ((dir-A (if ediff-use-last-dir ediff-last-dir-A @@ -1171,7 +1207,12 @@ lines. For small regions, use `ediff-regions-wordwise'." ;; to be associated with the ;; merge buffer merge-buffer-file) - "Merge two files with ancestor." + "Merge two files with ancestor. +FILE-A and FILE-B are the names of the files to be merged, and +FILE-ANCESTOR is the name of the ancestor file. STARTUP-HOOKS is +a list of functions that Emacs calls without arguments after +setting up the Ediff buffers. MERGE-BUFFER-FILE is the name of +the file to be associated with the merge buffer." (interactive (let ((dir-A (if ediff-use-last-dir ediff-last-dir-A @@ -1229,7 +1270,16 @@ lines. For small regions, use `ediff-regions-wordwise'." ;; MERGE-BUFFER-FILE is the file to be ;; associated with the merge buffer startup-hooks job-name merge-buffer-file) - "Merge buffers without ancestor." + "Merge buffers without ancestor. +BUFFER-A and BUFFER-B are the buffers to be merged. +STARTUP-HOOKS is a list of functions that Emacs calls without +arguments after setting up the Ediff buffers. JOB-NAME is a +symbol describing the Ediff job type; it defaults to +`ediff-merge-buffers', but can also be one of +`ediff-merge-files-with-ancestor', `ediff-last-dir-ancestor', +`ediff-last-dir-C', `ediff-buffers', `ediff-buffers3', or +`ediff-merge-buffers-with-ancestor'. MERGE-BUFFER-FILE is the +name of the file to be associated with the merge buffer." (interactive (let (bf) (list (setq bf (read-buffer "Buffer A to merge: " @@ -1256,7 +1306,16 @@ lines. For small regions, use `ediff-regions-wordwise'." ;; file to be associated ;; with the merge buffer merge-buffer-file) - "Merge buffers with ancestor." + "Merge buffers with ancestor. +BUFFER-A and BUFFER-B are the buffers to be merged, and +BUFFER-ANCESTOR is their ancestor. STARTUP-HOOKS is a list of +functions that Emacs calls without arguments after setting up the +Ediff buffers. JOB-NAME is a symbol describing the Ediff job +type; it defaults to `ediff-merge-buffers-with-ancestor', but can +also be one of `ediff-merge-files-with-ancestor', +`ediff-last-dir-ancestor', `ediff-last-dir-C', `ediff-buffers', +`ediff-buffers3', or `ediff-merge-buffers'. MERGE-BUFFER-FILE is +the name of the file to be associated with the merge buffer." (interactive (let (bf bff) (list (setq bf (read-buffer "Buffer A to merge: " @@ -1287,8 +1346,11 @@ lines. For small regions, use `ediff-regions-wordwise'." (defun ediff-merge-revisions (&optional file startup-hooks merge-buffer-file) ;; MERGE-BUFFER-FILE is the file to be associated with the merge buffer "Run Ediff by merging two revisions of a file. -The file is the optional FILE argument or the file visited by the current -buffer." +The file is the optional FILE argument or the file visited by the +current buffer. STARTUP-HOOKS is a list of functions that Emacs +calls without arguments after setting up the Ediff buffers. +MERGE-BUFFER-FILE is the name of the file to be associated with +the merge buffer." (interactive) (if (stringp file) (find-file file)) (let (rev1 rev2) @@ -1319,8 +1381,11 @@ buffer." ;; buffer merge-buffer-file) "Run Ediff by merging two revisions of a file with a common ancestor. -The file is the optional FILE argument or the file visited by the current -buffer." +The file is the optional FILE argument or the file visited by the +current buffer. STARTUP-HOOKS is a list of functions that Emacs +calls without arguments after setting up the Ediff buffers. +MERGE-BUFFER-FILE is the name of the file to be associated with +the merge buffer." (interactive) (if (stringp file) (find-file file)) (let (rev1 rev2 ancestor-rev) @@ -1360,8 +1425,8 @@ buffer." "Query for a file name, and then run Ediff by patching that file. If optional PATCH-BUF is given, use the patch in that buffer and don't ask the user. -If prefix argument, then: if even argument, assume that the patch is in a -buffer. If odd -- assume it is in a file." +If prefix argument ARG, then: if even argument, assume that the +patch is in a buffer. If odd -- assume it is in a file." (interactive "P") (let (source-dir source-file) (require 'ediff-ptch) @@ -1394,7 +1459,7 @@ prompts for the buffer or a file, depending on the answer. With ARG=1, assumes the patch is in a file and prompts for the file. With ARG=2, assumes the patch is in a buffer and prompts for the buffer. PATCH-BUF is an optional argument, which specifies the buffer that contains the -patch. If not given, the user is prompted according to the prefix argument." +patch. If not given, the user is prompted according to the prefix argument." (interactive "P") (require 'ediff-ptch) (setq patch-buf @@ -1421,7 +1486,9 @@ patch. If not given, the user is prompted according to the prefix argument." "Run Ediff by comparing versions of a file. The file is an optional FILE argument or the file entered at the prompt. Default: the file visited by the current buffer. -Uses `vc.el' or `rcs.el' depending on `ediff-version-control-package'." +Uses `vc.el' or `rcs.el' depending on `ediff-version-control-package'. +STARTUP-HOOKS is a list of functions that Emacs calls without +arguments after setting up the Ediff buffers." ;; if buffer is non-nil, use that buffer instead of the current buffer (interactive "P") (if (not (stringp file)) @@ -1434,7 +1501,7 @@ Uses `vc.el' or `rcs.el' depending on `ediff-version-control-package'." 'no-dirs))) (find-file file) (if (and (buffer-modified-p) - (y-or-n-p (format "Buffer %s is modified. Save buffer? " + (y-or-n-p (format "Buffer %s is modified. Save buffer? " (buffer-name)))) (save-buffer (current-buffer))) (let (rev1 rev2) @@ -1517,6 +1584,7 @@ With optional NODE, goes to that node." ;;;###autoload (defun ediff-files-command () + "Call `ediff-files' with the next two command line arguments." (let ((file-a (nth 0 command-line-args-left)) (file-b (nth 1 command-line-args-left))) (setq command-line-args-left (nthcdr 2 command-line-args-left)) @@ -1524,6 +1592,7 @@ With optional NODE, goes to that node." ;;;###autoload (defun ediff3-files-command () + "Call `ediff3-files' with the next three command line arguments." (let ((file-a (nth 0 command-line-args-left)) (file-b (nth 1 command-line-args-left)) (file-c (nth 2 command-line-args-left))) @@ -1532,6 +1601,7 @@ With optional NODE, goes to that node." ;;;###autoload (defun ediff-merge-command () + "Call `ediff-merge-files' with the next two command line arguments." (let ((file-a (nth 0 command-line-args-left)) (file-b (nth 1 command-line-args-left))) (setq command-line-args-left (nthcdr 2 command-line-args-left)) @@ -1539,6 +1609,7 @@ With optional NODE, goes to that node." ;;;###autoload (defun ediff-merge-with-ancestor-command () + "Call `ediff-merge-files-with-ancestor' with the next three command line arguments." (let ((file-a (nth 0 command-line-args-left)) (file-b (nth 1 command-line-args-left)) (ancestor (nth 2 command-line-args-left))) @@ -1547,6 +1618,7 @@ With optional NODE, goes to that node." ;;;###autoload (defun ediff-directories-command () + "Call `ediff-directories' with the next three command line arguments." (let ((file-a (nth 0 command-line-args-left)) (file-b (nth 1 command-line-args-left)) (regexp (nth 2 command-line-args-left))) @@ -1555,6 +1627,7 @@ With optional NODE, goes to that node." ;;;###autoload (defun ediff-directories3-command () + "Call `ediff-directories3' with the next four command line arguments." (let ((file-a (nth 0 command-line-args-left)) (file-b (nth 1 command-line-args-left)) (file-c (nth 2 command-line-args-left)) @@ -1564,6 +1637,7 @@ With optional NODE, goes to that node." ;;;###autoload (defun ediff-merge-directories-command () + "Call `ediff-merge-directories' with the next three command line arguments." (let ((file-a (nth 0 command-line-args-left)) (file-b (nth 1 command-line-args-left)) (regexp (nth 2 command-line-args-left))) @@ -1572,6 +1646,7 @@ With optional NODE, goes to that node." ;;;###autoload (defun ediff-merge-directories-with-ancestor-command () + "Call `ediff-merge-directories-with-ancestor' with the next four command line arguments." (let ((file-a (nth 0 command-line-args-left)) (file-b (nth 1 command-line-args-left)) (ancestor (nth 2 command-line-args-left)) diff --git a/lisp/vc/log-edit.el b/lisp/vc/log-edit.el index cd8ba19f6d5..30457d1e2d3 100644 --- a/lisp/vc/log-edit.el +++ b/lisp/vc/log-edit.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -623,7 +623,7 @@ Also saves its contents in the comment history and hides (setq buffer-read-only nil) (erase-buffer) (cvs-insert-strings files) - (setq buffer-read-only t) + (special-mode) (goto-char (point-min)) (save-selected-window (cvs-pop-to-buffer-same-frame buf) diff --git a/lisp/vc/log-view.el b/lisp/vc/log-view.el index e8efc1e6e09..d6963d0a1b9 100644 --- a/lisp/vc/log-view.el +++ b/lisp/vc/log-view.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -608,10 +608,16 @@ considered file(s)." (log-view-diff-common beg end t))) (defun log-view-diff-common (beg end &optional whole-changeset) - (let ((to (log-view-current-tag beg)) - (fr (log-view-current-tag end))) - (when (string-equal fr to) - ;; TO and FR are the same, look at the previous revision. + (let* ((to (log-view-current-tag beg)) + (fr-entry (log-view-current-entry end)) + (fr (cadr fr-entry))) + ;; When TO and FR are the same, or when point is on a line after + ;; the last entry, look at the previous revision. + (when (or (string-equal fr to) + (>= (point) + (save-excursion + (goto-char (car fr-entry)) + (forward-line)))) (setq fr (vc-call-backend log-view-vc-backend 'previous-revision nil fr))) (vc-diff-internal t (list log-view-vc-backend diff --git a/lisp/vc/pcvs-defs.el b/lisp/vc/pcvs-defs.el index 72caafc4fb0..41dc9401c0a 100644 --- a/lisp/vc/pcvs-defs.el +++ b/lisp/vc/pcvs-defs.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -269,7 +269,7 @@ This variable is buffer local and only used in the *cvs* buffer.") "Regexp matching the possible names of locks in the CVS repository.") (defconst cvs-cursor-column 22 - "Column to position cursor in in `cvs-mode'.") + "Column to position cursor in, in `cvs-mode'.") ;;;; ;;;; Global internal variables diff --git a/lisp/vc/pcvs-info.el b/lisp/vc/pcvs-info.el index 8dd513c81fa..239a2268aae 100644 --- a/lisp/vc/pcvs-info.el +++ b/lisp/vc/pcvs-info.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/vc/pcvs-parse.el b/lisp/vc/pcvs-parse.el index 2315938a32a..f0bb8943f23 100644 --- a/lisp/vc/pcvs-parse.el +++ b/lisp/vc/pcvs-parse.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/vc/pcvs-util.el b/lisp/vc/pcvs-util.el index de45141ddc1..9b62780a32d 100644 --- a/lisp/vc/pcvs-util.el +++ b/lisp/vc/pcvs-util.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/vc/pcvs.el b/lisp/vc/pcvs.el index 3ab34236685..4e74d5f6f86 100644 --- a/lisp/vc/pcvs.el +++ b/lisp/vc/pcvs.el @@ -27,7 +27,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/vc/smerge-mode.el b/lisp/vc/smerge-mode.el index 21c39c85ca8..b988463de1e 100644 --- a/lisp/vc/smerge-mode.el +++ b/lisp/vc/smerge-mode.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -725,7 +725,7 @@ this keeps \"UUU\"." (let ((i 3)) (while (or (not (match-end i)) (< (point) (match-beginning i)) - (>= (point) (match-end i))) + (> (point) (match-end i))) (cl-decf i)) i)) @@ -938,15 +938,15 @@ It has the following disadvantages: - cannot use `diff -w' because the weighting causes added spaces in a line to be represented as added copies of some line, so `diff -w' can't do the right thing any more. -- may in degenerate cases take a 1KB input region and turn it into a 1MB - file to pass to diff.") +- Is a bit more costly (may in degenerate cases use temp files that are 10x + larger than the refined regions).") (defun smerge--refine-forward (n) (let ((case-fold-search nil) (re "[[:upper:]]?[[:lower:]]+\\|[[:upper:]]+\\|[[:digit:]]+\\|.\\|\n")) (when (and smerge-refine-ignore-whitespace ;; smerge-refine-weight-hack causes additional spaces to - ;; appear as additional lines as well, so even if diff ignore + ;; appear as additional lines as well, so even if diff ignores ;; whitespace changes, it'll report added/removed lines :-( (not smerge-refine-weight-hack)) (setq re (concat "[ \t]*\\(?:" re "\\)"))) @@ -954,6 +954,8 @@ It has the following disadvantages: (unless (looking-at re) (error "Smerge refine internal error")) (goto-char (match-end 0))))) +(defvar smerge--refine-long-words) + (defun smerge--refine-chopup-region (beg end file &optional preproc) "Chopup the region into small elements, one per line. Save the result into FILE. @@ -976,18 +978,46 @@ chars to try and eliminate some spurious differences." (subst-char-in-region (point-min) (point-max) ?\n ?\s)) (goto-char (point-min)) (while (not (eobp)) - (funcall smerge-refine-forward-function 1) - (let ((s (if (prog2 (forward-char -1) (bolp) (forward-char 1)) - nil - (buffer-substring (line-beginning-position) (point))))) - ;; We add \n after each char except after \n, so we get - ;; one line per text char, where each line contains - ;; just one char, except for \n chars which are - ;; represented by the empty line. - (unless (eq (char-before) ?\n) (insert ?\n)) - ;; HACK ALERT!! - (if smerge-refine-weight-hack - (dotimes (_i (1- (length s))) (insert s "\n"))))) + (cl-assert (bolp)) + (let ((start (point))) + (funcall smerge-refine-forward-function 1) + (let ((len (- (point) start))) + (cl-assert (>= len 1)) + ;; We add \n after each chunk except after \n, so we get + ;; one line per text chunk, where each line contains + ;; just one chunk, except for \n chars which are + ;; represented by the empty line. + (unless (bolp) (insert ?\n)) + (when (and smerge-refine-weight-hack (> len 1)) + (let ((s (buffer-substring-no-properties start (point)))) + ;; The weight-hack inserts N copies of words of size N, + ;; so it naturally suffers from an O(N²) blow up. + ;; To circumvent this, we map each long word + ;; to a shorter (but still unique) replacement. + ;; Another option would be to change smerge--refine-forward + ;; so it chops up long words into smaller ones. + (when (> len 8) + (let ((short (gethash s smerge--refine-long-words))) + (unless short + ;; To avoid accidental conflicts with ≤8 words, + ;; we make sure the replacement is >8 chars. Overall, + ;; this should bound the blowup factor to ~10x, + ;; tho if those chars end up encoded as multiple bytes + ;; each, it could probably still reach ~30x in + ;; pathological cases. + (setq short + (concat (substring s 0 7) + " " + (string + (+ ?0 + (hash-table-count + smerge--refine-long-words))) + "\n")) + (puthash s short smerge--refine-long-words)) + (delete-region start (point)) + (insert short) + (setq s short))) + (dotimes (_i (1- len)) (insert s))))))) (unless (bolp) (error "Smerge refine internal error")) (let ((coding-system-for-write 'emacs-internal)) (write-region (point-min) (point-max) file nil 'nomessage)))) @@ -1042,7 +1072,9 @@ used to replace chars to try and eliminate some spurious differences." (let* ((pos (point)) deactivate-mark ; The code does not modify any visible buffer. (file1 (make-temp-file "diff1")) - (file2 (make-temp-file "diff2"))) + (file2 (make-temp-file "diff2")) + (smerge--refine-long-words + (if smerge-refine-weight-hack (make-hash-table :test #'equal)))) (unless (markerp beg1) (setq beg1 (copy-marker beg1))) (unless (markerp beg2) (setq beg2 (copy-marker beg2))) ;; Chop up regions into smaller elements and save into files. @@ -1062,7 +1094,7 @@ used to replace chars to try and eliminate some spurious differences." ;; also and more importantly because otherwise it ;; may happen that diff doesn't behave like ;; smerge-refine-weight-hack expects it to. - ;; See http://thread.gmane.org/gmane.emacs.devel/82685. + ;; See https://lists.gnu.org/r/emacs-devel/2007-11/msg00401.html "-awd" "-ad") file1 file2)) ;; Process diff's output. diff --git a/lisp/vc/vc-annotate.el b/lisp/vc/vc-annotate.el index 12df680ce03..a6c0e5a72d7 100644 --- a/lisp/vc/vc-annotate.el +++ b/lisp/vc/vc-annotate.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/vc/vc-bzr.el b/lisp/vc/vc-bzr.el index d0e9f7744b8..51b104cbcd5 100644 --- a/lisp/vc/vc-bzr.el +++ b/lisp/vc/vc-bzr.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/vc/vc-cvs.el b/lisp/vc/vc-cvs.el index bab835c84a5..36cb2e5fcbc 100644 --- a/lisp/vc/vc-cvs.el +++ b/lisp/vc/vc-cvs.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/vc/vc-dav.el b/lisp/vc/vc-dav.el index 4aa185186c4..f951c67498f 100644 --- a/lisp/vc/vc-dav.el +++ b/lisp/vc/vc-dav.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/vc/vc-dir.el b/lisp/vc/vc-dir.el index 0363aab8407..41c44e2c24a 100644 --- a/lisp/vc/vc-dir.el +++ b/lisp/vc/vc-dir.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Credits: diff --git a/lisp/vc/vc-dispatcher.el b/lisp/vc/vc-dispatcher.el index c71030aba17..479003e65a3 100644 --- a/lisp/vc/vc-dispatcher.el +++ b/lisp/vc/vc-dispatcher.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Credits: diff --git a/lisp/vc/vc-filewise.el b/lisp/vc/vc-filewise.el index 16f833cc5bc..87a2e33264c 100644 --- a/lisp/vc/vc-filewise.el +++ b/lisp/vc/vc-filewise.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/vc/vc-git.el b/lisp/vc/vc-git.el index cc3e295641c..ab8b358cf2c 100644 --- a/lisp/vc/vc-git.el +++ b/lisp/vc/vc-git.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -857,13 +857,13 @@ It is based on `log-edit-mode', and has Git-specific extensions.") (vc-git-command nil nil file "checkout" "-q" "--"))) (defvar vc-git-error-regexp-alist - '(("^ \\(.+\\) |" 1 nil nil 0)) + '(("^ \\(.+\\)\\> *|" 1 nil nil 0)) "Value of `compilation-error-regexp-alist' in *vc-git* buffers.") ;; To be called via vc-pull from vc.el, which requires vc-dispatcher. (declare-function vc-compilation-mode "vc-dispatcher" (backend)) -(defun vc-git--pushpull (command prompt) +(defun vc-git--pushpull (command prompt extra-args) "Run COMMAND (a string; either push or pull) on the current Git branch. If PROMPT is non-nil, prompt for the Git command to run." (let* ((root (vc-git-root default-directory)) @@ -882,6 +882,7 @@ If PROMPT is non-nil, prompt for the Git command to run." (setq git-program (car args) command (cadr args) args (cddr args))) + (setq args (nconc args extra-args)) (require 'vc-dispatcher) (apply 'vc-do-async-command buffer root git-program command args) (with-current-buffer buffer @@ -889,7 +890,7 @@ If PROMPT is non-nil, prompt for the Git command to run." (vc-compilation-mode 'git) (setq-local compile-command (concat git-program " " command " " - (if args (mapconcat 'identity args " ") ""))) + (mapconcat 'identity args " "))) (setq-local compilation-directory root) ;; Either set `compilation-buffer-name-function' locally to nil ;; or use `compilation-arguments' to set `name-function'. @@ -904,13 +905,13 @@ If PROMPT is non-nil, prompt for the Git command to run." "Pull changes into the current Git branch. Normally, this runs \"git pull\". If PROMPT is non-nil, prompt for the Git command to run." - (vc-git--pushpull "pull" prompt)) + (vc-git--pushpull "pull" prompt '("--stat"))) (defun vc-git-push (prompt) "Push changes from the current Git branch. Normally, this runs \"git push\". If PROMPT is non-nil, prompt for the Git command to run." - (vc-git--pushpull "push" prompt)) + (vc-git--pushpull "push" prompt nil)) (defun vc-git-merge-branch () "Merge changes into the current Git branch. @@ -951,6 +952,10 @@ This prompts for a branch to merge from." "DU" "AA" "UU")) (push (expand-file-name file directory) files))))))) +;; Everywhere but here, follows vc-git-command, which uses vc-do-command +;; from vc-dispatcher. +(autoload 'vc-resynch-buffer "vc-dispatcher") + (defun vc-git-resolve-when-done () "Call \"git add\" if the conflict markers have been removed." (save-excursion @@ -964,6 +969,7 @@ This prompts for a branch to merge from." (vc-git-root buffer-file-name))) (vc-git-conflicted-files (vc-git-root buffer-file-name))) (vc-git-command nil 0 nil "reset")) + (vc-resynch-buffer buffer-file-name t t) ;; Remove the hook so that it is not called multiple times. (remove-hook 'after-save-hook 'vc-git-resolve-when-done t)))) @@ -973,7 +979,7 @@ This prompts for a branch to merge from." ;; FIXME ;; 1) the net result is to call git twice per file. ;; 2) v-g-c-f is documented to take a directory. - ;; http://lists.gnu.org/archive/html/emacs-devel/2014-01/msg01126.html + ;; https://lists.gnu.org/r/emacs-devel/2014-01/msg01126.html (vc-git-conflicted-files buffer-file-name) (save-excursion (goto-char (point-min)) @@ -1030,6 +1036,7 @@ If LIMIT is non-nil, show no more than this many entries." (defun vc-git-log-outgoing (buffer remote-location) (interactive) + (vc-setup-buffer buffer) (vc-git-command buffer 'async nil "log" @@ -1043,6 +1050,7 @@ If LIMIT is non-nil, show no more than this many entries." (defun vc-git-log-incoming (buffer remote-location) (interactive) + (vc-setup-buffer buffer) (vc-git-command nil 0 nil "fetch") (vc-git-command buffer 'async nil @@ -1400,7 +1408,9 @@ This requires git 1.8.4 or later, for the \"-L\" option of \"git log\"." "Run git grep, searching for REGEXP in FILES in directory DIR. The search is limited to file names matching shell pattern FILES. FILES may use abbreviations defined in `grep-files-aliases', e.g. -entering `ch' is equivalent to `*.[ch]'. +entering `ch' is equivalent to `*.[ch]'. As whitespace triggers +completion when entering a pattern, including it requires +quoting, e.g. `\\[quoted-insert]<space>'. With \\[universal-argument] prefix, you can edit the constructed shell command line before it is executed. @@ -1421,7 +1431,9 @@ This command shares argument histories with \\[rgrep] and \\[grep]." nil nil 'grep-history) nil)) (t (let* ((regexp (grep-read-regexp)) - (files (grep-read-files regexp)) + (files + (mapconcat #'shell-quote-argument + (split-string (grep-read-files regexp)) " ")) (dir (read-directory-name "In directory: " nil default-directory t))) (list regexp files dir)))))) @@ -1450,10 +1462,6 @@ This command shares argument histories with \\[rgrep] and \\[grep]." (if (eq next-error-last-buffer (current-buffer)) (setq default-directory dir)))))) -;; Everywhere but here, follows vc-git-command, which uses vc-do-command -;; from vc-dispatcher. -(autoload 'vc-resynch-buffer "vc-dispatcher") - (defun vc-git-stash (name) "Create a stash." (interactive "sStash name: ") @@ -1554,7 +1562,7 @@ The difference to vc-do-command is that this function always invokes (or coding-system-for-write vc-git-commits-coding-system)) (process-environment (cons "GIT_DIR" process-environment))) (apply 'vc-do-command (or buffer "*vc*") okstatus vc-git-program - ;; http://debbugs.gnu.org/16897 + ;; https://debbugs.gnu.org/16897 (unless (and (not (cdr-safe file-or-list)) (let ((file (or (car-safe file-or-list) file-or-list))) diff --git a/lisp/vc/vc-hg.el b/lisp/vc/vc-hg.el index 4be529624a4..9e597a209a7 100644 --- a/lisp/vc/vc-hg.el +++ b/lisp/vc/vc-hg.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -687,7 +687,8 @@ PREFIX is the directory name of the directory against which these patterns are rooted. We understand only a subset of PCRE syntax; if we don't understand a construct, we signal `vc-hg-unsupported-syntax'." - (cl-assert (string-match "^/\\(.*/\\)?$" prefix)) + (cl-assert (and (file-name-absolute-p prefix) + (directory-name-p prefix))) (let ((parts nil) (i 0) (anchored nil) @@ -875,7 +876,8 @@ if we don't understand a construct, we signal (defun vc-hg--slurp-hgignore (repo) "Read hg ignore patterns from REPO. REPO must be the directory name of an hg repository." - (cl-assert (string-match "^/\\(.*/\\)?$" repo)) + (cl-assert (and (file-name-absolute-p repo) + (directory-name-p repo))) (let* ((hgignore (concat repo ".hgignore")) (vc-hg--hgignore-patterns nil) (vc-hg--hgignore-filenames nil)) @@ -930,7 +932,8 @@ FILENAME must be the file's true absolute name." (concat repo repo-relative-filename)))) (defun vc-hg--read-repo-requirements (repo) - (cl-assert (string-match "^/\\(.*/\\)?$" repo)) + (cl-assert (and (file-name-absolute-p repo) + (directory-name-p repo))) (let* ((requires-filename (concat repo ".hg/requires"))) (and (file-exists-p requires-filename) (with-temp-buffer @@ -1001,7 +1004,8 @@ hg binary." ;; dirstate must exist (not (progn (setf repo (expand-file-name repo)) - (cl-assert (string-match "^/\\(.*/\\)?$" repo)) + (cl-assert (and (file-name-absolute-p repo) + (directory-name-p repo))) (setf dirstate (concat repo ".hg/dirstate")) (setf dirstate-attr (file-attributes dirstate)))) ;; Repository must be in an understood format @@ -1292,12 +1296,8 @@ REV is the revision to check out into WORKFILE." (vc-hg-command buffer 1 nil "outgoing" "-n" (unless (string= remote-location "") remote-location))) -(defvar vc-hg-error-regexp-alist nil - ;; 'hg pull' does not list modified files, so, for now, the only - ;; benefit of `vc-compilation-mode' is that one can get rid of - ;; *vc-hg* buffer with 'q' or 'z'. - ;; TODO: call 'hg incoming' before pull/merge to get the list of - ;; modified files +(defvar vc-hg-error-regexp-alist + '(("^M \\(.+\\)" 1 nil nil 0)) "Value of `compilation-error-regexp-alist' in *vc-hg* buffers.") (autoload 'vc-do-async-command "vc-dispatcher") @@ -1305,9 +1305,10 @@ REV is the revision to check out into WORKFILE." (defvar compilation-directory) (defvar compilation-arguments) ; defined in compile.el -(defun vc-hg--pushpull (command prompt &optional obsolete) +(defun vc-hg--pushpull (command prompt post-processing &optional obsolete) "Run COMMAND (a string; either push or pull) on the current Hg branch. If PROMPT is non-nil, prompt for the Hg command to run. +POST-PROCESSING is a list of commands to execute after the command. If OBSOLETE is non-nil, behave like the old versions of the Hg push/pull commands, which only operated on marked files." (let (marked-list) @@ -1323,18 +1324,14 @@ commands, which only operated on marked files." (let* ((root (vc-hg-root default-directory)) (buffer (format "*vc-hg : %s*" (expand-file-name root))) (hg-program vc-hg-program) - ;; Fixme: before updating the working copy to the latest - ;; state, should check if it's visiting an old revision. - (args (if (equal command "pull") '("-u")))) + args) ;; If necessary, prompt for the exact command. ;; TODO if pushing, prompt if no default push location - cf bzr. (when prompt (setq args (split-string (read-shell-command (format "Hg %s command: " command) - (format "%s %s%s" hg-program command - (if (not args) "" - (concat " " (mapconcat 'identity args " ")))) + (format "%s %s" hg-program command) 'vc-hg-history) " " t)) (setq hg-program (car args) @@ -1343,10 +1340,17 @@ commands, which only operated on marked files." (apply 'vc-do-async-command buffer root hg-program command args) (with-current-buffer buffer (vc-run-delayed + (dolist (cmd post-processing) + (apply 'vc-do-command buffer nil hg-program nil cmd)) (vc-compilation-mode 'hg) (setq-local compile-command (concat hg-program " " command " " - (if args (mapconcat 'identity args " ") ""))) + (mapconcat 'identity args " ") + (mapconcat (lambda (args) + (concat " && " hg-program " " + (mapconcat 'identity + args " "))) + post-processing ""))) (setq-local compilation-directory root) ;; Either set `compilation-buffer-name-function' locally to nil ;; or use `compilation-arguments' to set `name-function'. @@ -1367,7 +1371,15 @@ specific Mercurial pull command. The default is \"hg pull -u\", which fetches changesets from the default remote repository and then attempts to update the working directory." (interactive "P") - (vc-hg--pushpull "pull" prompt (called-interactively-p 'interactive))) + (vc-hg--pushpull "pull" prompt + ;; Fixme: before updating the working copy to the latest + ;; state, should check if it's visiting an old revision. + ;; post-processing: list modified files and update + ;; NB: this will not work with "pull = --rebase" + ;; or "pull = --update" in hgrc. + '(("--pager" "no" "status" "--rev" "." "--rev" "tip") + ("update")) + (called-interactively-p 'interactive))) (defun vc-hg-push (prompt) "Push changes from the current Mercurial branch. @@ -1377,7 +1389,7 @@ for the Hg command to run. If called interactively with a set of marked Log View buffers, call \"hg push -r REVS\" to push the specified revisions REVS." (interactive "P") - (vc-hg--pushpull "push" prompt (called-interactively-p 'interactive))) + (vc-hg--pushpull "push" prompt nil (called-interactively-p 'interactive))) (defun vc-hg-merge-branch () "Merge incoming changes into the current working directory. diff --git a/lisp/vc/vc-hooks.el b/lisp/vc/vc-hooks.el index 2c2534a034e..99c8211ad5f 100644 --- a/lisp/vc/vc-hooks.el +++ b/lisp/vc/vc-hooks.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -882,7 +882,7 @@ In the latter case, VC mode is deactivated for this buffer." (define-key map "d" 'vc-dir) (define-key map "g" 'vc-annotate) (define-key map "G" 'vc-ignore) - (define-key map "h" 'vc-insert-headers) + (define-key map "h" 'vc-region-history) (define-key map "i" 'vc-register) (define-key map "l" 'vc-print-log) (define-key map "L" 'vc-print-root-log) diff --git a/lisp/vc/vc-mtn.el b/lisp/vc/vc-mtn.el index ac95da08f1f..eed4bd09dfb 100644 --- a/lisp/vc/vc-mtn.el +++ b/lisp/vc/vc-mtn.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/vc/vc-rcs.el b/lisp/vc/vc-rcs.el index afeb5ef23d5..4634e76088f 100644 --- a/lisp/vc/vc-rcs.el +++ b/lisp/vc/vc-rcs.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -177,6 +177,8 @@ For a description of possible values, see `vc-check-master-templates'." (push (list frel state) result)))) (funcall update-function result))) +(defun vc-rcs-dir-extra-headers (&rest _ignore)) + (defun vc-rcs-working-revision (file) "RCS-specific version of `vc-working-revision'." (or (and vc-consult-headers @@ -849,7 +851,7 @@ and CVS." ;; You might think that this should be distributed with RCS, but ;; apparently not. CVS sometimes provides a version of it. -;; http://lists.gnu.org/archive/html/emacs-devel/2014-05/msg00288.html +;; https://lists.gnu.org/r/emacs-devel/2014-05/msg00288.html (defvar vc-rcs-rcs2log-program (let (exe) (cond ((file-executable-p diff --git a/lisp/vc/vc-sccs.el b/lisp/vc/vc-sccs.el index fd27db381d3..f873fbfe1d4 100644 --- a/lisp/vc/vc-sccs.el +++ b/lisp/vc/vc-sccs.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/vc/vc-src.el b/lisp/vc/vc-src.el index 5c8b3da6f1a..0a219ff94a0 100644 --- a/lisp/vc/vc-src.el +++ b/lisp/vc/vc-src.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -180,7 +180,7 @@ For a description of possible values, see `vc-check-master-templates'." (defun vc-src-dir-status-files (dir files update-function) ;; FIXME: Use one src status -a call for this - (if (not files) (setq files (vc-expand-dirs (list dir) 'RCS))) + (if (not files) (setq files (vc-expand-dirs (list dir) 'SRC))) (let ((result nil)) (dolist (file files) (let ((state (vc-state file)) diff --git a/lisp/vc/vc-svn.el b/lisp/vc/vc-svn.el index db16eb202de..f0987bf6671 100644 --- a/lisp/vc/vc-svn.el +++ b/lisp/vc/vc-svn.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -184,6 +184,7 @@ switches." (?M . edited) (?D . removed) (?R . removed) + (?! . needs-update) (?? . unregistered) ;; This is what vc-svn-parse-status does. (?~ . edited))) @@ -700,8 +701,7 @@ Set file properties accordingly. If FILENAME is non-nil, return its status." (let (multifile file status propstat) (goto-char (point-min)) (while (re-search-forward - ;; Ignore the files with status X. - "^\\(?:\\?\\|[ ACDGIMR!~][ MC][ L][ +][ S]..\\([ *]\\) +\\([-0-9]+\\) +\\([0-9?]+\\) +\\([^ ]+\\)\\) +" nil t) + "^\\(?:\\?\\|[ ACDGIMR!~][ MC][ L][ +][ SX]..\\([ *]\\) +\\([-0-9]+\\) +\\([0-9?]+\\) +\\([^ ]+\\)\\) +" nil t) ;; If the username contains spaces, the output format is ambiguous, ;; so don't trust the output's filename unless we have to. (setq file (or (unless multifile filename) diff --git a/lisp/vc/vc.el b/lisp/vc/vc.el index 9379bcf74de..211feddc55d 100644 --- a/lisp/vc/vc.el +++ b/lisp/vc/vc.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Credits: @@ -2377,6 +2377,7 @@ When called interactively with a prefix argument, prompt for LIMIT." ;;;###autoload (defun vc-print-branch-log (branch) + "Show the change log for BRANCH in a window." (interactive (list (vc-read-revision "Branch to log: "))) diff --git a/lisp/vcursor.el b/lisp/vcursor.el index 22727bc8d64..0d7a5ff885f 100644 --- a/lisp/vcursor.el +++ b/lisp/vcursor.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/version.el b/lisp/version.el index ea6f1b46948..1792a81f71f 100644 --- a/lisp/version.el +++ b/lisp/version.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/view.el b/lisp/view.el index fb478e17785..6a4705acd6f 100644 --- a/lisp/view.el +++ b/lisp/view.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -982,7 +982,7 @@ for highlighting the match that is found." ;; This is the dumb approach, looking at each line. The original ;; version of this function looked like it might have been trying to ;; do something clever, but not succeeding: -;; http://lists.gnu.org/archive/html/bug-gnu-emacs/2007-09/msg00073.html +;; https://lists.gnu.org/r/bug-gnu-emacs/2007-09/msg00073.html (defun view-search-no-match-lines (times regexp) "Search for the TIMESth occurrence of a line with no match for REGEXP. If such a line is found, return non-nil and set the match-data to that line. diff --git a/lisp/vt-control.el b/lisp/vt-control.el index 8755420d188..7ea68817c28 100644 --- a/lisp/vt-control.el +++ b/lisp/vt-control.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/vt100-led.el b/lisp/vt100-led.el index c86a826ddbc..a6f43fbf6bb 100644 --- a/lisp/vt100-led.el +++ b/lisp/vt100-led.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/w32-fns.el b/lisp/w32-fns.el index 93551de4404..1ba6403bea5 100644 --- a/lisp/w32-fns.el +++ b/lisp/w32-fns.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/w32-vars.el b/lisp/w32-vars.el index 50f62559a44..3309db34123 100644 --- a/lisp/w32-vars.el +++ b/lisp/w32-vars.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/wdired.el b/lisp/wdired.el index 179b51b711a..b8de02dd37a 100644 --- a/lisp/wdired.el +++ b/lisp/wdired.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/whitespace.el b/lisp/whitespace.el index c6d5b16caeb..32a90ba485b 100644 --- a/lisp/whitespace.el +++ b/lisp/whitespace.el @@ -1,9 +1,9 @@ -;;; whitespace.el --- minor mode to visualize TAB, (HARD) SPACE, NEWLINE +;;; whitespace.el --- minor mode to visualize TAB, (HARD) SPACE, NEWLINE -*- lexical-binding: t -*- ;; Copyright (C) 2000-2017 Free Software Foundation, Inc. -;; Author: Vinicius Jose Latorre <viniciusjl@ig.com.br> -;; Maintainer: Vinicius Jose Latorre <viniciusjl@ig.com.br> +;; Author: Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> +;; Maintainer: Vinicius Jose Latorre <viniciusjl.gnu@gmail.com> ;; Keywords: data, wp ;; Version: 13.2.2 ;; X-URL: http://www.emacswiki.org/cgi-bin/wiki/ViniciusJoseLatorre @@ -21,7 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -495,7 +495,8 @@ Used when `whitespace-style' includes the value `spaces'.") (defvar whitespace-tab 'whitespace-tab "Symbol face used to visualize TAB. Used when `whitespace-style' includes the value `tabs'.") -(make-obsolete-variable 'whitespace-tab "use the face instead." "24.4") +(make-obsolete-variable 'whitespace-tab + "customize the face `whitespace-tab' instead." "24.4") (defface whitespace-tab '((((class color) (background dark)) diff --git a/lisp/wid-browse.el b/lisp/wid-browse.el index f105de244a2..d8054e348e9 100644 --- a/lisp/wid-browse.el +++ b/lisp/wid-browse.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/wid-edit.el b/lisp/wid-edit.el index ca402c18e53..4fa36edb9c6 100644 --- a/lisp/wid-edit.el +++ b/lisp/wid-edit.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Wishlist items (from widget.texi): @@ -832,7 +832,7 @@ button end points." ;; This alias exists only so that one can choose in doc-strings (e.g. ;; Custom-mode) which key-binding of widget-keymap one wants to refer to. -;; http://lists.gnu.org/archive/html/emacs-devel/2008-11/msg00480.html +;; https://lists.gnu.org/r/emacs-devel/2008-11/msg00480.html (define-obsolete-function-alias 'advertised-widget-backward 'widget-backward "23.2") @@ -3694,15 +3694,17 @@ example: (defun widget-color--choose-action (widget &optional _event) (list-colors-display nil nil - `(lambda (color) - (when (buffer-live-p ,(current-buffer)) - (widget-value-set ',(widget-get widget :parent) color) - (let* ((buf (get-buffer "*Colors*")) - (win (get-buffer-window buf 0))) - (if win - (quit-window nil win) - (bury-buffer buf))) - (pop-to-buffer ,(current-buffer)))))) + (let ((cbuf (current-buffer)) + (wp (widget-get widget :parent))) + (lambda (color) + (when (buffer-live-p cbuf) + (widget-value-set wp color) + (let* ((buf (get-buffer "*Colors*")) + (win (get-buffer-window buf 0))) + (if win + (quit-window nil win) + (bury-buffer buf))) + (pop-to-buffer cbuf)))))) (defun widget-color-sample-face-get (widget) (let* ((value (condition-case nil diff --git a/lisp/widget.el b/lisp/widget.el index 30d28180abb..baa9140b260 100644 --- a/lisp/widget.el +++ b/lisp/widget.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; diff --git a/lisp/windmove.el b/lisp/windmove.el index 0797ef8b9d9..f8de303fffb 100644 --- a/lisp/windmove.el +++ b/lisp/windmove.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; ;; -------------------------------------------------------------------- @@ -425,7 +425,7 @@ supplied, if ARG is greater or smaller than zero, respectively." top-left ;; Don't care whether window is horizontally scrolled - ;; `posn-at-point' handles that already. See also: - ;; http://lists.gnu.org/archive/html/emacs-devel/2012-01/msg00638.html + ;; https://lists.gnu.org/r/emacs-devel/2012-01/msg00638.html (posn-col-row (posn-at-point (window-point window) window)))))))) diff --git a/lisp/window.el b/lisp/window.el index 8b07ed462c9..b7736d85cd1 100644 --- a/lisp/window.el +++ b/lisp/window.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -320,22 +320,34 @@ WINDOW can be any window." (defun window-normalize-buffer (buffer-or-name) "Return buffer specified by BUFFER-OR-NAME. -BUFFER-OR-NAME must be either a buffer or a string naming a live -buffer and defaults to the current buffer." - (cond - ((not buffer-or-name) - (current-buffer)) - ((bufferp buffer-or-name) - (if (buffer-live-p buffer-or-name) - buffer-or-name - (error "Buffer %s is not a live buffer" buffer-or-name))) - ((get-buffer buffer-or-name)) - (t - (error "No such buffer %s" buffer-or-name)))) +BUFFER-OR-NAME must be a live buffer, a string naming a live +buffer or nil which means to return the current buffer. + +This function is commonly used to process the (usually optional) +\"BUFFER-OR-NAME\" argument of window related functions where nil +stands for the current buffer." + (let ((buffer + (cond + ((not buffer-or-name) + (current-buffer)) + ((bufferp buffer-or-name) + buffer-or-name) + ((stringp buffer-or-name) + (get-buffer buffer-or-name)) + (t + (error "No such buffer %s" buffer-or-name))))) + (if (buffer-live-p buffer) + buffer + (error "No such live buffer %s" buffer-or-name)))) (defun window-normalize-frame (frame) "Return frame specified by FRAME. -FRAME must be a live frame and defaults to the selected frame." +FRAME must be a live frame or nil which means to return the +selected frame. + +This function is commonly used to process the (usually optional) +\"FRAME\" argument of window and frame related functions where +nil stands for the selected frame." (if frame (if (frame-live-p frame) frame @@ -343,11 +355,15 @@ FRAME must be a live frame and defaults to the selected frame." (selected-frame))) (defun window-normalize-window (window &optional live-only) - "Return the window specified by WINDOW. + "Return window specified by WINDOW. If WINDOW is nil, return the selected window. Otherwise, if WINDOW is a live or an internal window, return WINDOW; if LIVE-ONLY is non-nil, return WINDOW for a live window only. -Otherwise, signal an error." +Otherwise, signal an error. + +This function is commonly used to process the (usually optional) +\"WINDOW\" argument of window related functions where nil stands +for the selected window." (cond ((null window) (selected-window)) @@ -999,7 +1015,7 @@ for displaying BUFFER, nil if no suitable window can be found. This function installs the `window-side' and `window-slot' parameters and makes them persistent. It neither modifies ALIST nor installs any other window parameters unless they have been -explicitly provided via a `window-parameter' entry in ALIST." +explicitly provided via a `window-parameters' entry in ALIST." (let* ((side (or (cdr (assq 'side alist)) 'bottom)) (slot (or (cdr (assq 'slot alist)) 0)) (left-or-right (memq side '(left right))) @@ -2567,7 +2583,7 @@ and no others." (defun minibuffer-window-active-p (window) "Return t if WINDOW is the currently active minibuffer window." - (eq window (active-minibuffer-window))) + (and (window-live-p window) (eq window (active-minibuffer-window)))) (defun count-windows (&optional minibuf) "Return the number of live windows on the selected frame. @@ -3703,7 +3719,7 @@ are one more than the actual value of these edges. Note that if ABSOLUTE is non-nil, PIXELWISE is implicitly non-nil too." (let* ((window (window-normalize-window window body)) (frame (window-frame window)) - (border-width (frame-border-width frame)) + (border-width (frame-internal-border-width frame)) (char-width (frame-char-width frame)) (char-height (frame-char-height frame)) (left (if pixelwise @@ -4096,17 +4112,17 @@ WINDOW must be a valid window and defaults to the selected one. Return nil. If the variable `ignore-window-parameters' is non-nil or the -`delete-other-windows' parameter of WINDOW equals t, do not -process any parameters of WINDOW. Otherwise, if the +`delete-other-windows' parameter of WINDOW equals t, do not pay +attention to any other parameters of WINDOW. Otherwise, if the `delete-other-windows' parameter of WINDOW specifies a function, call that function with WINDOW as its sole argument and return the value returned by that function. -Otherwise, if WINDOW is part of an atomic window, call this -function with the root of the atomic window as its argument. If -WINDOW is a non-side window, make WINDOW the only non-side window -on the frame. Side windows are not deleted. If WINDOW is a side -window signal an error." +Else, if WINDOW is part of an atomic window, call this function +with the root of the atomic window as its argument. Signal an +error if that root window is the root window of WINDOW's frame. +Also signal an error if WINDOW is a side window. Do not delete +any window whose `no-delete-other-windows' parameter is non-nil." (interactive) (setq window (window-normalize-window window)) (let* ((frame (window-frame window)) @@ -4137,28 +4153,28 @@ window signal an error." (cond ((or ignore-window-parameters - (not (window-with-parameter 'no-delete-other-window nil frame))) + (not (window-with-parameter 'no-delete-other-windows nil frame))) (setq main (frame-root-window frame))) ((catch 'tag (walk-window-tree (lambda (other) (when (or (and (window-parameter other 'window-side) (not (window-parameter - other 'no-delete-other-window))) + other 'no-delete-other-windows))) (and (not (window-parameter other 'window-side)) (window-parameter - other 'no-delete-other-window))) + other 'no-delete-other-windows))) (throw 'tag nil)))) t) (setq main (window-main-window frame))) (t - ;; Delete other windows via `delete-window' because either a - ;; side window is or a non-side-window is not deletable. + ;; Delete windows via `delete-window' because we found either a + ;; deletable side window or a non-deletable non-side-window. (dolist (other (window-list frame)) (when (and (window-live-p other) (not (eq other window)) (not (window-parameter - other 'no-delete-other-window)) + other 'no-delete-other-windows)) ;; When WINDOW and the other window are part of the ;; same atomic window, don't delete the other. (or (not atom-root) @@ -4572,12 +4588,13 @@ The function is called with one argument - a frame. Functions affected by this option are those that bury a buffer shown in a separate frame like `quit-window' and `bury-buffer'." :type '(choice (const :tag "Iconify" iconify-frame) + (const :tag "Make invisible" make-frame-invisible) (const :tag "Delete" delete-frame) (const :tag "Do nothing" ignore) function) :group 'windows :group 'frames - :version "24.1") + :version "26.1") (defun window--delete (&optional window dedicated-only kill) "Delete WINDOW if possible. @@ -4595,7 +4612,9 @@ if WINDOW gets deleted or its frame is auto-hidden." (cond (kill (delete-frame frame)) - ((functionp frame-auto-hide-function) + ((functionp (frame-parameter frame 'auto-hide-function)) + (funcall (frame-parameter frame 'auto-hide-function))) + ((functionp frame-auto-hide-function) (funcall frame-auto-hide-function frame)))) 'frame) (deletable @@ -6446,8 +6465,9 @@ If this is an integer, `split-window-sensibly' may split a window vertically only if it has at least this many lines. If this is nil, `split-window-sensibly' is not allowed to split a window vertically. If, however, a window is the only window on its -frame, `split-window-sensibly' may split it vertically -disregarding the value of this variable." +frame, or all the other ones are dedicated, +`split-window-sensibly' may split it vertically disregarding the +value of this variable." :type '(choice (const nil) (integer :tag "lines")) :version "23.1" :group 'windows) @@ -6554,15 +6574,27 @@ split." ;; Split window horizontally. (with-selected-window window (split-window-right))) - (and (eq window (frame-root-window (window-frame window))) - (not (window-minibuffer-p window)) - ;; If WINDOW is the only window on its frame and is not the - ;; minibuffer window, try to split it vertically disregarding - ;; the value of `split-height-threshold'. - (let ((split-height-threshold 0)) - (when (window-splittable-p window) - (with-selected-window window - (split-window-below)))))))) + (and + ;; If WINDOW is the only usable window on its frame (it is + ;; the only one or, not being the only one, all the other + ;; ones are dedicated) and is not the minibuffer window, try + ;; to split it vertically disregarding the value of + ;; `split-height-threshold'. + (let ((frame (window-frame window))) + (or + (eq window (frame-root-window frame)) + (catch 'done + (walk-window-tree (lambda (w) + (unless (or (eq w window) + (window-dedicated-p w)) + (throw 'done nil))) + frame) + t))) + (not (window-minibuffer-p window)) + (let ((split-height-threshold 0)) + (when (window-splittable-p window) + (with-selected-window window + (split-window-below)))))))) (defun window--try-to-split-window (window &optional alist) "Try to split WINDOW. @@ -6734,15 +6766,17 @@ live." window)) (defun window--maybe-raise-frame (frame) - (let ((visible (frame-visible-p frame))) - (unless (or (not visible) - ;; Assume the selected frame is already visible enough. - (eq frame (selected-frame)) - ;; Assume the frame from which we invoked the - ;; minibuffer is visible. - (and (minibuffer-window-active-p (selected-window)) - (eq frame (window-frame (minibuffer-selected-window))))) - (raise-frame frame)))) + (make-frame-visible frame) + (unless (or (frame-parameter frame 'no-focus-on-map) + ;; Don't raise frames that should not get focus. + (frame-parameter frame 'no-accept-focus) + ;; Assume the selected frame is already visible enough. + (eq frame (selected-frame)) + ;; Assume the frame from which we invoked the + ;; minibuffer is visible. + (and (minibuffer-window-active-p (selected-window)) + (eq frame (window-frame (minibuffer-selected-window))))) + (raise-frame frame))) ;; FIXME: Not implemented. ;; FIXME: By the way, there could be more levels of dedication: @@ -6762,6 +6796,7 @@ The actual non-nil value of this variable will be copied to the (const display-buffer-pop-up-window) (const display-buffer-same-window) (const display-buffer-pop-up-frame) + (const display-buffer-in-child-frame) (const display-buffer-below-selected) (const display-buffer-at-bottom) (const display-buffer-in-previous-window) @@ -6908,6 +6943,7 @@ Available action functions include: `display-buffer-same-window' `display-buffer-reuse-window' `display-buffer-pop-up-frame' + `display-buffer-in-child-frame' `display-buffer-pop-up-window' `display-buffer-in-previous-window' `display-buffer-use-some-window' @@ -7185,9 +7221,9 @@ See `display-buffer' for the format of display actions." (let ((pars (special-display-p (buffer-name buffer)))) (when pars (list (list #'display-buffer-reuse-window - `(lambda (buffer _alist) - (funcall special-display-function - buffer ',(if (listp pars) pars))))))))) + (lambda (buffer _alist) + (funcall special-display-function + buffer (if (listp pars) pars))))))))) (defun display-buffer-pop-up-frame (buffer alist) "Display BUFFER in a new frame. @@ -7239,6 +7275,7 @@ raising the frame." (get-largest-window frame t) alist) (window--try-to-split-window (get-lru-window frame t) alist)))) + (prog1 (window--display-buffer buffer window 'window alist display-buffer-mark-dedicated) (unless (cdr (assq 'inhibit-switch-frame alist)) @@ -7258,6 +7295,47 @@ again with `display-buffer-pop-up-window'." (and pop-up-windows (display-buffer-pop-up-window buffer alist)))) +(defun display-buffer-in-child-frame (buffer alist) + "Display BUFFER in a child frame. +By default, this either reuses a child frame of the selected +frame or makes a new child frame of the selected frame. If +successful, return the window used; otherwise return nil. + +If ALIST has a non-nil 'child-frame-parameters' entry, the +corresponding value is an alist of frame parameters to give the +new frame. A 'parent-frame' parameter specifying the selected +frame is provided by default. If the child frame should be or +become the child of any other frame, a corresponding entry must +be added to ALIST." + (let* ((parameters + (append + (cdr (assq 'child-frame-parameters alist)) + `((parent-frame . ,(selected-frame))))) + (parent (or (assq 'parent-frame parameters) + (selected-frame))) + (share (assq 'share-child-frame parameters)) + share1 frame window) + (with-current-buffer buffer + (when (frame-live-p parent) + (catch 'frame + (dolist (frame1 (frame-list)) + (when (eq (frame-parent frame1) parent) + (setq share1 (assq 'share-child-frame + (frame-parameters frame1))) + (when (eq share share1) + (setq frame frame1) + (throw 'frame t)))))) + + (if frame + (setq window (frame-selected-window frame)) + (setq frame (make-frame parameters)) + (setq window (frame-selected-window frame)))) + + (prog1 (window--display-buffer + buffer window 'frame alist display-buffer-mark-dedicated) + (unless (cdr (assq 'inhibit-switch-frame alist)) + (window--maybe-raise-frame frame))))) + (defun display-buffer-below-selected (buffer alist) "Try displaying BUFFER in a window below the selected window. If there is a window below the selected one and that window @@ -7272,7 +7350,8 @@ below the selected one, use that window." (and (not (frame-parameter nil 'unsplittable)) (let ((split-height-threshold 0) split-width-threshold) - (setq window (window--try-to-split-window (selected-window) alist))) + (setq window (window--try-to-split-window + (selected-window) alist))) (window--display-buffer buffer window 'window alist display-buffer-mark-dedicated)) (and (setq window (window-in-direction 'below)) @@ -7576,10 +7655,11 @@ another window. In interactive use, if the selected window is strongly dedicated to its buffer, the value of the option `switch-to-buffer-in-dedicated-window' specifies how to proceed. -If called interactively, read the buffer name using the -minibuffer. The variable `confirm-nonexistent-file-or-buffer' -determines whether to request confirmation before creating a new -buffer. +If called interactively, read the buffer name using `read-buffer'. +The variable `confirm-nonexistent-file-or-buffer' determines +whether to request confirmation before creating a new buffer. +See `read-buffer' for features related to input and completion +of buffer names. BUFFER-OR-NAME may be a buffer, a string (a buffer name), or nil. If BUFFER-OR-NAME is a string that does not identify an existing @@ -7656,10 +7736,11 @@ Return the buffer switched to." BUFFER-OR-NAME may be a buffer, a string (a buffer name), or nil. Return the buffer switched to. -If called interactively, prompt for the buffer name using the -minibuffer. The variable `confirm-nonexistent-file-or-buffer' -determines whether to request confirmation before creating a new -buffer. +If called interactively, read the buffer name using `read-buffer'. +The variable `confirm-nonexistent-file-or-buffer' determines +whether to request confirmation before creating a new buffer. +See `read-buffer' for features related to input and completion +of buffer names. If BUFFER-OR-NAME is a string and does not identify an existing buffer, create a new buffer with that name. If BUFFER-OR-NAME is @@ -7680,10 +7761,11 @@ documentation for additional customization information." BUFFER-OR-NAME may be a buffer, a string (a buffer name), or nil. Return the buffer switched to. -If called interactively, prompt for the buffer name using the -minibuffer. The variable `confirm-nonexistent-file-or-buffer' -determines whether to request confirmation before creating a new -buffer. +If called interactively, read the buffer name using `read-buffer'. +The variable `confirm-nonexistent-file-or-buffer' determines +whether to request confirmation before creating a new buffer. +See `read-buffer' for features related to input and completion +of buffer names. If BUFFER-OR-NAME is a string and does not identify an existing buffer, create a new buffer with that name. If BUFFER-OR-NAME is @@ -7885,10 +7967,12 @@ See also `fit-frame-to-buffer-margins'." (declare-function x-display-pixel-height "xfns.c" (&optional terminal)) (defun window--sanitize-margin (margin left right) - "Return MARGIN if it's a number between LEFT and RIGHT." - (when (and (numberp margin) - (<= left (- right margin)) (<= margin right)) - margin)) + "Return MARGIN if it's a number between LEFT and RIGHT. +Return 0 otherwise." + (if (and (numberp margin) + (<= left (- right margin)) (<= margin right)) + margin + 0)) (declare-function tool-bar-height "xdisp.c" (&optional frame pixelwise)) @@ -7906,190 +7990,197 @@ horizontally only. The new position and size of FRAME can be additionally determined by customizing the options `fit-frame-to-buffer-sizes' and -`fit-frame-to-buffer-margins' or the corresponding parameters of -FRAME." +`fit-frame-to-buffer-margins' or setting the corresponding +parameters of FRAME." (interactive) - (unless (and (fboundp 'x-display-pixel-height) - ;; We need the respective sizes now. - (fboundp 'display-monitor-attributes-list)) + (unless (fboundp 'display-monitor-attributes-list) (user-error "Cannot resize frame in non-graphic Emacs")) (setq frame (window-normalize-frame frame)) (when (window-live-p (frame-root-window frame)) - (with-selected-window (frame-root-window frame) - (let* ((char-width (frame-char-width)) - (char-height (frame-char-height)) - (monitor-attributes (car (display-monitor-attributes-list - (frame-parameter frame 'display)))) - (geometry (cdr (assq 'geometry monitor-attributes))) - (display-width (- (nth 2 geometry) (nth 0 geometry))) - (display-height (- (nth 3 geometry) (nth 1 geometry))) - (workarea (cdr (assq 'workarea monitor-attributes))) - ;; Handle margins. - (margins (or (frame-parameter frame 'fit-frame-to-buffer-margins) - fit-frame-to-buffer-margins)) - (left-margin (if (nth 0 margins) - (or (window--sanitize-margin - (nth 0 margins) 0 display-width) - 0) - (nth 0 workarea))) - (top-margin (if (nth 1 margins) - (or (window--sanitize-margin - (nth 1 margins) 0 display-height) - 0) - (nth 1 workarea))) - (workarea-width (nth 2 workarea)) - (right-margin (if (nth 2 margins) - (- display-width - (or (window--sanitize-margin - (nth 2 margins) left-margin display-width) - 0)) - (nth 2 workarea))) - (workarea-height (nth 3 workarea)) - (bottom-margin (if (nth 3 margins) - (- display-height - (or (window--sanitize-margin - (nth 3 margins) top-margin display-height) - 0)) - (nth 3 workarea))) - ;; The pixel width of FRAME (which does not include the - ;; window manager's decorations). - (frame-width (frame-pixel-width)) - ;; The pixel width of the body of FRAME's root window. - (window-body-width (window-body-width nil t)) - ;; The difference in pixels between total and body width of - ;; FRAME's window. - (window-extra-width (- (window-pixel-width) window-body-width)) - ;; The difference in pixels between the frame's pixel width - ;; and the window's body width. This is the space we can't - ;; use for fitting. - (extra-width (- frame-width window-body-width)) - ;; The pixel position of FRAME's left border. We usually - ;; try to leave this alone. - (left - (let ((left (frame-parameter nil 'left))) - (if (consp left) - (funcall (car left) (cadr left)) - left))) - ;; The pixel height of FRAME (which does not include title - ;; line, decorations, and sometimes neither the menu nor - ;; the toolbar). - (frame-height (frame-pixel-height)) - ;; The pixel height of FRAME's root window (we don't care - ;; about the window's body height since the return value of - ;; `window-text-pixel-size' includes header and mode line). - (window-height (window-pixel-height)) - ;; The difference in pixels between the frame's pixel - ;; height and the window's height. - (extra-height (- frame-height window-height)) - ;; The pixel position of FRAME's top border. - (top - (let ((top (frame-parameter nil 'top))) - (if (consp top) - (funcall (car top) (cadr top)) - top))) - ;; Sanitize minimum and maximum sizes. - (sizes (or (frame-parameter frame 'fit-frame-to-buffer-sizes) - fit-frame-to-buffer-sizes)) - (max-height - (cond - ((numberp (nth 0 sizes)) (* (nth 0 sizes) char-height)) - ((numberp max-height) (* max-height char-height)) - (t display-height))) - (min-height - (cond - ((numberp (nth 1 sizes)) (* (nth 1 sizes) char-height)) - ((numberp min-height) (* min-height char-height)) - (t (* window-min-height char-height)))) - (max-width - (cond - ((numberp (nth 2 sizes)) - (- (* (nth 2 sizes) char-width) window-extra-width)) - ((numberp max-width) - (- (* max-width char-width) window-extra-width)) - (t display-width))) - (min-width - (cond - ((numberp (nth 3 sizes)) - (- (* (nth 3 sizes) char-width) window-extra-width)) - ((numberp min-width) - (- (* min-width char-width) window-extra-width)) - (t (* window-min-width char-width)))) - ;; Note: Currently, for a new frame the sizes of the header - ;; and mode line may be estimated incorrectly - (value (window-text-pixel-size - nil t t workarea-width workarea-height t)) - (width (+ (car value) (window-right-divider-width))) - (height - (+ (cdr value) - (window-bottom-divider-width) - (window-scroll-bar-height)))) - ;; Don't change height or width when the window's size is fixed - ;; in either direction or ONLY forbids it. - (cond - ((or (eq window-size-fixed 'width) (eq only 'vertically)) - (setq width nil)) - ((or (eq window-size-fixed 'height) (eq only 'horizontally)) - (setq height nil))) - ;; Fit width to constraints. - (when width - (unless frame-resize-pixelwise - ;; Round to character sizes. - (setq width (* (/ (+ width char-width -1) char-width) - char-width))) - ;; Fit to maximum and minimum widths. - (setq width (max (min width max-width) min-width)) - ;; Add extra width. - (setq width (+ width extra-width)) - ;; Preserve margins. - (let ((right (+ left width))) - (cond - ((> right right-margin) - ;; Move frame to left (we don't know its real width). - (setq left (max left-margin (- left (- right right-margin))))) - ((< left left-margin) - ;; Move frame to right. - (setq left left-margin))))) - ;; Fit height to constraints. - (when height - (unless frame-resize-pixelwise - (setq height (* (/ (+ height char-height -1) char-height) - char-height))) - ;; Fit to maximum and minimum heights. - (setq height (max (min height max-height) min-height)) - ;; Add extra height. - (setq height (+ height extra-height)) - ;; Preserve margins. - (let ((bottom (+ top height))) - (cond - ((> bottom bottom-margin) - ;; Move frame up (we don't know its real height). - (setq top (max top-margin (- top (- bottom bottom-margin))))) - ((< top top-margin) - ;; Move frame down. - (setq top top-margin))))) - ;; Apply changes. - (set-frame-position frame left top) - ;; Clumsily try to translate our calculations to what - ;; `set-frame-size' wants. - (when width - (setq width (- (+ (frame-text-width) width) - extra-width window-body-width))) - (when height - (setq height (- (+ (frame-text-height) height) - extra-height window-height))) - (set-frame-size - frame - (if width - (if frame-resize-pixelwise - width - (/ width char-width)) - (frame-text-width)) - (if height - (if frame-resize-pixelwise - height - (/ height char-height)) - (frame-text-height)) - frame-resize-pixelwise))))) + (let* ((char-width (frame-char-width frame)) + (char-height (frame-char-height frame)) + ;; WINDOW is FRAME's root window. + (window (frame-root-window frame)) + (parent (frame-parent frame)) + (monitor-attributes + (unless parent + (car (display-monitor-attributes-list + (frame-parameter frame 'display))))) + ;; FRAME'S parent or display sizes. Used in connection + ;; with margins. + (geometry + (unless parent + (cdr (assq 'geometry monitor-attributes)))) + (parent-or-display-width + (if parent + (frame-native-width parent) + (- (nth 2 geometry) (nth 0 geometry)))) + (parent-or-display-height + (if parent + (frame-native-height parent) + (- (nth 3 geometry) (nth 1 geometry)))) + ;; FRAME'S parent or workarea sizes. Used when no margins + ;; are specified. + (parent-or-workarea + (if parent + `(0 0 ,parent-or-display-width ,parent-or-display-height) + (cdr (assq 'workarea monitor-attributes)))) + ;; The outer size of FRAME. Needed to calculate the + ;; margins around the root window's body that have to + ;; remain untouched by fitting. + (outer-edges (frame-edges frame 'outer-edges)) + (outer-width (if outer-edges + (- (nth 2 outer-edges) (nth 0 outer-edges)) + ;; A poor guess. + (frame-pixel-width frame))) + (outer-height (if outer-edges + (- (nth 3 outer-edges) (nth 1 outer-edges)) + ;; Another poor guess. + (frame-pixel-height frame))) + ;; The text size of FRAME. Needed to specify FRAME's + ;; text size after the root window's body's new sizes have + ;; been calculated. + (text-width (frame-text-width frame)) + (text-height (frame-text-height frame)) + ;; WINDOW's body size. + (body-width (window-body-width window t)) + (body-height (window-body-height window t)) + ;; The difference between FRAME's outer size and WINDOW's + ;; body size. + (outer-minus-body-width (- outer-width body-width)) + (outer-minus-body-height (- outer-height body-height)) + ;; The difference between FRAME's text size and WINDOW's + ;; body size (these values "should" be positive). + (text-minus-body-width (- text-width body-width)) + (text-minus-body-height (- text-height body-height)) + ;; The current position of FRAME. + (position (frame-position frame)) + (left (car position)) + (top (cdr position)) + ;; The margins specified for FRAME. These represent pixel + ;; offsets from the left, top, right and bottom edge of the + ;; display or FRAME's parent's native rectangle and have to + ;; take care of the display's taskbar and other obstacles. + ;; If they are unspecified, constrain the resulting frame + ;; to its workarea or the parent frame's native rectangle. + (margins (or (frame-parameter frame 'fit-frame-to-buffer-margins) + fit-frame-to-buffer-margins)) + ;; Convert margins into pixel offsets from the left-top + ;; corner of FRAME's display or parent. + (left-margin (if (nth 0 margins) + (window--sanitize-margin + (nth 0 margins) 0 parent-or-display-width) + (nth 0 parent-or-workarea))) + (top-margin (if (nth 1 margins) + (window--sanitize-margin + (nth 1 margins) 0 parent-or-display-height) + (nth 1 parent-or-workarea))) + (right-margin (if (nth 2 margins) + (- parent-or-display-width + (window--sanitize-margin + (nth 2 margins) left-margin + parent-or-display-width)) + (nth 2 parent-or-workarea))) + (bottom-margin (if (nth 3 margins) + (- parent-or-display-height + (window--sanitize-margin + (nth 3 margins) top-margin + parent-or-display-height)) + (nth 3 parent-or-workarea))) + ;; Minimum and maximum sizes specified for FRAME. + (sizes (or (frame-parameter frame 'fit-frame-to-buffer-sizes) + fit-frame-to-buffer-sizes)) + ;; Calculate the minimum and maximum pixel sizes of FRAME + ;; from the values provided by the MAX-HEIGHT, MIN-HEIGHT, + ;; MAX-WIDTH and MIN-WIDTH arguments or, if these are nil, + ;; from those provided by `fit-frame-to-buffer-sizes'. + (max-height + (min + (cond + ((numberp max-height) (* max-height char-height)) + ((numberp (nth 0 sizes)) (* (nth 0 sizes) char-height)) + (t parent-or-display-height)) + ;; The following is the maximum height that fits into the + ;; top and bottom margins. + (max (- bottom-margin top-margin outer-minus-body-height)))) + (min-height + (cond + ((numberp min-height) (* min-height char-height)) + ((numberp (nth 1 sizes)) (* (nth 1 sizes) char-height)) + (t (window-min-size window nil nil t)))) + (max-width + (min + (cond + ((numberp max-width) (* max-width char-width)) + ((numberp (nth 2 sizes)) (* (nth 2 sizes) char-width)) + (t parent-or-display-width)) + ;; The following is the maximum width that fits into the + ;; left and right margins. + (max (- right-margin left-margin outer-minus-body-width)))) + (min-width + (cond + ((numberp min-width) (* min-width char-width)) + ((numberp (nth 3 sizes)) (nth 3 sizes)) + (t (window-min-size window t nil t)))) + ;; Note: Currently, for a new frame the sizes of the header + ;; and mode line may be estimated incorrectly + (size + (window-text-pixel-size window t t max-width max-height)) + (width (max (car size) min-width)) + (height (max (cdr size) min-height))) + ;; Don't change height or width when the window's size is fixed + ;; in either direction or ONLY forbids it. + (cond + ((or (eq window-size-fixed 'width) (eq only 'vertically)) + (setq width nil)) + ((or (eq window-size-fixed 'height) (eq only 'horizontally)) + (setq height nil))) + ;; Fit width to constraints. + (when width + (unless frame-resize-pixelwise + ;; Round to character sizes. + (setq width (* (/ (+ width char-width -1) char-width) + char-width))) + ;; The new outer width (in pixels). + (setq outer-width (+ width outer-minus-body-width)) + ;; Maybe move FRAME to preserve margins. + (let ((right (+ left outer-width))) + (cond + ((> right right-margin) + ;; Move frame to left. + (setq left (max left-margin (- left (- right right-margin))))) + ((< left left-margin) + ;; Move frame to right. + (setq left left-margin))))) + ;; Fit height to constraints. + (when height + (unless frame-resize-pixelwise + (setq height (* (/ (+ height char-height -1) char-height) + char-height))) + ;; The new outer height. + (setq outer-height (+ height outer-minus-body-height)) + ;; Preserve margins. + (let ((bottom (+ top outer-height))) + (cond + ((> bottom bottom-margin) + ;; Move frame up. + (setq top (max top-margin (- top (- bottom bottom-margin))))) + ((< top top-margin) + ;; Move frame down. + (setq top top-margin))))) + ;; Apply our changes. + (setq text-width + (if width + (+ width text-minus-body-width) + (frame-text-width frame))) + (setq text-height + (if height + (+ height text-minus-body-height) + (frame-text-height frame))) + (modify-frame-parameters + frame `((left . ,left) (top . ,top) + (width . (text-pixels . ,text-width)) + (height . (text-pixels . ,text-height))))))) (defun fit-window-to-buffer (&optional window max-height min-height max-width min-width preserve-size) "Adjust size of WINDOW to display its buffer's contents exactly. @@ -8286,6 +8377,168 @@ Return non-nil if the window was shrunk, nil otherwise." (when (and (window-combined-p window) (pos-visible-in-window-p (point-min) window)) (fit-window-to-buffer window (window-total-height window)))) + +(defun window-largest-empty-rectangle--maximums-1 (quad maximums) + "Support function for `window-largest-empty-rectangle'." + (cond + ((null maximums) + (list quad)) + ((> (car quad) (caar maximums)) + (cons quad maximums)) + (t + (cons (car maximums) + (window-largest-empty-rectangle--maximums-1 quad (cdr maximums)))))) + +(defun window-largest-empty-rectangle--maximums (quad maximums count) + "Support function for `window-largest-empty-rectangle'." + (setq maximums (window-largest-empty-rectangle--maximums-1 quad maximums)) + (if (> (length maximums) count) + (nbutlast maximums) + maximums)) + +(defun window-largest-empty-rectangle--disjoint-maximums (maximums count) + "Support function for `window-largest-empty-rectangle'." + (setq maximums (sort maximums (lambda (x y) (> (car x) (car y))))) + (let ((new-length 0) + new-maximums) + (while (and maximums (< new-length count)) + (let* ((maximum (car maximums)) + (at (nth 2 maximum)) + (to (nth 3 maximum))) + (catch 'drop + (dolist (new-maximum new-maximums) + (let ((new-at (nth 2 new-maximum)) + (new-to (nth 3 new-maximum))) + (when (if (< at new-at) (> to new-at) (< at new-to)) + ;; Intersection -> drop. + (throw 'drop nil)))) + (setq new-maximums (cons maximum new-maximums)) + (setq new-length (1+ new-length))) + (setq maximums (cdr maximums)))) + + (nreverse new-maximums))) + +(defun window-largest-empty-rectangle (&optional window count min-width min-height positions left) + "Return dimensions of largest empty rectangle in WINDOW. +WINDOW must be a live window and defaults to the selected one. + +The return value is a triple of the width and the start and end +Y-coordinates of the largest rectangle that can be inscribed into +the empty space (the space not displaying any text) of WINDOW's +text area. The return value is nil if the current glyph matrix +of WINDOW is not up-to-date. + +Optional argument COUNT, if non-nil, specifies the maximum number +of rectangles to return. This means that the return value is a +list of triples specifying rectangles with the largest rectangle +first. COUNT can be also a cons cell whose car specifies the +number of rectangles to return and whose cdr, if non-nil, states +that all rectangles returned must be disjoint. + +Note that the right edge of any rectangle returned by this +function is the right edge of WINDOW (the left edge if its buffer +displays RTL text). + +Optional arguments MIN-WIDTH and MIN-HEIGHT, if non-nil, specify +the minimum width and height of any rectangle returned. + +Optional argument POSITIONS, if non-nil, is a cons cell whose car +specifies the uppermost and whose cdr specifies the lowermost +pixel position that must be covered by any rectangle returned. +Note that positions are counted from the start of the text area +of WINDOW. + +Optional argument LEFT, if non-nil, means to return values suitable for +buffers displaying right to left text." + ;; Process lines as returned by ‘window-lines-pixel-dimensions’. + ;; STACK is a stack that contains rows that have to be processed yet. + (let* ((window (window-normalize-window window t)) + (disjoint (and (consp count) (cdr count))) + (count (or (and (numberp count) count) + (and (consp count) (numberp (car count)) (car count)))) + (rows (window-lines-pixel-dimensions window nil nil t t left)) + (rows-at 0) + (max-size 0) + row stack stack-at stack-to + top top-width top-at top-to top-size + max-width max-at max-to maximums) + ;; ROWS-AT is the position where the first element of ROWS starts. + ;; STACK-AT is the position where the first element of STACK starts. + (while rows + (setq row (car rows)) + (if (or (not stack) (>= (car row) (caar stack))) + (progn + (unless stack + (setq stack-at rows-at)) + (setq stack (cons row stack)) + ;; Set ROWS-AT to where the first element of ROWS ends + ;; which, after popping ROW, makes it the start position of + ;; the next ROW. + (setq rows-at (cdr row)) + (setq rows (cdr rows))) + (setq top (car stack)) + (setq stack (cdr stack)) + (setq top-width (car top)) + (setq top-at (if stack (cdar stack) stack-at)) + (setq top-to (cdr top)) + (setq top-size (* top-width (- top-to top-at))) + (unless (or (and min-width (< top-width min-width)) + (and min-height (< (- top-to top-at) min-height)) + (and positions + (or (> top-at (car positions)) + (< top-to (cdr positions))))) + (if count + (if disjoint + (setq maximums (cons (list top-size top-width top-at top-to) + maximums)) + (setq maximums (window-largest-empty-rectangle--maximums + (list top-size top-width top-at top-to) + maximums count))) + (when (> top-size max-size) + (setq max-size top-size) + (setq max-width top-width) + (setq max-at top-at) + (setq max-to top-to)))) + (if (and stack (> (caar stack) (car row))) + ;; Have new top element of stack include old top. + (setq stack (cons (cons (caar stack) (cdr top)) (cdr stack))) + ;; Move rows-at backwards to top-at. + (setq rows-at top-at)))) + + (when stack + ;; STACK-TO is the position where the stack ends. + (setq stack-to (cdar stack)) + (while stack + (setq top (car stack)) + (setq stack (cdr stack)) + (setq top-width (car top)) + (setq top-at (if stack (cdar stack) stack-at)) + (setq top-size (* top-width (- stack-to top-at))) + (unless (or (and min-width (< top-width min-width)) + (and min-height (< (- stack-to top-at) min-height)) + (and positions + (or (> top-at (car positions)) + (< stack-to (cdr positions))))) + (if count + (if disjoint + (setq maximums (cons (list top-size top-width top-at stack-to) + maximums)) + (setq maximums (window-largest-empty-rectangle--maximums + (list top-size top-width top-at stack-to) + maximums count))) + (when (> top-size max-size) + (setq max-size top-size) + (setq max-width top-width) + (setq max-at top-at) + (setq max-to stack-to)))))) + + (cond + (maximums + (if disjoint + (window-largest-empty-rectangle--disjoint-maximums maximums count) + maximums)) + ((> max-size 0) + (list max-width max-at max-to))))) (defun kill-buffer-and-window () "Kill the current buffer and delete the selected window." @@ -8441,7 +8694,7 @@ result is a list containing only the selected window." (make-variable-buffer-local 'move-to-window-group-line-function) (put 'move-to-window-group-line-function 'permanent-local t) (defun move-to-window-group-line (arg) - "Position point relative to the the current group of windows. + "Position point relative to the current group of windows. When a grouping mode (such as Follow Mode) is not active, this function is identical to `move-to-window-line'. diff --git a/lisp/winner.el b/lisp/winner.el index 7b0483338b9..6bc27484a79 100644 --- a/lisp/winner.el +++ b/lisp/winner.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -304,12 +304,15 @@ You may want to include buffer names such as *Help*, *Apropos*, (push win xwins))) ; delete this window ;; Restore marks - (save-current-buffer - (cl-loop for buf in buffers - for entry = (cadr (assq buf winner-point-alist)) - do (progn (set-buffer buf) - (set-mark (car entry)) - (setf (winner-active-region) (cdr entry))))) + ;; `winner-undo' shouldn't update the selection (Bug#28631) when + ;; select-enable-primary is non-nil. + (unless select-enable-primary + (save-current-buffer + (cl-loop for buf in buffers + for entry = (cadr (assq buf winner-point-alist)) + do (progn (set-buffer buf) + (set-mark (car entry)) + (setf (winner-active-region) (cdr entry)))))) ;; Delete windows, whose buffers are dead or boring. ;; Return t if this is still a possible configuration. (or (null xwins) diff --git a/lisp/woman.el b/lisp/woman.el index aa856c39577..1edf6e34c35 100644 --- a/lisp/woman.el +++ b/lisp/woman.el @@ -22,7 +22,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -1619,7 +1619,7 @@ decompress the file if appropriate. See the documentation for the (setq woman-buffer-alist (cons (cons file-name bufname) woman-buffer-alist) woman-buffer-number 0))))) - (Man-build-section-alist) + (Man-build-section-list) (Man-build-references-alist) (goto-char (point-min))) @@ -4261,22 +4261,11 @@ Delete line from point and eol unless LEAVE-EOL is non-nil." (if (> i 0) (setq woman-prevailing-indent i)))) woman-prevailing-indent) -(defmacro woman-push (value stack) - "Push VALUE onto STACK." - `(setq ,stack (cons ,value ,stack))) - -(defmacro woman-pop (variable stack) - "Pop into VARIABLE the value at the top of STACK. -Allow for mismatched requests!" - `(if ,stack - (setq ,variable (car ,stack) - ,stack (cdr ,stack)))) - (defun woman2-RS (to) ".RS i -- Start relative indent, move left margin in distance i. Set prevailing indent to 5 for nested indents. Format paragraphs upto TO." - (woman-push woman-left-margin woman-RS-left-margin) - (woman-push woman-prevailing-indent woman-RS-prevailing-indent) + (push woman-left-margin woman-RS-left-margin) + (push woman-prevailing-indent woman-RS-prevailing-indent) (setq woman-left-margin (+ woman-left-margin (woman2-get-prevailing-indent)) woman-prevailing-indent woman-default-indent) @@ -4285,8 +4274,10 @@ Set prevailing indent to 5 for nested indents. Format paragraphs upto TO." (defun woman2-RE (to) ".RE -- End of relative indent. Format paragraphs upto TO. Set prevailing indent to amount of starting .RS." - (woman-pop woman-left-margin woman-RS-left-margin) - (woman-pop woman-prevailing-indent woman-RS-prevailing-indent) + (when woman-RS-left-margin + (setq woman-left-margin (pop woman-RS-left-margin))) + (when woman-RS-prevailing-indent + (setq woman-prevailing-indent (pop woman-RS-prevailing-indent))) (woman-delete-line 1) ; ignore any arguments (woman2-format-paragraphs to woman-left-margin)) diff --git a/lisp/x-dnd.el b/lisp/x-dnd.el index 80ec4101bdf..acbdcb9ee5c 100644 --- a/lisp/x-dnd.el +++ b/lisp/x-dnd.el @@ -20,7 +20,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/xdg.el b/lisp/xdg.el index 4973065f91a..9edc3d2629c 100644 --- a/lisp/xdg.el +++ b/lisp/xdg.el @@ -19,7 +19,7 @@ ;; General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -29,9 +29,14 @@ ;; - XDG Base Directory Specification ;; - Thumbnail Managing Standard ;; - xdg-user-dirs configuration +;; - Desktop Entry Specification ;;; Code: +(eval-when-compile + (require 'cl-lib) + (require 'subr-x)) + ;; XDG Base Directory Specification ;; https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html @@ -80,7 +85,7 @@ (defun xdg-thumb-uri (filename) "Return the canonical URI for FILENAME. -If FILENAME has absolute path /foo/bar.jpg, its canonical URI is +If FILENAME has absolute file name /foo/bar.jpg, its canonical URI is file:///foo/bar.jpg" (concat "file://" (expand-file-name filename))) @@ -89,8 +94,8 @@ file:///foo/bar.jpg" (concat (md5 (xdg-thumb-uri filename)) ".png")) (defun xdg-thumb-mtime (filename) - "Return modification time of FILENAME as integral seconds from the epoch." - (floor (float-time (nth 5 (file-attributes filename))))) + "Return modification time of FILENAME as an Emacs timestamp." + (file-attribute-modification-time (file-attributes filename))) ;; XDG User Directories @@ -128,17 +133,18 @@ This should be called at the beginning of a line." (defun xdg--user-dirs-parse-file (filename) "Return alist of xdg-user-dirs from FILENAME." (let (elt res) - (with-temp-buffer - (insert-file-contents filename) - (goto-char (point-min)) - (while (not (eobp)) - (setq elt (xdg--user-dirs-parse-line)) - (when (consp elt) (push elt res)) - (forward-line))) + (when (file-readable-p filename) + (with-temp-buffer + (insert-file-contents filename) + (goto-char (point-min)) + (while (not (eobp)) + (setq elt (xdg--user-dirs-parse-line)) + (when (consp elt) (push elt res)) + (forward-line)))) res)) (defun xdg-user-dir (name) - "Return the path of user directory referred to by NAME." + "Return the directory referred to by NAME." (when (null xdg-user-dirs) (setq xdg-user-dirs (xdg--user-dirs-parse-file @@ -146,6 +152,169 @@ This should be called at the beginning of a line." (let ((dir (cdr (assoc name xdg-user-dirs)))) (when dir (expand-file-name dir)))) + +;; Desktop Entry Specification +;; https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.1.html + +(defconst xdg-desktop-group-regexp + (rx "[" (group-n 1 (+? (in " -Z\\^-~"))) "]") + "Regexp matching desktop file group header names.") + +;; TODO Localized strings left out intentionally, as Emacs has no +;; notion of l10n/i18n +(defconst xdg-desktop-entry-regexp + (rx (group-n 1 (+ (in "A-Za-z0-9-"))) + ;; (? "[" (group-n 3 (+ nonl)) "]") + (* blank) "=" (* blank) + (group-n 2 (* nonl))) + "Regexp matching desktop file entry key-value pairs.") + +(defun xdg-desktop-read-group () + "Return hash table of group of desktop entries in the current buffer." + (let ((res (make-hash-table :test #'equal))) + (while (not (or (eobp) (looking-at xdg-desktop-group-regexp))) + (skip-chars-forward "[:blank:]") + (cond + ((eolp)) + ((= (following-char) ?#)) + ((looking-at xdg-desktop-entry-regexp) + (puthash (match-string 1) (match-string 2) res)) + ;; Filter localized strings + ((looking-at (rx (group-n 1 (+ (in alnum "-"))) (* blank) "["))) + (t (error "Malformed line: %s" + (buffer-substring (point) (point-at-eol))))) + (forward-line)) + res)) + +(defun xdg-desktop-read-file (filename &optional group) + "Return group contents of desktop file FILENAME as a hash table. +Optional argument GROUP defaults to the string \"Desktop Entry\"." + (with-temp-buffer + (insert-file-contents-literally filename) + (goto-char (point-min)) + (while (and (skip-chars-forward "[:blank:]" (line-end-position)) + (or (eolp) (= (following-char) ?#))) + (forward-line)) + (unless (looking-at xdg-desktop-group-regexp) + (error "Expected group name! Instead saw: %s" + (buffer-substring (point) (point-at-eol)))) + (when group + (while (and (re-search-forward xdg-desktop-group-regexp nil t) + (not (equal (match-string 1) group))))) + (forward-line) + (xdg-desktop-read-group))) + +(defun xdg-desktop-strings (value) + "Partition VALUE into elements delimited by unescaped semicolons." + (let (res) + (setq value (string-trim-left value)) + (dolist (x (split-string (replace-regexp-in-string "\\\\;" "\0" value) ";")) + (push (replace-regexp-in-string "\0" ";" x) res)) + (when (null (string-match-p "[^[:blank:]]" (car res))) (pop res)) + (nreverse res))) + + +;; MIME apps specification +;; https://standards.freedesktop.org/mime-apps-spec/mime-apps-spec-1.0.1.html + +(defvar xdg-mime-table nil + "Table of MIME type to desktop file associations. +The table is an alist with keys being MIME major types (\"application\", +\"audio\", etc.), and values being hash tables. Each hash table has +MIME subtypes as keys and lists of desktop file absolute filenames.") + +(defun xdg-mime-apps-files () + "Return a list of files containing MIME/Desktop associations. +The list is in order of descending priority: user config, then +admin config, and finally system cached associations." + (let ((xdg-data-dirs (xdg-data-dirs)) + (desktop (getenv "XDG_CURRENT_DESKTOP")) + res) + (when desktop + (setq desktop (format "%s-mimeapps.list" desktop))) + (dolist (name (cons "mimeapps.list" desktop)) + (push (expand-file-name name (xdg-config-home)) res) + (push (expand-file-name (format "applications/%s" name) (xdg-data-home)) + res) + (dolist (dir (xdg-config-dirs)) + (push (expand-file-name name dir) res)) + (dolist (dir xdg-data-dirs) + (push (expand-file-name (format "applications/%s" name) dir) res))) + (dolist (dir xdg-data-dirs) + (push (expand-file-name "applications/mimeinfo.cache" dir) res)) + (nreverse res))) + +(defun xdg-mime-collect-associations (mime files) + "Return a list of desktop file names associated with MIME. +The associations are searched in the list of file names FILES, +which is expected to be ordered by priority as in +`xdg-mime-apps-files'." + (let ((regexp (concat (regexp-quote mime) "=\\([^[:cntrl:]]*\\)$")) + res sec defaults added removed cached) + (with-temp-buffer + (dolist (f (reverse files)) + (when (file-readable-p f) + (insert-file-contents-literally f nil nil nil t) + (goto-char (point-min)) + (let (end) + (while (not (or (eobp) end)) + (if (= (following-char) ?\[) + (progn (setq sec (char-after (1+ (point)))) + (forward-line)) + (if (not (looking-at regexp)) + (forward-line) + (dolist (str (xdg-desktop-strings (match-string 1))) + (cl-pushnew str + (cond ((eq sec ?D) defaults) + ((eq sec ?A) added) + ((eq sec ?R) removed) + ((eq sec ?M) cached)) + :test #'equal)) + (while (and (zerop (forward-line)) + (/= (following-char) ?\[))))))) + ;; Accumulate results into res + (dolist (f cached) + (when (not (member f removed)) (cl-pushnew f res :test #'equal))) + (dolist (f added) + (when (not (member f removed)) (push f res))) + (dolist (f removed) + (setq res (delete f res))) + (dolist (f defaults) + (push f res)) + (setq defaults nil added nil removed nil cached nil)))) + (delete-dups res))) + +(defun xdg-mime-apps (mime) + "Return list of desktop files associated with MIME, otherwise nil. +The list is in order of descending priority, and each element is +an absolute file name of a readable file. +Results are cached in `xdg-mime-table'." + (pcase-let ((`(,type ,subtype) (split-string mime "/")) + (xdg-data-dirs (xdg-data-dirs)) + (caches (xdg-mime-apps-files)) + (files ())) + (let ((mtim1 (get 'xdg-mime-table 'mtime)) + (mtim2 (cl-loop for f in caches when (file-readable-p f) + maximize (float-time (nth 5 (file-attributes f)))))) + ;; If one of the MIME/Desktop cache files has been modified: + (when (or (null mtim1) (time-less-p mtim1 mtim2)) + (setq xdg-mime-table nil))) + (when (null (assoc type xdg-mime-table)) + (push (cons type (make-hash-table :test #'equal)) xdg-mime-table)) + (if (let ((def (make-symbol "def")) + (table (cdr (assoc type xdg-mime-table)))) + (not (eq (setq files (gethash subtype table def)) def))) + files + (and files (setq files nil)) + (let ((dirs (mapcar (lambda (dir) (expand-file-name "applications" dir)) + (cons (xdg-data-home) xdg-data-dirs)))) + ;; Not being particular about desktop IDs + (dolist (f (nreverse (xdg-mime-collect-associations mime caches))) + (push (locate-file f dirs) files)) + (when files + (put 'xdg-mime-table 'mtime (current-time))) + (puthash subtype (delq nil files) (cdr (assoc type xdg-mime-table))))))) + (provide 'xdg) ;;; xdg.el ends here diff --git a/lisp/xml.el b/lisp/xml.el index 88dc70bc413..36880886938 100644 --- a/lisp/xml.el +++ b/lisp/xml.el @@ -19,7 +19,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/xt-mouse.el b/lisp/xt-mouse.el index acb30187a8e..d704cfa4e8f 100644 --- a/lisp/xt-mouse.el +++ b/lisp/xt-mouse.el @@ -18,7 +18,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: @@ -278,6 +278,8 @@ which is the \"1006\" extension implemented in Xterm >= 277." (last-name (symbol-name last-type)) (last-time (nth 1 last-click)) (click-count (nth 2 last-click)) + (last-x (nth 3 last-click)) + (last-y (nth 4 last-click)) (this-time (float-time)) (name (symbol-name type))) (cond @@ -288,14 +290,20 @@ which is the \"1006\" extension implemented in Xterm >= 277." (string-match "down-" last-name) (equal name (replace-match "" t t last-name))) (xterm-mouse--set-click-count event click-count))) - ((not last-time) nil) - ((and (> double-click-time (* 1000 (- this-time last-time))) + ((and last-time + double-click-time + (or (eq double-click-time t) + (> double-click-time (* 1000 (- this-time last-time)))) + (<= (abs (- x last-x)) + (/ double-click-fuzz 8)) + (<= (abs (- y last-y)) + (/ double-click-fuzz 8)) (equal last-name (replace-match "" t t name))) (setq click-count (1+ click-count)) (xterm-mouse--set-click-count event click-count)) (t (setq click-count 1))) (set-terminal-parameter nil 'xterm-mouse-last-click - (list type this-time click-count))) + (list type this-time click-count x y))) (set-terminal-parameter nil 'xterm-mouse-x x) (set-terminal-parameter nil 'xterm-mouse-y y) diff --git a/lisp/xwidget.el b/lisp/xwidget.el index c908f1a5b0f..5e37209cc2e 100644 --- a/lisp/xwidget.el +++ b/lisp/xwidget.el @@ -17,7 +17,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;; ;; -------------------------------------------------------------------- |