1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885
|
;;; compat-27.el --- Functionality added in Emacs 27.1 -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2025 Free Software Foundation, Inc.
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; Functionality added in Emacs 27.1, needed by older Emacs versions.
;;; Code:
(eval-when-compile (load "compat-macs.el" nil t t))
(compat-require compat-26 "26.1")
(compat-version "27.1")
;;;; Defined in fns.c
(compat-defun proper-list-p (object) ;; <compat-tests:proper-list-p>
"Return OBJECT's length if it is a proper list, nil otherwise.
A proper list is neither circular nor dotted (i.e., its last cdr
is nil)."
(if (eval-when-compile (< emacs-major-version 26))
;; On older Emacs than 26.1 use Tortoise and Hare algorithm
(when (listp object)
(catch 'cycle
(let ((hare object) (tortoise object)
(max 2) (q 2))
(while (consp hare)
(setq hare (cdr hare))
(when (and (or (/= 0 (setq q (1- q)))
(ignore
(setq max (ash max 1)
q max
tortoise hare)))
(eq hare tortoise))
(throw 'cycle nil)))
(and (null hare) (length object)))))
;; Errors on 26.1 and newer
(and (listp object) (ignore-errors (length object)))))
(compat-defun string-distance (string1 string2 &optional bytecompare) ;; <compat-tests:string-distance>
"Return Levenshtein distance between STRING1 and STRING2.
The distance is the number of deletions, insertions, and substitutions
required to transform STRING1 into STRING2.
If BYTECOMPARE is nil or omitted, compute distance in terms of characters.
If BYTECOMPARE is non-nil, compute distance in terms of bytes.
Letter-case is significant, but text properties are ignored."
;; https://en.wikipedia.org/wiki/Levenshtein_distance
(let ((s1 (if bytecompare
(encode-coding-string string1 'raw-text)
(concat string1 "")))
(s2 (if bytecompare
(encode-coding-string string2 'raw-text)
string2)))
(let* ((len1 (length s1))
(len2 (length s2))
(column (make-vector (1+ len1) 0)))
(dotimes (y len1)
(setf (aref column (1+ y)) y))
(dotimes (x len2)
(setf (aref column 0) (1+ x))
(let ((lastdiag x) olddiag)
(dotimes (y len1)
(setf olddiag (aref column (1+ y))
(aref column (1+ y))
(min (+ (if (= (aref s1 y) (aref s2 x)) 0 1)
lastdiag)
(1+ (aref column (1+ y)))
(1+ (aref column y)))
lastdiag olddiag))))
(aref column len1))))
;;;; Defined in window.c
(compat-defun recenter (&optional arg redisplay) ;; <compat-tests:recenter>
"Handle optional argument REDISPLAY."
:extended t
(recenter arg)
(when (and redisplay recenter-redisplay)
(redisplay)))
;;;; Defined in keymap.c
(compat-defun lookup-key (keymap key &optional accept-default) ;; <compat-tests:lookup-key>
"Allow for KEYMAP to be a list of keymaps."
:extended t
(cond
((keymapp keymap)
(lookup-key keymap key accept-default))
((listp keymap)
(catch 'found
(dolist (map keymap)
(when-let ((fn (lookup-key map key accept-default)))
(throw 'found fn)))))
((signal 'wrong-type-argument (list 'keymapp keymap)))))
;;;; Defined in timefns.c
(compat-defun time-equal-p (t1 t2) ;; <compat-tests:time-equal-p>
"Return non-nil if time value T1 is equal to time value T2.
A nil value for either argument stands for the current time.
NOTE: This function is not as accurate as the actual `time-equal-p'."
(cond
((eq t1 t2))
((and (consp t1) (consp t2))
(equal t1 t2))
(t
;; Due to inaccuracies and the relatively slow evaluating of
;; Emacs Lisp compared to C, we allow for slight inaccuracies
;; (less than a millisecond) when comparing time values.
(< (abs (- (float-time t1) (float-time t2)))
(if (and t1 t2) 1e-6 1e-5)))))
;;;; Defined in subr.el
(compat-defalias fixnump integerp) ;; <compat-tests:fixnump>
(compat-defalias bignump ignore) ;; <compat-tests:bignump>
(compat-defmacro setq-local (&rest pairs) ;; <compat-tests:setq-local>
"Handle multiple assignments."
:extended t
(unless (zerop (mod (length pairs) 2))
(error "PAIRS must have an even number of variable/value members"))
(let (body)
(while pairs
(let* ((sym (pop pairs))
(val (pop pairs)))
(unless (symbolp sym)
(error "Attempting to set a non-symbol: %s" (car pairs)))
(push `(set (make-local-variable ',sym) ,val)
body)))
(cons 'progn (nreverse body))))
(compat-defmacro ignore-error (condition &rest body) ;; <compat-tests:ignore-error>
"Execute BODY; if the error CONDITION occurs, return nil.
Otherwise, return result of last form in BODY.
CONDITION can also be a list of error conditions."
(declare (debug t) (indent 1))
`(condition-case nil (progn ,@body) (,condition nil)))
(compat-defmacro dolist-with-progress-reporter (spec reporter-or-message &rest body) ;; <compat-tests:dolist-with-progress-reporter>
"Loop over a list and report progress in the echo area.
Evaluate BODY with VAR bound to each car from LIST, in turn.
Then evaluate RESULT to get return value, default nil.
REPORTER-OR-MESSAGE is a progress reporter object or a string. In the latter
case, use this string to create a progress reporter.
At each iteration, print the reporter message followed by progress
percentage in the echo area. After the loop is finished,
print the reporter message followed by the word \"done\".
\(fn (VAR LIST [RESULT]) REPORTER-OR-MESSAGE BODY...)"
(declare (indent 2) (debug ((symbolp form &optional form) form body)))
(let ((prep (make-symbol "--dolist-progress-reporter--"))
(count (make-symbol "--dolist-count--"))
(list (make-symbol "--dolist-list--")))
`(let ((,prep ,reporter-or-message)
(,count 0)
(,list ,(cadr spec)))
(when (stringp ,prep)
(setq ,prep (make-progress-reporter ,prep 0 (length ,list))))
(dolist (,(car spec) ,list)
,@body
(progress-reporter-update ,prep (setq ,count (1+ ,count))))
(progress-reporter-done ,prep)
(or ,@(cdr (cdr spec)) nil))))
(compat-defun flatten-tree (tree) ;; <compat-tests:flatten-tree>
"Return a \"flattened\" copy of TREE.
In other words, return a list of the non-nil terminal nodes, or
leaves, of the tree of cons cells rooted at TREE. Leaves in the
returned list are in the same order as in TREE.
\(flatten-tree \\='(1 (2 . 3) nil (4 5 (6)) 7))
=> (1 2 3 4 5 6 7)"
(let (elems)
(while (consp tree)
(let ((elem (pop tree)))
(while (consp elem)
(push (cdr elem) tree)
(setq elem (car elem)))
(if elem (push elem elems))))
(if tree (push tree elems))
(nreverse elems)))
(compat-defun xor (cond1 cond2) ;; <compat-tests:xor>
"Return the boolean exclusive-or of COND1 and COND2.
If only one of the arguments is non-nil, return it; otherwise
return nil."
(declare (pure t) (side-effect-free error-free))
(cond ((not cond1) cond2)
((not cond2) cond1)))
(compat-defvar regexp-unmatchable "\\`a\\`" ;; <compat-tests:regexp-unmatchable>
"Standard regexp guaranteed not to match any string at all."
:constant t)
(compat-defun assoc-delete-all (key alist &optional test) ;; <compat-tests:assoc-delete-all>
"Handle optional argument TEST."
:extended "26.2"
(unless test (setq test #'equal))
(while (and (consp (car alist))
(funcall test (caar alist) key))
(setq alist (cdr alist)))
(let ((tail alist) tail-cdr)
(while (setq tail-cdr (cdr tail))
(if (and (consp (car tail-cdr))
(funcall test (caar tail-cdr) key))
(setcdr tail (cdr tail-cdr))
(setq tail tail-cdr))))
alist)
(compat-defvar major-mode--suspended nil ;; <compat-tests:major-mode-suspend>
"Suspended major mode."
:local permanent)
(compat-defun major-mode-suspend () ;; <compat-tests:major-mode-suspend>
"Exit current major mode, remembering it."
(let* ((prev-major-mode (or major-mode--suspended
(unless (eq major-mode 'fundamental-mode)
major-mode))))
(kill-all-local-variables)
(setq-local major-mode--suspended prev-major-mode)))
(compat-defun major-mode-restore (&optional avoided-modes) ;; <compat-tests:major-mode-suspend>
"Restore major mode earlier suspended with `major-mode-suspend'.
If there was no earlier suspended major mode, then fallback to `normal-mode',
though trying to avoid AVOIDED-MODES."
(if major-mode--suspended
(funcall (prog1 major-mode--suspended
(kill-local-variable 'major-mode--suspended)))
(let ((auto-mode-alist
(let ((alist (copy-sequence auto-mode-alist)))
(dolist (mode avoided-modes)
(setq alist (rassq-delete-all mode alist)))
alist))
(magic-fallback-mode-alist
(let ((alist (copy-sequence magic-fallback-mode-alist)))
(dolist (mode avoided-modes)
(setq alist (rassq-delete-all mode alist)))
alist)))
(normal-mode))))
(compat-defun read-char-from-minibuffer-insert-char () ;; <compat-tests:read-char-from-minibuffer>
"Insert the character you type into the minibuffer and exit minibuffer.
Discard all previous input before inserting and exiting the minibuffer."
(interactive)
(when (minibufferp)
(delete-minibuffer-contents)
(insert last-command-event)
(exit-minibuffer)))
(compat-defun read-char-from-minibuffer-insert-other () ;; <compat-tests:read-char-from-minibuffer>
"Reject a disallowed character typed into the minibuffer.
This command is intended to be bound to keys that users are not
allowed to type into the minibuffer. When the user types any
such key, this command discard all minibuffer input and displays
an error message."
(interactive)
(when (minibufferp)
(delete-minibuffer-contents)
(ding)
(discard-input)
(minibuffer-message "Wrong answer")
(sit-for 2)))
(compat-defvar read-char-history nil ;; <compat-tests:read-char-from-minibuffer>
"The default history for the `read-char-from-minibuffer' function.")
(compat-defvar read-char-from-minibuffer-map ;; <compat-tests:read-char-from-minibuffer>
(let ((map (make-sparse-keymap)))
(set-keymap-parent map minibuffer-local-map)
(define-key map [remap self-insert-command] #'read-char-from-minibuffer-insert-char)
(define-key map [remap exit-minibuffer] #'read-char-from-minibuffer-insert-other)
map)
"Keymap for the `read-char-from-minibuffer' function.")
(compat-defvar read-char-from-minibuffer-map-hash ;; <compat-tests:read-char-from-minibuffer>
(make-hash-table :test 'equal)
"Hash table of keymaps used by `read-char-from-minibuffer'."
:constant t)
(compat-defun read-char-from-minibuffer (prompt &optional chars history) ;; <compat-tests:read-char-from-minibuffer>
"Read a character from the minibuffer, prompting for it with PROMPT.
Like `read-char', but uses the minibuffer to read and return a character.
Optional argument CHARS, if non-nil, should be a list of characters;
the function will ignore any input that is not one of CHARS.
Optional argument HISTORY, if non-nil, should be a symbol that
specifies the history list variable to use for navigating in input
history using \\`M-p' and \\`M-n', with \\`RET' to select a character from
history.
If you bind the variable `help-form' to a non-nil value
while calling this function, then pressing `help-char'
causes it to evaluate `help-form' and display the result.
There is no need to explicitly add `help-char' to CHARS;
`help-char' is bound automatically to `help-form-show'."
(let* ((map (if (consp chars)
(or (gethash (list help-form (cons help-char chars))
read-char-from-minibuffer-map-hash)
(let ((map (make-sparse-keymap))
(msg help-form))
(set-keymap-parent map read-char-from-minibuffer-map)
;; If we have a dynamically bound `help-form'
;; here, then the `C-h' (i.e., `help-char')
;; character should output that instead of
;; being a command char.
(when help-form
(define-key map (vector help-char)
(lambda ()
(interactive)
(let ((help-form msg)) ; lexically bound msg
(help-form-show)))))
(dolist (char chars)
(define-key map (vector char)
#'read-char-from-minibuffer-insert-char))
(define-key map [remap self-insert-command]
#'read-char-from-minibuffer-insert-other)
(puthash (list help-form (cons help-char chars))
map read-char-from-minibuffer-map-hash)
map))
read-char-from-minibuffer-map))
;; Protect this-command when called from pre-command-hook (bug#45029)
(this-command this-command)
(result (read-from-minibuffer prompt nil map nil (or history t)))
(char
(if (> (length result) 0)
;; We have a string (with one character), so return the first one.
(elt result 0)
;; The default value is RET.
(when history (push "\r" (symbol-value history)))
?\r)))
;; Display the question with the answer.
(message "%s%s" prompt (char-to-string char))
char))
;;;; Defined in simple.el
(compat-guard (not (fboundp 'decoded-time-second)) ;; <compat-tests:decoded-time>
(cl-defstruct (decoded-time
(:constructor nil)
(:copier nil)
(:type list))
(second nil :documentation "\
This is an integer or a Lisp timestamp (TICKS . HZ) representing a nonnegative
number of seconds less than 61. (If not less than 60, it is a leap second,
which only some operating systems support.)")
(minute nil :documentation "This is an integer between 0 and 59 (inclusive).")
(hour nil :documentation "This is an integer between 0 and 23 (inclusive).")
(day nil :documentation "This is an integer between 1 and 31 (inclusive).")
(month nil :documentation "\
This is an integer between 1 and 12 (inclusive). January is 1.")
(year nil :documentation "This is a four digit integer.")
(weekday nil :documentation "\
This is a number between 0 and 6, and 0 is Sunday.")
(dst -1 :documentation "\
This is t if daylight saving time is in effect, nil if it is not
in effect, and -1 if daylight saving information is not available.
Also see `decoded-time-dst'.")
(zone nil :documentation "\
This is an integer indicating the UTC offset in seconds, i.e.,
the number of seconds east of Greenwich.")))
(compat-defun minibuffer-history-value () ;; <compat-tests:minibuffer-history-value>
"Return the value of the minibuffer input history list.
If `minibuffer-history-variable' points to a buffer-local variable and
the minibuffer is active, return the buffer-local value for the buffer
that was current when the minibuffer was activated."
(buffer-local-value minibuffer-history-variable
(window-buffer (minibuffer-selected-window))))
;;;; Defined in minibuffer.el
(compat-defmacro with-minibuffer-selected-window (&rest body) ;; <compat-tests:with-minibuffer-selected-window>
"Execute the forms in BODY from the minibuffer in its original window.
When used in a minibuffer window, select the window selected just before
the minibuffer was activated, and execute the forms."
(declare (indent 0) (debug t))
`(when-let ((window (minibuffer-selected-window)))
(with-selected-window window
,@body)))
;;;; Defined in byte-run.el
(compat-defmacro with-suppressed-warnings (_warnings &rest body) ;; <compat-tests:with-suppressed-warnings>
"Like `progn', but prevents compiler WARNINGS in BODY.
NOTE: The compatibility version behaves like `with-no-warnings'."
`(with-no-warnings ,@body))
;;;; Defined in image.el
(compat-defun image--set-property (image property value) ;; <compat-tests:image-property>
"Set PROPERTY in IMAGE to VALUE, internal use only."
:extended "26.1"
:feature image
(if (null value)
(while (cdr image)
(if (eq (cadr image) property)
(setcdr image (cdddr image))
(setq image (cddr image))))
(setcdr image (plist-put (cdr image) property value)))
value)
;; HACK: image--set-property was broken with an off-by-one error on Emacs 26.
;; The bug was fixed in a4ad7bed187493c1c230f223b52c71f5c34f7c89. Therefore we
;; override the gv expander until Emacs 27.1.
(compat-guard ;; <compat-tests:image-property>
(or (= emacs-major-version 26) (not (get 'image-property 'gv-expander)))
:feature image
(gv-define-setter image-property (value image prop)
`(,(if (< emacs-major-version 26) 'image--set-property 'compat--image--set-property)
,image ,prop ,value)))
;;;; Defined in files.el
(compat-defun file-name-quoted-p (name &optional top) ;; <compat-tests:file-name-quoted-p>
"Handle optional argument TOP."
:extended "26.1"
(let ((file-name-handler-alist (unless top file-name-handler-alist)))
(string-prefix-p "/:" (file-local-name name))))
(compat-defun file-name-quote (name &optional top) ;; <compat-tests:file-name-quote>
"Handle optional argument TOP."
:extended "26.1"
(let* ((file-name-handler-alist (unless top file-name-handler-alist))
(localname (file-local-name name)))
(if (string-prefix-p "/:" localname)
name
(concat (file-remote-p name) "/:" localname))))
(compat-defun file-name-unquote (name &optional top) ;; <compat-tests:file-name-unquote>
"Handle optional argument TOP."
:extended "26.1"
(let* ((file-name-handler-alist (unless top file-name-handler-alist))
(localname (file-local-name name)))
(when (string-prefix-p "/:" localname)
(setq localname (if (= (length localname) 2) "/" (substring localname 2))))
(concat (file-remote-p name) localname)))
(compat-defun file-size-human-readable (file-size &optional flavor space unit) ;; <compat-tests:file-size-human-readable>
"Handle the optional arguments SPACE and UNIT."
:extended t
(let ((power (if (or (null flavor) (eq flavor 'iec))
1024.0
1000.0))
(prefixes '("" "k" "M" "G" "T" "P" "E" "Z" "Y")))
(while (and (>= file-size power) (cdr prefixes))
(setq file-size (/ file-size power)
prefixes (cdr prefixes)))
(let* ((prefix (car prefixes))
(prefixed-unit (if (eq flavor 'iec)
(concat
(if (string= prefix "k") "K" prefix)
(if (string= prefix "") "" "i")
(or unit "B"))
(concat prefix unit))))
(format (if (and (>= (mod file-size 1.0) 0.05)
(< (mod file-size 1.0) 0.95))
"%.1f%s%s"
"%.0f%s%s")
file-size
(if (string= prefixed-unit "") "" (or space ""))
prefixed-unit))))
(compat-defun file-size-human-readable-iec (size) ;; <compat-tests:file-size-human-readable-iec>
"Human-readable string for SIZE bytes, using IEC prefixes."
(compat--file-size-human-readable size 'iec " "))
(compat-defun exec-path () ;; <compat-tests:exec-path>
"Return list of directories to search programs to run in remote subprocesses.
The remote host is identified by `default-directory'. For remote
hosts that do not support subprocesses, this returns nil.
If `default-directory' is a local directory, this function returns
the value of the variable `exec-path'."
(let ((handler (find-file-name-handler default-directory 'exec-path)))
;; NOTE: The handler may fail since it was added in 27.1.
(or (and handler (ignore-errors (funcall handler 'exec-path)))
(if (file-remote-p default-directory)
;; FIXME: Just return some standard path on remote
'("/bin" "/usr/bin" "/sbin" "/usr/sbin" "/usr/local/bin" "/usr/local/sbin")
exec-path))))
(compat-defun executable-find (command &optional remote) ;; <compat-tests:executable-find>
"Handle optional argument REMOTE."
:extended t
(if (and remote (file-remote-p default-directory))
(let ((res (locate-file
command
(mapcar
(apply-partially
#'concat (file-remote-p default-directory))
(exec-path))
exec-suffixes 'file-executable-p)))
(when (stringp res) (file-local-name res)))
(executable-find command)))
(compat-defun make-empty-file (filename &optional parents) ;; <compat-tests:make-empty-file>
"Create an empty file FILENAME.
Optional arg PARENTS, if non-nil then creates parent dirs as needed."
(when (and (file-exists-p filename) (null parents))
(signal 'file-already-exists (list "File exists" filename)))
(let ((paren-dir (file-name-directory filename)))
(when (and paren-dir (not (file-exists-p paren-dir)))
(make-directory paren-dir parents)))
(write-region "" nil filename nil 0))
;;;; Defined in regexp-opt.el
(compat-defun regexp-opt (strings &optional paren) ;; <compat-tests:regexp-opt>
"Handle an empty list of STRINGS."
:extended t
(if (null strings)
(let ((re "\\`a\\`"))
(cond ((null paren)
(concat "\\(?:" re "\\)"))
((stringp paren)
(concat paren re "\\)"))
((eq paren 'words)
(concat "\\<\\(" re "\\)\\>"))
((eq paren 'symbols)
(concat "\\_\\(<" re "\\)\\_>"))
((concat "\\(" re "\\)"))))
(regexp-opt strings paren)))
;;;; Defined in package.el
(declare-function lm-header "lisp-mnt")
(declare-function macroexp-file-name nil)
(compat-defun package-get-version () ;; <compat-tests:package-get-version>
"Return the version number of the package in which this is used.
Assumes it is used from an Elisp file placed inside the top-level directory
of an installed ELPA package.
The return value is a string (or nil in case we can’t find it)."
;; No :feature since the function is autoloaded.
;; In a sense, this is a lie, but it does just what we want: precompute
;; the version at compile time and hardcodes it into the .elc file!
(declare (pure t))
;; Hack alert!
(let ((file (or (macroexp-file-name) buffer-file-name)))
(cond
((null file) nil)
;; Packages are normally installed into directories named "<pkg>-<vers>",
;; so get the version number from there.
((string-match
"/[^/]+-\\([0-9]\\(?:[0-9.]\\|pre\\|beta\\|alpha\\|snapshot\\)+\\)/[^/]+\\'"
file)
(match-string 1 file))
;; For packages run straight from the an elpa.git clone, there's no
;; "-<vers>" in the directory name, so we have to fetch the version
;; the hard way.
((let* ((pkgdir (file-name-directory file))
(pkgname (file-name-nondirectory (directory-file-name pkgdir)))
(mainfile (expand-file-name (concat pkgname ".el") pkgdir)))
(when (file-readable-p mainfile)
(require 'lisp-mnt)
(with-temp-buffer
(insert-file-contents mainfile)
(or (lm-header "package-version")
(lm-header "version")))))))))
;;;; Defined in time-date.el
(compat-defun make-decoded-time ;; <compat-tests:make-decoded-time>
(&key second minute hour day month year (dst -1) zone)
"Return a `decoded-time' structure with only the keywords given filled out."
:feature time-date
(list second minute hour day month year nil dst zone))
(compat-defun date-days-in-month (year month) ;; <compat-tests:date-days-in-month>
"The number of days in MONTH in YEAR."
:feature time-date
(unless (and (numberp month)
(<= 1 month)
(<= month 12))
(error "Month %s is invalid" month))
(if (= month 2)
(if (date-leap-year-p year)
29
28)
(if (memq month '(1 3 5 7 8 10 12))
31
30)))
(compat-defun date-ordinal-to-time (year ordinal) ;; <compat-tests:date-ordinal-to-time>
"Convert a YEAR/ORDINAL to the equivalent `decoded-time' structure.
ORDINAL is the number of days since the start of the year, with
January 1st being 1."
(let ((month 1))
(while (> ordinal (date-days-in-month year month))
(setq ordinal (- ordinal (date-days-in-month year month))
month (1+ month)))
(list nil nil nil ordinal month year nil nil nil)))
;;;; Defined in text-property-search.el
(declare-function make-prop-match nil)
(compat-guard (not (fboundp 'make-prop-match)) ;; <compat-tests:prop-match>
(cl-defstruct (prop-match) beginning end value))
(compat-defun text-property-search-forward ;; <compat-tests:text-property-search-forward>
(property &optional value predicate not-current)
"Search for the next region of text where PREDICATE is true.
PREDICATE is used to decide whether a value of PROPERTY should be
considered as matching VALUE.
If PREDICATE is a function, it will be called with two arguments:
VALUE and the value of PROPERTY. The function should return
non-nil if these two values are to be considered a match.
Two special values of PREDICATE can also be used:
If PREDICATE is t, that means a value must `equal' VALUE to be
considered a match.
If PREDICATE is nil (which is the default value), a value will
match if is not `equal' to VALUE. Furthermore, a nil PREDICATE
means that the match region is ended if the value changes. For
instance, this means that if you loop with
(while (setq prop (text-property-search-forward \\='face))
...)
you will get all distinct regions with non-nil `face' values in
the buffer, and the `prop' object will have the details about the
match. See the manual for more details and examples about how
VALUE and PREDICATE interact.
If NOT-CURRENT is non-nil, the function will search for the first
region that doesn't include point and has a value of PROPERTY
that matches VALUE.
If no matches can be found, return nil and don't move point.
If found, move point to the end of the region and return a
`prop-match' object describing the match. To access the details
of the match, use `prop-match-beginning' and `prop-match-end' for
the buffer positions that limit the region, and
`prop-match-value' for the value of PROPERTY in the region."
(let* ((match-p
(lambda (prop-value)
(funcall
(cond
((eq predicate t)
#'equal)
((eq predicate nil)
(lambda (val p-val)
(not (equal val p-val))))
(predicate))
value prop-value)))
(find-end
(lambda (start)
(let (end)
(if (and value
(null predicate))
;; This is the normal case: We're looking for areas where the
;; values aren't, so we aren't interested in sub-areas where the
;; property has different values, all non-matching value.
(let ((ended nil))
(while (not ended)
(setq end (next-single-property-change (point) property))
(if (not end)
(progn
(goto-char (point-max))
(setq end (point)
ended t))
(goto-char end)
(unless (funcall match-p (get-text-property (point) property))
(setq ended t)))))
;; End this at the first place the property changes value.
(setq end (next-single-property-change (point) property nil (point-max)))
(goto-char end))
(make-prop-match
:beginning start
:end end
:value (get-text-property start property))))))
(cond
;; No matches at the end of the buffer.
((eobp)
nil)
;; We're standing in the property we're looking for, so find the
;; end.
((and (funcall match-p (get-text-property (point) property))
(not not-current))
(funcall find-end (point)))
(t
(let ((origin (point))
(ended nil)
pos)
;; Find the next candidate.
(while (not ended)
(setq pos (next-single-property-change (point) property))
(if (not pos)
(progn
(goto-char origin)
(setq ended t))
(goto-char pos)
(if (funcall match-p (get-text-property (point) property))
(setq ended (funcall find-end (point)))
;; Skip past this section of non-matches.
(setq pos (next-single-property-change (point) property))
(unless pos
(goto-char origin)
(setq ended t)))))
(and (not (eq ended t))
ended))))))
(compat-defun text-property-search-backward ;; <compat-tests:text-property-search-backward>
(property &optional value predicate not-current)
"Search for the previous region of text whose PROPERTY matches VALUE.
Like `text-property-search-forward', which see, but searches backward,
and if a matching region is found, place point at the start of the region."
(let* ((match-p
(lambda (prop-value)
(funcall
(cond
((eq predicate t)
#'equal)
((eq predicate nil)
(lambda (val p-val)
(not (equal val p-val))))
(predicate))
value prop-value)))
(find-end
(lambda (start)
(let (end)
(if (and value
(null predicate))
;; This is the normal case: We're looking for areas where the
;; values aren't, so we aren't interested in sub-areas where the
;; property has different values, all non-matching value.
(let ((ended nil))
(while (not ended)
(setq end (previous-single-property-change (point) property))
(if (not end)
(progn
(goto-char (point-min))
(setq end (point)
ended t))
(goto-char (1- end))
(unless (funcall match-p (get-text-property (point) property))
(goto-char end)
(setq ended t)))))
;; End this at the first place the property changes value.
(setq end (previous-single-property-change
(point) property nil (point-min)))
(goto-char end))
(make-prop-match
:beginning end
:end (1+ start)
:value (get-text-property end property))))))
(cond
;; We're at the start of the buffer; no previous matches.
((bobp)
nil)
;; We're standing in the property we're looking for, so find the
;; end.
((funcall match-p (get-text-property (1- (point)) property))
(let ((origin (point))
(match (funcall find-end (1- (point)) property value predicate)))
;; When we want to ignore the current element, then repeat the
;; search if we haven't moved out of it yet.
(if (and not-current
(equal (get-text-property (point) property)
(get-text-property origin property)))
(text-property-search-backward property value predicate)
match)))
(t
(let ((origin (point))
(ended nil)
pos)
;; Find the previous candidate.
(while (not ended)
(setq pos (previous-single-property-change (point) property))
(if (not pos)
(progn
(goto-char origin)
(setq ended t))
(goto-char (1- pos))
(if (funcall match-p (get-text-property (point) property))
(setq ended
(funcall find-end (point)))
;; Skip past this section of non-matches.
(setq pos (previous-single-property-change (point) property))
(unless pos
(goto-char origin)
(setq ended t)))))
(and (not (eq ended t))
ended))))))
;;;; Defined in ring.el
(compat-defun ring-resize (ring size) ;; <compat-tests:ring-resize>
"Set the size of RING to SIZE.
If the new size is smaller, then the oldest items in the ring are
discarded."
:feature ring
(when (integerp size)
(let ((length (ring-length ring))
(new-vec (make-vector size nil)))
(if (= length 0)
(setcdr ring (cons 0 new-vec))
(let* ((hd (car ring))
(old-size (ring-size ring))
(old-vec (cddr ring))
(copy-length (min size length))
(copy-hd (mod (+ hd (- length copy-length)) length)))
(setcdr ring (cons copy-length new-vec))
;; If the ring is wrapped, the existing elements must be written
;; out in the right order.
(dotimes (j copy-length)
(aset new-vec j (aref old-vec (mod (+ copy-hd j) old-size))))
(setcar ring 0))))))
;;;; Defined in map-ynp.el
(compat-version "26.2")
(compat-defvar read-answer-short 'auto ;; <compat-tests:read-answer>
"If non-nil, the `read-answer' function accepts single-character answers.
If t, accept short (single key-press) answers to the question.
If nil, require long answers. If `auto', accept short answers if
`use-short-answers' is non-nil, or the function cell of `yes-or-no-p'
is set to `y-or-n-p'.
Note that this variable does not affect calls to the more
commonly-used `yes-or-no-p' function; it only affects calls to
the `read-answer' function. To control whether `yes-or-no-p'
requires a long or a short answer, see the `use-short-answers'
variable.")
(compat-defun read-answer (question answers) ;; <compat-tests:read-answer>
"Read an answer either as a complete word or its character abbreviation.
Ask user a question and accept an answer from the list of possible answers.
QUESTION should end in a space; this function adds a list of answers to it.
ANSWERS is an alist with elements in the following format:
(LONG-ANSWER SHORT-ANSWER HELP-MESSAGE)
where
LONG-ANSWER is a complete answer,
SHORT-ANSWER is an abbreviated one-character answer,
HELP-MESSAGE is a string describing the meaning of the answer.
SHORT-ANSWER is not necessarily a single character answer. It can be
also a function key like F1, a character event such as C-M-h, or
a control character like C-h.
Example:
\\='((\"yes\" ?y \"perform the action\")
(\"no\" ?n \"skip to the next\")
(\"all\" ?! \"accept all remaining without more questions\")
(\"help\" ?h \"show help\")
(\"quit\" ?q \"exit\"))
When `read-answer-short' is non-nil, accept short answers.
Return a long answer even in case of accepting short ones.
When `use-dialog-box' is t, pop up a dialog window to get user input."
;; NOTE: For simplicity we provide a primitive implementation based on
;; `read-multiple-choice', which does neither support long answers nor the the
;; gui dialog box.
(cadr (read-multiple-choice
(string-trim-right question)
(delq nil
(mapcar (lambda (x) (unless (equal "help" (car x))
(list (cadr x) (car x) (caddr x))))
answers)))))
(provide 'compat-27)
;;; compat-27.el ends here
|