commit 040b5a18fc327ab3939b668947bd89dd5086af15 Author: Eli Zaretskii Date: Sun Jul 5 08:04:20 2026 +0300 ; * etc/NEWS: Fix wording of last change. diff --git a/etc/NEWS b/etc/NEWS index c705b5365f4..80bb25183bd 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -182,7 +182,7 @@ the currently selected item in the list view to the kill-ring. ** Rmail +++ -*** Files named "RMAIL" or ending in ".rmail" now visit in Rmail mode. +*** Files named "RMAIL" or ending in ".rmail" are now visited in Rmail mode. You can now visit such files in Rmail mode using ordinary file-visiting commands, such as 'C-x C-f'. commit 35af392aee538254f92fb2386f1dd69c7d0fb82b Author: Paul Nelson Date: Sun Jun 21 18:19:26 2026 +0200 Keep Rmail collection buffers unibyte * lisp/mail/rmail.el (rmail-enable-multibyte): Remove it. (rmail-mode): Do not bind 'rmail-enable-multibyte'. Do not make the collection buffer multibyte. (Bug#81274) (rmail-revert): Default 'coding-system-for-read' to 'no-conversion, as in 'rmail'; remove corresponding FIXME comment. (rmail-get-new-mail): Do not bind 'rmail-enable-multibyte'. * lisp/files.el (auto-mode-alist): Add Rmail file pattern. * lisp/international/mule-conf.el (file-coding-system-alist): Add Rmail file pattern. * doc/emacs/rmail.texi (Rmail Files): Document that "RMAIL" and "*.rmail" files now visit in Rmail mode automatically. * etc/NEWS: Announce the changes. * test/lisp/mail/rmail-tests.el (rmail-mode-makes-collection-buffer-unibyte): New test. diff --git a/doc/emacs/rmail.texi b/doc/emacs/rmail.texi index d82cd177fa6..4c3989f51b9 100644 --- a/doc/emacs/rmail.texi +++ b/doc/emacs/rmail.texi @@ -451,6 +451,10 @@ in Rmail mode. You can use @kbd{M-x rmail-input} even when not in Rmail, but it is easier to type @kbd{C-u M-x rmail}, which does the same thing. + Files named @file{RMAIL} or ending in @file{.rmail} are visited in +Rmail mode automatically by ordinary file-visiting commands, such as +@kbd{C-x C-f}. + The file you read with @kbd{i} should normally be a valid mbox file. If it is not, Rmail tries to convert its text to mbox format, and visits the converted text in the buffer. If you save the buffer, that diff --git a/etc/NEWS b/etc/NEWS index ce53ea51f44..c705b5365f4 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -179,6 +179,12 @@ as well as the default nil and arbitrary user functions. The new command 'newsticker-copy-url', bound to 'w', adds the URL of the currently selected item in the list view to the kill-ring. +** Rmail + ++++ +*** Files named "RMAIL" or ending in ".rmail" now visit in Rmail mode. +You can now visit such files in Rmail mode using ordinary file-visiting +commands, such as 'C-x C-f'. * New Modes and Packages in Emacs 32.1 diff --git a/lisp/files.el b/lisp/files.el index 149a3e80866..fa120f5c8ae 100644 --- a/lisp/files.el +++ b/lisp/files.el @@ -3155,6 +3155,7 @@ since only a single case-insensitive search through the alist is made." ("\\.srt\\'" . srecode-template-mode) ("\\.prolog\\'" . prolog-mode) ("\\.tar\\'" . tar-mode) + ("\\(?:\\.rmail\\|\\(?:\\`\\|[/\\]\\)RMAIL\\)\\'" . rmail-mode) ;; The list of archive file extensions should be in sync with ;; `auto-coding-alist' with `no-conversion' coding system. ("\\.\\(\ diff --git a/lisp/international/mule-conf.el b/lisp/international/mule-conf.el index 818ae9728ce..f6347d3765c 100644 --- a/lisp/international/mule-conf.el +++ b/lisp/international/mule-conf.el @@ -1644,6 +1644,8 @@ for decoding and encoding files, process I/O, etc." ;; the beginning of a doc string, work. ("\\(\\`\\|/\\)loaddefs.el\\'" . (raw-text . raw-text-unix)) ("\\.tar\\'" . (no-conversion . no-conversion)) + ("\\(?:\\.rmail\\|\\(?:\\`\\|[/\\]\\)RMAIL\\)\\'" + . (no-conversion . no-conversion)) ( "\\.po[tx]?\\'\\|\\.po\\." . po-find-file-coding-system) ("\\.\\(tex\\|ltx\\|dtx\\|drv\\)\\'" . latexenc-find-file-coding-system) ("" . (undecided . nil)))) diff --git a/lisp/mail/rmail.el b/lisp/mail/rmail.el index c168d6b750e..9f495ddf0f8 100644 --- a/lisp/mail/rmail.el +++ b/lisp/mail/rmail.el @@ -1259,8 +1259,6 @@ The buffer is expected to be narrowed to just the header of the message." (defun rmail-mode-kill-summary () (if rmail-summary-buffer (kill-buffer rmail-summary-buffer))) -(defvar rmail-enable-multibyte) ; dynamically bound - ;;;###autoload (defun rmail-mode () "Rmail Mode is used by \\\\[rmail] for editing Rmail files. @@ -1317,11 +1315,9 @@ Instead, these commands are available: (rmail-mode-2) (when (and finding-rmail-file (null coding-system-for-read)) - (let ((rmail-enable-multibyte t)) - (rmail-require-mime-maybe) - (rmail-convert-file-maybe) - (goto-char (point-max)) - (set-buffer-multibyte t))) + (rmail-require-mime-maybe) + (rmail-convert-file-maybe) + (goto-char (point-max))) (rmail-set-message-counters) (rmail-show-message rmail-total-messages) (when finding-rmail-file @@ -1504,10 +1500,8 @@ If so restore the actual mbox message collection." (defun rmail-revert (arg noconfirm) (set-buffer rmail-buffer) (let* ((revert-buffer-function (default-value 'revert-buffer-function)) - (rmail-enable-multibyte enable-multibyte-characters) - ;; See similar code in `rmail'. - ;; FIXME needs updating? - (coding-system-for-read (and rmail-enable-multibyte 'raw-text)) + (coding-system-for-read + (or coding-system-for-read 'no-conversion)) (before-revert-hook 'rmail-swap-buffers-maybe)) ;; Call our caller again, but this time it does the default thing. (when (revert-buffer arg noconfirm) @@ -1515,7 +1509,6 @@ If so restore the actual mbox message collection." ;; reparse the messages. (set-buffer rmail-buffer) (rmail-mode-2) - ;; Convert all or part to Babyl file if possible. (rmail-convert-file-maybe) (goto-char (point-max)) (rmail-set-message-counters) @@ -1723,7 +1716,6 @@ not be a new one). It returns non-nil if it got any new messages." (or (eq buffer-undo-list t) (setq buffer-undo-list nil)) (let ((all-files (if file-name (list file-name) rmail-inbox-list)) - (rmail-enable-multibyte t) found) (unwind-protect (progn diff --git a/test/lisp/mail/rmail-tests.el b/test/lisp/mail/rmail-tests.el index 5877c562001..8e888a087fd 100644 --- a/test/lisp/mail/rmail-tests.el +++ b/test/lisp/mail/rmail-tests.el @@ -31,5 +31,22 @@ (symbol-function 'rmail-edit-current-message)))) +(ert-deftest rmail-mode-makes-collection-buffer-unibyte () + (with-temp-buffer + (set-buffer-multibyte nil) + (insert "From sender@example.fr Tue Jun 9 12:00:00 2026\n" + "From: sender@example.fr\n" + "To: recipient@example.com\n" + "Subject: lunch\n" + "Date: Tue, 09 Jun 2026 12:00:00 +0000\n" + "\n" + "Caf\xe9\n" + "\n") + (rmail-mode) + (with-current-buffer (if (rmail-buffers-swapped-p) + rmail-view-buffer + rmail-buffer) + (should-not enable-multibyte-characters)))) + (provide 'rmail-tests) ;;; rmail-tests.el ends here commit 2a2675fd3977b872d8c6b3efb8cc1d93c9e6b4fa Author: Eshel Yaron Date: Thu Jul 2 18:01:06 2026 +0200 New macro 'elisp-scope-define-function-spec' (bug#81355) * lisp/emacs-lisp/elisp-scope.el (elisp-scope--analyze-function-args): New helper function. (elisp-scope-define-function-spec): New macro. Convert simple function analyzers to use it. * etc/NEWS: Announce it. diff --git a/etc/NEWS b/etc/NEWS index 7733c5e8eba..ce53ea51f44 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -246,6 +246,13 @@ method call returns with a D-Bus error; the error is passed as argument. which the defined analyzer should apply (or a single symbol, as before). This makes it easy to specify the same analyzer for multiple functions. +*** New macro 'elisp-scope-define-function-spec'. +This macro in a declarative alternative to +'elisp-scope-define-function-analyzer'. It lets you tell 'elisp-scope' +how to analyze the arguments of a function by declaring the +specification of each argument, rather than implementing an analyzer +function as you would with 'elisp-scope-define-function-analyzer'. + * Changes in Emacs 32.1 on Non-Free Operating Systems diff --git a/lisp/emacs-lisp/elisp-scope.el b/lisp/emacs-lisp/elisp-scope.el index 4d3c39850b4..a81a7dfacaf 100644 --- a/lisp/emacs-lisp/elisp-scope.el +++ b/lisp/emacs-lisp/elisp-scope.el @@ -108,7 +108,7 @@ ;; ;; When analyzer functions invoke `elisp-scope-1/n' to analyze some ;; sub-forms, they specify the OUTSPEC argument to convey information -;; but the expected value of the evaluated sub-form(s), so +;; about the expected value of the evaluated sub-form(s), so ;; `elisp-scope-1/n' will know what to do with a sub-form that is just ;; (quoted) data. For example, the analyzer function for ;; `face-attribute' calls `elisp-scope-1' to analyze its first argument @@ -130,6 +130,10 @@ ;; ;; See also the docstring of `elisp-scope-1' for details about the ;; format of the `outspec' argument. +;; +;; To define custom analyzers, you can use the macros +;; `elisp-scope-define-function-spec' and +;; `elisp-scope-define-macro-analyzer', which see. ;;; Code: @@ -1608,6 +1612,31 @@ ARGS bound to the analyzed arguments." (declare (indent defun)) `(elisp-scope--define-function-analyzer ,fsym ,args function ,@body)) +(defun elisp-scope--analyze-function-args (args specs &optional rest-spec) + (while (and args specs) (elisp-scope-1 (pop args) (pop specs))) + (dolist (arg args) (elisp-scope-1 arg rest-spec))) + +(defmacro elisp-scope-define-function-spec (fsym arg-specs &optional rest-spec) + "Specify how to analyze arguments of FSYM. +FSYM is a function symbol or a list of function symbols, and ARG-SPECS +is a list of output specs corresponding to the arguments of FSYM. +Optional argument REST-SPEC is the output spec of any remaining +arguments after those specified by ARG-SPECS. + +For example, the following form says that the functions `foo' and `bar' +take a face name as their second argument, and that all arguments after +the second are macro names. The first argument remains unspecified. + + (elisp-scope-define-function-spec (foo bar) + (nil (symbol . face)) + (symbol . macro))" + (declare (indent 1)) + `(elisp-scope-define-function-analyzer ,fsym (&rest args) + (elisp-scope--analyze-function-args args ',arg-specs ',rest-spec))) + +(elisp-scope-define-function-spec elisp-scope--analyze-function-args + (nil (repeat . spec) spec)) + (defmacro elisp-scope-define-macro-analyzer (fsym args &rest body) "Define an analyzer function for macro FSYM. FSYM is a symbol, or a list of symbols that all share the same analyzer. @@ -1661,14 +1690,10 @@ ARGS bound to the analyzed arguments." (elisp-scope-1 (or (elisp-scope--unquote form) form)) (elisp-scope-1 lexical)) -(elisp-scope-define-function-analyzer (funcall apply) (&optional f &rest args) - (elisp-scope-1 f '(symbol . function)) - (elisp-scope-n args)) +(elisp-scope-define-function-spec (funcall apply) ((symbol . function))) -(elisp-scope-define-function-analyzer defalias (&optional sym def docstring) - (elisp-scope-1 sym '(symbol . defun)) - (elisp-scope-1 def '(symbol . defun)) - (elisp-scope-1 docstring)) +(elisp-scope-define-function-spec defalias + ((symbol . defun) (symbol . defun))) (elisp-scope-define-function-analyzer oclosure--define (&optional name docstring parent-names slots &rest props) @@ -1688,108 +1713,81 @@ ARGS bound to the analyzed arguments." (setq props (cddr props))) (when props (elisp-scope-n props))) -(elisp-scope-define-function-analyzer define-charset - (&optional name docstring &rest props) - (elisp-scope-1 name '(symbol . defcharset)) - (elisp-scope-1 docstring) - (elisp-scope-n props)) - -(elisp-scope-define-function-analyzer define-charset-alias - (&optional alias charset) - (elisp-scope-1 alias '(symbol . defcharset)) - (elisp-scope-1 charset '(symbol . charset))) - -(elisp-scope-define-function-analyzer - (charset-chars - charset-description charset-info charset-iso-final-char - charset-long-name charset-plist charset-short-name - get-charset-property put-charset-property list-charset-chars - set-charset-plist set-charset-priority unify-charset - locale-charset-to-coding-system) - (&optional charset &rest rest) - (elisp-scope-1 charset '(symbol . charset)) - (elisp-scope-n rest)) - -(elisp-scope-define-function-analyzer define-coding-system - (&optional name &rest rest) - (elisp-scope-1 name '(symbol . defcoding)) - (mapc #'elisp-scope-1 rest)) - -(elisp-scope-define-function-analyzer define-coding-system-alias - (&optional alias coding-system) - (elisp-scope-1 alias '(symbol . defcoding)) - (elisp-scope-1 coding-system '(symbol . coding))) - -(elisp-scope-define-function-analyzer - (decode-coding-region encode-coding-region) - (&optional start end coding-system &rest rest) - (elisp-scope-1 start) - (elisp-scope-1 end) - (elisp-scope-1 coding-system '(symbol . coding)) - (elisp-scope-n rest)) - -(elisp-scope-define-function-analyzer - (decode-coding-string encode-coding-char encode-coding-string) - (&optional string coding-system &rest rest) - (elisp-scope-1 string) - (elisp-scope-1 coding-system '(symbol . coding)) - (elisp-scope-n rest)) - -(elisp-scope-define-function-analyzer - (coding-system-mnemonic - add-to-coding-system-list - check-coding-system - coding-system-aliases - coding-system-base - coding-system-category - coding-system-change-eol-conversion - coding-system-change-text-conversion - coding-system-charset-list - coding-system-doc-string - coding-system-eol-type - coding-system-eol-type-mnemonic - coding-system-get - coding-system-plist - coding-system-post-read-conversion - coding-system-pre-write-conversion - coding-system-put - coding-system-translation-table-for-decode - coding-system-translation-table-for-encode - coding-system-type - describe-coding-system - prefer-coding-system - print-coding-system - print-coding-system-briefly - revert-buffer-with-coding-system - set-buffer-file-coding-system - set-clipboard-coding-system - set-coding-system-priority - set-default-coding-systems - set-file-name-coding-system - set-keyboard-coding-system - set-next-selection-coding-system - set-selection-coding-system - set-terminal-coding-system - universal-coding-system-argument) - (&optional coding-system &rest rest) - (elisp-scope-1 coding-system '(symbol . coding)) - (elisp-scope-n rest)) - -(elisp-scope-define-function-analyzer - (thing-at-point - forward-thing beginning-of-thing end-of-thing bounds-of-thing-at-point) - (&optional thing no-props) - (elisp-scope-1 thing '(symbol . thing)) - (elisp-scope-1 no-props)) - -(elisp-scope-define-function-analyzer bounds-of-thing-at-mouse (&optional event thing) - (elisp-scope-1 event) - (elisp-scope-1 thing '(symbol . thing))) - -(elisp-scope-define-function-analyzer thing-at-mouse (&optional event thing no-props) - (elisp-scope-1 event) - (elisp-scope-1 thing '(symbol . thing)) - (elisp-scope-1 no-props)) +(elisp-scope-define-function-spec define-charset ((symbol . defcharset))) + +(elisp-scope-define-function-spec define-charset-alias + ((symbol . defcharset) (symbol . charset))) + +(elisp-scope-define-function-spec + (charset-chars + charset-description charset-info charset-iso-final-char + charset-long-name charset-plist charset-short-name + get-charset-property put-charset-property list-charset-chars + set-charset-plist set-charset-priority unify-charset + locale-charset-to-coding-system) + ((symbol . charset))) + +(elisp-scope-define-function-spec define-coding-system ((symbol . defcoding))) + +(elisp-scope-define-function-spec define-coding-system-alias + ((symbol . defcoding) (symbol . coding))) + +(elisp-scope-define-function-spec + (decode-coding-region encode-coding-region) + (nil nil (symbol . coding))) + +(elisp-scope-define-function-spec + (decode-coding-string encode-coding-char encode-coding-string) + (nil (symbol . coding))) + +(elisp-scope-define-function-spec + (coding-system-mnemonic + add-to-coding-system-list + check-coding-system + coding-system-aliases + coding-system-base + coding-system-category + coding-system-change-eol-conversion + coding-system-change-text-conversion + coding-system-charset-list + coding-system-doc-string + coding-system-eol-type + coding-system-eol-type-mnemonic + coding-system-get + coding-system-plist + coding-system-post-read-conversion + coding-system-pre-write-conversion + coding-system-put + coding-system-translation-table-for-decode + coding-system-translation-table-for-encode + coding-system-type + describe-coding-system + prefer-coding-system + print-coding-system + print-coding-system-briefly + revert-buffer-with-coding-system + set-buffer-file-coding-system + set-clipboard-coding-system + set-coding-system-priority + set-default-coding-systems + set-file-name-coding-system + set-keyboard-coding-system + set-next-selection-coding-system + set-selection-coding-system + set-terminal-coding-system + universal-coding-system-argument) + ((symbol . coding))) + +(elisp-scope-define-function-spec + (thing-at-point + forward-thing beginning-of-thing end-of-thing bounds-of-thing-at-point) + ((symbol . thing))) + +(elisp-scope-define-function-spec bounds-of-thing-at-mouse + (nil (symbol . thing))) + +(elisp-scope-define-function-spec thing-at-mouse + (nil (symbol . thing))) (elisp-scope-define-function-analyzer custom-declare-variable (sym default doc &rest args) (elisp-scope-1 sym '(symbol . defvar)) @@ -1830,14 +1828,10 @@ ARGS bound to the analyzed arguments." (setq args (cddr args))) (when args (elisp-scope-n args))) -(elisp-scope-define-function-analyzer cl-typep (val type) - (elisp-scope-1 val) - (elisp-scope-1 type 'cl-type)) +(elisp-scope-define-function-spec cl-typep (nil cl-type)) -(elisp-scope-define-function-analyzer pulse-momentary-highlight-region (start end &optional face) - (elisp-scope-1 start) - (elisp-scope-1 end) - (elisp-scope-1 face '(symbol . face))) +(elisp-scope-define-function-spec pulse-momentary-highlight-region + (nil nil (symbol . face))) (elisp-scope--define-function-analyzer throw (&optional tag val) non-local-exit (elisp-scope-1 tag '(symbol . throw-tag)) @@ -1855,11 +1849,9 @@ ARGS bound to the analyzed arguments." (&rest rest) non-local-exit (elisp-scope-n rest)) -(elisp-scope-define-function-analyzer run-hooks (&rest hooks) - (dolist (hook hooks) (elisp-scope-1 hook '(symbol . free-variable)))) +(elisp-scope-define-function-spec run-hooks () (symbol . free-variable)) -(elisp-scope-define-function-analyzer fboundp (&optional symbol) - (elisp-scope-1 symbol '(symbol . function))) +(elisp-scope-define-function-spec fboundp ((symbol . function))) (elisp-scope-define-function-analyzer overlay-put (&optional ov prop val) (elisp-scope-1 ov) @@ -1870,59 +1862,44 @@ ARGS bound to the analyzed arguments." (when (memq (elisp-scope--sym-bare q) '(face mouse-face)) 'face)))) -(elisp-scope-define-function-analyzer add-face-text-property (&optional start end face &rest rest) - (elisp-scope-1 start) - (elisp-scope-1 end) - (elisp-scope-1 face 'face) - (elisp-scope-n rest)) - -(elisp-scope-define-function-analyzer - (facep - check-face face-id face-differs-from-default-p - face-name face-all-attributes face-attribute - face-foreground face-background face-stipple - face-underline-p face-inverse-video-p face-bold-p - face-italic-p face-extend-p face-documentation - set-face-documentation set-face-attribute - set-face-font set-face-background set-face-foreground - set-face-stipple set-face-underline set-face-inverse-video - set-face-bold set-face-italic set-face-extend) - (&optional face &rest rest) - (elisp-scope-1 face '(symbol . face)) - (elisp-scope-n rest)) - -(elisp-scope-define-function-analyzer - (boundp - set symbol-value define-abbrev-table - special-variable-p local-variable-p - local-variable-if-set-p add-variable-watcher - get-variable-watchers remove-variable-watcher - default-value set-default make-local-variable - buffer-local-value add-to-list add-to-history find-buffer - customize-set-variable set-variable - add-hook remove-hook run-hook-with-args run-hook-wrapped) - (&optional var &rest rest) - (elisp-scope-1 var '(symbol . free-variable)) - (elisp-scope-n rest)) - -(elisp-scope-define-function-analyzer defvaralias (new base &optional docstring) - (elisp-scope-1 new '(symbol . defvar)) - (elisp-scope-1 base '(symbol . free-variable)) - (elisp-scope-1 docstring)) - -(elisp-scope-define-function-analyzer define-error (&optional name message parent) - (elisp-scope-1 name '(symbol . defcondition)) - (elisp-scope-1 message) - (elisp-scope-1 parent '(or (symbol . condition) - (repeat . (symbol . condition))))) - -(elisp-scope-define-function-analyzer (featurep require) (feature &rest rest) - (elisp-scope-1 feature '(symbol . feature)) - (elisp-scope-n rest)) - -(elisp-scope-define-function-analyzer provide (feature &rest rest) - (elisp-scope-1 feature '(symbol . deffeature)) - (elisp-scope-n rest)) +(elisp-scope-define-function-spec add-face-text-property (nil nil face)) + +(elisp-scope-define-function-spec + (facep + check-face face-id face-differs-from-default-p + face-name face-all-attributes face-attribute + face-foreground face-background face-stipple + face-underline-p face-inverse-video-p face-bold-p + face-italic-p face-extend-p face-documentation + set-face-documentation set-face-attribute + set-face-font set-face-background set-face-foreground + set-face-stipple set-face-underline set-face-inverse-video + set-face-bold set-face-italic set-face-extend) + ((symbol . face))) + +(elisp-scope-define-function-spec + (boundp + set symbol-value define-abbrev-table + special-variable-p local-variable-p + local-variable-if-set-p add-variable-watcher + get-variable-watchers remove-variable-watcher + default-value set-default make-local-variable + buffer-local-value add-to-list add-to-history find-buffer + customize-set-variable set-variable + add-hook remove-hook run-hook-with-args run-hook-wrapped) + ((symbol . free-variable))) + +(elisp-scope-define-function-spec defvaralias + ((symbol . defvar) (symbol . free-variable))) + +(elisp-scope-define-function-spec define-error + ((symbol . defcondition) nil + (or (symbol . condition) + (repeat . (symbol . condition))))) + +(elisp-scope-define-function-spec (featurep require) ((symbol . feature))) + +(elisp-scope-define-function-spec provide ((symbol . deffeature))) (elisp-scope-define-function-analyzer (put-text-property remove-overlays) (&optional beg end prop val obj) @@ -1948,37 +1925,26 @@ ARGS bound to the analyzed arguments." (setq props (cddr props))) (when props (elisp-scope-n props))) -(elisp-scope-define-function-analyzer eieio-defclass-internal - (&optional name superclasses slots options) - (elisp-scope-1 name '(symbol . deftype)) - (elisp-scope-1 superclasses '(repeat . (symbol . type))) - (elisp-scope-1 slots - '(repeat - cons - (symbol . slot) - plist - (:initform . code) - (:initarg . (symbol . constant)) - (:accessor . (symbol . defun)) - (:allocation . code) - (:writer . (symbol . function)) - (:reader . (symbol . function)) - (:type . cl-type) - ;; TODO: add (:custom . custom-type) - )) - (elisp-scope-1 options)) - -(elisp-scope-define-function-analyzer cl-struct-define - (&optional name doc parent type named slots children tag print) - (elisp-scope-1 name '(symbol . deftype)) - (elisp-scope-1 doc) - (elisp-scope-1 parent '(symbol . type)) - (elisp-scope-1 type) - (elisp-scope-1 named) - (elisp-scope-1 slots) ;TODO: Specify type of `slots'. - (elisp-scope-1 children) - (elisp-scope-1 tag) - (elisp-scope-1 print)) +(elisp-scope-define-function-spec eieio-defclass-internal + ((symbol . deftype) + (repeat . (symbol . type)) + (repeat + cons + (symbol . slot) + plist + (:initform . code) + (:initarg . (symbol . constant)) + (:accessor . (symbol . defun)) + (:allocation . code) + (:writer . (symbol . function)) + (:reader . (symbol . function)) + (:type . cl-type) + ;; TODO: add (:custom . custom-type) + ))) + +;; TODO: Specify spec of the `slots' argument. +(elisp-scope-define-function-spec cl-struct-define + ((symbol . deftype) nil (symbol . type))) (elisp-scope-define-function-analyzer define-widget (name class doc &rest args) (elisp-scope-1 name '(symbol . widget-type-definition)) @@ -1995,62 +1961,43 @@ ARGS bound to the analyzed arguments." (setq args (cddr args))) (when args (elisp-scope-n args))) -(elisp-scope-define-function-analyzer - (provide-theme - enable-theme disable-theme load-theme custom-theme-p) - (name &rest rest) - (elisp-scope-1 name '(symbol . theme)) - (elisp-scope-n rest)) +(elisp-scope-define-function-spec + (provide-theme enable-theme disable-theme load-theme custom-theme-p) + ((symbol . theme))) -(elisp-scope-define-function-analyzer custom-theme-set-variables (theme &rest args) - (elisp-scope-1 theme '(symbol . theme)) - (dolist (arg args) - (elisp-scope-1 - arg - '(cons (symbol . free-variable) . - (cons code . - (or (cons t . - (cons (repeat . (symbol . feature)) . - t)) - t)))))) - -(elisp-scope-define-function-analyzer custom-declare-theme (name &rest rest) - (elisp-scope-1 name '(symbol . deftheme)) - (elisp-scope-n rest)) +(elisp-scope-define-function-spec custom-theme-set-variables + ((symbol . theme)) + (cons (symbol . free-variable) . + (cons code . + (or (cons t . + (cons (repeat . (symbol . feature)) . + t)) + t)))) -(elisp-scope-define-function-analyzer - (eieio-oref - slot-boundp slot-makeunbound slot-exists-p eieio-oref-default) - (obj slot) - (elisp-scope-1 obj) - (elisp-scope-1 slot '(symbol . slot))) - -(elisp-scope-define-function-analyzer (eieio-oset eieio-oset-default) - (obj slot value) - (elisp-scope-1 obj) - (elisp-scope-1 slot '(symbol . slot)) - (elisp-scope-1 value)) - -(elisp-scope-define-function-analyzer derived-mode-p (modes &rest rest) - (elisp-scope-1 modes '(or (repeat . (symbol . major-mode)) - (symbol . major-mode))) - (dolist (mode rest) (elisp-scope-1 mode '(symbol . major-mode)))) - -(elisp-scope-define-function-analyzer derived-mode-set-parent (&optional mode parent) - (elisp-scope-1 mode '(symbol . major-mode)) - (elisp-scope-1 parent '(symbol . major-mode))) - -(elisp-scope-define-function-analyzer elisp-scope-report (role &rest args) - (elisp-scope-1 role '(symbol . symbol-role)) - (mapc #'elisp-scope-1 args)) +(elisp-scope-define-function-spec custom-declare-theme ((symbol . deftheme))) -(elisp-scope-define-function-analyzer elisp-scope-report-s (&optional sym role) - (elisp-scope-1 sym) - (elisp-scope-1 role '(symbol . symbol-role))) +(elisp-scope-define-function-spec + (eieio-oref slot-boundp slot-makeunbound slot-exists-p eieio-oref-default) + (nil (symbol . slot))) -(elisp-scope-define-function-analyzer elisp-scope-1 (&optional form outspec) - (elisp-scope-1 form) - (elisp-scope-1 outspec 'spec)) +(elisp-scope-define-function-spec (eieio-oset eieio-oset-default) + (nil (symbol . slot))) + +(elisp-scope-define-function-spec derived-mode-p + ((or (repeat . (symbol . major-mode)) + (symbol . major-mode))) + (symbol . major-mode)) + +(elisp-scope-define-function-spec derived-mode-set-parent + ((symbol . major-mode) (symbol . major-mode))) + +(elisp-scope-define-function-spec elisp-scope-report + ((symbol . symbol-role))) + +(elisp-scope-define-function-spec elisp-scope-report-s + (nil (symbol . symbol-role))) + +(elisp-scope-define-function-spec elisp-scope-1 (nil spec)) (elisp-scope-define-function-analyzer icons--register (&optional name parent spec doc kws) (elisp-scope-1 name '(symbol . deficon)) @@ -2072,21 +2019,15 @@ ARGS bound to the analyzed arguments." (elisp-scope-1 var '(symbol . free-variable)) (elisp-scope-1 val elisp-scope-output-spec)) -(elisp-scope-define-function-analyzer autoload (&optional func file doc int type) - (elisp-scope-1 func '(symbol . function)) - (elisp-scope-1 file) - (elisp-scope-1 doc) - (elisp-scope-1 int '(repeat . (symbol . major-mode))) - (elisp-scope-1 type)) +(elisp-scope-define-function-spec autoload + ((symbol . function) nil nil (repeat . (symbol . major-mode)))) -(elisp-scope-define-function-analyzer define-completion-category (&optional name parents &rest rest) - (elisp-scope-1 name '(symbol . completion-category-definition)) - (elisp-scope-1 parents '(repeat . (symbol . completion-category))) - (elisp-scope-n rest)) +(elisp-scope-define-function-spec define-completion-category + ((symbol . completion-category-definition) + (repeat . (symbol . completion-category)))) -(elisp-scope-define-function-analyzer completion-table-with-category (&optional category table) - (elisp-scope-1 category '(symbol . completion-category)) - (elisp-scope-1 table)) +(elisp-scope-define-function-spec completion-table-with-category + ((symbol . completion-category))) (defun elisp-scope--easy-menu-do-define-menu (menu) (let ((items (cdr menu))) @@ -2145,9 +2086,8 @@ ARGS bound to the analyzed arguments." (elisp-scope-1 def)) (elisp-scope-1 remove)) -(elisp-scope-define-function-analyzer eval-after-load (&optional file form) - (elisp-scope-1 file '(symbol . feature)) - (elisp-scope-1 form 'code)) +(elisp-scope-define-function-spec eval-after-load + ((symbol . feature) code)) ;; We use a bespoke analyzer for `if-let*' instead of letting ;; `elisp-scope-1' expand it because `if-let*' expands to a form that commit 8c8caa308857b89e22b2d2f9d4be2241cf0a6f67 Author: Eshel Yaron Date: Thu Jul 2 17:53:02 2026 +0200 elisp-scope.el: Support associating analyzers with multiple symbols Many analyzers (those defined with 'elisp-scope-define-analyzer' and friends) are appropriate for more than one function/macro. Before this commit, our way of reusing an analyzer was to do this: ;; Define analyzer for `foo' (elisp-scope-define-function-analyzer foo (...) ...) ;; Reuse it for `bar' (put 'bar 'elisp-scope-analyzer #'elisp-scope--analyze-foo) This isn't a good solution for users/packages that want to reuse their custom analyzers, because it relies on too many brittle internal implementation details. This commit provides a nicer solution: we generalize the analyzer-defining macros so they can take a list of symbols and directly associated the defined analyzer with all of the given symbols. So the above example is simplified to: (elisp-scope-define-function-analyzer (foo bar) (...) ...) * lisp/emacs-lisp/elisp-scope.el (elisp-scope-define-analyzer): Accept either a symbol or a list of symbols, and register the analyzer function for each symbol in the list (bug#81355). (elisp-scope--define-function-analyzer) (elisp-scope-define-function-analyzer) (elisp-scope-define-macro-analyzer) (elisp-scope-define-special-form-analyzer): Likewise. Convert all call sites that used to register extra symbols via separate 'put' calls to use this new feature instead. * etc/NEWS: Announce it. diff --git a/etc/NEWS b/etc/NEWS index d39ede2b346..7733c5e8eba 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -237,6 +237,15 @@ The HANDLER argument of 'dbus-call-method-asynchronously' can be a cons cell '(HANDLER . ERROR-HANDLER)'. ERROR-HANDLER is invoked if the method call returns with a D-Bus error; the error is passed as argument. +** elisp-scope.el + +*** Custom analyzers can be associated with multiple functions. +'elisp-scope' macros that define custom analyzer functions, such as +'elisp-scope-define-function-analyzer' and +'elisp-scope-define-macro-analyzer', can now take a list of symbols to +which the defined analyzer should apply (or a single symbol, as before). +This makes it easy to specify the same analyzer for multiple functions. + * Changes in Emacs 32.1 on Non-Free Operating Systems diff --git a/lisp/emacs-lisp/elisp-scope.el b/lisp/emacs-lisp/elisp-scope.el index 55e82001df9..4d3c39850b4 100644 --- a/lisp/emacs-lisp/elisp-scope.el +++ b/lisp/emacs-lisp/elisp-scope.el @@ -1576,16 +1576,23 @@ property, or if the current buffer is trusted (see `trusted-content-p')." (defmacro elisp-scope-define-analyzer (fsym args &rest body) "Define an analyzer function for function/macro FSYM. +FSYM is a symbol, or a list of symbols that all share the same analyzer. ARGS is the arguments list of the analyzer function, and BODY is its body." (declare (indent defun)) - (let ((analyzer (intern (concat "elisp-scope--analyze-" (symbol-name fsym))))) + (let* ((syms (ensure-list fsym)) + (fsym (car syms)) + (analyzer (intern (concat "elisp-scope--analyze-" (symbol-name fsym))))) `(progn (defun ,analyzer ,args ,@body) - (put ',fsym 'elisp-scope-analyzer #',analyzer)))) + . ,(mapcar (lambda (sym) + `(put ',sym 'elisp-scope-analyzer #',analyzer)) + syms)))) (defmacro elisp-scope--define-function-analyzer (fsym args role &rest body) (declare (indent defun)) - (let ((helper (intern (concat "elisp-scope--analyze-" (symbol-name fsym) "-1")))) + (let ((helper (intern (concat "elisp-scope--analyze-" + (symbol-name (car (ensure-list fsym))) + "-1")))) `(progn (defun ,helper ,args ,@body) (elisp-scope-define-analyzer ,fsym (f &rest args) @@ -1594,6 +1601,7 @@ ARGS is the arguments list of the analyzer function, and BODY is its body." (defmacro elisp-scope-define-function-analyzer (fsym args &rest body) "Define an analyzer function for function FSYM. +FSYM is a symbol, or a list of symbols that all share the same analyzer. The analyzer function analyzes occurrences of FSYM as a function call, and it analyzes the arguments in calls to FSYM by executing BODY with ARGS bound to the analyzed arguments." @@ -1602,11 +1610,14 @@ ARGS bound to the analyzed arguments." (defmacro elisp-scope-define-macro-analyzer (fsym args &rest body) "Define an analyzer function for macro FSYM. +FSYM is a symbol, or a list of symbols that all share the same analyzer. The analyzer function analyzes occurrences of FSYM as a macro call, and it analyzes the arguments in calls to FSYM by executing BODY with ARGS bound to the analyzed arguments." (declare (indent defun)) - (let ((helper (intern (concat "elisp-scope--analyze-" (symbol-name fsym) "-1")))) + (let ((helper (intern (concat "elisp-scope--analyze-" + (symbol-name (car (ensure-list fsym))) + "-1")))) `(progn (defun ,helper ,args ,@body) (elisp-scope-define-analyzer ,fsym (f &rest args) @@ -1615,11 +1626,14 @@ bound to the analyzed arguments." (defmacro elisp-scope-define-special-form-analyzer (fsym args &rest body) "Define an analyzer function for special form FSYM. +FSYM is a symbol, or a list of symbols that all share the same analyzer. The analyzer function analyzes occurrences of FSYM as a special form, and it analyzes the arguments in calls to FSYM by executing BODY with ARGS bound to the analyzed arguments." (declare (indent defun)) - (let ((helper (intern (concat "elisp-scope--analyze-" (symbol-name fsym) "-1")))) + (let ((helper (intern (concat "elisp-scope--analyze-" + (symbol-name (car (ensure-list fsym))) + "-1")))) `(progn (defun ,helper ,args ,@body) (elisp-scope-define-analyzer ,fsym (f &rest args) @@ -1647,12 +1661,10 @@ ARGS bound to the analyzed arguments." (elisp-scope-1 (or (elisp-scope--unquote form) form)) (elisp-scope-1 lexical)) -(elisp-scope-define-function-analyzer funcall (&optional f &rest args) +(elisp-scope-define-function-analyzer (funcall apply) (&optional f &rest args) (elisp-scope-1 f '(symbol . function)) (elisp-scope-n args)) -(put 'apply 'elisp-scope-analyzer #'elisp-scope--analyze-funcall) - (elisp-scope-define-function-analyzer defalias (&optional sym def docstring) (elisp-scope-1 sym '(symbol . defun)) (elisp-scope-1 def '(symbol . defun)) @@ -1687,22 +1699,17 @@ ARGS bound to the analyzed arguments." (elisp-scope-1 alias '(symbol . defcharset)) (elisp-scope-1 charset '(symbol . charset))) -(elisp-scope-define-function-analyzer charset-chars +(elisp-scope-define-function-analyzer + (charset-chars + charset-description charset-info charset-iso-final-char + charset-long-name charset-plist charset-short-name + get-charset-property put-charset-property list-charset-chars + set-charset-plist set-charset-priority unify-charset + locale-charset-to-coding-system) (&optional charset &rest rest) (elisp-scope-1 charset '(symbol . charset)) (elisp-scope-n rest)) -(dolist (sym '(charset-description charset-info charset-iso-final-char - charset-long-name charset-plist - charset-short-name - get-charset-property put-charset-property - list-charset-chars - set-charset-plist - set-charset-priority - unify-charset - locale-charset-to-coding-system)) - (put sym 'elisp-scope-analyzer #'elisp-scope--analyze-charset-chars)) - (elisp-scope-define-function-analyzer define-coding-system (&optional name &rest rest) (elisp-scope-1 name '(symbol . defcoding)) @@ -1713,75 +1720,68 @@ ARGS bound to the analyzed arguments." (elisp-scope-1 alias '(symbol . defcoding)) (elisp-scope-1 coding-system '(symbol . coding))) -(elisp-scope-define-function-analyzer decode-coding-region +(elisp-scope-define-function-analyzer + (decode-coding-region encode-coding-region) (&optional start end coding-system &rest rest) (elisp-scope-1 start) (elisp-scope-1 end) (elisp-scope-1 coding-system '(symbol . coding)) (elisp-scope-n rest)) -(put 'encode-coding-region 'elisp-scope-analyzer #'elisp-scope--analyze-decode-coding-region) - -(elisp-scope-define-function-analyzer decode-coding-string +(elisp-scope-define-function-analyzer + (decode-coding-string encode-coding-char encode-coding-string) (&optional string coding-system &rest rest) (elisp-scope-1 string) (elisp-scope-1 coding-system '(symbol . coding)) (elisp-scope-n rest)) -(dolist (sym '(encode-coding-char encode-coding-string)) - (put sym 'elisp-scope-analyzer #'elisp-scope--analyze-decode-coding-string)) - -(elisp-scope-define-function-analyzer coding-system-mnemonic +(elisp-scope-define-function-analyzer + (coding-system-mnemonic + add-to-coding-system-list + check-coding-system + coding-system-aliases + coding-system-base + coding-system-category + coding-system-change-eol-conversion + coding-system-change-text-conversion + coding-system-charset-list + coding-system-doc-string + coding-system-eol-type + coding-system-eol-type-mnemonic + coding-system-get + coding-system-plist + coding-system-post-read-conversion + coding-system-pre-write-conversion + coding-system-put + coding-system-translation-table-for-decode + coding-system-translation-table-for-encode + coding-system-type + describe-coding-system + prefer-coding-system + print-coding-system + print-coding-system-briefly + revert-buffer-with-coding-system + set-buffer-file-coding-system + set-clipboard-coding-system + set-coding-system-priority + set-default-coding-systems + set-file-name-coding-system + set-keyboard-coding-system + set-next-selection-coding-system + set-selection-coding-system + set-terminal-coding-system + universal-coding-system-argument) (&optional coding-system &rest rest) (elisp-scope-1 coding-system '(symbol . coding)) (elisp-scope-n rest)) -(dolist (sym '(add-to-coding-system-list - check-coding-system - coding-system-aliases - coding-system-base - coding-system-category - coding-system-change-eol-conversion - coding-system-change-text-conversion - coding-system-charset-list - coding-system-doc-string - coding-system-eol-type - coding-system-eol-type-mnemonic - coding-system-get - coding-system-plist - coding-system-post-read-conversion - coding-system-pre-write-conversion - coding-system-put - coding-system-translation-table-for-decode - coding-system-translation-table-for-encode - coding-system-type - describe-coding-system - prefer-coding-system - print-coding-system - print-coding-system-briefly - revert-buffer-with-coding-system - set-buffer-file-coding-system - set-clipboard-coding-system - set-coding-system-priority - set-default-coding-systems - set-file-name-coding-system - set-keyboard-coding-system - set-next-selection-coding-system - set-selection-coding-system - set-terminal-coding-system - universal-coding-system-argument)) - (put sym 'elisp-scope-analyzer #'elisp-scope--analyze-coding-system-mnemonic)) - -(elisp-scope-define-function-analyzer thing-at-point (&optional thing no-props) +(elisp-scope-define-function-analyzer + (thing-at-point + forward-thing beginning-of-thing end-of-thing bounds-of-thing-at-point) + (&optional thing no-props) (elisp-scope-1 thing '(symbol . thing)) (elisp-scope-1 no-props)) -(dolist (sym '( forward-thing - beginning-of-thing - end-of-thing - bounds-of-thing-at-point)) - (put sym 'elisp-scope-analyzer #'elisp-scope--analyze-thing-at-point)) - (elisp-scope-define-function-analyzer bounds-of-thing-at-mouse (&optional event thing) (elisp-scope-1 event) (elisp-scope-1 thing '(symbol . thing))) @@ -1847,14 +1847,14 @@ ARGS bound to the analyzed arguments." (elisp-scope-1 error-symbol '(symbol . condition)) (elisp-scope-1 data)) -(elisp-scope--define-function-analyzer kill-emacs (&rest rest) non-local-exit +(elisp-scope--define-function-analyzer + (kill-emacs + abort-recursive-edit top-level exit-recursive-edit + tty-frame-restack error user-error + minibuffer-quit-recursive-edit exit-minibuffer) + (&rest rest) non-local-exit (elisp-scope-n rest)) -(dolist (sym '( abort-recursive-edit top-level exit-recursive-edit - tty-frame-restack error user-error - minibuffer-quit-recursive-edit exit-minibuffer)) - (put sym 'elisp-scope-analyzer #'elisp-scope--analyze-kill-emacs)) - (elisp-scope-define-function-analyzer run-hooks (&rest hooks) (dolist (hook hooks) (elisp-scope-1 hook '(symbol . free-variable)))) @@ -1876,35 +1876,35 @@ ARGS bound to the analyzed arguments." (elisp-scope-1 face 'face) (elisp-scope-n rest)) -(elisp-scope-define-function-analyzer facep (&optional face &rest rest) +(elisp-scope-define-function-analyzer + (facep + check-face face-id face-differs-from-default-p + face-name face-all-attributes face-attribute + face-foreground face-background face-stipple + face-underline-p face-inverse-video-p face-bold-p + face-italic-p face-extend-p face-documentation + set-face-documentation set-face-attribute + set-face-font set-face-background set-face-foreground + set-face-stipple set-face-underline set-face-inverse-video + set-face-bold set-face-italic set-face-extend) + (&optional face &rest rest) (elisp-scope-1 face '(symbol . face)) (elisp-scope-n rest)) -(dolist (sym '( check-face face-id face-differs-from-default-p - face-name face-all-attributes face-attribute - face-foreground face-background face-stipple - face-underline-p face-inverse-video-p face-bold-p - face-italic-p face-extend-p face-documentation - set-face-documentation set-face-attribute - set-face-font set-face-background set-face-foreground - set-face-stipple set-face-underline set-face-inverse-video - set-face-bold set-face-italic set-face-extend)) - (put sym 'elisp-scope-analyzer #'elisp-scope--analyze-facep)) - -(elisp-scope-define-function-analyzer boundp (&optional var &rest rest) +(elisp-scope-define-function-analyzer + (boundp + set symbol-value define-abbrev-table + special-variable-p local-variable-p + local-variable-if-set-p add-variable-watcher + get-variable-watchers remove-variable-watcher + default-value set-default make-local-variable + buffer-local-value add-to-list add-to-history find-buffer + customize-set-variable set-variable + add-hook remove-hook run-hook-with-args run-hook-wrapped) + (&optional var &rest rest) (elisp-scope-1 var '(symbol . free-variable)) (elisp-scope-n rest)) -(dolist (sym '( set symbol-value define-abbrev-table - special-variable-p local-variable-p - local-variable-if-set-p add-variable-watcher - get-variable-watchers remove-variable-watcher - default-value set-default make-local-variable - buffer-local-value add-to-list add-to-history find-buffer - customize-set-variable set-variable - add-hook remove-hook run-hook-with-args run-hook-wrapped)) - (put sym 'elisp-scope-analyzer #'elisp-scope--analyze-boundp)) - (elisp-scope-define-function-analyzer defvaralias (new base &optional docstring) (elisp-scope-1 new '(symbol . defvar)) (elisp-scope-1 base '(symbol . free-variable)) @@ -1916,17 +1916,16 @@ ARGS bound to the analyzed arguments." (elisp-scope-1 parent '(or (symbol . condition) (repeat . (symbol . condition))))) -(elisp-scope-define-function-analyzer featurep (feature &rest rest) +(elisp-scope-define-function-analyzer (featurep require) (feature &rest rest) (elisp-scope-1 feature '(symbol . feature)) (elisp-scope-n rest)) -(put 'require 'elisp-scope-analyzer #'elisp-scope--analyze-featurep) - (elisp-scope-define-function-analyzer provide (feature &rest rest) (elisp-scope-1 feature '(symbol . deffeature)) (elisp-scope-n rest)) -(elisp-scope-define-function-analyzer put-text-property (&optional beg end prop val obj) +(elisp-scope-define-function-analyzer (put-text-property remove-overlays) + (&optional beg end prop val obj) (elisp-scope-1 beg) (elisp-scope-1 end) (elisp-scope-1 prop) @@ -1937,8 +1936,6 @@ ARGS bound to the analyzed arguments." 'face))) (elisp-scope-1 obj)) -(put 'remove-overlays 'elisp-scope-analyzer #'elisp-scope--analyze-put-text-property) - (elisp-scope-define-function-analyzer propertize (string &rest props) (elisp-scope-1 string) (while props @@ -1998,13 +1995,13 @@ ARGS bound to the analyzed arguments." (setq args (cddr args))) (when args (elisp-scope-n args))) -(elisp-scope-define-function-analyzer provide-theme (name &rest rest) +(elisp-scope-define-function-analyzer + (provide-theme + enable-theme disable-theme load-theme custom-theme-p) + (name &rest rest) (elisp-scope-1 name '(symbol . theme)) (elisp-scope-n rest)) -(dolist (sym '(enable-theme disable-theme load-theme custom-theme-p)) - (put sym 'elisp-scope-analyzer #'elisp-scope--analyze-provide-theme)) - (elisp-scope-define-function-analyzer custom-theme-set-variables (theme &rest args) (elisp-scope-1 theme '(symbol . theme)) (dolist (arg args) @@ -2021,20 +2018,19 @@ ARGS bound to the analyzed arguments." (elisp-scope-1 name '(symbol . deftheme)) (elisp-scope-n rest)) -(elisp-scope-define-function-analyzer eieio-oref (obj slot) +(elisp-scope-define-function-analyzer + (eieio-oref + slot-boundp slot-makeunbound slot-exists-p eieio-oref-default) + (obj slot) (elisp-scope-1 obj) (elisp-scope-1 slot '(symbol . slot))) -(dolist (fun '(slot-boundp slot-makeunbound slot-exists-p eieio-oref-default)) - (put fun 'elisp-scope-analyzer #'elisp-scope--analyze-eieio-oref)) - -(elisp-scope-define-function-analyzer eieio-oset (obj slot value) +(elisp-scope-define-function-analyzer (eieio-oset eieio-oset-default) + (obj slot value) (elisp-scope-1 obj) (elisp-scope-1 slot '(symbol . slot)) (elisp-scope-1 value)) -(put 'eieio-oset-default 'elisp-scope-analyzer #'elisp-scope--analyze-eieio-oset) - (elisp-scope-define-function-analyzer derived-mode-p (modes &rest rest) (elisp-scope-1 modes '(or (repeat . (symbol . major-mode)) (symbol . major-mode))) @@ -2332,25 +2328,19 @@ ARGS bound to the analyzed arguments." (elisp-scope-define-macro-analyzer define-minor-mode (&optional mode doc &rest body) (when mode (elisp-scope-define-minor-mode mode doc body))) -(elisp-scope-define-macro-analyzer setq-local (&rest args) +(elisp-scope-define-macro-analyzer (setq-local setq-default) (&rest args) (elisp-scope-setq args)) -(put 'setq-default 'elisp-scope-analyzer #'elisp-scope--analyze-setq-local) - -(elisp-scope-define-macro-analyzer cl-defun (name arglist &rest body) +(elisp-scope-define-macro-analyzer (cl-defun cl-defmacro) (name arglist &rest body) (elisp-scope-cl-defun name arglist body)) -(put 'cl-defmacro 'elisp-scope-analyzer #'elisp-scope--analyze-cl-defun) - -(elisp-scope-define-macro-analyzer defun (&optional name arglist &rest body) +(elisp-scope-define-macro-analyzer (defun ert-deftest) (&optional name arglist &rest body) (when name (elisp-scope-defun name arglist body))) (elisp-scope-define-macro-analyzer defmacro (&optional name arglist &rest body) (elisp-scope-report-s name 'defmacro) (elisp-scope-lambda arglist body)) -(put 'ert-deftest 'elisp-scope-analyzer #'elisp-scope--analyze-defun) - (elisp-scope-define-macro-analyzer elisp-scope-define-symbol-role (&optional name parents &rest props) (elisp-scope-report-s name 'symbol-role-definition) (dolist (parent parents) (elisp-scope-report-s parent 'symbol-role)) @@ -2386,34 +2376,25 @@ ARGS bound to the analyzed arguments." (elisp-scope-1 place) (elisp-scope-n body elisp-scope-output-spec)) -(elisp-scope-define-macro-analyzer cl-pushnew (&rest args) +(elisp-scope-define-macro-analyzer (cl-pushnew incf decf) (&rest args) (mapc #'elisp-scope-1 args)) -(dolist (sym '(incf decf)) - (put sym 'elisp-scope-analyzer #'elisp-scope--analyze-cl-pushnew)) - (elisp-scope-define-macro-analyzer static-if (&optional test then &rest else) (elisp-scope-1 test) (elisp-scope-1 then elisp-scope-output-spec) (elisp-scope-n else elisp-scope-output-spec)) -(elisp-scope-define-macro-analyzer static-when (&optional test &rest body) +(elisp-scope-define-macro-analyzer (static-when static-unless) (&optional test &rest body) (elisp-scope-1 test) (elisp-scope-n body elisp-scope-output-spec)) -(put 'static-unless 'elisp-scope-analyzer #'elisp-scope--analyze-static-when) - -(elisp-scope-define-macro-analyzer eval-when-compile (&rest body) +(elisp-scope-define-macro-analyzer (eval-when-compile eval-and-compile) (&rest body) (elisp-scope-n body elisp-scope-output-spec)) -(put 'eval-and-compile 'elisp-scope-analyzer #'elisp-scope--analyze-eval-when-compile) - -(elisp-scope-define-macro-analyzer cl-callf (&rest args) +(elisp-scope-define-macro-analyzer (cl-callf cl-callf2) (&rest args) (elisp-scope-sharpquote (car args)) (elisp-scope-n (cdr args))) -(put 'cl-callf2 'elisp-scope-analyzer #'elisp-scope--analyze-cl-callf) - (elisp-scope-define-macro-analyzer seq-let (args sequence &rest body) (elisp-scope-1 sequence) (let ((l elisp-scope-local-bindings)) @@ -2696,23 +2677,18 @@ ARGS bound to the analyzed arguments." (elisp-scope-1 tag '(symbol . throw-tag)) (elisp-scope-n body elisp-scope-output-spec)) -(elisp-scope-define-special-form-analyzer progn (&rest body) +(elisp-scope-define-special-form-analyzer + (progn inline save-current-buffer save-excursion save-restriction) + (&rest body) (elisp-scope-n body elisp-scope-output-spec)) -(put 'inline 'elisp-scope-analyzer #'elisp-scope--analyze-progn) -(put 'save-current-buffer 'elisp-scope-analyzer #'elisp-scope--analyze-progn) -(put 'save-excursion 'elisp-scope-analyzer #'elisp-scope--analyze-progn) -(put 'save-restriction 'elisp-scope-analyzer #'elisp-scope--analyze-progn) - (elisp-scope-define-special-form-analyzer while (&rest rest) (mapc #'elisp-scope-1 rest)) -(elisp-scope-define-special-form-analyzer prog1 (&rest body) +(elisp-scope-define-special-form-analyzer (prog1 unwind-protect) (&rest body) (when (consp body) (elisp-scope-1 (pop body) elisp-scope-output-spec)) (elisp-scope-n body)) -(put 'unwind-protect 'elisp-scope-analyzer #'elisp-scope--analyze-prog1) - ;;; Specifications for some common variables: (put 'coding-system-for-read 'elisp-scope-variable-spec '(symbol . coding)) (put 'coding-system-for-write 'elisp-scope-variable-spec '(symbol . coding)) commit 56154c939bf8f3cd7dbadeb91ef0a2fe4294ec65 Author: Stephen Berman Date: Sat Jul 4 15:57:00 2026 +0200 Improve commit log entry creation from ChangeLog (bug#80928) * lisp/vc/log-edit.el (log-edit-insert-changelog-entry): New function. (log-edit-insert-changelog-entries): Use it when all log entries come from the same ChangeLog file or buffer. * test/lisp/vc/vc-tests/vc-test-misc.el (vc-test-log-message-from-changelog): New test. * doc/emacs/maintaining.texi (VC With A Merging VCS): (Log Buffer): Document improved creation of commit messages from ChangeLog. Improve documentation of *vc-log* header lines. Document 'log-edit-hook'. (VC Directory Commands): Correct documentation of 'M' and 'U'. Document determination of current VC fileset without marking. (Format of ChangeLog): Improve documentation of multi-item entries. * etc/NEWS: Announce user-facing changes. diff --git a/doc/emacs/maintaining.texi b/doc/emacs/maintaining.texi index e40ff8e3f26..7e893ab4fdf 100644 --- a/doc/emacs/maintaining.texi +++ b/doc/emacs/maintaining.texi @@ -562,10 +562,12 @@ v}. Note that registering the files doesn't commit them; you must invoke @w{@kbd{C-x v v}} again to commit; see the next point. @item -If every file in the VC fileset has been either newly-added or -modified, commit the changed files. To do this, Emacs pops up a -@file{*vc-log*} buffer; type the desired log entry for the changes, -followed by @kbd{C-c C-c} to commit. @xref{Log Buffer}. +If every file in the VC fileset has been either newly-added or modified, +commit the changed files. To do this, Emacs pops up a @file{*vc-log*} +buffer, in which you can type the log entry for the changes (or, if the +entry has been automatically inserted from a ChangeLog file or buffer, +further edit it). When you are satisfied with the log entry, type +@kbd{C-c C-c} to commit the changes. See @ref{Log Buffer} for details. If @kbd{C-x v v} is invoked from a buffer under Diff mode, the command treats the buffer as holding a set of patches for one or more files. It @@ -715,49 +717,83 @@ out individual files. When you tell VC to commit a change, it pops up a buffer named @file{*vc-log*}. In this buffer, you should write a @dfn{log entry} describing the changes you have made (@pxref{Why Version Control?}). -After you are done, type @kbd{C-c C-c} (@code{log-edit-done}) to exit -the buffer and commit the change, together with your log entry. +Alternatively, you can have the entry automatically inserted from a +ChangeLog file or buffer (see below), which you can further edit in the +@file{*vc-log*} buffer if necessary. After you are done, type @kbd{C-c +C-c} (@code{log-edit-done}) to exit the buffer and commit the change, +together with your log entry. + + In the @file{*vc-log*} buffer, you can also write one or more +@dfn{header lines} specifying additional information to be supplied to +the version control system. For example, the following header line +states that the present change was not written by you, but by another +developer: + +@smallexample +Author: J. R. Hacker +@end smallexample + +@noindent +Each header line must occupy a single line at the top of the buffer; the +first line that is not a header line is treated as the start of the log +entry. In addition to the @samp{Author} header, most version control +systems also support the headers @samp{Summary} (a one-line summary of +the changeset) and @samp{Date} (a manually specified commit time). +Emacs recognizes these as well as several VCS-specific headers. If you +specify a header for a VCS that does not support it, the header is +treated as part of the log entry. @cindex Log Edit mode @cindex mode, Log Edit @vindex vc-log-mode-hook @c FIXME: Mention log-edit-mode-hook here? --xfq +@vindex log-edit-hook The major mode for the @file{*vc-log*} buffer is Log Edit mode, a variant of Text mode (@pxref{Text Mode}). On entering Log Edit mode, Emacs runs the hooks @code{text-mode-hook} and @code{vc-log-mode-hook} -(@pxref{Hooks}). - - In the @file{*vc-log*} buffer, you can write one or more @dfn{header -lines}, specifying additional information to be supplied to the -version control system. Each header line must occupy a single line at -the top of the buffer; the first line that is not a header line is -treated as the start of the log entry. For example, the following -header line states that the present change was not written by you, but -by another developer: - -@smallexample -Author: J. R. Hacker -@end smallexample - -@noindent -Apart from the @samp{Author} header, Emacs recognizes the headers -@samp{Summary} (a one-line summary of the changeset), @samp{Date} (a -manually-specified commit time), and @samp{Fixes} (a reference to a -bug fixed by the change). Not all version control systems recognize -all headers. If you specify a header for a system that does not -support it, the header is treated as part of the log entry. +(@pxref{Hooks}). In addition, to set up the @file{*vc-log*} buffer, +Emacs runs the hook @code{log-edit-hook}, which optionally makes several +useful features available. The following paragraphs describe several of +these features, including three that are generally useful and enabled by +default. (To see a complete list of the features and enable or disable +any of them, type @w{@kbd{M-x customize-option RET log-edit-hook RET}}; +@pxref{Specific Customization}.) + +@findex log-edit-insert-message-template + The function @code{log-edit-insert-message-template} produces the +default @file{*vc-log*} buffer setup, containing @samp{Author} and +@samp{Summary} headers, and displays a thick line separating the header +part of the buffer from the log entry part. If you remove +@code{log-edit-insert-message-template} from @code{log-edit-hook}, then +the initial @file{*vc-log*} buffer is simply empty. The two header +lines are initially empty. If you are the author of the change and you +have configured the VCS to use your name and email address in commit +messages, you can leave the @samp{Author} header empty. If you leave +the @samp{Summary} header empty, most systems will use the first line of +the log entry as the summary line (see below for a way to have the +@samp{Summary} header automatically filled). @kindex C-c C-f @r{(Log Edit mode)} @findex log-edit-show-files + When VC pops up the @file{*vc-log*} buffer, a buffer named +@file{*log-edit-files*} listing the current VC fileset, that is, the +files that will be committed if you type @w{@kbd{C-c C-c}}, also pops up +by default. If you have removed the command @code{log-edit-show-files} +from @code{log-edit-hook}, the @file{*log-edit-files*} buffer does not +automatically pop up, but you can invoke the command by typing +@w{@kbd{C-c C-f}}. Likewise, if you kill or bury the +@file{*log-edit-files*} buffer, typing @w{@kbd{C-c C-f}} will pop it up +again. + @kindex C-c C-d @r{(Log Edit mode)} @findex log-edit-show-diff - While in the @file{*vc-log*} buffer, the current VC fileset is -considered to be the fileset that will be committed if you type -@w{@kbd{C-c C-c}}. To view a list of the files in the VC fileset, -type @w{@kbd{C-c C-f}} (@code{log-edit-show-files}). To view a diff -of changes between the VC fileset and the version from which you -started editing (@pxref{Old Revisions}), type @kbd{C-c C-d} -(@code{log-edit-show-diff}). + To view a diff of changes between the VC fileset and the version from +which you started editing (@pxref{Old Revisions}), type @kbd{C-c C-d} +(@code{log-edit-show-diff}). This command also selects the window +displaying the diff. To have VC automatically display the diff when it +pops up the @file{*vc-log*} buffer, add @code{log-edit-maybe-show-diff} +to @code{log-edit-hook}; unlike @kbd{C-c C-d}, this function does not +select the window displaying the diff. @kindex C-c C-w @r{(Log Edit mode)} @findex log-edit-generate-changelog-from-diff @@ -773,15 +809,37 @@ VC root, customize @code{diff-add-log-use-relative-names}. @kindex C-c C-a @r{(Log Edit mode)} @findex log-edit-insert-changelog - If the VC fileset includes one or more @file{ChangeLog} files -(@pxref{Change Log}), type @kbd{C-c C-a} -(@code{log-edit-insert-changelog}) to pull the relevant entries into -the @file{*vc-log*} buffer. If the topmost item in each -@file{ChangeLog} was made under your user name on the current date, -this command searches that item for entries matching the file(s) to be -committed, and inserts them. + If you have added log entries for the current VC fileset to one or +more @file{ChangeLog} files or to a temporary ChangeLog buffer +(@pxref{Change Log}), by default VC automatically inserts these entries +into the @file{*vc-log*} buffer on popping it up. If you have removed +the command @code{log-edit-insert-changelog} from @code{log-edit-hook}, +VC does not automatically insert the log entries, but you can invoke the +command by typing @kbd{C-c C-a}. Likewise, if you delete or otherwise +change the content of this buffer and then decide you want to use the +ChangeLog entries after all, type @kbd{C-c C-a} to repopulate the buffer +with these entries. + + If the relevant log entries are in one or more @file{ChangeLog} files +and the topmost item in each file was made under your user name on the +current date, this command searches that item (or those items) for +entries matching the file(s) to be committed, and inserts them. If you +invoke the command with a prefix argument (@kbd{C-u C-c C-a}) or repeat +it immediately (@kbd{C-c C-a C-c C-a}), it searches the topmost item +regardless of the user name and date of the item. + + If VC detects that all log entries come from the same ChangeLog file +or buffer, then the command simply inserts the entire body of the entry +into the @file{*vc-log*} buffer. In addition, if the first line of the +entry does not begin with @samp{* } (an asterisk followed by a space), +VC considers it to be a summary line and moves it to the @samp{Summary} +header. Finally, if VC detects that the set of changed files listed in +the ChangeLog entry differs from the current VC fileset, it displays a +warning urging you to fix this discrepancy before committing the +changes. + @ifnottex -If you are using CVS or RCS, see @ref{Change Logs and VC}, for the + If you are using CVS or RCS, see @ref{Change Logs and VC}, for the opposite way of working---generating ChangeLog entries from the Log Edit buffer. @end ifnottex @@ -1641,9 +1699,10 @@ cannot be marked with this command if any file in its tree is marked. @item M If point is on a file entry, mark all files with the same status; if -point is on a directory entry, mark all files in that directory tree -(@code{vc-dir-mark-all-files}). With a prefix argument, mark all -listed files and directories. +point is on a directory entry, mark all and only the files that are +immediate descendents (i.e., children) of that directory +(@code{vc-dir-mark-all-files}). With a prefix argument, mark all listed +files. @findex vc-dir-mark-by-regexp @item % m @@ -1680,9 +1739,10 @@ active, unmark all the files in the region (@code{vc-dir-unmark}). @item U If point is on a file entry, unmark all files with the same status; if -point is on a directory entry, unmark all files in that directory tree +point is on a directory entry, unmark all and only the files that are +immediate descendents (i.e., children) of that directory (@code{vc-dir-unmark-all-files}). With a prefix argument, unmark all -files and directories. +listed files and directories. @item x Hide files with @samp{up-to-date} or @samp{ignored} status @@ -1701,13 +1761,22 @@ Quit the VC Directory buffer, and bury it (@code{quit-window}). While in the VC Directory buffer, all the files that you mark with @kbd{m} (@code{vc-dir-mark}) or @kbd{M} (@code{vc-dir-mark-all-files}) are in the current VC fileset. If you mark a directory entry with -@kbd{m}, all the listed files in that directory tree are in the -current VC fileset. The files and directories that belong to the -current VC fileset are indicated with a @samp{*} character in the VC -Directory buffer, next to their VC status. In this way, you can set -up a multi-file VC fileset to be acted on by VC commands like -@w{@kbd{C-x v v}} (@pxref{Basic VC Editing}), @w{@kbd{C-x v =}} -(@pxref{Old Revisions}), and @w{@kbd{C-x v u}} (@pxref{VC Undo}). +@kbd{m}, all the listed files in that directory tree (i.e., not only the +listed files of the directory but also the listed files of its +subdirectories) are in the current VC fileset. The files and +directories that belong to the current VC fileset are indicated with a +@samp{*} character in the VC Directory buffer, next to their VC status. +In this way, you can set up a multi-file VC fileset to be acted on by VC +commands like @w{@kbd{C-x v v}} (@pxref{Basic VC Editing}), @w{@kbd{C-x +v =}} (@pxref{Old Revisions}), and @w{@kbd{C-x v u}} (@pxref{VC Undo}). + + In a VC Directory buffer containing no marked file or directory +entries, the current VC fileset is determined as follows. If point is +on a file entry, the fileset consists of just that file. If point is on +a directory entry, the fileset consists of all files in that directory +tree. Consequently, if point in on (or above) the entry @samp{./} +(representing the working directory), the fileset consists of all listed +files. The VC Directory buffer also defines some single-key shortcuts for VC commands with the @kbd{C-x v} prefix: @kbd{=}, @kbd{+}, @kbd{l}, @@ -2526,13 +2595,36 @@ dated in May 1993, with two items and one item respectively. @end smallexample One entry can describe several changes; each change should have its -own item, or its own line in an item. Normally there should be a -blank line between items. When items are related (parts of the same -change, in different places), group them by leaving no blank line -between them. +own item, or its own line in an item. Normally there should be a blank +line between items. When items are related (parts of the same change, +in different places), group them by leaving no blank line between them. +Such a multi-item entry often begins with a single line of text that +succintly summarizes the whole changeset. The entry may also contain +additional explanatory text between the summary line and the first +change item; the explanatory text should also be bounded by blank lines. +Here is an example: + +@iftex +@medbreak +@end iftex +@smallexample +2026-02-27 Sean Whitton + + New 'log-outstanding' value for vc-log-view-type + + This fixes vc-hg-print-log's choice of -r arguments to 'hg log'. + + * lisp/vc/vc.el (vc-log-view-type): New 'log-outstanding' value. + (vc-log-outstanding): Pass it. + * lisp/vc/vc-git.el (vc-git-log-view-mode): + * lisp/vc/vc-hg.el (vc-hg-print-log, vc-hg-log-view-mode): Treat + 'log-outstanding' the same as 'log-outgoing'. +@end smallexample - You should put a copyright notice and permission notice at the -end of the change log file. Here is an example: + If you include one or more change log files in a software package that +is distributed to others, you should put a copyright notice and +permission notice at the end of each change log file. Here is an +example: @smallexample Copyright 1997--1998, 2025--2026 Free Software Foundation, Inc. diff --git a/etc/NEWS b/etc/NEWS index 8d89b65a3a4..d39ede2b346 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -119,6 +119,15 @@ are now buttonized, allowing using mouse or 'RET' to follow them. ** VC ++++ +*** Improved creation of commit log entry from ChangeLog. +When VC detects that all log entries for the current changeset come from +the same ChangeLog file or buffer, it now inserts the entire body of the +ChangeLog entry into the *vc-log* buffer. If the ChangeLog entry +contains a summary line, VC inserts it into the *vc-log* Summary header. +If VC detects that the set of changed files listed in the ChangeLog +entry differs from the current VC fileset, it displays a warning. + +++ *** VC-Dir outgoing revisions count is now asynchronous. This means it won't get in your way even if it's slow for your diff --git a/lisp/vc/log-edit.el b/lisp/vc/log-edit.el index e3c92bd4d28..b7b9c32ad99 100644 --- a/lisp/vc/log-edit.el +++ b/lisp/vc/log-edit.el @@ -1367,16 +1367,74 @@ entry." (if (setq entry (assoc key log-entries)) (setcdr entry (append (cdr entry) (list file))) (push (list key file) log-entries))))) - ;; Now map over log-entries, and extract the strings. - (dolist (log-entry (nreverse log-entries)) - (apply 'log-edit-changelog-insert-entries - (append (car log-entry) (cdr log-entry))) - (insert "\n")) + ;; Check whether all log entries come from the same ChangeLog file. + (if (length= (seq-uniq (mapcar #'caar log-entries)) 1) + ;; If so, extract the commit message in toto from that file. + (log-edit-insert-changelog-entry (caaar log-entries)) + ;; Otherwise, map over the entries constructed from each ChangeLog + ;; file used, and extract the strings. + (dolist (log-entry (nreverse log-entries)) + (apply 'log-edit-changelog-insert-entries + (append (car log-entry) (cdr log-entry))) + (insert "\n"))) ;; No newline after the last entry. (when log-entries (delete-char -1)) log-edit-author)) +(declare-function vc-deduce-fileset "vc") + +(defun log-edit-insert-changelog-entry (buffer) + "Use body of ChangeLog entry in BUFFER as commit message. +Insert the body of the latest entry in the ChangeLog file that BUFFER is +visiting into the \"*vc-log*\" buffer. If the first line of the body +does not begin with \"* \", move it to the Summary header in the +\"*vc-log*\" buffer, thus making is the summary line of the commit +message. + +If the set of files listed in the ChangeLog entry differs from the set +of files with changes to commit according to VC, display a warning +urging the user to correct this discrepancy before committing the +changes." + (let (summary beg end files-in-changelog) + (with-current-buffer buffer + (save-restriction + (log-edit-narrow-changelog) + (setq summary (let ((s (buffer-substring-no-properties + (pos-bol) (pos-eol)))) + (and (string-match "^\t\\([^*].+\\)$" s) + (match-string 1 s))) + beg (or (and summary (forward-line) + (skip-syntax-forward "\s-") + (goto-char (pos-bol))) + (point)) + end (point-max)) + ;; List of changed files according to the ChangeLog entry. + (save-excursion + (let* ((bfn (buffer-file-name buffer)) + (bn (buffer-name)) + (fnd (or (and bfn (file-name-directory bfn)) + ;; If ChangeLog buffer is not visiting a file, + ;; extract the directory from the buffer name. + (and + (string-match "\\`\\*changes to \\(.+\\)\\*\\'" bn) + (match-string 1 bn))))) + (while (re-search-forward "\t\\* \\([^ :\n]+\\)[ :\n]" nil t) + (let ((fn (concat fnd (match-string-no-properties 1)))) + (when (file-exists-p fn) + (push fn files-in-changelog)))))))) + (log-edit-changelog-insert-entries buffer beg end) + (when summary (log-edit-set-header "Summary" summary)) + (unless (seq-set-equal-p + (save-current-buffer + (nth 2 (vc-deduce-fileset nil nil 'state-model-only-files))) + files-in-changelog) + (display-warning + 'log-edit + "Files in ChangeLog entry differ from files with changes to commit! +Remove this discrepancy before committing the changes by adjusting as +appropriate either the ChangeLog entry or the selection of files to commit.")))) + (defun log-edit-toggle-header (header value) "Toggle a boolean-type header in the current buffer. See `log-edit-set-header' for details." diff --git a/test/lisp/vc/vc-tests/vc-test-misc.el b/test/lisp/vc/vc-tests/vc-test-misc.el index 4e3f883c1cd..30c5df5b63a 100644 --- a/test/lisp/vc/vc-tests/vc-test-misc.el +++ b/test/lisp/vc/vc-tests/vc-test-misc.el @@ -522,5 +522,59 @@ See bug#80803 and bug#80967." (should-not (seq-intersection directories (vc-dir-marked-files))) (kill-buffer vc-dir-buf)))))) +(ert-deftest vc-test-log-message-from-changelog () ; bug#80928 + "Test automatic insertion of log message from ChangeLog." + (skip-unless (executable-find vc-git-program)) + (vc-test--with-author-identity 'Git + (let ((vc-handled-backends '(Git)) + file-buf vc-dir-buf vc-diff-buf changelog-buf log-edit-buf + changelog-entry log-edit-entry) + (unwind-protect + (ert-with-temp-directory tempdir + (let* ((default-directory tempdir) + (file (expand-file-name "README" default-directory)) + vc-async-checkin) + (vc-test--create-repo-function 'Git) + (write-region "hello\n" nil file) + (with-current-buffer (setq file-buf (find-file-noselect file)) + (vc-register `(Git (,file))) + (vc-checkin (list file) 'Git) + (insert "Initial commit") + (let (vc-async-checkin) + (log-edit-done)) + (write-region "Hello\n" nil "README" nil t)) + (vc-dir default-directory 'Git) + (while (vc-dir-busy) (sit-for 0.05)) + (setq vc-dir-buf (current-buffer)) + (save-window-excursion + (vc-diff) + (setq vc-diff-buf (current-buffer)) + (diff-add-change-log-entries-other-window) + (with-current-buffer (window-buffer (frame-first-window)) + (setq changelog-buf (current-buffer)) + (insert "Change text.") + (forward-line -1) + (newline-and-indent) + (insert "Summary line") + (newline) + (save-restriction + (log-edit-narrow-changelog) + ;; ChangeLog entry ends with "\n\n" so omit last "\n" to + ;; ensure equivalence with to commit log entry in the test. + (let ((s (buffer-substring-no-properties + (point-min) (1- (point-max))))) + (setq changelog-entry + (mapconcat #'concat (string-split s "\t"))))))) + (vc-next-action nil) + (setq log-edit-buf (current-buffer)) + (goto-char (point-min)) + (re-search-forward "^Summary: " nil t) + (setq log-edit-entry + (buffer-substring-no-properties (point) (point-max))) + (should (equal changelog-entry log-edit-entry)))) + (dolist (buf (list file-buf vc-dir-buf vc-diff-buf changelog-buf + log-edit-buf "*log-edit-files*" "*vc*")) + (kill-buffer buf)))))) + (provide 'vc-test-misc) ;;; vc-test-misc.el ends here commit c32fae39f854273ffc391c524f8b1f0afb425f18 Merge: 2adce1839db f13e409cf4f Author: Eli Zaretskii Date: Sat Jul 4 08:14:32 2026 -0400 Merge from origin/emacs-31 f13e409cf4f Fix min-width in mode-line constructs commit f13e409cf4ff7c1833553b1743d9d224c81ffb98 Author: Eli Zaretskii Date: Sat Jul 4 15:09:36 2026 +0300 Fix min-width in mode-line constructs * src/xdisp.c (handle_display_prop): To handle min-width's end on the mode line, call display_min_width also when starting a new string. This is needed because mode line supports :propertize application to a list of strings, and we only need to apply the effect of min-width at the end of the last element. (Bug#81354) diff --git a/src/xdisp.c b/src/xdisp.c index 8513139d77e..5377d5e8ada 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -5960,7 +5960,11 @@ handle_display_prop (struct it *it) pos = IT_STRING_CHARPOS (*it); start = 0; } - if (pos > start) + if (pos > start + /* If we are iterating over a string and display-stack level + is zero, this is a mode line or similar. The case of + it->sp > 0 is handled in set_iterator_to_next. */ + || (STRINGP (object) && it->sp == 0)) display_min_width (it, pos, objwin, Qnil); } commit 2adce1839db773c2a45ab6b506a5f92452d314ce Merge: c7c131c30d2 7d01f8f7a4e Author: Eli Zaretskii Date: Sat Jul 4 04:28:34 2026 -0400 Merge from origin/emacs-31 7d01f8f7a4e Fix syntax of tsx tag angle brackets to use matching pairs 43ff22a0d1f ; * etc/NEWS: Presentational fixes and improvements. d837ba1ed60 ; * etc/NEWS: Fix broken reference to standard-mode-line-... 3b22019ff0d Fix Tramp container name completion e45a83cb700 ; * etc/NEWS: Document mode line minimum widths change (b... 5d6a13136ad server-tests/can-create-frames-p: Check for TERM=dumb-ema... a07abfc6bc8 ; * test/lisp/emacs-lisp/package-tests.el (lm-version): D... # Conflicts: # etc/NEWS commit c7c131c30d24853dc5f13e03280d6eba2ca8cbba Merge: 6b006c92f4d c7947191675 Author: Eli Zaretskii Date: Sat Jul 4 04:28:28 2026 -0400 ; Merge from origin/emacs-31 The following commit was skipped: c7947191675 Disable failing SCCS tests on the release branch commit 6b006c92f4d2dc48bd0235c763ab5daf7c41f31d Merge: b1d827a428d 388adcc570b Author: Eli Zaretskii Date: Sat Jul 4 04:28:26 2026 -0400 Merge from origin/emacs-31 388adcc570b Finish reverting experiment with proportional font on mod... 7746db313a6 CC Mode: Fix erroneous type: arguments to two defcustoms. ba7c04d26c7 ; Fix double docstring in a test 82009df4de5 Fix initials completion style after // in file name 6b31360a319 Fix c-pcm-try-completion with boundaries completion e560eacf6dc In pgtk_free_frame_resources transfer keyboard focus to p... 6f9ac7ffdba Fix fullscreen state handling for PGTK (Bug#81165, Bug#81... 727a57d9712 ; Don't autoload 'define-multisession-variable' d1c3ba95cb1 * lisp/progmodes/project.el: Update Commentary. 5fde9732b4b Localize cache invalidation to project-try-vc 707b0ba0f4c Fix project--clear-cache and project--value-in-dir in spe... ba7d2b82325 * admin/notes/jargon: Add TTTT. bcb83d7d2ad Fix error handling in Tramp delete-{file,directory} commit b1d827a428d224c7e536fea661f2d9b0c17eeee6 Merge: 9d616a13fa6 b8d3c4f8011 Author: Eli Zaretskii Date: Sat Jul 4 04:28:26 2026 -0400 ; Merge from origin/emacs-31 The following commit was skipped: b8d3c4f8011 Change Tramp version integrated in Emacs 31.1 (don't merge) commit 9d616a13fa68ba9bbecd1bc054963a277a195afb Merge: 78c3c8c1b0f 521c6263fa6 Author: Eli Zaretskii Date: Sat Jul 4 04:28:26 2026 -0400 Merge from origin/emacs-31 521c6263fa6 ; * src/buffer.c (Fbuffer_file_name): Doc fix (bug#81340). 69463bfc0c5 ; * etc/PROBLEMS: Problem with Enchant 2.x. (Bug#81330) 5dd8fe25c23 Correct cursor range in Ftreesit_query_capture (bug#81297) commit 78c3c8c1b0f286be6a157b24c7f19dc9d05e77b8 Merge: 017c7c8c871 a057a32bf97 Author: Eli Zaretskii Date: Sat Jul 4 04:28:26 2026 -0400 ; Merge from origin/emacs-31 The following commit was skipped: a057a32bf97 vc-dir-update: Remove assertion invalid on this branch commit 017c7c8c871e373546a3c01d73ed6e5cc8088b94 Merge: 43f2c8f9699 7b812bc38aa Author: Eli Zaretskii Date: Sat Jul 4 04:28:21 2026 -0400 Merge from origin/emacs-31 7b812bc38aa Don't use window manager activation when a child frame ha... b8f71a60119 Compile User Lisp files after adjusting 'load-path' 1ac2f60b534 Fix strange logic in vc-git-incoming-revision 10ae134ceba ; Add new text for xdisp.c 4d2701ecde9 Fix 'format-mode-line' when faces are in format string a34c29e4598 Restore frame's fullheight/fullwidth after exiting from f... c12c91563ee Fix interactive mode spec of `xwidget-webkit-end-edit-tex... # Conflicts: # src/xterm.c commit 43f2c8f9699d7171c516c9335c9a52f0eddd5e47 Author: Lin Sun Date: Wed Jun 24 06:05:01 2026 +0000 emacs-lock-mode: user-error instead of error * lisp/emacs-lock.el (emacs-lock--kill-emacs-hook): Use 'user-error' instead of signaling an error. (Bug#81292) diff --git a/lisp/emacs-lock.el b/lisp/emacs-lock.el index c28b9d7bd52..0bc1678979f 100644 --- a/lisp/emacs-lock.el +++ b/lisp/emacs-lock.el @@ -146,7 +146,7 @@ Used from `kill-emacs-hook' (which see)." (let ((locked (emacs-lock--exit-locked-buffer))) (when locked (run-hook-with-args 'emacs-lock-locked-buffer-functions locked) - (error "Emacs cannot exit because buffer %S is locked" + (user-error "Emacs cannot exit because buffer %S is locked" (buffer-name locked))))) (defun emacs-lock--kill-emacs-query-functions () commit 7d01f8f7a4e1b2a5608d2c866309ef4074ecf404 Author: Binbin Ye Date: Fri Jul 3 16:23:48 2026 +0800 Fix syntax of tsx tag angle brackets to use matching pairs The matching characters for JSX tag angle brackets were self-referential ('<' matched '<', and '>' matched '>') rather than pointing at each other. Tools that check the matching character, such as 'show-paren-mode' and the rainbow-delimiters package, therefore treated every JSX closing '>' as a mismatched delimiter. Use the same descriptors as 'sgml-make-syntax-table', where '<' is closed by '>' and '>' by '<' (bug#81348). * lisp/progmodes/typescript-ts-mode.el (tsx-ts--syntax-propertize-captures): Give '<' the syntax descriptor "(>" and '>' the descriptor ")<". Copyright-paperwork-exempt: yes diff --git a/lisp/progmodes/typescript-ts-mode.el b/lisp/progmodes/typescript-ts-mode.el index 61e281310aa..cf13363e48a 100644 --- a/lisp/progmodes/typescript-ts-mode.el +++ b/lisp/progmodes/typescript-ts-mode.el @@ -860,8 +860,8 @@ at least 3 (which is the default value)." (match-beginning 0) (match-end 0) 'syntax-table (string-to-syntax (cond - ((equal (match-string 0) "<") "(<") - ((equal (match-string 0) ">") ")>") + ((equal (match-string 0) "<") "(>") + ((equal (match-string 0) ">") ")<") (t "."))))))))))) ;;;###autoload commit 43ff22a0d1f7aad01d95c92c63a8d086b07aa175 Author: Michael Albinus Date: Fri Jul 3 16:32:28 2026 +0200 ; * etc/NEWS: Presentational fixes and improvements. diff --git a/etc/NEWS b/etc/NEWS index d1267a3ef6f..89c8e592f9c 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -617,6 +617,13 @@ The command 'mode-line-invisible-mode' toggles the visibility of the current-buffer's mode line. The default is to show the mode line of every buffer. +--- +*** Standard mode line no longer specifies minimum widths. +The default values for the 'mode-line-position' variable and +'mode-line-format' user option no longer specify any minimum widths. If +you use a proportional font for your mode line, you may need to +customize the values of these variables to include minimum widths again. + ** Tab Bars and Tab Lines --- @@ -840,13 +847,6 @@ doing search. 'compilation-search-extra-path' is consulted first. One possible use case of this option is to add new search paths on a per-project basis with directory-local variables. ---- -** Standard mode line no longer specifies minimum widths. -The default values for the 'mode-line-position' and 'mode-line-format' -variables no longer specify any minimum widths. If you use a -proportional font for your mode line, you may need to customize the -vales of these variables to include minimum widths again. - * Editing Changes in Emacs 31.1 commit d837ba1ed60534f53cf4ffd367e8d861c7c3d01d Author: Sean Whitton Date: Fri Jul 3 13:45:05 2026 +0100 ; * etc/NEWS: Fix broken reference to standard-mode-line-format. diff --git a/etc/NEWS b/etc/NEWS index 6a44d629f41..d1267a3ef6f 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -842,11 +842,10 @@ per-project basis with directory-local variables. --- ** Standard mode line no longer specifies minimum widths. -The 'mode-line-position' and 'standard-mode-line-format' variables no -longer specify any minimum widths. If you use a proportional font for -your mode line, you may need to customize the vales of these variables -to include minimum widths again. - +The default values for the 'mode-line-position' and 'mode-line-format' +variables no longer specify any minimum widths. If you use a +proportional font for your mode line, you may need to customize the +vales of these variables to include minimum widths again. * Editing Changes in Emacs 31.1 commit 3b22019ff0dfda9efc1c4341d6e465d5c797091f Author: Samuele FAVAZZA Date: Fri Jul 3 13:44:05 2026 +0200 Fix Tramp container name completion * lisp/net/tramp-container.el (tramp-container--completion-function): Use "<>" as separator instead of "\t", which could be modified in the shell. Copyright-paperwork-exempt: yes diff --git a/lisp/net/tramp-container.el b/lisp/net/tramp-container.el index fec2e16a624..dabc03f990b 100644 --- a/lisp/net/tramp-container.el +++ b/lisp/net/tramp-container.el @@ -290,14 +290,14 @@ see its function help for a description of the format." (tramp-skeleton-completion-function method (when-let* ((raw-list (shell-command-to-string - (concat program " ps --format '{{.ID}}\t{{.Names}}'"))) + (concat program " ps --format '{{.ID}}<>{{.Names}}'"))) (lines (split-string raw-list "\n" 'omit)) (names (tramp-compat-seq-keep (lambda (line) (when (string-match (rx bol (group (1+ nonl)) - "\t" (? (group (1+ nonl))) eol) + "<>" (? (group (1+ nonl))) eol) line) (or (match-string 2 line) (match-string 1 line)))) lines))) commit e45a83cb700a567ab0e459c6cb41972caaf7c320 Author: Sean Whitton Date: Fri Jul 3 12:29:06 2026 +0100 ; * etc/NEWS: Document mode line minimum widths change (bug#81336). diff --git a/etc/NEWS b/etc/NEWS index 8e7d035bf84..6a44d629f41 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -840,6 +840,14 @@ doing search. 'compilation-search-extra-path' is consulted first. One possible use case of this option is to add new search paths on a per-project basis with directory-local variables. +--- +** Standard mode line no longer specifies minimum widths. +The 'mode-line-position' and 'standard-mode-line-format' variables no +longer specify any minimum widths. If you use a proportional font for +your mode line, you may need to customize the vales of these variables +to include minimum widths again. + + * Editing Changes in Emacs 31.1 commit 5d6a13136ad4e870e36a48b44f1c4101eba9d8a3 Author: Sean Whitton Date: Fri Jul 3 12:10:29 2026 +0100 server-tests/can-create-frames-p: Check for TERM=dumb-emacs-ansi * test/lisp/server-tests.el (server-tests/can-create-frames-p): Check for TERM=dumb-emacs-ansi. diff --git a/test/lisp/server-tests.el b/test/lisp/server-tests.el index a96dc85074d..5e7938abf52 100644 --- a/test/lisp/server-tests.el +++ b/test/lisp/server-tests.el @@ -25,7 +25,8 @@ (defconst server-tests/can-create-frames-p (and (not (memq system-type '(windows-nt ms-dos))) - (not (member (getenv "TERM") '("dumb" "" nil))) + (not (member (getenv "TERM") + '("dumb" "dumb-emacs-ansi" "" nil))) (or (not (eq system-type 'cygwin)) (featurep 'gfilenotify) (featurep 'dbus) commit a07abfc6bc85c0a513a386842ee980a9e8300643 Author: Sean Whitton Date: Fri Jul 3 11:30:09 2026 +0100 ; * test/lisp/emacs-lisp/package-tests.el (lm-version): Declare. diff --git a/test/lisp/emacs-lisp/package-tests.el b/test/lisp/emacs-lisp/package-tests.el index b6fb9005e4c..eac92290349 100644 --- a/test/lisp/emacs-lisp/package-tests.el +++ b/test/lisp/emacs-lisp/package-tests.el @@ -1028,6 +1028,8 @@ but with a different end of line convention (bug#48137)." (list new-pkg-desc simple-depend-desc-2 simple-depend-desc-1 multi-file-desc simple-depend-desc simple-single-desc))))) +(declare-function lm-version "lisp-mnt") + (ert-deftest package-test-builtin-package-info () "Test public interfaces for querying package info." (should (not (null (package-versioned-builtin-packages)))) commit c79471916757aa34808ab056a4a02b319b0787f0 Author: Sean Whitton Date: Fri Jul 3 10:55:53 2026 +0100 Disable failing SCCS tests on the release branch These are broken and we don't have an SCCS expert to fix them at present. This shouldn't block the Emacs 31 release because SCCS is a relatively obscure VCS. * test/lisp/vc/vc-tests/vc-tests.el (vc-test-sccs05-rename-file) (vc-test-sccs10-rename-directory): Mark as expected to fail. Do not merge to master. diff --git a/test/lisp/vc/vc-tests/vc-tests.el b/test/lisp/vc/vc-tests/vc-tests.el index f190db103d5..0e34e571a12 100644 --- a/test/lisp/vc/vc-tests/vc-tests.el +++ b/test/lisp/vc/vc-tests/vc-tests.el @@ -1167,6 +1167,7 @@ This checks also `vc-backend' and `vc-responsible-backend'." ,(intern (format "vc-test-%s05-rename-file" backend-string)) () ,(format "Check `vc-rename-file' for the %s backend." backend-string) + ,@(and (eq backend 'SCCS) '(:expected-result :failed)) (skip-unless (ert-test-passed-p (ert-test-most-recent-result @@ -1252,6 +1253,7 @@ This checks also `vc-backend' and `vc-responsible-backend'." ,(intern (format "vc-test-%s10-rename-directory" backend-string)) () ,(format "Check `vc-rename-file' with directories for the %s backend." backend-string) + ,@(and (eq backend 'SCCS) '(:expected-result :failed)) (skip-unless (ert-test-passed-p (ert-test-most-recent-result commit 388adcc570be1b6bef87f5da449b6bbef8854222 Author: Sean Whitton Date: Fri Jul 3 10:42:22 2026 +0100 Finish reverting experiment with proportional font on mode line This change Author: Lars Ingebrigtsen AuthorDate: Thu Dec 23 11:43:47 2021 +0100 Revert back to using monospaced fonts in the mode line * lisp/faces.el (mode-line-active, mode-line-inactive): Revert back to using monospaced fonts on the mode line (for now). The main remaining usability problem is clicking on the very small "-" characters in "U:--". didn't also undo these changes to bindings.el that were introduced along with the proportional font experiment, leading to bug#81336. * lisp/bindings.el (mode-line-position) (standard-mode-line-format): Don't set display minimum widths (bug#81336). diff --git a/lisp/bindings.el b/lisp/bindings.el index b13dc0a705c..1dcf824f12e 100644 --- a/lisp/bindings.el +++ b/lisp/bindings.el @@ -726,7 +726,6 @@ mouse-1: Display Line and Column Mode Menu")) `((:propertize ("" mode-line-percent-position) local-map ,mode-line-column-line-number-mode-map - display (min-width (5.0)) mouse-face mode-line-highlight ;; XXX needs better description help-echo "Window Scroll Percentage @@ -745,31 +744,26 @@ mouse-1: Display Line and Column Mode Menu"))) (10 (:propertize mode-line-position-column-line-format - display (min-width (10.0)) ,@mode-line-position--column-line-properties)) (10 (:propertize (:eval (string-replace "%c" "%C" (car mode-line-position-column-line-format))) - display (min-width (10.0)) ,@mode-line-position--column-line-properties))) (6 (:propertize mode-line-position-line-format - display (min-width (6.0)) ,@mode-line-position--column-line-properties))) (column-number-mode (column-number-indicator-zero-based (6 (:propertize mode-line-position-column-format - display (min-width (6.0)) ,@mode-line-position--column-line-properties)) (6 (:propertize (:eval (string-replace "%c" "%C" (car mode-line-position-column-format))) - display (min-width (6.0)) ,@mode-line-position--column-line-properties)))))) "Mode line construct for displaying the position in the buffer. Normally displays the buffer percentage and, optionally, the @@ -824,15 +818,11 @@ By default, this shows the information specified by `global-mode-string'.") (let ((standard-mode-line-format (list "%e" 'mode-line-front-space - (list - :propertize - (list "" - 'mode-line-mule-info - 'mode-line-client - 'mode-line-modified - 'mode-line-remote - 'mode-line-window-dedicated) - 'display '(min-width (6.0))) + 'mode-line-mule-info + 'mode-line-client + 'mode-line-modified + 'mode-line-remote + 'mode-line-window-dedicated 'mode-line-frame-identification 'mode-line-buffer-identification " " commit 7746db313a61ed8809cf2be748da40c4ca6ca103 Author: Alan Mackenzie Date: Thu Jul 2 10:26:39 2026 +0000 CC Mode: Fix erroneous type: arguments to two defcustoms. This allows customize to set the variables directly to regular expressions as an alternative to a list of identifiers. It fixes bug#81339. * lisp/progmodes/cc-vars.el (c-noise-macro-with-parens-names) (c-noise-macro-names): Correct the type: arguments to defcustom to two `choice' constructs. diff --git a/lisp/progmodes/cc-vars.el b/lisp/progmodes/cc-vars.el index 4ac18869400..cfdbd73f20a 100644 --- a/lisp/progmodes/cc-vars.el +++ b/lisp/progmodes/cc-vars.el @@ -1737,7 +1737,9 @@ If you change this variable's value, call the function `c-make-noise-macro-regexps' to set the necessary internal variables (or do this implicitly by reinitializing C/C++/Objc Mode on any buffer)." :version "26.1" - :type '(repeat :tag "List of names" string) + :type '(choice + (repeat :tag "List of names (possibly empty)" string) + (regexp :tag "Names regexp")) :group 'c) (put 'c-noise-macro-names 'safe-local-variable #'c-string-list-p) (make-variable-buffer-local 'c-noise-macro-names) @@ -1754,7 +1756,9 @@ If you change this variable's value, call the function `c-make-noise-macro-regexps' to set the necessary internal variables (or do this implicitly by reinitializing C/C++/Objc Mode on any buffer)." :version "26.1" - :type '(repeat :tag "List of names (possibly empty)" string) + :type '(choice + (repeat :tag "List of names (possibly empty)" string) + (regexp :tag "Names regexp")) :group 'c) (put 'c-noise-macro-with-parens-names 'safe-local-variable #'c-string-list-p) (make-variable-buffer-local 'c-noise-macro-with-parens-names) commit ba7c04d26c7a61ef4c4ccc08b817534a8a071095 Author: Dmitry Gutov Date: Thu Jul 2 19:27:26 2026 +0300 ; Fix double docstring in a test diff --git a/test/lisp/progmodes/project-tests.el b/test/lisp/progmodes/project-tests.el index f17ea46b411..9516c2ad119 100644 --- a/test/lisp/progmodes/project-tests.el +++ b/test/lisp/progmodes/project-tests.el @@ -276,7 +276,6 @@ When `project-ignores' includes a name matching project dir." (ert-deftest project-try-vc-invalidates-cache () "Check that it invalidates the cached value that's too old." - "Check that one can add wildcard entries." (skip-unless (eq (vc-responsible-backend default-directory) 'Git)) ;; Prepare (let* ((dir (file-name-directory project-tests--this-file)) commit 82009df4de536366097db9c28a139469b3e81ff0 Author: Spencer Baugh Date: Wed Jul 1 15:39:31 2026 -0400 Fix initials completion style after // in file name * lisp/minibuffer.el (completion-initials-expand): Change the heuristic to check for an empty previous field, not total string length (bug#81241). * test/lisp/minibuffer-tests.el (completion-initials): New test. diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el index 4db3d0d0bea..5b1486d2af4 100644 --- a/lisp/minibuffer.el +++ b/lisp/minibuffer.el @@ -5070,12 +5070,14 @@ usual. Returns (ALL PAT PREFIX SUFFIX)." ;; to /usr/share/a/e just because we mistyped "ae" for "ar", ;; so we probably don't want initials to touch anything that ;; looks like /usr/share/foo. As a heuristic, we just check that - ;; the text before the boundary char is at most 1 char. - ;; This allows both ~/eee and /eee and not much more. + ;; the previous completion field is empty. + ;; This allows ~/eee and /eee and /usr//eee and not much more. ;; FIXME: It sadly also disallows the use of ~/eee when that's ;; embedded within something else (e.g. "(~/eee" in Info node ;; completion or "ancestor:/eee" in bzr-revision completion). - (when (< (car bounds) 3) + (when (let ((str-without-last-field (substring str 0 (1- (car bounds))))) + (= (car (completion-boundaries str-without-last-field table pred "")) + (length str-without-last-field))) (let ((sep (substring str (1- (car bounds)) (car bounds)))) ;; FIXME: the above string-match checks the whole string, whereas ;; we end up only caring about the after-boundary part. diff --git a/test/lisp/minibuffer-tests.el b/test/lisp/minibuffer-tests.el index 1db0c07f510..59ae6ae3758 100644 --- a/test/lisp/minibuffer-tests.el +++ b/test/lisp/minibuffer-tests.el @@ -355,6 +355,18 @@ ("sources/clang" "sys/class") "pcm/" ""))))) +(ert-deftest completion-initials () + ;; Should expand initials: + (should (equal (completion-initials-expand "/ttab" #'read-file-name-internal nil) + "/t/t/a/b")) + (should (equal (completion-initials-expand "~/ttab" #'read-file-name-internal nil) + "~/t/t/a/b")) + (should (equal (completion-initials-expand "/home//ttab" #'read-file-name-internal nil) + "/home//t/t/a/b")) ; bug#81241 + ;; Should not expand initials: + (should-not (completion-initials-expand "/x/ttab" #'read-file-name-internal nil)) + (should-not (completion-initials-expand "/usr/share/ttab" #'read-file-name-internal nil))) + (ert-deftest completion-pcm-test-anydelim () ;; After each delimiter is a special wildcard which matches any ;; sequence of delimiters. commit 6b31360a3195ba9786df9767097961fcfa49b909 Author: Spencer Baugh Date: Mon Jun 29 17:30:01 2026 -0400 Fix c-pcm-try-completion with boundaries completion PCM try-completion could behavior incorrectly with completion tables using boundaries, such as file name completion. It would "grow" earlier path components as if point was at the end of each path component (rather than at its true location), which meant all path components would "grow" not only from the left \(which is correct) but also from the right (which can only work when point is there). * lisp/minibuffer.el (completion-pcm--find-all-completions): Drop the sub-pattern's trailing `point' (bug#80914). * test/lisp/minibuffer-tests.el (completion-pcm-test-9): New test. diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el index a24b92cdae8..4db3d0d0bea 100644 --- a/lisp/minibuffer.el +++ b/lisp/minibuffer.el @@ -4608,6 +4608,11 @@ filter out additional entries (because TABLE might not obey PRED)." ;; Text that goes between the new submatches and the ;; completion substring. (between nil)) + ;; SUBPAT was computed with point=(length substring); remove + ;; the trailing `point' since that's not the real location of + ;; point (bug#80914). + (cl-assert (eq (car (last subpat)) 'point)) + (setq subpat (butlast subpat)) ;; Eliminate submatches that don't end with the separator. (dolist (submatch (prog1 suball (setq suball ()))) (when (eq sep (aref submatch (1- (length submatch)))) diff --git a/test/lisp/minibuffer-resources/pcm/sources/clang b/test/lisp/minibuffer-resources/pcm/sources/clang new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/lisp/minibuffer-resources/pcm/sys/class b/test/lisp/minibuffer-resources/pcm/sys/class new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/lisp/minibuffer-tests.el b/test/lisp/minibuffer-tests.el index 16ee1753645..1db0c07f510 100644 --- a/test/lisp/minibuffer-tests.el +++ b/test/lisp/minibuffer-tests.el @@ -340,6 +340,21 @@ "" '("fooxbar" "fooybar") nil 0) '("foobar" . 3)))) +(ert-deftest completion-pcm-bug80914 () + ;; Completing a partial match in an earlier component (here "s" + ;; matches both "sys" and "sources", which contain "class" and + ;; "clang") should not leave a stray `point' in the middle of the + ;; merged pattern (bug#80914). + (let ((default-directory (ert-resource-directory)) + (input "pcm/s/cl")) + ;; The pattern has a single `point' at the end rather than an extra + ;; `point' after the "s". + (should (equal (completion-pcm--find-all-completions + input #'completion--file-name-table nil (length input)) + '(("s" any "/" "cl" point) + ("sources/clang" "sys/class") + "pcm/" ""))))) + (ert-deftest completion-pcm-test-anydelim () ;; After each delimiter is a special wildcard which matches any ;; sequence of delimiters. commit e560eacf6dc5dfc8be1be24568ad40ecb56a07a1 Author: haiyang miao Date: Thu Jul 2 10:55:24 2026 +0200 In pgtk_free_frame_resources transfer keyboard focus to parent (Bug#64625) * src/pgtkterm.c (pgtk_new_focus_frame): Declare static. (pgtk_free_frame_resources): If this frame currently holds keyboard focus, explicitly transfer focus to its parent frame before releasing resources (Bug#64625). Copyright-paperwork-exempt: yes diff --git a/src/pgtkterm.c b/src/pgtkterm.c index 66a42b9008a..733c7d27c4e 100644 --- a/src/pgtkterm.c +++ b/src/pgtkterm.c @@ -109,6 +109,7 @@ static bool current_drop_context_drop; static guint32 current_drop_time; static void pgtk_delete_display (struct pgtk_display_info *); +static void pgtk_new_focus_frame (struct pgtk_display_info *, struct frame *); static void pgtk_clear_frame_area (struct frame *, int, int, int, int); static void pgtk_fill_rectangle (struct frame *, unsigned long, int, int, int, int, bool); @@ -467,9 +468,62 @@ pgtk_free_frame_resources (struct frame *f) #define CLEAR_IF_EQ(FIELD) \ do { if (f == dpyinfo->FIELD) dpyinfo->FIELD = 0; } while (false) - CLEAR_IF_EQ (x_focus_frame); + /* If this frame currently holds keyboard focus, explicitly transfer + focus to its parent frame before releasing resources. + + On X11 the X server delivers a FocusIn event to the parent when a + child window is destroyed, so focus was restored automatically. + On Wayland there is no such mechanism: the compositor does nothing, + leaving Emacs with x_focus_frame == NULL permanently and ignoring + all keyboard input (bug#64625). + + IMPORTANT: we must clear highlight_frame from f BEFORE calling + pgtk_new_focus_frame. pgtk_new_focus_frame calls + pgtk_frame_rehighlight, which calls frame_unhighlight(old_highlight) + where old_highlight = dpyinfo->highlight_frame. If that is still f, + frame_unhighlight -> gui_update_cursor(f) would try to use f's face + cache, which was already freed by free_frame_faces(f) above -> + segfault. By clearing highlight_frame first, pgtk_frame_rehighlight + sees old_highlight == NULL and skips frame_unhighlight entirely. */ + if (f == dpyinfo->x_focus_frame) + { + struct frame *new_focus = FRAME_PARENT_FRAME (f); + + /* Clear highlight_frame from f so pgtk_frame_rehighlight will not + call frame_unhighlight on the dying frame whose faces are freed. */ + if (dpyinfo->highlight_frame == f) + dpyinfo->highlight_frame = NULL; + + if (new_focus != NULL + && FRAME_LIVE_P (new_focus) + && FRAME_GTK_WIDGET (new_focus) != NULL + && gtk_widget_get_realized (FRAME_GTK_WIDGET (new_focus))) + { + /* Transfer Emacs's internal focus/highlight to the parent. */ + pgtk_new_focus_frame (dpyinfo, new_focus); + /* Physically move the GTK/Wayland keyboard focus to the parent + widget. The Wayland compositor will not do this automatically + when the child widget is destroyed. */ + gtk_widget_grab_focus (FRAME_GTK_WIDGET (new_focus)); + } + else + { + /* Parent is gone or unrealized; just clear focus state. */ + dpyinfo->x_focus_frame = NULL; + pgtk_frame_rehighlight (dpyinfo); + } + } + + /* CLEAR_IF_EQ is now a no-op for highlight_frame if we already cleared + it above, which is correct. */ CLEAR_IF_EQ (highlight_frame); - CLEAR_IF_EQ (x_focus_event_frame); + + /* Clear x_focus_event_frame directly so that any focus-out event + emitted by gtk_widget_destroy below is a no-op in + pgtk_focus_changed, preventing a spurious + pgtk_new_focus_frame(dpyinfo, NULL) that would undo the transfer. */ + if (f == dpyinfo->x_focus_event_frame) + dpyinfo->x_focus_event_frame = NULL; CLEAR_IF_EQ (last_mouse_frame); CLEAR_IF_EQ (last_mouse_motion_frame); CLEAR_IF_EQ (last_mouse_glyph_frame); commit 6f9ac7ffdbaaf81152f38bca6194504f10bb88e8 Author: Johan Myréen Date: Thu Jul 2 10:41:31 2026 +0200 Fix fullscreen state handling for PGTK (Bug#81165, Bug#81320) * src/pgtkterm.c (set_fullscreen_state): Unfullscreen frame in the FULLSCREEN_HEIGHT/_WIDTH case (Bug#81165, Bug#81320). Copyright-paperwork-exempt: yes diff --git a/src/pgtkterm.c b/src/pgtkterm.c index 757ff57a9f2..66a42b9008a 100644 --- a/src/pgtkterm.c +++ b/src/pgtkterm.c @@ -4483,7 +4483,8 @@ set_fullscreen_state (struct frame *f) case FULLSCREEN_WIDTH: case FULLSCREEN_HEIGHT: - /* Not supported by gtk. Ignore them. */ + /* Restoring from fullscreen to tiled (Bug#81165, Bug#81320). */ + gtk_window_unfullscreen (widget); break; } commit 727a57d9712c5cfb3a2afb11dab437332b3912f4 Author: Eli Zaretskii Date: Thu Jul 2 11:37:06 2026 +0300 ; Don't autoload 'define-multisession-variable' * lisp/emacs-lisp/multisession.el (define-multisession-variable): Don't autoload, since it calls a function that isn't autoloaded. (Bug#81299) diff --git a/lisp/emacs-lisp/multisession.el b/lisp/emacs-lisp/multisession.el index 8df3d9e4b22..fbd5452a71f 100644 --- a/lisp/emacs-lisp/multisession.el +++ b/lisp/emacs-lisp/multisession.el @@ -54,7 +54,6 @@ Valid methods are `sqlite' and `files'." :version "29.1" :group 'files) -;;;###autoload (defmacro define-multisession-variable (name initial-value &optional doc &rest args) "Make NAME into a multisession variable initialized from INITIAL-VALUE. commit d1c3ba95cb1ee1059edd76956bf5ce1a08850315 Author: Dmitry Gutov Date: Thu Jul 2 08:44:33 2026 +0300 * lisp/progmodes/project.el: Update Commentary. diff --git a/lisp/progmodes/project.el b/lisp/progmodes/project.el index 9ecc1f910d2..616962e3e2b 100644 --- a/lisp/progmodes/project.el +++ b/lisp/progmodes/project.el @@ -157,11 +157,9 @@ ;; force every project backend to be aware of the build tool(s) the ;; project is using. ;; -;; * Command to (re)build the tag files in all project roots. To that -;; end, we might need to add a way to provide file whitelist -;; wildcards for each root to limit etags to certain files (in -;; addition to the blacklist provided by ignores), and/or allow -;; specifying additional tag regexps. +;; * Minor mode to (re)build the tags across the current project: +;; `etags-regen-mode'. It would be straightforward to add support for +;; multiple roots, if someone works with a project organized this way. ;; ;; * UI for the user to be able to pick the current project for the ;; whole Emacs session, independent of the current directory. Or, commit 5fde9732b4be6fc2dbc9ba8ae67dad01572ffcad Author: Dmitry Gutov Date: Thu Jul 2 07:58:09 2026 +0300 Localize cache invalidation to project-try-vc With other functions only using the cached values or populating when necessary. * lisp/progmodes/project.el: Update commentary (bug#81317). (project--get-cached): Add explicit parameter TIMEOUT, use it. (project-try-vc): Build its value from 'non-essential' and the values of two timeout variables. And pass them on. (project-try-vc--search, project--vc-merge-submodules-p) (project--value-in-dir): Also add TIMEOUT. (project-files, vc-git-project-list-files) (vc-hg-project-list-files, project-ignores, project-buffers) (project-name, project-uniquify-dirname-transform): Remove the binding of 'non-essential' as now redundant for cache duration. * test/lisp/progmodes/project-tests.el (project-try-vc-uses-cache) (project-try-vc-invalidates-cache) (project-name--reuses-cache) (project-name--obeys-cache-invalidation): New tests. (project-vc-supports-project-in-different-dir) (project-vc-ignores-in-external-directory): Use 'project--clear-cache' as the more reliable option. diff --git a/lisp/progmodes/project.el b/lisp/progmodes/project.el index 557d4260b77..9ecc1f910d2 100644 --- a/lisp/progmodes/project.el +++ b/lisp/progmodes/project.el @@ -84,11 +84,11 @@ ;; This project type can also be used for non-VCS controlled ;; directories, see the variable `project-vc-extra-root-markers'. ;; -;; Some of the methods on this backend cache their computations for time -;; determined either by variable `project-vc-cache-timeout' or +;; Some of the methods on this backend cache their computations. +;; Cache invalidation is done inside the `project-current' call, with +;; duration determined either by variable `project-vc-cache-timeout' or ;; `project-vc-non-essential-cache-timeout', depending on whether the -;; MAYBE-PROMPT argument to `project-current' is non-nil, or the value -;; of `non-essential' when project methods are called. +;; argument MAYBE-PROMPT is non-nil. ;; ;; Utils: ;; @@ -613,27 +613,21 @@ higher numbers, intended for \"background\" things like `project-mode-line' indicators and `project-uniquify-dirname-transform'. It is used when `non-essential' is non-nil.") -(defun project--get-cached (dir key) +(defun project--get-cached (dir key timeout) (let ((cached (vc-file-getprop dir key)) (current-time (float-time))) (when (and (numberp (cdr cached)) ;; Support package upgrade mid-session. - (let* ((project-vc-cache-timeout - (if non-essential - project-vc-non-essential-cache-timeout - project-vc-cache-timeout)) - (timeout + (let* ((timeout (cond - ((numberp project-vc-cache-timeout) - project-vc-cache-timeout) - ((null project-vc-cache-timeout) - nil) - ((listp project-vc-cache-timeout) + ((numberp timeout) + timeout) + ((listp timeout) (cdr (seq-find (lambda (pair) (and (functionp (car pair)) (funcall (car pair) dir))) - project-vc-cache-timeout))) + timeout))) (t nil)))) (or (null timeout) (< (- current-time (cdr cached)) timeout)))) @@ -658,15 +652,18 @@ It is used when `non-essential' is non-nil.") The value is cached, and depending on whether MAYBE-PROMPT was non-nil in the `project-current' call, the timeout is determined by `project-vc-cache-timeout' or `project-vc-non-essential-cache-timeout'." - (let ((cached (project--get-cached dir 'project-vc))) + (let* ((timeout (if non-essential + project-vc-non-essential-cache-timeout + project-vc-cache-timeout)) + (cached (project--get-cached dir 'project-vc timeout))) (if (eq cached 'none) nil (or cached - (let ((res (project-try-vc--search dir))) + (let ((res (project-try-vc--search dir timeout))) (project--set-cached dir 'project-vc (or res 'none)) res))))) -(defun project-try-vc--search (dir) +(defun project-try-vc--search (dir timeout) (let* ((backend-markers (delete nil @@ -679,7 +676,7 @@ in the `project-current' call, the timeout is determined by (mapconcat (lambda (m) (format "\\(%s\\)" (wildcard-to-regexp m))) (append backend-markers - (project--value-in-dir 'project-vc-extra-root-markers dir)) + (project--value-in-dir 'project-vc-extra-root-markers dir timeout)) "\\|") "\\'")) (locate-dominating-stop-dir-regexp @@ -704,7 +701,7 @@ in the `project-current' call, the timeout is determined by (while (and root (eq backend 'Git) - (project--vc-merge-submodules-p root) + (project--vc-merge-submodules-p root timeout) (project--submodule-p root)) (let* ((parent (file-name-directory (directory-file-name root)))) (setq root (vc-call-backend 'Git 'root parent)))) @@ -715,7 +712,7 @@ in the `project-current' call, the timeout is determined by (let* ((project-vc-extra-root-markers nil) ;; Avoid submodules scan. (enable-dir-local-variables nil) - (parent (project-try-vc--search root))) + (parent (project-try-vc--search root timeout))) (and parent (setq backend (nth 1 parent))))) (setq project (list 'vc backend root)) project))) @@ -764,7 +761,7 @@ in the `project-current' call, the timeout is determined by (cl-defmethod project-files ((project (head vc)) &optional dirs) (mapcan (lambda (dir) - (let ((ignores (project--value-in-dir 'project-vc-ignores dir)) + (let ((ignores (project--value-in-dir 'project-vc-ignores dir nil)) (backend (project-vc--backend project dir))) (if backend (vc-call-backend backend 'project-list-files dir ignores) @@ -792,7 +789,8 @@ in the `project-current' call, the timeout is determined by (vc-git-use-literal-pathspecs nil) (include-untracked (project--value-in-dir 'project-vc-include-untracked - dir)) + dir + nil)) (submodules (project--git-submodules)) (gitver (vc-git--program-version)) (dedup (and (version<= "2.31" gitver) '("--deduplicate"))) @@ -844,7 +842,7 @@ in the `project-current' call, the timeout is determined by (with-output-to-string (apply #'vc-git-command standard-output 0 nil "ls-files" args)) "\0" t)))) - (when (project--vc-merge-submodules-p default-directory) + (when (project--vc-merge-submodules-p default-directory nil) ;; Unfortunately, 'ls-files --recurse-submodules' conflicts with '-o'. (let ((sub-files (mapcar @@ -869,7 +867,8 @@ in the `project-current' call, the timeout is determined by (let* ((default-directory (expand-file-name (file-name-as-directory dir))) (include-untracked (project--value-in-dir 'project-vc-include-untracked - dir)) + dir + nil)) (args (list (concat "-mcard" (and include-untracked "u")) "--no-status" "-0")) @@ -889,10 +888,11 @@ in the `project-current' call, the timeout is determined by files))) files))) -(defun project--vc-merge-submodules-p (dir) +(defun project--vc-merge-submodules-p (dir timeout) (project--value-in-dir 'project-vc-merge-submodules - dir)) + dir + timeout)) (defun project--git-submodules () ;; 'git submodule foreach' is much slower. @@ -909,7 +909,7 @@ in the `project-current' call, the timeout is determined by (cl-defmethod project-ignores ((project (head vc)) dir) (project--vc-ignores dir (project-vc--backend project dir) - (project--value-in-dir 'project-vc-ignores dir))) + (project--value-in-dir 'project-vc-ignores dir nil))) (defun project--vc-ignores (dir backend extra-ignores) (require 'vc) ; Can be removed when we require Emacs 31.1. @@ -966,12 +966,14 @@ DIRS must contain directory names." ;; Sidestep the issue of expanded/abbreviated file names here. (cl-set-difference files dirs :test #'file-in-directory-p)) -(defun project--value-in-dir (var dir) +(defun project--value-in-dir (var dir timeout) + "Look up variable VAR's value in DIR, with cache duration TIMEOUT. +If TIMEOUT is nil, the cache is not invalidated." (alist-get var (and enable-dir-local-variables - (let ((cached (project--get-cached dir 'project-vc-dir-locals))) + (let ((cached (project--get-cached dir 'project-vc-dir-locals timeout))) (if (eq cached 'none) nil (or cached @@ -990,7 +992,7 @@ DIRS must contain directory names." (cl-defmethod project-buffers ((project (head vc))) (let* ((root (expand-file-name (file-name-as-directory (project-root project)))) - (modules (unless (or (project--vc-merge-submodules-p root) + (modules (unless (or (project--vc-merge-submodules-p root nil) (condition-case nil (project--submodule-p root) (file-missing nil))) @@ -1008,12 +1010,8 @@ DIRS must contain directory names." (nreverse bufs))) (cl-defmethod project-name ((project (head vc))) - "Returns the name of this VC-aware type PROJECT. - -The value is cached, and depending on whether `non-essential' is nil, -the timeout is determined by `project-vc-cache-timeout' or -`project-vc-non-essential-cache-timeout'." - (or (project--value-in-dir 'project-vc-name (project-root project)) + "Returns the name of this VC-aware type PROJECT." + (or (project--value-in-dir 'project-vc-name (project-root project) nil) (cl-call-next-method))) @@ -2735,8 +2733,7 @@ slash-separated components from `project-name' will be appended to the buffer's directory name when buffers from two different projects would otherwise have the same name." (if-let* ((proj (project-current nil dirname))) - (let ((root (project-root proj)) - (non-essential t)) + (let ((root (project-root proj))) (expand-file-name (file-name-concat (file-name-directory root) @@ -2782,7 +2779,6 @@ value is `non-remote', show the project name only for local files." ;; 'last-coding-system-used' when reading the project name ;; from .dir-locals.el also enables flyspell-mode (bug#66825). (when-let* ((last-coding-system-used last-coding-system-used) - (non-essential t) (project (project-current)) (project-name (project-name project))) (concat diff --git a/test/lisp/progmodes/project-tests.el b/test/lisp/progmodes/project-tests.el index 29aaaa1e502..f17ea46b411 100644 --- a/test/lisp/progmodes/project-tests.el +++ b/test/lisp/progmodes/project-tests.el @@ -150,7 +150,7 @@ When `project-ignores' includes a name matching project dir." "Check that it picks up dir-locals settings from somewhere else." (skip-unless (eq (vc-responsible-backend default-directory) 'Git)) (let* ((dir (ert-resource-directory)) - (_ (vc-file-clearprops dir)) + (_ (project--clear-cache)) (project-vc-extra-root-markers '(".dir-locals.el")) (project (project-current nil dir))) (should-not (null project)) @@ -181,7 +181,7 @@ When `project-ignores' includes a name matching project dir." "Check that it applies project-vc-ignores when DIR is external to root." (skip-unless (eq (vc-responsible-backend default-directory) 'Git)) (let* ((dir (ert-resource-directory)) - (_ (vc-file-clearprops dir)) + (_ (project--clear-cache)) ;; Do not detect VC backend. (project-vc-backend-markers-alist nil) (project-vc-extra-root-markers '("configure.ac")) @@ -259,4 +259,80 @@ When `project-ignores' includes a name matching project dir." (should (equal (sort (mapcar #'xref-item-summary matches) #'string<) '("((nil . ((project-vc-ignores . (\"etc\")))))" "etc")))))) +(ert-deftest project-try-vc-uses-cache () + "Check that it reuses the value that's already cached." + (skip-unless (eq (vc-responsible-backend default-directory) 'Git)) + ;; Prepare + (let* ((dir (file-name-directory project-tests--this-file)) + (_ (project--clear-cache)) + (project-vc-extra-root-markers '("files-x-tests.*")) + (project (project-current nil dir))) + (should (nth 1 project)) + (should (string-match-p "/test/lisp/\\'" (project-root project))) + (let* ((project-vc-extra-root-markers nil) + (project-vc-non-essential-cache-timeout 0.1) + (project-cached (project-current nil dir))) + (should (equal project project-cached))))) + +(ert-deftest project-try-vc-invalidates-cache () + "Check that it invalidates the cached value that's too old." + "Check that one can add wildcard entries." + (skip-unless (eq (vc-responsible-backend default-directory) 'Git)) + ;; Prepare + (let* ((dir (file-name-directory project-tests--this-file)) + (_ (project--clear-cache)) + (project-vc-extra-root-markers '("files-x-tests.*")) + (project (project-current nil dir))) + (should (nth 1 project)) + (should (string-match-p "/test/lisp/\\'" (project-root project))) + (let* ((project-vc-extra-root-markers nil) + (project-vc-non-essential-cache-timeout 0.0) + (project-fresh (project-current nil dir))) + (should (file-equal-p + (project-root project-fresh) + (expand-file-name "../../../" dir)))))) + +(ert-deftest project-name--reuses-cache () + "Check that it reuses the cached value." + (skip-unless (eq (vc-responsible-backend default-directory) 'Git)) + (project--clear-cache) + (ert-with-temp-directory dir + (write-region "((nil . ((project-vc-name . \"barbaz\"))))" + nil + (expand-file-name ".dir-locals.el" dir)) + (write-region "" nil (expand-file-name "project-marker" dir)) + (let* ((project-vc-extra-root-markers '("project-marker")) + (project (project-current nil dir))) + (should (equal (project-name project) + "barbaz")) + (let* ((project-vc-extra-root-markers nil) + (project-vc-non-essential-cache-timeout 0)) + ;; No change, even if the corresponding cache expired. + (should (equal (project-name project) + "barbaz")))))) + +(ert-deftest project-name--obeys-cache-invalidation () + "Check that project-name cache obeys invalidation in project-try-vc." + (skip-unless (eq (vc-responsible-backend default-directory) 'Git)) + (project--clear-cache) + (ert-with-temp-directory dir + (write-region "((nil . ((project-vc-name . \"barbaz\"))))" + nil + (expand-file-name ".dir-locals.el" dir)) + (write-region "" nil (expand-file-name "project-marker" dir)) + (let* ((project-vc-extra-root-markers '("project-marker")) + (project (project-current nil dir))) + (should (equal (project-name project) + "barbaz")) + (delete-file (expand-file-name ".dir-locals.el" dir)) + (let* ((project-vc-non-essential-cache-timeout 0) + (project-fresh (project-current nil dir))) + ;; Same project root. + (should (equal project project-fresh)) + ;; But the name is refreshed. + (should (not (equal (project-name project) "barbaz"))) + (should (equal (project-name project) + (file-name-nondirectory + (directory-file-name dir)))))))) + ;;; project-tests.el ends here commit 707b0ba0f4cc59b451ea039b01af5c977d96e46d Author: Dmitry Gutov Date: Thu Jul 2 07:33:09 2026 +0300 Fix project--clear-cache and project--value-in-dir in special case * lisp/progmodes/project.el (project--clear-cache): Make sure to clear the 'project-vc-dir-locals' keys too. (project--value-in-dir): Predicate the cache lookup (and most importantly, write) on whether enable-dir-local-variables is non-nil. So its uses inside recursive 'project-try-vc--search' call do not not bust the value (bug#81317). diff --git a/lisp/progmodes/project.el b/lisp/progmodes/project.el index ebbaa7609ea..557d4260b77 100644 --- a/lisp/progmodes/project.el +++ b/lisp/progmodes/project.el @@ -647,7 +647,9 @@ It is used when `non-essential' is non-nil.") (obarray-map (lambda (sym) (if (get sym 'project-vc) - (put sym 'project-vc nil))) + (put sym 'project-vc nil)) + (if (get sym 'project-vc-dir-locals) + (put sym 'project-vc-dir-locals nil))) vc-file-prop-obarray)) (defun project-try-vc (dir) @@ -967,13 +969,15 @@ DIRS must contain directory names." (defun project--value-in-dir (var dir) (alist-get var - (let ((cached (project--get-cached dir 'project-vc-dir-locals))) - (if (eq cached 'none) - nil - (or cached - (let ((res (project--read-dir-locals dir))) - (project--set-cached dir 'project-vc-dir-locals (or res 'none)) - res)))) + (and + enable-dir-local-variables + (let ((cached (project--get-cached dir 'project-vc-dir-locals))) + (if (eq cached 'none) + nil + (or cached + (let ((res (project--read-dir-locals dir))) + (project--set-cached dir 'project-vc-dir-locals (or res 'none)) + res))))) (default-value var))) (defun project--read-dir-locals (dir) commit ba7d2b8232584bc1c67708b3cd01f9470ff1bc76 Author: Michael Albinus Date: Wed Jul 1 17:01:30 2026 +0200 * admin/notes/jargon: Add TTTT. diff --git a/admin/notes/jargon b/admin/notes/jargon index 35f7a8c1037..20ebf200e92 100644 --- a/admin/notes/jargon +++ b/admin/notes/jargon @@ -75,6 +75,7 @@ TIA - thanks in advance TIL - today I learned TL;DR - too long; didn't read TRT - the right thing +TTTT - to tell the truth WFM - works for me WIP - work in progress WRT - with respect to commit bcb83d7d2ad1d34a6683fc466654437b1e25ad16 Author: Michael Albinus Date: Wed Jul 1 17:01:12 2026 +0200 Fix error handling in Tramp delete-{file,directory} * lisp/net/tramp-smb.el (tramp-smb-handle-delete-directory): * lisp/net/tramp.el (tramp-skeleton-delete-directory): Fail if DIRECTORY is missing. (tramp-skeleton-delete-file): Don't fail if DIRECTORY is missing. * test/lisp/net/tramp-tests.el (tramp-test14-delete-directory): Adapt test. diff --git a/lisp/net/tramp-smb.el b/lisp/net/tramp-smb.el index ac54e47e376..6f88c7dedba 100644 --- a/lisp/net/tramp-smb.el +++ b/lisp/net/tramp-smb.el @@ -651,36 +651,35 @@ PRESERVE-UID-GID and PRESERVE-EXTENDED-ATTRIBUTES are completely ignored." (defun tramp-smb-handle-delete-directory (directory &optional recursive trash) "Like `delete-directory' for Tramp files." (tramp-skeleton-delete-directory directory recursive trash - (when (file-exists-p directory) - (when recursive - (mapc - (lambda (file) - (if (file-directory-p file) - (delete-directory file recursive) - (delete-file file))) - ;; We do not want to delete "." and "..". - (directory-files directory 'full directory-files-no-dot-files-regexp))) + (when recursive + (mapc + (lambda (file) + (if (file-directory-p file) + (delete-directory file recursive) + (delete-file file))) + ;; We do not want to delete "." and "..". + (directory-files directory 'full directory-files-no-dot-files-regexp))) + + ;; We must also flush the cache of the directory, because + ;; `file-attributes' reads the values from there. + (tramp-flush-directory-properties v localname) + (unless (tramp-smb-send-command + v (format + "%s %s" + (if (tramp-smb-get-cifs-capabilities v) + "posix_rmdir" "rmdir") + (tramp-smb-shell-quote-localname v))) + ;; Error. + (with-current-buffer (tramp-get-connection-buffer v) + (goto-char (point-min)) + (search-forward-regexp tramp-smb-errors nil t) + (tramp-error v 'file-error "%s `%s'" (match-string 0) directory))) - ;; We must also flush the cache of the directory, because - ;; `file-attributes' reads the values from there. - (tramp-flush-directory-properties v localname) - (unless (tramp-smb-send-command - v (format - "%s %s" - (if (tramp-smb-get-cifs-capabilities v) - "posix_rmdir" "rmdir") - (tramp-smb-shell-quote-localname v))) - ;; Error. - (with-current-buffer (tramp-get-connection-buffer v) - (goto-char (point-min)) - (search-forward-regexp tramp-smb-errors nil t) - (tramp-error v 'file-error "%s `%s'" (match-string 0) directory))) - - ;; "rmdir" does not report an error. So we check ourselves. - ;; Deletion of a watched directory could be pending. - (when (and (not (tramp-directory-watched directory)) - (file-exists-p directory)) - (tramp-error v 'file-error "`%s' not removed" directory))))) + ;; "rmdir" does not report an error. So we check ourselves. + ;; Deletion of a watched directory could be pending. + (when (and (not (tramp-directory-watched directory)) + (file-exists-p directory)) + (tramp-error v 'file-error "`%s' not removed" directory)))) (defun tramp-smb-handle-delete-file (filename &optional trash) "Like `delete-file' for Tramp files." diff --git a/lisp/net/tramp.el b/lisp/net/tramp.el index c5ecbdd9675..78e1d34ebab 100644 --- a/lisp/net/tramp.el +++ b/lisp/net/tramp.el @@ -3734,14 +3734,15 @@ BODY is the backend specific code." ;; This variable exists since Emacs 30.1. (not (bound-and-true-p remote-file-name-inhibit-delete-by-moving-to-trash))))) - (if (and delete-by-moving-to-trash ,trash) - ;; Move non-empty dir to trash only if recursive deletion was - ;; requested. - (if (not (or ,recursive (directory-empty-p ,directory))) - (tramp-error - v 'file-error "Directory is not empty, not moving to trash") - (move-file-to-trash ,directory)) - ,@body) + (tramp-barf-if-file-missing v ,directory + (if (and delete-by-moving-to-trash ,trash) + ;; Move non-empty dir to trash only if recursive deletion was + ;; requested. + (if (not (or ,recursive (directory-empty-p ,directory))) + (tramp-error + v 'file-error "Directory is not empty, not moving to trash") + (move-file-to-trash ,directory)) + ,@body)) (tramp-flush-directory-properties v localname)))) (defmacro tramp-skeleton-delete-file (filename &optional trash &rest body) @@ -3754,9 +3755,10 @@ BODY is the backend specific code." ;; This variable exists since Emacs 30.1. (not (bound-and-true-p remote-file-name-inhibit-delete-by-moving-to-trash))))) - (if (and delete-by-moving-to-trash ,trash) - (move-file-to-trash ,filename) - ,@body) + (ignore-errors + (if (and delete-by-moving-to-trash ,trash) + (move-file-to-trash ,filename) + ,@body)) (tramp-flush-file-properties v localname)))) (defmacro tramp-skeleton-directory-files diff --git a/test/lisp/net/tramp-tests.el b/test/lisp/net/tramp-tests.el index b622a08dce3..6badf064efe 100644 --- a/test/lisp/net/tramp-tests.el +++ b/test/lisp/net/tramp-tests.el @@ -3322,6 +3322,16 @@ This tests also `file-directory-p' and `file-accessible-directory-p'." (dolist (quoted (if (tramp--test-expensive-test-p) '(nil t) '(nil))) (let* ((tmp-name1 (tramp--test-make-temp-name nil quoted)) (tmp-name2 (expand-file-name "foo" tmp-name1))) + ;; Deleting a non-existing file should not fail. + (delete-file tmp-name1) + (delete-file tmp-name1 'trash) + ;; Deleting a non-existing directory should fail. + (should-error + (delete-directory tmp-name1) + :type 'file-missing) + (should-error + (delete-directory tmp-name1 nil 'trash) + :type 'file-missing) ;; Delete empty directory. (make-directory tmp-name1) (should (file-directory-p tmp-name1)) commit b8d3c4f8011b902323c34967c36805b06d5d3ecc Author: Michael Albinus Date: Wed Jul 1 17:00:04 2026 +0200 Change Tramp version integrated in Emacs 31.1 (don't merge) * doc/misc/trampver.texi: * lisp/net/trampver.el (tramp-version): Adapt Tramp versions. * lisp/net/trampver.el (customize-package-emacs-version-alist): Change Tramp version integrated in Emacs 31.1. diff --git a/doc/misc/trampver.texi b/doc/misc/trampver.texi index 165d0eb1352..8b5e429d0cd 100644 --- a/doc/misc/trampver.texi +++ b/doc/misc/trampver.texi @@ -7,7 +7,7 @@ @c In the Tramp GIT, the version number and the bug report address @c are auto-frobbed from configure.ac. -@set trampver 2.8.2 +@set trampver 2.8.2.31.1 @set trampurl https://www.gnu.org/software/tramp/ @set tramp-bug-report-address tramp-devel@@gnu.org @set emacsver 28.1 diff --git a/lisp/net/trampver.el b/lisp/net/trampver.el index 00d5ffb2afa..1366147cec4 100644 --- a/lisp/net/trampver.el +++ b/lisp/net/trampver.el @@ -7,7 +7,7 @@ ;; Maintainer: Michael Albinus ;; Keywords: comm, processes ;; Package: tramp -;; Version: 2.8.2 +;; Version: 2.8.2.31.1 ;; Package-Requires: ((emacs "28.1")) ;; Package-Type: multi ;; URL: https://www.gnu.org/software/tramp/ @@ -40,7 +40,7 @@ ;; ./configure" to change them. ;;;###tramp-autoload -(defconst tramp-version "2.8.2" +(defconst tramp-version "2.8.2.31.1" "This version of Tramp.") ;;;###tramp-autoload @@ -76,7 +76,7 @@ ;; Check for Emacs version. (let ((x (if (not (string-version-lessp emacs-version "28.1")) "ok" - (format "Tramp 2.8.2 is not fit for %s" + (format "Tramp 2.8.2.31.1 is not fit for %s" (replace-regexp-in-string "\n" "" (emacs-version)))))) (unless (string-equal "ok" x) (error "%s" x))) @@ -110,7 +110,7 @@ ("2.6.0.29.1" . "29.1") ("2.6.2.29.2" . "29.2") ("2.6.3-pre" . "29.3") ("2.6.3" . "29.4") ("2.7.1.30.1" . "30.1") ("2.7.3.30.2" . "30.2") - ("2.8.2" . "31.1"))) + ("2.8.2.31.1" . "31.1"))) (add-hook 'tramp-unload-hook (lambda () commit 521c6263fa666d2e1621adec080088c3933acee7 Author: Eli Zaretskii Date: Wed Jul 1 15:43:46 2026 +0300 ; * src/buffer.c (Fbuffer_file_name): Doc fix (bug#81340). diff --git a/src/buffer.c b/src/buffer.c index ec26ff82c78..981a4e77739 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -1318,7 +1318,7 @@ immediately before it was killed. */) } DEFUN ("buffer-file-name", Fbuffer_file_name, Sbuffer_file_name, 0, 1, 0, - doc: /* Return name of file BUFFER is visiting, or nil if none. + doc: /* Return absolute name of file BUFFER is visiting, or nil if none. No argument or nil as argument means use the current buffer. */) (register Lisp_Object buffer) { commit 69463bfc0c5cd42bdbbad28aeea84fa205a32383 Author: Eli Zaretskii Date: Wed Jul 1 15:17:16 2026 +0300 ; * etc/PROBLEMS: Problem with Enchant 2.x. (Bug#81330) diff --git a/etc/PROBLEMS b/etc/PROBLEMS index 54302968e6d..19172a8b211 100644 --- a/etc/PROBLEMS +++ b/etc/PROBLEMS @@ -959,6 +959,16 @@ specified run-time search path in the executable. Please refer to the documentation of your dynamic linker for details. +*** Using Enchant 2.x as the Ispell back-end signals an error. + +The error message says "enchant-2: Unknown option -C". + +This is because many dictionary definitions in the default value of +'ispell-dictionary-alist' use the '-C' command-line option, which is not +supported by some versions of Enchant 2.x. Support for '-C' was added +to Enchant in version 2.8.17, so use that or later version to solve this +problem. + *** When you run Ispell from Emacs, it reports a "misalignment" error. This can happen if you compiled the Ispell program to use ASCII commit 5dd8fe25c23944cb8ac97d2d54b946c6a394d0fb Author: Yuan Fu Date: Tue Jun 30 22:28:50 2026 -0700 Correct cursor range in Ftreesit_query_capture (bug#81297) * src/treesit.c (Ftreesit_query_capture): Always explicitly set cursor's range. diff --git a/src/treesit.c b/src/treesit.c index 3d342be3dcc..55e6f8b94a1 100644 --- a/src/treesit.c +++ b/src/treesit.c @@ -4106,6 +4106,12 @@ the query. */) (uint32_t) (beg_byte - visible_beg), (uint32_t) (end_byte - visible_beg)); } + else + { + /* If range is unbounded, set it explicitly, in case the cursor is + reused and carries the range from last call. */ + ts_query_cursor_set_byte_range (cursor, 0, UINT32_MAX); + } /* Execute query. */ ts_query_cursor_exec (cursor, treesit_query, treesit_node); commit a057a32bf9700e862f412f508e93491d8b2ec309 Author: Sean Whitton Date: Tue Jun 30 17:11:23 2026 +0100 vc-dir-update: Remove assertion invalid on this branch * lisp/vc/vc-dir.el (vc-dir-update): Remove assertion invalid on the emacs-31 branch. Do not merge to master. diff --git a/lisp/vc/vc-dir.el b/lisp/vc/vc-dir.el index df3bbd14b66..50f3a75af68 100644 --- a/lisp/vc/vc-dir.el +++ b/lisp/vc/vc-dir.el @@ -646,8 +646,7 @@ Also update some VC file properties from ENTRIES." (or (null next) (vc-dir-fileinfo->directory (ewoc-data next))))) (ewoc-delete vc-ewoc crt))) - (setq crt prev)))) - (cl-assert (null to-remove)))) + (setq crt prev)))))) ;; Update VC file properties. (pcase-dolist (`(,file ,state ,_extra) entries) (vc-file-setprop file 'vc-backend commit 7b812bc38aa180532480e2eff54bdbd434a9881a Author: Martin Rudalics Date: Tue Jun 30 09:18:57 2026 +0200 Don't use window manager activation when a child frame has focus (Bug#81326) * src/xterm.c (x_get_toplevel_parent): Remove. (x_focus_frame): Never call x_ewmh_activate_frame when a child frame has focus (Bug#81326). diff --git a/src/xterm.c b/src/xterm.c index bc654952360..a1513d99f2d 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -28926,25 +28926,6 @@ x_get_focus_frame (struct frame *f) return lisp_focus; } -/* Return the toplevel parent of F, if it is a child frame. - Otherwise, return NULL. */ - -static struct frame * -x_get_toplevel_parent (struct frame *f) -{ - struct frame *parent; - - if (!FRAME_PARENT_FRAME (f)) - return NULL; - - parent = FRAME_PARENT_FRAME (f); - - while (FRAME_PARENT_FRAME (parent)) - parent = FRAME_PARENT_FRAME (parent); - - return parent; -} - static void x_set_input_focus (struct x_display_info *dpyinfo, Window window, Time time) @@ -29068,11 +29049,9 @@ x_focus_frame (struct frame *f, bool noactivate) may not work if its parent is not activated. */ && !FRAME_PARENT_FRAME (f) /* If the focus is being transferred from a child frame to - its toplevel parent, also use SetInputFocus. */ + another frame, also use SetInputFocus. */ && (!dpyinfo->x_focus_frame - || (x_get_toplevel_parent (dpyinfo->x_focus_frame) - != f)) - && x_wm_supports (f, dpyinfo->Xatom_net_active_window)) + || !FRAME_PARENT_FRAME (dpyinfo->x_focus_frame))) { /* When window manager activation is possible, use it instead. The window manager is expected to perform any commit b8f71a601193e3fa11aa54aa963203b8c313741a Author: Philip Kaludercic Date: Mon Jun 29 20:40:33 2026 +0200 Compile User Lisp files after adjusting 'load-path' * lisp/startup.el (prepare-user-lisp): Collect Lisp files in a list and process these after traversing the file system. This is necessary to prevent the compiler from failing to byte-compile files that depend on other files in the User Lisp directory because their neighboring dependencies cannot be located. (Bug#81304) diff --git a/lisp/startup.el b/lisp/startup.el index e6f2087604f..24cb3fc2582 100644 --- a/lisp/startup.el +++ b/lisp/startup.el @@ -1263,22 +1263,23 @@ unconditionally." (not (string-match-p ignored (file-name-nondirectory dir))))) (dir (expand-file-name user-lisp-directory)) (backup-inhibited t) - (dirs (list dir))) + (dirs (list dir)) (files '())) (add-to-list 'load-path (directory-file-name dir)) (dolist (file (directory-files-recursively dir "" t pred t)) (cond ((and (file-regular-p file) (string-suffix-p ".el" file)) - (unless just-activate - (with-demoted-errors "Error while compiling: %S" - (byte-recompile-file file force 0) - (when (native-comp-available-p) - (native-compile-async file))))) + (push file files)) ((and (file-directory-p file) (not (string-match-p ignored (file-name-nondirectory file)))) (add-to-list 'load-path (directory-file-name file)) (push file dirs)))) (unless just-activate - (loaddefs-generate dirs autoload-file nil nil nil force)) + (loaddefs-generate dirs autoload-file nil nil nil force) + (dolist (file files) + (with-demoted-errors "Error while compiling: %S" + (byte-recompile-file file force 0) + (when (native-comp-available-p) + (native-compile-async file))))) (when (file-exists-p autoload-file) (load autoload-file nil t)))) commit 1ac2f60b534add077c71a0f3ca7cf0e44b929d00 Author: Sean Whitton Date: Mon Jun 29 11:51:41 2026 +0100 Fix strange logic in vc-git-incoming-revision I think that I didn't fully update this function in this change: commit e915646b8944d8b611ab7094d9eb305ed162ff27 Author: Sean Whitton Date: Wed Feb 18 11:35:16 2026 +0000 vc-git-pull, vc-git-incoming-revision: Use push remotes * lisp/vc/vc-git.el (vc-git-pull, vc-git-incoming-revision): Use configured push remotes. * etc/NEWS: Announce change to vc-git-pull. * lisp/vc/vc-git.el (vc-git-incoming-revision): Don't duplicate looking for a branch remote after just having called vc-git--branch-remotes (bug#81328). diff --git a/lisp/vc/vc-git.el b/lisp/vc/vc-git.el index 2a328f5cc9f..f336aae984d 100644 --- a/lisp/vc/vc-git.el +++ b/lisp/vc/vc-git.el @@ -1911,28 +1911,18 @@ If LIMIT is a non-empty string, use it as a base revision." '("--"))))))) (defun vc-git-incoming-revision (&optional upstream-location refresh) - (let* ((remotes (and (not upstream-location) (vc-git--branch-remotes))) - (rev (or upstream-location - (cdr (assq 'push remotes)) - (cdr (assq 'upstream remotes))))) - (when (and (or refresh (null (vc-git--rev-parse rev))) - ;; If the branch has no upstream, and we weren't supplied - ;; with one, then fetching is always useless (bug#79952). - (or upstream-location - (and-let* ((branch (vc-git-working-branch))) - (with-temp-buffer - (vc-git--out-ok "config" "--get" - (format "branch.%s.remote" - branch)))))) - (vc-git-command nil 0 nil "fetch" - (and upstream-location - ;; Extract remote from "remote/branch". - (replace-regexp-in-string "/.*" "" - upstream-location)))) - (ignore-errors ; in order to return nil if no such branch - (with-output-to-string - (vc-git-command standard-output 0 nil - "log" "--max-count=1" "--pretty=format:%H" rev))))) + (let ((remotes (and (not upstream-location) (vc-git--branch-remotes)))) + (and-let* ((rev (or upstream-location + (cdr (assq 'push remotes)) + (cdr (assq 'upstream remotes))))) + (when (or refresh (null (vc-git--rev-parse rev))) + (vc-git-command nil 0 nil "fetch" + ;; Extract remote from "remote/branch". + (replace-regexp-in-string "/.*" "" rev))) + (ignore-errors ; in order to return nil if no such branch + (with-output-to-string + (vc-git-command standard-output 0 nil + "log" "--max-count=1" "--pretty=format:%H" rev)))))) (defun vc-git-log-search (buffer pattern) "Search the log of changes for PATTERN and output results into BUFFER. commit 10ae134ceba5f22c40dd1dc8ceb1f4f184760032 Author: Eli Zaretskii Date: Sun Jun 28 18:33:11 2026 +0300 ; Add new text for xdisp.c * test/src/xdisp-tests.el (xdisp-test-format-mode-line): Add another test for 'format-mode-line'. (Bug#81313) diff --git a/test/src/xdisp-tests.el b/test/src/xdisp-tests.el index 04dcfd6f59f..f3df858e218 100644 --- a/test/src/xdisp-tests.el +++ b/test/src/xdisp-tests.el @@ -196,6 +196,19 @@ int main () { (text-properties-at 0 (format-mode-line '((:propertize "Hello!" face bold)) 'mode-line)) - (list 'face '(bold mode-line) 'mode-line-elt-no 3)))) + (list 'face '(bold mode-line) 'mode-line-elt-no 3))) + (with-temp-buffer + ;; This test is due to Markus Triska . + (let ((m1 (format-mode-line mode-line-format nil)) + (m2 (format-mode-line mode-line-format 'default)) + s1 s2) + (font-lock-mode 0) + (insert "\n") + (insert m1) + (setq s1 (window-text-pixel-size nil (line-beginning-position) (point))) + (insert "\n") + (insert m2) + (setq s2 (window-text-pixel-size nil (line-beginning-position) (point))) + (should (equal m1 m2))))) ;;; xdisp-tests.el ends here commit 4d2701ecde9c3dfb7221ba6426c25dd4ca314033 Author: Eli Zaretskii Date: Sun Jun 28 14:59:16 2026 +0300 Fix 'format-mode-line' when faces are in format string * src/xdisp.c (store_mode_line_string): Don't assume that PROPS can only specify the face for LISP_STRING; if PROPS don't specify a face, fall back on the 'face' property of LISP_STRING. (Bug#81316) * test/src/xdisp-tests.el (xdisp-test-format-mode-line): Add a test for this issue. diff --git a/src/xdisp.c b/src/xdisp.c index a88f7ce830a..8513139d77e 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -28934,14 +28934,24 @@ store_mode_line_string (const char *string, Lisp_Object lisp_string, if (!NILP (mode_line_string_face)) { Lisp_Object face; - if (NILP (props)) - props = Ftext_properties_at (make_fixnum (0), lisp_string); - face = plist_get (props, Qface); + Lisp_Object string_face = + plist_get (Ftext_properties_at (make_fixnum (0), lisp_string), + Qface); + /* Use the face in PROPS, if any, falling back to the face of + LISP_STRING. */ + face = string_face; + if (!NILP (props)) + { + Lisp_Object propface = plist_get (props, Qface); + if (!NILP (propface)) + face = propface; + } if (NILP (face)) face = mode_line_string_face; else face = list2 (face, mode_line_string_face); - props = list2 (Qface, face); + props = Fcopy_sequence (props); + props = plist_put (props, Qface, face); if (copy_string) lisp_string = Fcopy_sequence (lisp_string); } diff --git a/test/src/xdisp-tests.el b/test/src/xdisp-tests.el index 4369c00ca34..04dcfd6f59f 100644 --- a/test/src/xdisp-tests.el +++ b/test/src/xdisp-tests.el @@ -190,6 +190,12 @@ int main () { (insert (format-mode-line (propertize "x" 'face 'bold-italic) 1200000000000000000000000000)) - (should (null (get-text-property 1 'face))))) + (should (null (get-text-property 1 'face)))) + (should + (equal + (text-properties-at + 0 + (format-mode-line '((:propertize "Hello!" face bold)) 'mode-line)) + (list 'face '(bold mode-line) 'mode-line-elt-no 3)))) ;;; xdisp-tests.el ends here commit a34c29e4598dd442b1b630da2923b2a698631ffe Author: Martin Rudalics Date: Sun Jun 28 10:27:02 2026 +0200 Restore frame's fullheight/fullwidth after exiting from fullboth (Bug#81165) * src/xterm.c (do_ewmh_fullscreen): Try to restore fullheight/fullwidth states after exiting from fullboth state (Bug#81165). diff --git a/src/xterm.c b/src/xterm.c index d95de2ef86b..bc654952360 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -28133,7 +28133,8 @@ do_ewmh_fullscreen (struct frame *f) || cur == FULLSCREEN_MAXIMIZED) set_wm_state (frame, false, dpyinfo->Xatom_net_wm_state_fullscreen, dpyinfo->Xatom_net_wm_state_maximized_vert); - if (cur != FULLSCREEN_MAXIMIZED || x_frame_normalize_before_maximize) + if ((cur != FULLSCREEN_MAXIMIZED && cur != FULLSCREEN_BOTH) + || x_frame_normalize_before_maximize) set_wm_state (frame, true, dpyinfo->Xatom_net_wm_state_maximized_horz, None); } @@ -28153,7 +28154,8 @@ do_ewmh_fullscreen (struct frame *f) || cur == FULLSCREEN_MAXIMIZED) set_wm_state (frame, false, dpyinfo->Xatom_net_wm_state_fullscreen, dpyinfo->Xatom_net_wm_state_maximized_horz); - if (cur != FULLSCREEN_MAXIMIZED || x_frame_normalize_before_maximize) + if ((cur != FULLSCREEN_MAXIMIZED && cur != FULLSCREEN_BOTH) + || x_frame_normalize_before_maximize) set_wm_state (frame, true, dpyinfo->Xatom_net_wm_state_maximized_vert, None); } commit c12c91563ee38740345bd693dd022b88421f4573 Author: Zhengyi Fu Date: Fri Jun 26 23:39:32 2026 +0800 Fix interactive mode spec of `xwidget-webkit-end-edit-textarea' * lisp/xwidget.el (xwidget-webkit-end-edit-textarea): Remove `xwidget-webkit-mode' condition from the interactive mode spec. This command is intended to be invoked in the 'textarea' buffer, which is created by `xwidget-webkit-begin-edit-textarea', and that buffer is in Fundamental mode. (Bug#81306) Copyright-paperwork-exempt: yes diff --git a/lisp/xwidget.el b/lisp/xwidget.el index c75cd047495..a24814c0e89 100644 --- a/lisp/xwidget.el +++ b/lisp/xwidget.el @@ -672,7 +672,7 @@ XW is the xwidget identifier, TEXT is retrieved from the webkit." (defun xwidget-webkit-end-edit-textarea () "End editing of a webkit text area." - (interactive nil xwidget-webkit-mode) + (interactive) (goto-char (point-min)) (while (search-forward "\n" nil t) (replace-match "\\n" nil t))