diff --git a/lisp/async/async-bytecomp.el b/lisp/async/async-bytecomp.el index eadb5df6..a5d63709 100644 --- a/lisp/async/async-bytecomp.el +++ b/lisp/async/async-bytecomp.el @@ -59,39 +59,85 @@ all packages are always compiled asynchronously." (const :tag "All packages" all) (repeat symbol))) -(defvar async-byte-compile-log-file - (concat user-emacs-directory "async-bytecomp.log")) +(defvar async-byte-compile-log-file "async-bytecomp.log" + "Prefix for a file used to pass errors from async process to the caller. +The `file-name-nondirectory' part of the value is passed to +`make-temp-file' as a prefix. When the value is an absolute +path, then the `file-name-directory' part of it is expanded in +the calling process (with `expand-file-name') and used as a value +of variable `temporary-file-directory' in async processes.") (defvar async-bytecomp-load-variable-regexp "\\`load-path\\'" "The variable used by `async-inject-variables' when (re)compiling async.") -(defun async-bytecomp--file-to-comp-buffer (file-or-dir &optional quiet type) +(defun async-bytecomp--file-to-comp-buffer-1 (log-file &optional postproc) + (let ((buf (get-buffer-create byte-compile-log-buffer))) + (with-current-buffer buf + (goto-char (point-max)) + (let ((inhibit-read-only t)) + (insert-file-contents log-file) + (compilation-mode)) + (display-buffer buf) + (delete-file log-file) + (and postproc (funcall postproc))))) + +(defun async-bytecomp--file-to-comp-buffer (file-or-dir &optional quiet type log-file) (let ((bn (file-name-nondirectory (directory-file-name file-or-dir))) (action-name (pcase type ('file "File") ('directory "Directory")))) - (if (file-exists-p async-byte-compile-log-file) - (let ((buf (get-buffer-create byte-compile-log-buffer)) - (n 0)) - (with-current-buffer buf - (goto-char (point-max)) - (let ((inhibit-read-only t)) - (insert-file-contents async-byte-compile-log-file) - (compilation-mode)) - (display-buffer buf) - (delete-file async-byte-compile-log-file) - (unless quiet - (save-excursion - (goto-char (point-min)) - (while (re-search-forward "^.*:Error:" nil t) - (cl-incf n))) - (if (> n 0) - (message "Failed to compile %d files in directory `%s'" n bn) - (message "%s `%s' compiled asynchronously with warnings" - action-name bn))))) + (if (and log-file (file-exists-p log-file)) + (async-bytecomp--file-to-comp-buffer-1 + log-file + (unless quiet + (lambda () + (let ((n 0)) + (unless quiet + (save-excursion + (goto-char (point-min)) + (while (re-search-forward "^.*:Error:" nil t) + (cl-incf n))) + (if (> n 0) + (message "Failed to compile %d files in directory `%s'" n bn) + (message "%s `%s' compiled asynchronously with warnings" + action-name bn))))))) (unless quiet (message "%s `%s' compiled asynchronously with success" action-name bn))))) +(defmacro async-bytecomp--comp-buffer-to-file () + "Write contents of `byte-compile-log-buffer' to a log file. +The log file is a temporary file that name is determined by +`async-byte-compile-log-file', which see. Return the actual log +file name, or nil if no log file has been created." + `(when (get-buffer byte-compile-log-buffer) + (let ((error-data (with-current-buffer byte-compile-log-buffer + (buffer-substring-no-properties (point-min) (point-max))))) + (unless (string= error-data "") + ;; The `async-byte-compile-log-file' used to be an absolute file name + ;; shared amongst all compilation async processes. For backward + ;; compatibility the directory part of it is used to create logs the same + ;; directory while the nondirectory part denotes the PREFIX for + ;; `make-temp-file' call. The `temporary-file-directory' is bound, such + ;; that the async process uses one set by the caller. + (let ((temporary-file-directory + ,(or (when (and async-byte-compile-log-file + (file-name-absolute-p + async-byte-compile-log-file)) + (expand-file-name (file-name-directory + async-byte-compile-log-file))) + temporary-file-directory)) + (log-file (make-temp-file ,(let ((log-file + (file-name-nondirectory + async-byte-compile-log-file))) + (format "%s%s" + log-file + (if (string-suffix-p "." log-file) + "" ".")))))) + (with-temp-file log-file + (erase-buffer) + (insert error-data)) + log-file))))) + ;;;###autoload (defun async-byte-recompile-directory (directory &optional quiet) "Compile all *.el files in DIRECTORY asynchronously. @@ -104,23 +150,16 @@ All *.elc files are systematically deleted before proceeding." ;; This happen when recompiling its own directory. (load "async") (let ((call-back - (lambda (&optional _ignore) - (async-bytecomp--file-to-comp-buffer directory quiet 'directory)))) + (lambda (&optional log-file) + (async-bytecomp--file-to-comp-buffer directory quiet 'directory log-file)))) (async-start `(lambda () (require 'bytecomp) ,(async-inject-variables async-bytecomp-load-variable-regexp) - (let ((default-directory (file-name-as-directory ,directory)) - error-data) + (let ((default-directory (file-name-as-directory ,directory))) (add-to-list 'load-path default-directory) (byte-recompile-directory ,directory 0 t) - (when (get-buffer byte-compile-log-buffer) - (setq error-data (with-current-buffer byte-compile-log-buffer - (buffer-substring-no-properties (point-min) (point-max)))) - (unless (string= error-data "") - (with-temp-file ,async-byte-compile-log-file - (erase-buffer) - (insert error-data)))))) + ,(macroexpand '(async-bytecomp--comp-buffer-to-file)))) call-back) (unless quiet (message "Started compiling asynchronously directory %s" directory)))) @@ -185,23 +224,16 @@ by default is async you don't need this." Same as `byte-compile-file' but asynchronous." (interactive "fFile: ") (let ((call-back - (lambda (&optional _ignore) - (async-bytecomp--file-to-comp-buffer file nil 'file)))) + (lambda (&optional log-file) + (async-bytecomp--file-to-comp-buffer file nil 'file log-file)))) (async-start `(lambda () (require 'bytecomp) ,(async-inject-variables async-bytecomp-load-variable-regexp) - (let ((default-directory ,(file-name-directory file)) - error-data) + (let ((default-directory ,(file-name-directory file))) (add-to-list 'load-path default-directory) (byte-compile-file ,file) - (when (get-buffer byte-compile-log-buffer) - (setq error-data (with-current-buffer byte-compile-log-buffer - (buffer-substring-no-properties (point-min) (point-max)))) - (unless (string= error-data "") - (with-temp-file ,async-byte-compile-log-file - (erase-buffer) - (insert error-data)))))) + ,(macroexpand '(async-bytecomp--comp-buffer-to-file)))) call-back))) (provide 'async-bytecomp) diff --git a/lisp/async/async-package.el b/lisp/async/async-package.el index d6c58236..208d5a0f 100644 --- a/lisp/async/async-package.el +++ b/lisp/async/async-package.el @@ -65,7 +65,14 @@ Argument ERROR-FILE is the file where errors are logged, if some." (action-string (pcase action ('install "Installing") ('upgrade "Upgrading") - ('reinstall "Reinstalling")))) + ('reinstall "Reinstalling"))) + ;; As PACKAGES are installed and compiled in a single async + ;; process we don't need to compute log-file in child process + ;; i.e. we use the same log-file for all PACKAGES. + (log-file (make-temp-file + (expand-file-name + (file-name-nondirectory async-byte-compile-log-file) + temporary-file-directory)))) (message "%s %s package(s)..." action-string (length packages)) (process-put (async-start @@ -92,13 +99,12 @@ Argument ERROR-FILE is the file where errors are logged, if some." (format "%S:\n Please refresh package list before %s" err ,action-string))))) - (let (error-data) - (when (get-buffer byte-compile-log-buffer) - (setq error-data (with-current-buffer byte-compile-log-buffer - (buffer-substring-no-properties - (point-min) (point-max)))) + (when (get-buffer byte-compile-log-buffer) + (let ((error-data (with-current-buffer byte-compile-log-buffer + (buffer-substring-no-properties + (point-min) (point-max))))) (unless (string= error-data "") - (with-temp-file ,async-byte-compile-log-file + (with-temp-file ,log-file (erase-buffer) (insert error-data))))))) (lambda (result) @@ -127,15 +133,9 @@ Argument ERROR-FILE is the file where errors are logged, if some." 'async-package-message str (length lst))) packages action-string) - (when (file-exists-p async-byte-compile-log-file) - (let ((buf (get-buffer-create byte-compile-log-buffer))) - (with-current-buffer buf - (goto-char (point-max)) - (let ((inhibit-read-only t)) - (insert-file-contents async-byte-compile-log-file) - (compilation-mode)) - (display-buffer buf) - (delete-file async-byte-compile-log-file))))))) + (if (zerop (nth 7 (file-attributes log-file))) + (delete-file log-file) + (async-bytecomp--file-to-comp-buffer-1 log-file))))) (run-hooks 'async-pkg-install-after-hook))) 'async-pkg-install t) (async-package--modeline-mode 1))) diff --git a/lisp/async/async-pkg.el b/lisp/async/async-pkg.el index b74d1c7c..ebe53e94 100644 --- a/lisp/async/async-pkg.el +++ b/lisp/async/async-pkg.el @@ -1,10 +1,10 @@ ;; -*- no-byte-compile: t; lexical-binding: nil -*- -(define-package "async" "20250325.509" +(define-package "async" "20251005.634" "Asynchronous processing in Emacs." '((emacs "24.4")) :url "https://github.com/jwiegley/emacs-async" - :commit "bb3f31966ed65a76abe6fa4f80a960a2917f554e" - :revdesc "bb3f31966ed6" + :commit "31cb2fea8f4bc7a593acd76187a89075d8075500" + :revdesc "31cb2fea8f4b" :keywords '("async") :authors '(("John Wiegley" . "jwiegley@gmail.com")) :maintainers '(("Thierry Volpiatto" . "thievol@posteo.net"))) diff --git a/lisp/async/async.el b/lisp/async/async.el index a1912597..4c2b01f2 100644 --- a/lisp/async/async.el +++ b/lisp/async/async.el @@ -6,8 +6,8 @@ ;; Maintainer: Thierry Volpiatto ;; Created: 18 Jun 2012 -;; Package-Version: 20250325.509 -;; Package-Revision: bb3f31966ed6 +;; Package-Version: 20251005.634 +;; Package-Revision: 31cb2fea8f4b ;; Package-Requires: ((emacs "24.4")) ;; Keywords: async @@ -118,7 +118,7 @@ is returned unmodified." collect elm)) (t object))) -(defvar async-inject-variables-exclude-regexps '("-syntax-table\\'") +(defvar async-inject-variables-exclude-regexps '("-syntax-table\\'" "-abbrev-table\\'") "A list of regexps that `async-inject-variables' should ignore.") (defun async-inject-variables diff --git a/lisp/async/dired-async.el b/lisp/async/dired-async.el index 43dcfdc0..b044bd12 100644 --- a/lisp/async/dired-async.el +++ b/lisp/async/dired-async.el @@ -71,7 +71,9 @@ Should take same args as `message'." (defcustom dired-async-skip-fast nil "If non-nil, skip async for fast operations. Same device renames and copying and renaming files smaller than -`dired-async-small-file-max' are considered fast." +`dired-async-small-file-max' are considered fast. +If the total size of all files exceed `dired-async-small-file-max' +operation is not considered fast." :risky t :type 'boolean) @@ -203,22 +205,22 @@ See `file-attributes'." (equal (file-attribute-device-number (file-attributes f1)) (file-attribute-device-number (file-attributes f2)))) -(defun dired-async--small-file-p (file) +(defun dired-async--small-file-p (file &optional attrs) "Return non-nil if FILE is considered small. File is considered small if it size is smaller than `dired-async-small-file-max'." - (let ((a (file-attributes file))) + (let ((a (or attrs (file-attributes file)))) ;; Directories are always large since we can't easily figure out ;; their total size. (and (not (dired-async--directory-p a)) (< (file-attribute-size a) dired-async-small-file-max)))) -(defun dired-async--skip-async-p (file-creator file name-constructor) +(defun dired-async--skip-async-p (file-creator file name-constructor &optional attrs) "Return non-nil if we should skip async for FILE. See `dired-create-files' for FILE-CREATOR and NAME-CONSTRUCTOR." ;; Skip async for small files. - (or (dired-async--small-file-p file) + (or (dired-async--small-file-p file attrs) ;; Also skip async for same device renames. (and (eq file-creator 'dired-rename-file) (let ((new (funcall name-constructor file))) @@ -230,14 +232,22 @@ See `dired-create-files' for FILE-CREATOR and NAME-CONSTRUCTOR." "Around advice for `dired-create-files'. Uses async like `dired-async-create-files' but skips certain fast cases if `dired-async-skip-fast' is non-nil." - (let (async-list quick-list) + (let ((total-size 0) + async-list quick-list) (if (or (eq file-creator 'backup-file) (null dired-async-skip-fast)) (setq async-list fn-list) (dolist (old fn-list) - (if (dired-async--skip-async-p file-creator old name-constructor) - (push old quick-list) - (push old async-list)))) + (let ((attrs (file-attributes old))) + (if (dired-async--skip-async-p + file-creator old name-constructor attrs) + (progn + (push old quick-list) + (setq total-size (+ total-size (nth 7 attrs)))) + (push old async-list))))) + (when (> total-size dired-async-small-file-max) + (setq async-list (append quick-list async-list) + quick-list nil)) (when async-list (dired-async-create-files file-creator operation (nreverse async-list) @@ -313,6 +323,7 @@ ESC or `q' to not overwrite any of the remaining files, from to))) ;; Skip file if it is too large. (if (and (member operation '("Copy" "Rename")) + dired-async-large-file-warning-threshold (eq (dired-async--abort-if-file-too-large (file-attribute-size (file-attributes (file-truename from))) diff --git a/lisp/biblio/biblio-hal.el b/lisp/biblio/biblio-hal.el index d5072c67..bf0d2906 100644 --- a/lisp/biblio/biblio-hal.el +++ b/lisp/biblio/biblio-hal.el @@ -28,6 +28,7 @@ ;;; Code: (require 'biblio-core) +(require 'timezone) (defun biblio-hal--forward-bibtex (metadata forward-to) "Forward BibTeX for HAL entry METADATA to FORWARD-TO." diff --git a/lisp/biblio/biblio-pkg.el b/lisp/biblio/biblio-pkg.el index 15f33d1e..172183dc 100644 --- a/lisp/biblio/biblio-pkg.el +++ b/lisp/biblio/biblio-pkg.el @@ -1,11 +1,11 @@ ;; -*- no-byte-compile: t; lexical-binding: nil -*- -(define-package "biblio" "20250409.2132" +(define-package "biblio" "20250812.1408" "Browse and import bibliographic references and BibTeX records from CrossRef, arXiv, DBLP, HAL, IEEE Xplore, Dissemin, and doi.org." '((emacs "24.3") (biblio-core "0.3")) :url "https://github.com/cpitclaudel/biblio.el" - :commit "0314982c0ca03d0f8e0ddbe9fc20588c35021098" - :revdesc "0314982c0ca0" + :commit "bb9d6b4b962fb2a4e965d27888268b66d868766b" + :revdesc "bb9d6b4b962f" :keywords '("bib" "tex" "convenience" "hypermedia") :authors '(("Clément Pit-Claudel" . "clement.pitclaudel@live.com")) :maintainers '(("Clément Pit-Claudel" . "clement.pitclaudel@live.com"))) diff --git a/lisp/biblio/biblio.el b/lisp/biblio/biblio.el index 0edfa056..756cb359 100644 --- a/lisp/biblio/biblio.el +++ b/lisp/biblio/biblio.el @@ -3,8 +3,8 @@ ;; Copyright (C) 2016 Clément Pit-Claudel ;; Author: Clément Pit-Claudel -;; Package-Version: 20250409.2132 -;; Package-Revision: 0314982c0ca0 +;; Package-Version: 20250812.1408 +;; Package-Revision: bb9d6b4b962f ;; Package-Requires: ((emacs "24.3") (biblio-core "0.3")) ;; Keywords: bib, tex, convenience, hypermedia ;; URL: https://github.com/cpitclaudel/biblio.el diff --git a/lisp/cfrs/cfrs-pkg.el b/lisp/cfrs/cfrs-pkg.el index 053ebfc7..f39936ee 100644 --- a/lisp/cfrs/cfrs-pkg.el +++ b/lisp/cfrs/cfrs-pkg.el @@ -1,12 +1,12 @@ ;; -*- no-byte-compile: t; lexical-binding: nil -*- -(define-package "cfrs" "20220129.1149" +(define-package "cfrs" "20250729.1422" "Child-frame based read-string." '((emacs "26.1") (dash "2.11.0") (s "1.10.0") (posframe "0.6.0")) :url "https://github.com/Alexander-Miller/cfrs" - :commit "f3a21f237b2a54e6b9f8a420a9da42b4f0a63121" - :revdesc "f3a21f237b2a" + :commit "981bddb3fb9fd9c58aed182e352975bd10ad74c8" + :revdesc "981bddb3fb9f" :authors '(("Alexander Miller" . "alexanderm@web.de")) :maintainers '(("Alexander Miller" . "alexanderm@web.de"))) diff --git a/lisp/cfrs/cfrs.el b/lisp/cfrs/cfrs.el index 602756d1..4529fad6 100644 --- a/lisp/cfrs/cfrs.el +++ b/lisp/cfrs/cfrs.el @@ -4,8 +4,8 @@ ;; Author: Alexander Miller ;; Package-Requires: ((emacs "26.1") (dash "2.11.0") (s "1.10.0") (posframe "0.6.0")) -;; Package-Version: 20220129.1149 -;; Package-Revision: f3a21f237b2a +;; Package-Version: 20250729.1422 +;; Package-Revision: 981bddb3fb9f ;; Homepage: https://github.com/Alexander-Miller/cfrs ;; This program is free software; you can redistribute it and/or modify @@ -70,13 +70,21 @@ See also `cfrs-max-width'" Only the `:background' part is used." :group 'cfrs) +(defconst cfrs--buffer-name " *Pos-Frame-Read*") + +(defun cfrs--detect-lost-focus (_) + "Abort the read operation when focus is lost." + (unless (eq major-mode 'cfrs-input-mode) + (posframe-hide cfrs--buffer-name) + (abort-recursive-edit))) + ;;;###autoload (defun cfrs-read (prompt &optional initial-input) "Read a string using a pos-frame with given PROMPT and INITIAL-INPUT." (if (not (or (display-graphic-p) (not (fboundp #'display-buffer-in-side-window)))) (read-string prompt initial-input) - (let* ((buffer (get-buffer-create " *Pos-Frame-Read*")) + (let* ((buffer (get-buffer-create cfrs--buffer-name)) (border-color (face-attribute 'cfrs-border-color :background nil t)) (cursor (cfrs--determine-cursor-type)) (width (+ 2 ;; extra space for margin and cursor @@ -132,7 +140,7 @@ Prevents showing an invisible cursor with a height or width of 0." (defun cfrs--hide () "Hide the current cfrs frame." (when (eq major-mode 'cfrs-input-mode) - (posframe-hide (current-buffer)) + (posframe-hide cfrs--buffer-name) (x-focus-frame (frame-parent (selected-frame))))) (defun cfrs--adjust-height () @@ -150,11 +158,13 @@ Prevents showing an invisible cursor with a height or width of 0." ;; XXX: workaround for persp believing we are in a different frame ;; and need a new perspective when the recursive edit ends (set-frame-parameter (selected-frame) 'persp--recursive nil) + (remove-hook 'window-selection-change-functions #'cfrs--detect-lost-focus :local) (exit-recursive-edit)) (defun cfrs-cancel () "Cancel the `cfrs-read' call and the function that called it." (interactive) + (remove-hook 'window-selection-change-functions #'cfrs--detect-lost-focus :local) (cfrs--hide) (abort-recursive-edit)) @@ -168,6 +178,7 @@ Prevents showing an invisible cursor with a height or width of 0." (define-derived-mode cfrs-input-mode fundamental-mode "Child Frame Read String" "Simple mode for buffers displayed in cfrs's input frames." (add-hook 'post-command-hook #'cfrs--adjust-height nil :local) + (add-hook 'window-selection-change-functions #'cfrs--detect-lost-focus nil :local) (display-line-numbers-mode -1)) ;; https://github.com/Alexander-Miller/treemacs/issues/775 diff --git a/lisp/citeproc/citeproc-bibtex.el b/lisp/citeproc/citeproc-bibtex.el index aeb17d9a..c9112124 100644 --- a/lisp/citeproc/citeproc-bibtex.el +++ b/lisp/citeproc/citeproc-bibtex.el @@ -232,7 +232,9 @@ character was found." (rx "\\" (1+ (any "a-z" "A-Z")) word-end)) ; \TEX-COMMAND + word-end (defconst citeproc-bt--braces-rx - (rx "{" (group (*? anything)) "}")) ; {TEXT} + (rx (group (or string-start (not "\\"))) "{" ; unescaped { + (group (optional (seq (*? anything) (not "\\")))) ; TEXT + "}")) ; unescaped } (defun citeproc-bt--process-brackets (s &optional lhb rhb) "Process LaTeX curly brackets in string S. @@ -250,11 +252,11 @@ The default is to remove them." match t)) ((string-match citeproc-bt--braces-rx result) (setq result (replace-match - (concat lhb "\\1" rhb) + (concat "\\1" lhb "\\2" rhb) t nil result) match t)) (t (setq match nil)))) - result)) + (s-replace-all '(("\\{" . "{") ("\\}" . "}")) result))) (defun citeproc-bt--preprocess-for-decode (s) "Preprocess field S before decoding. diff --git a/lisp/citeproc/citeproc-formatters.el b/lisp/citeproc/citeproc-formatters.el index 6168a753..d722ee18 100644 --- a/lisp/citeproc/citeproc-formatters.el +++ b/lisp/citeproc/citeproc-formatters.el @@ -258,7 +258,7 @@ CSL tests." ;; LaTeX (defconst citeproc-fmt--latex-esc-regex - (regexp-opt '("_" "&" "#" "%" "$")) + (regexp-opt '("_" "&" "#" "%" "$" "{" "}" )) "Regular expression matching characters to be escaped in LaTeX output.") (defun citeproc-fmt--latex-escape (s) diff --git a/lisp/citeproc/citeproc-locale.el b/lisp/citeproc/citeproc-locale.el index aee16912..68cc91e8 100644 --- a/lisp/citeproc/citeproc-locale.el +++ b/lisp/citeproc/citeproc-locale.el @@ -63,7 +63,7 @@ simply the result of upcasing.") (defun citeproc-locale-getter-from-dir (dir) "Return a locale getter getting parsed locales from a local DIR. If the requested locale couldn't be read then return the parsed -en-US locale, which must exist." +en-US locale, which must exist, and warn the user." (let ((default-loc-file (f-join dir "locales-en-US.xml"))) (lambda (loc) (let* ((ext-loc (if (or (member loc citeproc-locale--simple-locales) @@ -75,11 +75,16 @@ en-US locale, which must exist." (citeproc-lib-remove-xml-comments (citeproc-lib-parse-xml-file (if loc-available loc-file - (if (not (f-readable-p default-loc-file)) - (error - "The default CSL locale file %s doesn't exist or is unreadable" - default-loc-file) - default-loc-file)))))))) + (if (not (f-readable-p default-loc-file)) + (error + "The default CSL locale file %s doesn't exist or is unreadable" + default-loc-file) + (display-warning + 'citeproc + (format + "Could not read CSL locale file %s, using the fallback en-US locale" + loc-file)) + default-loc-file)))))))) (defun citeproc-locale-termlist-from-xml-frag (frag) "Transform xml FRAG representing citeproc--terms into a citeproc-term list." diff --git a/lisp/citeproc/citeproc-pkg.el b/lisp/citeproc/citeproc-pkg.el index 28c658b4..ceea34ae 100644 --- a/lisp/citeproc/citeproc-pkg.el +++ b/lisp/citeproc/citeproc-pkg.el @@ -1,5 +1,5 @@ ;; -*- no-byte-compile: t; lexical-binding: nil -*- -(define-package "citeproc" "20250525.1011" +(define-package "citeproc" "20251103.716" "A CSL 1.0.2 Citation Processor." '((emacs "26") (dash "2.13.0") @@ -11,8 +11,8 @@ (parsebib "2.4") (compat "28.1")) :url "https://github.com/andras-simonyi/citeproc-el" - :commit "e3bf1f80bcd64edf4afef564c0d94d38aa567d61" - :revdesc "e3bf1f80bcd6" + :commit "a3d62ab8e40a75fcfc6e4c0c107e3137b4db6db8" + :revdesc "a3d62ab8e40a" :keywords '("bib") :authors '(("András Simonyi" . "andras.simonyi@gmail.com")) :maintainers '(("András Simonyi" . "andras.simonyi@gmail.com"))) diff --git a/lisp/citeproc/citeproc-s.el b/lisp/citeproc/citeproc-s.el index 88d58663..e14f6b83 100644 --- a/lisp/citeproc/citeproc-s.el +++ b/lisp/citeproc/citeproc-s.el @@ -247,7 +247,7 @@ REPLACEMENTS is an alist with (FROM . TO) elements." "Replace dumb apostophes in string S with smart ones. The replacement character used is the unicode character `modifier letter apostrophe'." - (subst-char-in-string ?' ?ʼ (subst-char-in-string ?’ ?ʼ s t) t)) + (string-replace "'" "ʼ" (string-replace "’" "ʼ" s))) (defconst citeproc-s--cull-spaces-alist '((" " . " ") (";;" . ";") ("..." . ".") (",," . ",") (".." . ".")) diff --git a/lisp/citeproc/citeproc.el b/lisp/citeproc/citeproc.el index 09a950cb..5b954fa5 100644 --- a/lisp/citeproc/citeproc.el +++ b/lisp/citeproc/citeproc.el @@ -7,8 +7,8 @@ ;; URL: https://github.com/andras-simonyi/citeproc-el ;; Keywords: bib ;; Package-Requires: ((emacs "26") (dash "2.13.0") (s "1.12.0") (f "0.18.0") (queue "0.2") (string-inflection "1.0") (org "9") (parsebib "2.4")(compat "28.1")) -;; Package-Version: 20250525.1011 -;; Package-Revision: e3bf1f80bcd6 +;; Package-Version: 20251103.716 +;; Package-Revision: a3d62ab8e40a ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by diff --git a/lisp/company-statistics/company-statistics-pkg.el b/lisp/company-statistics/company-statistics-pkg.el index 30d04b03..036a00c0 100644 --- a/lisp/company-statistics/company-statistics-pkg.el +++ b/lisp/company-statistics/company-statistics-pkg.el @@ -1,11 +1,11 @@ ;; -*- no-byte-compile: t; lexical-binding: nil -*- -(define-package "company-statistics" "20170210.1933" +(define-package "company-statistics" "20250805.1524" "Sort candidates using completion history." '((emacs "24.3") (company "0.8.5")) :url "https://github.com/company-mode/company-statistics" - :commit "e62157d43b2c874d2edbd547c3bdfb05d0a7ae5c" - :revdesc "e62157d43b2c" + :commit "120e982f47e01945c044e0762ba376741c41b76c" + :revdesc "120e982f47e0" :keywords '("abbrev" "convenience" "matching") :authors '(("Ingo Lohmar" . "i.lohmar@gmail.com")) :maintainers '(("Ingo Lohmar" . "i.lohmar@gmail.com"))) diff --git a/lisp/company-statistics/company-statistics.el b/lisp/company-statistics/company-statistics.el index 684023a6..1f0fd6be 100644 --- a/lisp/company-statistics/company-statistics.el +++ b/lisp/company-statistics/company-statistics.el @@ -4,8 +4,8 @@ ;; Author: Ingo Lohmar ;; URL: https://github.com/company-mode/company-statistics -;; Package-Version: 20170210.1933 -;; Package-Revision: e62157d43b2c +;; Package-Version: 20250805.1524 +;; Package-Revision: 120e982f47e0 ;; Keywords: abbrev, convenience, matching ;; Package-Requires: ((emacs "24.3") (company "0.8.5")) @@ -347,7 +347,9 @@ preserved automatically between Emacs sessions in the default configuration. You can customize this behavior with `company-statistics-auto-save', `company-statistics-auto-restore' and `company-statistics-file'." - nil nil nil + :init-value nil + :lighter nil + :keymap nil :global t (if company-statistics-mode (progn diff --git a/lisp/company/company-dabbrev.el b/lisp/company/company-dabbrev.el index 2bd8e8d7..c228ada0 100644 --- a/lisp/company/company-dabbrev.el +++ b/lisp/company/company-dabbrev.el @@ -198,9 +198,11 @@ This variable affects both `company-dabbrev' and `company-dabbrev-code'." (company-dabbrev--search (company-dabbrev--make-regexp) company-dabbrev-time-limit (pcase company-dabbrev-other-buffers - (`t (list major-mode)) + ('t (list major-mode)) + ;; `all' is a function starting with Emacs 31. + ('all 'all) ((pred functionp) (funcall company-dabbrev-other-buffers (current-buffer))) - (`all `all)))) + ))) ;;;###autoload (defun company-dabbrev (command &optional arg &rest _ignored) diff --git a/lisp/company/company-files.el b/lisp/company/company-files.el index 73880960..64c89694 100644 --- a/lisp/company/company-files.el +++ b/lisp/company/company-files.el @@ -123,7 +123,7 @@ The values should use the same format as `completion-ignored-extensions'." (defun company-files--prefix () (let ((existing (company-files--grab-existing-name))) (when existing - (list existing (company-grab-suffix "[^ '\"\t\n\r/]*/?"))))) + (list existing (company-grab-suffix "[^] '\"\t\n\r/]*/?"))))) (defun company-file--keys-match-p (new old) (and (equal (cdr old) (cdr new)) diff --git a/lisp/company/company-keywords.el b/lisp/company/company-keywords.el index 785a090e..93b0552b 100644 --- a/lisp/company/company-keywords.el +++ b/lisp/company/company-keywords.el @@ -292,10 +292,10 @@ "then" "type" "where") (python-mode ;; https://docs.python.org/3/reference/lexical_analysis.html#keywords - "False" "None" "True" "and" "as" "assert" "break" "class" "continue" "def" - "del" "elif" "else" "except" "exec" "finally" "for" "from" "global" "if" - "import" "in" "is" "lambda" "nonlocal" "not" "or" "pass" "print" "raise" - "return" "try" "while" "with" "yield") + "False" "None" "True" "and" "as" "assert" "async" "await" "break" "class" + "continue" "def" "del" "elif" "else" "except" "exec" "finally" "for" "from" + "global" "if" "import" "in" "is" "lambda" "nonlocal" "not" "or" "pass" + "print" "raise" "return" "try" "while" "with" "yield") (ruby-mode "BEGIN" "END" "alias" "and" "begin" "break" "case" "class" "def" "defined?" "do" "else" "elsif" "end" "ensure" "false" "for" "if" "in" "module" diff --git a/lisp/company/company-pkg.el b/lisp/company/company-pkg.el index 0683790b..e5b24c06 100644 --- a/lisp/company/company-pkg.el +++ b/lisp/company/company-pkg.el @@ -1,9 +1,9 @@ ;; -*- no-byte-compile: t; lexical-binding: nil -*- -(define-package "company" "20250426.1319" +(define-package "company" "20251021.2211" "Modular text completion framework." '((emacs "26.1")) :url "http://company-mode.github.io/" - :commit "41f07c7d401c1374a76f3004a3448d3d36bdf347" - :revdesc "41f07c7d401c" + :commit "4ff89f7369227fbb89fe721d1db707f1af74cd0f" + :revdesc "4ff89f736922" :keywords '("abbrev" "convenience" "matching") :maintainers '(("Dmitry Gutov" . "dmitry@gutov.dev"))) diff --git a/lisp/company/company-tng.el b/lisp/company/company-tng.el index 41e65b9b..6c279d54 100644 --- a/lisp/company/company-tng.el +++ b/lisp/company/company-tng.el @@ -124,7 +124,12 @@ confirm the selection and finish the completion." (when (and company-selection (not (company--company-command-p (this-command-keys)))) (company--unread-this-command-keys) - (setq this-command 'company-complete-selection))))) + (setq this-command 'company-complete-selection))) + (post-command + (when (and (eq this-command 'company-complete-selection) + (zerop (length (car (company--boundaries)))) + (eql (preceding-char) (car unread-command-events))) + (delete-char -1))))) (defvar company-clang-insert-arguments) (defvar company-semantic-insert-arguments) diff --git a/lisp/company/company.el b/lisp/company/company.el index 09cfd464..fb967f8f 100644 --- a/lisp/company/company.el +++ b/lisp/company/company.el @@ -5,8 +5,8 @@ ;; Author: Nikolaj Schumacher ;; Maintainer: Dmitry Gutov ;; URL: http://company-mode.github.io/ -;; Package-Version: 20250426.1319 -;; Package-Revision: 41f07c7d401c +;; Package-Version: 20251021.2211 +;; Package-Revision: 4ff89f736922 ;; Keywords: abbrev, convenience, matching ;; Package-Requires: ((emacs "26.1")) @@ -1436,8 +1436,9 @@ be recomputed when this value changes." (let* ((entity (and (not (keywordp backend)) (company--force-sync backend '(prefix) backend))) - (new-len (company--prefix-len entity))) + new-len) (when (stringp (company--prefix-str entity)) + (setq new-len (company--prefix-len entity)) (or (not backends-after-with) (unless (memq backend backends-after-with) (setq backends-after-with nil))) diff --git a/lisp/company/company.info b/lisp/company/company.info index df6622a5..ca793f45 100644 --- a/lisp/company/company.info +++ b/lisp/company/company.info @@ -1,4 +1,4 @@ -This is company.info, produced by makeinfo version 7.1.1 from +This is company.info, produced by makeinfo version 7.2 from company.texi. This user manual is for Company version 1.0.3-snapshot @@ -82,7 +82,6 @@ Backends * Package Backends:: * Candidates Post-Processing:: -  File: company.info, Node: Overview, Next: Getting Started, Prev: Top, Up: Top @@ -1772,52 +1771,52 @@ Concept Index * troubleshoot: Troubleshooting. (line 6) * usage: Usage Basics. (line 6) -  Tag Table: -Node: Top575 -Node: Overview2002 -Node: Terminology2410 -Node: Structure3713 -Node: Getting Started5203 -Node: Installation5481 -Node: Initial Setup5864 -Node: Usage Basics6712 -Node: Commands7686 -Ref: Commands-Footnote-110082 -Node: Customization10249 -Node: Customization Interface10721 -Node: Configuration File11254 -Ref: company-selection-wrap-around13566 -Node: Frontends16055 -Node: Tooltip Frontends17024 -Ref: Tooltip Frontends-Footnote-127720 -Node: Preview Frontends27957 -Ref: Preview Frontends-Footnote-129215 -Node: Echo Frontends29342 -Node: Candidates Search30871 -Node: Filter Candidates32203 -Node: Quick Access a Candidate32983 -Node: Backends34601 -Node: Backends Usage Basics35631 -Ref: Backends Usage Basics-Footnote-137063 -Node: Grouped Backends37147 -Node: Package Backends38658 -Node: Code Completion39585 -Node: Text Completion45102 -Node: File Name Completion49526 -Node: Template Expansion51072 -Node: Candidates Post-Processing51791 -Node: Troubleshooting54368 -Node: Index56039 -Node: Key Index56202 -Node: Variable Index57701 -Node: Function Index62554 -Node: Concept Index67254 +Node: Top573 +Node: Overview1999 +Node: Terminology2407 +Node: Structure3710 +Node: Getting Started5200 +Node: Installation5478 +Node: Initial Setup5861 +Node: Usage Basics6709 +Node: Commands7683 +Ref: Commands-Footnote-110079 +Node: Customization10246 +Node: Customization Interface10718 +Node: Configuration File11251 +Ref: company-selection-wrap-around13563 +Node: Frontends16052 +Node: Tooltip Frontends17021 +Ref: Tooltip Frontends-Footnote-127717 +Node: Preview Frontends27954 +Ref: Preview Frontends-Footnote-129212 +Node: Echo Frontends29339 +Node: Candidates Search30868 +Node: Filter Candidates32200 +Node: Quick Access a Candidate32980 +Node: Backends34598 +Node: Backends Usage Basics35628 +Ref: Backends Usage Basics-Footnote-137060 +Node: Grouped Backends37144 +Node: Package Backends38655 +Node: Code Completion39582 +Node: Text Completion45099 +Node: File Name Completion49523 +Node: Template Expansion51069 +Node: Candidates Post-Processing51788 +Node: Troubleshooting54365 +Node: Index56036 +Node: Key Index56199 +Node: Variable Index57698 +Node: Function Index62551 +Node: Concept Index67251  End Tag Table  Local Variables: coding: utf-8 +Info-documentlanguage: en End: diff --git a/lisp/dash/dash.info b/lisp/dash/dash.info index c87eccf1..ec0ed68b 100644 --- a/lisp/dash/dash.info +++ b/lisp/dash/dash.info @@ -1,4 +1,4 @@ -This is dash.info, produced by makeinfo version 7.1.1 from dash.texi. +This is dash.info, produced by makeinfo version 7.2 from dash.texi. This manual is for Dash version 2.20.0. @@ -4732,223 +4732,223 @@ Index * global-dash-fontify-mode: Fontification of special variables. (line 12) -  Tag Table: -Node: Top734 -Node: Installation2377 -Node: Using in a package3139 -Node: Fontification of special variables3482 -Node: Info symbol lookup4272 -Node: Functions4855 -Node: Maps6339 -Ref: -map6636 -Ref: -map-when7007 -Ref: -map-first7581 -Ref: -map-last8176 -Ref: -map-indexed8766 -Ref: -annotate9450 -Ref: -splice10052 -Ref: -splice-list11125 -Ref: -mapcat11584 -Ref: -copy11957 -Node: Sublist selection12223 -Ref: -filter12416 -Ref: -remove12967 -Ref: -remove-first13514 -Ref: -remove-last14358 -Ref: -remove-item15086 -Ref: -non-nil15486 -Ref: -slice15768 -Ref: -take16297 -Ref: -take-last16715 -Ref: -drop17152 -Ref: -drop-last17599 -Ref: -take-while18031 -Ref: -drop-while18656 -Ref: -select-by-indices19287 -Ref: -select-columns19794 -Ref: -select-column20497 -Node: List to list20960 -Ref: -keep21152 -Ref: -concat21728 -Ref: -flatten22508 -Ref: -flatten-n23268 -Ref: -replace23652 -Ref: -replace-first24113 -Ref: -replace-last24608 -Ref: -insert-at25096 -Ref: -replace-at25421 -Ref: -update-at25808 -Ref: -remove-at26349 -Ref: -remove-at-indices26976 -Node: Reductions27666 -Ref: -reduce-from27862 -Ref: -reduce-r-from28584 -Ref: -reduce29845 -Ref: -reduce-r30594 -Ref: -reductions-from31870 -Ref: -reductions-r-from32672 -Ref: -reductions33498 -Ref: -reductions-r34205 -Ref: -count34946 -Ref: -sum35176 -Ref: -running-sum35364 -Ref: -product35685 -Ref: -running-product35893 -Ref: -inits36234 -Ref: -tails36479 -Ref: -common-prefix36724 -Ref: -common-suffix37018 -Ref: -min37312 -Ref: -min-by37538 -Ref: -max38059 -Ref: -max-by38284 -Ref: -frequencies38810 -Node: Unfolding39425 -Ref: -iterate39666 -Ref: -unfold40113 -Ref: -repeat40918 -Ref: -cycle41202 -Node: Predicates41599 -Ref: -some41776 -Ref: -every42203 -Ref: -any?42915 -Ref: -all?43264 -Ref: -none?44004 -Ref: -only-some?44324 -Ref: -contains?44869 -Ref: -is-prefix?45375 -Ref: -is-suffix?45707 -Ref: -is-infix?46039 -Ref: -cons-pair?46399 -Node: Partitioning46730 -Ref: -split-at46918 -Ref: -split-with47582 -Ref: -split-on48222 -Ref: -split-when48893 -Ref: -separate49536 -Ref: -partition50070 -Ref: -partition-all50519 -Ref: -partition-in-steps50944 -Ref: -partition-all-in-steps51490 -Ref: -partition-by52004 -Ref: -partition-by-header52382 -Ref: -partition-after-pred52983 -Ref: -partition-before-pred53434 -Ref: -partition-before-item53819 -Ref: -partition-after-item54126 -Ref: -group-by54428 -Node: Indexing54861 -Ref: -elem-index55063 -Ref: -elem-indices55550 -Ref: -find-index56009 -Ref: -find-last-index56676 -Ref: -find-indices57325 -Ref: -grade-up58085 -Ref: -grade-down58492 -Node: Set operations58906 -Ref: -union59089 -Ref: -difference59519 -Ref: -intersection59947 -Ref: -powerset60376 -Ref: -permutations60653 -Ref: -distinct61091 -Ref: -same-items?61485 -Node: Other list operations62094 -Ref: -rotate62319 -Ref: -cons*62672 -Ref: -snoc63094 -Ref: -interpose63506 -Ref: -interleave63800 -Ref: -iota64166 -Ref: -zip-with64649 -Ref: -zip-pair65455 -Ref: -zip-lists66021 -Ref: -zip-lists-fill66819 -Ref: -zip67529 -Ref: -zip-fill68556 -Ref: -unzip-lists69470 -Ref: -unzip70093 -Ref: -pad71086 -Ref: -table71571 -Ref: -table-flat72357 -Ref: -first73360 -Ref: -last73891 -Ref: -first-item74237 -Ref: -second-item74649 -Ref: -third-item75066 -Ref: -fourth-item75441 -Ref: -fifth-item75819 -Ref: -last-item76194 -Ref: -butlast76555 -Ref: -sort76800 -Ref: -list77294 -Ref: -fix77863 -Node: Tree operations78352 -Ref: -tree-seq78548 -Ref: -tree-map79409 -Ref: -tree-map-nodes79849 -Ref: -tree-reduce80713 -Ref: -tree-reduce-from81595 -Ref: -tree-mapreduce82195 -Ref: -tree-mapreduce-from83054 -Ref: -clone84339 -Node: Threading macros84677 -Ref: ->84902 -Ref: ->>85390 -Ref: -->85893 -Ref: -as->86450 -Ref: -some->86904 -Ref: -some->>87289 -Ref: -some-->87736 -Ref: -doto88303 -Node: Binding88856 -Ref: -when-let89063 -Ref: -when-let*89524 -Ref: -if-let90053 -Ref: -if-let*90419 -Ref: -let91042 -Ref: -let*97118 -Ref: -lambda98055 -Ref: -setq98861 -Node: Side effects99662 -Ref: -each99856 -Ref: -each-while100381 -Ref: -each-indexed101001 -Ref: -each-r101593 -Ref: -each-r-while102035 -Ref: -dotimes102679 -Node: Destructive operations103230 -Ref: !cons103448 -Ref: !cdr103652 -Node: Function combinators103845 -Ref: -partial104049 -Ref: -rpartial104567 -Ref: -juxt105215 -Ref: -compose105667 -Ref: -applify106274 -Ref: -on106704 -Ref: -flip107468 -Ref: -rotate-args107990 -Ref: -const108619 -Ref: -cut108961 -Ref: -not109441 -Ref: -orfn109985 -Ref: -andfn110778 -Ref: -iteratefn111565 -Ref: -fixfn112267 -Ref: -prodfn113841 -Node: Development114968 -Node: Contribute115257 -Node: Contributors116265 -Node: FDL118358 -Node: GPL143477 -Node: Index181023 +Node: Top732 +Node: Installation2375 +Node: Using in a package3137 +Node: Fontification of special variables3480 +Node: Info symbol lookup4270 +Node: Functions4853 +Node: Maps6337 +Ref: -map6634 +Ref: -map-when7005 +Ref: -map-first7579 +Ref: -map-last8174 +Ref: -map-indexed8764 +Ref: -annotate9448 +Ref: -splice10050 +Ref: -splice-list11123 +Ref: -mapcat11582 +Ref: -copy11955 +Node: Sublist selection12221 +Ref: -filter12414 +Ref: -remove12965 +Ref: -remove-first13512 +Ref: -remove-last14356 +Ref: -remove-item15084 +Ref: -non-nil15484 +Ref: -slice15766 +Ref: -take16295 +Ref: -take-last16713 +Ref: -drop17150 +Ref: -drop-last17597 +Ref: -take-while18029 +Ref: -drop-while18654 +Ref: -select-by-indices19285 +Ref: -select-columns19792 +Ref: -select-column20495 +Node: List to list20958 +Ref: -keep21150 +Ref: -concat21726 +Ref: -flatten22506 +Ref: -flatten-n23266 +Ref: -replace23650 +Ref: -replace-first24111 +Ref: -replace-last24606 +Ref: -insert-at25094 +Ref: -replace-at25419 +Ref: -update-at25806 +Ref: -remove-at26347 +Ref: -remove-at-indices26974 +Node: Reductions27664 +Ref: -reduce-from27860 +Ref: -reduce-r-from28582 +Ref: -reduce29843 +Ref: -reduce-r30592 +Ref: -reductions-from31868 +Ref: -reductions-r-from32670 +Ref: -reductions33496 +Ref: -reductions-r34203 +Ref: -count34944 +Ref: -sum35174 +Ref: -running-sum35362 +Ref: -product35683 +Ref: -running-product35891 +Ref: -inits36232 +Ref: -tails36477 +Ref: -common-prefix36722 +Ref: -common-suffix37016 +Ref: -min37310 +Ref: -min-by37536 +Ref: -max38057 +Ref: -max-by38282 +Ref: -frequencies38808 +Node: Unfolding39423 +Ref: -iterate39664 +Ref: -unfold40111 +Ref: -repeat40916 +Ref: -cycle41200 +Node: Predicates41597 +Ref: -some41774 +Ref: -every42201 +Ref: -any?42913 +Ref: -all?43262 +Ref: -none?44002 +Ref: -only-some?44322 +Ref: -contains?44867 +Ref: -is-prefix?45373 +Ref: -is-suffix?45705 +Ref: -is-infix?46037 +Ref: -cons-pair?46397 +Node: Partitioning46728 +Ref: -split-at46916 +Ref: -split-with47580 +Ref: -split-on48220 +Ref: -split-when48891 +Ref: -separate49534 +Ref: -partition50068 +Ref: -partition-all50517 +Ref: -partition-in-steps50942 +Ref: -partition-all-in-steps51488 +Ref: -partition-by52002 +Ref: -partition-by-header52380 +Ref: -partition-after-pred52981 +Ref: -partition-before-pred53432 +Ref: -partition-before-item53817 +Ref: -partition-after-item54124 +Ref: -group-by54426 +Node: Indexing54859 +Ref: -elem-index55061 +Ref: -elem-indices55548 +Ref: -find-index56007 +Ref: -find-last-index56674 +Ref: -find-indices57323 +Ref: -grade-up58083 +Ref: -grade-down58490 +Node: Set operations58904 +Ref: -union59087 +Ref: -difference59517 +Ref: -intersection59945 +Ref: -powerset60374 +Ref: -permutations60651 +Ref: -distinct61089 +Ref: -same-items?61483 +Node: Other list operations62092 +Ref: -rotate62317 +Ref: -cons*62670 +Ref: -snoc63092 +Ref: -interpose63504 +Ref: -interleave63798 +Ref: -iota64164 +Ref: -zip-with64647 +Ref: -zip-pair65453 +Ref: -zip-lists66019 +Ref: -zip-lists-fill66817 +Ref: -zip67527 +Ref: -zip-fill68554 +Ref: -unzip-lists69468 +Ref: -unzip70091 +Ref: -pad71084 +Ref: -table71569 +Ref: -table-flat72355 +Ref: -first73358 +Ref: -last73889 +Ref: -first-item74235 +Ref: -second-item74647 +Ref: -third-item75064 +Ref: -fourth-item75439 +Ref: -fifth-item75817 +Ref: -last-item76192 +Ref: -butlast76553 +Ref: -sort76798 +Ref: -list77292 +Ref: -fix77861 +Node: Tree operations78350 +Ref: -tree-seq78546 +Ref: -tree-map79407 +Ref: -tree-map-nodes79847 +Ref: -tree-reduce80711 +Ref: -tree-reduce-from81593 +Ref: -tree-mapreduce82193 +Ref: -tree-mapreduce-from83052 +Ref: -clone84337 +Node: Threading macros84675 +Ref: ->84900 +Ref: ->>85388 +Ref: -->85891 +Ref: -as->86448 +Ref: -some->86902 +Ref: -some->>87287 +Ref: -some-->87734 +Ref: -doto88301 +Node: Binding88854 +Ref: -when-let89061 +Ref: -when-let*89522 +Ref: -if-let90051 +Ref: -if-let*90417 +Ref: -let91040 +Ref: -let*97116 +Ref: -lambda98053 +Ref: -setq98859 +Node: Side effects99660 +Ref: -each99854 +Ref: -each-while100379 +Ref: -each-indexed100999 +Ref: -each-r101591 +Ref: -each-r-while102033 +Ref: -dotimes102677 +Node: Destructive operations103228 +Ref: !cons103446 +Ref: !cdr103650 +Node: Function combinators103843 +Ref: -partial104047 +Ref: -rpartial104565 +Ref: -juxt105213 +Ref: -compose105665 +Ref: -applify106272 +Ref: -on106702 +Ref: -flip107466 +Ref: -rotate-args107988 +Ref: -const108617 +Ref: -cut108959 +Ref: -not109439 +Ref: -orfn109983 +Ref: -andfn110776 +Ref: -iteratefn111563 +Ref: -fixfn112265 +Ref: -prodfn113839 +Node: Development114966 +Node: Contribute115255 +Node: Contributors116263 +Node: FDL118356 +Node: GPL143475 +Node: Index181021  End Tag Table  Local Variables: coding: utf-8 +Info-documentlanguage: en End: diff --git a/lisp/diff-hl/diff-hl-autoloads.el b/lisp/diff-hl/diff-hl-autoloads.el index dc147e89..032ae940 100644 --- a/lisp/diff-hl/diff-hl-autoloads.el +++ b/lisp/diff-hl/diff-hl-autoloads.el @@ -28,8 +28,6 @@ evaluate the variable `diff-hl-mode'. The mode's hook is called both when the mode is enabled and when it is disabled. -\\{diff-hl-mode-map} - (fn &optional ARG)" t) (autoload 'turn-on-diff-hl-mode "diff-hl" "\ Turn on `diff-hl-mode' or `diff-hl-dir-mode' in a buffer if appropriate.") @@ -39,6 +37,11 @@ Call `turn-on-diff-hl-mode' if the current major mode is applicable.") Set the reference revision globally to REV. When called interactively, REV read with completion. +When called with a prefix argument, reset the global reference to the most +recent one instead. With two prefix arguments, do the same and discard +every per-project reference created by +`diff-hl-set-reference-rev-in-project`. + The default value chosen using one of methods below: - In a log view buffer, it uses the revision of current entry. @@ -47,9 +50,33 @@ view buffer. - In a VC annotate buffer, it uses the revision of current line. - In other situations, it uses the symbol at point. -Notice that this sets the reference revision globally, so in -files from other repositories, `diff-hl-mode' will not highlight -changes correctly, until you run `diff-hl-reset-reference-rev'. +Notice that this sets the reference revision globally, so in files from +other repositories, `diff-hl-mode' will not highlight changes correctly, +until you run `diff-hl-reset-reference-rev'. To set the reference on a +per-project basis, see `diff-hl-set-reference-rev-in-project`. + +Also notice that this will disable `diff-hl-amend-mode' in +buffers that enables it, since `diff-hl-amend-mode' overrides its +effect. + +(fn REV)" t) +(autoload 'diff-hl-set-reference-rev-in-project "diff-hl" "\ +Set the reference revision in the current project to REV. +When called interactively, REV read with completion. + +When called with a prefix argument, reset to the global value instead. + +The default value chosen using one of methods below: + +- In a log view buffer, it uses the revision of current entry. +Call `vc-print-log' or `vc-print-root-log' first to open a log +view buffer. +- In a VC annotate buffer, it uses the revision of current line. +- In other situations, it uses the symbol at point. + +Projects whose reference was set with this command are unaffected by +subsequent changes to the global reference (see +`diff-hl-set-reference-rev`). Also notice that this will disable `diff-hl-amend-mode' in buffers that enables it, since `diff-hl-amend-mode' overrides its @@ -57,7 +84,12 @@ effect. (fn REV)" t) (autoload 'diff-hl-reset-reference-rev "diff-hl" "\ -Reset the reference revision globally to the most recent one." t) +Reset the reference revision globally to the most recent one. + +When called with a prefix argument, do the same and discard every +per-project reference created by `diff-hl-set-reference-rev-in-project'. + +(fn &optional ARG)" t) (put 'global-diff-hl-mode 'globalized-minor-mode t) (defvar global-diff-hl-mode nil "\ Non-nil if Global Diff-Hl mode is enabled. @@ -82,7 +114,7 @@ would do it. See `diff-hl-mode' for more information on Diff-Hl mode. (fn &optional ARG)" t) -(register-definition-prefixes "diff-hl" '("diff-hl-")) +(register-definition-prefixes "diff-hl" '("diff-hl-" "static-if")) ;;; Generated autoloads from diff-hl-amend.el @@ -192,20 +224,6 @@ disabled. (fn &optional ARG)" t) (register-definition-prefixes "diff-hl-flydiff" '("diff-hl-flydiff")) - -;;; Generated autoloads from diff-hl-inline-popup.el - -(autoload 'diff-hl-inline-popup-hide "diff-hl-inline-popup" "\ -Hide the current inline popup." t) -(autoload 'diff-hl-inline-popup-show "diff-hl-inline-popup" "\ -Create a phantom overlay to show the inline popup, with some -content LINES, and a HEADER and a FOOTER, at POINT. KEYMAP is -added to the current keymaps. CLOSE-HOOK is called when the popup -is closed. - -(fn LINES &optional HEADER FOOTER KEYMAP CLOSE-HOOK POINT HEIGHT)") -(register-definition-prefixes "diff-hl-inline-popup" '("diff-hl-inline-popup-")) - ;;; Generated autoloads from diff-hl-margin.el @@ -260,11 +278,6 @@ disabled. ;;; Generated autoloads from diff-hl-show-hunk.el -(autoload 'diff-hl-show-hunk-inline-popup "diff-hl-show-hunk" "\ -Implementation to show the hunk in a inline popup. -BUFFER is a buffer with the hunk. - -(fn BUFFER &optional IGNORED-LINE)") (autoload 'diff-hl-show-hunk-previous "diff-hl-show-hunk" "\ Go to previous hunk/change and show it." t) (autoload 'diff-hl-show-hunk-next "diff-hl-show-hunk" "\ @@ -325,6 +338,25 @@ Diff-Hl-Show-Hunk-Mouse mode. (fn &optional ARG)" t) (register-definition-prefixes "diff-hl-show-hunk" '("diff-hl-show-hunk-")) + +;;; Generated autoloads from diff-hl-show-hunk-inline.el + +(autoload 'diff-hl-show-hunk-inline-hide "diff-hl-show-hunk-inline" "\ +Hide the current inline popup." t) +(autoload 'diff-hl-show-hunk-inline-show "diff-hl-show-hunk-inline" "\ +Create a phantom overlay to show the inline popup, with some +content LINES, and a HEADER and a FOOTER, at POINT. KEYMAP is +added to the current keymaps. CLOSE-HOOK is called when the popup +is closed. + +(fn LINES &optional HEADER FOOTER KEYMAP CLOSE-HOOK POINT HEIGHT)") +(autoload 'diff-hl-show-hunk-inline "diff-hl-show-hunk-inline" "\ +Implementation to show the hunk in a inline popup. +BUFFER is a buffer with the hunk. + +(fn BUFFER &optional IGNORED-LINE)") +(register-definition-prefixes "diff-hl-show-hunk-inline" '("diff-hl-show-hunk-inline-")) + ;;; Generated autoloads from diff-hl-show-hunk-posframe.el diff --git a/lisp/diff-hl/diff-hl-dired.el b/lisp/diff-hl/diff-hl-dired.el index 84e7e185..4a643af4 100644 --- a/lisp/diff-hl/diff-hl-dired.el +++ b/lisp/diff-hl/diff-hl-dired.el @@ -74,6 +74,10 @@ status indicators." `(const :tag ,(symbol-name name) ,name)) vc-handled-backends)))) +(defcustom diff-hl-dired-fringe-bmp-function 'diff-hl-fringe-bmp-from-type + "Function to determine fringe bitmap from change type and position." + :type 'function) + ;;;###autoload (define-minor-mode diff-hl-dired-mode "Toggle VC diff highlighting on the side of a Dired window." @@ -151,7 +155,7 @@ for DIR containing FILES. Call UPDATE-FUNCTION as entries are added." (goto-char (point-min)) (when (and type (dired-goto-file-1 file (expand-file-name file) nil)) - (let* ((diff-hl-fringe-bmp-function 'diff-hl-fringe-bmp-from-type) + (let* ((diff-hl-fringe-bmp-function diff-hl-dired-fringe-bmp-function) (diff-hl-fringe-face-function 'diff-hl-dired-face-from-type) (o (diff-hl-add-highlighting type 'single))) (overlay-put o 'modification-hooks '(diff-hl-overlay-modified)) diff --git a/lisp/diff-hl/diff-hl-flydiff.el b/lisp/diff-hl/diff-hl-flydiff.el index e366eecc..1a6b11f6 100644 --- a/lisp/diff-hl/diff-hl-flydiff.el +++ b/lisp/diff-hl/diff-hl-flydiff.el @@ -1,4 +1,4 @@ -;; Copyright (C) 2015-2021 Free Software Foundation, Inc. -*- lexical-binding: t -*- +;; Copyright (C) 2015-2025 Free Software Foundation, Inc. -*- lexical-binding: t -*- ;; Author: Jonathan Hayase ;; URL: https://github.com/dgutov/diff-hl @@ -40,9 +40,13 @@ (defvar diff-hl-flydiff-timer nil) (make-variable-buffer-local 'diff-hl-flydiff-modified-tick) -(defun diff-hl-flydiff-changes-buffer (file &optional backend) +(defun diff-hl-flydiff-changes-buffer (file backend &optional new-rev buffer) + (setq buffer (or buffer " *diff-hl-diff*")) (setq diff-hl-flydiff-modified-tick (buffer-chars-modified-tick)) - (diff-hl-diff-buffer-with-reference file " *diff-hl-diff*" backend)) + (if new-rev + (diff-hl-with-diff-switches + (diff-hl-diff-against-reference file backend buffer new-rev)) + (diff-hl-diff-buffer-with-reference file buffer backend))) (defun diff-hl-flydiff-update () (unless (or diff --git a/lisp/diff-hl/diff-hl-inline-popup.el b/lisp/diff-hl/diff-hl-inline-popup.el deleted file mode 100644 index 0f202aff..00000000 --- a/lisp/diff-hl/diff-hl-inline-popup.el +++ /dev/null @@ -1,289 +0,0 @@ -;;; diff-hl-inline-popup.el --- inline popup using phantom overlays -*- lexical-binding: t -*- - -;; Copyright (C) 2020-2021 Free Software Foundation, Inc. - -;; Author: Álvaro González - -;; 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 . - -;;; Commentary: -;; Shows inline popups using phantom overlays. The lines of the popup -;; can be scrolled. -;;; Code: - -(require 'subr-x) - -(defvar diff-hl-inline-popup--current-popup nil "The overlay of the current inline popup.") -(defvar diff-hl-inline-popup--current-lines nil "A list of the lines to show in the popup.") -(defvar diff-hl-inline-popup--current-index nil "First line showed in popup.") -(defvar diff-hl-inline-popup--invokinkg-command nil "Command that invoked the popup.") -(defvar diff-hl-inline-popup--current-footer nil "String to be displayed in the footer.") -(defvar diff-hl-inline-popup--current-header nil "String to be displayed in the header.") -(defvar diff-hl-inline-popup--height nil "Height of the popup.") -(defvar diff-hl-inline-popup--current-custom-keymap nil "Keymap to be added to the keymap of the inline popup.") -(defvar diff-hl-inline-popup--close-hook nil "Function to be called when the popup closes.") - -(make-variable-buffer-local 'diff-hl-inline-popup--current-popup) -(make-variable-buffer-local 'diff-hl-inline-popup--current-lines) -(make-variable-buffer-local 'diff-hl-inline-popup--current-index) -(make-variable-buffer-local 'diff-hl-inline-popup--current-header) -(make-variable-buffer-local 'diff-hl-inline-popup--current-footer) -(make-variable-buffer-local 'diff-hl-inline-popup--invokinkg-command) -(make-variable-buffer-local 'diff-hl-inline-popup--current-custom-keymap) -(make-variable-buffer-local 'diff-hl-inline-popup--height) -(make-variable-buffer-local 'diff-hl-inline-popup--close-hook) - -(defun diff-hl-inline-popup--splice (list offset length) - "Compute a sublist of LIST starting at OFFSET, of LENGTH." - (butlast - (nthcdr offset list) - (- (length list) length offset))) - -(defun diff-hl-inline-popup--ensure-enough-lines (pos content-height) - "Ensure there is enough lines below POS to show the inline popup. -CONTENT-HEIGHT specifies the height of the popup." - (let* ((line (line-number-at-pos pos)) - (end (line-number-at-pos (window-end nil t))) - (height (+ 6 content-height)) - (overflow (- (+ line height) end))) - (when (< 0 overflow) - (run-with-timer 0.1 nil #'scroll-up overflow)))) - -(defun diff-hl-inline-popup--compute-content-height (&optional content-size) - "Compute the height of the inline popup. -Default for CONTENT-SIZE is the size of the current lines" - (let ((content-size (or content-size (length diff-hl-inline-popup--current-lines))) - (max-size (- (/(window-height) 2) 3))) - (min content-size max-size))) - -(defun diff-hl-inline-popup--compute-content-lines (lines index window-size) - "Compute the lines to show in the popup. -Compute it from LINES starting at INDEX with a WINDOW-SIZE." - (let* ((len (length lines)) - (window-size (min window-size len)) - (index (min index (- len window-size)))) - (diff-hl-inline-popup--splice lines index window-size))) - -(defun diff-hl-inline-popup--compute-header (width &optional header) - "Compute the header of the popup. -Compute it from some WIDTH, and some optional HEADER text." - (let* ((scroll-indicator (if (eq diff-hl-inline-popup--current-index 0) " " " ⬆ ")) - (header (or header "")) - (new-width (- width (length header) (length scroll-indicator))) - (header (if (< new-width 0) "" header)) - (new-width (- width (length header) (length scroll-indicator))) - (line (propertize (concat (diff-hl-inline-popup--separator new-width) - header scroll-indicator ) - 'face '(:underline t)))) - (concat line "\n") )) - -(defun diff-hl-inline-popup--compute-footer (width &optional footer) - "Compute the header of the popup. -Compute it from some WIDTH, and some optional FOOTER text." - (let* ((scroll-indicator (if (>= diff-hl-inline-popup--current-index - (- (length diff-hl-inline-popup--current-lines) - diff-hl-inline-popup--height)) - " " - " ⬇ ")) - (footer (or footer "")) - (new-width (- width (length footer) (length scroll-indicator))) - (footer (if (< new-width 0) "" footer)) - (new-width (- width (length footer) (length scroll-indicator))) - (blank-line (if (display-graphic-p) - "" - (concat "\n" (propertize (diff-hl-inline-popup--separator width) - 'face '(:underline t))))) - (line (propertize (concat (diff-hl-inline-popup--separator new-width) - footer scroll-indicator) - 'face '(:overline t)))) - (concat blank-line "\n" line))) - -(defun diff-hl-inline-popup--separator (width &optional sep) - "Return the horizontal separator with character SEP and a WIDTH." - (let ((sep (or sep ?\s))) - (make-string width sep))) - -(defun diff-hl-inline-popup--available-width () - "Compute the available width in chars." - (let ((magic-adjust 3)) - (if (not (display-graphic-p)) - (let* ((linumber-width (line-number-display-width nil)) - (width (- (window-body-width) linumber-width magic-adjust))) - width) - (let* ((font-width (window-font-width)) - (window-width (window-body-width nil t)) - (linenumber-width (line-number-display-width t)) - (available-pixels (- window-width linenumber-width)) - (width (- (/ available-pixels font-width) magic-adjust))) - - ;; https://emacs.stackexchange.com/questions/5495/how-can-i-determine-the-width-of-characters-on-the-screen - width)))) - -(defun diff-hl-inline-popup--compute-popup-str (lines index window-size header footer) - "Compute the string that represents the popup. -There are some content LINES starting at INDEX, with a WINDOW-SIZE. HEADER and -FOOTER are showed at start and end." - (let* ((width (diff-hl-inline-popup--available-width)) - (content-lines (diff-hl-inline-popup--compute-content-lines lines index window-size)) - (header (diff-hl-inline-popup--compute-header width header)) - (footer (diff-hl-inline-popup--compute-footer width footer))) - (concat header (string-join content-lines "\n") footer "\n"))) - -(defun diff-hl-inline-popup-scroll-to (index) - "Scroll the inline popup to make visible the line at position INDEX." - (when diff-hl-inline-popup--current-popup - (setq diff-hl-inline-popup--current-index (max 0 (min index (- (length diff-hl-inline-popup--current-lines) diff-hl-inline-popup--height)))) - (let* ((str (diff-hl-inline-popup--compute-popup-str - diff-hl-inline-popup--current-lines - diff-hl-inline-popup--current-index - diff-hl-inline-popup--height - diff-hl-inline-popup--current-header - diff-hl-inline-popup--current-footer))) - ;; https://debbugs.gnu.org/38563, `company--replacement-string'. - (add-face-text-property 0 (length str) 'default t str) - (put-text-property 0 1 'cursor 0 str) - (overlay-put diff-hl-inline-popup--current-popup 'before-string str)))) - -(defun diff-hl-inline-popup--popup-down() - "Scrolls one line down." - (interactive) - (diff-hl-inline-popup-scroll-to (1+ diff-hl-inline-popup--current-index) )) - -(defun diff-hl-inline-popup--popup-up() - "Scrolls one line up." - (interactive) - (diff-hl-inline-popup-scroll-to (1- diff-hl-inline-popup--current-index) )) - -(defun diff-hl-inline-popup--popup-pagedown() - "Scrolls one page down." - (interactive) - (diff-hl-inline-popup-scroll-to (+ diff-hl-inline-popup--current-index diff-hl-inline-popup--height) )) - -(defun diff-hl-inline-popup--popup-pageup() - "Scrolls one page up." - (interactive) - (diff-hl-inline-popup-scroll-to (- diff-hl-inline-popup--current-index diff-hl-inline-popup--height) )) - -(defvar diff-hl-inline-popup-transient-mode-map - (let ((map (make-sparse-keymap))) - (define-key map (kbd "") #'diff-hl-inline-popup--popup-pageup) - (define-key map (kbd "M-v") #'diff-hl-inline-popup--popup-pageup) - (define-key map (kbd "") #'diff-hl-inline-popup--popup-pagedown) - (define-key map (kbd "C-v") #'diff-hl-inline-popup--popup-pagedown) - (define-key map (kbd "") #'diff-hl-inline-popup--popup-up) - (define-key map (kbd "C-p") #'diff-hl-inline-popup--popup-up) - (define-key map (kbd "") #'diff-hl-inline-popup--popup-down) - (define-key map (kbd "C-n") #'diff-hl-inline-popup--popup-down) - (define-key map (kbd "C-g") #'diff-hl-inline-popup-hide) - (define-key map [escape] #'diff-hl-inline-popup-hide) - (define-key map (kbd "q") #'diff-hl-inline-popup-hide) - ;;http://ergoemacs.org/emacs/emacs_mouse_wheel_config.html - (define-key map (kbd "") #'diff-hl-inline-popup--popup-up) - (define-key map (kbd "") #'diff-hl-inline-popup--popup-up) - (define-key map (kbd "") #'diff-hl-inline-popup--popup-down) - (define-key map (kbd "") #'diff-hl-inline-popup--popup-down) - map) - "Keymap for command `diff-hl-inline-popup-transient-mode'. -Capture all the vertical movement of the point, and converts it -to scroll in the popup") - -(defun diff-hl-inline-popup--ignorable-command-p (command) - "Decide if COMMAND is a command allowed while showing an inline popup." - ;; https://emacs.stackexchange.com/questions/653/how-can-i-find-out-in-which-keymap-a-key-is-bound - (let ((keys (where-is-internal command (list diff-hl-inline-popup--current-custom-keymap - diff-hl-inline-popup-transient-mode-map ) t)) - (invoking (eq command diff-hl-inline-popup--invokinkg-command))) - (or keys invoking))) - -(defun diff-hl-inline-popup--post-command-hook () - "Called each time a command is executed." - (let ((allowed-command (or - (string-match-p "diff-hl-inline-popup-" (symbol-name this-command)) - (diff-hl-inline-popup--ignorable-command-p this-command)))) - (unless allowed-command - (diff-hl-inline-popup-hide)))) - -(define-minor-mode diff-hl-inline-popup-transient-mode - "Temporal minor mode to control an inline popup" - :global nil - (remove-hook 'post-command-hook #'diff-hl-inline-popup--post-command-hook t) - (set-keymap-parent diff-hl-inline-popup-transient-mode-map nil) - - (when diff-hl-inline-popup-transient-mode - (set-keymap-parent diff-hl-inline-popup-transient-mode-map - diff-hl-inline-popup--current-custom-keymap) - (add-hook 'post-command-hook #'diff-hl-inline-popup--post-command-hook 0 t))) - -;;;###autoload -(defun diff-hl-inline-popup-hide() - "Hide the current inline popup." - (interactive) - (when diff-hl-inline-popup-transient-mode - (diff-hl-inline-popup-transient-mode -1)) - (when diff-hl-inline-popup--close-hook - (funcall diff-hl-inline-popup--close-hook) - (setq diff-hl-inline-popup--close-hook nil)) - (when diff-hl-inline-popup--current-popup - (delete-overlay diff-hl-inline-popup--current-popup) - (setq diff-hl-inline-popup--current-popup nil))) - -;;;###autoload -(defun diff-hl-inline-popup-show (lines &optional header footer keymap close-hook point height) - "Create a phantom overlay to show the inline popup, with some -content LINES, and a HEADER and a FOOTER, at POINT. KEYMAP is -added to the current keymaps. CLOSE-HOOK is called when the popup -is closed." - (when diff-hl-inline-popup--current-popup - (delete-overlay diff-hl-inline-popup--current-popup) - (setq diff-hl-inline-popup--current-popup nil)) - - (when (< (diff-hl-inline-popup--compute-content-height 99) 2) - (user-error "There is no enough vertical space to show the inline popup")) - (let* ((the-point (or point (line-end-position))) - (the-buffer (current-buffer)) - (overlay (make-overlay the-point the-point the-buffer))) - (overlay-put overlay 'phantom t) - (overlay-put overlay 'diff-hl-inline-popup t) - (setq diff-hl-inline-popup--current-popup overlay) - - (setq diff-hl-inline-popup--current-lines - (mapcar (lambda (s) (replace-regexp-in-string "\n" " " s)) lines)) - (setq diff-hl-inline-popup--current-header header) - (setq diff-hl-inline-popup--current-footer footer) - (setq diff-hl-inline-popup--invokinkg-command this-command) - (setq diff-hl-inline-popup--current-custom-keymap keymap) - (setq diff-hl-inline-popup--close-hook close-hook) - (setq diff-hl-inline-popup--height (diff-hl-inline-popup--compute-content-height height)) - (setq diff-hl-inline-popup--height (min diff-hl-inline-popup--height - (length diff-hl-inline-popup--current-lines))) - ;; (diff-hl-inline-popup--ensure-enough-lines point diff-hl-inline-popup--height) - (diff-hl-inline-popup-transient-mode 1) - (diff-hl-inline-popup-scroll-to 0) - overlay)) - -(defun diff-hl-inline-popup--hide-all () - "Testing purposes, use in case some inline popups get stuck in a buffer." - (interactive) - (when diff-hl-inline-popup-transient-mode - (diff-hl-inline-popup-transient-mode -1)) - (setq diff-hl-inline-popup--current-popup nil) - (let* ((all-overlays (overlays-in (point-min) (point-max))) - (overlays (cl-remove-if-not (lambda (o)(overlay-get o 'diff-hl-inline-popup)) all-overlays))) - (dolist (o overlays) - (delete-overlay o)))) - -(provide 'diff-hl-inline-popup) -;;; diff-hl-inline-popup ends here diff --git a/lisp/diff-hl/diff-hl-margin.el b/lisp/diff-hl/diff-hl-margin.el index 3f142a2c..26c1e718 100644 --- a/lisp/diff-hl/diff-hl-margin.el +++ b/lisp/diff-hl/diff-hl-margin.el @@ -40,6 +40,8 @@ (defvar diff-hl-margin-old-highlight-function nil) +(defvar diff-hl-margin-old-highlight-ref-function nil) + (defvar diff-hl-margin-old-width nil) (defgroup diff-hl-margin nil @@ -66,13 +68,25 @@ '((default :inherit dired-ignored)) "Face used to highlight changed lines on the margin.") +(defface diff-hl-margin-reference-insert + '((default :inherit diff-hl-reference-insert)) + "Face used to highlight lines inserted since reference rev on the margin.") + +(defface diff-hl-margin-reference-delete + '((default :inherit diff-hl-reference-delete)) + "Face used to highlight lines deleted since reference rev on the margin.") + +(defface diff-hl-margin-reference-change + '((default :inherit diff-hl-reference-change)) + "Face used to highlight changed since reference rev on the margin.") + (defcustom diff-hl-margin-symbols-alist '((insert . "+") (delete . "-") (change . "!") - (unknown . "?") (ignored . "i")) + (unknown . "?") (ignored . "i") (reference . " ")) "Associative list from symbols to strings." :type '(alist :key-type symbol :value-type string - :options (insert delete change unknown ignored)) + :options (insert delete change unknown ignored reference)) :set (lambda (symbol value) (defvar diff-hl-margin-spec-cache) (set-default symbol value) @@ -112,12 +126,17 @@ You probably shouldn't use this function directly." (progn (setq-local diff-hl-margin-old-highlight-function diff-hl-highlight-function) + (setq-local diff-hl-margin-old-highlight-ref-function + diff-hl-highlight-reference-function) (setq-local diff-hl-highlight-function #'diff-hl-highlight-on-margin) + (setq-local diff-hl-highlight-reference-function + #'diff-hl-highlight-on-margin-flat) (setq-local diff-hl-margin-old-width (symbol-value width-var)) (set width-var 1)) (when diff-hl-margin-old-highlight-function (setq diff-hl-highlight-function diff-hl-margin-old-highlight-function + diff-hl-highlight-reference-function diff-hl-margin-old-highlight-ref-function diff-hl-margin-old-highlight-function nil)) (set width-var diff-hl-margin-old-width) (kill-local-variable 'diff-hl-margin-old-width))) @@ -135,17 +154,32 @@ You probably shouldn't use this function directly." (diff-hl-margin-build-spec-cache)))) (defun diff-hl-margin-build-spec-cache () - (cl-loop for (type . char) in diff-hl-margin-symbols-alist - nconc - (cl-loop for side in '(left right) - collect - (cons - (cons type side) - (propertize - " " 'display - `((margin ,(intern (format "%s-margin" side))) - ,(propertize char 'face - (intern (format "diff-hl-margin-%s" type))))))))) + (nconc + (cl-loop for (type . char) in diff-hl-margin-symbols-alist + unless (eq type 'reference) + nconc + (cl-loop for side in '(left right) + collect + (cons + (cons type side) + (propertize + " " 'display + `((margin ,(intern (format "%s-margin" side))) + ,(propertize char 'face + (intern (format "diff-hl-margin-%s" type)))))))) + (cl-loop for char = (or (assoc-default 'reference diff-hl-margin-symbols-alist) + " ") + for type in '(insert delete change) + nconc + (cl-loop for side in '(left right) + collect + (cons + (list type side 'reference) + (propertize + " " 'display + `((margin ,(intern (format "%s-margin" side))) + ,(propertize char 'face + (intern (format "diff-hl-margin-reference-%s" type)))))))))) (defun diff-hl-margin-ensure-visible () (let ((width-var (intern (format "%s-margin-width" diff-hl-side)))) @@ -160,6 +194,11 @@ You probably shouldn't use this function directly." (diff-hl-margin-spec-cache))))) (overlay-put ovl 'before-string spec))) +(defun diff-hl-highlight-on-margin-flat (ovl type _shape) + (let ((spec (cdr (assoc (list type diff-hl-side 'reference) + (diff-hl-margin-spec-cache))))) + (overlay-put ovl 'before-string spec))) + (provide 'diff-hl-margin) ;;; diff-hl-margin.el ends here diff --git a/lisp/diff-hl/diff-hl-pkg.el b/lisp/diff-hl/diff-hl-pkg.el index 10f2de1f..518fcf9b 100644 --- a/lisp/diff-hl/diff-hl-pkg.el +++ b/lisp/diff-hl/diff-hl-pkg.el @@ -1,11 +1,11 @@ ;; -*- no-byte-compile: t; lexical-binding: nil -*- -(define-package "diff-hl" "20250710.145" +(define-package "diff-hl" "20251125.238" "Highlight uncommitted changes using VC." '((cl-lib "0.2") (emacs "26.1")) :url "https://github.com/dgutov/diff-hl" - :commit "08243a6e0b681c34eb4e4abf1d1c4c1b251ce91e" - :revdesc "08243a6e0b68" + :commit "8dc486f568afa08dcf9932f4045677df6f5a23f8" + :revdesc "8dc486f568af" :keywords '("vc" "diff") :authors '(("Dmitry Gutov" . "dmitry@gutov.dev")) :maintainers '(("Dmitry Gutov" . "dmitry@gutov.dev"))) diff --git a/lisp/diff-hl/diff-hl-show-hunk-inline.el b/lisp/diff-hl/diff-hl-show-hunk-inline.el new file mode 100644 index 00000000..3b5214bb --- /dev/null +++ b/lisp/diff-hl/diff-hl-show-hunk-inline.el @@ -0,0 +1,405 @@ +;;; diff-hl-show-hunk-inline.el --- inline popup using phantom overlays -*- lexical-binding: t -*- + +;; Copyright (C) 2020-2025 Free Software Foundation, Inc. + +;; Author: Álvaro González + +;; 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 . + +;;; Commentary: +;; Shows inline popups using phantom overlays. The lines of the popup +;; can be scrolled. +;;; Code: + +(require 'subr-x) +(require 'diff-hl-show-hunk) + +(define-obsolete-variable-alias 'diff-hl-inline-popup--current-lines 'diff-hl-show-hunk-inline--current-lines "0.11.0") +(define-obsolete-variable-alias 'diff-hl-inline-popup--current-index 'diff-hl-show-hunk-inline--current-index "0.11.0") +(define-obsolete-variable-alias 'diff-hl-inline-popup--invoking-command 'diff-hl-show-hunk-inline--invoking-command "0.11.0") +(define-obsolete-variable-alias 'diff-hl-inline-popup--current-footer 'diff-hl-show-hunk-inline--current-footer "0.11.0") +(define-obsolete-variable-alias 'diff-hl-inline-popup--current-header 'diff-hl-show-hunk-inline--current-header "0.11.0") +(define-obsolete-variable-alias 'diff-hl-inline-popup--height 'diff-hl-show-hunk-inline--height "0.11.0") +(define-obsolete-variable-alias 'diff-hl-inline-popup--current-custom-keymap 'diff-hl-show-hunk-inline--current-custom-keymap "0.11.0") +(define-obsolete-variable-alias 'diff-hl-inline-popup--close-hook 'diff-hl-show-hunk-inline--close-hook "0.11.0") +(define-obsolete-variable-alias 'diff-hl-show-hunk-inline-popup-hide-hunk 'diff-hl-show-hunk-inline-hide-hunk "0.11.0") +(define-obsolete-variable-alias 'diff-hl-show-hunk-inline-popup-smart-lines 'diff-hl-show-hunk-inline-smart-lines "0.11.0") +(define-obsolete-variable-alias 'diff-hl-inline-popup-transient-mode-map 'diff-hl-show-hunk-inline-transient-mode-map "0.11.0") + +(defvar diff-hl-show-hunk-inline--current-popup nil "The overlay of the current inline popup.") +(defvar diff-hl-show-hunk-inline--current-lines nil "A list of the lines to show in the popup.") +(defvar diff-hl-show-hunk-inline--current-index nil "First line showed in popup.") +(defvar diff-hl-show-hunk-inline--invoking-command nil "Command that invoked the popup.") +(defvar diff-hl-show-hunk-inline--current-footer nil "String to be displayed in the footer.") +(defvar diff-hl-show-hunk-inline--current-header nil "String to be displayed in the header.") +(defvar diff-hl-show-hunk-inline--height nil "Height of the popup.") +(defvar diff-hl-show-hunk-inline--current-custom-keymap nil "Keymap to be added to the keymap of the inline popup.") +(defvar diff-hl-show-hunk-inline--close-hook nil "Function to be called when the popup closes.") + +(make-variable-buffer-local 'diff-hl-show-hunk-inline--current-popup) +(make-variable-buffer-local 'diff-hl-show-hunk-inline--current-lines) +(make-variable-buffer-local 'diff-hl-show-hunk-inline--current-index) +(make-variable-buffer-local 'diff-hl-show-hunk-inline--current-header) +(make-variable-buffer-local 'diff-hl-show-hunk-inline--current-footer) +(make-variable-buffer-local 'diff-hl-show-hunk-inline--invoking-command) +(make-variable-buffer-local 'diff-hl-show-hunk-inline--current-custom-keymap) +(make-variable-buffer-local 'diff-hl-show-hunk-inline--height) +(make-variable-buffer-local 'diff-hl-show-hunk-inline--close-hook) + +(defgroup diff-hl-show-hunk-inline nil + "Show vc diffs inline inside a buffer." + :group 'diff-hl-show-hunk) + +(defcustom diff-hl-show-hunk-inline-hide-hunk nil + "If t, inline-popup is shown over the hunk, hiding it." + :type 'boolean) + +(defcustom diff-hl-show-hunk-inline-smart-lines t + "If t, inline-popup tries to show only the deleted lines of the +hunk. The added lines are shown when scrolling the popup. If +the hunk consist only on added lines, then +`diff-hl-show-hunk--no-lines-removed-message' it is shown." + :type 'boolean) + +(defun diff-hl-show-hunk-inline--splice (list offset length) + "Compute a sublist of LIST starting at OFFSET, of LENGTH." + (butlast + (nthcdr offset list) + (- (length list) length offset))) + +(defun diff-hl-show-hunk-inline--ensure-enough-lines (pos content-height) + "Ensure there is enough lines below POS to show the inline popup. +CONTENT-HEIGHT specifies the height of the popup." + (let* ((line (line-number-at-pos pos)) + (end (line-number-at-pos (window-end nil t))) + (height (+ 6 content-height)) + (overflow (- (+ line height) end))) + (when (< 0 overflow) + (run-with-timer 0.1 nil #'scroll-up overflow)))) + +(defun diff-hl-show-hunk-inline--compute-content-height (&optional content-size) + "Compute the height of the inline popup. +Default for CONTENT-SIZE is the size of the current lines" + (let ((content-size (or content-size (length diff-hl-show-hunk-inline--current-lines))) + (max-size (- (/(window-height) 2) 3))) + (min content-size max-size))) + +(defun diff-hl-show-hunk-inline--compute-content-lines (lines index window-size) + "Compute the lines to show in the popup. +Compute it from LINES starting at INDEX with a WINDOW-SIZE." + (let* ((len (length lines)) + (window-size (min window-size len)) + (index (min index (- len window-size)))) + (diff-hl-show-hunk-inline--splice lines index window-size))) + +(defun diff-hl-show-hunk-inline--compute-header (width &optional header) + "Compute the header of the popup. +Compute it from some WIDTH, and some optional HEADER text." + (let* ((scroll-indicator (if (eq diff-hl-show-hunk-inline--current-index 0) " " " ⬆ ")) + (header (or header "")) + (new-width (- width (length header) (length scroll-indicator))) + (header (if (< new-width 0) "" header)) + (new-width (- width (length header) (length scroll-indicator))) + (line (propertize (concat (diff-hl-show-hunk-inline--separator new-width) + header scroll-indicator ) + 'face '(:underline t)))) + (concat line "\n") )) + +(defun diff-hl-show-hunk-inline--compute-footer (width &optional footer) + "Compute the header of the popup. +Compute it from some WIDTH, and some optional FOOTER text." + (let* ((scroll-indicator (if (>= diff-hl-show-hunk-inline--current-index + (- (length diff-hl-show-hunk-inline--current-lines) + diff-hl-show-hunk-inline--height)) + " " + " ⬇ ")) + (footer (or footer "")) + (new-width (- width (length footer) (length scroll-indicator))) + (footer (if (< new-width 0) "" footer)) + (new-width (- width (length footer) (length scroll-indicator))) + (blank-line (if (display-graphic-p) + "" + (concat "\n" (propertize (diff-hl-show-hunk-inline--separator width) + 'face '(:underline t))))) + (line (propertize (concat (diff-hl-show-hunk-inline--separator new-width) + footer scroll-indicator) + 'face '(:overline t)))) + (concat blank-line "\n" line))) + +(defun diff-hl-show-hunk-inline--separator (width &optional sep) + "Return the horizontal separator with character SEP and a WIDTH." + (let ((sep (or sep ?\s))) + (make-string width sep))) + +(defun diff-hl-show-hunk-inline--available-width () + "Compute the available width in chars." + (let ((magic-adjust 3)) + (if (not (display-graphic-p)) + (let* ((linumber-width (line-number-display-width nil)) + (width (- (window-body-width) linumber-width magic-adjust))) + width) + (let* ((font-width (window-font-width)) + (window-width (window-body-width nil t)) + (linenumber-width (line-number-display-width t)) + (available-pixels (- window-width linenumber-width)) + (width (- (/ available-pixels font-width) magic-adjust))) + + ;; https://emacs.stackexchange.com/questions/5495/how-can-i-determine-the-width-of-characters-on-the-screen + width)))) + +(defun diff-hl-show-hunk-inline--compute-popup-str (lines index window-size header footer) + "Compute the string that represents the popup. +There are some content LINES starting at INDEX, with a WINDOW-SIZE. HEADER and +FOOTER are showed at start and end." + (let* ((width (diff-hl-show-hunk-inline--available-width)) + (content-lines (diff-hl-show-hunk-inline--compute-content-lines lines index window-size)) + (header (diff-hl-show-hunk-inline--compute-header width header)) + (footer (diff-hl-show-hunk-inline--compute-footer width footer))) + (concat header (string-join content-lines "\n") footer "\n"))) + +(defun diff-hl-show-hunk-inline-scroll-to (index) + "Scroll the inline popup to make visible the line at position INDEX." + (when diff-hl-show-hunk-inline--current-popup + (setq diff-hl-show-hunk-inline--current-index (max 0 (min index (- (length diff-hl-show-hunk-inline--current-lines) diff-hl-show-hunk-inline--height)))) + (let* ((str (diff-hl-show-hunk-inline--compute-popup-str + diff-hl-show-hunk-inline--current-lines + diff-hl-show-hunk-inline--current-index + diff-hl-show-hunk-inline--height + diff-hl-show-hunk-inline--current-header + diff-hl-show-hunk-inline--current-footer))) + ;; https://debbugs.gnu.org/38563, `company--replacement-string'. + (add-face-text-property 0 (length str) 'default t str) + (put-text-property 0 1 'cursor 0 str) + (overlay-put diff-hl-show-hunk-inline--current-popup 'before-string str)))) + +(defun diff-hl-show-hunk-inline--popup-down() + "Scrolls one line down." + (interactive) + (diff-hl-show-hunk-inline-scroll-to (1+ diff-hl-show-hunk-inline--current-index) )) + +(defun diff-hl-show-hunk-inline--popup-up() + "Scrolls one line up." + (interactive) + (diff-hl-show-hunk-inline-scroll-to (1- diff-hl-show-hunk-inline--current-index) )) + +(defun diff-hl-show-hunk-inline--popup-pagedown() + "Scrolls one page down." + (interactive) + (diff-hl-show-hunk-inline-scroll-to (+ diff-hl-show-hunk-inline--current-index diff-hl-show-hunk-inline--height) )) + +(defun diff-hl-show-hunk-inline--popup-pageup() + "Scrolls one page up." + (interactive) + (diff-hl-show-hunk-inline-scroll-to (- diff-hl-show-hunk-inline--current-index diff-hl-show-hunk-inline--height) )) + +(defvar diff-hl-show-hunk-inline-transient-mode-map + (let ((map (make-sparse-keymap))) + (define-key map (kbd "") #'diff-hl-show-hunk-inline--popup-pageup) + (define-key map (kbd "M-v") #'diff-hl-show-hunk-inline--popup-pageup) + (define-key map (kbd "") #'diff-hl-show-hunk-inline--popup-pagedown) + (define-key map (kbd "C-v") #'diff-hl-show-hunk-inline--popup-pagedown) + (define-key map (kbd "") #'diff-hl-show-hunk-inline--popup-up) + (define-key map (kbd "C-p") #'diff-hl-show-hunk-inline--popup-up) + (define-key map (kbd "") #'diff-hl-show-hunk-inline--popup-down) + (define-key map (kbd "C-n") #'diff-hl-show-hunk-inline--popup-down) + (define-key map (kbd "C-g") #'diff-hl-show-hunk-inline-hide) + (define-key map [escape] #'diff-hl-show-hunk-inline-hide) + (define-key map (kbd "q") #'diff-hl-show-hunk-inline-hide) + ;;http://ergoemacs.org/emacs/emacs_mouse_wheel_config.html + (define-key map (kbd "") #'diff-hl-show-hunk-inline--popup-up) + (define-key map (kbd "") #'diff-hl-show-hunk-inline--popup-up) + (define-key map (kbd "") #'diff-hl-show-hunk-inline--popup-down) + (define-key map (kbd "") #'diff-hl-show-hunk-inline--popup-down) + map) + "Keymap for command `diff-hl-show-hunk-inline-transient-mode'. +Capture all the vertical movement of the point, and converts it +to scroll in the popup") + +(defun diff-hl-show-hunk-inline--ignorable-command-p (command) + "Decide if COMMAND is a command allowed while showing an inline popup." + ;; https://emacs.stackexchange.com/questions/653/how-can-i-find-out-in-which-keymap-a-key-is-bound + (let ((keys (where-is-internal command (list diff-hl-show-hunk-inline--current-custom-keymap + diff-hl-show-hunk-inline-transient-mode-map ) t)) + (invoking (eq command diff-hl-show-hunk-inline--invoking-command))) + (or keys invoking))) + +(defun diff-hl-show-hunk-inline--post-command-hook () + "Called each time a command is executed." + (let ((allowed-command (or + (diff-hl-show-hunk-ignorable-command-p this-command) + (string-match-p "diff-hl-show-hunk-inline-" (symbol-name this-command)) + (diff-hl-show-hunk-inline--ignorable-command-p this-command)))) + (unless allowed-command + (diff-hl-show-hunk-inline-hide)))) + +(define-minor-mode diff-hl-show-hunk-inline-transient-mode + "Temporal minor mode to control an inline popup" + :global nil + (remove-hook 'post-command-hook #'diff-hl-show-hunk-inline--post-command-hook t) + (set-keymap-parent diff-hl-show-hunk-inline-transient-mode-map nil) + + (when diff-hl-show-hunk-inline-transient-mode + (set-keymap-parent diff-hl-show-hunk-inline-transient-mode-map + diff-hl-show-hunk-inline--current-custom-keymap) + (add-hook 'post-command-hook #'diff-hl-show-hunk-inline--post-command-hook 0 t))) + +;;;###autoload +(defun diff-hl-show-hunk-inline-hide() + "Hide the current inline popup." + (interactive) + (when diff-hl-show-hunk-inline-transient-mode + (diff-hl-show-hunk-inline-transient-mode -1)) + (when diff-hl-show-hunk-inline--close-hook + (funcall diff-hl-show-hunk-inline--close-hook) + (setq diff-hl-show-hunk-inline--close-hook nil)) + (when diff-hl-show-hunk-inline--current-popup + (delete-overlay diff-hl-show-hunk-inline--current-popup) + (setq diff-hl-show-hunk-inline--current-popup nil))) + +;;;###autoload +(defun diff-hl-show-hunk-inline-show (lines &optional header footer keymap close-hook point height) + "Create a phantom overlay to show the inline popup, with some +content LINES, and a HEADER and a FOOTER, at POINT. KEYMAP is +added to the current keymaps. CLOSE-HOOK is called when the popup +is closed." + (when diff-hl-show-hunk-inline--current-popup + (delete-overlay diff-hl-show-hunk-inline--current-popup) + (setq diff-hl-show-hunk-inline--current-popup nil)) + + (when (< (diff-hl-show-hunk-inline--compute-content-height 99) 2) + (user-error "There is no enough vertical space to show the inline popup")) + (let* ((the-point (or point (line-end-position))) + (the-buffer (current-buffer)) + (overlay (make-overlay the-point the-point the-buffer))) + (overlay-put overlay 'phantom t) + (overlay-put overlay 'diff-hl-show-hunk-inline t) + (setq diff-hl-show-hunk-inline--current-popup overlay) + + (setq diff-hl-show-hunk-inline--current-lines + (mapcar (lambda (s) (replace-regexp-in-string "\n" " " s)) lines)) + (setq diff-hl-show-hunk-inline--current-header header) + (setq diff-hl-show-hunk-inline--current-footer footer) + (setq diff-hl-show-hunk-inline--invoking-command this-command) + (setq diff-hl-show-hunk-inline--current-custom-keymap keymap) + (setq diff-hl-show-hunk-inline--close-hook close-hook) + (setq diff-hl-show-hunk-inline--height (diff-hl-show-hunk-inline--compute-content-height height)) + (setq diff-hl-show-hunk-inline--height (min diff-hl-show-hunk-inline--height + (length diff-hl-show-hunk-inline--current-lines))) + ;; (diff-hl-show-hunk-inline--ensure-enough-lines point diff-hl-show-hunk-inline--height) + (diff-hl-show-hunk-inline-transient-mode 1) + (diff-hl-show-hunk-inline-scroll-to 0) + overlay)) + +(defun diff-hl-show-hunk-inline--hide-all () + "Testing purposes, use in case some inline popups get stuck in a buffer." + (interactive) + (when diff-hl-show-hunk-inline-transient-mode + (diff-hl-show-hunk-inline-transient-mode -1)) + (setq diff-hl-show-hunk-inline--current-popup nil) + (let* ((all-overlays (overlays-in (point-min) (point-max))) + (overlays (cl-remove-if-not (lambda (o)(overlay-get o 'diff-hl-show-hunk-inline)) all-overlays))) + (dolist (o overlays) + (delete-overlay o)))) + +;;;###autoload +(defun diff-hl-show-hunk-inline (buffer &optional _ignored-line) + "Implementation to show the hunk in a inline popup. +BUFFER is a buffer with the hunk." + ;; prevent diff-hl-show-hunk-inline-hide from being called twice + (let ((diff-hl-show-hunk-inline--close-hook nil)) + (diff-hl-show-hunk-inline-hide)) + (setq diff-hl-show-hunk--hide-function #'diff-hl-show-hunk-inline-hide) + (let* ((lines (split-string (with-current-buffer buffer (buffer-string)) "[\n\r]+" )) + (smart-lines diff-hl-show-hunk-inline-smart-lines) + (original-lines-number (cl-count-if (lambda (s) (string-prefix-p "-" s)) lines)) + (lines (if (string= (car (last lines)) "" ) (butlast lines) lines)) + (lines (if (and (eq original-lines-number 0) smart-lines) + diff-hl-show-hunk--no-lines-removed-message + lines)) + (overlay diff-hl-show-hunk--original-overlay) + (type (overlay-get overlay 'diff-hl-hunk-type)) + (point (if (eq type 'delete) (overlay-start overlay) (overlay-end overlay))) + (propertize-line (lambda (l) + (propertize l 'face + (cond ((string-prefix-p "+" l) + 'diff-added) + ((string-prefix-p "-" l) + 'diff-removed))))) + (propertized-lines (mapcar propertize-line lines))) + + (save-excursion + ;; Save point in case the hunk is hidden, so next/previous works as expected + ;; If the hunk is delete type, then don't hide the hunk + ;; (because the hunk is located in a non deleted line) + (when (and diff-hl-show-hunk-inline-hide-hunk + (not (eq type 'delete))) + (let* ((invisible-overlay (make-overlay (overlay-start overlay) + (overlay-end overlay)))) + ;; Make new overlay, since the diff-hl overlay can be changed by diff-hl-flydiff + (overlay-put invisible-overlay 'invisible t) + ;; Change default hide popup function, to make the overlay visible + (setq diff-hl-show-hunk--hide-function + (lambda () + (overlay-put invisible-overlay 'invisible nil) + (delete-overlay invisible-overlay) + (diff-hl-show-hunk-inline-hide))))) + (diff-hl-show-hunk--goto-hunk-overlay overlay) + (let ((height + (when smart-lines + (when (not (eq 0 original-lines-number)) + original-lines-number))) + (footer "(q)Quit (p)Previous (n)Next (r)Revert (c)Copy original")) + (unless diff-hl-show-staged-changes + (setq footer (concat footer " (S)Stage"))) + (diff-hl-show-hunk-inline-show + propertized-lines + (if (and (boundp 'diff-hl-reference-revision) diff-hl-reference-revision) + (concat "Diff with " diff-hl-reference-revision) + "Diff with HEAD") + footer + diff-hl-show-hunk-map + #'diff-hl-show-hunk-hide + point + height)) + ))) + +(define-obsolete-function-alias 'diff-hl-inline-popup--splice 'diff-hl-show-hunk-inline--splice "0.11.0") +(define-obsolete-function-alias 'diff-hl-inline-popup--ensure-enough-lines 'diff-hl-show-hunk-inline--ensure-enough-lines "0.11.0") +(define-obsolete-function-alias 'diff-hl-inline-popup--compute-content-height 'diff-hl-show-hunk-inline--compute-content-height "0.11.0") +(define-obsolete-function-alias 'diff-hl-inline-popup--compute-content-lines 'diff-hl-show-hunk-inline--compute-content-lines "0.11.0") +(define-obsolete-function-alias 'diff-hl-inline-popup--compute-header 'diff-hl-show-hunk-inline--compute-header "0.11.0") +(define-obsolete-function-alias 'diff-hl-inline-popup--compute-footer 'diff-hl-show-hunk-inline--compute-footer "0.11.0") +(define-obsolete-function-alias 'diff-hl-inline-popup--separator 'diff-hl-show-hunk-inline--separator "0.11.0") +(define-obsolete-function-alias 'diff-hl-inline-popup--available-width 'diff-hl-show-hunk-inline--available-width "0.11.0") +(define-obsolete-function-alias 'diff-hl-inline-popup--compute-popup-str 'diff-hl-show-hunk-inline--compute-popup-str "0.11.0") +(define-obsolete-function-alias 'diff-hl-inline-popup-scroll-to 'diff-hl-show-hunk-inline-scroll-to "0.11.0") +(define-obsolete-function-alias 'diff-hl-inline-popup--popup-down 'diff-hl-show-hunk-inline--popup-down "0.11.0") +(define-obsolete-function-alias 'diff-hl-inline-popup--popup-up 'diff-hl-show-hunk-inline--popup-up "0.11.0") +(define-obsolete-function-alias 'diff-hl-inline-popup--popup-pagedown 'diff-hl-show-hunk-inline--popup-pagedown "0.11.0") +(define-obsolete-function-alias 'diff-hl-inline-popup--popup-pageup 'diff-hl-show-hunk-inline--popup-pageup "0.11.0") +(define-obsolete-function-alias 'diff-hl-inline-popup--ignorable-command-p 'diff-hl-show-hunk-inline--ignorable-command-p "0.11.0") +(define-obsolete-function-alias 'diff-hl-inline-popup--post-command-hook 'diff-hl-show-hunk-inline--post-command-hook "0.11.0") +(define-obsolete-function-alias 'diff-hl-inline-popup-transient-mode 'diff-hl-show-hunk-inline-transient-mode "0.11.0") +(define-obsolete-function-alias 'diff-hl-inline-popup-hide 'diff-hl-show-hunk-inline-hide "0.11.0") +(define-obsolete-function-alias 'diff-hl-inline-popup-show 'diff-hl-show-hunk-inline-show "0.11.0") +(define-obsolete-function-alias 'diff-hl-inline-popup--hide-all 'diff-hl-show-hunk-inline--hide-all "0.11.0") + +(define-obsolete-function-alias 'diff-hl-show-hunk-inline-popup 'diff-hl-show-hunk-inline "0.11.0") + +(provide 'diff-hl-inline-popup) + +(provide 'diff-hl-show-hunk-inline) +;;; diff-hl-show-hunk-inline ends here diff --git a/lisp/diff-hl/diff-hl-show-hunk.el b/lisp/diff-hl/diff-hl-show-hunk.el index 45f201c9..c23ccb5c 100644 --- a/lisp/diff-hl/diff-hl-show-hunk.el +++ b/lisp/diff-hl/diff-hl-show-hunk.el @@ -22,9 +22,9 @@ ;;; Commentary: ;; `diff-hl-show-hunk' shows a popup with the modification hunk at point. -;; `diff-hl-show-hunk-function' points to the backend used to show the -;; hunk. Its default value is `diff-hl-show-hunk-inline-popup', that -;; shows diffs inline using overlay. There is another built-in backend: +;; `diff-hl-show-hunk-function' points to the backend used to show the hunk. +;; Its default value is `diff-hl-show-hunk-inline', that shows diffs inline +;; using overlay. There is another built-in backend: ;; `diff-hl-show-hunk-posframe' (based on posframe). ;; ;; `diff-hl-show-hunk-mouse-mode' adds interaction on clicking in the @@ -36,9 +36,31 @@ ;;; Code: -(require 'diff-hl-inline-popup) (require 'diff-hl) +(defgroup diff-hl-show-hunk nil + "Show vc diffs in a posframe or popup." + :group 'diff-hl) + +(defcustom diff-hl-show-hunk-ignorable-commands + '(ignore + diff-hl-show-hunk + handle-switch-frame + diff-hl-show-hunk--click) + "Commands that will keep the hunk shown. +Any command not on this list will cause the hunk to be hidden." + :type '(repeat function) + :group 'diff-hl-show-hunk) + +(defcustom diff-hl-show-hunk-function 'diff-hl-show-hunk-inline + "The function used to render the hunk. +The function receives as first parameter a buffer with the +contents of the hunk, and as second parameter the line number +corresponding to the clicked line in the original buffer." + :type '(choice + (const :tag "Show inline" diff-hl-show-hunk-inline) + (const :tag "Show using posframe" diff-hl-show-hunk-posframe))) + (defvar diff-hl-show-hunk-mouse-mode-map (let ((map (make-sparse-keymap))) (define-key map (kbd " ") 'diff-hl-show-hunk--click) @@ -66,33 +88,9 @@ (defvar diff-hl-show-hunk--original-overlay nil "Copy of the diff-hl hunk overlay.") -(defgroup diff-hl-show-hunk nil - "Show vc diffs in a posframe or popup." - :group 'diff-hl) - (defconst diff-hl-show-hunk-boundary "^@@.*@@") (defconst diff-hl-show-hunk--no-lines-removed-message (list "<>")) -(defcustom diff-hl-show-hunk-inline-popup-hide-hunk nil - "If t, inline-popup is shown over the hunk, hiding it." - :type 'boolean) - -(defcustom diff-hl-show-hunk-inline-popup-smart-lines t - "If t, inline-popup tries to show only the deleted lines of the -hunk. The added lines are shown when scrolling the popup. If -the hunk consist only on added lines, then -`diff-hl-show-hunk--no-lines-removed-message' it is shown." - :type 'boolean) - -(defcustom diff-hl-show-hunk-function 'diff-hl-show-hunk-inline-popup - "The function used to render the hunk. -The function receives as first parameter a buffer with the -contents of the hunk, and as second parameter the line number -corresponding to the clicked line in the original buffer." - :type '(choice - (const :tag "Show inline" diff-hl-show-hunk-inline-popup) - (const :tag "Show using posframe" diff-hl-show-hunk-posframe))) - (defvar diff-hl-show-hunk--hide-function nil "Function to call to close the shown hunk.") @@ -123,7 +121,7 @@ corresponding to the clicked line in the original buffer." (defun diff-hl-show-hunk-ignorable-command-p (command) "Decide if COMMAND is a command allowed while showing the current hunk." - (member command '(ignore diff-hl-show-hunk handle-switch-frame diff-hl-show-hunk--click))) + (member command diff-hl-show-hunk-ignorable-commands)) (defun diff-hl-show-hunk--compute-diffs () "Compute diffs using functions of diff-hl. @@ -136,7 +134,10 @@ buffer." (line (line-number-at-pos)) (dest-buffer diff-hl-show-hunk-diff-buffer-name)) (with-current-buffer buffer - (diff-hl-diff-buffer-with-reference (buffer-file-name buffer) dest-buffer) + (if (buffer-modified-p) + (diff-hl-diff-buffer-with-reference buffer-file-name dest-buffer) + (diff-hl-changes-buffer buffer-file-name (vc-backend buffer-file-name) + nil dest-buffer)) (switch-to-buffer dest-buffer) (diff-hl-diff-skip-to line) (setq vc-sentinel-movepoint (point))) @@ -226,68 +227,6 @@ Returns a list with the buffer and the line number of the clicked line." (define-key map (kbd "S") #'diff-hl-show-hunk-stage-hunk) map)) -(defvar diff-hl-show-hunk--hide-function) - -;;;###autoload -(defun diff-hl-show-hunk-inline-popup (buffer &optional _ignored-line) - "Implementation to show the hunk in a inline popup. -BUFFER is a buffer with the hunk." - (diff-hl-inline-popup-hide) - (setq diff-hl-show-hunk--hide-function #'diff-hl-inline-popup-hide) - (let* ((lines (split-string (with-current-buffer buffer (buffer-string)) "[\n\r]+" )) - (smart-lines diff-hl-show-hunk-inline-popup-smart-lines) - (original-lines-number (cl-count-if (lambda (s) (string-prefix-p "-" s)) lines)) - (lines (if (string= (car (last lines)) "" ) (butlast lines) lines)) - (lines (if (and (eq original-lines-number 0) smart-lines) - diff-hl-show-hunk--no-lines-removed-message - lines)) - (overlay diff-hl-show-hunk--original-overlay) - (type (overlay-get overlay 'diff-hl-hunk-type)) - (point (if (eq type 'delete) (overlay-start overlay) (overlay-end overlay))) - (propertize-line (lambda (l) - (propertize l 'face - (cond ((string-prefix-p "+" l) - 'diff-added) - ((string-prefix-p "-" l) - 'diff-removed))))) - (propertized-lines (mapcar propertize-line lines))) - - (save-excursion - ;; Save point in case the hunk is hidden, so next/previous works as expected - ;; If the hunk is delete type, then don't hide the hunk - ;; (because the hunk is located in a non deleted line) - (when (and diff-hl-show-hunk-inline-popup-hide-hunk - (not (eq type 'delete))) - (let* ((invisible-overlay (make-overlay (overlay-start overlay) - (overlay-end overlay)))) - ;; Make new overlay, since the diff-hl overlay can be changed by diff-hl-flydiff - (overlay-put invisible-overlay 'invisible t) - ;; Change default hide popup function, to make the overlay visible - (setq diff-hl-show-hunk--hide-function - (lambda () - (overlay-put invisible-overlay 'invisible nil) - (delete-overlay invisible-overlay) - (diff-hl-inline-popup-hide))))) - (diff-hl-show-hunk--goto-hunk-overlay overlay) - (let ((height - (when smart-lines - (when (not (eq 0 original-lines-number)) - original-lines-number))) - (footer "(q)Quit (p)Previous (n)Next (r)Revert (c)Copy original")) - (unless diff-hl-show-staged-changes - (setq footer (concat footer " (S)Stage"))) - (diff-hl-inline-popup-show - propertized-lines - (if (and (boundp 'diff-hl-reference-revision) diff-hl-reference-revision) - (concat "Diff with " diff-hl-reference-revision) - "Diff with HEAD") - footer - diff-hl-show-hunk-map - #'diff-hl-show-hunk-hide - point - height)) - ))) - (defun diff-hl-show-hunk-copy-original-text () "Extracts all the lines from BUFFER starting with '-' to the kill ring." (interactive) diff --git a/lisp/diff-hl/diff-hl.el b/lisp/diff-hl/diff-hl.el index 7e8bb891..2b307bc3 100644 --- a/lisp/diff-hl/diff-hl.el +++ b/lisp/diff-hl/diff-hl.el @@ -5,8 +5,8 @@ ;; Author: Dmitry Gutov ;; URL: https://github.com/dgutov/diff-hl ;; Keywords: vc, diff -;; Package-Version: 20250710.145 -;; Package-Revision: 08243a6e0b68 +;; Package-Version: 20251125.238 +;; Package-Revision: 8dc486f568af ;; Package-Requires: ((cl-lib "0.2") (emacs "26.1")) ;; This file is part of GNU Emacs. @@ -70,7 +70,18 @@ (require 'vc-git) (require 'vc-hg) (require 'face-remap) - (declare-function smartrep-define-key 'smartrep)) + (require 'project)) + +(defmacro static-if (condition then-form &rest else-forms) ; since Emacs 30.1 + "A conditional compilation macro. +Evaluate CONDITION at macro-expansion time. If it is non-nil, +expand the macro to THEN-FORM. Otherwise expand it to ELSE-FORMS +enclosed in a `progn' form. ELSE-FORMS may be empty." + (declare (indent 2) + (debug (sexp sexp &rest sexp))) + (if (eval condition lexical-binding) + then-form + (cons 'progn else-forms))) (defgroup diff-hl nil "VC diff highlighting on the side of a window" @@ -97,6 +108,21 @@ "Face used to highlight changed lines." :group 'diff-hl) +(defface diff-hl-reference-insert + '((default :inherit diff-hl-insert)) + "Face used to highlight lines inserted since reference rev." + :group 'diff-hl) + +(defface diff-hl-reference-delete + '((default :inherit diff-hl-delete)) + "Face used to highlight lines deleted since reference rev." + :group 'diff-hl) + +(defface diff-hl-reference-change + '((default :inherit diff-hl-change)) + "Face used to highlight lines changed since reference rev." + :group 'diff-hl) + (defcustom diff-hl-command-prefix (kbd "C-x v") "The prefix for all `diff-hl' commands." :group 'diff-hl @@ -137,6 +163,19 @@ features don't use margin for their indicators." :group 'diff-hl :type 'function) +(defcustom diff-hl-highlight-reference-function 'diff-hl-highlight-on-fringe-flat + "Function to highlight the changes against the reference revision. +Its arguments are overlay, change type and position within a hunk." + :group 'diff-hl + :type 'function) + +(defcustom diff-hl-fringe-flat-bmp 'diff-hl-bmp-empty + "The bitmap symbol to use in `diff-hl-highlight-on-fringe-flat'. +Some options are `diff-hl-bmp-empty', `diff-hl-bmp-i', or any of the +built-in bitmaps." + :group 'diff-hl + :type 'symbol) + (defcustom diff-hl-fringe-bmp-function 'diff-hl-fringe-bmp-from-pos "Function to choose the fringe bitmap for a given change type and position within a hunk. Should accept two arguments." @@ -151,6 +190,12 @@ features don't use margin for their indicators." :group 'diff-hl :type 'function) +(defcustom diff-hl-fringe-reference-face-function 'diff-hl-fringe-reference-face-from-type + "Function to choose the fringe face for a given change type +and position within a \"diff to reference\" hunk." + :group 'diff-hl + :type 'function) + (defcustom diff-hl-side 'left "Which side to use for indicators." :type '(choice (const left) @@ -201,7 +246,10 @@ performance when viewing such files in certain conditions." (defcustom diff-hl-show-staged-changes t "Whether to include staged changes in the indicators. -Only affects Git, it's the only backend that has staging area." +Only affects Git, it's the only backend that has staging area. + +When `diff-hl-highlight-reference-function' is non-nil, instead of being +hidden, the staged changes become part of the \"reference\" indicators." :type 'boolean) (defcustom diff-hl-goto-hunk-old-revisions nil @@ -218,10 +266,22 @@ the current version of the file)." (defcustom diff-hl-update-async nil "When non-nil, `diff-hl-update' will run asynchronously. -This can help prevent Emacs from freezing, especially by a slow version -control (VC) backend. It's disabled in remote buffers, though, since it -didn't work reliably in such during testing." - :type 'boolean) +When the value is `thread', it will call the diff process in a Lisp thread. + +Any other non-nil value will make the the diff process called +asynchronously in the main thread. + +Note that the latter mechanism is only compatible with a recent Emacs +31+ (for built-in backends such as Git and Hg). Whereas using a thread can +help with older Emacs as well, but might crash in some configurations. + +This feature makes Emacs more responsive with slower version control (VC) +backends and large projects. But it's disabled in remote buffers, since +current testing shows it doesn't work reliably over Tramp." + :type '(choice + (const :tag "Disabled" nil) + (const :tag "Simple async" t) + (const :tag "Thread" thread))) ;; Threads are not reliable with remote files, yet. (defcustom diff-hl-async-inhibit-functions (list #'diff-hl-with-editor-p @@ -234,12 +294,22 @@ and passed the value `default-directory'. If any returns non-nil, `diff-hl-update' will run synchronously anyway." :type '(repeat :tag "Predicate" function)) +(defvar diff-hl-reference-revision-projects-cache '() + "Alist of cached directory roots for per-project reference revisions. +Each element in this list has the form (DIR . REV). +DIR is the expanded name of the directory. +REV is the current reference revision.") + (defvar diff-hl-reference-revision nil "Revision to diff against. nil means the most recent one. It can be a relative expression as well, such as \"HEAD^\" with Git, or \"-2\" with Mercurial.") +(put 'diff-hl-reference-revision 'safe-local-variable + (lambda (value) + (or (null value) (stringp value)))) + (defun diff-hl-define-bitmaps () (let* ((scale (if (and (boundp 'text-scale-mode-amount) (numberp text-scale-mode-amount)) @@ -311,6 +381,9 @@ It can be a relative expression as well, such as \"HEAD^\" with Git, or (defun diff-hl-fringe-face-from-type (type _pos) (intern (format "diff-hl-%s" type))) +(defun diff-hl-fringe-reference-face-from-type (type _pos) + (intern (format "diff-hl-reference-%s" type))) + (defun diff-hl-fringe-bmp-from-pos (_type pos) (intern (format "diff-hl-bmp-%s" pos))) @@ -321,58 +394,76 @@ It can be a relative expression as well, such as \"HEAD^\" with Git, or (ignored 'diff-hl-bmp-i) (t (intern (format "diff-hl-bmp-%s" type))))) -(defvar vc-svn-diff-switches) -(defvar vc-fossil-diff-switches) -(defvar vc-jj-diff-switches) - (defmacro diff-hl-with-diff-switches (body) - `(let ((vc-git-diff-switches - ;; https://github.com/dgutov/diff-hl/issues/67 - (cons "-U0" - ;; https://github.com/dgutov/diff-hl/issues/9 - (and (boundp 'vc-git-diff-switches) - (listp vc-git-diff-switches) - (cl-remove-if-not - (lambda (arg) - (member arg '("--histogram" "--patience" "--minimal" "--textconv"))) - vc-git-diff-switches)))) - (vc-hg-diff-switches nil) - (vc-svn-diff-switches nil) - (vc-fossil-diff-switches '("-c" "0")) - (vc-jj-diff-switches '("--git" "--context=0")) - (vc-diff-switches '("-U0")) - ,@(when (boundp 'vc-disable-async-diff) - '((vc-disable-async-diff t)))) - ,body)) + `(progn + (defvar vc-svn-diff-switches) + (defvar vc-fossil-diff-switches) + (defvar vc-jj-diff-switches) + (let ((vc-git-diff-switches + ;; https://github.com/dgutov/diff-hl/issues/67 + (cons "-U0" + ;; https://github.com/dgutov/diff-hl/issues/9 + (and (boundp 'vc-git-diff-switches) + (listp vc-git-diff-switches) + (cl-remove-if-not + (lambda (arg) + (member arg '("--histogram" "--patience" "--minimal" "--textconv"))) + vc-git-diff-switches)))) + (vc-hg-diff-switches nil) + (vc-svn-diff-switches nil) + (vc-fossil-diff-switches '("-c" "0")) + (vc-jj-diff-switches '("--git" "--context=0")) + (vc-diff-switches '("-U0")) + ,@(when (boundp 'vc-disable-async-diff) + '((vc-disable-async-diff t)))) + ,body))) (defun diff-hl-modified-p (state) (or (memq state '(edited conflict)) (and (eq state 'up-to-date) ;; VC state is stale in after-revert-hook. - (or revert-buffer-in-progress-p + (or (static-if (>= emacs-major-version 31) + revert-buffer-in-progress + revert-buffer-in-progress-p) ;; Diffing against an older revision. diff-hl-reference-revision)))) (declare-function vc-git-command "vc-git") (declare-function vc-git--rev-parse "vc-git") (declare-function vc-hg-command "vc-hg") +(declare-function vc-bzr-command "vc-bzr") -(defun diff-hl-changes-buffer (file backend) +(defun diff-hl-changes-buffer (file backend &optional new-rev bufname) (diff-hl-with-diff-switches - (diff-hl-diff-against-reference file backend " *diff-hl* "))) + (diff-hl-diff-against-reference file backend (or bufname " *diff-hl* ") new-rev))) -(defun diff-hl-diff-against-reference (file backend buffer) - (if (and (eq backend 'Git) - (not diff-hl-reference-revision) - (not diff-hl-show-staged-changes)) - (apply #'vc-git-command buffer - (if (diff-hl--use-async-p) 'async 1) - (list file) - "diff-files" - (cons "-p" (vc-switches 'git 'diff))) +(defun diff-hl-diff-against-reference (file backend buffer &optional new-rev) + (cond + ((and (not new-rev) + (not diff-hl-reference-revision) + (not diff-hl-show-staged-changes) + (eq backend 'Git)) + (apply #'vc-git-command buffer + (if (diff-hl--use-async-p) 'async 1) + (list file) + "diff-files" + (cons "-p" (vc-switches 'git 'diff)))) + ((eq new-rev 'git-index) + (apply #'vc-git-command buffer + (if (diff-hl--use-async-p) 'async 1) + (list file) + "diff-index" + (append + (vc-switches 'git 'diff) + (list "-p" "--cached" + (or diff-hl-reference-revision + (diff-hl-head-revision backend)) + "--")))) + (t (condition-case err (vc-call-backend backend 'diff (list file) - diff-hl-reference-revision nil + diff-hl-reference-revision + new-rev buffer (diff-hl--use-async-p)) (error @@ -382,28 +473,112 @@ It can be a relative expression as well, such as \"HEAD^\" with Git, or "4b825dc642cb6eb9a060e54bf8d69288fbee4904" nil buffer - (diff-hl--use-async-p)))))) + (diff-hl--use-async-p))))))) buffer) (defun diff-hl-changes () (let* ((file buffer-file-name) - (backend (vc-backend file))) + (backend (vc-backend file)) + (hide-staged (and (eq backend 'Git) (not diff-hl-show-staged-changes)))) (when backend (let ((state (vc-state file backend)) ;; Workaround for debbugs#78946. ;; This is fiddly, but we basically allow the thread to start, while ;; prohibiting the async process call inside. ;; That still makes it partially async. - (diff-hl-update-async (and diff-hl-update-async - (not (eq window-system 'ns))))) + (diff-hl-update-async (and (not (eq window-system 'ns)) + (eq diff-hl-update-async t)))) (cond - ((diff-hl-modified-p state) - (diff-hl-changes-from-buffer - (diff-hl-changes-buffer file backend))) + ((and + (not diff-hl-highlight-reference-function) + (diff-hl-modified-p state)) + `((:working . ,(diff-hl-changes-buffer file backend)))) + ((or + diff-hl-reference-revision + (diff-hl-modified-p state)) + (let* ((ref-changes + (and (or diff-hl-reference-revision + hide-staged) + (diff-hl-changes-buffer file backend + (if hide-staged + 'git-index + (diff-hl-head-revision backend)) + " *diff-hl-reference* "))) + (diff-hl-reference-revision nil) + (work-changes (diff-hl-changes-buffer file backend))) + `((:reference . ,ref-changes) + (:working . ,work-changes)))) ((eq state 'added) - `((1 ,(line-number-at-pos (point-max)) insert))) + `((:working . ,`((1 ,(line-number-at-pos (point-max)) 0 insert))))) ((eq state 'removed) - `((1 ,(line-number-at-pos (point-max)) delete)))))))) + `((:working . ,`((1 0 ,(line-number-at-pos (point-max)) delete)))))))))) + +(defvar diff-hl-head-revision-alist '((Git . "HEAD") (Bzr . "last:1") (Hg . ".") + (JJ . "@-"))) + +(defun diff-hl-head-revision (backend) + (or (assoc-default backend diff-hl-head-revision-alist) + ;; It's usually cached already (e.g. for mode-line). + ;; So this is basically an optimization for rare cases. + (vc-working-revision buffer-file-name backend))) + +(defun diff-hl-adjust-changes (old new) + "Adjust changesets in OLD using changes in NEW. +The result alters the values inside the OLD changeset so that the line +numbers and insertion/deletion counts refer to the lines in the file +contents as they are (or would be) after applying the changes in NEW." + (let ((acc 0) + (ref old) + overlap) + (while (and new old) + (cond + ((<= (+ (nth 0 (car new)) (nth 2 (car new))) + (+ acc (nth 0 (car old)))) + ;; (A) NEW-END <= OLD-BEG + (cl-incf acc (- (nth 1 (car new)) (nth 2 (car new)))) + (setq new (cdr new))) + ((<= (+ acc (nth 0 (car old)) (nth 1 (car old))) + (nth 0 (car new))) + ;; (B) OLD-END <= NEW-BEG + (cl-incf (nth 0 (car old)) acc) + (setq old (cdr old))) + (t + ;; There is overlap. + (setq overlap + (- + (min + (+ (nth 0 (car new)) (nth 2 (car new))) + (+ acc (nth 0 (car old)) (nth 1 (car old)))) + (max + (nth 0 (car new)) + (+ acc (nth 0 (car old)))))) + (cl-decf (nth 1 (car old)) overlap) + ;; Add INSERTION amount to either before or inside. + (cl-incf (nth + (if (> (nth 0 (car new)) + (+ acc (nth 0 (car old)))) + 1 0) + (car old)) + (- + (nth 1 (car new)) + (- (nth 2 (car new)) + overlap))) + ;; See which of the heads to pop. + (if (> (+ (nth 0 (car new)) (nth 1 (car new))) + (+ acc (nth 0 (car old)) (nth 1 (car old)))) + (progn + ;; Repetition of B (how to avoid dup?) + (cl-incf (nth 0 (car old)) acc) + (setq old (cdr old))) + ;; Repetition of A. + (cl-incf acc (- (nth 1 (car new)) (nth 2 (car new)))) + ;; But also decrease OLD-BEG by the same amount: it's added later. + (cl-decf (nth 0 (car old)) (- (nth 1 (car new)) (nth 2 (car new)))) + (setq new (cdr new)))))) + (while old + (cl-incf (nth 0 (car old)) acc) + (setq old (cdr old))) + ref)) (defun diff-hl-process-wait (buf) (let ((proc (get-buffer-process buf))) @@ -411,7 +586,6 @@ It can be a relative expression as well, such as \"HEAD^\" with Git, or (accept-process-output proc 0.01)))) (defun diff-hl-changes-from-buffer (buf) - (diff-hl-process-wait buf) (with-current-buffer buf (let (res) (goto-char (point-min)) @@ -427,8 +601,6 @@ It can be a relative expression as well, such as \"HEAD^\" with Git, or (diff-beginning-of-hunk t)))) (while (looking-at diff-hunk-header-re-unified) (let ((line (string-to-number (match-string 3))) - (len (let ((m (match-string 4))) - (if m (string-to-number m) 1))) (beg (point))) (with-no-warnings (let (diff-auto-refine-mode) @@ -439,9 +611,8 @@ It can be a relative expression as well, such as \"HEAD^\" with Git, or ((zerop inserts) 'delete) (t 'change)))) (when (eq type 'delete) - (setq len 1) (cl-incf line)) - (push (list line len type) res))))) + (push (list line inserts deletes type) res))))) (nreverse res)))) (defun diff-hl--use-async-p () @@ -450,9 +621,13 @@ It can be a relative expression as well, such as \"HEAD^\" with Git, or (run-hook-with-args-until-success 'diff-hl-async-inhibit-functions default-directory)))) +(defvar diff-hl-timer nil) + (defun diff-hl-update () "Updates the diff-hl overlay." - (if (diff-hl--use-async-p) + (setq diff-hl-timer nil) + (if (and (diff-hl--use-async-p) + (eq diff-hl-update-async 'thread)) ;; TODO: debounce if a thread is already running. (let ((buf (current-buffer)) (temp-buffer @@ -470,42 +645,55 @@ It can be a relative expression as well, such as \"HEAD^\" with Git, or "diff-hl--update-safe"))) (diff-hl--update))) +(defun diff-hl--update-buffer (buf) + (when (buffer-live-p buf) + (with-current-buffer buf + (diff-hl-update)))) + (defun diff-hl-with-editor-p (_dir) (bound-and-true-p with-editor-mode)) (defun diff-hl--update-safe () "Updates the diff-hl overlay. It handles and logs when an error is signaled." - (condition-case err + (condition-case-unless-debug err (diff-hl--update) (error (message "An error occurred in diff-hl--update: %S" err) nil))) -(defun diff-hl--update () - "Updates the diff-hl overlay." - (let ((changes (diff-hl-changes)) - (current-line 1)) - (diff-hl-remove-overlays) - (when (not changes) - (diff-hl--autohide-margin)) +(defun diff-hl--update-overlays (changes reuse) + "Updates the diff-hl overlays based on CHANGES. +REUSE is a list of existing line overlays that can be used. +Return a list of line overlays used." + (let ((current-line 1) + ovls) (save-excursion (save-restriction (widen) (goto-char (point-min)) (dolist (c changes) - (cl-destructuring-bind (line len type) c + (cl-destructuring-bind (line inserts _deletes type) c (forward-line (- line current-line)) (setq current-line line) - (let ((hunk-beg (point))) + (let ((hunk-beg (point)) + (len (if (eq type 'delete) 1 inserts))) + (while (and reuse + (< (overlay-start (car reuse)) (point))) + (setq reuse (cdr reuse))) (while (cl-plusp len) - (diff-hl-add-highlighting - type - (cond - ((not diff-hl-draw-borders) 'empty) - ((and (= len 1) (= line current-line)) 'single) - ((= len 1) 'bottom) - ((= line current-line) 'top) - (t 'middle))) + (push + (diff-hl-add-highlighting + type + (cond + ((not diff-hl-draw-borders) 'empty) + ((and (= len 1) (= line current-line)) 'single) + ((= len 1) 'bottom) + ((= line current-line) 'top) + (t 'middle)) + (and reuse + (= (overlay-start (car reuse)) (point)) + (pop reuse))) + ovls) (forward-line 1) (cl-incf current-line) (cl-decf len)) @@ -516,7 +704,46 @@ It can be a relative expression as well, such as \"HEAD^\" with Git, or (overlay-put h 'diff-hl-hunk-type type) (overlay-put h 'modification-hooks hook) (overlay-put h 'insert-in-front-hooks hook) - (overlay-put h 'insert-behind-hooks hook))))))))) + (overlay-put h 'insert-behind-hooks hook))))))) + (nreverse ovls))) + +(defun diff-hl--no-query-on-exit (value) + (when-let* ((buf (and (stringp value) (get-buffer value))) + (proc (get-buffer-process buf))) + (set-process-query-on-exit-flag proc nil))) + +(defun diff-hl--update () + (let* ((orig (current-buffer)) + (cc (diff-hl-changes)) + (working (assoc-default :working cc)) + (reference (assoc-default :reference cc))) + (diff-hl--no-query-on-exit working) + (diff-hl--no-query-on-exit reference) + (diff-hl--resolve + working + (lambda (changes) + (diff-hl--resolve + reference + (lambda (ref-changes) + (let ((ref-changes (diff-hl-adjust-changes ref-changes changes)) + reuse) + (with-current-buffer orig + (diff-hl-remove-overlays) + (let ((diff-hl-highlight-function + diff-hl-highlight-reference-function) + (diff-hl-fringe-face-function + diff-hl-fringe-reference-face-function)) + (setq reuse (diff-hl--update-overlays ref-changes nil))) + (diff-hl--update-overlays changes reuse) + (when (not (or changes ref-changes)) + (diff-hl--autohide-margin)))))))))) + +(defun diff-hl--resolve (value-or-buffer cb) + (if (listp value-or-buffer) + (funcall cb value-or-buffer) + (with-current-buffer value-or-buffer + (vc-run-delayed + (funcall cb (diff-hl-changes-from-buffer (current-buffer))))))) (defun diff-hl--autohide-margin () (let ((width-var (intern (format "%s-margin-width" diff-hl-side)))) @@ -528,17 +755,15 @@ It can be a relative expression as well, such as \"HEAD^\" with Git, or (dolist (win (get-buffer-window-list)) (set-window-buffer win (current-buffer)))))) -(defvar-local diff-hl--modified-tick nil) - -(put 'diff-hl--modified-tick 'permanent-local t) - (defun diff-hl-update-once () - (unless (equal diff-hl--modified-tick (buffer-chars-modified-tick)) - (diff-hl-update) - (setq diff-hl--modified-tick (buffer-chars-modified-tick)))) + ;; Ensure that the update happens once, after all major mode changes. + ;; That will keep the the local value of -margin-width, if any. + (unless diff-hl-timer + (setq diff-hl-timer + (run-with-idle-timer 0 nil #'diff-hl--update-buffer (current-buffer))))) -(defun diff-hl-add-highlighting (type shape) - (let ((o (make-overlay (point) (point)))) +(defun diff-hl-add-highlighting (type shape &optional ovl) + (let ((o (or ovl (make-overlay (point) (point))))) (overlay-put o 'diff-hl t) (funcall diff-hl-highlight-function o type shape) o)) @@ -552,6 +777,10 @@ It can be a relative expression as well, such as \"HEAD^\" with Git, or (overlay-put ovl 'before-string (diff-hl-fringe-spec type shape diff-hl-side)))) +(defun diff-hl-highlight-on-fringe-flat (ovl type _shape) + (let ((diff-hl-fringe-bmp-function (lambda (&rest _s) diff-hl-fringe-flat-bmp))) + (diff-hl-highlight-on-fringe ovl type nil))) + (defun diff-hl-remove-overlays (&optional beg end) (save-restriction (widen) @@ -565,8 +794,6 @@ It can be a relative expression as well, such as \"HEAD^\" with Git, or (diff-hl-remove-overlays (overlay-start ov) (overlay-end ov)) (delete-overlay ov)))) -(defvar diff-hl-timer nil) - (defun diff-hl-edit (_beg _end _len) "DTRT when we've `undo'-ne the buffer into unmodified state." (when undo-in-progress @@ -581,16 +808,11 @@ It can be a relative expression as well, such as \"HEAD^\" with Git, or (unless (buffer-modified-p) (diff-hl-update))))) -(defun diff-hl-after-revert () - (when (bound-and-true-p revert-buffer-preserve-modes) - (diff-hl-update))) - -(defun diff-hl-diff-goto-hunk-1 (historic) +(defun diff-hl-diff-goto-hunk-1 (historic rev1) (defvar vc-sentinel-movepoint) (vc-buffer-sync) (let* ((line (line-number-at-pos)) (buffer (current-buffer)) - (rev1 diff-hl-reference-revision) rev2) (when historic (let ((revs (diff-hl-diff-read-revisions rev1))) @@ -602,16 +824,56 @@ It can be a relative expression as well, such as \"HEAD^\" with Git, or t) (vc-deduce-fileset) rev1 rev2 t) (vc-run-delayed (if (< (line-number-at-pos (point-max)) 3) - (with-current-buffer buffer (diff-hl-remove-overlays)) + (with-current-buffer buffer (diff-hl-update)) (when (or (not rev2) diff-hl-goto-hunk-old-revisions) (diff-hl-diff-skip-to line)) (setq vc-sentinel-movepoint (point)))))) (defun diff-hl-diff-goto-hunk (&optional historic) - "Run VC diff command and go to the line corresponding to the current." + "Run VC diff command and go to the corresponding line in diff. +With double prefix argument (C-u C-u), the diff is made against the +reference revision." (interactive (list current-prefix-arg)) (with-current-buffer (or (buffer-base-buffer) (current-buffer)) - (diff-hl-diff-goto-hunk-1 historic))) + (if (equal historic '(16)) + (diff-hl-diff-reference-goto-hunk) + (diff-hl-diff-goto-hunk-1 historic nil)))) + +(defun diff-hl-diff-reference-goto-hunk () + "Run VC diff command against the reference and go to the corresponding line." + (interactive) + (with-current-buffer (or (buffer-base-buffer) (current-buffer)) + (diff-hl-diff-goto-hunk-1 nil diff-hl-reference-revision))) + +(defun diff-hl-root-diff-reference-goto-hunk () + "Run VC diff command against the reference for the whole tree. +And if the current buffer is visiting a file, and it has changes, the diff +buffer will show the position corresponding to its current line." + (interactive) + (defvar vc-sentinel-movepoint) + (with-current-buffer (or (buffer-base-buffer) (current-buffer)) + (let ((backend (vc-deduce-backend)) + (default-directory default-directory) + rootdir fileset + relname) + (if backend + (setq rootdir (vc-call-backend backend 'root default-directory) + default-directory rootdir + fileset `(,backend (,rootdir)) + relname (if buffer-file-name (file-relative-name buffer-file-name + rootdir))) + (error "Directory is not version controlled")) + (setq fileset (or fileset (vc-deduce-fileset))) + (vc-buffer-sync-fileset fileset t) + (let* ((line (line-number-at-pos))) + (vc-diff-internal + (if (boundp 'vc-allow-async-diff) + vc-allow-async-diff + t) + fileset diff-hl-reference-revision nil t) + (vc-run-delayed (when relname + (diff-hl-diff-skip-to line relname) + (setq vc-sentinel-movepoint (point)))))))) (defun diff-hl-diff-read-revisions (rev1-default) (let* ((file buffer-file-name) @@ -642,13 +904,31 @@ It can be a relative expression as well, such as \"HEAD^\" with Git, or (when (string= rev2 "") (setq rev2 nil)) (cons rev1 rev2)))) -(defun diff-hl-diff-skip-to (line) +(defun diff-hl-diff-skip-to (line &optional filename) "In `diff-mode', skip to the hunk and line corresponding to LINE -in the source file, or the last line of the hunk above it." +in the source file, or the last line of the hunk above it. + +When passed FILENAME, ensure that the line is in the section belonging to +that file, if it's present." (goto-char (point-min)) ; Counteract any similar behavior in diff-mode. - (diff-hunk-next) - (let (found) - (while (and (looking-at diff-hunk-header-re-unified) (not found)) + (let (found + (end-pos (point-max))) + (diff-hunk-next) + (when filename + (while (and (not (equal (diff-find-file-name nil t) + filename)) + (not (eobp))) + (diff-file-next)) + (if (not (eobp)) + ;; Found our file in the changed set. Ensure that we don't go past. + (setq end-pos (save-excursion + (diff-end-of-file) + (point))) + (goto-char (point-min)) + (setq line 0))) + (while (and (looking-at diff-hunk-header-re-unified) + (not found) + (< (point) end-pos)) (let ((hunk-line (string-to-number (match-string 3))) (len (let ((m (match-string 4))) (if m (string-to-number m) 1)))) @@ -662,7 +942,10 @@ in the source file, or the last line of the hunk above it." (while (cl-plusp to-go) (forward-line 1) (unless (looking-at "^[-\\]") - (cl-decf to-go)))))))))) + (cl-decf to-go)))))))) + (when (> (point) end-pos) + (diff-hunk-prev) + (diff-end-of-hunk)))) (defface diff-hl-reverted-hunk-highlight '((default :inverse-video t)) @@ -687,7 +970,7 @@ in the source file, or the last line of the hunk above it." (widen) (vc-buffer-sync) (let* ((diff-buffer (get-buffer-create - (generate-new-buffer-name "*diff-hl*"))) + (generate-new-buffer-name "*diff-hl-revert*"))) (buffer (current-buffer)) (diff-hl-update-async nil) (line (save-excursion @@ -698,8 +981,9 @@ in the source file, or the last line of the hunk above it." (unwind-protect (progn (vc-setup-buffer diff-buffer) - (diff-hl-diff-against-reference file backend diff-buffer) - (set-buffer diff-buffer) + (with-current-buffer buffer + ;; Ensure that the buffer-local variable value is applied. + (diff-hl-diff-against-reference file backend diff-buffer)) (diff-mode) (setq-local diff-vc-backend backend) (setq-local diff-vc-revisions (list diff-hl-reference-revision nil)) @@ -935,7 +1219,8 @@ Pops up a diff buffer that can be edited to choose the changes to stage." (with-current-buffer dest-buffer (let ((inhibit-read-only t)) (erase-buffer))) - (diff-hl-diff-buffer-with-reference file dest-buffer nil 3) + (let (diff-hl-reference-revision) + (diff-hl-diff-buffer-with-reference file dest-buffer nil 3)) (with-current-buffer dest-buffer (let ((inhibit-read-only t)) (when end @@ -1026,7 +1311,7 @@ The value of this variable is a mode line template as in 'diff-hl-update-once t t) ;; Never removed because it acts globally. (add-hook 'vc-checkin-hook 'diff-hl-after-checkin) - (add-hook 'after-revert-hook 'diff-hl-after-revert nil t) + (add-hook 'after-revert-hook 'diff-hl-update-once nil t) ;; Magit does call `auto-revert-handler', but it usually ;; doesn't do much, because `buffer-stale--default-function' ;; doesn't care about changed VC state. @@ -1035,16 +1320,22 @@ The value of this variable is a mode line template as in ;; Magit versions 2.0-2.3 don't do the above and call this ;; instead, but only when they don't call `revert-buffer': (add-hook 'magit-not-reverted-hook 'diff-hl-update nil t) - (add-hook 'text-scale-mode-hook 'diff-hl-maybe-redefine-bitmaps nil t)) + (add-hook 'text-scale-mode-hook 'diff-hl-maybe-redefine-bitmaps nil t) + (when-let* ((rev (cdr + (cl-find-if + (lambda (pair) (string-prefix-p (car pair) default-directory)) + diff-hl-reference-revision-projects-cache)))) + (setq-local diff-hl-reference-revision rev))) (remove-hook 'after-save-hook 'diff-hl-update t) (remove-hook 'after-change-functions 'diff-hl-edit t) (remove-hook 'find-file-hook 'diff-hl-update-once t) - (remove-hook 'after-revert-hook 'diff-hl-after-revert t) + (remove-hook 'after-revert-hook 'diff-hl-update-once t) (remove-hook 'magit-revert-buffer-hook 'diff-hl-update t) (remove-hook 'magit-not-reverted-hook 'diff-hl-update t) (remove-hook 'text-scale-mode-hook 'diff-hl-maybe-redefine-bitmaps t) (diff-hl-remove-overlays) - (diff-hl--autohide-margin))) + (diff-hl--autohide-margin) + (kill-local-variable 'diff-hl-reference-revision))) (defun diff-hl-after-checkin () (let ((fileset (vc-deduce-fileset t))) @@ -1060,6 +1351,7 @@ The value of this variable is a mode line template as in diff-hl-show-hunk-next)) (when (require 'smartrep nil t) + (declare-function smartrep-define-key 'smartrep) (let (smart-keys) (cl-labels ((scan (map) (map-keymap @@ -1204,8 +1496,11 @@ CONTEXT-LINES is the size of the unified diff context, defaults to 0." (diff-hl-git-index-object-name file)) (diff-hl-create-revision file - (or (diff-hl-resolved-reference-revision backend) - (diff-hl-working-revision file backend))))) + (or (diff-hl-resolved-revision + backend + (or diff-hl-reference-revision + (assoc-default backend diff-hl-head-revision-alist))) + (diff-hl-working-revision buffer-file-name backend))))) (switches (format "-U %d --strip-trailing-cr" (or context-lines 0)))) (diff-no-select rev (current-buffer) switches (not (diff-hl--use-async-p)) (get-buffer-create dest-buffer)) @@ -1213,21 +1508,32 @@ CONTEXT-LINES is the size of the unified diff context, defaults to 0." ;; In all commands which use exact text we call it synchronously. (get-buffer-create dest-buffer)))) -(defun diff-hl-resolved-reference-revision (backend) +(declare-function vc-jj--process-lines "vc-jj") + +(defun diff-hl-resolved-revision (backend revision) (cond - ((null diff-hl-reference-revision) - nil) ((eq backend 'Git) - (vc-git--rev-parse diff-hl-reference-revision)) + (vc-git--rev-parse revision)) ((eq backend 'Hg) (with-temp-buffer (vc-hg-command (current-buffer) 0 nil - "identify" "-r" diff-hl-reference-revision - "-i") + "identify" "-r" revision "-i") (goto-char (point-min)) (buffer-substring-no-properties (point) (line-end-position)))) + ((eq backend 'Bzr) + (with-temp-buffer + (vc-bzr-command (current-buffer) 0 nil + "log" "--log-format=template" + "--template-str='{revno}'" + "-r" revision) + (goto-char (point-min)) + (buffer-substring-no-properties (point) (line-end-position)))) + ((eq backend 'JJ) + (car (last (vc-jj--process-lines "log" "--no-graph" + "-r" revision + "-T" "change_id" "-n" "1")))) (t - diff-hl-reference-revision))) + revision))) ;; TODO: Cache based on .git/index's mtime, maybe. (defun diff-hl-git-index-object-name (file) @@ -1288,6 +1594,11 @@ CONTEXT-LINES is the size of the unified diff context, defaults to 0." "Set the reference revision globally to REV. When called interactively, REV read with completion. +When called with a prefix argument, reset the global reference to the most +recent one instead. With two prefix arguments, do the same and discard +every per-project reference created by +`diff-hl-set-reference-rev-in-project`. + The default value chosen using one of methods below: - In a log view buffer, it uses the revision of current entry. @@ -1296,42 +1607,159 @@ view buffer. - In a VC annotate buffer, it uses the revision of current line. - In other situations, it uses the symbol at point. -Notice that this sets the reference revision globally, so in -files from other repositories, `diff-hl-mode' will not highlight -changes correctly, until you run `diff-hl-reset-reference-rev'. +Notice that this sets the reference revision globally, so in files from +other repositories, `diff-hl-mode' will not highlight changes correctly, +until you run `diff-hl-reset-reference-rev'. To set the reference on a +per-project basis, see `diff-hl-set-reference-rev-in-project`. Also notice that this will disable `diff-hl-amend-mode' in buffers that enables it, since `diff-hl-amend-mode' overrides its effect." (interactive - (let* ((def (or (and (equal major-mode 'vc-annotate-mode) - (car (vc-annotate-extract-revision-at-line))) - (log-view-current-tag) - (thing-at-point 'symbol t))) - (prompt (if def - (format "Reference revision (default %s): " def) - "Reference revision: "))) - (list (vc-read-revision prompt nil nil def)))) - (if rev - (message "Set reference revision to %s" rev) + (if current-prefix-arg current-prefix-arg + (let* ((def (or (and (equal major-mode 'vc-annotate-mode) + (car (vc-annotate-extract-revision-at-line))) + (log-view-current-tag) + (thing-at-point 'symbol t))) + (prompt (if def + (format "Reference revision (default %s): " def) + "Reference revision: "))) + (list (vc-read-revision prompt nil nil def))))) + (unless rev (user-error "No reference revision specified")) - (setq diff-hl-reference-revision rev) - (dolist (buf (buffer-list)) - (with-current-buffer buf - (when diff-hl-mode - (when (bound-and-true-p diff-hl-amend-mode) - (diff-hl-amend-mode -1)) - (diff-hl-update))))) + (cond + ((equal '(4) current-prefix-arg) + ;; reset global value + (diff-hl-reset-reference-rev)) + ((equal '(16) current-prefix-arg) + ;; reset global value and remove per-project value + (diff-hl-reset-reference-rev '(4))) + ;; change only global value + (t (setq-default diff-hl-reference-revision rev) + (unless current-prefix-arg + (message "Set global reference revision to %s" rev)) + (dolist (buf (buffer-list)) + (with-current-buffer buf + (when diff-hl-mode + (when (bound-and-true-p diff-hl-amend-mode) + (diff-hl-amend-mode -1)) + (when (not (local-variable-p 'diff-hl-reference-revision)) + (diff-hl-update)))))))) ;;;###autoload -(defun diff-hl-reset-reference-rev () - "Reset the reference revision globally to the most recent one." - (interactive) - (setq diff-hl-reference-revision nil) +(defun diff-hl-set-reference-rev-in-project (rev) + "Set the reference revision in the current project to REV. +When called interactively, REV read with completion. + +When called with a prefix argument, reset to the global value instead. + +The default value chosen using one of methods below: + +- In a log view buffer, it uses the revision of current entry. +Call `vc-print-log' or `vc-print-root-log' first to open a log +view buffer. +- In a VC annotate buffer, it uses the revision of current line. +- In other situations, it uses the symbol at point. + +Projects whose reference was set with this command are unaffected by +subsequent changes to the global reference (see +`diff-hl-set-reference-rev`). + +Also notice that this will disable `diff-hl-amend-mode' in +buffers that enables it, since `diff-hl-amend-mode' overrides its +effect." + (interactive + (if current-prefix-arg current-prefix-arg + (let* ((def (or (and (equal major-mode 'vc-annotate-mode) + (car (vc-annotate-extract-revision-at-line))) + (log-view-current-tag) + (thing-at-point 'symbol t))) + (prompt (if def + (format "Reference revision (default %s): " def) + "Reference revision: "))) + (list (vc-read-revision prompt nil nil def))))) + (unless rev + (user-error "No reference revision specified")) + (let* ((proj (project-current)) + (name (project-name proj))) + (cond + ;; reset + (current-prefix-arg + (diff-hl-reset-reference-rev-in-project proj)) + ;; set + (t + (diff-hl-set-reference-rev-in-project-internal rev proj) + (message "Showing changes against %s (project %s)" rev name))))) + +(defun diff-hl--project-root (proj) + ;; Emacs 26 and 27 don't have `project-root'. + (expand-file-name (static-if (>= emacs-major-version 28) + (project-root proj) + (project-roots proj)))) + +(defun diff-hl-set-reference-rev-in-project-internal (rev proj) + (let* ((root (diff-hl--project-root proj))) + ;; newly opened files will share this value + (setf (alist-get root diff-hl-reference-revision-projects-cache + nil nil #'string-equal) + rev) + ;; update currently open files + (dolist (buf (project-buffers proj)) + (with-current-buffer buf + (when diff-hl-mode + (when (bound-and-true-p diff-hl-amend-mode) + (diff-hl-amend-mode -1)) + (setq-local diff-hl-reference-revision rev) + (diff-hl-update)))))) + +;;;###autoload +(defun diff-hl-reset-reference-rev (&optional arg) + "Reset the reference revision globally to the most recent one. + +When called with a prefix argument, do the same and discard every +per-project reference created by `diff-hl-set-reference-rev-in-project'." + (interactive "P") + (setq-default diff-hl-reference-revision nil) + (when arg + ;; reset all cache + (setq diff-hl-reference-revision-projects-cache nil)) (dolist (buf (buffer-list)) (with-current-buffer buf (when diff-hl-mode - (diff-hl-update))))) + (when arg + ;; reset value in buffers + (kill-local-variable 'diff-hl-reference-revision)) + (when (bound-and-true-p diff-hl-amend-mode) + (diff-hl-amend-mode -1)) + ;; Don't touch buffers with the local reference (set by + ;; `diff-hl-set-reference-rev-in-project' ), when called without a + ;; prefix. + (unless (local-variable-p 'diff-hl-reference-revision) + (diff-hl-update))))) + (message "Reference revision reset globally to the most recent revision")) + +(defun diff-hl-reset-reference-rev-in-project (&optional proj) + "Reset the reference revision in the project PROJ to the +global value. + +PROJ defaults to the current project." + (interactive) + (when-let* ((proj (or proj (project-current)))) + ;; reset cache for the project + (setq diff-hl-reference-revision-projects-cache + (assoc-delete-all (diff-hl--project-root proj) + diff-hl-reference-revision-projects-cache + #'string-equal)) + ;; reset value in project buffers + (dolist (buf (project-buffers proj)) + (with-current-buffer buf + (when diff-hl-mode + (when (bound-and-true-p diff-hl-amend-mode) + (diff-hl-amend-mode -1)) + (kill-local-variable 'diff-hl-reference-revision) + (diff-hl-update)))) + (message "Reference revision reset to the global value (project %s)" + (project-name proj)))) ;;;###autoload (define-globalized-minor-mode global-diff-hl-mode diff-hl-mode diff --git a/lisp/emacsql/emacsql-pkg.el b/lisp/emacsql/emacsql-pkg.el index c3161a1d..dd1fbdac 100644 --- a/lisp/emacsql/emacsql-pkg.el +++ b/lisp/emacsql/emacsql-pkg.el @@ -1,9 +1,9 @@ ;; -*- no-byte-compile: t; lexical-binding: nil -*- -(define-package "emacsql" "20250601.1009" +(define-package "emacsql" "20251116.1655" "High-level SQL database front-end." '((emacs "26.1")) :url "https://github.com/magit/emacsql" - :commit "ced062890061b6e4fbe4d00c0617f7ff84fff25c" - :revdesc "ced062890061" + :commit "e1908de2cf2c7b77798ef6645d514dded1d7f8a4" + :revdesc "e1908de2cf2c" :authors '(("Christopher Wellons" . "wellons@nullprogram.com")) :maintainers '(("Jonas Bernoulli" . "emacs.emacsql@jonas.bernoulli.dev"))) diff --git a/lisp/emacsql/emacsql-sqlite-builtin.el b/lisp/emacsql/emacsql-sqlite-builtin.el index 94edd26c..2a7bf7f1 100644 --- a/lisp/emacsql/emacsql-sqlite-builtin.el +++ b/lisp/emacsql/emacsql-sqlite-builtin.el @@ -49,8 +49,9 @@ buffer. This is for debugging purposes." (and (oref connection handle) t)) (cl-defmethod emacsql-close ((connection emacsql-sqlite-builtin-connection)) - (sqlite-close (oref connection handle)) - (oset connection handle nil)) + (when (oref connection handle) + (sqlite-close (oref connection handle)) + (oset connection handle nil))) (cl-defmethod emacsql-send-message ((connection emacsql-sqlite-builtin-connection) message) diff --git a/lisp/emacsql/emacsql-sqlite-module.el b/lisp/emacsql/emacsql-sqlite-module.el index 8dd1986e..413a6260 100644 --- a/lisp/emacsql/emacsql-sqlite-module.el +++ b/lisp/emacsql/emacsql-sqlite-module.el @@ -55,8 +55,9 @@ buffer. This is for debugging purposes." (and (oref connection handle) t)) (cl-defmethod emacsql-close ((connection emacsql-sqlite-module-connection)) - (sqlite3-close (oref connection handle)) - (oset connection handle nil)) + (when (oref connection handle) + (sqlite3-close (oref connection handle)) + (oset connection handle nil))) (cl-defmethod emacsql-send-message ((connection emacsql-sqlite-module-connection) message) diff --git a/lisp/emacsql/emacsql.el b/lisp/emacsql/emacsql.el index 8c287748..5651f31f 100644 --- a/lisp/emacsql/emacsql.el +++ b/lisp/emacsql/emacsql.el @@ -6,8 +6,8 @@ ;; Maintainer: Jonas Bernoulli ;; Homepage: https://github.com/magit/emacsql -;; Package-Version: 20250601.1009 -;; Package-Revision: ced062890061 +;; Package-Version: 20251116.1655 +;; Package-Revision: e1908de2cf2c ;; Package-Requires: ((emacs "26.1")) ;; SPDX-License-Identifier: Unlicense @@ -19,6 +19,11 @@ ;; PostgreSQL and MySQL are also supported, but use of these connectors ;; is not recommended. +;; Any readable lisp value can be stored as a value in EmacSQL, +;; including numbers, strings, symbols, lists, vectors, and closures. +;; EmacSQL has no concept of TEXT values; it's all just lisp objects. +;; The lisp object `nil' corresponds 1:1 with NULL in the database. + ;; See README.md for much more complete documentation. ;;; Code: @@ -33,7 +38,7 @@ "The EmacSQL SQL database front-end." :group 'comm) -(defconst emacsql-version "4.3.1") +(defconst emacsql-version "4.3.3") (defvar emacsql-global-timeout 30 "Maximum number of seconds to wait before bailing out on a SQL command. diff --git a/lisp/ess/ess-custom.el b/lisp/ess/ess-custom.el index 1276e7f0..08a84091 100644 --- a/lisp/ess/ess-custom.el +++ b/lisp/ess/ess-custom.el @@ -563,6 +563,7 @@ contain spaces on either side." :type '(repeat string) :group 'ess :package-version '(ess . "25.01.1")) + (defvar ess-S-assign) (make-obsolete-variable 'ess-S-assign 'ess-assign-list "ESS 18.10") diff --git a/lisp/ess/ess-inf.el b/lisp/ess/ess-inf.el index 32285f32..28c79357 100644 --- a/lisp/ess/ess-inf.el +++ b/lisp/ess/ess-inf.el @@ -1,6 +1,6 @@ ;;; ess-inf.el --- Support for running S as an inferior Emacs process -*- lexical-binding: t; -*- -;; Copyright (C) 1989-2023 Free Software Foundation, Inc. +;; Copyright (C) 1989-2025 Free Software Foundation, Inc. ;; Author: David Smith ;; Created: 7 Jan 1994 @@ -1998,7 +1998,7 @@ meaning as for `ess-eval-region'." (define-key map "\C-c\C-z" #'ess-switch-to-inferior-or-script-buffer) ; mask comint map (define-key map "\C-d" #'delete-char) ; EOF no good in S (define-key map "\t" #'completion-at-point) - (define-key map "\M-?" #'ess-complete-object-name) + (define-key map "\M-?" #'ess-complete-object-name); stealing M-? from xref(standard Emacs) (define-key map "\C-c\C-k" #'ess-request-a-process) (define-key map "," #'ess-smart-comma) (define-key map "\C-c\C-d" 'ess-doc-map) @@ -3143,7 +3143,7 @@ Uses `temp-buffer-show-function' and respects (defun ess--inject-code-from-file (file &optional chunked) "Load code from FILE into process. -If CHUNKED is non-nil, split the file by separator (must be at +If CHUNKED is non-nil, split the file by \\^L separator (must be at bol) and load each chunk separately." ;; This is different from ess-load-file as it works by directly loading the ;; string into the process and thus works on remotes. diff --git a/lisp/ess/ess-pkg.el b/lisp/ess/ess-pkg.el index 59ad83d1..3a35cd86 100644 --- a/lisp/ess/ess-pkg.el +++ b/lisp/ess/ess-pkg.el @@ -1,10 +1,10 @@ ;; -*- no-byte-compile: t; lexical-binding: nil -*- -(define-package "ess" "20250606.831" +(define-package "ess" "20251015.1619" "Emacs Speaks Statistics." '((emacs "25.1")) :url "https://ess.r-project.org/" - :commit "cd85d1e1f0e897b409a948a3a4afdaffe032812e" - :revdesc "cd85d1e1f0e8" + :commit "a7d685bd9a3dbc8540edf86318012a0a0528e49e" + :revdesc "a7d685bd9a3d" :authors '(("David Smith" . "dsmith@stats.adelaide.edu.au") ("A.J. Rossini" . "blindglobe@gmail.com") ("Richard M. Heiberger" . "rmh@temple.edu") diff --git a/lisp/ess/ess-r-flymake.el b/lisp/ess/ess-r-flymake.el index a9fedb31..f223a608 100644 --- a/lisp/ess/ess-r-flymake.el +++ b/lisp/ess/ess-r-flymake.el @@ -27,7 +27,7 @@ ;; Flymake is the built-in Emacs package that supports on-the-fly ;; syntax checking. This file adds support for this in ess-r-mode by ;; relying on the lintr package, available on CRAN and currently -;; hosted at https://github.com/jimhester/lintr. +;; hosted at https://github.com/r-lib/lintr. ;;; Code: @@ -76,28 +76,26 @@ each element is passed as argument to `lintr::linters_with_defaults'." (defvar-local ess-r--flymake-proc nil) (defvar-local ess-r--lintr-file nil - "Location of the .lintr file for this buffer.") + "Location of the .lintr config file for this buffer.") (defvar ess-r--flymake-def-linter (replace-regexp-in-string "[\n\t ]+" " " "esslint <- function(str, ...) { - if (!suppressWarnings(require(lintr, quietly=T))) { + if (!suppressWarnings(requireNamespace('lintr', quietly=TRUE))) { cat('@@error: @@`lintr` package not installed') + } else if (packageVersion('lintr') <= '3.0.0') { + cat('@@error: @@Need `lintr` version > v3.0.0') } else { - if (packageVersion('lintr') <= '3.0.0') { - cat('@@error: @@Need `lintr` version > v3.0.0') - } else { - tryCatch(lintr::lint(commandArgs(TRUE), ...), - error = function(e) { - cat('@@warning: @@', conditionMessage(e)) - }) - } + tryCatch(lintr::lint(text=str, ..., parse_settings=TRUE), + error = function(e) { + cat('@@warning: @@', conditionMessage(e)) + }) } };")) (defun ess-r--find-lintr-file () - "Return the absolute path to the .lintr file. + "Return the absolute path to the .lintr config file. Check first the current directory, then the project root, then the package root, then the user's home directory. Return nil if we couldn't find a .lintr file." diff --git a/lisp/ess/ess-r-mode.el b/lisp/ess/ess-r-mode.el index 1cf1ae50..2a6c3293 100644 --- a/lisp/ess/ess-r-mode.el +++ b/lisp/ess/ess-r-mode.el @@ -1,6 +1,6 @@ ;;; ess-r-mode.el --- R customization -*- lexical-binding: t; -*- -;; Copyright (C) 1997-2022 Free Software Foundation, Inc. +;; Copyright (C) 1997-2025 Free Software Foundation, Inc. ;; Author: A.J. Rossini ;; Created: 12 Jun 1997 ;; Maintainer: ESS-core @@ -264,7 +264,7 @@ value by using `ess-r-runners-reset'." (defvar ess-r-mode-map (let ((map (make-sparse-keymap))) (define-key map (kbd "C-c C-=") #'ess-cycle-assign) - (define-key map "\M-?" #'ess-complete-object-name) + ;;(define-key map "\M-?" #'ess-complete-object-name); not stealing M-? from "standard Emacs" (define-key map (kbd "C-c C-.") 'ess-rutils-map) map)) @@ -992,7 +992,7 @@ as `ess-r-created-runners' upon ESS initialization." (message "Recreated %d R versions known to ESS: %s" (length versions) versions)) (if ess-microsoft-p - (cl-mapcar (lambda (v p) (ess-define-runner v "R" p)) versions ess-rterm-version-paths) + (cl-mapc (lambda (v p) (ess-define-runner v "R" p)) versions ess-rterm-version-paths) (mapc (lambda (v) (ess-define-runner v "R")) versions)) ;; Add to menu (when ess-r-created-runners @@ -1619,7 +1619,7 @@ environment to the search path." Send the contents of the etc/ESSR/R directory to the remote process through the process connection file by file. Then, collect all the objects into an ESSR environment and attach to -the search path. If CHUNKED is non-nil, split each file by +the search path. If CHUNKED is non-nil, split each file by \\^L separators and send chunk by chunk." (ess-command (format ".ess.ESSRversion <<- '%s'\n" essr-version)) (with-temp-message "Loading ESSR into remote ..." diff --git a/lisp/ess/ess-rd.el b/lisp/ess/ess-rd.el index 78ca7b4c..937c30cf 100644 --- a/lisp/ess/ess-rd.el +++ b/lisp/ess/ess-rd.el @@ -1,6 +1,6 @@ ;; ess-rd.el --- Support for editing R documentation (Rd) source -*- lexical-binding: t; -*- -;; Copyright (C) 1997-2023 Free Software Foundation, Inc. +;; Copyright (C) 1997-2025 Free Software Foundation, Inc. ;; Author: KH ;; Created: 25 July 1997 ;; Maintainer: ESS-core @@ -48,6 +48,7 @@ ("`al" "\\alias" nil :system t) ("`au" "\\author" nil :system t) ("`bf" "\\bold" nil :system t) + ;; not (yet) "bibcitep" "bibcitet" "bibshow" "bibinfo" ("`co" "\\code" nil :system t) ("`de" "\\describe" nil :system t) ("`dn" "\\description" nil :system t) @@ -62,6 +63,7 @@ ("`kw" "\\keyword" nil :system t) ("`li" "\\link" nil :system t) ("`me" "\\method" nil :system t) + ("`ma" "\\manual" nil :system t) ("`na" "\\name" nil :system t) ("`no" "\\note" nil :system t) ("`re" "\\references" nil :system t) @@ -122,7 +124,7 @@ All Rd mode abbrevs start with a grave accent (`)." "tabular" "title" "usage" "value")) -(defvar Rd-keywords +(defvar Rd-keywords ; to be highlighted in Rd-mode '( ;; the next two lines: only valid in R <= 2.8.1 ;; commented out on 2011-01-14 for ESS version 5.13: @@ -136,11 +138,14 @@ All Rd mode abbrevs start with a grave accent (`)." "href" "ifelse" "if" "item" "kbd" "ldots" "linkS4class" "link" "method" + "manual" "newcommand" "option" "out" "pkg" "sQuote" "renewcommand" "samp" "strong" "tab" "url" "var" "verb" ;; System macros (from /share/Rd/macros/system.Rd ): + "bibcitep" "bibcitet" "bibshow" "bibinfo" "CRANpkg" "PR" "sspace" "doi" + "I" ; should we? "LaTeX" "proglang" "packageTitle" "packageDescription" "packageAuthor" diff --git a/lisp/ess/ess-tracebug.el b/lisp/ess/ess-tracebug.el index f7f2698f..36ee08e9 100644 --- a/lisp/ess/ess-tracebug.el +++ b/lisp/ess/ess-tracebug.el @@ -1,6 +1,6 @@ ;; ess-tracebug.el --- Tracing and debugging facilities for ESS. -*- lexical-binding: t; -*- -;; Copyright (C) 2011-2022 Free Software Foundation, Inc. +;; Copyright (C) 2011-2025 Free Software Foundation, Inc. ;; Author: Vitalie Spinu ;; Maintainer: Vitalie Spinu ;; Created: Oct 14 14:15:22 2010 @@ -588,7 +588,7 @@ ESS internal code assumes default R prompts.") (setq-local compilation-error-regexp-alist ess-error-regexp-alist) (let (compilation-mode-font-lock-keywords) (compilation-setup t)) - (setq next-error-function 'ess-tracebug-next-error-function) + (setq next-error-function #'ess-tracebug-next-error-function) ;; new locals (make-local-variable 'ess--tb-last-input) (make-local-variable 'ess--tb-last-input-overlay) @@ -1231,10 +1231,10 @@ value from EXPR and then sent to the subprocess." (defun ess-mpi-handle-messages (buf) "Handle all mpi messages in BUF and delete them. -The MPI message has the form TYPEFIELD... where TYPE is the +The MPI message has the form \\^[TYPE\\^^FIELD...\\^] where TYPE is the type of the messages on which handlers in `ess-mpi-handlers' are -dispatched. And FIELDs are strings. Return :incomplete if BUF -ends with an incomplete message." +dispatched, \\^C are ASCII control chars, and FIELDs are strings. +Return `:incomplete' if BUF ends with an incomplete message." (let ((obuf (current-buffer)) (out nil)) (with-current-buffer buf @@ -1992,6 +1992,9 @@ Each sublist has five elements: doesn't apply to current context." :group 'ess-debug :type '(alist :key-type symbol + ;; FIXME: What's this `group'? The values looks like strings! + ;; FIXME: The docstring talks about a 6th element (function) + ;; but it's missing here. :value-type (group string string symbol face))) (defcustom ess-bp-inactive-spec @@ -2001,7 +2004,8 @@ Each sublist has five elements: ;; `ess-bp-type-spec-alist' except that the second element giving ;; the R expression is meaningless here." ;;fixme: second element is missing make it nil for consistency with all other specs :group 'ess-debug - :type 'list) + :type '(alist :key-type symbol + :value-type (group string string symbol face))) (defcustom ess-bp-conditional-spec '(conditional "browser(expr={%s})" "CB[ %s ]>\n" question-mark ess-bp-fringe-browser-face) @@ -2011,14 +2015,16 @@ List format is identical to that of the elements of expression to be replaced instead of %s in the second and third elements of the specifications." :group 'ess-debug - :type 'list) + :type '(alist :key-type symbol + :value-type (group string string symbol face))) (defcustom ess-bp-logger-spec '(logger ".ess_log_eval('%s')" "L[ \"%s\" ]>\n" hollow-square ess-bp-fringe-logger-face) "List giving the loggers specifications. List format is identical to that of `ess-bp-type-spec-alist'." :group 'ess-debug - :type 'list) + :type '(alist :key-type symbol + :value-type (group string string symbol face))) (defun ess-bp-get-bp-specs (type &optional condition no-error) @@ -2339,7 +2345,7 @@ If there is no active R session, this command triggers an error." (defun ess-bp-next nil "Goto next breakpoint." (interactive) - (when-let ((bp-pos (next-single-property-change (point) 'ess-bp))) + (when-let* ((bp-pos (next-single-property-change (point) 'ess-bp))) (save-excursion (goto-char bp-pos) (when (get-text-property (1- (point)) 'ess-bp) @@ -2352,7 +2358,7 @@ If there is no active R session, this command triggers an error." (defun ess-bp-previous nil "Goto previous breakpoint." (interactive) - (if-let ((bp-pos (previous-single-property-change (point) 'ess-bp))) + (if-let* ((bp-pos (previous-single-property-change (point) 'ess-bp))) (goto-char (or (previous-single-property-change bp-pos 'ess-bp) bp-pos)) (message "No breakpoints before the point found"))) @@ -2820,7 +2826,10 @@ for signature and trace it with browser tracer." "*ALL*")) (setq fun (ess-completing-read "Undebug" debugged nil t nil nil def-val)) (if (equal fun "*ALL*" ) - (ess-command (concat ".ess_dbg_UndebugALL(c(\"" (mapconcat 'identity debugged "\", \"") "\"))\n") tbuffer) + (ess-command (concat ".ess_dbg_UndebugALL(c(\"" + (mapconcat #'identity debugged "\", \"") + "\"))\n") + tbuffer) (ess-command (format ".ess_dbg_UntraceOrUndebug(\"%s\")\n" fun) tbuffer)) (with-current-buffer tbuffer (if (= (point-max) 1) ;; not reliable TODO: diff --git a/lisp/ess/ess.el b/lisp/ess/ess.el index a807ab34..00186524 100644 --- a/lisp/ess/ess.el +++ b/lisp/ess/ess.el @@ -1,6 +1,6 @@ ;;; ess.el --- Emacs Speaks Statistics -*- lexical-binding: t; -*- -;; Copyright (C) 1997-2024 Free Software Foundation, Inc. +;; Copyright (C) 1997-2025 Free Software Foundation, Inc. ;; Author: David Smith ;; A.J. Rossini @@ -17,8 +17,8 @@ ;; ;; Maintainer: ESS Core Team ;; Created: 7 Jan 1994 -;; Package-Version: 20250606.831 -;; Package-Revision: cd85d1e1f0e8 +;; Package-Version: 20251015.1619 +;; Package-Revision: a7d685bd9a3d ;; URL: https://ess.r-project.org/ ;; Package-Requires: ((emacs "25.1")) ;; ESSR-Version: 1.8 @@ -129,7 +129,7 @@ Is set by \\[ess-version-string].") (interactive) (let ((reporter-prompt-for-summary-p 't)) (reporter-submit-bug-report - "ess-bugs@r-project.org" + "ess-help@r-project.org" (concat "ess-mode " (ess-version-string)) (list 'ess-language 'ess-dialect @@ -151,10 +151,12 @@ Is set by \\[ess-version-string].") ;;(goto-char (point-max)) (rfc822-goto-eoh) (forward-line 1) - (insert "\n\n-------------------------------------------------------\n") - (insert "This bug report will be sent to the ESS bugs email list\n") - (insert "Press C-c C-c when you are ready to send your message.\n") - (insert "-------------------------------------------------------\n\n") + (insert "\n\n-------------------------------------------------------------\n") + (insert "This bug report will be sent to the ESS _help_ email list\n") + (insert ">>> _INSTEAD_ we strongly recommend you open an issue for this\n") + (insert " at https://github.com/emacs-ess/ESS/issues .\n\n") + (insert "If you still prefer to use the ESS help email, press C-c C-c to send your message.\n") + (insert "-------------------------------------------------------------\n\n") (insert (with-current-buffer ess-dribble-buffer (goto-char (point-max)) (forward-line -100) diff --git a/lisp/ess/ess.info b/lisp/ess/ess.info index b17a2d86..f93e5716 100644 --- a/lisp/ess/ess.info +++ b/lisp/ess/ess.info @@ -1,4 +1,4 @@ -This is ess.info, produced by makeinfo version 7.1.1 from ess.texi. +This is ess.info, produced by makeinfo version 7.2 from ess.texi. INFO-DIR-SECTION Emacs START-INFO-DIR-ENTRY @@ -257,7 +257,7 @@ Changes and New Features in 25.01.0: suggest the related polymodes including poly-noweb, poly-markdown and poly-R (installed in that order). The package polymode itself, as well as the polymodes packages, are all on MELPA rather than - ELPA. Therefore, you need to add MELPA to the list of installation + ELPA. Therefore, you need to add MELPA to the list of installation archives as follows. ‘(add-to-list 'package-archives '("melpa-stable" . "https://stable.melpa.org/packages/"))’ for ‘M-x package-install’ @@ -2896,7 +2896,6 @@ are available: M-U . Up frame . `ess-debug-command-up' M-Q . Quit debugging . `ess-debug-command-quit' - These are all the tracebug commands defined in ‘ess-dev-map’ (‘C-c C-t ?’ to show this table): @@ -2933,7 +2932,6 @@ C-t ?’ to show this table): ? . Show this help . `ess-tracebug-show-help' - To configure how electric watch window splits the display see ‘ess-watch-width-threshold’ and ‘ess-watch-height-threshold’ variables. @@ -3828,7 +3826,6 @@ Comments as to what should be happening are prefixed by "##". ## myfile.Rout. With this suffix, the file will be opened in ## ess-transcript. -  File: ess.info, Node: ESS for SAS, Next: ESS for BUGS, Prev: ESS for R, Up: Top @@ -4541,11 +4538,11 @@ File: ess.info, Node: Reporting Bugs, Next: Mailing Lists, Prev: Bugs, Up: M 16.2 Reporting Bugs =================== -Please send bug reports, suggestions etc. to , -or post them on our github issue tracker -(https://github.com/emacs-ess/ESS/issues) +Please post bug reports, suggestions etc. on our github issue tracker +(https://github.com/emacs-ess/ESS/issues); if not possible, e-mail them +to . - The easiest way to do this is within Emacs by typing + The easiest way to set this up, is within Emacs by typing ‘M-x ess-submit-bug-report’ @@ -4567,7 +4564,7 @@ File: ess.info, Node: Mailing Lists, Next: Help with Emacs, Prev: Reporting B ================== There is a mailing list for discussions and announcements relating to -ESS. Join the list by sending an e-mail with "subscribe ess-help" (or +ESS. Join the list by sending an e-mail with "subscribe ess-help" (or "help") in the body to ; contributions to the list may be mailed to . Rest assured, this is a fairly low-volume mailing list. @@ -5052,113 +5049,112 @@ Concept Index * X Windows: X11. (line 6) * xref: Xref. (line 6) -  Tag Table: -Node: Top270 -Node: Introduction2908 -Node: Features5694 -Node: Current Features6520 -Node: New features10067 -Node: Credits37886 -Node: Manual41545 -Node: Installation44255 -Node: Installing from a third-party repository45192 -Node: Installing from source46139 -Node: Activating and Loading ESS47763 -Node: Check Installation48845 -Node: Interactive ESS49069 -Node: Starting up49914 -Node: Multiple ESS processes50674 -Node: ESS processes on Remote Computers51787 -Node: Customizing startup56014 -Node: Controlling buffer display58996 -Node: Entering commands61633 -Node: Command-line editing62795 -Node: Transcript64060 -Node: Last command65857 -Node: Process buffer motion67315 -Node: Transcript resubmit68858 -Node: Saving transcripts70855 -Node: Command History72689 -Node: Saving History76190 -Node: History expansion76971 -Node: Hot keys80362 -Node: Statistical Process running in ESS?84532 -Node: Emacsclient85879 -Node: Other86699 -Node: Evaluating code87742 -Node: Transcript Mode91674 -Node: Resubmit92847 -Node: Clean93922 -Node: Editing objects94922 -Node: Edit buffer96040 -Node: Loading98130 -Node: Error Checking99185 -Node: Indenting100258 -Node: Styles103410 -Node: Other edit buffer commands105912 -Node: Source Files107624 -Node: Source Directories112340 -Node: Help115549 -Node: Completion120251 -Node: Object names120466 -Node: Function arguments123180 -Node: Minibuffer completion124159 -Node: Company124657 -Node: Icicles125056 -Node: Developing with ESS126432 -Node: ESS tracebug126878 -Node: Getting started with tracebug129937 -Node: Editing documentation132223 -Node: R documentation files132775 -Node: roxygen2136590 -Node: Namespaced Evaluation141113 -Node: Extras143127 -Node: ESS ElDoc144151 -Node: ESS Flymake145731 -Node: Handy commands146861 -Node: Highlighting148138 -Node: Parens149189 -Node: Graphics149665 -Node: printer150336 -Node: X11151108 -Node: winjava151447 -Node: Imenu151859 -Node: Toolbar152714 -Node: Xref153122 -Node: Rdired153450 -Node: Package listing154529 -Node: Org155977 -Node: Sweave and AUCTeX156931 -Node: ESS for R159563 -Node: ESS(R)--Editing files159863 -Node: iESS(R)--Inferior ESS processes160368 -Node: Philosophies for using ESS(R)163087 -Node: Example ESS usage164014 -Node: ESS for SAS165419 -Node: ESS(SAS)--Design philosophy166146 -Node: ESS(SAS)--Editing files167083 -Node: ESS(SAS)--TAB key169027 -Node: ESS(SAS)--Batch SAS processes170441 -Node: ESS(SAS)--Function keys for batch processing175661 -Node: iESS(SAS)--Interactive SAS processes185568 -Node: iESS(SAS)--Common problems189510 -Node: ESS(SAS)--Graphics191124 -Node: ESS(SAS)--Windows191923 -Node: ESS for BUGS192507 -Node: ESS for JAGS194319 -Node: Mailing lists/bug reports197815 -Node: Bugs198079 -Node: Reporting Bugs199755 -Node: Mailing Lists200652 -Node: Help with Emacs201389 -Node: Customization201925 -Node: Indices202703 -Node: Key index202878 -Node: Function and program index208010 -Node: Variable index217430 -Node: Concept index220991 +Node: Top268 +Node: Introduction2906 +Node: Features5692 +Node: Current Features6518 +Node: New features10065 +Node: Credits37885 +Node: Manual41544 +Node: Installation44254 +Node: Installing from a third-party repository45191 +Node: Installing from source46138 +Node: Activating and Loading ESS47762 +Node: Check Installation48844 +Node: Interactive ESS49068 +Node: Starting up49913 +Node: Multiple ESS processes50673 +Node: ESS processes on Remote Computers51786 +Node: Customizing startup56013 +Node: Controlling buffer display58995 +Node: Entering commands61632 +Node: Command-line editing62794 +Node: Transcript64059 +Node: Last command65856 +Node: Process buffer motion67314 +Node: Transcript resubmit68857 +Node: Saving transcripts70854 +Node: Command History72688 +Node: Saving History76189 +Node: History expansion76970 +Node: Hot keys80361 +Node: Statistical Process running in ESS?84531 +Node: Emacsclient85878 +Node: Other86698 +Node: Evaluating code87741 +Node: Transcript Mode91673 +Node: Resubmit92846 +Node: Clean93921 +Node: Editing objects94921 +Node: Edit buffer96039 +Node: Loading98129 +Node: Error Checking99184 +Node: Indenting100257 +Node: Styles103409 +Node: Other edit buffer commands105911 +Node: Source Files107623 +Node: Source Directories112339 +Node: Help115548 +Node: Completion120250 +Node: Object names120465 +Node: Function arguments123179 +Node: Minibuffer completion124158 +Node: Company124656 +Node: Icicles125055 +Node: Developing with ESS126431 +Node: ESS tracebug126877 +Node: Getting started with tracebug129934 +Node: Editing documentation132220 +Node: R documentation files132772 +Node: roxygen2136587 +Node: Namespaced Evaluation141110 +Node: Extras143124 +Node: ESS ElDoc144148 +Node: ESS Flymake145728 +Node: Handy commands146858 +Node: Highlighting148135 +Node: Parens149186 +Node: Graphics149662 +Node: printer150333 +Node: X11151105 +Node: winjava151444 +Node: Imenu151856 +Node: Toolbar152711 +Node: Xref153119 +Node: Rdired153447 +Node: Package listing154526 +Node: Org155974 +Node: Sweave and AUCTeX156928 +Node: ESS for R159560 +Node: ESS(R)--Editing files159860 +Node: iESS(R)--Inferior ESS processes160365 +Node: Philosophies for using ESS(R)163084 +Node: Example ESS usage164011 +Node: ESS for SAS165415 +Node: ESS(SAS)--Design philosophy166142 +Node: ESS(SAS)--Editing files167079 +Node: ESS(SAS)--TAB key169023 +Node: ESS(SAS)--Batch SAS processes170437 +Node: ESS(SAS)--Function keys for batch processing175657 +Node: iESS(SAS)--Interactive SAS processes185564 +Node: iESS(SAS)--Common problems189506 +Node: ESS(SAS)--Graphics191120 +Node: ESS(SAS)--Windows191919 +Node: ESS for BUGS192503 +Node: ESS for JAGS194315 +Node: Mailing lists/bug reports197811 +Node: Bugs198075 +Node: Reporting Bugs199751 +Node: Mailing Lists200670 +Node: Help with Emacs201408 +Node: Customization201944 +Node: Indices202722 +Node: Key index202897 +Node: Function and program index208029 +Node: Variable index217449 +Node: Concept index221010  End Tag Table diff --git a/lisp/flycheck/flycheck-pkg.el b/lisp/flycheck/flycheck-pkg.el index e18a3696..e7a2462b 100644 --- a/lisp/flycheck/flycheck-pkg.el +++ b/lisp/flycheck/flycheck-pkg.el @@ -1,10 +1,11 @@ ;; -*- no-byte-compile: t; lexical-binding: nil -*- -(define-package "flycheck" "20250527.907" +(define-package "flycheck" "20251119.1203" "On-the-fly syntax checking." - '((emacs "27.1")) + '((emacs "27.1") + (seq "2.24")) :url "https://www.flycheck.org" - :commit "a4d782e7af12e20037c0cecf0d4386cd2676c085" - :revdesc "a4d782e7af12" + :commit "1eafe2911d50c9f58efce81ff8abea59495e1ff3" + :revdesc "1eafe2911d50" :keywords '("convenience" "languages" "tools") :authors '(("Sebastian Wiesner" . "swiesner@lunaryorn.com")) :maintainers '(("Clément Pit-Claudel" . "clement.pitclaudel@live.com") diff --git a/lisp/flycheck/flycheck.el b/lisp/flycheck/flycheck.el index 94a74cd3..2989e501 100644 --- a/lisp/flycheck/flycheck.el +++ b/lisp/flycheck/flycheck.el @@ -10,9 +10,9 @@ ;; Bozhidar Batsov ;; URL: https://www.flycheck.org ;; Keywords: convenience, languages, tools -;; Package-Version: 20250527.907 -;; Package-Revision: a4d782e7af12 -;; Package-Requires: ((emacs "27.1")) +;; Package-Version: 20251119.1203 +;; Package-Revision: 1eafe2911d50 +;; Package-Requires: ((emacs "27.1") (seq "2.24")) ;; This file is not part of GNU Emacs. @@ -10886,6 +10886,15 @@ Requires Flake8 3.0 or newer. See URL (flycheck-def-config-file-var flycheck-python-ruff-config python-ruff '("pyproject.toml" "ruff.toml" ".ruff.toml")) +(defun flycheck-python-ruff-explainer (err) + "Return documentation for the ruff `flycheck-error' ERR." + (when-let (error-code (flycheck-error-id err)) + (lambda () + (flycheck-call-checker-process + 'python-ruff nil standard-output t "rule" error-code) + (with-current-buffer standard-output + (flycheck--fontify-as-markdown))))) + (flycheck-define-checker python-ruff "A Python syntax and style checker using Ruff. @@ -10907,14 +10916,16 @@ See URL `https://docs.astral.sh/ruff/'." :error-patterns ((error line-start (or "-" (file-name)) ":" line ":" (optional column ":") " " - "SyntaxError: " + ;; first variant is produced by ruff < 0.8 and kept for backward compat + (or "SyntaxError: " "invalid-syntax: ") (message (one-or-more not-newline)) line-end) (warning line-start (or "-" (file-name)) ":" line ":" (optional column ":") " " - (id (one-or-more (any alpha)) (one-or-more digit) " ") + (id (one-or-more (any alpha)) (one-or-more digit)) " " (message (one-or-more not-newline)) line-end)) + :error-explainer flycheck-python-ruff-explainer :working-directory flycheck-python-find-project-root :modes (python-mode python-ts-mode) :next-checkers ((warning . python-mypy))) @@ -12502,13 +12513,25 @@ or added as a shellcheck directive before the source command: :safe #'booleanp :package-version '(flycheck . "31")) +(flycheck-def-option-var flycheck-shellcheck-infer-shell nil sh-shellcheck + "Whether to let ShellCheck infer the shell from the script. + +When non-nil, the --shell flag is not passed to ShellCheck, +allowing it to infer the shell from the shebang line or +shellcheck directives in the script." + :type 'boolean + :safe #'booleanp + :package-version '(flycheck . "36")) + (flycheck-define-checker sh-shellcheck "A shell script syntax and style checker using Shellcheck. See URL `https://github.com/koalaman/shellcheck/'." :command ("shellcheck" "--format" "checkstyle" - "--shell" (eval (symbol-name sh-shell)) + (eval + (unless flycheck-shellcheck-infer-shell + (list "--shell" (symbol-name sh-shell)))) (option-flag "--external-sources" flycheck-shellcheck-follow-sources) (option "--exclude" flycheck-shellcheck-excluded-warnings list diff --git a/lisp/gnuplot/gnuplot-context.el b/lisp/gnuplot/gnuplot-context.el index 1dd29451..25831a61 100644 --- a/lisp/gnuplot/gnuplot-context.el +++ b/lisp/gnuplot/gnuplot-context.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 . +;; along with this program. If not, see . ;;; Commentary: @@ -84,7 +84,7 @@ ;; ;; The parsing machine and compiler are partially based on the ;; description in Medeiros and Ierusalimschy 2008, "A Parsing Machine -;; for PEGs" (http://dl.acm.org/citation.cfm?doid=1408681.1408683). +;; for PEGs" (https://dl.acm.org/citation.cfm?doid=1408681.1408683). ;; ;; The pattern-matching language ;; ============================= diff --git a/lisp/gnuplot/gnuplot-gui.el b/lisp/gnuplot/gnuplot-gui.el index fd61c594..756c0bdc 100644 --- a/lisp/gnuplot/gnuplot-gui.el +++ b/lisp/gnuplot/gnuplot-gui.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 . +;; along with this program. If not, see . ;;; Commentary: diff --git a/lisp/gnuplot/gnuplot-pkg.el b/lisp/gnuplot/gnuplot-pkg.el index 4260885f..18b68a0e 100644 --- a/lisp/gnuplot/gnuplot-pkg.el +++ b/lisp/gnuplot/gnuplot-pkg.el @@ -1,11 +1,11 @@ ;; -*- no-byte-compile: t; lexical-binding: nil -*- -(define-package "gnuplot" "20250613.1223" +(define-package "gnuplot" "20250724.1531" "Major-mode and interactive frontend for gnuplot." '((emacs "28.1") (compat "30")) :url "https://github.com/emacs-gnuplot/gnuplot" - :commit "f10d42221856e86c57dd5cc7400c078c021ba710" - :revdesc "f10d42221856" + :commit "43e9674b869475b1c2a32f045c167673eb2faae0" + :revdesc "43e9674b8694" :keywords '("data" "gnuplot" "plotting") :maintainers '(("Maxime Tréca" . "maxime@gmail.com") ("Daniel Mendler" . "mail@daniel-mendler.de"))) diff --git a/lisp/gnuplot/gnuplot.el b/lisp/gnuplot/gnuplot.el index edb7b278..7d749478 100644 --- a/lisp/gnuplot/gnuplot.el +++ b/lisp/gnuplot/gnuplot.el @@ -5,8 +5,8 @@ ;; Author: Jon Oddie, Bruce Ravel, Phil Type ;; Maintainer: Maxime Tréca , Daniel Mendler ;; Created: 1998 -;; Package-Version: 20250613.1223 -;; Package-Revision: f10d42221856 +;; Package-Version: 20250724.1531 +;; Package-Revision: 43e9674b8694 ;; Keywords: data gnuplot plotting ;; URL: https://github.com/emacs-gnuplot/gnuplot ;; Package-Requires: ((emacs "28.1") (compat "30")) @@ -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 this program. If not, see . +;; along with this program. If not, see . ;;; Commentary: diff --git a/lisp/gnuplot/gnuplot.info b/lisp/gnuplot/gnuplot.info index 24be7a4c..6359747a 100644 --- a/lisp/gnuplot/gnuplot.info +++ b/lisp/gnuplot/gnuplot.info @@ -1,4 +1,4 @@ -This is gnuplot.info, produced by makeinfo version 7.1.1 from +This is gnuplot.info, produced by makeinfo version 7.2 from gnuplot.texi. INFO-DIR-SECTION Math @@ -96,7 +96,6 @@ File: gnuplot.info, Node: Copyright, Next: Introduction, Prev: Gnuplot, Up: Copyright (C) 1986 - 1993, 1998, 2004, 2007 Thomas Williams, Colin Kelley Copyright (C) 2004-2024 various authors - Permission to use, copy, and distribute this software and its documentation for any purpose with or without fee is hereby granted, provided that the above copyright notice appear in all copies and that @@ -134,7 +133,6 @@ to the extent permitted by applicable law. Gnuplot 4.0 and subsequent releases: See list of contributors at head of this document. -  File: gnuplot.info, Node: Introduction, Next: Seeking-assistance_/_Bugs, Prev: Copyright, Up: Gnuplot @@ -174,7 +172,6 @@ See 'syntax' and 'quotes' for more details. Example: set title "My First Plot"; plot 'data'; print "all done!" - Commands may extend over several input lines by ending each line but the last with a backslash (\). The backslash must be the _last_ character on each line. The effect is as if the backslash and newline @@ -207,7 +204,6 @@ executed in the order specified, as are commands supplied by the -e option, for example gnuplot file1.in -e "reset" file2.in - The special filename "-" is used to force reading from stdin. 'Gnuplot' exits after the last file is processed. If no load files are named, 'Gnuplot' takes interactive input from stdin. See help @@ -215,7 +211,6 @@ named, 'Gnuplot' takes interactive input from stdin. See help more details, or type gnuplot --help - In sessions with an interactive plot window you can hit 'h' anywhere on the plot for help about 'hotkeys' and 'mousing' features. @@ -236,7 +231,6 @@ for a FAQ (Frequently Asked Questions) list. is https://stackoverflow.com/questions/tagged/gnuplot - Bug reports and feature requests should be uploaded to the trackers at https://sourceforge.net/p/gnuplot/_list/tickets @@ -261,12 +255,10 @@ spam. The address for mailing to list members is: gnuplot-info@lists.sourceforge.net - A mailing list for those interested in the development version of gnuplot is: gnuplot-beta@lists.sourceforge.net -  File: gnuplot.info, Node: New_features_in_version_6, Next: Differences_between_versions_5_and_6, Prev: Seeking-assistance_/_Bugs, Up: Gnuplot @@ -561,7 +553,6 @@ reloaded later. print $GPVAL_LAST_MULTIPLOT unset print - • The *note replot:: command will check to see if the most recent plot command was part of a completed multiplot. If so, it will execute *note remultiplot:: instead of reexecuting that single plot @@ -820,14 +811,12 @@ Deprecated in version 5.4, removed in 6.0 pause -1 } - Deprecated in version 5.4, removed in 6.0 set dgrid3d ,,foo # no keyword to indicate meaning of foo Current equivalent set dgrid3d qnorm foo # (example only; qnorm is not the only option) - Deprecated in version 5.0, not present in 6.0 unless built with configuration optione -enable-backward-compatibility set style increment user @@ -836,7 +825,6 @@ configuration optione -enable-backward-compatibility use "set linetype" to redefine a convenient range of linetypes explicit use of "linestyle N" or "linestyle variable" - Deprecated in version 5.0, removed in 6.0 set clabel @@ -844,11 +832,9 @@ configuration optione -enable-backward-compatibility `set clabel "format"` is replaced by `set cntrlabel format "format"`. *note clabel:: is replaced by `set cntrlabel onecolor`. - Deprecated in version 5.0, removed in 6.0 show palette fit2rgbformulae -  File: gnuplot.info, Node: Demos_and_Online_Examples, Next: Batch/Interactive_Operation, Prev: Differences_between_versions_5_and_6, Up: Gnuplot @@ -899,7 +885,6 @@ Gnuplot accepts the following options on the command line -e "command1; command2; ..." -c scriptfile ARG1 ARG2 ... - -p tells the program not to close any remaining interactive plot windows when the program exits. @@ -924,26 +909,21 @@ File: gnuplot.info, Node: Examples, Prev: command_line_options, Up: Batch/Int To launch an interactive session: gnuplot - To execute two command files "input1" and "input2" in batch mode: gnuplot input1 input2 - To launch an interactive session after an initialization file "header" and followed by another command file "trailer": gnuplot header - trailer - To give 'gnuplot' commands directly in the command line, using the "-persist" option so that the plot remains on the screen afterwards: gnuplot -persist -e "set title 'Sine curve'; plot sin(x)" - To set user-defined variables a and s prior to executing commands from a file: gnuplot -e "a=2; s='file.png'" input.gpl -  File: gnuplot.info, Node: Canvas_size, Next: Command-line-editing, Prev: Batch/Interactive_Operation, Up: Gnuplot @@ -971,7 +951,6 @@ values larger than 1 may cause problems. set output "figure.png" plot "data" with lines - These commands produce an output file "figure.png" that is 600 pixels wide and 400 pixels tall. The plot will fill the lower left quarter of this canvas. @@ -997,7 +976,6 @@ readline and BSD libedit libraries have their own documentation. `Line-editing`: - ^B moves back a single character. ^F moves forward a single character. ^A moves to the beginning of the line. @@ -1012,15 +990,12 @@ readline and BSD libedit libraries have their own documentation. ^V inhibits the interpretation of the following key as editing command. TAB performs filename-completion. - `History`: - ^P moves back through history. ^N moves forward through history. ^R starts a backward-search. -  File: gnuplot.info, Node: Comments, Next: Coordinates, Prev: Command-line-editing, Up: Gnuplot @@ -1047,7 +1022,6 @@ position is specified by the syntax: {} , {} {,{} } - Each can either be 'first', 'second', 'polar', 'graph', 'screen', or 'character'. @@ -1079,7 +1053,6 @@ relative value is interpreted as multiplier. For example, set logscale x set arrow 100,5 rto 10,2 - plots an arrow from position 100,5 to position 1000,7 since the x axis is logarithmic while the y axis is linear. @@ -1103,14 +1076,12 @@ four columns, with a text field in column 3: 1.000 2.000 "Third column is all of this text" 4.00 - Text fields can be positioned within a 2-D or 3-D plot using the commands: plot 'datafile' using 1:2:4 with labels splot 'datafile' using 1:2:3:4 with labels - A column of text data can also be used to label the ticmarks along one or more of the plot axes. The example below plots a line through a series of points with (X,Y) coordinates taken from columns 3 and 4 of @@ -1122,7 +1093,6 @@ tic mark with text taken from column 1 of the input datafile. set xtics plot 'datafile' using 3:4:xticlabels(1) with linespoints - There is also an option that will interpret the first entry in a column of input data (i.e. the column heading) as a text field, and use it as the key title for data plotted from that column. The example @@ -1132,12 +1102,10 @@ the required line: plot 'datafile' using 1:(f($2)/$4) with lines title columnhead(2) - Another example: plot for [i=2:6] 'datafile' using i title "Results for ".columnhead(i) - This use of column headings is automated by *note columnheaders:: or 'set key autotitle columnhead'. See *note labels::, 'using xticlabels', *note title::, 'using', 'key autotitle'. @@ -1173,7 +1141,6 @@ See 'latex'. {/Arial:Bold=20 abc} print abc in boldface Arial font size 20 \U+ \U+221E Unicode point U+221E (INFINITY) - The markup control characters act on the following single character or bracketed clause. The bracketed clause may contain a string of characters with no additional markup, e.g. 2^{10}, or it may contain @@ -1191,7 +1158,6 @@ point size. {/:Bold A_{/:Normal{/:Italic i}}} {/"Times New Roman":Bold=20 A_{/:Normal{/:Italic i}}} - The phantom box is useful for a@^b_c to align superscripts and subscripts but does not work well for overwriting a diacritical mark on a letter. For that purpose it is much better to use an encoding (e.g. @@ -1207,7 +1173,6 @@ character. Thus would produce 'abc ghi'. - The '~' character causes the next character or bracketed text to be overprinted by the following character or bracketed text. The second text will be horizontally centered on the first. Thus '~a/' will result @@ -1281,7 +1246,6 @@ be overridden by a system or personal initialization file (see Terminal options may be included. E.g. bash$ export GNUTERM="postscript eps color size 5in, 3in" - GNUHELP, if defined, sets the pathname of the HELP file (gnuplot.gih). @@ -1415,7 +1379,6 @@ H is mapped to 0. The example shown starts and wraps at H = 0.3 I = {0,1} splot '++' using 1:2:(abs(E0(x+I*y))):(arg(E0(x+I*y))) with pm3d -  File: gnuplot.info, Node: Constants, Next: Functions, Prev: Complex_values, Up: Expressions @@ -1450,7 +1413,6 @@ single and double quotes is important. See 'quotes'. "Line 1\nLine 2" # string constant (\n is expanded to newline) '123\na\456' # string constant (\ and n are ordinary characters) -  File: gnuplot.info, Node: Functions, Next: operators, Prev: Constants, Up: Expressions @@ -2220,7 +2182,6 @@ leading and trailing whitespace. This is useful for string comparisons of input data fields that may contain extra whitespace. For example plot FOO using 1:( trim(strcol(3)) eq "A" ? $2 : NaN ) -  File: gnuplot.info, Node: word, Next: words, Prev: trim, Up: Functions @@ -2298,7 +2259,6 @@ File: gnuplot.info, Node: palette, Next: rgbcolor, Prev: hsv2rgb, Up: Functi 'palette(z)' returns the 24 bit RGB representation of the palette color mapped to z given the current extremes of cbrange. -  File: gnuplot.info, Node: rgbcolor, Next: voxel, Prev: palette, Up: Functions @@ -2307,7 +2267,7 @@ File: gnuplot.info, Node: rgbcolor, Next: voxel, Prev: palette, Up: Function 'rgbcolor("name")' returns an integer containing the 32 bit alpha + RGB color value of a named color or a string of the form "0xAARRGGBB" or -"#AARRGGBB". If the string is not recognized as a color description the +"#AARRGGBB". If the string is not recognized as a color description the function returns 0. This can be used to read a color name from a data file or to add an alpha channel to a named color in the upper byte of the returned value. See *note colorspec::. @@ -2590,7 +2550,6 @@ Transactions on Mathematical Software, 17:98-111 (1991). rand({x,y}) for integer 0 < x,y < 2^31-1 sets seed1 to x and seed2 to y. -  File: gnuplot.info, Node: Special_functions_with_complex_arguments, Next: Synchrotron_function, Prev: Random_number_generator, Up: Functions @@ -2706,7 +2665,6 @@ defining a week as starting on Sunday, rather than on Monday. Syntax: time = weekdate_iso( year, week [, day] ) - This function converts from the year, week, day components of a date in ISO 8601 "week date" format to the calendar date as a time in seconds since the epoch date 1 Jan 1970. Note that the nominal year in the week @@ -2726,13 +2684,11 @@ convention. set xtics time format "%b\n%Y" plot FILE using (calendar_date(strcol(1))) : 2 title columnhead - -- WEEKDATE_CDC -- Syntax: time = weekdate_cdc( year, week [, day] ) - This function converts from the year, week, day components of a date in the CDC/MMWR "epi week" format to the calendar date as a time in seconds since the epoch date 1 Jan 1970. The CDC week date convention @@ -2785,7 +2741,6 @@ datafile using'. Example: set datafile columnheader plot for [i=2:4] DATA using 1:i title columnhead(i) - -- STRINGCOLUMN -- The 'stringcolumn(x)' function may be used only in the 'using' @@ -2812,7 +2767,6 @@ valid() would be called. See 'plot datafile using', 'missing'. # prior expectation to the bin total rather than ignoring it. plot DATA using 1 : (valid(2) ? $2 : prior) smooth unique -  File: gnuplot.info, Node: value, Next: Counting_and_extracting_words, Prev: using_specifier_functions, Up: Functions @@ -2875,7 +2829,6 @@ Otherwise the full sequence of characters in "sep" must be matched. t2 = split( "A B C D", " ") t3 = split( "A;B;C;D", ";") - However the command t4 = split( "A;B; C;D", "; " ) @@ -2897,13 +2850,11 @@ string to create an array. Example: print join(A,";") A;B;;;E - 'trim(" padded string ")' returns the original string stripped of leading and trailing whitespace. This is useful for string comparisons of input data fields that may contain extra whitespace. For example plot FOO using 1:( trim(strcol(3)) eq "A" ? $2 : NaN ) -  File: gnuplot.info, Node: zeta, Prev: Counting_and_extracting_words, Up: Functions @@ -3024,7 +2975,6 @@ There is a single ternary operator: Symbol Example Explanation ?: a?b:c ternary operation - The ternary operator behaves as it does in C. The first argument (a), which must be an integer, is evaluated. If it is true (non-zero), the second argument (b) is evaluated and returned; otherwise the third @@ -3052,7 +3002,6 @@ non-negative: plot 'file' using 1:( $4<0 ? 1/0 : ($2+$3)/2 ) - For an explanation of the 'using' syntax, please see 'plot datafile using'. @@ -3103,7 +3052,6 @@ those currently 'set'. FRAC_X = SCREEN_X * GPVAL_TERM_SCALE / GPVAL_TERM_XSIZE FRAC_Y = SCREEN_Y * GPVAL_TERM_SCALE / GPVAL_TERM_YSIZE - The read-only variable GPVAL_ERRNO is set to a non-zero value if any gnuplot command terminates early due to an error. The most recent error message is stored in the string variable GPVAL_ERRMSG. Both GPVAL_ERRNO @@ -3134,7 +3082,6 @@ itself. User-defined function syntax: ( {,} ... {,} ) = - where is defined in terms of through . This form of function definition is limited to a single line. More complicated multi-line functions can be defined using the function block @@ -3143,7 +3090,6 @@ mechanism (new in this version). See 'function blocks'. User-defined variable syntax: = - Examples: w = 2 q = floor(tan(pi/2 - 0.1)) @@ -3156,11 +3102,9 @@ mechanism (new in this version). See 'function blocks'. len3d(x,y,z) = sqrt(x*x+y*y+z*z) plot f(x) = sin(x*a), a = 0.2, f(x), a = 0.4, f(x) - file = "mydata.inp" file(n) = sprintf("run_%d.dat",n) - The final two examples illustrate a user-defined string variable and a user-defined string function. @@ -3172,7 +3116,6 @@ setting: NaN = GPVAL_NaN pi = GPVAL_pi - Other variables may be defined under various gnuplot operations like mousing in interactive terminals or fitting; see 'gnuplot-defined variables' for details. @@ -3183,7 +3126,6 @@ expression. For example if (exists("a")) print "a is defined" if (!exists("b")) print "b is not defined" - Valid names are the same as in most programming languages: they must begin with a letter, but subsequent characters may be letters, digits, or "_". @@ -3194,7 +3136,6 @@ variable with the prefix 'GPFUN_'. Example: set label GPFUN_sinc at graph .05,.95 - See *note functions::, *note functions::, 'gnuplot-defined variables', *note macros::, *note value::. @@ -3224,7 +3165,6 @@ expression |A|. array B[6] = [ 1, 2.0, A[3], "four", , B[2]**3 ] array C = split("A B C D E F") - do for [i=1:6] { print A[i], B[i] } 1 1 2.0 2.0 @@ -3233,7 +3173,6 @@ expression |A|. 8.0 8.0 - Note: Arrays and variables share the same namespace. For example, assignment of a string to a variable named FOO will destroy any previously created array with name FOO. @@ -3248,7 +3187,6 @@ value of real(A[i]) and column 3 holds the value of imag(A[i]). do for [i=1:200] { A[i] = sin(i * pi/100.) } plot A title "sin(x) in centiradians" - When plotting the imaginary component of complex array values, it may be referenced either as imag(A[$1]) or as $3. These two commands are equivalent @@ -3256,7 +3194,6 @@ equivalent plot A using (real(A[$1])) : (imag(A[$1])) plot A using 2:3 - * Menu: * Array_functions:: @@ -3274,7 +3211,6 @@ acting on two equal-sized numerical arrays could be defined: dot(A,B) = (|A| != |B|) ? NaN : sum [i=1:|A|] A[i] * B[i] - Built-in functions that return an array include the slice operation array[min:max] and the index retrieval function index(Array,value). @@ -3287,7 +3223,6 @@ array[min:max] and the index retrieval function index(Array,value). print index( T, "D" ) 4 - Note that T and U in this example are now arrays, whether or not they had been previously declared. @@ -3312,7 +3247,6 @@ value. A return of 0 indicates that no match was found. print index( A, "D4"[2:2] ) 3 -  File: gnuplot.info, Node: Fonts, Next: Glossary, Prev: Expressions, Up: Gnuplot @@ -3355,7 +3289,6 @@ following will probably all work: set term pdfcairo font "Times,12" set term pdfcairo font "Times-New-Roman,12" -  File: gnuplot.info, Node: gd_(png, Next: postscript__(also_encapsulated_postscript_*.eps), Prev: cairo_(pdfcairo, Up: Fonts @@ -3368,7 +3301,6 @@ the libgd library. At a minimum it provides five basic fonts named rotated. Use one of these keywords instead of the 'font' keyword. E.g. set term png tiny - On many systems libgd can also use generic font handling provided by the fontconfig tools (see 'fontconfig'). On most systems without fontconfig, libgd provides access to Adobe fonts (*.pfa *.pfb) and to @@ -3389,7 +3321,6 @@ commands are equivalent To request a default font size at the same time: set term png font "arial,11" - If no specific font is requested in the "set term" command, gnuplot checks the environmental variable GNUPLOT_DEFAULT_GDFONT. @@ -3487,7 +3418,6 @@ plot command. Example: stats $Mydata using 1:3 plot $Mydata using 1:3 with points, $Mydata using 1:2 with impulses - Data block names must begin with a $ character, which distinguishes them from other types of persistent variables. The end-of-data delimiter (EOD in the example) may be any sequence of alphanumeric @@ -3522,17 +3452,14 @@ features: } unset multiplot - Iteration is controlled by an iteration specifier with syntax for [ in "string of N elements"] - or for [ = : { : }] - In the first case is a string variable that successively evaluates to single-word substrings 1 to N of the string in the iteration specifier. In the second case , , and @@ -3549,7 +3476,6 @@ end of the iteration. For example, the following commands will print 1 do for [i=1:10] { print i; i=10; } print i -  File: gnuplot.info, Node: linetypes, Next: layers, Prev: iteration, Up: Gnuplot @@ -3583,7 +3509,6 @@ giving explicit line properties in the plot command. plot "foo", "bar" # plot two files using linetypes 1, 2 plot sin(x) linetype 4 # use linetype color 4 - In general, colors can be specified using named colors, rgb (red, green, blue) components, hsv (hue, saturation, value) components, or a coordinate along the current pm3d palette. The keyword 'linecolor' may @@ -3597,7 +3522,6 @@ be abbreviated to 'lc'. # in the current cbrange of the palette plot sin(x) lc palette frac 0.3 # fractional value along the palette - See *note colorspec::, *note colornames::, 'hsv', *note palette::, *note cbrange::. See also *note monochrome::. @@ -3626,7 +3550,6 @@ Many commands allow you to specify a linetype with an explicit color. ... {textcolor | tc} { | {linetype | lt} } ... {fillcolor | fc} { | linetype | linestyle } - where has one of the following forms: rgbcolor "colorname" # e.g. "blue" @@ -3644,7 +3567,6 @@ Many commands allow you to specify a linetype with an explicit color. background or bgnd # background color black - The "" is the linetype number the color of which is used, see 'test'. @@ -3707,7 +3629,6 @@ in this color, and is also recognized as a color name. Examples: # Draw an "invisible" line at y=0, erasing whatever was underneath plot 0 lt background -  File: gnuplot.info, Node: linecolor_variable, Next: palette_, Prev: background_color, Up: colorspec @@ -3726,7 +3647,6 @@ variable'. # Use the third column of data to assign colors to individual points plot 'data' using 1:2:3 with points lc variable - # A single data file may contain multiple sets of data, separated by two # blank lines. Each data set is assigned as index value (see *note index::) # that can be retrieved via the `using` specifier `column(-2)`. @@ -3734,7 +3654,6 @@ variable'. # draw each data set in a different line color. plot 'data' using 1:2:(column(-2)) with lines lc variable -  File: gnuplot.info, Node: palette_, Next: rgbcolor_variable, Prev: linecolor_variable, Up: colorspec @@ -3747,7 +3666,6 @@ Syntax ... {lc|fc|tc} palette cb ... fc palette - The palette defines a range of colors with gray values between 0 and 1. 'palette frac ' selects the color with gray value . @@ -3793,7 +3711,6 @@ rgbcolor variable'. rgb(r,g,b) = 65536 * int(r) + 256 * int(g) + int(b) splot "data" using 1:2:3:(rgb($1,$2,$3)) with points lc rgb variable -  File: gnuplot.info, Node: dashtype, Next: linestyles_vs_linetypes, Prev: colorspec, Up: linetypes @@ -3819,12 +3736,10 @@ command. dashtype (s1,e1,s2,e2,s3,e3,s4,e4) # dash pattern specified by 1 to 4 # numerical pairs , - Example: # Two functions using linetype 1 but distinguished by dashtype plot f1(x) with lines lt 1 dt solid, f2(x) with lines lt 1 dt 3 - Some terminals support user-defined dash patterns in addition to whatever set of predefined dash patterns they offer. @@ -3835,7 +3750,6 @@ whatever set of predefined dash patterns they offer. set dashtype 11 (2,4,4,7) # define new dashtype to be called by index plot f(x) dt 11 # plot using our new dashtype - If you specify a dash pattern using a string the program will convert this to a sequence of , pairs. Dot "." becomes (2,5), dash "-" becomes (10,10), underscore "_" becomes (20,10), and each space @@ -3865,7 +3779,6 @@ state. set style line 5 lt rgb "cyan" lw 3 pt 6 plot sin(x) with linespoints ls 5 # user-defined line style 5 -  File: gnuplot.info, Node: special_linetypes, Prev: linestyles_vs_linetypes, Up: linetypes @@ -3960,7 +3873,6 @@ Syntax: bind "" reset bind - The 'bind' allows defining or redefining a hotkey, i.e. a sequence of gnuplot commands which will be executed when a certain key or key sequence is pressed while the driver's window has the input focus. Note @@ -3995,55 +3907,45 @@ command. bind Home "set view 60,30; replot" bind all Home 'print "This is window ",MOUSE_KEY_WINDOW' - - show bindings: bind "ctrl-a" # shows the binding for ctrl-a bind # shows all bindings show bind # show all bindings - - remove bindings: bind "ctrl-alt-a" "" # removes binding for ctrl-alt-a (note that builtins cannot be removed) reset bind # installs default (builtin) bindings - - bind a key to toggle something: v=0 bind "ctrl-r" "v=v+1;if(v%2)set term x11 noraise; else set term x11 raise" - Modifiers (ctrl / alt) are case insensitive, keys not: ctrl-alt-a == CtRl-alT-a ctrl-alt-a != ctrl-alt-A - List of modifiers (alt == meta): ctrl, alt, shift (only valid for Button1 Button2 Button3) - List of supported special keys: "BackSpace", "Tab", "Linefeed", "Clear", "Return", "Pause", "Scroll_Lock", "Sys_Req", "Escape", "Delete", "Home", "Left", "Up", "Right", "Down", "PageUp", "PageDown", "End", "Begin", - "KP_Space", "KP_Tab", "KP_Enter", "KP_F1", "KP_F2", "KP_F3", "KP_F4", "KP_Home", "KP_Left", "KP_Up", "KP_Right", "KP_Down", "KP_PageUp", "KP_PageDown", "KP_End", "KP_Begin", "KP_Insert", "KP_Delete", "KP_Equal", "KP_Multiply", "KP_Add", "KP_Separator", "KP_Subtract", "KP_Decimal", "KP_Divide", - "KP_1" - "KP_9", "F1" - "F12" - The following are window events rather than actual keys "Button1" "Button2" "Button3" "Close" - See also help for 'mouse'. * Menu: @@ -4084,7 +3986,6 @@ test for any one of these variables being defined. if (exists("MOUSE_BUTTON")) call 'something_else'; \ else print "No mouse click." - It is also possible to track keystrokes in the plot window using the mousing code. @@ -4092,7 +3993,6 @@ mousing code. pause mouse keypress print "Keystroke ", MOUSE_KEY, " at ", MOUSE_X, " ", MOUSE_Y - When 'pause mouse keypress' is terminated by a keypress, then MOUSE_KEY will contain the ascii character value of the key that was pressed. MOUSE_CHAR will contain the character itself as a string @@ -4118,14 +4018,12 @@ For example if you issue the command gnuplot -persist -e 'plot sinh(x)' - gnuplot will open a display window, draw the plot into it, and then exit, leaving the display window containing the plot on the screen. You can also specify 'persist' or 'nopersist' when you set a new terminal. set term qt persist size 700,500 - Depending on the terminal type, some mousing operations may still be possible in the persistent window. However operations like zoom/unzoom that require redrawing the plot are not possible because the main @@ -4219,7 +4117,6 @@ of gnuplot to provide the same function *note uigamma:: as version 6. import Q(a,x) from "uigamma_plugin" uigamma(a,x) = ((x<1 || x plot FOO watch mouse - set style watchpoints nolabels set style watchpoints label - unset style watchpoints # return to default style - show watchpoints # summarizes all watches from previous plot command - A watchpoint is a target value for the x, y, or z coordinate or for a function f(x,y). Each watchpoint is attached to a single plot within a 'plot' command. Watchpoints are tracked only for styles 'with lines' @@ -4761,7 +4630,6 @@ point is then found by linear interpolation or by iterative bisection. More than one watchpoint per plot component may be specified. Example: plot DATA using 1:2 smooth cnormal watch y=0.25 watch y=0.5 watch y=0.75 - Watchpoint hits for each target in the previous plot command are stored in named arrays WATCH_n. You can also display a summary of all watchpoint hits from the previous plot command; see *note watchpoints::. @@ -4775,7 +4643,6 @@ watchpoint hits from the previous plot command; see *note watchpoints::. Watch 3 target y = 0.75 (1 hits) hit 1 x 67.8 y 0.75 - Smoothing: Line segments are checked as they are drawn. For unsmoothed data plots this means a hit found by interpolation will lie exactly on a line segment connecting two data points. If a data plot is @@ -4812,7 +4679,6 @@ style watchpoint' and 'set style textbox' set style textbox 1 lw 0.5 opaque plot for [i=1:N] "file.dat" using 1:(column(i)) watch mouse -  File: gnuplot.info, Node: watch_labels, Prev: watch_mouse, Up: Watchpoints_ @@ -4851,7 +4717,6 @@ plot, you must specify the plot style for each component. plot 'data' with boxes, sin(x) with lines - Each plot style has its own expected set of data entries in a data file. For example, by default the 'lines' style expects either a single column of y values (with implicit x ordering) or a pair of columns with @@ -4924,7 +4789,6 @@ delta_y. See *note vectors::. 4 columns: x y length angle - The keywords 'with arrows' may be followed by inline arrow style properties, a reference to a predefined arrow style, or 'arrowstyle variable' to load the index of the desired arrow style for each arrow @@ -4959,7 +4823,6 @@ corresponding to style previously defined using 'set style arrow'. # determining which of the two previous defined styles to use plot DATA using 1:2:3:4:5 with arrows arrowstyle variable -  File: gnuplot.info, Node: Bee_swarm_plots, Next: boxerrorbars, Prev: arrows, Up: Plotting_styles @@ -4977,7 +4840,6 @@ command but different jitter settings. set jitter plot $data using 1:2:1 with points lc variable -  File: gnuplot.info, Node: boxerrorbars, Next: boxes, Prev: Bee_swarm_plots, Up: Plotting_styles @@ -4994,7 +4856,6 @@ information (see 'linecolor' and 'rgbcolor variable'). 4 columns: x y ydelta xdelta (xdelta <= 0 means use boxwidth) 5 columns: x y ylow yhigh xdelta (xdelta <= 0 means use boxwidth) - The boxwidth will come from the fourth column if the y errors are given as "ydelta" or from the fifth column if they are in the form of "ylow yhigh". If xdelta is zero or negative, the width of the box is @@ -5050,7 +4911,6 @@ color. See 'rgbcolor variable'. 2 columns: x y 3 columns: x y x_width - The width of the box is obtained in one of three ways. If the input data has a third column, this will be used to set the box width. Otherwise if a width has been set using the *note boxwidth:: command, @@ -5071,14 +4931,12 @@ space separating them (bargraph): set style fill solid 1.0 plot 'file.dat' with boxes - To plot a sine and a cosine curve in pattern-filled boxes style with explicit fill color: set style fill pattern plot sin(x) with boxes fc 'blue', cos(x) with boxes fc 'gold' - The sin plot will use pattern 0; the cos plot will use pattern 1. Any additional plots would cycle through the patterns supported by the terminal driver. @@ -5097,7 +4955,6 @@ fill color. 4 columns: x y z [x_width or color] 5 columns: x y z x_width color - The last column is used as a color only if the splot command specifies a variable color mode. Examples @@ -5105,7 +4962,6 @@ specifies a variable color mode. Examples splot 'rgb_boxes.dat' using 1:2:3:4 fc rgb variable splot 'category_boxes.dat' using 1:2:3:4:5 lc variable - In the first example all boxes are blue and have the width previously set by *note boxwidth::. In the second example the box width is still taken from *note boxwidth:: because the 4th column is interpreted as a @@ -5149,7 +5005,6 @@ it in a third field of the 'using' specifier in the plot command. 3 columns: x-position y-value boxwidth 4 columns: first-x-position y-value boxwidth category - The horizontal position of a boxplot is usually given as a constant value in the first field (x-position) of the 'using' specifier in the plot command. You can place an identifying label at this position under @@ -5176,7 +5031,6 @@ plot with layout similar to the one in the boxplot example figure. boxwidth = 0.5 plot 'mixeddata' using (start_x):2:(boxwidth):1 with boxplot - By default a single boxplot is produced from all y values found in the column specified by the second field of the using specification. If a fourth field is given in the 'using' specification the content of that @@ -5223,7 +5077,6 @@ used to provide variable (per-datapoint) color information (see 4 columns: x y xdelta ydelta 6 columns: x y xlow xhigh ylow yhigh - The box width and height are determined from the x and y errors in the same way as they are for the *note xyerrorbars:: style--either from xlow to xhigh and from ylow to yhigh, or from x-xdelta to x+xdelta and @@ -5256,7 +5109,6 @@ line will be unchanged if the low and high prices are interchanged. financial data: date open low high close whisker plot: x box_min whisker_min whisker_high box_high - The width of the rectangle can be controlled by the *note boxwidth:: command. For backwards compatibility with earlier gnuplot versions, when the boxwidth parameter has not been set then the width of the @@ -5297,11 +5149,9 @@ at the median value. plot 'stat.dat' using 1:3:2:6:5 with candlesticks title 'Quartiles', \ '' using 1:4:4:4:4 with candlesticks lt -1 notitle - # Plot with crossbars on the whiskers, crossbars are 50% of full width plot 'stat.dat' using 1:3:2:6:5 with candlesticks whiskerbars 0.5 - See *note boxwidth::, *note errorbars::, 'set style fill', and *note boxplot::. @@ -5328,7 +5178,6 @@ possible. For 2D plots these include using x:y:radius:arc_begin:arc_end using x:y:radius:arc_begin:arc_end:color - By default a full circle will be drawn. The result is similar to using a 'points' plot with variable size points and pointtype 7, except that the circles scale with the x axis range. It is possible to instead @@ -5345,7 +5194,6 @@ variable color term such as 'lc variable' or 'fillcolor rgb variable'. splot DATA using x:y:z:radius:color - where the variable color column is optional. Examples: @@ -5355,11 +5203,9 @@ variable color term such as 'lc variable' or 'fillcolor rgb variable'. plot 'data' using 1:2:(sqrt($3)) with circles, \ 'data' using 1:2 with linespoints - # draws Pac-men instead of circles plot 'data' using 1:2:(10):(40):(320) with circles - # draw a pie chart with inline data set xrange [-15:15] set style fill transparent solid 0.9 noborder @@ -5371,7 +5217,6 @@ variable color term such as 'lc variable' or 'fillcolor rgb variable'. 0 0 5 230 360 5 e -  File: gnuplot.info, Node: contourfill, Next: dots, Prev: circles, Up: Plotting_styles @@ -5381,7 +5226,6 @@ File: gnuplot.info, Node: contourfill, Next: dots, Prev: circles, Up: Plotti Syntax: splot f(x,y) with contourfill {at base} {fillstyle diff --git a/lisp/org-ref/contrib.el b/lisp/org-ref/contrib.el index f726f755..f1dcec00 100644 --- a/lisp/org-ref/contrib.el +++ b/lisp/org-ref/contrib.el @@ -19,7 +19,7 @@ (defun org-ref-get-bibtex-key-under-cursor--display () "Return key under the cursor in `org-mode'. If not on a key, but on a cite, prompt for key." - (if-let ((key (get-text-property (point) 'cite-key))) + (if-let* ((key (get-text-property (point) 'cite-key))) ;; Point is on a key, so we get it directly key ;; point is not on a key, but may still be on a cite link diff --git a/lisp/org-ref/doi-utils.el b/lisp/org-ref/doi-utils.el index ddde49ab..8cd8de44 100644 --- a/lisp/org-ref/doi-utils.el +++ b/lisp/org-ref/doi-utils.el @@ -1,4 +1,4 @@ -;;; doi-utils.el --- DOI utilities for making bibtex entries +;;; doi-utils.el --- DOI utilities for making bibtex entries -*- lexical-binding: t; -*- ;; Copyright (C) 2015-2021 John Kitchin @@ -41,15 +41,14 @@ (declare-function bibtex-completion-edit-notes "bibtex-completion") (declare-function org-bibtex-yank "org-bibtex") (declare-function org-ref-possible-bibfiles "org-ref-core") +(declare-function org-ref-normalize-bibtex-completion-bibliography "org-ref-utils") -(declare-function f-ext? "f") -(declare-function f-entries "f") -(declare-function s-match "s") +(declare-function org-ref--file-ext-p "org-ref-utils") +(declare-function org-ref--directory-files "org-ref-utils") (eval-when-compile (require 'cl-lib)) (require 'bibtex) -(require 'dash) (require 'json) (require 'org) ; org-add-link-type @@ -59,7 +58,7 @@ (require 'url-http) (require 'url-handlers) (require 'org-ref-utils) -(require 'hydra) +(require 'transient) ;;* Customization (defgroup doi-utils nil @@ -120,6 +119,35 @@ If nil use `doi-utils-get-bibtex-entry-pdf' synchronously." :group 'doi-utils) +(defun doi-utils-pdf-filename-from-key () + "Generate PDF filename from bibtex entry key. +This is the default function for `doi-utils-pdf-filename-function'. +Returns the bibtex entry key as the filename (without extension or path)." + (cdr (assoc "=key=" (bibtex-parse-entry)))) + + +(defcustom doi-utils-pdf-filename-function + 'doi-utils-pdf-filename-from-key + "Function to generate PDF filename from bibtex entry. +The function is called with no arguments while point is in the bibtex +entry, and should return a string to use as the PDF filename (without +the .pdf extension or directory path). + +The directory path is determined separately by `bibtex-completion-library-path'. + +The default function uses the bibtex entry key as the filename. + +Example: To use the title field as the filename: + (setq doi-utils-pdf-filename-function + (lambda () (bibtex-autokey-get-field \"title\"))) + +Note: The function is responsible for ensuring the returned filename +is valid for the filesystem. Special characters in fields like title +may cause issues on some systems." + :type 'function + :group 'doi-utils) + + ;;* Getting pdf files from a DOI ;; The idea here is simple. When you visit http://dx.doi.org/doi or @@ -308,7 +336,7 @@ https://onlinelibrary.wiley.com/doi/pdfdirect/10.1002/anie.201310461?download=tr (setq p2 (replace-regexp-in-string "^http\\(s?\\)://scitation.aip.org/" "" *doi-utils-redirect*)) (setq s (split-string p2 "/")) - (setq p1 (mapconcat 'identity (-remove-at-indices '(0 6) s) "/")) + (setq p1 (mapconcat 'identity (org-ref--remove-at-indices '(0 6) s) "/")) (setq p3 (concat "/" (nth 0 s) (nth 1 s) "/" (nth 2 s) "/" (nth 3 s))) (format "http://scitation.aip.org/deliver/fulltext/%s.pdf?itemId=/%s&mimeType=pdf&containerItemId=%s" p1 p2 p3)))) @@ -334,7 +362,7 @@ https://onlinelibrary.wiley.com/doi/pdfdirect/10.1002/anie.201310461?download=tr (defun ecs-pdf-url (*doi-utils-redirect*) "Get url to the pdf from *DOI-UTILS-REDIRECT*." (when (string-match "^http\\(s?\\)://jes.ecsdl.org" *doi-utils-redirect*) - (replace-regexp-in-string "\.abstract$" ".full.pdf" *doi-utils-redirect*))) + (replace-regexp-in-string "\\.abstract$" ".full.pdf" *doi-utils-redirect*))) ;; http://ecst.ecsdl.org/content/25/2/2769 ;; http://ecst.ecsdl.org/content/25/2/2769.full.pdf @@ -762,7 +790,7 @@ too. " (doi (replace-regexp-in-string "https?://\\(dx.\\)?.doi.org/" "" (bibtex-autokey-get-field "doi"))) - (key (cdr (assoc "=key=" (bibtex-parse-entry)))) + (base-name (funcall doi-utils-pdf-filename-function)) (pdf-url) (pdf-file)) @@ -774,7 +802,7 @@ too. " (car bibtex-completion-library-path)) (t (completing-read "Dir: " bibtex-completion-library-path))) - key ".pdf")) + base-name ".pdf")) (unless doi (error "No DOI found to get a pdf for")) @@ -834,7 +862,7 @@ checked." (doi (replace-regexp-in-string "https?://\\(dx.\\)?.doi.org/" "" (bibtex-autokey-get-field "doi"))) - (key (cdr (assoc "=key=" (bibtex-parse-entry)))) + (base-name (funcall doi-utils-pdf-filename-function)) (pdf-url) (pdf-file)) @@ -846,7 +874,7 @@ checked." (car bibtex-completion-library-path)) (t (completing-read "Dir: " bibtex-completion-library-path))) - key ".pdf")) + base-name ".pdf")) ;; now get file if needed. (unless (file-exists-p pdf-file) (cond @@ -900,7 +928,7 @@ every field.") (defun doi-utils-get-json-metadata (doi) "Try to get json metadata for DOI. Open the DOI in a browser if we do not get it." - (if-let ((data (cdr (assoc doi doi-utils-cache)))) + (if-let* ((data (cdr (assoc doi doi-utils-cache)))) ;; We have the data already, so we return it. data (let ((url-request-method "GET") @@ -1055,7 +1083,7 @@ MATCHING-TYPES." fields) (concat ,@(doi-utils-concat-prepare - (-flatten + (org-ref--flatten-list (list (concat "@" (symbol-name name) "{,\n") ;; there seems to be some bug with mapcan, ;; so we fall back to flatten @@ -1096,7 +1124,7 @@ MATCHING-TYPES." (let* ((results (funcall doi-utils-metadata-function doi)) (type (plist-get results :type))) ;; (format "%s" results) ; json-data - (or (-some (lambda (g) (funcall g type results)) doi-utils-bibtex-type-generators) + (or (cl-some (lambda (g) (funcall g type results)) doi-utils-bibtex-type-generators) (message "%s not supported yet\n%S." type results)))) ;; That is just the string for the entry. To be useful, we need a function that @@ -1171,31 +1199,31 @@ Argument BIBFILE the bibliography to use." ;; DOI raw ;; Ex: 10.1109/MALWARE.2014.6999410 ((and (stringp the-active-region) - (s-match (concat "^" doi-regexp) the-active-region)) + (org-ref--string-match (concat "^" doi-regexp) the-active-region)) the-active-region) ;; DOI url ;; Ex: https://doi.org/10.1109/MALWARE.2014.6999410 ((and (stringp the-active-region) - (s-match (concat doi-url-prefix-regexp doi-regexp) the-active-region)) + (org-ref--string-match (concat doi-url-prefix-regexp doi-regexp) the-active-region)) (replace-regexp-in-string doi-url-prefix-regexp "" the-active-region)) ;; DOI url as customized ((and (stringp the-active-region) - (s-match (regexp-quote doi-utils-dx-doi-org-url) the-active-region)) + (org-ref--string-match (regexp-quote doi-utils-dx-doi-org-url) the-active-region)) (replace-regexp-in-string (regexp-quote doi-utils-dx-doi-org-url) "" the-active-region)) ;; Check if DOI can be found in the current kill ;; DOI raw ;; Ex: 10.1109/MALWARE.2014.6999410 ((and (stringp the-current-kill) - (s-match (concat "^" doi-regexp) the-current-kill)) + (org-ref--string-match (concat "^" doi-regexp) the-current-kill)) the-current-kill) ;; DOI url ;; Ex: https://doi.org/10.1109/MALWARE.2014.6999410 ((and (stringp the-current-kill) - (s-match (concat doi-url-prefix-regexp doi-regexp) the-current-kill)) + (org-ref--string-match (concat doi-url-prefix-regexp doi-regexp) the-current-kill)) (replace-regexp-in-string doi-url-prefix-regexp "" the-current-kill)) ;; DOI url as customized ((and (stringp the-current-kill) - (s-match (regexp-quote doi-utils-dx-doi-org-url) the-current-kill)) + (org-ref--string-match (regexp-quote doi-utils-dx-doi-org-url) the-current-kill)) (replace-regexp-in-string (regexp-quote doi-utils-dx-doi-org-url) "" the-current-kill)) ;; otherwise, return nil (t @@ -1251,7 +1279,7 @@ Optional argument NODELIM see `bibtex-make-field'." (defun plist-get-keys (plist) "Return keys in a PLIST." - (-slice plist 0 nil 2)) + (cl-loop for (key _value) on plist by #'cddr collect key)) ;;;###autoload @@ -1415,9 +1443,7 @@ May be empty if none are found." (defun doi-utils-open-bibtex (doi) "Search through variable `bibtex-completion-bibliography' for DOI." (interactive "sDOI: ") - (cl-loop for f in (if (listp bibtex-completion-bibliography) - bibtex-completion-bibliography - (list bibtex-completion-bibliography)) + (cl-loop for f in (org-ref-normalize-bibtex-completion-bibliography) when (progn (find-file f) (when (search-forward doi (point-max) t) (bibtex-beginning-of-entry))) @@ -1453,23 +1479,40 @@ May be empty if none are found." (declare-function org-element-property "org-element") -(defhydra doi-link-follow (:color blue :hint nil) - "DOI actions: -" - ("o" (doi-utils-open (org-element-property :path (org-element-context))) "open") - ("w" (doi-utils-wos (org-element-property :path (org-element-context))) "wos") - ("c" (doi-utils-wos-citing (org-element-property :path (org-element-context))) "wos citing articles") - ("r" (doi-utils-wos-related (org-element-property :path (org-element-context))) "wos related articles" ) - ("a" (doi-utils-ads (org-element-property :path (org-element-context))) "ads") - ("s" (doi-utils-google-scholar (org-element-property :path (org-element-context))) "Google Scholar") - ("f" (doi-utils-crossref (org-element-property :path (org-element-context))) "CrossRef") - ("p" (doi-utils-pubmed (org-element-property :path (org-element-context))) "Pubmed") - ("b" (doi-utils-open-bibtex (org-element-property :path (org-element-context))) "open in bibtex") - ("g" (doi-utils-add-bibtex-entry-from-doi (org-element-property :path (org-element-context))) "get bibtex entry")) +(defun doi-utils--context-doi () + (org-element-property :path (org-element-context))) + +(transient-define-prefix doi-link-follow-menu () + "DOI actions." + [["Actions" + ("o" "open" (lambda () (interactive) + (doi-utils-open (doi-utils--context-doi)))) + ("w" "wos" (lambda () (interactive) + (doi-utils-wos (doi-utils--context-doi)))) + ("c" "wos citing articles" (lambda () (interactive) + (doi-utils-wos-citing (doi-utils--context-doi)))) + ("r" "wos related articles" (lambda () (interactive) + (doi-utils-wos-related (doi-utils--context-doi)))) + ("a" "ads" (lambda () (interactive) + (doi-utils-ads (doi-utils--context-doi)))) + ("s" "Google Scholar" (lambda () (interactive) + (doi-utils-google-scholar (doi-utils--context-doi)))) + ("f" "CrossRef" (lambda () (interactive) + (doi-utils-crossref (doi-utils--context-doi)))) + ("p" "Pubmed" (lambda () (interactive) + (doi-utils-pubmed (doi-utils--context-doi)))) + ("b" "open in bibtex" (lambda () (interactive) + (doi-utils-open-bibtex (doi-utils--context-doi)))) + ("g" "get bibtex entry" (lambda () (interactive) + (doi-utils-add-bibtex-entry-from-doi (doi-utils--context-doi)))) + ("q" "quit" transient-quit-one)]]) + +(define-obsolete-function-alias 'doi-link-follow/body + #'doi-link-follow-menu "3.1") (org-link-set-parameters "doi" - :follow (lambda (_) (doi-link-follow/body)) + :follow (lambda (_) (doi-link-follow-menu)) :export (lambda (doi desc format) (cond ((eq format 'html) @@ -1639,7 +1682,7 @@ Get a list of possible matches. Choose one with completion." nil))) (completing-read "Bibfile: " - (append (f-entries "." (lambda (f) (f-ext? f "bib"))) + (append (org-ref--directory-files "." (lambda (f) (org-ref--file-ext-p f "bib"))) bibtex-completion-bibliography)))) (let* ((json-data (with-temp-buffer (url-insert diff --git a/lisp/org-ref/openalex.el b/lisp/org-ref/openalex.el index 78bcd796..7e7768ff 100644 --- a/lisp/org-ref/openalex.el +++ b/lisp/org-ref/openalex.el @@ -1,4 +1,4 @@ -;;; openalex.el --- Org-ref interface to OpenAlex +;;; openalex.el --- Org-ref interface to OpenAlex -*- lexical-binding: t; -*- ;;; Commentary: ;; This is an elisp interface to OpenAlex (https://docs.openalex.org/) for org-ref. @@ -25,14 +25,13 @@ ;; `user-mail-address' value will be added to the queries if it exists so you ;; will get the polite pool. ;; -;; This library extends the `org-ref-citation-hydra' and adds keys to get to +;; This library extends the `org-ref-citation-menu' and adds keys to get to ;; cited by, references and related documents in OpenAlex. -(require 'dash) -(require 's) (require 'request) (require 'doi-utils) (require 'org-ref-citation-links) +(require 'org-ref-utils) (declare-function org-ref-possible-bibfiles "org-ref-core") (declare-function org-ref-find-bibliography "org-ref-core") @@ -40,6 +39,7 @@ (declare-function bibtex-completion-show-entry "bibtex-completion") (declare-function bibtex-completion-apa-format-reference "bibtex-completion") (declare-function ivy-read "ivy") +(declare-function ivy-more-chars "ivy") (defcustom oa-api-key nil @@ -59,6 +59,22 @@ (json-read))) +(defun oa--params (&rest params) + "Build API request parameters, excluding nil values. +PARAMS should be an alist of (key . value) pairs. +The mailto and api_key parameters are added automatically." + (let ((result (when user-mail-address + `(("mailto" . ,user-mail-address))))) + ;; Only add api_key if it's set and non-empty + (when (and oa-api-key (not (string-empty-p oa-api-key))) + (push `("api_key" . ,oa-api-key) result)) + ;; Add other params, filtering out nil values + (dolist (param params) + (when (cdr param) + (push param result))) + (nreverse result))) + + (defun oa-get (data query &optional iterable) "Get fields from DATA with QUERY. QUERY is a dot notation string. @@ -77,7 +93,7 @@ Assumes data is in plist form." data) ;; query[] means get query then turn iteration on - ((and (s-ends-with-p "[]" current-field) (null iterable)) + ((and (string-suffix-p "[]" current-field) (null iterable)) (setq current-field (substring current-field 0 -2)) (oa-get (plist-get data (intern-soft (concat ":" current-field))) (when fields @@ -86,7 +102,7 @@ Assumes data is in plist form." ;; this means another level of iteration. You already have a collection. we ;; have to iterate over each one I think. - ((and (s-ends-with-p "[]" current-field) iterable) + ((and (string-suffix-p "[]" current-field) iterable) (setq current-field (substring current-field 0 -2)) (cl-loop for item in data collect (oa-get (plist-get item (intern-soft (concat ":" current-field))) @@ -113,17 +129,16 @@ Assumes data is in plist form." (defun oa--query-all-data (endpoint &rest filter) (let* ((page 1) (url (concat "https://api.openalex.org/" endpoint)) - (filter-string (string-join (cl-loop for key in (plist-get-keys filter) collect - (concat (substring (symbol-name key) 1) - ":" - (url-hexify-string - (plist-get filter key)))) - ",")) - (params `(("mailto" . ,user-mail-address) - ("api_key" . ,oa-api-key) - ("filter" . ,filter-string) - ("page" . ,page))) - + (filter-string (string-join + (cl-loop for key in (plist-get-keys filter) collect + (concat (substring (symbol-name key) 1) + ":" + (url-hexify-string + (plist-get filter key)))) + ",")) + (params (oa--params `("filter" . ,filter-string) + `("page" . ,page))) + (req (request url :sync t :parser 'oa--response-parser :params params)) (data (request-response-data req)) (meta (plist-get data :meta)) @@ -133,7 +148,7 @@ Assumes data is in plist form." (results (plist-get data :results))) (cl-loop for i from 2 to pages do (setf (cdr (assoc "page" params)) i) - (setq req (request purl :sync t :parser 'oa--response-parser :params params) + (setq req (request url :sync t :parser 'oa--response-parser :params params) data (request-response-data req) results (append results (plist-get data :results)))) results)) @@ -171,12 +186,10 @@ non-nil, and `oa-api-key' if it is non-nil to the API url." (url-hexify-string (plist-get filter key)))) ",")) - + (req (request url :sync t :parser 'oa--response-parser - :params `(("mailto" . ,user-mail-address) - ("api_key" . ,oa-api-key) - ("filter" . ,filter-string) - ("page" . ,page)))) + :params (oa--params `("filter" . ,filter-string) + `("page" . ,page)))) (data (request-response-data req)) (meta (plist-get data :meta)) (count (plist-get meta :count)) @@ -184,7 +197,7 @@ non-nil, and `oa-api-key' if it is non-nil to the API url." (pages (ceiling (/ (float count) per-page))) (results (plist-get data :results)) (next-page (format "[[elisp:(oa-query \"%s\" %s :page \"%s\")][Next page: %s]]" - endpoint + endpoint (string-join (cl-loop for x in filter collect (if (keywordp x) @@ -194,7 +207,7 @@ non-nil, and `oa-api-key' if it is non-nil to the API url." (+ page 1) (+ page 1))) (buf (generate-new-buffer "*OpenAlex - Query*"))) - + (with-current-buffer buf (erase-buffer) (org-mode) @@ -216,7 +229,7 @@ non-nil, and `oa-api-key' if it is non-nil to the API url." ((string= endpoint "works") (string-join (cl-loop for wrk in results collect - (s-format "*** ${title} + (org-ref--format-template "*** ${title} :PROPERTIES: :HOST: ${primary_location.source.display_name} :YEAR: ${publication_year} @@ -237,8 +250,6 @@ ${get-bibtex} ${abstract} " - (lambda (key data) - (or (cdr (assoc key data)) "")) `(("title" . ,(oa--title wrk)) ("primary_location.source.display_name" . ,(oa-get wrk "primary_location.source.display_name")) ("publication_year" . ,(oa-get wrk "publication_year")) @@ -283,9 +294,7 @@ https://docs.openalex.org/api-entities/works" (t (format "/%s" entity-id))))) (req (request url :sync t :parser 'oa--response-parser - :params `(("mailto" . ,user-mail-address) - ("api_key" . ,oa-api-key) - ("filter" . ,filter)))) + :params (oa--params `("filter" . ,filter)))) (data (request-response-data req))) ;; this is for convenience to inspect data in a browser, e.g. you can click ;; on the url in Emacs and it opens in a browser. @@ -301,9 +310,10 @@ https://docs.openalex.org/api-entities/works" (ivy-more-chars) (let* ((url "https://api.openalex.org/autocomplete/works") (req (request url :sync t :parser 'oa--response-parser - :params `(("mailto" . ,user-mail-address) - ("api_key" . ,oa-api-key) - ("q" . ,query)))) + :params (let ((params `(("q" . ,query)))) + (when user-mail-address + (push `("mailto" . ,user-mail-address) params)) + (nreverse params)))) (data (request-response-data req)) (results (plist-get data :results))) (cl-loop for work in results collect @@ -318,11 +328,11 @@ https://docs.openalex.org/api-entities/works" "Autocomplete works. This doesn't seem as useful as it could be." (interactive) - + (ivy-read "Work: " #'oa--works-candidates :dynamic-collection t :action - '(1 + '(1 ("o" (lambda (candidate) (browse-url (get-text-property 0 'oaid candidate))) "Open in browser")))) @@ -338,7 +348,7 @@ This doesn't seem as useful as it could be." (defun oa--authors (wrk) "Return an author string for WRK. The string is a comma-separated list of links to author pages in OpenAlex." - (s-join ", " (cl-loop for author in (plist-get wrk :authorships) + (string-join (cl-loop for author in (plist-get wrk :authorships) collect (format "[[elisp:(oa--author-org \"%s\")][%s]]" (plist-get @@ -346,7 +356,8 @@ The string is a comma-separated list of links to author pages in OpenAlex." :id) (plist-get (plist-get author :author) - :display_name))))) + :display_name))) + ", ")) (defun oa--title (wrk) @@ -358,7 +369,7 @@ The string is a comma-separated list of links to author pages in OpenAlex." ;; be nice to integrate M-, navigation. (defun oa--elisp-get-bibtex (wrk) "Return a elisp link to get a bibtex entry for WRK if there is a doi." - (if-let ((doi (plist-get wrk :doi))) + (if-let* ((doi (plist-get wrk :doi))) (format "[[elisp:(doi-add-bibtex-entry \"%s\")][Get bibtex entry]]" doi) "")) @@ -389,7 +400,7 @@ The string is a comma-separated list of links to author pages in OpenAlex." WORKS is a list of results from OpenAlex." (cl-loop for wrk in (plist-get works :results) collect - (s-format "** ${title} + (org-ref--format-template "** ${title} :PROPERTIES: :HOST: ${primary_location.source.display_name} :YEAR: ${publication_year} @@ -407,8 +418,6 @@ ${get-bibtex} - ${oa-cited} " - (lambda (key data) - (or (cdr (assoc key data)) "")) `(("title" . ,(oa-get wrk "title")) ("primary_location.source.display_name" . ,(oa-get wrk "primary_location.source.display_name")) ("publication_year" . ,(oa-get wrk "publication_year")) @@ -428,7 +437,7 @@ HEADER is the first thing in the buffer WORKS is usually a list of results from OpenAlex. Argument ENTRIES A list of strings for each org entry." (let ((buf (get-buffer-create bufname))) - + (with-current-buffer buf (erase-buffer) (insert header) @@ -441,7 +450,7 @@ elisp:org-columns elisp:org-columns-quit | cited by | [[elisp:(oa-buffer-sort-cited-by-count t)][low first]] | [[elisp:(oa-buffer-sort-cited-by-count)][high first]] | ") - (insert (s-join "\n" entries)) + (insert (string-join entries "\n")) (org-mode) (goto-char (point-min)) (org-next-visible-heading 1)) @@ -456,21 +465,21 @@ elisp:org-columns elisp:org-columns-quit split entries) (while related-work - (setq split (-split-at 25 related-work) + (setq split (org-ref--split-at 25 related-work) related-work (nth 1 split)) - + ;; split is what we process now (setq entries (append entries (oa--works-entries - (oa--work (format "?filter=openalex:%s" (s-join "|" (nth 0 split)))))))) - + (oa--work (format "?filter=openalex:%s" (string-join (nth 0 split) "|"))))))) + (oa--works-buffer "*OpenAlex - Related works*" (format "* OpenAlex - Related works for %s ([[%s][json]]) %s\n\n" entity-id (plist-get wrk :oa-url) - (s-format ":PROPERTIES: + (org-ref--format-template ":PROPERTIES: :TITLE: ${title} :HOST: ${primary_location.source.display_name} :AUTHOR: ${authors} @@ -481,15 +490,13 @@ elisp:org-columns elisp:org-columns-quit Found ${nentries} results. " - (lambda (key data) - (or (cdr (assoc key data)) "")) - `(("title" . ,(oa-get wrk "title")) - ("primary_location.source.display_name" . ,(oa-get wrk "primary_location.source.display_name")) - ("authors" . ,(oa--authors wrk)) - ("doi" . ,(oa-get wrk "doi")) - ("publication_year" . ,(oa-get wrk "publication_year")) - ("id" . ,(oa-get wrk "id")) - ("nentries" . ,(length entries))))) + `(("title" . ,(oa-get wrk "title")) + ("primary_location.source.display_name" . ,(oa-get wrk "primary_location.source.display_name")) + ("authors" . ,(oa--authors wrk)) + ("doi" . ,(oa-get wrk "doi")) + ("publication_year" . ,(oa-get wrk "publication_year")) + ("id" . ,(oa-get wrk "id")) + ("nentries" . ,(length entries))))) entries))) @@ -500,20 +507,20 @@ Found ${nentries} results. split (entries '())) (while referenced-work - (setq split (-split-at 25 referenced-work) + (setq split (org-ref--split-at 25 referenced-work) referenced-work (nth 1 split)) ;; split is what we process now (setq entries (append entries (oa--works-entries - (oa--work (format "?filter=openalex:%s" - (s-join "|" (nth 0 split)))))))) + (oa--work (format "?filter=openalex:%s" + (string-join (nth 0 split) "|"))))))) (oa--works-buffer "*OpenAlex - References*" (format "* OpenAlex - References from %s ([[%s][json]]) %s\n\n" entity-id (plist-get wrk :oa-url) - (s-format ":PROPERTIES: + (org-ref--format-template ":PROPERTIES: :TITLE: ${title} :HOST: ${primary_location.source.display_name} :AUTHOR: ${authors} @@ -524,15 +531,13 @@ Found ${nentries} results. Found ${nentries} results. " - (lambda (key data) - (or (cdr (assoc key data)) "")) - `(("title" . ,(oa-get wrk "title")) - ("primary_location.source.display_name" . ,(oa-get wrk "primary_location.source.display_name")) - ("authors" . ,(oa--authors wrk)) - ("doi" . ,(oa-get wrk "doi")) - ("publication_year" . ,(oa-get wrk "publication_year")) - ("id" . ,(oa-get wrk "id")) - ("nentries" . ,(length entries))))) + `(("title" . ,(oa-get wrk "title")) + ("primary_location.source.display_name" . ,(oa-get wrk "primary_location.source.display_name")) + ("authors" . ,(oa--authors wrk)) + ("doi" . ,(oa-get wrk "doi")) + ("publication_year" . ,(oa-get wrk "publication_year")) + ("id" . ,(oa-get wrk "id")) + ("nentries" . ,(length entries))))) entries))) @@ -546,8 +551,7 @@ Found ${nentries} results. (request url :sync t :parser 'oa--response-parser - :params `(("mailto" . ,user-mail-address) - ("api_key" . ,oa-api-key))))) + :params (oa--params)))) (count (plist-get (plist-get cited-by-works :meta) :count)) (per-page (plist-get (plist-get cited-by-works :meta) :per_page)) (entries '()) @@ -559,19 +563,17 @@ Found ${nentries} results. (request url :sync t :parser 'oa--response-parser - :params `(("mailto" . ,user-mail-address) - ("api_key" . ,oa-api-key) - ("page" . ,page))))) + :params (oa--params `("page" . ,page))))) (setq entries (append entries (oa--works-entries cited-by-works))) (cl-incf page)) - + (oa--works-buffer "*OpenAlex - Cited by*" (format "* OpenAlex - %s Cited by ([[%s][json]]) %s" entity-id url - (s-format ":PROPERTIES: + (org-ref--format-template ":PROPERTIES: :TITLE: ${title} :HOST: ${primary_location.source.display_name} :AUTHOR: ${authors} @@ -583,8 +585,6 @@ Found ${nentries} results. Found ${nentries} results. " - (lambda (key data) - (or (cdr (assoc key data)) "")) `(("title" . ,(oa-get wrk "title")) ("primary_location.source.display_name" . ,(oa-get wrk "primary_location.source.display_name")) ("authors" . ,(oa--authors wrk)) @@ -665,12 +665,15 @@ With prefix arg ASCENDING sort from low to high." (browse-url (plist-get data :id)))) -(defhydra+ org-ref-citation-hydra () - "Add open from action to `org-ref-citation-hydra'." - ("xa" oa-open "Open in OpenAlex" :column "OpenAlex") - ("xr" oa-related-works "Related documents" :column "OpenAlex") - ("xc" oa-cited-by-works "Cited by documents" :column "OpenAlex") - ("xf" oa-referenced-works "References from" :column "OpenAlex")) +(with-eval-after-load 'org-ref-citation-links + (transient-append-suffix 'org-ref-citation-menu "u" + '("xa" "Open in OpenAlex" oa-open)) + (transient-append-suffix 'org-ref-citation-menu "xa" + '("xr" "Related documents" oa-related-works)) + (transient-append-suffix 'org-ref-citation-menu "xr" + '("xc" "Cited by documents" oa-cited-by-works)) + (transient-append-suffix 'org-ref-citation-menu "xc" + '("xf" "References from" oa-referenced-works))) ;; * Author object @@ -681,10 +684,8 @@ FILTER is an optional string to add to the URL." (let* ((url (concat "https://api.openalex.org/authors" entity-id)) (req (request url :sync t :parser 'oa--response-parser - :params `(("mailto" . ,user-mail-address) - ("api_key" . ,oa-api-key) - ("filter" . ,filter)))) - (data (request-response-data req))) + :params (oa--params `("filter" . ,filter)))) + (data (request-response-data req))) ;; this is for convenience to inspect data in a browser. (plist-put data :oa-url url) data)) @@ -692,15 +693,14 @@ FILTER is an optional string to add to the URL." (defun oa--author-entries (works-data url) "Get entries from WORKS-DATA." - (let* ((meta (plist-get works-data :meta)) + (let* ((meta (plist-get works-data :meta)) (per-page (plist-get meta :per_page)) (count (plist-get meta :count)) (pages (/ count per-page)) - (entries '()) - purl) + (entries '())) ;; if there is a remainder we need to get the rest (when (> (mod count per-page) 0) (cl-incf pages)) - + ;; Now we have to loop through the pages (cl-loop for i from 1 to pages do @@ -708,13 +708,11 @@ FILTER is an optional string to add to the URL." (request url :sync t :parser 'oa--response-parser - :params `(("mailto" . ,user-mail-address) - ("api_key" . ,oa-api-key) - ("page" . ,i)))) + :params (oa--params `("page" . ,i)))) entries (append entries (cl-loop for result in (plist-get works-data :results) collect - (s-format "*** ${title} + (org-ref--format-template "*** ${title} :PROPERTIES: :ID: ${id} :DOI: ${ids.doi} @@ -734,9 +732,7 @@ ${get-bibtex} ${abstract} - " (lambda (key data) - (or (cdr (assoc key data)) "")) - `(("title" . ,(oa--title result)) + " `(("title" . ,(oa--title result)) ("id" . ,(oa-get result "id")) ("ids.doi" . ,(oa-get result "ids.doi")) ("publication_year" . ,(oa-get result "publication_year")) @@ -787,9 +783,10 @@ ${abstract} (ivy-more-chars) (let* ((url "https://api.openalex.org/autocomplete/authors") (req (request url :sync t :parser 'oa--response-parser - :params `(("mailto" . ,user-mail-address) - ("api_key" . ,oa-api-key) - ("q" . ,query)))) + :params (let ((params `(("q" . ,query)))) + (when user-mail-address + (push `("mailto" . ,user-mail-address) params)) + (nreverse params)))) (data (request-response-data req)) (results (plist-get data :results))) (cl-loop for author in results collect @@ -801,6 +798,20 @@ ${abstract} 'oaid (plist-get author :id)))))) +(defun oa--format-institution (data) + "Format institution from DATA, handling nil values gracefully. +Extracts the first institution from last_known_institutions array." + (let* ((institutions (plist-get data :last_known_institutions)) + (first-inst (and institutions (listp institutions) (car institutions))) + (name (and first-inst (plist-get first-inst :display_name))) + (country (and first-inst (plist-get first-inst :country_code)))) + (cond + ((and name country) (format "%s, %s" name country)) + (name name) + (country country) + (t "")))) + + (defun oa--counts-by-year (data) "Get citation counts by year and make a graph. DATA is an author from OpenAlex. @@ -858,18 +869,17 @@ plot $counts using 1:3:xtic(2) with boxes lc rgb \"grey\" title \"Citations per (request works-url :sync t :parser 'oa--response-parser - :params `(("mailto" . ,user-mail-address) - ("api_key" . ,oa-api-key)))))) + :params (oa--params))))) (with-current-buffer buf (erase-buffer) - (insert (s-format "* ${display_name} ([[${oa-url}][json]]) + (insert (org-ref--format-template "* ${display_name} ([[${oa-url}][json]]) :PROPERTIES: :OPENALEX: ${id} :ORCID: ${orcid} :SCOPUS: ${ids.scopus} :WORKS_COUNT: ${works_count} :CITED_BY_COUNT: ${cited_by_count} -:INSTITUTION: ${last_known_institution.display_name}, ${last_known_institution.country_code} +:INSTITUTION: ${institution} :END: #+COLUMNS: %25ITEM %YEAR %CITED_BY_COUNT @@ -884,18 +894,16 @@ ${citations-image} | cited by | [[elisp:(oa-buffer-sort-cited-by-count t)][low first]] | [[elisp:(oa-buffer-sort-cited-by-count)][high first]] | " - (lambda (key data) - (or (cdr (assoc key data)) "")) `(("display_name" . ,(oa-get data "display_name")) ("oa-url" . ,(oa-get data "oa-url")) ("id" . ,(oa-get data "id")) ("orcid" . ,(oa-get data "orcid")) ("ids.scopus" . ,(oa-get data "ids.scopus")) ("works_count" . ,(oa-get data "works_count")) - ("last_known_institution.display_name" . ,(oa-get data "last_known_institution.display_name")) - ("last_known_institution.country_code" . ,(oa-get data "last_known_institution.country_code")) + ("cited_by_count" . ,(oa-get data "cited_by_count")) + ("institution" . ,(oa--format-institution data)) ("citations-image" . ,citations-image)))) - (insert (s-join "\n" (oa--author-entries works-data works-url))) + (insert (string-join (oa--author-entries works-data works-url) "\n")) (org-mode) (goto-char (point-min)) @@ -906,7 +914,7 @@ ${citations-image} (defun oa-author () "Get data and act on it for an author." (interactive) - + (ivy-read "Author: " #'oa--author-candidates :dynamic-collection t :action @@ -936,10 +944,8 @@ PAGE is optional, and loads that page of results. Defaults to 1." (req (request url :sync t :parser #'oa--response-parser - :params `(("mailto" . ,user-mail-address) - ("api_key" . ,oa-api-key) - ("page" . ,page) - ("filter" . ,(format "fulltext.search:%s" query))))) + :params (oa--params `("page" . ,page) + `("filter" . ,(format "fulltext.search:%s" query))))) (data (request-response-data req)) (metadata (plist-get data :meta)) (count (plist-get metadata :count)) @@ -952,29 +958,27 @@ PAGE is optional, and loads that page of results. Defaults to 1." (+ page 1) npages)) (buf (get-buffer-create "*OpenAlex Full-text search*"))) - + (with-current-buffer buf (erase-buffer) (org-mode) - (insert (s-format "#+title: Full-text search: ${query} + (insert (org-ref--format-template "#+title: Full-text search: ${query} [[elisp:(oa-fulltext-search \"${query}\" ${page})]]" - 'aget `(("query" . ,query) ("page" . ,page)))) - (insert (s-format + (insert (org-ref--format-template " ${meta.count} results: Page ${meta.page} of ${s1} ${s2} \n\n" - (lambda (key data) - (or (cdr (assoc key data)) "")) - `(("meta.page" . ,(oa-get data "meta.page")) + `(("meta.count" . ,count) + ("meta.page" . ,(oa-get data "meta.page")) ("s1" . ,(format "%s" npages)) ("s2" . ,(format "%s" next-page))))) - + (insert - (cl-loop for result in results concat - (s-format "* ${title} + (cl-loop for result in results concat + (org-ref--format-template "* ${title} :PROPERTIES: :JOURNAL: ${primary_location.source.display_name} :AUTHOR: ${authors} @@ -989,10 +993,7 @@ ${get-bibtex} - ${oa-related} - ${oa-cited} -" (lambda (key data) - (or (cdr (assoc key data)) "")) - -`(("title" . ,(oa--title result)) +" `(("title" . ,(oa--title result)) ("primary_location.source.display_name" . ,(oa-get result "primary_location.source.display_name")) ("authors" . ,(oa--authors result)) ("publication_year" . ,(oa-get result "publication_year")) @@ -1004,7 +1005,7 @@ ${get-bibtex} ("oa-cited" . ,(oa--elisp-get-oa-cited-by result)))))) (insert next-page) - + (goto-char (point-min))) (pop-to-buffer buf))) @@ -1050,23 +1051,21 @@ Recently published papers are probably missing. :dynamic-collection t)) (when (y-or-n-p "Save to file?") (read-file-name "File: ")))) - + (let* ((data (oa--author entity-id)) - (works-url (plist-get data :works_api_url)) + (works-url (plist-get data :works_api_url)) (works-data (request-response-data (request works-url :sync t :parser 'oa--response-parser - :params `(("mailto" . ,user-mail-address) - ("api_key" . ,oa-api-key))))) + :params (oa--params)))) (meta (plist-get works-data :meta)) (count (plist-get meta :count)) (per-page (plist-get meta :per_page)) (pages (/ count per-page)) (results (plist-get works-data :results)) (current-year (string-to-number (format-time-string "%Y" (current-time)))) - (current-authors '()) - purl) + (current-authors '())) ;; Now we need to accumulate the rest of the results from other pages (when (> (mod count per-page) 0) (cl-incf pages)) @@ -1077,11 +1076,9 @@ Recently published papers are probably missing. (request works-url :sync t :parser 'oa--response-parser - :params `(("mailto" . ,user-mail-address) - ("api_key" . ,oa-api-key) - ("page" . ,i)))) + :params (oa--params `("page" . ,i)))) results (append results (plist-get works-data :results)))) - + ;; Now results is a list of your publications. We need to iterate over each ;; one, and accumulate author information (cl-loop for result in results do @@ -1093,30 +1090,30 @@ Recently published papers are probably missing. (name-parts (mapcar #'capitalize (split-string name))) (name-string (concat (car (last name-parts)) ", " (string-join (butlast name-parts) " "))) - - (institutions (plist-get authorship :institutions)) + + (institutions (plist-get authorship :institutions)) (institution (plist-get (car institutions) :display_name))) ;; name, institution, contact info, last-active ;; we won't have contact info from openalex. (push (list name-string institution "" year (plist-get result :publication_date)) current-authors)))))) - + (setq current-authors (sort current-authors (lambda (a b) "Sort first on name, then on year in descending order" (if (string= (nth 0 a) (nth 0 b)) (> (nth 3 a) (nth 3 b)) (string< (car a) (car b)))))) - + ;; now filter for unique authors (setq current-authors (cl-loop for group in (seq-group-by (lambda (x) (car x)) - current-authors) + current-authors) collect (cadr group))) ;; Finally lets fix the year so Excel reads it correctly. I use the publication date - (setq current-authors (cl-loop for row in current-authors + (setq current-authors (cl-loop for row in current-authors collect (list "A:" (nth 0 row) @@ -1132,7 +1129,7 @@ Recently published papers are probably missing. row) "\t") "\n"))) - + (kill-new (mapconcat (lambda (row) (concat (string-join (mapcar (lambda (x) @@ -1152,7 +1149,7 @@ Recently published papers are probably missing. (interactive) (cl-loop for buf in (buffer-list) do - (when (s-starts-with? "*OpenAlex" (buffer-name buf)) + (when (string-prefix-p "*OpenAlex" (buffer-name buf)) (kill-buffer buf)))) @@ -1165,7 +1162,7 @@ Operates on headings with a DOI property." (lambda () (kill-new (org-entry-get (point) "DOI")) (doi-utils-add-bibtex-entry-from-doi - (doi-utils-maybe-doi-from-region-or-current-kill) + (doi-utils-maybe-doi-from-region-or-current-kill) bibfile)) "DOI<>\"\""))) diff --git a/lisp/org-ref/org-ref-arxiv.el b/lisp/org-ref/org-ref-arxiv.el index 035e4b1e..f2c26fe8 100644 --- a/lisp/org-ref/org-ref-arxiv.el +++ b/lisp/org-ref/org-ref-arxiv.el @@ -26,10 +26,7 @@ ;;; Code: (require 'bibtex) -(require 'dash) -(require 'f) (require 'org) -(require 's) (require 'org-ref-utils) (require 'parsebib) @@ -42,6 +39,7 @@ (declare-function parsebib-find-bibtex-dialect "parsebib") (declare-function org-ref-clean-bibtex-entry "org-ref-core") +(declare-function org-ref-normalize-bibtex-completion-bibliography "org-ref-utils") ;; this is a C function (declare-function libxml-parse-xml-region "xml") @@ -85,7 +83,7 @@ (with-current-buffer (url-retrieve-synchronously (format "https://ui.adsabs.harvard.edu/abs/%s/exportcitation" arxiv-bibliographic-code)) (when (re-search-forward - "\\(.*\\(?:\n.*\\)*?\\(?:\n\\s-*\n\\|\\'\\)\\)" + "\\(.*\\(?:\n.*\\)*?\\)\\(?:\n\\s-*\n\\)" nil t) (xml-substitute-special (match-string 1))))) @@ -121,13 +119,13 @@ Returns a formatted BibTeX entry." (match-beginning 0)) (point-max))) (entry (assq 'entry parse-tree)) - (authors (--map (nth 2 (nth 2 it)) - (--filter (and (listp it) (eq (car it) 'author)) entry))) + (authors (mapcar (lambda (it) (nth 2 (nth 2 it))) + (seq-filter (lambda (it) (and (listp it) (eq (car it) 'author))) entry))) (year (format-time-string "%Y" (date-to-time (nth 2 (assq 'published entry))))) (title (nth 2 (assq 'title entry))) (names (arxiv-bibtexify-authors authors)) (category (cdar (nth 1 (assq 'primary_category entry)))) - (abstract (s-trim (nth 2 (assq 'summary entry)))) + (abstract (string-trim (nth 2 (assq 'summary entry)))) (url (nth 2 (assq 'id entry))) (temp-bibtex (format arxiv-entry-format-string "" title names year arxiv-number category abstract url)) (key (with-temp-buffer @@ -145,9 +143,12 @@ Returns a formatted BibTeX entry." (defun arxiv-bibtexify-authors (authors) "Return names in 'SURNAME, FIRST NAME' format from AUTHORS list." - (s-join " and " - (--map (concat (-last-item it) ", " (s-join " " (-remove-last 'stringp it))) - (--map (s-split " +" it) authors)))) + (string-join + (mapcar (lambda (it) + (concat (car (last it)) ", " + (string-join (butlast it) " "))) + (mapcar (lambda (it) (split-string it " +")) authors)) + " and ")) (defun arxiv-maybe-arxiv-id-from-current-kill () @@ -166,22 +167,22 @@ Returns a formatted BibTeX entry." (;; check if current-kill looks like an arxiv ID ;; if so, return it ;; Ex: 1304.4404v2 - (s-match (concat "^" arxiv-id-regexp) the-current-kill) + (org-ref--string-match (concat "^" arxiv-id-regexp) the-current-kill) the-current-kill) (;; check if current-kill looks like an arxiv cite ;; if so, remove the prefix and return ;; Ex: arXiv:1304.4404v2 --> 1304.4404v2 - (s-match (concat arxiv-cite-prefix-regexp arxiv-id-regexp "$") the-current-kill) + (org-ref--string-match (concat arxiv-cite-prefix-regexp arxiv-id-regexp "$") the-current-kill) (replace-regexp-in-string arxiv-cite-prefix-regexp "" the-current-kill)) (;; check if current-kill looks like an arxiv url ;; if so, remove the url prefix and return ;; Ex: https://arxiv.org/abs/1304.4404 --> 1304.4404 - (s-match (concat arxiv-url-prefix-regexp arxiv-id-regexp "$") the-current-kill) + (org-ref--string-match (concat arxiv-url-prefix-regexp arxiv-id-regexp "$") the-current-kill) (replace-regexp-in-string arxiv-url-prefix-regexp "" the-current-kill)) (;; check if current-kill looks like an arxiv PDF url ;; if so, remove the url prefix, the .pdf suffix, and return ;; Ex: https://arxiv.org/pdf/1304.4404.pdf --> 1304.4404 - (s-match (concat arxiv-url-prefix-regexp arxiv-id-regexp "\\.pdf$") the-current-kill) + (org-ref--string-match (concat arxiv-url-prefix-regexp arxiv-id-regexp "\\.pdf$") the-current-kill) (replace-regexp-in-string arxiv-url-prefix-regexp "" (substring the-current-kill 0 (- (length the-current-kill) 4)))) ;; otherwise, return nil (t @@ -199,10 +200,8 @@ Returns a formatted BibTeX entry." ;; now get the bibfile to add it to (completing-read "Bibfile: " - (append (f-entries "." (lambda (f) (f-ext? f "bib"))) - (if (stringp bibtex-completion-bibliography) - (list bibtex-completion-bibliography) - bibtex-completion-bibliography))))) + (append (org-ref--directory-files "." (lambda (f) (org-ref--file-ext-p f "bib"))) + (org-ref-normalize-bibtex-completion-bibliography))))) (save-window-excursion (find-file bibfile) (goto-char (point-max)) @@ -245,8 +244,8 @@ key." ;; now get the bibfile to add it to (completing-read "Bibfile: " - (append (f-entries "." (lambda (f) (f-ext? f "bib"))) - bibtex-completion-bibliography)) + (append (org-ref--directory-files "." (lambda (f) (org-ref--file-ext-p f "bib"))) + (org-ref-normalize-bibtex-completion-bibliography))) (cond ((stringp bibtex-completion-library-path) bibtex-completion-library-path) diff --git a/lisp/org-ref/org-ref-bibliography-links.el b/lisp/org-ref/org-ref-bibliography-links.el index 6deb0b1b..f5c996bf 100644 --- a/lisp/org-ref/org-ref-bibliography-links.el +++ b/lisp/org-ref/org-ref-bibliography-links.el @@ -257,11 +257,11 @@ Returns `org-ref-bst-styles' or sets it and returns it." (setq org-ref-bst-styles (mapcar 'file-name-nondirectory (mapcar 'file-name-sans-extension - (-flatten + (org-ref--flatten-list (mapcar (lambda (path) (setq path (replace-regexp-in-string "!" "" path)) (when (file-directory-p path) - (f-entries path (lambda (f) (f-ext? f "bst"))))) + (org-ref--directory-files path (lambda (f) (org-ref--file-ext-p f "bst"))))) (split-string ;; https://tex.stackexchange.com/questions/431948/get-a-list-of-installed-bibliography-styles-with-kpsewhich?noredirect=1#comment1082436_431948 (shell-command-to-string "kpsewhich -expand-path '$BSTINPUTS'") diff --git a/lisp/org-ref/org-ref-bibtex.el b/lisp/org-ref/org-ref-bibtex.el index 3c666836..b45399c9 100644 --- a/lisp/org-ref/org-ref-bibtex.el +++ b/lisp/org-ref/org-ref-bibtex.el @@ -52,18 +52,16 @@ ;; org-ref-replace-nonascii :: replace nonascii characters in a bibtex ;; entry. Replacements are in `org-ref-nonascii-latex-replacements'. ;; -;; ** hydra menu for bibtex files -;; `org-ref-bibtex-hydra/body' gives a hydra menu to a lot of useful functions. -;; `org-ref-bibtex-new-entry/body' gives a hydra menu to add new bibtex entries. -;; `org-ref-bibtex-file/body' gives a hydra menu of actions for the bibtex file +;; ** Transient menu for bibtex files +;; `org-ref-bibtex-entry-menu' gives access to many useful functions. +;; `org-ref-bibtex-new-entry-menu' presents commands to add new bibtex entries. +;; `org-ref-bibtex-file-menu' collects actions for entire bibtex files. ;; ;;; Code (require 'bibtex) -(require 'dash) -(require 'hydra) +(require 'transient) (require 'message) -(require 's) (require 'doi-utils) (require 'avy) (require 'sgml-mode) @@ -419,7 +417,7 @@ START and END allow you to use this with `bibtex-map-entries'" (lambda (row) (cons (nth 2 row) (nth 0 row))) org-ref-bibtex-journal-abbreviations)) - (journal (s-trim (bibtex-autokey-get-field "journal"))) + (journal (string-trim (bibtex-autokey-get-field "journal"))) (bstring (or (cdr (assoc journal full-names)) (cdr (assoc journal abbrev-names))))) @@ -512,19 +510,18 @@ books." word) ;; these words should not be capitalized, unless they ;; are the first word - ((-contains? org-ref-lower-case-words - (s-downcase word)) - (s-downcase word)) + ((member (downcase word) org-ref-lower-case-words) + (downcase word)) ;; Words that are quoted - ((s-starts-with? "\"" word) - (concat "\"" (s-capitalize (substring word 1)))) + ((string-prefix-p "\"" word) + (concat "\"" (capitalize (substring word 1)))) (t - (s-capitalize word)))) + (capitalize word)))) words)) ;; Check if first word should be capitalized - (when (-contains? org-ref-lower-case-words (car words)) - (setf (car words) (s-capitalize (car words)))) + (when (member (car words) org-ref-lower-case-words) + (setf (car words) (capitalize (car words)))) (setq title (mapconcat 'identity words " ")) @@ -578,11 +575,11 @@ all the title entries in articles." ;; LaTeX or protected words (string-match "\\$\\|{\\|}\\|\\\\" word) word - (s-downcase word))) + (downcase word))) words)) ;; capitalize first word - (setf (car words) (s-capitalize (car words))) + (setf (car words) (capitalize (car words))) ;; join the words (setq title (mapconcat 'identity words " ")) @@ -816,132 +813,137 @@ a directory. Optional PREFIX argument toggles between ;;* Hydra menus ;;** Hydra menu for bibtex entries -;; hydra menu for actions on bibtex entries -(defhydra org-ref-bibtex-hydra (:color blue :hint nil) - "Bibtex actions: -" - ;; Open-like actions - ("p" org-ref-open-bibtex-pdf "PDF" :column "Open") - ("n" org-ref-open-bibtex-notes "Notes" :column "Open") - ("b" org-ref-open-in-browser "URL" :column "Open") - - ;; edit/modify - ("K" (lambda () - (interactive) - (org-ref-set-bibtex-keywords - (read-string "Keywords: " - (bibtex-autokey-get-field "keywords")) - t)) - "Keywords" :column "Edit") - ("a" org-ref-replace-nonascii "Replace nonascii" :column "Edit") - ("s" org-ref-sort-bibtex-entry "Sort fields" :column "Edit") - ("T" org-ref-title-case-article "Title case" :column "Edit") - ("S" org-ref-sentence-case-article "Sentence case" :column "Edit") - ("U" (doi-utils-update-bibtex-entry-from-doi (org-ref-bibtex-entry-doi)) "Update entry" :column "Edit") - ("u" doi-utils-update-field "Update field" :column "Edit" :color red) - ("" (cl--set-buffer-substring (line-beginning-position) (+ 1 (line-end-position)) "") - "Delete line" :column "Edit" :color red) - ("d" bibtex-kill-entry "Kill entry" :column "Edit") - ("L" org-ref-clean-bibtex-entry "Clean entry" :column "Edit") - ("A" org-ref-bibtex-assoc-pdf-with-entry "Add pdf" :column "Edit") - ("r" (lambda () - (interactive) - (bibtex-beginning-of-entry) - (bibtex-kill-entry) - (find-file (completing-read - "Bibtex file: " - (append bibtex-completion-bibliography - (f-entries "." (lambda (f) (f-ext? f "bib")))))) - (goto-char (point-max)) - (bibtex-yank) - (save-buffer) - (kill-buffer)) - "Refile entry" :column "Edit") - - ;; www - ("P" org-ref-bibtex-pubmed "Pubmed" :column "WWW") - ("w" org-ref-bibtex-wos "WOS" :column "WWW") - ("c" org-ref-bibtex-wos-citing "WOS citing" :column "WWW") - ("a" org-ref-bibtex-wos-related "WOS related" :column "WWW") - ("R" org-ref-bibtex-crossref "Crossref" :column "WWW") - ("g" org-ref-bibtex-google-scholar "Google Scholar" :column "WWW") - ("e" org-ref-email-bibtex-entry "Email" :column "WWW") - - - ;; Copy - ("o" (lambda () - (interactive) - (bibtex-copy-entry-as-kill) - (message "Use %s to paste the entry" - (substitute-command-keys (format "\\[bibtex-yank]")))) - "Copy entry" :column "Copy") - - ("y" (save-excursion - (bibtex-beginning-of-entry) - (when (looking-at bibtex-entry-maybe-empty-head) - (kill-new (bibtex-key-in-head)))) - "Copy key" :column "Copy") - - ("f" (save-excursion - (bibtex-beginning-of-entry) - (kill-new (bibtex-completion-apa-format-reference - (cdr (assoc "=key=" (bibtex-parse-entry t)))))) - "Formatted entry" :column "Copy") - - ;; Navigation - ("[" org-ref-bibtex-next-entry "Next entry" :column "Navigation" :color red) - ("]" org-ref-bibtex-previous-entry "Previous entry" :column "Navigation" :color red) - ("" next-line "Next line" :column "Navigation" :color red) - ("" previous-line "Previous line" :column "Navigation" :color red) - ("" scroll-up-command "Scroll up" :column "Navigation" :color red) - ("" scroll-down-command "Scroll down" :column "Navigation" :color red) - ("v" org-ref-bibtex-visible-entry "Visible entry" :column "Navigation" :color red) - ("V" org-ref-bibtex-visible-field "Visible field" :column "Navigation" :color red) - - - ;; Miscellaneous - ("F" org-ref-bibtex-file/body "File hydra" :column "Misc") - ("N" org-ref-bibtex-new-entry/body "New entry" :column "Misc") - ("q" nil)) +;; transient menu for actions on bibtex entries +(transient-define-prefix org-ref-bibtex-entry-menu () + "Bibtex actions." + [["Open" + ("p" "PDF" org-ref-open-bibtex-pdf) + ("n" "Notes" org-ref-open-bibtex-notes) + ("b" "URL" org-ref-open-in-browser)] + ["Edit" + ("K" "Keywords" (lambda () + (interactive) + (org-ref-set-bibtex-keywords + (read-string "Keywords: " + (bibtex-autokey-get-field "keywords")) + t))) + ("a" "Replace nonascii" org-ref-replace-nonascii) + ("s" "Sort fields" org-ref-sort-bibtex-entry) + ("T" "Title case" org-ref-title-case-article) + ("S" "Sentence case" org-ref-sentence-case-article) + ("U" "Update entry" (lambda () + (interactive) + (doi-utils-update-bibtex-entry-from-doi (org-ref-bibtex-entry-doi)))) + ("u" "Update field" doi-utils-update-field :transient t) + ("" "Delete line" (lambda () + (interactive) + (cl--set-buffer-substring + (line-beginning-position) + (1+ (line-end-position)) + "")) :transient t) + ("d" "Kill entry" bibtex-kill-entry) + ("L" "Clean entry" org-ref-clean-bibtex-entry) + ("A" "Add pdf" org-ref-bibtex-assoc-pdf-with-entry) + ("r" "Refile entry" (lambda () + (interactive) + (bibtex-beginning-of-entry) + (bibtex-kill-entry) + (find-file (completing-read + "Bibtex file: " + (append bibtex-completion-bibliography + (org-ref--directory-files "." (lambda (f) (org-ref--file-ext-p f "bib")))))) + (goto-char (point-max)) + (bibtex-yank) + (save-buffer) + (kill-buffer)))] + ["WWW" + ("P" "Pubmed" org-ref-bibtex-pubmed) + ("w" "WOS" org-ref-bibtex-wos) + ("c" "WOS citing" org-ref-bibtex-wos-citing) + ("a" "WOS related" org-ref-bibtex-wos-related) + ("R" "Crossref" org-ref-bibtex-crossref) + ("g" "Google Scholar" org-ref-bibtex-google-scholar) + ("e" "Email" org-ref-email-bibtex-entry)] + ["Copy" + ("o" "Copy entry" (lambda () + (interactive) + (bibtex-copy-entry-as-kill) + (message "Use %s to paste the entry" + (substitute-command-keys (format "\\[bibtex-yank]"))))) + ("y" "Copy key" (lambda () + (interactive) + (save-excursion + (bibtex-beginning-of-entry) + (when (looking-at bibtex-entry-maybe-empty-head) + (kill-new (bibtex-key-in-head)))))) + ("f" "Formatted entry" (lambda () + (interactive) + (save-excursion + (bibtex-beginning-of-entry) + (kill-new (bibtex-completion-apa-format-reference + (cdr (assoc "=key=" (bibtex-parse-entry t))))))))] + ["Navigation" + ("[" "Next entry" org-ref-bibtex-next-entry :transient t) + ("]" "Previous entry" org-ref-bibtex-previous-entry :transient t) + ("" "Next line" next-line :transient t) + ("" "Previous line" previous-line :transient t) + ("" "Scroll up" scroll-up-command :transient t) + ("" "Scroll down" scroll-down-command :transient t) + ("v" "Visible entry" org-ref-bibtex-visible-entry :transient t) + ("V" "Visible field" org-ref-bibtex-visible-field :transient t)] + ["Misc" + ("F" "File menu" org-ref-bibtex-file-menu) + ("N" "New entry" org-ref-bibtex-new-entry-menu) + ("q" "Quit" transient-quit-one)]]) (declare-function biblio-lookup "biblio") (declare-function arxiv-add-bibtex-entry "org-ref-arxiv") (declare-function doi-insert-bibtex "doi-utils") -;;** Hydra menu for new bibtex entries -;; A hydra for adding new bibtex entries. -(defhydra org-ref-bibtex-new-entry (:color blue) - "New Bibtex entry:" - ("d" doi-insert-bibtex "from DOI" :column "Automatic") - ("c" crossref-add-bibtex-entry "from Crossref" :column "Automatic") - ("a" arxiv-add-bibtex-entry "From Arxiv" :column "Automatic") - ("b" biblio-lookup "From biblio" :column "Automatic") - ;; Bibtex types - ("ma" bibtex-Article "Article" :column "Manual") - ("mb" bibtex-Book "Book" :column "Manual") - ("mi" bibtex-InBook "In book" :column "Manual") - ("ml" bibtex-Booklet "Booklet" :column "Manual") - ("mP" bibtex-Proceedings "Proceedings" :column "Manual") - ("mp" bibtex-InProceedings "In proceedings" :column "Manual") - ("mm" bibtex-Misc "Misc." :column "Manual") - ("mM" bibtex-Manual "Manual" :column "Manual") - ("mT" bibtex-PhdThesis "PhD Thesis" :column "Manual") - ("mt" bibtex-MastersThesis "MS Thesis" :column "Manual") - ("mR" bibtex-TechReport "Report" :column "Manual") - ("mu" bibtex-Unpublished "unpublished" :column "Manual") - ("mc" bibtex-InCollection "Article in collection" :column "Manual") - ("q" nil "quit")) +;;** Transient menu for new bibtex entries +;; A transient for adding new bibtex entries. +(transient-define-prefix org-ref-bibtex-new-entry-menu () + "New Bibtex entry." + [["Automatic" + ("d" "from DOI" doi-insert-bibtex) + ("c" "from Crossref" crossref-add-bibtex-entry) + ("a" "From Arxiv" arxiv-add-bibtex-entry) + ("b" "From biblio" biblio-lookup)] + ["Manual" + ("ma" "Article" bibtex-Article) + ("mb" "Book" bibtex-Book) + ("mi" "In book" bibtex-InBook) + ("ml" "Booklet" bibtex-Booklet) + ("mP" "Proceedings" bibtex-Proceedings) + ("mp" "In proceedings" bibtex-InProceedings) + ("mm" "Misc." bibtex-Misc) + ("mM" "Manual" bibtex-Manual) + ("mT" "PhD Thesis" bibtex-PhdThesis) + ("mt" "MS Thesis" bibtex-MastersThesis) + ("mR" "Report" bibtex-TechReport) + ("mu" "unpublished" bibtex-Unpublished) + ("mc" "Article in collection" bibtex-InCollection) + ("q" "Quit" transient-quit-one)]]) -;;** Hydra menu of functions to act on a bibtex file. -(defhydra org-ref-bibtex-file (:color blue) - "Bibtex file functions: " - ("v" bibtex-validate "Validate entries") - ("s" bibtex-sort-buffer "Sort entries") - ("r" bibtex-reformat "Reformat entries") - ("c" bibtex-count-entries "Count entries") - ("p" org-ref-build-full-bibliography "PDF bibliography")) +;;** Transient menu of functions to act on a bibtex file. +(transient-define-prefix org-ref-bibtex-file-menu () + "Bibtex file functions." + [["Commands" + ("v" "Validate entries" bibtex-validate) + ("s" "Sort entries" bibtex-sort-buffer) + ("r" "Reformat entries" bibtex-reformat) + ("c" "Count entries" bibtex-count-entries) + ("p" "PDF bibliography" org-ref-build-full-bibliography) + ("q" "Quit" transient-quit-one)]]) + +(define-obsolete-function-alias 'org-ref-bibtex-hydra/body + #'org-ref-bibtex-entry-menu "3.1") +(define-obsolete-function-alias 'org-ref-bibtex-new-entry/body + #'org-ref-bibtex-new-entry-menu "3.1") +(define-obsolete-function-alias 'org-ref-bibtex-file/body + #'org-ref-bibtex-file-menu "3.1") ;;* Email a bibtex entry @@ -1022,7 +1024,7 @@ keywords. Optional argument ARG prefix arg to replace keywords." (cl-loop for buffer in (buffer-list) do (with-current-buffer buffer - (when (and (buffer-file-name) (f-ext? (buffer-file-name) "bib")) + (when (and (buffer-file-name) (org-ref--file-ext-p (buffer-file-name) "bib")) (save-buffer))))) @@ -1150,7 +1152,7 @@ will clobber the file." ;;these are the other fields in the entry, and we sort them alphabetically. (setq other-fields - (sort (-remove (lambda(x) (member x field-order)) entry-fields) + (sort (cl-remove-if (lambda(x) (member x field-order)) entry-fields) 'string<)) (save-restriction @@ -1170,7 +1172,7 @@ will clobber the file." (cl-loop for (f . v) in entry concat (when (string= f field) (format "%s = %s,\n" f v)))) - (-uniq other-fields) "\n") + (delete-dups other-fields) "\n") "\n}")) (bibtex-search-entry key) (bibtex-fill-entry) @@ -1290,7 +1292,7 @@ If optional NEW-YEAR set it to that, otherwise prompt for it." (defun orcb-& () - "Replace naked & with \& in a bibtex entry." + "Replace naked & with \\& in a bibtex entry." (save-restriction (bibtex-narrow-to-entry) (bibtex-beginning-of-entry) @@ -1365,7 +1367,7 @@ If not, issue a warning." (journal (cdr (assoc "journal" entry)))) (when (null journal) (warn "Unable to get journal for this entry.")) - (unless (member journal (-flatten org-ref-bibtex-journal-abbreviations)) + (unless (member journal (org-ref--flatten-list org-ref-bibtex-journal-abbreviations)) (message "Journal \"%s\" not found in org-ref-bibtex-journal-abbreviations." journal)))))) diff --git a/lisp/org-ref/org-ref-citation-links.el b/lisp/org-ref/org-ref-citation-links.el index d030746e..8c308e06 100644 --- a/lisp/org-ref/org-ref-citation-links.el +++ b/lisp/org-ref/org-ref-citation-links.el @@ -28,8 +28,8 @@ ;; indicate the pre/post-note structure. They also have tooltips that show ;; information from the bibtex entry. ;; -;; Each link is functional, and clicking on one will open a hydra menu -;; `org-ref-citation-hydra/body' of actions that range from opening the bibtex +;; Each link is functional, and clicking on one will open a transient menu +;; `org-ref-citation-menu' of actions that range from opening the bibtex ;; entry, notes, pdf or associated URL, to searching the internet for related ;; articles. ;; @@ -48,7 +48,7 @@ ;; ;;; Code: (require 'org-keys) -(require 'hydra) +(require 'transient) (require 'xref) (eval-when-compile (require 'subr-x)) @@ -340,14 +340,14 @@ to a path string." (string-join (cl-loop for ref in (plist-get data :references) collect (plist-get ref :key)) ",")) (3 (concat - (when-let (prefix (plist-get data :prefix)) (concat prefix ";")) + (when-let* ((prefix (plist-get data :prefix))) (concat prefix ";")) (string-join (cl-loop for ref in (plist-get data :references) collect (concat (plist-get ref :prefix) "&" (plist-get ref :key) (plist-get ref :suffix))) ";") - (when-let (suffix (plist-get data :suffix)) (concat ";" suffix)))))) + (when-let* ((suffix (plist-get data :suffix))) (concat ";" suffix)))))) ;; * Activating citation links @@ -360,19 +360,6 @@ to a path string." (defvar bibtex-completion-bibliography) (defvar bibtex-completion-display-formats-internal) -;; (defun org-ref-valid-keys () -;; "Return a list of valid bibtex keys for this buffer. -;; This is used a lot in `org-ref-cite-activate' so it needs to be -;; fast, but also up to date." -;; ;; this seems to be needed, but we don't want to do this every time -;; (unless bibtex-completion-display-formats-internal -;; (bibtex-completion-init)) - -;; (let ((bibtex-completion-bibliography (org-ref-find-bibliography))) -;; (cl-loop for entry in (bibtex-completion-candidates) -;; collect -;; (cdr (assoc "=key=" (cdr entry)))))) - (defun org-ref-valid-keys () "Return a list of valid bibtex keys for this buffer. @@ -380,6 +367,8 @@ This is used a lot in `org-ref-cite-activate' so it needs to be fast, but also up to date." ;; this seems to be needed, but we don't want to do this every time + ;; I found when bibtex-completion-display-formats-internal is nil + ;; we have to run this init function (unless bibtex-completion-display-formats-internal (bibtex-completion-init)) @@ -394,7 +383,9 @@ fast, but also up to date." for file in files append (cddr (assoc file bibtex-completion-cache))) collect (cdr (assoc "=key=" (cdr entry)))) - ;; you need to get a cache because one or more of the files was not in the cache. + ;; you need to get a cache because one or more of the files was not in the + ;; cache. The cache should be automatically made by + ;; bibtex-completion-candidates (let ((bibtex-completion-bibliography files)) (cl-loop for entry in (bibtex-completion-candidates) collect @@ -405,11 +396,15 @@ fast, but also up to date." (defvar-local org-ref-valid-keys-cache nil) (defun org-ref-valid-keys-cached () - "Update `org-ref-valid-keys-cache` only when files changed." + "Update `org-ref-valid-keys-cache` only when files changed or it is empty. +Returns a hash-table you can use to test key validity. + +(gethash key (org-ref-valid-keys-cached)" (let ((local-hashes (cons bibtex-completion-bibliography (mapcar 'cadr bibtex-completion-cache)))) - (when (not (equal local-hashes org-ref-valid-keys-hashes)) + (when (or (null org-ref-valid-keys-cache) + (not (equal local-hashes org-ref-valid-keys-hashes))) (setq-local org-ref-valid-keys-hashes local-hashes) (setq-local org-ref-valid-keys-cache (make-hash-table :test 'equal)) (cl-loop for entry in (org-ref-valid-keys) @@ -427,7 +422,7 @@ PATH has the citations in it." ;; path containing @ which makes it likely to be an org-cite. Maybe ;; a text property is better, in case this is an issue in the ;; future. - (not (s-contains-p "@" path))) + (not (string-match-p (regexp-quote "@") path))) (let* ((valid-keys (org-ref-valid-keys)) valid-key substrings) @@ -522,64 +517,67 @@ PATH has the citations in it." (declare-function org-ref-get-bibtex-key-and-file "org-ref-core") -(defhydra org-ref-citation-hydra (:color blue :hint nil) - "Citation actions -" - ("o" org-ref-open-citation-at-point "Bibtex" :column "Open") - ("p" org-ref-open-pdf-at-point "PDF" :column "Open") - ("n" org-ref-open-notes-at-point "Notes" :column "Open") - ("u" org-ref-open-url-at-point "URL" :column "Open") - - ;; WWW actions - ("ww" org-ref-wos-at-point "WOS" :column "WWW") - ("wr" org-ref-wos-related-at-point "WOS related" :column "WWW") - ("wc" org-ref-wos-citing-at-point "WOS citing" :column "WWW") - ("wg" org-ref-google-scholar-at-point "Google Scholar" :column "WWW") - ("wp" org-ref-pubmed-at-point "Pubmed" :column "WWW") - ("wf" org-ref-crossref-at-point "Crossref" :column "WWW") - ("wb" org-ref-biblio-at-point "Biblio" :column "WWW") - ("e" org-ref-email-at-point "Email" :column "WWW") - - ;; Copyish actions - ("K" (save-window-excursion - (let ((bibtex-completion-bibliography (org-ref-find-bibliography))) - (bibtex-completion-show-entry (list (org-ref-get-bibtex-key-under-cursor))) - (bibtex-copy-entry-as-kill) - (kill-new (pop bibtex-entry-kill-ring)))) - "Copy bibtex" :column "Copy") - ("a" org-ref-add-pdf-at-point "add pdf to library" :column "Copy") - ("k" (kill-new (car (org-ref-get-bibtex-key-and-file))) "Copy key" :column "Copy") - ("f" (kill-new (bibtex-completion-apa-format-reference - (org-ref-get-bibtex-key-under-cursor))) - "Copy formatted" :column "Copy") - ("h" (kill-new - (format "* %s\n\n cite:&%s" - (bibtex-completion-apa-format-reference - (org-ref-get-bibtex-key-under-cursor)) - (car (org-ref-get-bibtex-key-and-file)))) - "Copy org heading" - :column "Copy") - - ;; Editing actions - ("" org-ref-cite-shift-left "Shift left" :color red :column "Edit") - ("" org-ref-cite-shift-right "Shift right" :color red :column "Edit") - ("" org-ref-sort-citation-link "Sort by year" :column "Edit") - ("i" (funcall org-ref-insert-cite-function) "Insert cite" :column "Edit") - ("t" org-ref-change-cite-type "Change cite type" :column "Edit") - ("d" org-ref-delete-citation-at-point "Delete at point" :column "Edit") - ("r" org-ref-replace-citation-at-point "Replace cite" :column "Edit") - ("P" org-ref-edit-pre-post-notes "Edit pre/suffix" :column "Edit") - - ;; Navigation - ("[" org-ref-previous-key "Previous key" :column "Navigation" :color red) - ("]" org-ref-next-key "Next key" :column "Navigation" :color red) - ("v" org-ref-jump-to-visible-key "Visible key" :column "Navigation" :color red) - ("q" nil "Quit")) +(transient-define-prefix org-ref-citation-menu () + "Citation actions." + [["Open" + ("o" "Bibtex" org-ref-open-citation-at-point) + ("p" "PDF" org-ref-open-pdf-at-point) + ("n" "Notes" org-ref-open-notes-at-point) + ("u" "URL" org-ref-open-url-at-point)] + ["WWW" + ("ww" "WOS" org-ref-wos-at-point) + ("wr" "WOS related" org-ref-wos-related-at-point) + ("wc" "WOS citing" org-ref-wos-citing-at-point) + ("wg" "Google Scholar" org-ref-google-scholar-at-point) + ("wp" "Pubmed" org-ref-pubmed-at-point) + ("wf" "Crossref" org-ref-crossref-at-point) + ("wb" "Biblio" org-ref-biblio-at-point) + ("e" "Email" org-ref-email-at-point)] + ["Copy" + ("K" "Copy bibtex" (lambda () + (interactive) + (save-window-excursion + (let ((bibtex-completion-bibliography (org-ref-find-bibliography))) + (bibtex-completion-show-entry (list (org-ref-get-bibtex-key-under-cursor))) + (bibtex-copy-entry-as-kill) + (kill-new (pop bibtex-entry-kill-ring)))))) + ("a" "Add pdf to library" org-ref-add-pdf-at-point) + ("k" "Copy key" (lambda () + (interactive) + (kill-new (car (org-ref-get-bibtex-key-and-file))))) + ("f" "Copy formatted" (lambda () + (interactive) + (kill-new (bibtex-completion-apa-format-reference + (org-ref-get-bibtex-key-under-cursor))))) + ("h" "Copy org heading" (lambda () + (interactive) + (kill-new + (format "* %s\n\n cite:&%s" + (bibtex-completion-apa-format-reference + (org-ref-get-bibtex-key-under-cursor)) + (car (org-ref-get-bibtex-key-and-file))))))] + ["Edit" + ("" "Shift left" org-ref-cite-shift-left :transient t) + ("" "Shift right" org-ref-cite-shift-right :transient t) + ("" "Sort by year" org-ref-sort-citation-link) + ("i" "Insert cite" (lambda () (interactive) (funcall org-ref-insert-cite-function))) + ("t" "Change cite type" org-ref-change-cite-type) + ("d" "Delete at point" org-ref-delete-citation-at-point) + ("r" "Replace cite" org-ref-replace-citation-at-point) + ("P" "Edit pre/suffix" org-ref-edit-pre-post-notes)] + ["Navigation" + ("[" "Previous key" org-ref-previous-key :transient t) + ("]" "Next key" org-ref-next-key :transient t) + ("v" "Visible key" org-ref-jump-to-visible-key :transient t) + ("q" "Quit" transient-quit-one)]]) (defun org-ref-cite-follow (_path) "Follow a cite link." - (org-ref-citation-hydra/body)) + (org-ref-citation-menu)) + +(define-obsolete-function-alias 'org-ref-citation-hydra/body + #'org-ref-citation-menu "3.1") ;; * Citation links tooltips @@ -637,13 +635,13 @@ Use with apply-partially." (format "[%s]" (cl-second prefix-suffix))) (t "")))) - (s-format "\\${cmd}${prefix}${suffix}{${keys}}" 'aget + (org-ref--format-template "\\${cmd}${prefix}${suffix}{${keys}}" `(("cmd" . ,cmd) ("prefix" . ,(string-trim prefix)) ("suffix" . ,(string-trim suffix)) ("keys" . ,(string-join keys ",")))))) (3 - (s-format "\\${cmd}${prefix}${suffix}{${keys}}" 'aget + (org-ref--format-template "\\${cmd}${prefix}${suffix}{${keys}}" `(("cmd" . ,cmd) ;; if there is more than one key, we only do global ;; prefix/suffix But for one key, we should allow local @@ -748,7 +746,7 @@ Use with apply-partially." (pcase backend ('latex (let ((cite (org-ref-parse-cite-path path))) - (s-format "\\${cmd}${global-prefix}${global-suffix}${keys}" 'aget + (org-ref--format-template "\\${cmd}${global-prefix}${global-suffix}${keys}" `(("cmd" . ,cmd) ("global-prefix" . ,(cond ((plist-get cite :prefix) @@ -858,7 +856,7 @@ Use with apply-partially." (setq i (seq-position references key (lambda (el key) (string= key (plist-get el :key))))) ;; delete i'th reference - (setq references (-remove-at i references)) + (setq references (org-ref--remove-at i references)) (setq data (plist-put data :references references)) (save-excursion (goto-char begin) @@ -1006,7 +1004,7 @@ arg COMMON, edit the common prefixes instead." If not on a key, but on a cite, prompt for key." (cond (org-ref-activate-cite-links - (if-let ((key (get-text-property (point) 'cite-key))) + (if-let* ((key (get-text-property (point) 'cite-key))) ;; Point is on a key, so we get it directly key ;; point is not on a key, but may still be on a cite link @@ -1046,10 +1044,10 @@ If not on a key, but on a cite, prompt for key." (prog1 (get-text-property (point) 'cite-key) (goto-char cp))))))))) - + ;; org-ref-activate-cite-links is nil so font-lock does not put ;; text-properties on keys. We temporarily activate this - + (t (let ((el (org-element-context)) (org-ref-activate-cite-links t)) ;; temporary @@ -1171,10 +1169,10 @@ If not on a key, but on a cite, prompt for key." Otherwise run `right-word'. If the cursor moves off the link, move to the beginning of the next cite link after this one." (interactive) - (when-let (next (next-single-property-change (point) 'cite-key)) + (when-let* ((next (next-single-property-change (point) 'cite-key))) (goto-char next)) (unless (get-text-property (point) 'cite-key) - (when-let (next (next-single-property-change (point) 'cite-key)) + (when-let* ((next (next-single-property-change (point) 'cite-key))) (goto-char next)))) @@ -1184,10 +1182,10 @@ move to the beginning of the next cite link after this one." Otherwise run `left-word'. If the cursor moves off the link, move to the beginning of the previous cite link after this one." (interactive) - (when-let (prev (previous-single-property-change (point) 'cite-key)) + (when-let* ((prev (previous-single-property-change (point) 'cite-key))) (goto-char prev)) (unless (get-text-property (point) 'cite-key) - (when-let (prev (previous-single-property-change (point) 'cite-key)) + (when-let* ((prev (previous-single-property-change (point) 'cite-key))) (goto-char prev)))) (defvar avy-goto-key) @@ -1316,7 +1314,7 @@ Rules: (string= key-at-point (plist-get el1 :key))))) (setq data (plist-put data :references - (-insert-at + (org-ref--insert-at (+ index (if (and (= 3 version) (looking-at "&")) 0 1)) diff --git a/lisp/org-ref/org-ref-core.el b/lisp/org-ref/org-ref-core.el index c7b34ded..e1907452 100644 --- a/lisp/org-ref/org-ref-core.el +++ b/lisp/org-ref/org-ref-core.el @@ -35,13 +35,11 @@ (require 'org-element) -(require 'dash) -(require 'f) -(require 's) +(require 'org-ref-utils) (require 'parsebib) (require 'bibtex-completion) -(require 'hydra) +(require 'transient) (require 'org-ref-bibliography-links) (require 'org-ref-citation-links) @@ -172,9 +170,7 @@ set in `bibtex-completion-bibliography'" (throw 'result (nreverse (delete-dups (mapcar 'org-ref-get-bibfile-path org-ref-bibliography-files))))))) ;; we did not find anything. use defaults. Make sure we have a list in ;; case it is a single string. - (throw 'result (if (listp bibtex-completion-bibliography) - bibtex-completion-bibliography - (list bibtex-completion-bibliography))))))) + (throw 'result (org-ref-normalize-bibtex-completion-bibliography)))))) (defun org-ref-key-in-file-p (key filename) @@ -188,18 +184,16 @@ set in `bibtex-completion-bibliography'" (defun org-ref-possible-bibfiles () "Make a unique list of possible bibliography files for completing-read" - (-uniq + (delete-dups (append ;; see if we should add it to a bib-file defined in the file (org-ref-find-bibliography) ;; or any bib-files that exist in the current directory - (f-entries "." (lambda (f) + (org-ref--directory-files "." (lambda (f) (and (not (string-match "#" f)) - (f-ext? f "bib")))) + (org-ref--file-ext-p f "bib")))) ;; and last in the default bibliography - (if (stringp bibtex-completion-bibliography) - (list bibtex-completion-bibliography) - bibtex-completion-bibliography)))) + (org-ref-normalize-bibtex-completion-bibliography)))) (defun org-ref-get-bibtex-key-and-file (&optional key) @@ -236,65 +230,106 @@ provide their own version." ;; This is an alternative that doesn't rely on prefix args. -(defhydra org-ref-insert-link-hydra (:color red :hint nil) - "Insert an org-ref link -" - ("[" (funcall org-ref-insert-cite-function) "Citation" :column "org-ref") - ("]" (funcall org-ref-insert-ref-function) "Cross-reference" :column "org-ref") - ("\\" (funcall org-ref-insert-label-function) "Label" :column "org-ref") - - ("bs" (insert (org-ref-bibliographystyle-complete-link)) "Bibliographystyle" :column "Bibliography" :color blue) - ("bf" (insert (org-ref-bibliography-complete)) "Bibliography" :column "Bibliography" :color blue) - ("nb" (insert (org-ref-nobibliography-complete)) "Bibliography" :column "Bibliography" :color blue) - - ("g" org-ref-insert-glossary-link "Glossary link" :column "Glossary" :color blue) - ("a" org-ref-insert-acronym-link "Acronym link" :column "Glossary" :color blue) - ("ng" (progn - (org-mark-ring-push) - (goto-char (point-min)) - (if (re-search-forward "#\\+name: glossary" nil t) - (progn - (goto-char (org-element-property :contents-end (org-element-context))) - (backward-char) - (org-table-insert-row '(4))) - ;; no table found - (goto-char (point-max)) - (insert "\n\n#+name: glossary +(defun org-ref-insert-link-menu--insert-citation () + (interactive) + (funcall org-ref-insert-cite-function)) + +(defun org-ref-insert-link-menu--insert-reference () + (interactive) + (funcall org-ref-insert-ref-function)) + +(defun org-ref-insert-link-menu--insert-label () + (interactive) + (funcall org-ref-insert-label-function)) + +(defun org-ref-insert-link-menu--open-bibliography () + (interactive) + (find-file (completing-read "Bibliography: " (org-ref-find-bibliography)))) + +(defun org-ref-insert-link-menu--ensure-table (name template) + (org-mark-ring-push) + (goto-char (point-min)) + (if (re-search-forward (format "#\\+name: %s" name) nil t) + (progn + (goto-char (org-element-property :contents-end (org-element-context))) + (backward-char) + (org-table-insert-row '(4))) + (goto-char (point-max)) + (insert template) + (beginning-of-line) + (forward-char))) + +(defun org-ref-insert-link-menu--new-glossary-term () + (interactive) + (org-ref-insert-link-menu--ensure-table + "glossary" + "\n\n#+name: glossary | label | term | definition | |-------+---------+-------------------------------| -| | | |") - (beginning-of-line) - (forward-char))) - "New glossary term" :column "Glossary") - - ("na" (progn - (org-mark-ring-push) - (goto-char (point-min)) - (if (re-search-forward "#\\+name: acronym" nil t) - (progn - (goto-char (org-element-property :contents-end (org-element-context))) - (backward-char) - (org-table-insert-row '(4))) - ;; no table found - (goto-char (point-max)) - (insert "\n\n#+name: acronyms +| | | |")) + +(defun org-ref-insert-link-menu--new-acronym-term () + (interactive) + (org-ref-insert-link-menu--ensure-table + "acronym" + "\n\n#+name: acronyms | label | abbreviation | full form | |-------+--------------+----------------------------| -| | | |") - (beginning-of-line) - (forward-char))) - "New acronym term" :column "Glossary") +| | | |")) - ("bd" doi-add-bibtex-entry "Add bibtex entry from a DOI" :column "Bibtex") - ("bc" crossref-add-bibtex-entry "Add bibtex entry from Crossref" :column "Bibtex") - ("bo" (find-file (completing-read "Bibliography: " (org-ref-find-bibliography))) - "Open bibtex file" :column "Bibtex") - - ("t" (insert "[[list-of-tables:]]\n") "List of tables" :column "Misc") - ("f" (insert "[[list-of-figures:]]\n") "List of figures" :column "Misc") - ("i" (insert (format "[[index:%s]]" (string-trim (read-string "Index entry: ")))) "Index entry" :column "Misc") - ("pi" (insert "[[printindex:]]") "Print index" :column "Misc") - ("pg" (insert "[[printglossaries:]]") "Print glossary" :column "Misc")) +(defun org-ref-insert-link-menu--insert-string (string) + (insert string)) + +(defun org-ref-insert-link-menu--insert-index (prompt template) + (org-ref-insert-link-menu--insert-string + (format template (string-trim (read-string prompt))))) + +(transient-define-prefix org-ref-insert-link-menu () + "Insert an org-ref link." + [["org-ref" + ("]" "Citation" org-ref-insert-link-menu--insert-citation :transient t) + ("r" "Cross-reference" org-ref-insert-link-menu--insert-reference :transient t) + ("\\" "Label" org-ref-insert-link-menu--insert-label :transient t)] + ["Bibliography" + ("bs" "Bibliographystyle" (lambda () (interactive) + (org-ref-insert-link-menu--insert-string + (org-ref-bibliographystyle-complete-link)))) + ("bf" "Bibliography" (lambda () (interactive) + (org-ref-insert-link-menu--insert-string + (org-ref-bibliography-complete)))) + ("nb" "Nobibliography" (lambda () (interactive) + (org-ref-insert-link-menu--insert-string + (org-ref-nobibliography-complete))))] + ["Glossary" + ("g" "Glossary link" org-ref-insert-glossary-link) + ("a" "Acronym link" org-ref-insert-acronym-link) + ("ng" "New glossary term" org-ref-insert-link-menu--new-glossary-term :transient t) + ("na" "New acronym term" org-ref-insert-link-menu--new-acronym-term :transient t)] + ["Bibtex" + ("bd" "Add bibtex entry from a DOI" doi-add-bibtex-entry :transient t) + ("bc" "Add bibtex entry from Crossref" crossref-add-bibtex-entry :transient t) + ("bo" "Open bibtex file" org-ref-insert-link-menu--open-bibliography :transient t)] + ["Misc" + ("t" "List of tables" (lambda () (interactive) + (org-ref-insert-link-menu--insert-string "[[list-of-tables:]]\n")) + :transient t) + ("f" "List of figures" (lambda () (interactive) + (org-ref-insert-link-menu--insert-string "[[list-of-figures:]]\n")) + :transient t) + ("i" "Index entry" (lambda () (interactive) + (org-ref-insert-link-menu--insert-index + "Index entry: " "[[index:%s]]")) + :transient t) + ("pi" "Print index" (lambda () (interactive) + (org-ref-insert-link-menu--insert-string "[[printindex:]]")) + :transient t) + ("pg" "Print glossary" (lambda () (interactive) + (org-ref-insert-link-menu--insert-string "[[printglossaries:]]")) + :transient t) + ("q" "Quit" transient-quit-one)]]) + +(define-obsolete-function-alias 'org-ref-insert-link-hydra/body + #'org-ref-insert-link-menu "3.1") ;;* org-ref-help diff --git a/lisp/org-ref/org-ref-export.el b/lisp/org-ref/org-ref-export.el index 983d47ec..68c226d7 100644 --- a/lisp/org-ref/org-ref-export.el +++ b/lisp/org-ref/org-ref-export.el @@ -39,18 +39,36 @@ ;; export is your goal, you may want to use org-ref-refproc.el to handle ;; cross-references. ;; +;;; Formatting Limitations: +;; +;; Different export backends have different formatting capabilities. HTML and +;; LaTeX support full CSL formatting including small caps, italics, bold, etc. +;; +;; The org-mode backend has limitations because org-mode syntax does not support +;; small caps. Many CSL citation styles use small caps for author names, so these +;; will not render correctly when exporting to formats that use the org backend +;; (like DOCX by default). +;; +;; For DOCX export with better formatting support, set +;; `org-ref-docx-use-html-backend' to t. This causes citations to be rendered +;; as HTML which pandoc can convert to DOCX while preserving formatting. +;; +;; See `org-ref-backend-csl-formats' to customize backend mappings for different +;; export formats. +;; ;;; Code: -(eval-when-compile - (require 'hydra)) +(require 'transient) +(require 'org-ref-utils) +(require 'org-ref-citation-links) (defvar hfy-user-sheet-assoc) ; to quiet compiler (require 'ox-org) -(if (executable-find "pandoc") - (require 'ox-pandoc)) +(when (executable-find "pandoc") + (ignore-errors (require 'ox-pandoc))) -(require 'citeproc) +(ignore-errors (require 'citeproc)) (defvar org-cite-csl-styles-dir) @@ -62,7 +80,24 @@ (ascii . plain) (odt . org-odt) (docx . org)) - "Mapping of export backend to csl-backends." + "Mapping of export backend to csl-backends. + +Each entry maps an org export backend symbol to a citeproc-el backend +format used for rendering citations and bibliographies. + +Note: The 'org backend has formatting limitations because org-mode +syntax does not support small caps. CSL styles that use small caps +(common for author names) will not render correctly when using the +'org backend. For DOCX export, consider setting +`org-ref-docx-use-html-backend' to t to use HTML formatting which +pandoc can convert while preserving small caps and other formatting. + +Available citeproc backends: +- html: Full HTML formatting with CSS styles +- latex: Full LaTeX formatting with commands like \\textsc{} +- org: Org-mode markup (limited, no small caps support) +- org-odt: ODT-specific XML formatting +- plain: Plain text with no formatting" :type '(alist :key-type (symbol) :value-type (symbol)) :group 'org-ref) @@ -92,6 +127,30 @@ Should be a csl filename, or an absolute path to a csl filename." :group 'org-ref) +(defcustom org-ref-docx-use-html-backend nil + "Use HTML backend for DOCX export to preserve CSL formatting. + +When non-nil, citations exported to DOCX will be rendered using the +HTML backend, which preserves formatting like small caps and italics +that pandoc can convert to DOCX. + +When nil (default), uses the org backend which has limited formatting +support due to org-mode syntax limitations. Specifically, org-mode has +no native syntax for small caps, so CSL styles that use small caps +(common for author names in many citation styles) will not render +correctly in DOCX output. + +Note: This option only affects DOCX export via pandoc. Other export +formats (HTML, LaTeX, ODT) are not affected. + +See also `org-ref-backend-csl-formats' to customize backend mappings +for other export formats. + +Related to issue #981: https://github.com/jkitchin/org-ref/issues/981" + :type 'boolean + :group 'org-ref) + + (defcustom org-ref-csl-label-aliases '((("app" "apps") . "appendix") (("art" "arts") . "article-locator") @@ -128,7 +187,7 @@ See https://github.com/citation-style-language/documentation/blob/master/specifi :group 'org-ref) -(defcustom org-ref-export-suppress-affix-types +(defcustom org-ref-export-suppress-affix-types '("citet" "citet*" "citetitle" @@ -141,6 +200,68 @@ See https://github.com/citation-style-language/documentation/blob/master/specifi :group 'org-ref) +;;; Footnote citation support (issue #993) + +(defvar org-ref-footnote-counter 0 + "Counter for footnote citations during export. +Reset to 0 at the start of each export process.") + +(defcustom org-ref-footnote-cite-types + '("footcite" + "footfullcite" + "footcitetext" + "footcites" + "footcitetexts" + "smartcite" + "Smartcite") + "List of citation types that should be rendered as footnotes. +These citation types will be given a :note-index parameter when +passed to citeproc-el, which causes them to be rendered as notes/footnotes +when using CSL styles that support note formatting." + :type '(repeat string) + :group 'org-ref) + + +(defun org-ref-footnote-cite-type-p (cite-type) + "Return non-nil if CITE-TYPE should be rendered as a footnote. +CITE-TYPE is a string like \"footcite\" or \"cite\"." + (member cite-type org-ref-footnote-cite-types)) + + +(defun org-ref-get-next-footnote-number () + "Get the next footnote number and increment the counter. +Returns the next sequential footnote number (1, 2, 3, ...)." + (setq org-ref-footnote-counter (1+ org-ref-footnote-counter))) + + +(defun org-ref-csl-style-supports-notes-p (csl-style-file) + "Return non-nil if CSL-STYLE-FILE supports note/footnote formatting. +CSL-STYLE-FILE is a filename like \"chicago-fullnote-bibliography.csl\". + +This function checks if the CSL style file contains note-related +class attributes, which indicate it supports footnote formatting." + (or + ;; Check filename for common note-style indicators + ;; Match: -note-, -note.csl, fullnote, footnote + (string-match-p "\\(\\bnote\\b\\|fullnote\\|footnote\\)" csl-style-file) + ;; If we have the actual file, check its contents + (when (file-exists-p csl-style-file) + (with-temp-buffer + (insert-file-contents csl-style-file) + (goto-char (point-min)) + ;; Look for class="note" in the CSL XML + (re-search-forward "class=\"note\"" nil t))))) + + +(defun org-ref-get-citation-note-index (citation-link) + "Get the note-index for CITATION-LINK if it's a footnote citation type. +CITATION-LINK is an org-element link object. +Returns a footnote number if this is a footnote citation, nil otherwise." + (when (org-ref-footnote-cite-type-p + (org-element-property :type citation-link)) + (org-ref-get-next-footnote-number))) + + (defun org-ref-dealias-label (alias) "Return the full, de-aliased label for ALIAS. Looked up from `org-ref-csl-label-aliases'. @@ -234,10 +355,17 @@ REF is a plist data structure returned from `org-ref-parse-cite-path'." "Process the citations and bibliography in the org-buffer. Usually run on a copy of the buffer during export. BACKEND is the org export backend." + ;; Reset footnote counter for this export + (setq org-ref-footnote-counter 0) + (save-restriction (when subtreep (org-narrow-to-subtree)) (let* ((csl-backend (or (cdr (assoc backend org-ref-backend-csl-formats)) 'plain)) + ;; Use HTML backend for DOCX if requested for better formatting + (csl-backend (if (and (eq backend 'docx) org-ref-docx-use-html-backend) + 'html + csl-backend)) (style (or (cadr (assoc "CSL-STYLE" (org-collect-keywords @@ -248,6 +376,24 @@ BACKEND is the org export backend." '("CSL-LOCALE")))) org-ref-csl-default-locale)) + ;; Determine the actual style file path for checking note support + (style-file (cond + ((file-exists-p style) + style) + ((and (bound-and-true-p org-cite-csl-styles-dir) + (file-exists-p (org-ref--file-join org-cite-csl-styles-dir style))) + (org-ref--file-join org-cite-csl-styles-dir style)) + ((file-exists-p (expand-file-name style + (org-ref--file-join (file-name-directory + (locate-library "org-ref")) + "citeproc/csl-styles"))) + (expand-file-name style (org-ref--file-join + (file-name-directory + (locate-library "org-ref")) + "citeproc/csl-styles"))) + (t + style))) ; Fallback to style name itself + (proc (citeproc-create ;; The style (cond @@ -255,14 +401,14 @@ BACKEND is the org export backend." style) ;; In a user-dir ((and (bound-and-true-p org-cite-csl-styles-dir) - (file-exists-p (f-join org-cite-csl-styles-dir style))) - (f-join org-cite-csl-styles-dir style)) + (file-exists-p (org-ref--file-join org-cite-csl-styles-dir style))) + (org-ref--file-join org-cite-csl-styles-dir style)) ;; provided by org-ref ((file-exists-p (expand-file-name style - (f-join (file-name-directory + (org-ref--file-join (file-name-directory (locate-library "org-ref")) "citeproc/csl-styles"))) - (expand-file-name style (f-join + (expand-file-name style (org-ref--file-join (file-name-directory (locate-library "org-ref")) "citeproc/csl-styles"))) @@ -274,7 +420,7 @@ BACKEND is the org export backend." ;; locale getter (citeproc-locale-getter-from-dir (if (bound-and-true-p org-cite-csl-locales-dir) org-cite-csl-locales-dir - (f-join (file-name-directory + (org-ref--file-join (file-name-directory (locate-library "org-ref")) "citeproc/csl-locales"))) ;; the actual locale @@ -338,8 +484,15 @@ BACKEND is the org export backend." "[A-Z]" (substring (org-element-property :type cl) 0 1)) - ;; I don't know where this information would come from. - :note-index nil + ;; Set note-index for footnote citation types (issue #993) + :note-index (when (org-ref-footnote-cite-type-p + (org-element-property :type cl)) + ;; Warn if using footnote citations with non-note style + (unless (org-ref-csl-style-supports-notes-p style-file) + (warn "Citation type '%s' is a footnote citation but CSL style '%s' may not support notes. Consider using a note-based style like chicago-fullnote-bibliography.csl" + (org-element-property :type cl) + style)) + (org-ref-get-next-footnote-number)) :ignore-et-al nil :grouped nil)))) @@ -547,6 +700,11 @@ VISIBLE-ONLY BODY-ONLY and INFO." (defun org-ref-export-to-docx (&optional async subtreep visible-only body-only info) "Export the buffer to docx via pandoc and open. + +Note: By default, DOCX export uses the org backend for citations which +has formatting limitations (no small caps support). Set +`org-ref-docx-use-html-backend' to t for better formatting preservation. + See `org-export-as' for the meaning of ASYNC SUBTREEP VISIBLE-ONLY BODY-ONLY and INFO." (org-ref-export-to 'docx async subtreep visible-only @@ -614,23 +772,67 @@ I am not positive on this though." (org-at-heading-p))))) -;; A hydra exporter with preprocessors -(defhydradio org-ref () - (natmove "natmove") - (citeproc "CSL citations") - (refproc "cross-references") - (acrossproc "Acronyms, glossary") - (idxproc "Index") - (bblproc "BBL citations")) +;; A transient exporter with preprocessors +(defvar org-ref/natmove nil + "Toggle for moving natbib citations before export.") + +(defvar org-ref/citeproc nil + "Toggle for running citeproc before export.") + +(defvar org-ref/refproc nil + "Toggle for running refproc before export.") + +(defvar org-ref/acrossproc nil + "Toggle for running acrossproc before export.") + +(defvar org-ref/idxproc nil + "Toggle for running idxproc before export.") + +(defvar org-ref/bblproc nil + "Toggle for running bblproc before export.") + +(defun org-ref-export--toggle (var) + (set var (not (symbol-value var))) + (message "%s %s" + (capitalize (replace-regexp-in-string "/" " " (symbol-name var))) + (if (symbol-value var) "enabled" "disabled")) + (symbol-value var)) + +(defun org-ref-export-toggle-natmove () + (interactive) + (org-ref-export--toggle 'org-ref/natmove)) + +(defun org-ref-export-toggle-citeproc () + (interactive) + (org-ref-export--toggle 'org-ref/citeproc)) + +(defun org-ref-export-toggle-refproc () + (interactive) + (org-ref-export--toggle 'org-ref/refproc)) + +(defun org-ref-export-toggle-acrossproc () + (interactive) + (org-ref-export--toggle 'org-ref/acrossproc)) + +(defun org-ref-export-toggle-idxproc () + (interactive) + (org-ref-export--toggle 'org-ref/idxproc)) + +(defun org-ref-export-toggle-bblproc () + (interactive) + (org-ref-export--toggle 'org-ref/bblproc)) + +(defun org-ref-export--toggle-description (label var) + (format "%s %s" label (if (symbol-value var) "[on]" "[off]"))) -(defun org-ref-export-from-hydra (&optional arg) - "Run the export dispatcher with the desired hooks selected in `org-ref-export/body'." +(defun org-ref-export-dispatch (&optional arg) + "Run the export dispatcher with the desired hooks selected in the export menu." (interactive "P") (when (and org-ref/citeproc org-ref/bblproc) (error "You cannot use CSL and BBL at the same time.")) - + (let ((org-export-before-parsing-functions org-export-before-parsing-functions)) (when org-ref/citeproc (cl-pushnew 'org-ref-csl-preprocess-buffer org-export-before-parsing-functions)) @@ -648,28 +850,37 @@ I am not positive on this though." (unless (featurep 'org-ref-natbib-bbl-citeproc) (require 'org-ref-natbib-bbl-citeproc)) (cl-pushnew 'org-ref-bbl-preprocess org-export-before-parsing-functions)) - + ;; this goes last since it moves cites before they might get replaced. (when org-ref/natmove (cl-pushnew 'org-ref-cite-natmove org-export-before-parsing-functions)) (org-export-dispatch arg))) +(transient-define-prefix org-ref-export-menu () + "Select export preprocessors and dispatch export." + [["Preprocessors" + ("n" org-ref-export-toggle-natmove :transient t + :description (lambda () (org-ref-export--toggle-description "natmove" 'org-ref/natmove))) + ("c" org-ref-export-toggle-citeproc :transient t + :description (lambda () (org-ref-export--toggle-description "citeproc" 'org-ref/citeproc))) + ("r" org-ref-export-toggle-refproc :transient t + :description (lambda () (org-ref-export--toggle-description "refproc" 'org-ref/refproc))) + ("a" org-ref-export-toggle-acrossproc :transient t + :description (lambda () (org-ref-export--toggle-description "acrossproc" 'org-ref/acrossproc))) + ("i" org-ref-export-toggle-idxproc :transient t + :description (lambda () (org-ref-export--toggle-description "idxproc" 'org-ref/idxproc))) + ("b" org-ref-export-toggle-bblproc :transient t + :description (lambda () (org-ref-export--toggle-description "bblproc" 'org-ref/bblproc)))] + ["Actions" + ("e" "Export" org-ref-export-dispatch) + ("q" "Quit" transient-quit-one)]]) -(defhydra org-ref-export (:color red) - " -_C-n_: natmove % -15`org-ref/natmove _C-c_: citeproc % -15`org-ref/citeproc^^^ _C-r_: refproc % -15`org-ref/refproc^^^ -_C-a_: acrossproc % -15`org-ref/acrossproc _C-i_: idxproc % -15`org-ref/idxproc^^^ _C-b_: bblproc % -15`org-ref/bblproc^^^ -" - ("C-n" (org-ref/natmove) nil) - ("C-c" (org-ref/citeproc) nil) - ("C-r" (org-ref/refproc) nil) - ("C-a" (org-ref/acrossproc) nil) - ("C-i" (org-ref/idxproc) nil) - ("C-b" (org-ref/bblproc) nil) +(define-obsolete-function-alias 'org-ref-export/body + #'org-ref-export-menu "3.1") - ("e" org-ref-export-from-hydra "Export" :color blue) - ("q" nil "quit")) +(define-obsolete-function-alias 'org-ref-export-from-hydra + #'org-ref-export-dispatch "3.1") (provide 'org-ref-export) diff --git a/lisp/org-ref/org-ref-extract.el b/lisp/org-ref/org-ref-extract.el index bbd01f1f..50ac2064 100644 --- a/lisp/org-ref/org-ref-extract.el +++ b/lisp/org-ref/org-ref-extract.el @@ -22,6 +22,11 @@ ;; +;;; Code: + +(declare-function org-ref-clean-bibtex-entry "org-ref-bibtex" ()) +(declare-function xml-substitute-special "xml" ()) + (defun org-ref--extract (html-buffer rx num) "Return content matched within HTML-BUFFER by RX at parenthesized @@ -104,9 +109,12 @@ and return the buffer." (interactive "MOpenReview ID: ") (let* ((url (concat "https://openreview.net/forum?id=" id)) (html-buffer (org-ref--html-buffer url))) + (with-current-buffer html-buffer + (replace-string-in-region "\\\\n" "\\n" (point-min) (point-max))) (org-ref--extract-entry-from-html html-buffer - '("\"_bibtex\":\"\\(@.+?}\\)\"" . 1) + '("\\\\\"_bibtex\\\\\":\\({\\\\\"value\\\\\":\\)?\\\\\"\\(@.+?}\\)\\\\\"" + . 2) (replace-regexp-in-string "forum" "pdf" url) '("abstract" . ("" . 1)) @@ -117,7 +125,7 @@ and return the buffer." ("\\(Summary\\|TL;DR\\).*?\"note-content-value\">\\(.+?\\)" . 2)) ;; Should we proactively download supplementary materials too? (cons "supp" - (if-let ((supp (org-ref--extract + (if-let* ((supp (org-ref--extract html-buffer ">Supplementary Material<.*?href=\"\\([^\"]+\\)" 1))) (concat "https://openreview.net" supp)))))) @@ -160,7 +168,7 @@ and return the buffer." '("abstract" . ("

Abstract

[ \n]*?\\(

\\)+\\(.+?\\)

" . 2)) ;; Should we proactively download supplementary materials too? (cons "supp" - (if-let + (if-let* ((supp (org-ref--extract html-buffer "href=[\"']\\([^\"']+-Supplemental[^\"']*\\)[\"']" 1))) diff --git a/lisp/org-ref/org-ref-glossary.el b/lisp/org-ref/org-ref-glossary.el index aea320a3..7bcc4992 100644 --- a/lisp/org-ref/org-ref-glossary.el +++ b/lisp/org-ref/org-ref-glossary.el @@ -86,6 +86,7 @@ (require 'org-element) (require 'org-ref-utils) (require 'ox) +(require 'org-ref-ref-links) ; For multi-file support utilities ;;; Code: (defgroup org-ref-glossary nil @@ -102,6 +103,28 @@ This is not always fast, so we provide a way to disable it." :group 'org-ref-glossary) +(defcustom org-ref-glossary-show-tooltips t + "If non-nil, show tooltips when hovering over glossary and acronym links. +When nil, tooltips are disabled entirely for glossary links, which can +improve responsiveness if you find the tooltips distracting or slow. + +This is separate from `org-ref-activate-glossary-links' which controls +whether links are fontified and clickable." + :type 'boolean + :group 'org-ref-glossary) + + +(defcustom org-ref-glossary-enable-multi-file t + "Enable scanning #+INCLUDE'd files for glossary/acronym definitions. +When non-nil, glossary and acronym lookups will search in files included +via #+INCLUDE directives, enabling multi-file document support. + +Uses timestamp-based caching to maintain performance. Only files that have +changed since the last scan are re-parsed." + :type 'boolean + :group 'org-ref-glossary) + + (defcustom org-ref-glsentries '() "Variable to hold locations of glsentries load files.") @@ -114,6 +137,18 @@ This is not always fast, so we provide a way to disable it." "Buffer-local variable for acronym entry cache.") +(defvar org-ref-glossary-file-cache (make-hash-table :test 'equal) + "Global cache of glossary entries per file. +Maps file paths to lists of glossary entry plists. +Used when `org-ref-glossary-enable-multi-file' is non-nil.") + + +(defvar org-ref-acronym-file-cache (make-hash-table :test 'equal) + "Global cache of acronym entries per file. +Maps file paths to lists of acronym entry plists. +Used when `org-ref-glossary-enable-multi-file' is non-nil.") + + (defun or-find-closing-curly-bracket (&optional limit) "Find closing bracket for the bracket at point and move point to it. Go up to LIMIT or `point-max'. This is a parsing function. I @@ -160,12 +195,12 @@ changes." (let* (end-of-entry data (external (when (re-search-forward - "\\loadglsentries\\(\\[.*\\]\\){\\(?1:.*\\)}" nil t) + "\\\\loadglsentries\\(\\[.*\\]\\)?{\\(?1:.*\\)}" nil t) (match-string 1))) (glsentries (and external (or (cdr (assoc external org-ref-glsentries)) (progn - (cl-pushnew (cons external (s-trim + (cl-pushnew (cons external (string-trim (shell-command-to-string (format "kpsewhich tex %s" external)))) @@ -268,6 +303,131 @@ changes." data)))) +;;** Multi-file glossary support + +(defun or-scan-file-for-glossary-table (file) + "Scan FILE and return list of glossary entries from #+name: glossary table. +Returns list of plists with :label, :name, :description, :file. +Returns nil if no glossary table is found in FILE." + (when (file-exists-p file) + (with-temp-buffer + (insert-file-contents file) + (org-mode) + (let ((entries '())) + (or (catch 'found + (org-element-map + (org-element-parse-buffer) + 'table + (lambda (el) + (when (string= "glossary" (org-element-property :name el)) + (goto-char (org-element-property :contents-begin el)) + (let ((table-data (nthcdr 2 (org-babel-read-table)))) + ;; Convert table rows to plists + (dolist (row table-data) + (when (and (listp row) (= 3 (length row))) + (push (list :label (nth 0 row) + :name (nth 1 row) + :description (nth 2 row) + :file file) + entries))) + (throw 'found (nreverse entries))))))) + entries))))) + + +(defun or-scan-file-for-acronym-table (file) + "Scan FILE and return list of acronym entries from #+name: acronyms table. +Returns list of plists with :label, :abbrv, :full, :file. +Returns nil if no acronym table is found in FILE." + (when (file-exists-p file) + (with-temp-buffer + (insert-file-contents file) + (org-mode) + (let ((entries '())) + (or (catch 'found + (org-element-map + (org-element-parse-buffer) + 'table + (lambda (el) + (when (string= "acronyms" (org-element-property :name el)) + (goto-char (org-element-property :contents-begin el)) + (let ((table-data (nthcdr 2 (org-babel-read-table)))) + ;; Convert table rows to plists + (dolist (row table-data) + (when (and (listp row) (= 3 (length row))) + (push (list :label (nth 0 row) + :abbrv (nth 1 row) + :full (nth 2 row) + :file file) + entries))) + (throw 'found (nreverse entries))))))) + entries))))) + + +(defun or-parse-glossary-entry-multi-file (label) + "Find glossary LABEL in current file or included files. +Uses timestamp-based caching to avoid re-scanning unchanged files. +Returns plist with :label, :name, :description, :file." + (when (and (boundp 'org-ref-glossary-enable-multi-file) + org-ref-glossary-enable-multi-file + (boundp 'org-ref-glossary-file-cache) + (buffer-file-name)) + (cl-block or-parse-glossary-entry-multi-file + (let* ((current-file (buffer-file-name)) + (included-files (org-ref-get-included-files)) + (all-files (cons current-file included-files))) + + ;; Scan each file (with caching) + (dolist (file all-files) + ;; Scan if file changed OR if not in cache yet + (when (or (org-ref-file-changed-p file) + (not (gethash file org-ref-glossary-file-cache))) + ;; File changed or not cached, scan it + (let ((file-entries (or-scan-file-for-glossary-table file))) + (puthash file file-entries org-ref-glossary-file-cache) + (org-ref-mark-file-scanned file))) + + ;; Look for label in this file's cache + (let ((entries (gethash file org-ref-glossary-file-cache))) + (when entries + (let ((entry (cl-find label entries + :key (lambda (e) (plist-get e :label)) + :test 'string=))) + (when entry + (cl-return-from or-parse-glossary-entry-multi-file entry)))))))))) + + +(defun or-parse-acronym-entry-multi-file (label) + "Find acronym LABEL in current file or included files. +Uses timestamp-based caching to avoid re-scanning unchanged files. +Returns plist with :label, :abbrv, :full, :file." + (when (and (boundp 'org-ref-glossary-enable-multi-file) + org-ref-glossary-enable-multi-file + (boundp 'org-ref-acronym-file-cache) + (buffer-file-name)) + (cl-block or-parse-acronym-entry-multi-file + (let* ((current-file (buffer-file-name)) + (included-files (org-ref-get-included-files)) + (all-files (cons current-file included-files))) + + ;; Scan each file (with caching) + (dolist (file all-files) + ;; Scan if file changed OR if not in cache yet + (when (or (org-ref-file-changed-p file) + (not (gethash file org-ref-acronym-file-cache))) + ;; File changed or not cached, scan it + (let ((file-entries (or-scan-file-for-acronym-table file))) + (puthash file file-entries org-ref-acronym-file-cache) + (org-ref-mark-file-scanned file))) + + ;; Look for label in this file's cache + (let ((entries (gethash file org-ref-acronym-file-cache))) + (when entries + (let ((entry (cl-find label entries + :key (lambda (e) (plist-get e :label)) + :test 'string=))) + (when entry + (cl-return-from or-parse-acronym-entry-multi-file entry)))))))))) + ;;** Glossary links @@ -276,13 +436,22 @@ changes." set data on text with properties Set face property, and help-echo." (let ((data (or (or-parse-glossary-entry path) - (or-parse-acronym-entry path)))) + (or-parse-acronym-entry path) + ;; Try multi-file lookup if enabled and not found in current buffer + (or-parse-glossary-entry-multi-file path) + (or-parse-acronym-entry-multi-file path)))) (add-text-properties start end (list 'or-glossary data 'face (if data 'org-ref-glossary-face - 'font-lock-warning-face))))) + 'font-lock-warning-face) + ;; Suppress spell-checking with nospell property. + ;; For jinx users: add 'nospell to jinx-exclude-properties: + ;; (setq jinx-exclude-properties '((org-mode read-only nospell))) + ;; Or exclude by face using jinx-exclude-faces: + ;; (add-to-list 'jinx-exclude-faces 'org-ref-glossary-face) + 'nospell t)))) (defface org-ref-glossary-face `((t (:inherit org-link :foreground "Mediumpurple3"))) @@ -290,28 +459,53 @@ Set face property, and help-echo." (defun or-follow-glossary (entry) - "Goto beginning of the glossary ENTRY." + "Goto beginning of the glossary ENTRY. +If entry is in an included file, opens that file and navigates to the glossary table." (org-mark-ring-push) - (goto-char (plist-get (get-text-property (point) 'or-glossary) :position))) + (let* ((data (get-text-property (point) 'or-glossary)) + (file (plist-get data :file)) + (label (plist-get data :label)) + (position (plist-get data :position))) + (cond + ;; Entry in current buffer (has position) + (position + (goto-char position)) + ;; Entry in external file + (file + (find-file file) + (goto-char (point-min)) + (when (re-search-forward "^[ \t]*#\\+name:[ \t]+\\(glossary\\|acronyms\\)" nil t) + (when (re-search-forward (regexp-quote label) nil t) + (goto-char (line-beginning-position))))) + ;; Fallback: search in current buffer + (t + (goto-char (point-min)) + (when (re-search-forward "^[ \t]*#\\+name:[ \t]+\\(glossary\\|acronyms\\)" nil t) + (when (re-search-forward (regexp-quote label) nil t) + (goto-char (line-beginning-position)))))))) (defun or-glossary-tooltip (_window buffer position) "Return tooltip for the glossary entry. The entry is in WINDOW and OBJECT at POSITION. -Used in fontification." - (with-current-buffer buffer - (let* ((data (get-text-property position 'or-glossary)) - (name (or (plist-get data :name) - (plist-get data :abbrv))) - (description (or (plist-get data :description) - (plist-get data :full)))) - (format - "%s: %s" - name - (with-temp-buffer - (insert (concat description ".")) - (fill-paragraph) - (buffer-string)))))) +Used in fontification." + (when org-ref-glossary-show-tooltips + (with-current-buffer buffer + (let ((data (get-text-property position 'or-glossary))) + (if data + (let ((name (or (plist-get data :name) + (plist-get data :abbrv))) + (description (or (plist-get data :description) + (plist-get data :full)))) + (when (and name description) + (format "%s: %s" + name + (with-temp-buffer + (insert (concat description ".")) + (fill-paragraph) + (buffer-string))))) + ;; No data found - return helpful message or nil + "This is not defined in this file."))))) (defvar org-ref-glossary-gls-commands @@ -431,13 +625,13 @@ The plist maps to \newacronym{