dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

compat-29.el (67275B)


      1 ;;; compat-29.el --- Functionality added in Emacs 29.1 -*- lexical-binding: t; -*-
      2 
      3 ;; Copyright (C) 2021-2024 Free Software Foundation, Inc.
      4 
      5 ;; This program is free software; you can redistribute it and/or modify
      6 ;; it under the terms of the GNU General Public License as published by
      7 ;; the Free Software Foundation, either version 3 of the License, or
      8 ;; (at your option) any later version.
      9 
     10 ;; This program is distributed in the hope that it will be useful,
     11 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
     12 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13 ;; GNU General Public License for more details.
     14 
     15 ;; You should have received a copy of the GNU General Public License
     16 ;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
     17 
     18 ;;; Commentary:
     19 
     20 ;; Functionality added in Emacs 29.1, needed by older Emacs versions.
     21 
     22 ;;; Code:
     23 
     24 (eval-when-compile (load "compat-macs.el" nil t t))
     25 (compat-require compat-28 "28.1")
     26 
     27 ;; Preloaded in loadup.el
     28 (compat-require seq "29.1") ;; <compat-tests:seq>
     29 
     30 (compat-version "29.1")
     31 
     32 ;;;; Defined in startup.el
     33 
     34 (compat-defvar lisp-directory ;; <compat-tests:lisp-directory>
     35     (file-truename
     36      (file-name-directory
     37       (locate-file "simple" load-path (get-load-suffixes))))
     38   "Directory where Emacs's own *.el and *.elc Lisp files are installed.")
     39 
     40 ;;;; Defined in window.c
     41 
     42 (compat-defalias window-configuration-equal-p compare-window-configurations) ;; <compat-tests:window-configuration-equal-p>
     43 
     44 ;;;; Defined in xdisp.c
     45 
     46 (compat-defun get-display-property (position prop &optional object properties) ;; <compat-tests:get-display-property>
     47   "Get the value of the `display' property PROP at POSITION.
     48 If OBJECT, this should be a buffer or string where the property is
     49 fetched from.  If omitted, OBJECT defaults to the current buffer.
     50 
     51 If PROPERTIES, look for value of PROP in PROPERTIES instead of
     52 the properties at POSITION."
     53   (if properties
     54       (unless (listp properties)
     55         (signal 'wrong-type-argument (list 'listp properties)))
     56     (setq properties (get-text-property position 'display object)))
     57   (cond
     58    ((vectorp properties)
     59     (catch 'found
     60       (dotimes (i (length properties))
     61         (let ((ent (aref properties i)))
     62           (when (eq (car ent) prop)
     63             (throw 'found (cadr ent )))))))
     64    ((consp (car properties))
     65     (condition-case nil
     66         (cadr (assq prop properties))
     67       ;; Silently handle improper lists:
     68       (wrong-type-argument nil)))
     69    ((and (consp (cdr properties))
     70          (eq (car properties) prop))
     71     (cadr properties))))
     72 
     73 ;;;; Defined in fns.c
     74 
     75 (compat-defun ntake (n list) ;; <compat-tests:ntake>
     76   "Modify LIST to keep only the first N elements.
     77 If N is zero or negative, return nil.
     78 If N is greater or equal to the length of LIST, return LIST unmodified.
     79 Otherwise, return LIST after truncating it."
     80   (and (> n 0) (let ((cons (nthcdr (1- n) list)))
     81                  (when cons (setcdr cons nil))
     82                  list)))
     83 
     84 (compat-defun take (n list) ;; <compat-tests:take>
     85   "Return the first N elements of LIST.
     86 If N is zero or negative, return nil.
     87 If N is greater or equal to the length of LIST, return LIST (or a copy)."
     88   (declare (pure t) (side-effect-free t))
     89   (let (copy)
     90     (while (and (< 0 n) list)
     91       (push (pop list) copy)
     92       (setq n (1- n)))
     93     (nreverse copy)))
     94 
     95 (compat-defun string-equal-ignore-case (string1 string2) ;; <compat-tests:string-equal-ignore-case>
     96   "Like `string-equal', but case-insensitive.
     97 Upper-case and lower-case letters are treated as equal.
     98 Unibyte strings are converted to multibyte for comparison."
     99   (declare (pure t) (side-effect-free t))
    100   (eq t (compare-strings string1 0 nil string2 0 nil t)))
    101 
    102 (compat-defun plist-get (plist prop &optional predicate) ;; <compat-tests:plist-get>
    103   "Handle optional argument PREDICATE."
    104   :extended t
    105   (pcase predicate
    106     ((or `nil `eq) (plist-get plist prop))
    107     (`equal (lax-plist-get plist prop))
    108     (_ (catch 'found
    109          (while (consp plist)
    110            (when (funcall predicate prop (car plist))
    111              (throw 'found (cadr plist)))
    112            (setq plist (cddr plist)))))))
    113 
    114 (compat-defun plist-put (plist prop val &optional predicate) ;; <compat-tests:plist-get>
    115   "Handle optional argument PREDICATE."
    116   :extended t
    117   (pcase predicate
    118     ((or `nil `eq) (plist-put plist prop val))
    119     (`equal (lax-plist-put plist prop val))
    120     (_ (catch 'found
    121          (let ((tail plist))
    122            (while (consp tail)
    123              (when (funcall predicate prop (car tail))
    124                (setcar (cdr tail) val)
    125                (throw 'found plist))
    126              (setq tail (cddr tail))))
    127          (nconc plist (list prop val))))))
    128 
    129 (compat-defun plist-member (plist prop &optional predicate) ;; <compat-tests:plist-get>
    130   "Handle optional argument PREDICATE."
    131   :extended t
    132   (pcase predicate
    133     ((or `nil `eq) (plist-member plist prop))
    134     (_ (catch 'found
    135          (while (consp plist)
    136            (when (funcall predicate prop (car plist))
    137              (throw 'found plist))
    138            (setq plist (cddr plist)))))))
    139 
    140 ;;;; Defined in gv.el
    141 
    142 (compat-guard t ;; <compat-tests:plist-get-gv>
    143   (gv-define-expander compat--plist-get
    144     (lambda (do plist prop &optional predicate)
    145       (macroexp-let2 macroexp-copyable-p key prop
    146         (gv-letplace (getter setter) plist
    147           (macroexp-let2 nil p `(cdr (compat--plist-member ,getter ,key ,predicate))
    148             (funcall do
    149                      `(car ,p)
    150                      (lambda (val)
    151                        `(if ,p
    152                             (setcar ,p ,val)
    153                           ,(funcall setter
    154                                     `(cons ,key (cons ,val ,getter)))))))))))
    155   (unless (get 'plist-get 'gv-expander)
    156     (put 'plist-get 'gv-expander (get 'compat--plist-get 'gv-expander))))
    157 
    158 ;;;; Defined in editfns.c
    159 
    160 (compat-defun pos-bol (&optional n) ;; <compat-tests:pos-bol>
    161   "Return the position of the first character on the current line.
    162 With optional argument N, scan forward N - 1 lines first.
    163 If the scan reaches the end of the buffer, return that position.
    164 
    165 This function ignores text display directionality; it returns the
    166 position of the first character in logical order, i.e. the smallest
    167 character position on the logical line.  See `vertical-motion' for
    168 movement by screen lines.
    169 
    170 This function does not move point.  Also see `line-beginning-position'."
    171   (declare (side-effect-free t))
    172   (let ((inhibit-field-text-motion t))
    173     (line-beginning-position n)))
    174 
    175 (compat-defun pos-eol (&optional n) ;; <compat-tests:pos-bol>
    176   "Return the position of the last character on the current line.
    177 With argument N not nil or 1, move forward N - 1 lines first.
    178 If scan reaches end of buffer, return that position.
    179 
    180 This function ignores text display directionality; it returns the
    181 position of the last character in logical order, i.e. the largest
    182 character position on the line.
    183 
    184 This function does not move point.  Also see `line-end-position'."
    185   (declare (side-effect-free t))
    186   (let ((inhibit-field-text-motion t))
    187     (line-end-position n)))
    188 
    189 ;;;; Defined in subr.el
    190 
    191 (compat-defmacro with-delayed-message (_args &rest body) ;; <compat-tests:with-delayed-message>
    192   "Like `progn', but display MESSAGE if BODY takes longer than TIMEOUT seconds.
    193 The MESSAGE form will be evaluated immediately, but the resulting
    194 string will be displayed only if BODY takes longer than TIMEOUT seconds.
    195 
    196 NOTE: The compatibility function never displays the message,
    197 which is not problematic since the only effect of the function is
    198 to display a progress message to the user.  Backporting this
    199 feature is not possible, since the implementation is directly
    200 baked into the Elisp interpreter.
    201 
    202 \(fn (timeout message) &rest body)"
    203   (declare (indent 1))
    204   (macroexp-progn body))
    205 
    206 (compat-defun funcall-with-delayed-message (timeout message function) ;; <compat-tests:with-delayed-message>
    207   "Like `funcall', but display MESSAGE if FUNCTION takes longer than TIMEOUT.
    208 TIMEOUT is a number of seconds, and can be an integer or a
    209 floating point number.  If FUNCTION takes less time to execute
    210 than TIMEOUT seconds, MESSAGE is not displayed.
    211 
    212 NOTE: The compatibility function never displays the message,
    213 which is not problematic since the only effect of the function is
    214 to display a progress message to the user.  Backporting this
    215 feature is not possible, since the implementation is directly
    216 baked into the Elisp interpreter."
    217   (ignore timeout message)
    218   (funcall function))
    219 
    220 (compat-defun string-lines (string &optional omit-nulls keep-newlines) ;; <compat-tests:string-lines>
    221   "Handle additional KEEP-NEWLINES argument."
    222   :extended "28.1"
    223   (if (equal string "")
    224       (if omit-nulls
    225           nil
    226         (list ""))
    227     (let ((lines nil)
    228           (start 0))
    229       (while (< start (length string))
    230         (let ((newline (string-search "\n" string start)))
    231           (if newline
    232               (progn
    233                 (when (or (not omit-nulls)
    234                           (not (= start newline)))
    235                   (let ((line (substring string start
    236                                          (if keep-newlines
    237                                              (1+ newline)
    238                                            newline))))
    239                     (when (not (and keep-newlines omit-nulls
    240                                     (equal line "\n")))
    241                       (push line lines))))
    242                 (setq start (1+ newline)))
    243             (if (zerop start)
    244                 (push string lines)
    245               (push (substring string start) lines))
    246             (setq start (length string)))))
    247       (nreverse lines))))
    248 
    249 (compat-defun readablep (object) ;; <compat-tests:readablep>
    250   "Say whether OBJECT has a readable syntax.
    251 This means that OBJECT can be printed out and then read back
    252 again by the Lisp reader.  This function returns nil if OBJECT is
    253 unreadable, and the printed representation (from `prin1') of
    254 OBJECT if it is readable."
    255   (declare (side-effect-free error-free))
    256   (ignore-errors (equal object (read (prin1-to-string object)))))
    257 
    258 (compat-defun buffer-local-restore-state (states) ;; <compat-tests:buffer-local-set-state>
    259   "Restore values of buffer-local variables recorded in STATES.
    260 STATES should be an object returned by `buffer-local-set-state'."
    261   (dolist (state states)
    262     (if (cadr state)
    263         (set (car state) (caddr state))
    264       (kill-local-variable (car state)))))
    265 
    266 (compat-defun buffer-local-set-state--get (pairs) ;; <compat-tests:buffer-local-set-state>
    267   "Internal helper function."
    268   (let ((states nil))
    269     (while pairs
    270       (push (list (car pairs)
    271                   (and (boundp (car pairs))
    272                        (local-variable-p (car pairs)))
    273                   (and (boundp (car pairs))
    274                        (symbol-value (car pairs))))
    275             states)
    276       (setq pairs (cddr pairs)))
    277     (nreverse states)))
    278 
    279 (compat-defmacro buffer-local-set-state (&rest pairs) ;; <compat-tests:buffer-local-set-state>
    280   "Like `setq-local', but allow restoring the previous state of locals later.
    281 This macro returns an object that can be passed to `buffer-local-restore-state'
    282 in order to restore the state of the local variables set via this macro.
    283 
    284 \(fn [VARIABLE VALUE]...)"
    285   (declare (debug setq))
    286   (unless (zerop (mod (length pairs) 2))
    287     (error "PAIRS must have an even number of variable/value members"))
    288   `(prog1
    289        (buffer-local-set-state--get ',pairs)
    290      (,(if (fboundp 'compat--setq-local) 'compat--setq-local 'setq-local)
    291       ,@pairs)))
    292 
    293 (compat-defun list-of-strings-p (object) ;; <compat-tests:list-of-strings-p>
    294   "Return t if OBJECT is nil or a list of strings."
    295   (declare (pure t) (side-effect-free t))
    296   (while (and (consp object) (stringp (car object)))
    297     (setq object (cdr object)))
    298   (null object))
    299 
    300 (compat-defun plistp (object) ;; <compat-tests:plistp>
    301   "Non-nil if and only if OBJECT is a valid plist."
    302   (let ((len (proper-list-p object)))
    303     (and len (zerop (% len 2)))))
    304 
    305 (compat-defun delete-line () ;; <compat-tests:delete-line>
    306   "Delete the current line."
    307   (delete-region (pos-bol) (pos-bol 2)))
    308 
    309 (compat-defmacro with-restriction (start end &rest rest) ;; <compat-tests:with-restriction>
    310   "Execute BODY with restrictions set to START and END.
    311 
    312 The current restrictions, if any, are restored upon return.
    313 
    314 When the optional :label LABEL argument is present, in which
    315 LABEL is a symbol, inside BODY, `narrow-to-region' and `widen'
    316 can be used only within the START and END limits.  To gain access
    317 to other portions of the buffer, use `without-restriction' with the
    318 same LABEL argument.
    319 
    320 \(fn START END [:label LABEL] BODY)"
    321   (declare (indent 0) (debug t))
    322   `(save-restriction
    323      (narrow-to-region ,start ,end)
    324      ;; Locking is ignored
    325      ,@(if (eq (car rest) :label) (cddr rest) rest)))
    326 
    327 (compat-defmacro without-restriction (&rest rest) ;; <compat-tests:without-restriction>
    328   "Execute BODY without restrictions.
    329 
    330 The current restrictions, if any, are restored upon return.
    331 
    332 When the optional :label LABEL argument is present, the
    333 restrictions set by `with-restriction' with the same LABEL argument
    334 are lifted.
    335 
    336 \(fn [:label LABEL] BODY)"
    337   (declare (indent 0) (debug t))
    338   `(save-restriction
    339      (widen)
    340      ;; Locking is ignored
    341      ,@(if (eq (car rest) :label) (cddr rest) rest)))
    342 
    343 (compat-defmacro with-memoization (place &rest code) ;; <compat-tests:with-memoization>
    344   "Return the value of CODE and stash it in PLACE.
    345 If PLACE's value is non-nil, then don't bother evaluating CODE
    346 and return the value found in PLACE instead."
    347   (declare (indent 1))
    348   (gv-letplace (getter setter) place
    349     `(or ,getter
    350          ,(macroexp-let2 nil val (macroexp-progn code)
    351             `(progn
    352                ,(funcall setter val)
    353                ,val)))))
    354 
    355 (compat-defalias string-split split-string) ;; <compat-tests:string-split>
    356 
    357 (compat-defun compiled-function-p (object) ;; <compat-tests:compiled-function-p>
    358   "Return non-nil if OBJECT is a function that has been compiled.
    359 Does not distinguish between functions implemented in machine code
    360 or byte-code."
    361   (or (subrp object) (byte-code-function-p object)))
    362 
    363 (compat-defun function-alias-p (func &optional noerror) ;; <compat-tests:function-alias-p>
    364   "Return nil if FUNC is not a function alias.
    365 If FUNC is a function alias, return the function alias chain.
    366 
    367 If the function alias chain contains loops, an error will be
    368 signalled.  If NOERROR, the non-loop parts of the chain is returned."
    369   (declare (side-effect-free t))
    370   (let ((chain nil)
    371         (orig-func func))
    372     (nreverse
    373      (catch 'loop
    374        (while (and (symbolp func)
    375                    (setq func (symbol-function func))
    376                    (symbolp func))
    377          (when (or (memq func chain)
    378                    (eq func orig-func))
    379            (if noerror
    380                (throw 'loop chain)
    381              (signal 'cyclic-function-indirection (list orig-func))))
    382          (push func chain))
    383        chain))))
    384 
    385 (compat-defun buffer-match-p (condition buffer-or-name &optional arg) ;; <compat-tests:buffer-match-p>
    386   "Return non-nil if BUFFER-OR-NAME matches CONDITION.
    387 CONDITION is either:
    388 - the symbol t, to always match,
    389 - the symbol nil, which never matches,
    390 - a regular expression, to match a buffer name,
    391 - a predicate function that takes a buffer object and ARG as
    392   arguments, and returns non-nil if the buffer matches,
    393 - a cons-cell, where the car describes how to interpret the cdr.
    394   The car can be one of the following:
    395   * `derived-mode': the buffer matches if the buffer's major mode
    396     is derived from the major mode in the cons-cell's cdr.
    397   * `major-mode': the buffer matches if the buffer's major mode
    398     is eq to the cons-cell's cdr.  Prefer using `derived-mode'
    399     instead when both can work.
    400   * `not': the cadr is interpreted as a negation of a condition.
    401   * `and': the cdr is a list of recursive conditions, that all have
    402     to be met.
    403   * `or': the cdr is a list of recursive condition, of which at
    404     least one has to be met."
    405   (letrec
    406       ((buffer (get-buffer buffer-or-name))
    407        (match
    408         (lambda (conditions)
    409           (catch 'match
    410             (dolist (condition conditions)
    411               (when (cond
    412                      ((eq condition t))
    413                      ((stringp condition)
    414                       (string-match-p condition (buffer-name buffer)))
    415                      ((functionp condition)
    416                       (condition-case nil
    417                           (funcall condition buffer)
    418                         (wrong-number-of-arguments
    419                          (funcall condition buffer arg))))
    420                      ((eq (car-safe condition) 'major-mode)
    421                       (eq
    422                        (buffer-local-value 'major-mode buffer)
    423                        (cdr condition)))
    424                      ((eq (car-safe condition) 'derived-mode)
    425                       (provided-mode-derived-p
    426                        (buffer-local-value 'major-mode buffer)
    427                        (cdr condition)))
    428                      ((eq (car-safe condition) 'not)
    429                       (not (funcall match (cdr condition))))
    430                      ((eq (car-safe condition) 'or)
    431                       (funcall match (cdr condition)))
    432                      ((eq (car-safe condition) 'and)
    433                       (catch 'fail
    434                         (dolist (c (cdr condition))
    435                           (unless (funcall match (list c))
    436                             (throw 'fail nil)))
    437                         t)))
    438                 (throw 'match t)))))))
    439     (funcall match (list condition))))
    440 
    441 (compat-defun match-buffers (condition &optional buffers arg) ;; <compat-tests:match-buffers>
    442   "Return a list of buffers that match CONDITION.
    443 See `buffer-match' for details on CONDITION.  By default all
    444 buffers are checked, this can be restricted by passing an
    445 optional argument BUFFERS, set to a list of buffers to check.
    446 ARG is passed to `buffer-match', for predicate conditions in
    447 CONDITION."
    448   (let (bufs)
    449     (dolist (buf (or buffers (buffer-list)))
    450       (when (buffer-match-p condition (get-buffer buf) arg)
    451         (push buf bufs)))
    452     bufs))
    453 
    454 (compat-defvar set-transient-map-timeout nil ;; <compat-tests:set-transient-map>
    455   "Timeout in seconds for deactivation of a transient keymap.
    456 If this is a number, it specifies the amount of idle time
    457 after which to deactivate the keymap set by `set-transient-map',
    458 thus overriding the value of the TIMEOUT argument to that function.")
    459 
    460 (compat-defvar set-transient-map-timer nil ;; <compat-tests:set-transient-map>
    461   "Timer for `set-transient-map-timeout'.")
    462 
    463 (declare-function format-spec "format-spec")
    464 (compat-defun set-transient-map (map &optional keep-pred on-exit message timeout) ;; <compat-tests:set-transient-map>
    465   "Handle the optional arguments MESSAGE and TIMEOUT."
    466   :extended t
    467   (unless (fboundp 'format-spec)
    468     (require 'format-spec))
    469   (let* ((timeout (or set-transient-map-timeout timeout))
    470          (message
    471           (when message
    472             (let (keys)
    473               (map-keymap (lambda (key cmd) (and cmd (push key keys))) map)
    474               (format-spec (if (stringp message) message "Repeat with %k")
    475                            `((?k . ,(mapconcat
    476                                      (lambda (key)
    477                                        (substitute-command-keys
    478                                         (format "\\`%s'"
    479                                                 (key-description (vector key)))))
    480                                      keys ", ")))))))
    481          (clearfun (make-symbol "clear-transient-map"))
    482          (exitfun
    483           (lambda ()
    484             (internal-pop-keymap map 'overriding-terminal-local-map)
    485             (remove-hook 'pre-command-hook clearfun)
    486             (when message (message ""))
    487             (when set-transient-map-timer (cancel-timer set-transient-map-timer))
    488             (when on-exit (funcall on-exit)))))
    489     (fset clearfun
    490           (lambda ()
    491             (with-demoted-errors "set-transient-map PCH: %S"
    492               (if (cond
    493                        ((null keep-pred) nil)
    494                        ((and (not (eq map (cadr overriding-terminal-local-map)))
    495                              (memq map (cddr overriding-terminal-local-map)))
    496                         t)
    497                        ((eq t keep-pred)
    498                         (let ((mc (lookup-key map (this-command-keys-vector))))
    499                           (when (and mc (symbolp mc))
    500                             (setq mc (or (command-remapping mc) mc)))
    501                           (and mc (eq this-command mc))))
    502                        (t (funcall keep-pred)))
    503                   (when message (message "%s" message))
    504                 (funcall exitfun)))))
    505     (add-hook 'pre-command-hook clearfun)
    506     (internal-push-keymap map 'overriding-terminal-local-map)
    507     (when timeout
    508       (when set-transient-map-timer (cancel-timer set-transient-map-timer))
    509       (setq set-transient-map-timer (run-with-idle-timer timeout nil exitfun)))
    510     (when message (message "%s" message))
    511     exitfun))
    512 
    513 ;;;; Defined in simple.el
    514 
    515 (compat-defun char-uppercase-p (char) ;; <compat-tests:char-uppercase-p>
    516   "Return non-nil if CHAR is an upper-case character.
    517 If the Unicode tables are not yet available, e.g. during bootstrap,
    518 then gives correct answers only for ASCII characters."
    519   (cond ((unicode-property-table-internal 'lowercase)
    520          (characterp (get-char-code-property char 'lowercase)))
    521         ((and (>= char ?A) (<= char ?Z)))))
    522 
    523 (compat-defun use-region-noncontiguous-p () ;; <compat-tests:region-noncontiguous-p>
    524   "Return non-nil for a non-contiguous region if `use-region-p'."
    525   (and (use-region-p) (region-noncontiguous-p)))
    526 
    527 (compat-defun use-region-beginning () ;; <compat-tests:use-region>
    528   "Return the start of the region if `use-region-p'."
    529   (and (use-region-p) (region-beginning)))
    530 
    531 (compat-defun use-region-end () ;; <compat-tests:use-region>
    532   "Return the end of the region if `use-region-p'."
    533   (and (use-region-p) (region-end)))
    534 
    535 (compat-defun get-scratch-buffer-create () ;; <compat-tests:get-scratch-buffer-create>
    536   "Return the *scratch* buffer, creating a new one if needed."
    537   (or (get-buffer "*scratch*")
    538       (let ((scratch (get-buffer-create "*scratch*")))
    539         ;; Don't touch the buffer contents or mode unless we know that
    540         ;; we just created it.
    541         (with-current-buffer scratch
    542           (when initial-scratch-message
    543             (insert (substitute-command-keys initial-scratch-message))
    544             (set-buffer-modified-p nil))
    545           (funcall initial-major-mode))
    546         scratch)))
    547 
    548 ;;;; Defined in subr-x.el
    549 
    550 (compat-defmacro with-buffer-unmodified-if-unchanged (&rest body) ;; <compat-tests:with-buffer-unmodified-if-unchanged>
    551   "Like `progn', but change buffer-modified status only if buffer text changes.
    552 If the buffer was unmodified before execution of BODY, and
    553 buffer text after execution of BODY is identical to what it was
    554 before, ensure that buffer is still marked unmodified afterwards.
    555 For example, the following won't change the buffer's modification
    556 status:
    557 
    558   (with-buffer-unmodified-if-unchanged
    559     (insert \"a\")
    560     (delete-char -1))
    561 
    562 Note that only changes in the raw byte sequence of the buffer text,
    563 as stored in the internal representation, are monitored for the
    564 purpose of detecting the lack of changes in buffer text.  Any other
    565 changes that are normally perceived as \"buffer modifications\", such
    566 as changes in text properties, `buffer-file-coding-system', buffer
    567 multibyteness, etc. -- will not be noticed, and the buffer will still
    568 be marked unmodified, effectively ignoring those changes."
    569   (declare (debug t) (indent 0))
    570   (let ((hash (gensym))
    571         (buffer (gensym)))
    572     `(let ((,hash (and (not (buffer-modified-p))
    573                        (buffer-hash)))
    574            (,buffer (current-buffer)))
    575        (prog1
    576            (progn
    577              ,@body)
    578          ;; If we didn't change anything in the buffer (and the buffer
    579          ;; was previously unmodified), then flip the modification status
    580          ;; back to "unchanged".
    581          (when (and ,hash (buffer-live-p ,buffer))
    582            (with-current-buffer ,buffer
    583              (when (and (buffer-modified-p)
    584                         (equal ,hash (buffer-hash)))
    585                (restore-buffer-modified-p nil))))))))
    586 
    587 (compat-defun add-display-text-property (start end prop value ;; <compat-tests:add-display-text-property>
    588                                                &optional object)
    589   "Add display property PROP with VALUE to the text from START to END.
    590 If any text in the region has a non-nil `display' property, those
    591 properties are retained.
    592 
    593 If OBJECT is non-nil, it should be a string or a buffer.  If nil,
    594 this defaults to the current buffer."
    595   (let ((sub-start start)
    596         (sub-end 0)
    597         disp)
    598     (while (< sub-end end)
    599       (setq sub-end (next-single-property-change sub-start 'display object
    600                                                  (if (stringp object)
    601                                                      (min (length object) end)
    602                                                    (min end (point-max)))))
    603       (if (not (setq disp (get-text-property sub-start 'display object)))
    604           ;; No old properties in this range.
    605           (put-text-property sub-start sub-end 'display (list prop value)
    606                              object)
    607         ;; We have old properties.
    608         (let ((vector nil))
    609           ;; Make disp into a list.
    610           (setq disp
    611                 (cond
    612                  ((vectorp disp)
    613                   (setq vector t)
    614                   (append disp nil))
    615                  ((not (consp (car disp)))
    616                   (list disp))
    617                  (t
    618                   disp)))
    619           ;; Remove any old instances.
    620           (when-let ((old (assoc prop disp)))
    621             (setq disp (delete old disp)))
    622           (setq disp (cons (list prop value) disp))
    623           (when vector
    624             (setq disp (vconcat disp)))
    625           ;; Finally update the range.
    626           (put-text-property sub-start sub-end 'display disp object)))
    627       (setq sub-start sub-end))))
    628 
    629 (compat-defmacro while-let (spec &rest body) ;; <compat-tests:while-let>
    630   "Bind variables according to SPEC and conditionally evaluate BODY.
    631 Evaluate each binding in turn, stopping if a binding value is nil.
    632 If all bindings are non-nil, eval BODY and repeat.
    633 
    634 The variable list SPEC is the same as in `if-let*'."
    635   (declare (indent 1) (debug if-let))
    636   (let ((done (gensym "done")))
    637     `(catch ',done
    638        (while t
    639          (if-let* ,spec
    640              (progn
    641                ,@body)
    642            (throw ',done nil))))))
    643 
    644 ;;;; Defined in files.el
    645 
    646 (compat-defun directory-abbrev-make-regexp (directory) ;; <compat-tests:directory-abbrev-make-regexp>
    647   "Create a regexp to match DIRECTORY for `directory-abbrev-alist'."
    648   (let ((regexp
    649          ;; We include a slash at the end, to avoid spurious
    650          ;; matches such as `/usr/foobar' when the home dir is
    651          ;; `/usr/foo'.
    652          (concat "\\`" (regexp-quote directory) "\\(/\\|\\'\\)")))
    653     ;; The value of regexp could be multibyte or unibyte.  In the
    654     ;; latter case, we need to decode it.
    655     (if (multibyte-string-p regexp)
    656         regexp
    657       (decode-coding-string regexp
    658                             (if (eq system-type 'windows-nt)
    659                                 'utf-8
    660                               locale-coding-system)))))
    661 
    662 (compat-defun directory-abbrev-apply (filename) ;; <compat-tests:directory-abbrev-apply>
    663   "Apply the abbreviations in `directory-abbrev-alist' to FILENAME.
    664 Note that when calling this, you should set `case-fold-search' as
    665 appropriate for the filesystem used for FILENAME."
    666   (dolist (dir-abbrev directory-abbrev-alist filename)
    667     (when (string-match (car dir-abbrev) filename)
    668          (setq filename (concat (cdr dir-abbrev)
    669                                 (substring filename (match-end 0)))))))
    670 
    671 (compat-defun file-name-split (filename) ;; <compat-tests:file-name-split>
    672   "Return a list of all the components of FILENAME.
    673 On most systems, this will be true:
    674 
    675   (equal (string-join (file-name-split filename) \"/\") filename)"
    676   (let ((components nil))
    677     ;; If this is a directory file name, then we have a null file name
    678     ;; at the end.
    679     (when (directory-name-p filename)
    680       (push "" components)
    681       (setq filename (directory-file-name filename)))
    682     ;; Loop, chopping off components.
    683     (while (length> filename 0)
    684       (push (file-name-nondirectory filename) components)
    685       (let ((dir (file-name-directory filename)))
    686         (setq filename (and dir (directory-file-name dir)))
    687         ;; If there's nothing left to peel off, we're at the root and
    688         ;; we can stop.
    689         (when (and dir (equal dir filename))
    690           (push (if (equal dir "") ""
    691                   ;; On Windows, the first component might be "c:" or
    692                   ;; the like.
    693                   (substring dir 0 -1))
    694                 components)
    695           (setq filename nil))))
    696     components))
    697 
    698 (compat-defun file-attribute-file-identifier (attributes) ;; <compat-tests:file-attribute-getters>
    699   "The inode and device numbers in ATTRIBUTES returned by `file-attributes'.
    700 The value is a list of the form (INODENUM DEVICE), where DEVICE could be
    701 either a single number or a cons cell of two numbers.
    702 This tuple of numbers uniquely identifies the file."
    703   (nthcdr 10 attributes))
    704 
    705 (compat-defun file-name-parent-directory (filename) ;; <compat-tests:file-name-parent-directory>
    706   "Return the directory name of the parent directory of FILENAME.
    707 If FILENAME is at the root of the filesystem, return nil.
    708 If FILENAME is relative, it is interpreted to be relative
    709 to `default-directory', and the result will also be relative."
    710   (let* ((expanded-filename (expand-file-name filename))
    711          (parent (file-name-directory (directory-file-name expanded-filename))))
    712     (cond
    713      ;; filename is at top-level, therefore no parent
    714      ((or (null parent)
    715           ;; `equal' is enough, we don't need to resolve symlinks here
    716           ;; with `file-equal-p', also for performance
    717           (equal parent expanded-filename))
    718       nil)
    719      ;; filename is relative, return relative parent
    720      ((not (file-name-absolute-p filename))
    721       (file-relative-name parent))
    722      (t
    723       parent))))
    724 
    725 (compat-defvar file-has-changed-p--hash-table ;; <compat-tests:file-has-changed-p>
    726                (make-hash-table :test #'equal)
    727   "Internal variable used by `file-has-changed-p'.")
    728 
    729 (compat-defun file-has-changed-p (file &optional tag) ;; <compat-tests:file-has-changed-p>
    730   "Return non-nil if FILE has changed.
    731 The size and modification time of FILE are compared to the size
    732 and modification time of the same FILE during a previous
    733 invocation of `file-has-changed-p'.  Thus, the first invocation
    734 of `file-has-changed-p' always returns non-nil when FILE exists.
    735 The optional argument TAG, which must be a symbol, can be used to
    736 limit the comparison to invocations with identical tags; it can be
    737 the symbol of the calling function, for example."
    738   (let* ((file (directory-file-name (expand-file-name file)))
    739          (remote-file-name-inhibit-cache t)
    740          (fileattr (file-attributes file 'integer))
    741          (attr (and fileattr
    742                     (cons (file-attribute-size fileattr)
    743                           (file-attribute-modification-time fileattr))))
    744          (sym (concat (symbol-name tag) "@" file))
    745          (cachedattr (gethash sym file-has-changed-p--hash-table)))
    746     (unless (equal attr cachedattr)
    747       (puthash sym attr file-has-changed-p--hash-table))))
    748 
    749 ;;;; Defined in keymap.el
    750 
    751 (compat-defun key-valid-p (keys) ;; <compat-tests:key-valid-p>
    752   "Say whether KEYS is a valid key.
    753 A key is a string consisting of one or more key strokes.
    754 The key strokes are separated by single space characters.
    755 
    756 Each key stroke is either a single character, or the name of an
    757 event, surrounded by angle brackets.  In addition, any key stroke
    758 may be preceded by one or more modifier keys.  Finally, a limited
    759 number of characters have a special shorthand syntax.
    760 
    761 Here's some example key sequences.
    762 
    763   \"f\"           (the key `f')
    764   \"S o m\"       (a three key sequence of the keys `S', `o' and `m')
    765   \"C-c o\"       (a two key sequence of the keys `c' with the control modifier
    766                  and then the key `o')
    767   \"H-<left>\"    (the key named \"left\" with the hyper modifier)
    768   \"M-RET\"       (the \"return\" key with a meta modifier)
    769   \"C-M-<space>\" (the \"space\" key with both the control and meta modifiers)
    770 
    771 These are the characters that have shorthand syntax:
    772 NUL, RET, TAB, LFD, ESC, SPC, DEL.
    773 
    774 Modifiers have to be specified in this order:
    775 
    776    A-C-H-M-S-s
    777 
    778 which is
    779 
    780    Alt-Control-Hyper-Meta-Shift-super"
    781   (declare (pure t) (side-effect-free t))
    782   (let ((case-fold-search nil))
    783     (and
    784      (stringp keys)
    785      (string-match-p "\\`[^ ]+\\( [^ ]+\\)*\\'" keys)
    786      (save-match-data
    787        (catch 'exit
    788          (let ((prefixes
    789                 "\\(A-\\)?\\(C-\\)?\\(H-\\)?\\(M-\\)?\\(S-\\)?\\(s-\\)?"))
    790            (dolist (key (split-string keys " "))
    791              ;; Every key might have these modifiers, and they should be
    792              ;; in this order.
    793              (when (string-match (concat "\\`" prefixes) key)
    794                (setq key (substring key (match-end 0))))
    795              (unless (or (and (= (length key) 1)
    796                               ;; Don't accept control characters as keys.
    797                               (not (< (aref key 0) ?\s))
    798                               ;; Don't accept Meta'd characters as keys.
    799                               (or (multibyte-string-p key)
    800                                   (not (<= 127 (aref key 0) 255))))
    801                          (and (string-match-p "\\`<[-_A-Za-z0-9]+>\\'" key)
    802                               ;; Don't allow <M-C-down>.
    803                               (= (progn
    804                                    (string-match
    805                                     (concat "\\`<" prefixes) key)
    806                                    (match-end 0))
    807                                  1))
    808                          (string-match-p
    809                           "\\`\\(NUL\\|RET\\|TAB\\|LFD\\|ESC\\|SPC\\|DEL\\)\\'"
    810                           key))
    811                ;; Invalid.
    812                (throw 'exit nil)))
    813            t))))))
    814 
    815 (compat-defun keymap--check (key) ;; <compat-tests:keymap--check>
    816   "Signal an error if KEY doesn't have a valid syntax."
    817   (unless (key-valid-p key)
    818     (error "%S is not a valid key definition; see `key-valid-p'" key)))
    819 
    820 (compat-defun key-parse (keys) ;; <compat-tests:key-parse>
    821   "Convert KEYS to the internal Emacs key representation.
    822 See `kbd' for a descripion of KEYS."
    823   (declare (pure t) (side-effect-free t))
    824   ;; A pure function is expected to preserve the match data.
    825   (save-match-data
    826     (let ((case-fold-search nil)
    827           (len (length keys)) ; We won't alter keys in the loop below.
    828           (pos 0)
    829           (res []))
    830       (while (and (< pos len)
    831                   (string-match "[^ \t\n\f]+" keys pos))
    832         (let* ((word-beg (match-beginning 0))
    833                (word-end (match-end 0))
    834                (word (substring keys word-beg len))
    835                (times 1)
    836                key)
    837           ;; Try to catch events of the form "<as df>".
    838           (if (string-match "\\`<[^ <>\t\n\f][^>\t\n\f]*>" word)
    839               (setq word (match-string 0 word)
    840                     pos (+ word-beg (match-end 0)))
    841             (setq word (substring keys word-beg word-end)
    842                   pos word-end))
    843           (when (string-match "\\([0-9]+\\)\\*." word)
    844             (setq times (string-to-number (substring word 0 (match-end 1))))
    845             (setq word (substring word (1+ (match-end 1)))))
    846           (cond ((string-match "^<<.+>>$" word)
    847                  (setq key (vconcat (if (eq (key-binding [?\M-x])
    848                                             'execute-extended-command)
    849                                         [?\M-x]
    850                                       (or (car (where-is-internal
    851                                                 'execute-extended-command))
    852                                           [?\M-x]))
    853                                     (substring word 2 -2) "\r")))
    854                 ((and (string-match "^\\(\\([ACHMsS]-\\)*\\)<\\(.+\\)>$" word)
    855                       (progn
    856                         (setq word (concat (match-string 1 word)
    857                                            (match-string 3 word)))
    858                         (not (string-match
    859                               "\\<\\(NUL\\|RET\\|LFD\\|ESC\\|SPC\\|DEL\\)$"
    860                               word))))
    861                  (setq key (list (intern word))))
    862                 ((or (equal word "REM") (string-match "^;;" word))
    863                  (setq pos (string-match "$" keys pos)))
    864                 (t
    865                  (let ((orig-word word) (prefix 0) (bits 0))
    866                    (while (string-match "^[ACHMsS]-." word)
    867                      (setq bits (+ bits
    868                                    (cdr
    869                                     (assq (aref word 0)
    870                                           '((?A . ?\A-\0) (?C . ?\C-\0)
    871                                             (?H . ?\H-\0) (?M . ?\M-\0)
    872                                             (?s . ?\s-\0) (?S . ?\S-\0))))))
    873                      (setq prefix (+ prefix 2))
    874                      (setq word (substring word 2)))
    875                    (when (string-match "^\\^.$" word)
    876                      (setq bits (+ bits ?\C-\0))
    877                      (setq prefix (1+ prefix))
    878                      (setq word (substring word 1)))
    879                    (let ((found (assoc word '(("NUL" . "\0") ("RET" . "\r")
    880                                               ("LFD" . "\n") ("TAB" . "\t")
    881                                               ("ESC" . "\e") ("SPC" . " ")
    882                                               ("DEL" . "\177")))))
    883                      (when found (setq word (cdr found))))
    884                    (when (string-match "^\\\\[0-7]+$" word)
    885                      (let ((n 0))
    886                        (dolist (ch (cdr (string-to-list word)))
    887                          (setq n (+ (* n 8) ch -48)))
    888                        (setq word (vector n))))
    889                    (cond ((= bits 0)
    890                           (setq key word))
    891                          ((and (= bits ?\M-\0) (stringp word)
    892                                (string-match "^-?[0-9]+$" word))
    893                           (setq key (mapcar (lambda (x) (+ x bits))
    894                                             (append word nil))))
    895                          ((/= (length word) 1)
    896                           (error "%s must prefix a single character, not %s"
    897                                  (substring orig-word 0 prefix) word))
    898                          ((and (/= (logand bits ?\C-\0) 0) (stringp word)
    899                                ;; We used to accept . and ? here,
    900                                ;; but . is simply wrong,
    901                                ;; and C-? is not used (we use DEL instead).
    902                                (string-match "[@-_a-z]" word))
    903                           (setq key (list (+ bits (- ?\C-\0)
    904                                              (logand (aref word 0) 31)))))
    905                          (t
    906                           (setq key (list (+ bits (aref word 0)))))))))
    907           (when key
    908             (dolist (_ (number-sequence 1 times))
    909               (setq res (vconcat res key))))))
    910       res)))
    911 
    912 (compat-defun keymap-set (keymap key definition) ;; <compat-tests:defvar-keymap>
    913   "Set KEY to DEFINITION in KEYMAP.
    914 KEY is a string that satisfies `key-valid-p'.
    915 
    916 DEFINITION is anything that can be a key's definition:
    917  nil (means key is undefined in this keymap),
    918  a command (a Lisp function suitable for interactive calling),
    919  a string (treated as a keyboard macro),
    920  a keymap (to define a prefix key),
    921  a symbol (when the key is looked up, the symbol will stand for its
    922     function definition, which should at that time be one of the above,
    923     or another symbol whose function definition is used, etc.),
    924  a cons (STRING . DEFN), meaning that DEFN is the definition
    925     (DEFN should be a valid definition in its own right) and
    926     STRING is the menu item name (which is used only if the containing
    927     keymap has been created with a menu name, see `make-keymap'),
    928  or a cons (MAP . CHAR), meaning use definition of CHAR in keymap MAP,
    929  or an extended menu item definition.
    930  (See info node `(elisp)Extended Menu Items'.)"
    931   (keymap--check key)
    932   (when (stringp definition)
    933     (keymap--check definition)
    934     (setq definition (key-parse definition)))
    935   (define-key keymap (key-parse key) definition))
    936 
    937 (compat-defun keymap-unset (keymap key &optional remove) ;; <compat-tests:keymap-unset>
    938   "Remove key sequence KEY from KEYMAP.
    939 KEY is a string that satisfies `key-valid-p'.
    940 
    941 If REMOVE, remove the binding instead of unsetting it.  This only
    942 makes a difference when there's a parent keymap.  When unsetting
    943 a key in a child map, it will still shadow the same key in the
    944 parent keymap.  Removing the binding will allow the key in the
    945 parent keymap to be used."
    946   (keymap--check key)
    947   (compat--define-key keymap (key-parse key) nil remove))
    948 
    949 (compat-defun keymap-global-set (key command) ;; <compat-tests:keymap-global-set>
    950   "Give KEY a global binding as COMMAND.
    951 COMMAND is the command definition to use; usually it is
    952 a symbol naming an interactively-callable function.
    953 
    954 KEY is a string that satisfies `key-valid-p'.
    955 
    956 Note that if KEY has a local binding in the current buffer,
    957 that local binding will continue to shadow any global binding
    958 that you make with this function.
    959 
    960 NOTE: The compatibility version is not a command."
    961   (keymap-set (current-global-map) key command))
    962 
    963 (compat-defun keymap-local-set (key command) ;; <compat-tests:keymap-local-set>
    964   "Give KEY a local binding as COMMAND.
    965 COMMAND is the command definition to use; usually it is
    966 a symbol naming an interactively-callable function.
    967 
    968 KEY is a string that satisfies `key-valid-p'.
    969 
    970 The binding goes in the current buffer's local map, which in most
    971 cases is shared with all other buffers in the same major mode.
    972 
    973 NOTE: The compatibility version is not a command."
    974   (let ((map (current-local-map)))
    975     (unless map
    976       (use-local-map (setq map (make-sparse-keymap))))
    977     (keymap-set map key command)))
    978 
    979 (compat-defun keymap-global-unset (key &optional remove) ;; <compat-tests:keymap-global-unset>
    980   "Remove global binding of KEY (if any).
    981 KEY is a string that satisfies `key-valid-p'.
    982 
    983 If REMOVE (interactively, the prefix arg), remove the binding
    984 instead of unsetting it.  See `keymap-unset' for details.
    985 
    986 NOTE: The compatibility version is not a command."
    987   (keymap-unset (current-global-map) key remove))
    988 
    989 (compat-defun keymap-local-unset (key &optional remove) ;; <compat-tests:keymap-local-unset>
    990   "Remove local binding of KEY (if any).
    991 KEY is a string that satisfies `key-valid-p'.
    992 
    993 If REMOVE (interactively, the prefix arg), remove the binding
    994 instead of unsetting it.  See `keymap-unset' for details.
    995 
    996 NOTE: The compatibility version is not a command."
    997   (when (current-local-map)
    998     (keymap-unset (current-local-map) key remove)))
    999 
   1000 (compat-defun keymap-substitute (keymap olddef newdef &optional oldmap prefix) ;; <compat-tests:keymap-substitute>
   1001   "Replace OLDDEF with NEWDEF for any keys in KEYMAP now defined as OLDDEF.
   1002 In other words, OLDDEF is replaced with NEWDEF wherever it appears.
   1003 Alternatively, if optional fourth argument OLDMAP is specified, we redefine
   1004 in KEYMAP as NEWDEF those keys that are defined as OLDDEF in OLDMAP.
   1005 
   1006 If you don't specify OLDMAP, you can usually get the same results
   1007 in a cleaner way with command remapping, like this:
   1008   (define-key KEYMAP [remap OLDDEF] NEWDEF)
   1009 \n(fn OLDDEF NEWDEF KEYMAP &optional OLDMAP)"
   1010   ;; Don't document PREFIX in the doc string because we don't want to
   1011   ;; advertise it.  It's meant for recursive calls only.  Here's its
   1012   ;; meaning
   1013 
   1014   ;; If optional argument PREFIX is specified, it should be a key
   1015   ;; prefix, a string.  Redefined bindings will then be bound to the
   1016   ;; original key, with PREFIX added at the front.
   1017   (unless prefix
   1018     (setq prefix ""))
   1019   (let* ((scan (or oldmap keymap))
   1020          (prefix1 (vconcat prefix [nil]))
   1021          (key-substitution-in-progress
   1022           (cons scan key-substitution-in-progress)))
   1023     ;; Scan OLDMAP, finding each char or event-symbol that
   1024     ;; has any definition, and act on it with hack-key.
   1025     (map-keymap
   1026      (lambda (char defn)
   1027        (aset prefix1 (length prefix) char)
   1028        (substitute-key-definition-key defn olddef newdef prefix1 keymap))
   1029      scan)))
   1030 
   1031 (compat-defun keymap-set-after (keymap key definition &optional after) ;; <compat-tests:keymap-set-after>
   1032   "Add binding in KEYMAP for KEY => DEFINITION, right after AFTER's binding.
   1033 This is like `keymap-set' except that the binding for KEY is placed
   1034 just after the binding for the event AFTER, instead of at the beginning
   1035 of the map.  Note that AFTER must be an event type (like KEY), NOT a command
   1036 \(like DEFINITION).
   1037 
   1038 If AFTER is t or omitted, the new binding goes at the end of the keymap.
   1039 AFTER should be a single event type--a symbol or a character, not a sequence.
   1040 
   1041 Bindings are always added before any inherited map.
   1042 
   1043 The order of bindings in a keymap matters only when it is used as
   1044 a menu, so this function is not useful for non-menu keymaps."
   1045   (keymap--check key)
   1046   (when (eq after t) (setq after nil)) ; nil and t are treated the same
   1047   (when (stringp after)
   1048     (keymap--check after)
   1049     (setq after (key-parse after)))
   1050   ;; If we're binding this key to another key, then parse that other
   1051   ;; key, too.
   1052   (when (stringp definition)
   1053     (keymap--check definition)
   1054     (setq definition (key-parse definition)))
   1055   (define-key-after keymap (key-parse key) definition
   1056     after))
   1057 
   1058 (compat-defun keymap-lookup ;; <compat-tests:keymap-lookup>
   1059     (keymap key &optional accept-default no-remap position)
   1060   "Return the binding for command KEY.
   1061 KEY is a string that satisfies `key-valid-p'.
   1062 
   1063 If KEYMAP is nil, look up in the current keymaps.  If non-nil, it
   1064 should either be a keymap or a list of keymaps, and only these
   1065 keymap(s) will be consulted.
   1066 
   1067 The binding is probably a symbol with a function definition.
   1068 
   1069 Normally, `keymap-lookup' ignores bindings for t, which act as
   1070 default bindings, used when nothing else in the keymap applies;
   1071 this makes it usable as a general function for probing keymaps.
   1072 However, if the optional second argument ACCEPT-DEFAULT is
   1073 non-nil, `keymap-lookup' does recognize the default bindings,
   1074 just as `read-key-sequence' does.
   1075 
   1076 Like the normal command loop, `keymap-lookup' will remap the
   1077 command resulting from looking up KEY by looking up the command
   1078 in the current keymaps.  However, if the optional third argument
   1079 NO-REMAP is non-nil, `keymap-lookup' returns the unmapped
   1080 command.
   1081 
   1082 If KEY is a key sequence initiated with the mouse, the used keymaps
   1083 will depend on the clicked mouse position with regard to the buffer
   1084 and possible local keymaps on strings.
   1085 
   1086 If the optional argument POSITION is non-nil, it specifies a mouse
   1087 position as returned by `event-start' and `event-end', and the lookup
   1088 occurs in the keymaps associated with it instead of KEY.  It can also
   1089 be a number or marker, in which case the keymap properties at the
   1090 specified buffer position instead of point are used."
   1091   (keymap--check key)
   1092   (when (and keymap position)
   1093     (error "Can't pass in both keymap and position"))
   1094   (if keymap
   1095       (let ((value (lookup-key keymap (key-parse key) accept-default)))
   1096         (if (and (not no-remap)
   1097                    (symbolp value))
   1098             (or (command-remapping value) value)
   1099           value))
   1100     (key-binding (key-parse key) accept-default no-remap position)))
   1101 
   1102 (compat-defun keymap-local-lookup (keys &optional accept-default) ;; <compat-tests:keymap-local-lookup>
   1103   "Return the binding for command KEYS in current local keymap only.
   1104 KEY is a string that satisfies `key-valid-p'.
   1105 
   1106 The binding is probably a symbol with a function definition.
   1107 
   1108 If optional argument ACCEPT-DEFAULT is non-nil, recognize default
   1109 bindings; see the description of `keymap-lookup' for more details
   1110 about this."
   1111   (when-let ((map (current-local-map)))
   1112     (keymap-lookup map keys accept-default)))
   1113 
   1114 (compat-defun keymap-global-lookup (keys &optional accept-default _message) ;; <compat-tests:keymap-global-lookup>
   1115   "Return the binding for command KEYS in current global keymap only.
   1116 KEY is a string that satisfies `key-valid-p'.
   1117 
   1118 The binding is probably a symbol with a function definition.
   1119 This function's return values are the same as those of `keymap-lookup'
   1120 \(which see).
   1121 
   1122 If optional argument ACCEPT-DEFAULT is non-nil, recognize default
   1123 bindings; see the description of `keymap-lookup' for more details
   1124 about this.
   1125 
   1126 NOTE: The compatibility version is not a command."
   1127   (keymap-lookup (current-global-map) keys accept-default))
   1128 
   1129 (compat-defun define-keymap (&rest definitions) ;; <compat-tests:defvar-keymap>
   1130   "Create a new keymap and define KEY/DEFINITION pairs as key bindings.
   1131 The new keymap is returned.
   1132 
   1133 Options can be given as keywords before the KEY/DEFINITION
   1134 pairs.  Available keywords are:
   1135 
   1136 :full      If non-nil, create a chartable alist (see `make-keymap').
   1137              If nil (i.e., the default), create a sparse keymap (see
   1138              `make-sparse-keymap').
   1139 
   1140 :suppress  If non-nil, the keymap will be suppressed (see `suppress-keymap').
   1141              If `nodigits', treat digits like other chars.
   1142 
   1143 :parent    If non-nil, this should be a keymap to use as the parent
   1144              (see `set-keymap-parent').
   1145 
   1146 :keymap    If non-nil, instead of creating a new keymap, the given keymap
   1147              will be destructively modified instead.
   1148 
   1149 :name      If non-nil, this should be a string to use as the menu for
   1150              the keymap in case you use it as a menu with `x-popup-menu'.
   1151 
   1152 :prefix    If non-nil, this should be a symbol to be used as a prefix
   1153              command (see `define-prefix-command').  If this is the case,
   1154              this symbol is returned instead of the map itself.
   1155 
   1156 KEY/DEFINITION pairs are as KEY and DEF in `keymap-set'.  KEY can
   1157 also be the special symbol `:menu', in which case DEFINITION
   1158 should be a MENU form as accepted by `easy-menu-define'.
   1159 
   1160 \(fn &key FULL PARENT SUPPRESS NAME PREFIX KEYMAP &rest [KEY DEFINITION]...)"
   1161   (declare (indent defun))
   1162   (let (full suppress parent name prefix keymap)
   1163     ;; Handle keywords.
   1164     (while (and definitions
   1165                 (keywordp (car definitions))
   1166                 (not (eq (car definitions) :menu)))
   1167       (let ((keyword (pop definitions)))
   1168         (unless definitions
   1169           (error "Missing keyword value for %s" keyword))
   1170         (let ((value (pop definitions)))
   1171           (pcase keyword
   1172             (:full (setq full value))
   1173             (:keymap (setq keymap value))
   1174             (:parent (setq parent value))
   1175             (:suppress (setq suppress value))
   1176             (:name (setq name value))
   1177             (:prefix (setq prefix value))
   1178             (_ (error "Invalid keyword: %s" keyword))))))
   1179 
   1180     (when (and prefix
   1181                (or full parent suppress keymap))
   1182       (error "A prefix keymap can't be defined with :full/:parent/:suppress/:keymap keywords"))
   1183 
   1184     (when (and keymap full)
   1185       (error "Invalid combination: :keymap with :full"))
   1186 
   1187     (let ((keymap (cond
   1188                    (keymap keymap)
   1189                    (prefix (define-prefix-command prefix nil name))
   1190                    (full (make-keymap name))
   1191                    (t (make-sparse-keymap name))))
   1192           seen-keys)
   1193       (when suppress
   1194         (suppress-keymap keymap (eq suppress 'nodigits)))
   1195       (when parent
   1196         (set-keymap-parent keymap parent))
   1197 
   1198       ;; Do the bindings.
   1199       (while definitions
   1200         (let ((key (pop definitions)))
   1201           (unless definitions
   1202             (error "Uneven number of key/definition pairs"))
   1203           (let ((def (pop definitions)))
   1204             (if (eq key :menu)
   1205                 (easy-menu-define nil keymap "" def)
   1206               (if (member key seen-keys)
   1207                   (error "Duplicate definition for key: %S %s" key keymap)
   1208                 (push key seen-keys))
   1209               (keymap-set keymap key def)))))
   1210       keymap)))
   1211 
   1212 (compat-defmacro defvar-keymap (variable-name &rest defs) ;; <compat-tests:defvar-keymap>
   1213   "Define VARIABLE-NAME as a variable with a keymap definition.
   1214 See `define-keymap' for an explanation of the keywords and KEY/DEFINITION.
   1215 
   1216 In addition to the keywords accepted by `define-keymap', this
   1217 macro also accepts a `:doc' keyword, which (if present) is used
   1218 as the variable documentation string.
   1219 
   1220 The `:repeat' keyword can also be specified; it controls the
   1221 `repeat-mode' behavior of the bindings in the keymap.  When it is
   1222 non-nil, all commands in the map will have the `repeat-map'
   1223 symbol property.
   1224 
   1225 More control is available over which commands are repeatable; the
   1226 value can also be a property list with properties `:enter' and
   1227 `:exit', for example:
   1228 
   1229      :repeat (:enter (commands ...) :exit (commands ...))
   1230 
   1231 `:enter' specifies the list of additional commands that only
   1232 enter `repeat-mode'.  When the list is empty, then only the
   1233 commands defined in the map enter `repeat-mode'.  Specifying a
   1234 list of commands is useful when there are commands that have the
   1235 `repeat-map' symbol property, but don't exist in this specific
   1236 map.
   1237 
   1238 `:exit' is a list of commands that exit `repeat-mode'.  When the
   1239 list is empty, no commands in the map exit `repeat-mode'.
   1240 Specifying a list of commands is useful when those commands exist
   1241 in this specific map, but should not have the `repeat-map' symbol
   1242 property.
   1243 
   1244 \(fn VARIABLE-NAME &key DOC FULL PARENT SUPPRESS NAME PREFIX KEYMAP REPEAT &rest [KEY DEFINITION]...)"
   1245   (declare (indent 1))
   1246   (let ((opts nil)
   1247         doc repeat props)
   1248     (while (and defs
   1249                 (keywordp (car defs))
   1250                 (not (eq (car defs) :menu)))
   1251       (let ((keyword (pop defs)))
   1252         (unless defs
   1253           (error "Uneven number of keywords"))
   1254         (cond
   1255          ((eq keyword :doc) (setq doc (pop defs)))
   1256          ((eq keyword :repeat) (setq repeat (pop defs)))
   1257          (t (push keyword opts)
   1258             (push (pop defs) opts)))))
   1259     (unless (zerop (% (length defs) 2))
   1260       (error "Uneven number of key/definition pairs: %s" defs))
   1261 
   1262     (let ((defs defs)
   1263           key seen-keys)
   1264       (while defs
   1265         (setq key (pop defs))
   1266         (pop defs)
   1267         (unless (eq key :menu)
   1268           (if (member key seen-keys)
   1269               (error "Duplicate definition for key '%s' in keymap '%s'"
   1270                      key variable-name)
   1271             (push key seen-keys)))))
   1272 
   1273     (when repeat
   1274       (let ((defs defs)
   1275             def)
   1276         (dolist (def (plist-get repeat :enter))
   1277           (push `(put ',def 'repeat-map ',variable-name) props))
   1278         (while defs
   1279           (pop defs)
   1280           (setq def (pop defs))
   1281           (when (and (memq (car def) '(function quote))
   1282                      (not (memq (cadr def) (plist-get repeat :exit))))
   1283             (push `(put ,def 'repeat-map ',variable-name) props)))))
   1284 
   1285     (let ((defvar-form
   1286            `(defvar ,variable-name
   1287               (define-keymap ,@(nreverse opts) ,@defs)
   1288               ,@(and doc (list doc)))))
   1289       (if props
   1290           `(progn
   1291              ,defvar-form
   1292              ,@(nreverse props))
   1293         defvar-form))))
   1294 
   1295 ;;;; Defined in keymap.c
   1296 
   1297 (compat-defun define-key (keymap key def &optional remove) ;; <compat-tests:define-key>
   1298   "Handle optional argument REMOVE."
   1299   :extended t
   1300   (if (not remove)
   1301       (define-key keymap key def)
   1302     ;; Canonicalize key
   1303     (setq key (key-parse (key-description key)))
   1304     (define-key keymap key nil)
   1305     ;; Split M-key in ESC key
   1306     (setq key (mapcan (lambda (k)
   1307                         (if (and (integerp k) (/= (logand k ?\M-\0) 0))
   1308                             (list ?\e (logxor k ?\M-\0))
   1309                           (list k)))
   1310                       key))
   1311     ;; Delete single keys directly
   1312     (if (length= key 1)
   1313         (delete key keymap)
   1314       ;; Lookup submap and delete key from there
   1315       (let ((submap (lookup-key keymap (vconcat (butlast key)))))
   1316         (unless (keymapp submap)
   1317           (error "Not a keymap for %s" key))
   1318         (when (symbolp submap)
   1319           (setq submap (symbol-function submap)))
   1320         (delete (last key) submap)))
   1321     def))
   1322 
   1323 ;;;; Defined in help.el
   1324 
   1325 (compat-defun substitute-quotes (string) ;; <compat-tests:substitute-quotes>
   1326   "Substitute quote characters for display.
   1327 Each grave accent \\=` is replaced by left quote, and each
   1328 apostrophe \\=' is replaced by right quote.  Left and right quote
   1329 characters are specified by `text-quoting-style'."
   1330   (cond ((eq (text-quoting-style) 'curve)
   1331          (string-replace "`" "‘"
   1332                          (string-replace "'" "’" string)))
   1333         ((eq (text-quoting-style) 'straight)
   1334          (string-replace "`" "'" string))
   1335         (t string)))
   1336 
   1337 ;;;; Defined in button.el
   1338 
   1339 (compat-defun button--properties (callback data help-echo) ;; <compat-tests:buttonize>
   1340   "Helper function."
   1341   (list 'font-lock-face 'button
   1342         'mouse-face 'highlight
   1343         'help-echo help-echo
   1344         'button t
   1345         'follow-link t
   1346         'category t
   1347         'button-data data
   1348         'keymap button-map
   1349         'action callback))
   1350 
   1351 (compat-defun buttonize (string callback &optional data help-echo) ;; <compat-tests:buttonize>
   1352   "Make STRING into a button and return it.
   1353 When clicked, CALLBACK will be called with the DATA as the
   1354 function argument.  If DATA isn't present (or is nil), the button
   1355 itself will be used instead as the function argument.
   1356 
   1357 If HELP-ECHO, use that as the `help-echo' property.
   1358 
   1359 Also see `buttonize-region'."
   1360   (let ((string
   1361          (apply #'propertize string
   1362                 (button--properties callback data help-echo))))
   1363     ;; Add the face to the end so that it can be overridden.
   1364     (add-face-text-property 0 (length string) 'button t string)
   1365     string))
   1366 
   1367 (compat-defun buttonize-region (start end callback &optional data help-echo) ;; <compat-tests:buttonize-region>
   1368   "Make the region between START and END into a button.
   1369 When clicked, CALLBACK will be called with the DATA as the
   1370 function argument.  If DATA isn't present (or is nil), the button
   1371 itself will be used instead as the function argument.
   1372 
   1373 If HELP-ECHO, use that as the `help-echo' property.
   1374 
   1375 Also see `buttonize'."
   1376   (add-text-properties start end (button--properties callback data help-echo))
   1377   (add-face-text-property start end 'button t))
   1378 
   1379 ;;;; Defined in rmc.el
   1380 
   1381 (compat-defun read-multiple-choice  ;; <compat-tests:read-multiple-choice>
   1382     (prompt choices &optional _help-str _show-help long-form)
   1383     "Handle LONG-FORM argument."
   1384   :extended t
   1385   (if (not long-form)
   1386       (read-multiple-choice prompt choices)
   1387     (let ((answer
   1388            (completing-read
   1389             (concat prompt " ("
   1390                     (mapconcat #'identity (mapcar #'cadr choices) "/")
   1391                     ") ")
   1392             (mapcar #'cadr choices) nil t)))
   1393       (catch 'found
   1394         (dolist (c choices)
   1395           (when (equal answer (cadr c))
   1396             (throw 'found c)))))))
   1397 
   1398 ;;;; Defined in paragraphs.el
   1399 
   1400 (compat-defun count-sentences (start end) ;; <compat-tests:count-sentences>
   1401   "Count sentences in current buffer from START to END."
   1402   (let ((sentences 0)
   1403         (inhibit-field-text-motion t))
   1404     (save-excursion
   1405       (save-restriction
   1406         (narrow-to-region start end)
   1407         (goto-char (point-min))
   1408         (while (ignore-errors (forward-sentence))
   1409           (setq sentences (1+ sentences)))
   1410         (when (/= (skip-chars-backward " \t\n") 0)
   1411           (setq sentences (1- sentences)))
   1412         sentences))))
   1413 
   1414 ;;;; Defined in cl-lib.el
   1415 
   1416 (compat-defun cl-constantly (value) ;; <compat-tests:cl-constantly>
   1417   "Return a function that takes any number of arguments, but returns VALUE."
   1418   :feature cl-lib
   1419   (lambda (&rest _) value))
   1420 
   1421 ;;;; Defined in cl-macs.el
   1422 
   1423 (compat-defmacro cl-with-gensyms (names &rest body) ;; <compat-tests:cl-with-gensyms>
   1424   "Bind each of NAMES to an uninterned symbol and evaluate BODY."
   1425   ;; No :feature since macro is autoloaded
   1426   (declare (debug (sexp body)) (indent 1))
   1427   `(let ,(cl-loop for name in names collect
   1428                   `(,name (gensym (symbol-name ',name))))
   1429      ,@body))
   1430 
   1431 (compat-defmacro cl-once-only (names &rest body) ;; <compat-tests:cl-once-only>
   1432   "Generate code to evaluate each of NAMES just once in BODY.
   1433 
   1434 This macro helps with writing other macros.  Each of names is
   1435 either (NAME FORM) or NAME, which latter means (NAME NAME).
   1436 During macroexpansion, each NAME is bound to an uninterned
   1437 symbol.  The expansion evaluates each FORM and binds it to the
   1438 corresponding uninterned symbol.
   1439 
   1440 For example, consider this macro:
   1441 
   1442     (defmacro my-cons (x)
   1443       (cl-once-only (x)
   1444         \\=`(cons ,x ,x)))
   1445 
   1446 The call (my-cons (pop y)) will expand to something like this:
   1447 
   1448     (let ((g1 (pop y)))
   1449       (cons g1 g1))
   1450 
   1451 The use of `cl-once-only' ensures that the pop is performed only
   1452 once, as intended.
   1453 
   1454 See also `macroexp-let2'."
   1455   ;; No :feature since macro is autoloaded
   1456   (declare (debug (sexp body)) (indent 1))
   1457   (setq names (mapcar #'ensure-list names))
   1458   (let ((our-gensyms (cl-loop for _ in names collect (gensym))))
   1459     `(let ,(cl-loop for sym in our-gensyms collect `(,sym (gensym)))
   1460        `(let ,(list
   1461                ,@(cl-loop for name in names for gensym in our-gensyms
   1462                           for to-eval = (or (cadr name) (car name))
   1463                           collect ``(,,gensym ,,to-eval)))
   1464           ,(let ,(cl-loop for name in names for gensym in our-gensyms
   1465                           collect `(,(car name) ,gensym))
   1466              ,@body)))))
   1467 
   1468 ;;;; Defined in ert-x.el
   1469 
   1470 (compat-defmacro ert-with-temp-file (name &rest body) ;; <compat-tests:ert-with-temp-file>
   1471   "Bind NAME to the name of a new temporary file and evaluate BODY.
   1472 Delete the temporary file after BODY exits normally or
   1473 non-locally.  NAME will be bound to the file name of the temporary
   1474 file.
   1475 
   1476 The following keyword arguments are supported:
   1477 
   1478 :prefix STRING  If non-nil, pass STRING to `make-temp-file' as
   1479                 the PREFIX argument.  Otherwise, use the value of
   1480                 `ert-temp-file-prefix'.
   1481 
   1482 :suffix STRING  If non-nil, pass STRING to `make-temp-file' as the
   1483                 SUFFIX argument.  Otherwise, use the value of
   1484                 `ert-temp-file-suffix'; if the value of that
   1485                 variable is nil, generate a suffix based on the
   1486                 name of the file that `ert-with-temp-file' is
   1487                 called from.
   1488 
   1489 :text STRING    If non-nil, pass STRING to `make-temp-file' as
   1490                 the TEXT argument.
   1491 
   1492 :buffer SYMBOL  Open the temporary file using `find-file-noselect'
   1493                 and bind SYMBOL to the buffer.  Kill the buffer
   1494                 after BODY exits normally or non-locally.
   1495 
   1496 :coding CODING  If non-nil, bind `coding-system-for-write' to CODING
   1497                 when executing BODY.  This is handy when STRING includes
   1498                 non-ASCII characters or the temporary file must have a
   1499                 specific encoding or end-of-line format.
   1500 
   1501 See also `ert-with-temp-directory'."
   1502   :feature ert-x
   1503   (declare (indent 1) (debug (symbolp body)))
   1504   (cl-check-type name symbol)
   1505   (let (keyw prefix suffix directory text extra-keywords buffer coding)
   1506     (while (keywordp (setq keyw (car body)))
   1507       (setq body (cdr body))
   1508       (pcase keyw
   1509         (:prefix (setq prefix (pop body)))
   1510         (:suffix (setq suffix (pop body)))
   1511         ;; This is only for internal use by `ert-with-temp-directory'
   1512         ;; and is therefore not documented.
   1513         (:directory (setq directory (pop body)))
   1514         (:text (setq text (pop body)))
   1515         (:buffer (setq buffer (pop body)))
   1516         (:coding (setq coding (pop body)))
   1517         (_ (push keyw extra-keywords) (pop body))))
   1518     (when extra-keywords
   1519       (error "Invalid keywords: %s" (mapconcat #'symbol-name extra-keywords " ")))
   1520     (let ((temp-file (make-symbol "temp-file"))
   1521           (prefix (or prefix "emacs-test-"))
   1522           (suffix (or suffix
   1523                       (thread-last
   1524                         (file-name-base (or (macroexp-file-name) buffer-file-name))
   1525                         (replace-regexp-in-string (rx string-start
   1526                                                       (group (+? not-newline))
   1527                                                       (regexp "-?tests?")
   1528                                                       string-end)
   1529                                                   "\\1")
   1530                         (concat "-")))))
   1531       `(let* ((coding-system-for-write ,(or coding coding-system-for-write))
   1532               (,temp-file (,(if directory 'file-name-as-directory 'identity)
   1533                            (,(if (fboundp 'compat--make-temp-file)
   1534                                  'compat--make-temp-file 'make-temp-file)
   1535                             ,prefix ,directory ,suffix ,text)))
   1536               (,name ,(if directory
   1537                           `(file-name-as-directory ,temp-file)
   1538                         temp-file))
   1539               ,@(when buffer
   1540                   (list `(,buffer (find-file-literally ,temp-file)))))
   1541          (unwind-protect
   1542              (progn ,@body)
   1543            (ignore-errors
   1544              ,@(when buffer
   1545                  (list `(with-current-buffer ,buffer
   1546                           (set-buffer-modified-p nil))
   1547                        `(kill-buffer ,buffer))))
   1548            (ignore-errors
   1549              ,(if directory
   1550                   `(delete-directory ,temp-file :recursive)
   1551                 `(delete-file ,temp-file))))))))
   1552 
   1553 (compat-defmacro ert-with-temp-directory (name &rest body) ;; <compat-tests:ert-with-temp-directory>
   1554   "Bind NAME to the name of a new temporary directory and evaluate BODY.
   1555 Delete the temporary directory after BODY exits normally or
   1556 non-locally.
   1557 
   1558 NAME is bound to the directory name, not the directory file
   1559 name.  (In other words, it will end with the directory delimiter;
   1560 on Unix-like systems, it will end with \"/\".)
   1561 
   1562 The same keyword arguments are supported as in
   1563 `ert-with-temp-file' (which see), except for :text."
   1564   :feature ert-x
   1565   (declare (indent 1) (debug (symbolp body)))
   1566   (let ((tail body) keyw)
   1567     (while (keywordp (setq keyw (car tail)))
   1568       (setq tail (cddr tail))
   1569       (pcase keyw (:text (error "Invalid keyword for directory: :text")))))
   1570   `(ert-with-temp-file ,name
   1571      :directory t
   1572      ,@body))
   1573 
   1574 ;;;; Defined in wid-edit.el
   1575 
   1576 (compat-guard (not (fboundp 'widget-key-validate)) ;; <compat-tests:widget-key>
   1577   :feature wid-edit
   1578   (defvar widget-key-prompt-value-history nil
   1579     "History of input to `widget-key-prompt-value'.")
   1580   (define-widget 'key 'editable-field
   1581     "A key sequence."
   1582     :prompt-value 'widget-field-prompt-value
   1583     :match 'widget-key-valid-p
   1584     :format "%{%t%}: %v"
   1585     :validate 'widget-key-validate
   1586     :keymap widget-key-sequence-map
   1587     :help-echo "C-q: insert KEY, EVENT, or CODE; RET: enter value"
   1588     :tag "Key")
   1589   (defun widget-key-valid-p (_widget value)
   1590     (key-valid-p value))
   1591   (defun widget-key-validate (widget)
   1592     (unless (and (stringp (widget-value widget))
   1593                  (key-valid-p (widget-value widget)))
   1594       (widget-put widget :error (format "Invalid key: %S"
   1595                                         (widget-value widget)))
   1596       widget)))
   1597 
   1598 (provide 'compat-29)
   1599 ;;; compat-29.el ends here