From fd9d685c63697aa65121c6fabcb7333f5381b5bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Engdeg=C3=A5rd?= Date: Fri, 23 Jan 2026 22:05:28 +0100 Subject: [PATCH 01/74] Neater pcase predicate transform Suggested by Stefan Monnier. * lisp/emacs-lisp/pcase.el (pcase--macroexpand): Simplify. * test/lisp/emacs-lisp/pcase-tests.el (pcase-pred-equiv): New test. --- lisp/emacs-lisp/pcase.el | 14 ++++++-------- test/lisp/emacs-lisp/pcase-tests.el | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/lisp/emacs-lisp/pcase.el b/lisp/emacs-lisp/pcase.el index 61b8f283bd2..7bb7d4a6b27 100644 --- a/lisp/emacs-lisp/pcase.el +++ b/lisp/emacs-lisp/pcase.el @@ -525,15 +525,13 @@ how many time this CODEGEN is called." (if (pcase--self-quoting-p pat) `',pat pat)) ((memq head '(guard quote)) pat) ((eq head 'pred) - ;; Ad-hoc expansion of some predicates that are the complement of another. + ;; Ad-hoc expansion of some predicates that are complements or aliases. ;; Not required for correctness but results in better code. - (let* ((expr (cadr pat)) - (compl (assq expr '((atom . consp) - (nlistp . listp) - (identity . null))))) - (cond (compl `(,head (not ,(cdr compl)))) - ((eq expr 'not) `(,head null)) ; normalise - (t pat)))) + (let ((equiv (assq (cadr pat) '((atom . (not consp)) + (nlistp . (not listp)) + (identity . (not null)) + (not . null))))) + (if equiv `(,head ,(cdr equiv)) pat))) ((memq head '(or and)) `(,head ,@(mapcar #'pcase--macroexpand (cdr pat)))) ((eq head 'app) `(app ,(nth 1 pat) ,(pcase--macroexpand (nth 2 pat)))) (t diff --git a/test/lisp/emacs-lisp/pcase-tests.el b/test/lisp/emacs-lisp/pcase-tests.el index e06c1e621c2..9b8a643c731 100644 --- a/test/lisp/emacs-lisp/pcase-tests.el +++ b/test/lisp/emacs-lisp/pcase-tests.el @@ -192,4 +192,22 @@ (should (pcase--mutually-exclusive-p (nth 1 x) (nth 0 x))) (should-not (pcase--mutually-exclusive-p (nth 1 x) (nth 0 x)))))) +(ert-deftest pcase-pred-equiv () + (cl-flet ((f1 (x) (pcase x ((pred atom) 1) (_ 2)))) + (should (equal (f1 'a) 1)) + (should (equal (f1 nil) 1)) + (should (equal (f1 '(a)) 2))) + (cl-flet ((f2 (x) (pcase x ((pred nlistp) 1) (_ 2)))) + (should (equal (f2 'a) 1)) + (should (equal (f2 nil) 2)) + (should (equal (f2 '(a)) 2))) + (cl-flet ((f3 (x) (pcase x ((pred identity) 1) (_ 2)))) + (should (equal (f3 'a) 1)) + (should (equal (f3 nil) 2)) + (should (equal (f3 '(a)) 1))) + (cl-flet ((f4 (x) (pcase x ((pred not) 1) (_ 2)))) + (should (equal (f4 'a) 2)) + (should (equal (f4 nil) 1)) + (should (equal (f4 '(a)) 2)))) + ;;; pcase-tests.el ends here. From 446177ce252b84a93fb10ef50069d4290de7d4c9 Mon Sep 17 00:00:00 2001 From: Daniel Mendler Date: Sun, 25 Jan 2026 20:04:45 +0100 Subject: [PATCH 02/74] ; Update defcustom type of 'line-spacing' * lisp/cus-start.el: Update type of `line-spacing'. (Bug#76390) --- lisp/cus-start.el | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lisp/cus-start.el b/lisp/cus-start.el index 3720e267f4e..14935632b4d 100644 --- a/lisp/cus-start.el +++ b/lisp/cus-start.el @@ -150,7 +150,10 @@ Leaving \"Default\" unchecked is equivalent with specifying a default of (scroll-down-aggressively windows (choice (const :tag "off" nil) float) "21.1") - (line-spacing display (choice (const :tag "none" nil) number) + (line-spacing display + (choice (const :tag "none" nil) + number + (cons number number)) "22.1") (cursor-in-non-selected-windows cursor ,cursor-type-types nil From 821928808c4558781c58ce3dc668ff4888fb393a Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Mon, 26 Jan 2026 14:29:47 +0200 Subject: [PATCH 03/74] ; Improve last change * lisp/cus-start.el (line-spacing): Add :tag's. (Bug#76390) --- lisp/cus-start.el | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lisp/cus-start.el b/lisp/cus-start.el index 14935632b4d..6761bc8bd3f 100644 --- a/lisp/cus-start.el +++ b/lisp/cus-start.el @@ -151,9 +151,10 @@ Leaving \"Default\" unchecked is equivalent with specifying a default of (choice (const :tag "off" nil) float) "21.1") (line-spacing display - (choice (const :tag "none" nil) - number - (cons number number)) + (choice (const :tag "No spacing" nil) + (number :tag "Spacing below") + (cons :tag "Spacing above and below" + number number)) "22.1") (cursor-in-non-selected-windows cursor ,cursor-type-types nil From d5a0dc0bade09cebbf34c13fe4e4f8dd7e308e2a Mon Sep 17 00:00:00 2001 From: Bastien Guerry Date: Mon, 26 Jan 2026 14:45:48 +0100 Subject: [PATCH 04/74] Update TUTORIAL.translators * etc/tutorials/TUTORIAL.translators (Maintainer): Add myself as TUTORIAL.fr co-maintainer. --- etc/tutorials/TUTORIAL.translators | 1 + 1 file changed, 1 insertion(+) diff --git a/etc/tutorials/TUTORIAL.translators b/etc/tutorials/TUTORIAL.translators index e81e6c665f4..2b1444b13b8 100644 --- a/etc/tutorials/TUTORIAL.translators +++ b/etc/tutorials/TUTORIAL.translators @@ -39,6 +39,7 @@ Maintainer: Mohsen BANAN * TUTORIAL.fr: Author: Éric Jacoboni Maintainer: Éric Jacoboni + Bastien Guerry * TUTORIAL.he Author: Eli Zaretskii From 9983b189356756448882b934a5c3e9c61c5438e6 Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Mon, 26 Jan 2026 16:49:01 +0200 Subject: [PATCH 05/74] Fix point-adjustment when overlays are specific to windows * src/keyboard.c (adjust_point_for_property): Consider only overlays associated with the selected window. (Bug#80255) --- src/keyboard.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/keyboard.c b/src/keyboard.c index 6a4faa7aba7..0bf134961a3 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -1789,7 +1789,8 @@ adjust_point_for_property (ptrdiff_t last_pt, bool modified) TEXT_PROP_MEANS_INVISIBLE (val)) #endif && !NILP (val = get_char_property_and_overlay - (make_fixnum (end), Qinvisible, Qnil, &overlay)) + (make_fixnum (end), Qinvisible, + selected_window, &overlay)) && (inv = TEXT_PROP_MEANS_INVISIBLE (val))) { ellipsis = ellipsis || inv > 1 @@ -1807,7 +1808,8 @@ adjust_point_for_property (ptrdiff_t last_pt, bool modified) TEXT_PROP_MEANS_INVISIBLE (val)) #endif && !NILP (val = get_char_property_and_overlay - (make_fixnum (beg - 1), Qinvisible, Qnil, &overlay)) + (make_fixnum (beg - 1), Qinvisible, + selected_window, &overlay)) && (inv = TEXT_PROP_MEANS_INVISIBLE (val))) { ellipsis = ellipsis || inv > 1 @@ -1874,11 +1876,11 @@ adjust_point_for_property (ptrdiff_t last_pt, bool modified) could lead to an infinite loop. */ ; else if (val = Fget_pos_property (make_fixnum (PT), - Qinvisible, Qnil), + Qinvisible, selected_window), TEXT_PROP_MEANS_INVISIBLE (val) && (val = (Fget_pos_property (make_fixnum (PT == beg ? end : beg), - Qinvisible, Qnil)), + Qinvisible, selected_window)), !TEXT_PROP_MEANS_INVISIBLE (val))) (check_composition = check_display = true, SET_PT (PT == beg ? end : beg)); From 120a451c040011925c3c736058f2ce040e04d5fc Mon Sep 17 00:00:00 2001 From: Stefan Monnier Date: Mon, 26 Jan 2026 10:14:03 -0500 Subject: [PATCH 06/74] (read_char_minibuf_menu_prompt): Fix bug#80146 * src/keyboard.c (read_char_minibuf_menu_prompt): Give priority to a binding in the map over the `menu_prompt_more_char` "binding". (follow_key): Move before new first use. --- src/keyboard.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/keyboard.c b/src/keyboard.c index 0bf134961a3..bc36d899250 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -10118,6 +10118,13 @@ read_char_x_menu_prompt (Lisp_Object map, return Qnil ; } +static Lisp_Object +follow_key (Lisp_Object keymap, Lisp_Object key) +{ + return access_keymap (get_keymap (keymap, 0, 1), + key, 1, 0, 1); +} + static Lisp_Object read_char_minibuf_menu_prompt (int commandflag, Lisp_Object map) @@ -10329,7 +10336,10 @@ read_char_minibuf_menu_prompt (int commandflag, if (!FIXNUMP (obj) || XFIXNUM (obj) == -2 || (! EQ (obj, menu_prompt_more_char) && (!FIXNUMP (menu_prompt_more_char) - || ! BASE_EQ (obj, make_fixnum (Ctl (XFIXNUM (menu_prompt_more_char))))))) + || ! BASE_EQ (obj, make_fixnum (Ctl (XFIXNUM (menu_prompt_more_char)))))) + /* If 'menu_prompt_more_char' collides with a binding in the + map, gives precedence to the map's binding (bug#80146). */ + || !NILP (follow_key (map, obj))) { if (!NILP (KVAR (current_kboard, defining_kbd_macro))) store_kbd_macro_char (obj); @@ -10341,13 +10351,6 @@ read_char_minibuf_menu_prompt (int commandflag, /* Reading key sequences. */ -static Lisp_Object -follow_key (Lisp_Object keymap, Lisp_Object key) -{ - return access_keymap (get_keymap (keymap, 0, 1), - key, 1, 0, 1); -} - static Lisp_Object active_maps (Lisp_Object first_event, Lisp_Object second_event) { From 3dbddb4497c6571687c5c096e8f33daa4de04a79 Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Mon, 26 Jan 2026 18:33:59 +0200 Subject: [PATCH 07/74] Fix image.c compilation when HAVE_GIF is not defined * src/image.c (gif_clear_image): Make it available for other image types. (Bug#80266) --- src/image.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/image.c b/src/image.c index 59be186a839..119287db899 100644 --- a/src/image.c +++ b/src/image.c @@ -9653,6 +9653,8 @@ static const struct image_keyword gif_format[GIF_LAST] = {":background", IMAGE_STRING_OR_NIL_VALUE, 0} }; +#endif + /* Free X resources of GIF image IMG which is used on frame F. Also used by other image types. */ @@ -9663,6 +9665,8 @@ gif_clear_image (struct frame *f, struct image *img) image_clear_image (f, img); } +#if defined (HAVE_GIF) + /* Return true if OBJECT is a valid GIF image specification. */ static bool From b224605d305f71c798877c4228afe18ded7a39ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20T=C3=A1vora?= Date: Mon, 26 Jan 2026 22:56:33 +0000 Subject: [PATCH 08/74] Jsonrpc: avoid redisplay_internal calls from jsonrpc-request The 'jsonrpc-request' function, when called with non-nil CANCEL-ON-INPUT, relies on 'sit-for' to stop immediately when the user inputs something into Emacs. Although this behavior is working well, it has the hitherto undiscovered side effect of invoking 'redisplay_internal', which triggers expensive operations such as fontification. This bug was noticied when using the 'breadcrumb' package in conjunction with Eglot and a narrowed buffer. To provide breadcrumbs for the current context, breadcrumb.el invokes 'imenu--make-index-alist' on a timer. That function temporarily widens the buffer and then eventually calls 'redisplay_internal' (through 'eglot-imenu', 'jsonrpc-request', and 'sit-for'). This has the effect that the temporarily widened buffer is re-rendered and displayed to the user until the LSP server answers the request and 'imenu--make-index-alist' restores the restriction, an effect that lasts between 0.5 and 2 seconds usually and is annoying and confusing. To fix this, using a non-nil NODISP argument in the 'sit-for' is not enough (though it's arguable it should be and maybe that's a separate bug). Binding 'inhibit-redisplay' to 't' around 'sit-for' seems to fix the issue robustly. * lisp/jsonrpc.el (jsonrpc-request): Bind inhibit-redisplay to t and pass NODISP to sit-for. --- lisp/jsonrpc.el | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lisp/jsonrpc.el b/lisp/jsonrpc.el index 74a59a04095..fca00dd2fc7 100644 --- a/lisp/jsonrpc.el +++ b/lisp/jsonrpc.el @@ -488,7 +488,8 @@ to the original request (normal or error) are ignored." ,@(when (plist-member args :timeout) `(:timeout ,timeout))))) (cond (cancel-on-input (unwind-protect - (let ((inhibit-quit t)) (while (sit-for 30))) + (let ((inhibit-quit t) (inhibit-redisplay t)) + (while (sit-for 30 t))) (setq canceled t)) (when (functionp cancel-on-input) (funcall cancel-on-input (car id-and-timer))) From 53305372d04b6e9463e22ea996b0d2e6b156fbb6 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Tue, 27 Jan 2026 11:01:35 +0800 Subject: [PATCH 09/74] ; Avoid warnings when neither GIF nor WebP are supported * src/image.c (gif_clear_image): Render contingent on HAVE_GIF || HAVE_WEBP. --- src/image.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/image.c b/src/image.c index 119287db899..5a4bc3024c3 100644 --- a/src/image.c +++ b/src/image.c @@ -9655,6 +9655,8 @@ static const struct image_keyword gif_format[GIF_LAST] = #endif +#if defined HAVE_GIF || defined HAVE_WEBP + /* Free X resources of GIF image IMG which is used on frame F. Also used by other image types. */ @@ -9665,6 +9667,8 @@ gif_clear_image (struct frame *f, struct image *img) image_clear_image (f, img); } +#endif /* defined HAVE_GIF || defined HAVE_WEBP */ + #if defined (HAVE_GIF) /* Return true if OBJECT is a valid GIF image specification. */ From 99abaa70bf99de7a2f150ac9bb1e62ca6ea4d6f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Marks?= Date: Tue, 27 Jan 2026 10:23:34 +0100 Subject: [PATCH 10/74] Document frame id related commands and tweak a return value (Bug#80192) * doc/lispref/frames.texi (Input Focus): Document the commands 'select-frame-by-id' and 'undelete-frame-by-id'. * lisp/frame.el (select-frame-by-id): Clarify return value and add missing optional argument 'noerror' in sympathy with 'undelete-frame-by-id'. --- doc/lispref/frames.texi | 24 ++++++++++++++++++++++++ lisp/frame.el | 25 +++++++++++++++---------- 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/doc/lispref/frames.texi b/doc/lispref/frames.texi index 5bf0bfc8c10..bdd79528cac 100644 --- a/doc/lispref/frames.texi +++ b/doc/lispref/frames.texi @@ -109,6 +109,7 @@ must be a root frame, which means it cannot be a child frame itself descending from it. @end defun +@cindex frame identifier @defun frame-id &optional frame This function returns the unique identifier of a frame, an integer, assigned to @var{frame}. If @var{frame} is @code{nil} or unspecified, @@ -3187,6 +3188,29 @@ could switch to a different terminal without switching back when you're done. @end deffn +@deffn Command select-frame-by-id id &optional noerror +This function searches open and undeletable frames for a matching frame +identifier @var{id} (@pxref{Frames}). If found, its frame is undeleted, +if necessary, then raised, given focus, and made the selected frame. On +a text terminal, raising a frame causes it to occupy the entire terminal +display. + +This function returns the selected frame or signals an error if @var{id} +is not found, unless @var{noerror} is non-@code{nil}, in which case it +returns @code{nil}. +@end deffn + +@deffn Command undelete-frame-by-id id &optional noerror +This function searches undeletable frames for a matching frame +identifier @var{id} (@pxref{Frames}). If found, its frame is undeleted, +raised, given focus, and made the selected frame. On a text terminal, +raising a frame causes it to occupy the entire terminal display. + +This function returns the undeleted frame or signals an error if +@var{id} is not found, unless @var{noerror} is non-@code{nil}, in which +case it returns @code{nil}. +@end deffn + @cindex text-terminal focus notification Emacs cooperates with the window system by arranging to select frames as the server and window manager request. When a window system diff --git a/lisp/frame.el b/lisp/frame.el index 1e2ae5ae73c..54502837bf6 100644 --- a/lisp/frame.el +++ b/lisp/frame.el @@ -1440,13 +1440,14 @@ This is useful when you have a frame ID and a potentially dead frame reference that may have been resurrected. Also see `frame-live-p'." (frame-live-p (frame-by-id id))) -(defun select-frame-by-id (id) +(defun select-frame-by-id (id &optional noerror) "Select the frame whose identifier is ID and raise it. If the frame is undeletable, undelete it. Frames on the current terminal are checked first. Raise the frame and give it input focus. On a text terminal, the frame will occupy the entire terminal screen after the next redisplay. -If there is no frame with that ID, signal an error." +Return the selected frame or signal an error if no frame matching ID +was found. If NOERROR is non-nil, return nil instead." (interactive (let* ((frame-ids-alist (frame--make-frame-ids-alist)) (default (car (car frame-ids-alist))) @@ -1455,15 +1456,19 @@ If there is no frame with that ID, signal an error." frame-ids-alist nil t))) (list (string-to-number (if (zerop (length input)) default input))))) + ;; `undelete-frame-by-id' returns the undeleted frame, or nil. (unless (undelete-frame-by-id id 'noerror) - (select-frame-set-input-focus - ;; Prefer frames on the current display. - (or (cdr (assq id (frame--make-frame-ids-alist))) - (catch 'done - (dolist (frame (frame-list)) - (when (eq (frame-id frame) id) - (throw 'done frame)))) - (error "There is no frame with identifier `%S'" id))))) + ;; Prefer frames on the current display. + (if-let* ((found (or (cdr (assq id (frame--make-frame-ids-alist))) + (catch 'done + (dolist (frame (frame-list)) + (when (eq (frame-id frame) id) + (throw 'done frame))))))) + (progn + (select-frame-set-input-focus found) + found) + (unless noerror + (error "There is no frame with identifier `%S'" id))))) ;;;; Background mode. From 19cd6972faab7f63388359a87b11d00b9e718855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Engdeg=C3=A5rd?= Date: Tue, 27 Jan 2026 15:05:49 +0100 Subject: [PATCH 11/74] ; * lisp/files.el (file-name-version-regexp): typo in doc string --- lisp/files.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp/files.el b/lisp/files.el index ec5896e8731..d4b3dd490c5 100644 --- a/lisp/files.el +++ b/lisp/files.el @@ -5478,7 +5478,7 @@ BACKUPNAME is the backup file name, which is the old file renamed." (defvar file-name-version-regexp "\\(?:~\\|\\.~[-[:alnum:]:#@^._]+\\(?:~[[:digit:]]+\\)?~\\)" - ;; The last ~[[:digit]]+ matches relative versions in git, + ;; The last ~[[:digit:]]+ matches relative versions in git, ;; e.g. `foo.js.~HEAD~1~'. "Regular expression matching the backup/version part of a file name. Used by `file-name-sans-versions'.") From 4fae092e2d8b20471ee1b30bf7d30d26feef0bd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Kryger?= Date: Fri, 23 Jan 2026 16:36:37 +0000 Subject: [PATCH 12/74] Ensure skipped package-vc-tests are not installed (bug#80235) * test/lisp/emacs-lisp/package-vc-tests.el (package-vc-tests-packages): Add argument `full'. When `full' is non-nil, then return full entries. (package-vc-test-deftest): Use `pkg-arg' for the name of argument `in-body'. Call `skip-when' and `skip-unless' before `packgage-vc-tests-with-installed'. --- test/lisp/emacs-lisp/package-vc-tests.el | 148 ++++++++++++----------- 1 file changed, 79 insertions(+), 69 deletions(-) diff --git a/test/lisp/emacs-lisp/package-vc-tests.el b/test/lisp/emacs-lisp/package-vc-tests.el index 150d5c4a6e0..01c08ca7d3f 100644 --- a/test/lisp/emacs-lisp/package-vc-tests.el +++ b/test/lisp/emacs-lisp/package-vc-tests.el @@ -70,20 +70,21 @@ preserve all temporary directories.") (defvar package-vc-tests-repository) (eval-and-compile - (defun package-vc-tests-packages () + (defun package-vc-tests-packages (&optional full) "Return a list of package definitions to test. When variable `package-vc-tests-packages' is bound then return its -value. If `package-vc-tests-dir' is bound then each entry is in a form -of (PKG CHECKOUT-DIR LISP-DIR INSTALL-FUN), where PKG is a package -name (a symbol), CHECKOUT-DIR is an expected checkout directory, -LISP-DIR is a directory with package's sources (relative to +value. If `package-vc-tests-dir' is bound or FULL is non nil then each +entry is in a form of (PKG CHECKOUT-DIR LISP-DIR INSTALL-FUN), where PKG +is a package name (a symbol), CHECKOUT-DIR either is nil when +`package-vc-tests-dir' is not bound or is an expected checkout +directory, LISP-DIR is a directory with package's sources (relative to CHECKOUT-DIR), and INSTALL-FUN is a function that checkouts and install the package. Otherwise each entry is in a form of PKG." (if (boundp 'package-vc-tests-packages) package-vc-tests-packages (cl-macrolet ((test-package-def (pkg checkout-dir-exp lisp-dir install-fun) - `(if (boundp 'package-vc-tests-dir) + `(if (or (boundp 'package-vc-tests-dir) full) (list ',pkg (expand-file-name (symbol-name ',pkg) @@ -91,51 +92,54 @@ the package. Otherwise each entry is in a form of PKG." ,lisp-dir #',install-fun) ',pkg))) - (list - ;; checkout and install with `package-vc-install' (on ELPA) - (test-package-def - test-package-one package-user-dir nil - package-vc-tests-install-from-elpa) - ;; checkout and install with `package-vc-install' (not on ELPA) - (test-package-def - test-package-two package-user-dir nil - package-vc-tests-install-from-spec) - ;; checkout with `package-vc-checktout' and install with - ;; `package-vc-install-from-checkout' (on ELPA) - (test-package-def - test-package-three package-vc-tests-dir nil - package-vc-tests-checkout-from-elpa-install-from-checkout) - ;; checkout with git and install with - ;; `package-vc-install-from-checkout' - (test-package-def - test-package-four package-vc-tests-dir nil - package-vc-tests-checkout-with-git-install-from-checkout) - ;; sources in "lisp" sub directory, checkout and install with - ;; `package-vc-install' (not on ELPA) - (test-package-def - test-package-five package-user-dir "lisp" - package-vc-tests-install-from-spec) - ;; sources in "lisp" sub directory, checkout with git and - ;; install with `package-vc-install-from-checkout' - (test-package-def - test-package-six package-vc-tests-dir "lisp" - package-vc-tests-checkout-with-git-install-from-checkout) - ;; sources in "src" sub directory, checkout and install with - ;; `package-vc-install' (on ELPA) - (test-package-def - test-package-seven package-user-dir "src" - package-vc-tests-install-from-elpa) - ;; sources in "src" sub directory, checkout with - ;; `package-vc-checktout' and install with - ;; `package-vc-install-from-checkout' (on ELPA) - (test-package-def - test-package-eight package-vc-tests-dir nil - package-vc-tests-checkout-from-elpa-install-from-checkout) - ;; sources in "custom-dir" sub directory, checkout and install - ;; with `package-vc-install' (on ELPA) - (test-package-def - test-package-nine package-user-dir "custom-dir" - package-vc-tests-install-from-elpa)))))) + (let* ((tests-dir (bound-and-true-p package-vc-tests-dir)) + (user-dir (and tests-dir package-user-dir))) + (list + ;; checkout and install with `package-vc-install' (on ELPA) + (test-package-def + test-package-one user-dir nil + package-vc-tests-install-from-elpa) + ;; checkout and install with `package-vc-install' (not on + ;; ELPA) + (test-package-def + test-package-two user-dir nil + package-vc-tests-install-from-spec) + ;; checkout with `package-vc-checktout' and install with + ;; `package-vc-install-from-checkout' (on ELPA) + (test-package-def + test-package-three tests-dir nil + package-vc-tests-checkout-from-elpa-install-from-checkout) + ;; checkout with git and install with + ;; `package-vc-install-from-checkout' + (test-package-def + test-package-four tests-dir nil + package-vc-tests-checkout-with-git-install-from-checkout) + ;; sources in "lisp" sub directory, checkout and install with + ;; `package-vc-install' (not on ELPA) + (test-package-def + test-package-five user-dir "lisp" + package-vc-tests-install-from-spec) + ;; sources in "lisp" sub directory, checkout with git and + ;; install with `package-vc-install-from-checkout' + (test-package-def + test-package-six tests-dir "lisp" + package-vc-tests-checkout-with-git-install-from-checkout) + ;; sources in "src" sub directory, checkout and install with + ;; `package-vc-install' (on ELPA) + (test-package-def + test-package-seven user-dir "src" + package-vc-tests-install-from-elpa) + ;; sources in "src" sub directory, checkout with + ;; `package-vc-checktout' and install with + ;; `package-vc-install-from-checkout' (on ELPA) + (test-package-def + test-package-eight tests-dir nil + package-vc-tests-checkout-from-elpa-install-from-checkout) + ;; sources in "custom-dir" sub directory, checkout and + ;; install with `package-vc-install' (on ELPA) + (test-package-def + test-package-nine user-dir "custom-dir" + package-vc-tests-install-from-elpa))))))) ;; TODO: add test for deleting packages, with asserting ;; `package-vc-selected-packages' @@ -678,27 +682,33 @@ contains key `:tags' use its value as tests tags." (error "`package-vc' tests first argument has to be a symbol")) (let ((file (or (macroexp-file-name) buffer-file-name)) (tests '()) (fn (gensym)) + (pkg-arg (car args)) + (skip-forms (take-while (lambda (form) + (memq (car-safe form) '(skip-when + skip-unless))) + body)) (tags (plist-get (cdr-safe args) :tags))) + (setq body (nthcdr (length skip-forms) body)) (dolist (pkg (package-vc-tests-packages)) (let ((name (intern (format "package-vc-tests-%s/%s" name pkg)))) (push - `(ert-set-test - ',name - (make-ert-test - :name ',name - :tags (cons 'package-vc ',tags) - :file-name ,file - :body - (lambda () - (package-vc-tests-with-installed - ',pkg (funcall ,fn ',pkg)) - nil))) + `(ert-set-test ',name + (make-ert-test + :name ',name + :tags (cons 'package-vc ',tags) + :file-name ,file + :body + (lambda () + (funcall ,fn ',pkg) + nil))) tests))) - `(let ((,fn (lambda (,(car args)) - (cl-macrolet ((skip-when (form) `(ert--skip-when ,form)) - (skip-unless (form) `(ert--skip-unless ,form))) - (lambda () ,@body))))) - ,@tests))) + `(cl-macrolet ((skip-when (form) `(ert--skip-when ,form)) + (skip-unless (form) `(ert--skip-unless ,form))) + (let ((,fn (lambda (,pkg-arg) + ,@skip-forms + (package-vc-tests-with-installed ,pkg-arg + (lambda () ,@body))))) + ,@tests)))) (package-vc-test-deftest install-post-conditions (pkg) (let ((install-begin @@ -1006,7 +1016,7 @@ contains key `:tags' use its value as tests tags." (package-vc-test-deftest pkg-spec-make-shell-command (pkg) ;; Only `package-vc-install' runs make and shell command - (skip-unless (memq (caddr (alist-get pkg package-vc-tests-packages)) + (skip-unless (memq (caddr (alist-get pkg (package-vc-tests-packages t))) '(package-vc-tests-install-from-elpa package-vc-tests-install-from-spec))) (let* ((desc (package-vc-tests-package-desc pkg t)) @@ -1024,7 +1034,7 @@ contains key `:tags' use its value as tests tags." ;; Only `package-vc-install' builds info manuals, but only when ;; executable install-info is available. (skip-unless (and (executable-find "install-info") - (memq (caddr (alist-get pkg package-vc-tests-packages)) + (memq (caddr (alist-get pkg (package-vc-tests-packages t))) '(package-vc-tests-install-from-elpa package-vc-tests-install-from-spec)))) (should-not (package-vc-tests-log-buffer-exists 'doc pkg)) From 6e2a4b8111cfb5ee66bfe24bb8411aaac8cf0bf8 Mon Sep 17 00:00:00 2001 From: Stefan Monnier Date: Tue, 27 Jan 2026 11:17:37 -0500 Subject: [PATCH 13/74] (pcase--subtype-bitsets): Make it a bit more precise `null`, `booleanp`, and `symbolp` were treated as equivalent in `pcase--subtype-bitsets`, which was not incorrect to the extent that we currently use this table only to detect mutual-exclusion, but made it incorrect to use that same table to test things like inclusion. * lisp/emacs-lisp/cl-preloaded.el (built-in-class): New slot `non-abstract-supertype`. (cl--define-built-in-type): Add corresponding keyword argument. (symbol, boolean): Use it. * lisp/emacs-lisp/pcase.el (pcase--subtype-bitsets): Use it. * lisp/emacs-lisp/cl-macs.el (cl--transform-lambda): Require `help` before calling `help--docstring-quote`. Fixes a corner case bootstrap problem found along the way. --- lisp/emacs-lisp/cl-macs.el | 19 +++++------ lisp/emacs-lisp/cl-preloaded.el | 24 +++++++++----- lisp/emacs-lisp/pcase.el | 56 ++++++++++++++++++++++----------- 3 files changed, 64 insertions(+), 35 deletions(-) diff --git a/lisp/emacs-lisp/cl-macs.el b/lisp/emacs-lisp/cl-macs.el index 989f8f5ce20..caa02fb24b2 100644 --- a/lisp/emacs-lisp/cl-macs.el +++ b/lisp/emacs-lisp/cl-macs.el @@ -327,15 +327,16 @@ FORM is of the form (ARGS . BODY)." ;; "manual" parsing. (let ((slen (length simple-args)) (usage-str - ;; Macro expansion can take place in the middle of - ;; apparently harmless computation, so it should not - ;; touch the match-data. - (save-match-data - (help--docstring-quote - (let ((print-gensym nil) (print-quoted t) - (print-escape-newlines t)) - (format "%S" (cons 'fn (cl--make-usage-args - orig-args)))))))) + ;; Macro expansion can take place in the middle of + ;; apparently harmless computation, so it should not + ;; touch the match-data. + (save-match-data + (require 'help) + (help--docstring-quote + (let ((print-gensym nil) (print-quoted t) + (print-escape-newlines t)) + (format "%S" (cons 'fn (cl--make-usage-args + orig-args)))))))) (when (memq '&optional simple-args) (decf slen)) (setq header diff --git a/lisp/emacs-lisp/cl-preloaded.el b/lisp/emacs-lisp/cl-preloaded.el index d75a32a8d4e..f6376fbd192 100644 --- a/lisp/emacs-lisp/cl-preloaded.el +++ b/lisp/emacs-lisp/cl-preloaded.el @@ -296,10 +296,11 @@ (cl-defstruct (built-in-class (:include cl--class) + (:conc-name built-in-class--) (:noinline t) (:constructor nil) (:constructor built-in-class--make - (name docstring parent-types + (name docstring parent-types &optional non-abstract-supertype &aux (parents (mapcar (lambda (type) (or (get type 'cl--class) @@ -308,7 +309,9 @@ (:copier nil)) "Type descriptors for built-in types. The `slots' (and hence `index-table') are currently unused." - ) + ;; As a general rule, built-in types are abstract if-and-only-if they have + ;; other built-in types as subtypes. But there are a few exceptions. + (non-abstract-supertype nil :read-only t)) (defmacro cl--define-built-in-type (name parents &optional docstring &rest slots) ;; `slots' is currently unused, but we could make it take @@ -322,19 +325,22 @@ The `slots' (and hence `index-table') are currently unused." (let ((predicate (intern-soft (format (if (string-match "-" (symbol-name name)) "%s-p" "%sp") - name)))) + name))) + (nas nil)) (unless (fboundp predicate) (setq predicate nil)) (while (keywordp (car slots)) (let ((kw (pop slots)) (val (pop slots))) (pcase kw (:predicate (setq predicate val)) + (:non-abstract-supertype (setq nas val)) (_ (error "Unknown keyword arg: %S" kw))))) `(progn ,(if predicate `(put ',name 'cl-deftype-satisfies #',predicate) ;; (message "Missing predicate for: %S" name) nil) (put ',name 'cl--class - (built-in-class--make ',name ,docstring ',parents))))) + (built-in-class--make ',name ,docstring ',parents + ,@(if nas '(t))))))) ;; FIXME: Our type DAG has various quirks: ;; - Some `keyword's are also `symbol-with-pos' but that's not reflected @@ -381,6 +387,7 @@ regardless if `funcall' would accept to call them." "Abstract supertype of both `number's and `marker's.") (cl--define-built-in-type symbol atom "Type of symbols." + :non-abstract-supertype t ;; Example of slots we could document. It would be desirable to ;; have some way to extract this from the C code, or somehow keep it ;; in sync (probably not for `cons' and `symbol' but for things like @@ -411,7 +418,8 @@ The size depends on the Emacs version and compilation options. For this build of Emacs it's %dbit." (1+ (logb (1+ most-positive-fixnum))))) (cl--define-built-in-type boolean (symbol) - "Type of the canonical boolean values, i.e. either nil or t.") + "Type of the canonical boolean values, i.e. either nil or t." + :non-abstract-supertype t) (cl--define-built-in-type symbol-with-pos (symbol) "Type of symbols augmented with source-position information.") (cl--define-built-in-type vector (array)) @@ -450,9 +458,9 @@ The fields are used as follows: 5 [iform] The interactive form (if present)") (cl--define-built-in-type byte-code-function (compiled-function closure) "Type of functions that have been byte-compiled.") -(cl--define-built-in-type subr (atom) - "Abstract type of functions compiled to machine code.") -(cl--define-built-in-type module-function (function) +(cl--define-built-in-type subr (atom) ;Beware: not always a function. + "Abstract type of functions and special forms compiled to machine code.") +(cl--define-built-in-type module-function (compiled-function) "Type of functions provided via the module API.") (cl--define-built-in-type interpreted-function (closure) "Type of functions that have not been compiled.") diff --git a/lisp/emacs-lisp/pcase.el b/lisp/emacs-lisp/pcase.el index 7bb7d4a6b27..6126679e870 100644 --- a/lisp/emacs-lisp/pcase.el +++ b/lisp/emacs-lisp/pcase.el @@ -662,13 +662,22 @@ recording whether the var has been referenced by earlier parts of the match." (lambda (x y) (> (length (nth 2 x)) (length (nth 2 y)))))) + ;; We presume that the "fundamental types" (i.e. the built-in types + ;; that have no subtypes) are all mutually exclusive and give them + ;; one bit each in bitsets. + ;; The "non-abstract-supertypes" also get their own bit. + ;; All other built-in types are abstract, so they don't need their + ;; own bits (they are faithfully modeled by the set of bits + ;; corresponding to their subtypes). (let ((bitsets (make-hash-table)) (i 1)) (dolist (x built-in-types) ;; Don't dedicate any bit to those predicates which already ;; have a bitset, since it means they're already represented ;; by their subtypes. - (unless (and (nth 1 x) (gethash (nth 1 x) bitsets)) + (unless (and (nth 1 x) (gethash (nth 1 x) bitsets) + (not (built-in-class--non-abstract-supertype + (get (nth 0 x) 'cl--class)))) (dolist (parent (nth 2 x)) (let ((pred (nth 1 (assq parent built-in-types)))) (unless (or (eq parent t) (null pred)) @@ -676,24 +685,35 @@ recording whether the var has been referenced by earlier parts of the match." bitsets)))) (setq i (+ i i)))) + ;; (cl-assert (= (1- i) (apply #'logior (map-values bitsets)))) + ;; Extra predicates that don't have matching types. - (dolist (pred-types '((functionp cl-functionp consp symbolp) - (keywordp symbolp) - (characterp fixnump) - (natnump integerp) - (facep symbolp stringp) - (plistp listp) - (cl-struct-p recordp) - ;; ;; FIXME: These aren't quite in the same - ;; ;; category since they'll signal errors. - (fboundp symbolp) - )) - (puthash (car pred-types) - (apply #'logior - (mapcar (lambda (pred) - (gethash pred bitsets)) - (cdr pred-types))) - bitsets)) + ;; Beware: For these predicates, the bitsets are conservative + ;; approximations (so, e.g., it wouldn't be correct to use one of + ;; them after a `!' since the negation would be an unsound + ;; under-approximation). + (let ((all (1- i))) + (dolist (pred-types '((functionp cl-functionp consp symbolp) + (keywordp symbolp) + (nlistp ! listp) + (characterp fixnump) + (natnump integerp) + (facep symbolp stringp) + (plistp listp) + (cl-struct-p recordp) + ;; ;; FIXME: These aren't quite in the same + ;; ;; category since they'll signal errors. + (fboundp symbolp) + )) + (let* ((types (cdr pred-types)) + (neg (when (eq '! (car types)) (setq types (cdr types)))) + (bitset (apply #'logior + (mapcar (lambda (pred) + (gethash pred bitsets)) + types)))) + (puthash (car pred-types) + (if neg (- all bitset) bitset) + bitsets)))) bitsets))) (defconst pcase--subtype-bitsets From 7fa90d50c6570e396f69c6b4ce0df68d6f79122d Mon Sep 17 00:00:00 2001 From: Michael Albinus Date: Tue, 27 Jan 2026 18:25:05 +0100 Subject: [PATCH 14/74] Organize tramp-adb-handle-make-process a little bit better * lisp/net/tramp-adb.el (tramp-adb-handle-make-process): Call `tramp-taint-remote-process-buffer' where it belongs to. --- lisp/net/tramp-adb.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp/net/tramp-adb.el b/lisp/net/tramp-adb.el index 1def3aa3791..5bcb92536fd 100644 --- a/lisp/net/tramp-adb.el +++ b/lisp/net/tramp-adb.el @@ -882,8 +882,8 @@ will be used." ;; is deleted. The temporary file will exist ;; until the process is deleted. (when (bufferp stderr) + (tramp-taint-remote-process-buffer stderr) (ignore-errors - (tramp-taint-remote-process-buffer stderr) (with-current-buffer stderr (insert-file-contents-literally remote-tmpstderr 'visit))) From 50bb4ae1eb8a4a2395e4bfd02130021e90d842ab Mon Sep 17 00:00:00 2001 From: Michael Albinus Date: Tue, 27 Jan 2026 18:39:47 +0100 Subject: [PATCH 15/74] ; * test/src/process-tests.el: Instrument for bug#80166. --- test/src/process-tests.el | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/src/process-tests.el b/test/src/process-tests.el index 2f3dd4b8043..29e9d3323ce 100644 --- a/test/src/process-tests.el +++ b/test/src/process-tests.el @@ -106,6 +106,9 @@ process to complete." (looking-at "hello stdout!"))) (should (with-current-buffer stderr-buffer (goto-char (point-min)) + ;; Instrument for bug#80166. + (when (getenv "EMACS_EMBA_CI") + (message "stderr\n%s" (buffer-string)) (looking-at "hello stderr!")))))) (ert-deftest process-test-stderr-filter () From d09cedc9bf2addd4ca0be7d63af8434067ab71bf Mon Sep 17 00:00:00 2001 From: Michael Albinus Date: Tue, 27 Jan 2026 18:49:37 +0100 Subject: [PATCH 16/74] ; Instrument filenotify test * test/lisp/filenotify-tests.el (file-notify-test08-backup): Instrument test. --- test/lisp/filenotify-tests.el | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/lisp/filenotify-tests.el b/test/lisp/filenotify-tests.el index 7a68e637653..fc826aba8d4 100644 --- a/test/lisp/filenotify-tests.el +++ b/test/lisp/filenotify-tests.el @@ -1256,6 +1256,10 @@ delivered." :tags '(:expensive-test) (skip-unless (file-notify--test-local-enabled)) + (let ((file-notify-debug ;; Temporarily. + (or file-notify-debug + (getenv "EMACS_EMBA_CI")))) + (with-file-notify-test (write-region "any text" nil file-notify--test-tmpfile nil 'no-message) (should @@ -1334,7 +1338,7 @@ delivered." (file-notify--rm-descriptor file-notify--test-desc) ;; The environment shall be cleaned up. - (file-notify--test-cleanup-p)))) + (file-notify--test-cleanup-p))))) (file-notify--deftest-remote file-notify-test08-backup "Check that backup keeps file notification for remote files.") From 32cffe17077b1bce3131e7b4606b8930c0e35bb7 Mon Sep 17 00:00:00 2001 From: Daniel Mendler Date: Wed, 28 Jan 2026 04:32:46 +0200 Subject: [PATCH 17/74] Customizable xref-references-in-directory backend Optionally use find and grep directly instead of going through the Semantic framework (bug#80246). * lisp/progmodes/project.el (project--vc-ignores): Require 'vc' to ensure that vc-default-ignore-completion-table is available. * lisp/progmodes/xref.el (xref-references-in-directory-function): New user option. (xref-references-in-directory): Call it. (xref-references-in-directory-grep): Implementation based on find/grep. (xref-references-in-directory-semantic): Implementation using Semantic. (xref-matches-in-directory): Add new argument DELIMITED. Co-authored-by: Dmitry Gutov --- lisp/progmodes/project.el | 1 + lisp/progmodes/xref.el | 44 ++++++++++++++++++++++++++++++++------- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/lisp/progmodes/project.el b/lisp/progmodes/project.el index 35840024326..997c876b1fa 100644 --- a/lisp/progmodes/project.el +++ b/lisp/progmodes/project.el @@ -841,6 +841,7 @@ See `project-vc-extra-root-markers' for the marker value format.") (project--value-in-dir 'project-vc-ignores dir))) (defun project--vc-ignores (dir backend extra-ignores) + (require 'vc) (append (when backend (delq diff --git a/lisp/progmodes/xref.el b/lisp/progmodes/xref.el index 22797335b10..df9e00a0d36 100644 --- a/lisp/progmodes/xref.el +++ b/lisp/progmodes/xref.el @@ -269,9 +269,7 @@ To create an xref object, call `xref-make'.") The result must be a list of xref objects. If no references can be found, return nil. -The default implementation uses `semantic-symref-tool-alist' to -find a search tool; by default, this uses \"find | grep\" in the -current project's main and external roots." +The default implementation uses `xref-references-in-directory'." (mapcan (lambda (dir) (message "Searching %s..." dir) @@ -1793,15 +1791,43 @@ and just use etags." (declare-function grep-expand-template "grep") (defvar ede-minor-mode) ;; ede.el +(defcustom xref-references-in-directory-function + #'xref-references-in-directory-semantic + "Function to find all references to a symbol in a directory. +It should take two string arguments: SYMBOL and DIR. +And return a list of xref values representing all code references to +SYMBOL in files under DIR." + :type '(choice + (const :tag "Using Grep via Find" xref-references-in-directory-grep) + (const :tag "Using Semantic Symbol Reference API" + xref-references-in-directory-semantic) + function) + :version "31.1") + ;;;###autoload (defun xref-references-in-directory (symbol dir) "Find all references to SYMBOL in directory DIR. +See `xref-references-in-directory-function' for the implementation. +Return a list of xref values." + (cl-assert (directory-name-p dir)) + (funcall xref-references-in-directory-function symbol dir)) + +(defun xref-references-in-directory-grep (symbol dir) + "Find all references to SYMBOL in directory DIR using find and grep. +Return a list of xref values. The files in DIR are filtered according +to its project's list of ignore patterns (as returned by +`project-ignores'), or the default ignores if there is no project." + (let ((ignores (project-ignores (project-current nil dir) dir))) + (xref-matches-in-directory (regexp-quote symbol) "*" dir ignores + 'symbol))) + +(defun xref-references-in-directory-semantic (symbol dir) + "Find all references to SYMBOL in directory DIR. Return a list of xref values. This function uses the Semantic Symbol Reference API, see `semantic-symref-tool-alist' for details on which tools are used, and when." - (cl-assert (directory-name-p dir)) (require 'semantic/symref) (defvar semantic-symref-tool) @@ -1831,12 +1857,13 @@ and when." "27.1") ;;;###autoload -(defun xref-matches-in-directory (regexp files dir ignores) +(defun xref-matches-in-directory (regexp files dir ignores &optional delimited) "Find all matches for REGEXP in directory DIR. Return a list of xref values. Only files matching some of FILES and none of IGNORES are searched. FILES is a string with glob patterns separated by spaces. -IGNORES is a list of glob patterns for files to ignore." +IGNORES is a list of glob patterns for files to ignore. +If DELIMITED is `symbol', only select matches that span full symbols." ;; DIR can also be a regular file for now; let's not advertise that. (grep-compute-defaults) (defvar grep-find-template) @@ -1855,6 +1882,9 @@ IGNORES is a list of glob patterns for files to ignore." (local-dir (directory-file-name (file-name-unquote (file-local-name (expand-file-name dir))))) + (hits-regexp (if (eq delimited 'symbol) + (format "\\_<%s\\_>" regexp) + regexp)) (buf (get-buffer-create " *xref-grep*")) (`(,grep-re ,file-group ,line-group . ,_) (car grep-regexp-alist)) (status nil) @@ -1877,7 +1907,7 @@ IGNORES is a list of glob patterns for files to ignore." (concat local-dir (substring (match-string file-group) 1)) (buffer-substring-no-properties (point) (line-end-position))) hits))) - (xref--convert-hits (nreverse hits) regexp))) + (xref--convert-hits (nreverse hits) hits-regexp))) (define-obsolete-function-alias 'xref-collect-matches From 69dc5d3f0ed04d3c0b08b8e09efe62a279fa8962 Mon Sep 17 00:00:00 2001 From: Yuan Fu Date: Wed, 28 Jan 2026 01:06:57 -0800 Subject: [PATCH 18/74] Fix tree-sitter traversal slowness (bug#80108) * configure.ac (LIBSYSTEMD_CFLAGS): Increase minimal required tree-sitter version to 0.20.10. * src/treesit.c (treesit_traverse_sibling_helper): When traversing forward, use the new function ts_tree_cursor_goto_previous_sibling. --- configure.ac | 32 ++++---------------------------- src/treesit.c | 48 ++++++------------------------------------------ 2 files changed, 10 insertions(+), 70 deletions(-) diff --git a/configure.ac b/configure.ac index 0d7c58d8020..420ab6dabe6 100644 --- a/configure.ac +++ b/configure.ac @@ -4069,39 +4069,15 @@ TREE_SITTER_OBJ= NEED_DYNLIB=no if test "${with_tree_sitter}" != "no"; then - dnl Tree-sitter 0.20.2 added support to change the malloc it uses - dnl at runtime, we need that feature. However, tree-sitter's - dnl Makefile has problems, until that's fixed, all tree-sitter - dnl libraries distributed are versioned 0.6.3. We try to - dnl accept a tree-sitter library that has incorrect version as long - dnl as it supports changing malloc. - EMACS_CHECK_MODULES([TREE_SITTER], [tree-sitter >= 0.20.2], + dnl Tree-sitter 0.20.10 added ts_tree_cursor_goto_previous_sibling, we + dnl need it for a more efficient implementation for traversing the + dnl parse tree backwards (bug#80108). + EMACS_CHECK_MODULES([TREE_SITTER], [tree-sitter >= 0.20.10], [HAVE_TREE_SITTER=yes], [HAVE_TREE_SITTER=no]) if test "${HAVE_TREE_SITTER}" = yes; then AC_DEFINE(HAVE_TREE_SITTER, 1, [Define if using tree-sitter.]) NEED_DYNLIB=yes - else - EMACS_CHECK_MODULES([TREE_SITTER], [tree-sitter >= 0.6.3], - [HAVE_TREE_SITTER=yes], [HAVE_TREE_SITTER=no]) - if test "${HAVE_TREE_SITTER}" = yes; then - OLD_CFLAGS=$CFLAGS - OLD_LIBS=$LIBS - CFLAGS="$CFLAGS $TREE_SITTER_CFLAGS" - LIBS="$TREE_SITTER_LIBS $LIBS" - AC_CHECK_FUNCS([ts_set_allocator]) - CFLAGS=$OLD_CFLAGS - LIBS=$OLD_LIBS - if test "$ac_cv_func_ts_set_allocator" = yes; then - AC_DEFINE(HAVE_TREE_SITTER, 1, [Define if using tree-sitter.]) - NEED_DYNLIB=yes - else - AC_MSG_ERROR([Tree-sitter library exists but its version is too old]); - TREE_SITTER_CFLAGS= - TREE_SITTER_LIBS= - fi - fi fi - # Windows loads tree-sitter dynamically if test "${opsys}" = "mingw32"; then TREE_SITTER_LIBS= diff --git a/src/treesit.c b/src/treesit.c index ae73885e71d..e9ae1ad3605 100644 --- a/src/treesit.c +++ b/src/treesit.c @@ -4278,50 +4278,14 @@ treesit_traverse_sibling_helper (TSTreeCursor *cursor, } else /* Backward. */ { - /* Go to first child and go through each sibling, until we find - the one just before the starting node. */ - TSNode start = ts_tree_cursor_current_node (cursor); - if (!ts_tree_cursor_goto_parent (cursor)) - return false; - treesit_assume_true (ts_tree_cursor_goto_first_child (cursor)); - - /* Now CURSOR is at the first child. If we started at the first - child, then there is no further siblings. */ - TSNode first_child = ts_tree_cursor_current_node (cursor); - if (ts_node_eq (first_child, start)) - return false; - - /* PROBE is always DELTA siblings ahead of CURSOR. */ - TSTreeCursor probe = ts_tree_cursor_copy (cursor); - /* This is position of PROBE minus position of CURSOR. */ - ptrdiff_t delta = 0; - TSNode probe_node; - TSNode cursor_node; - while (ts_tree_cursor_goto_next_sibling (&probe)) + if (!named) + return ts_tree_cursor_goto_previous_sibling (cursor); + /* Else named... */ + while (ts_tree_cursor_goto_previous_sibling (cursor)) { - /* Move PROBE forward, if it equals to the starting node, - CURSOR points to the node we want (prev valid sibling of - the starting node). */ - delta++; - probe_node = ts_tree_cursor_current_node (&probe); - - /* PROBE matched, depending on NAMED, return true/false. */ - if (ts_node_eq (probe_node, start)) - { - ts_tree_cursor_delete (&probe); - cursor_node = ts_tree_cursor_current_node (cursor); - ts_tree_cursor_delete (&probe); - return (!named || (named && ts_node_is_named (cursor_node))); - } - - /* PROBE didn't match, move CURSOR forward to PROBE's - position, but if we are looking for named nodes, only - move CURSOR to PROBE if PROBE is at a named node. */ - if (!named || (named && ts_node_is_named (probe_node))) - for (; delta > 0; delta--) - treesit_assume_true (ts_tree_cursor_goto_next_sibling (cursor)); + if (ts_node_is_named (ts_tree_cursor_current_node (cursor))) + return true; } - ts_tree_cursor_delete (&probe); return false; } } From 89dad017639265c313fd0e90f02e00c2a1cfea84 Mon Sep 17 00:00:00 2001 From: "Basil L. Contovounesios" Date: Wed, 28 Jan 2026 09:01:42 +0100 Subject: [PATCH 19/74] ; Fix last change to test/src/process-tests.el. --- test/src/process-tests.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/src/process-tests.el b/test/src/process-tests.el index 29e9d3323ce..2cc5b37b187 100644 --- a/test/src/process-tests.el +++ b/test/src/process-tests.el @@ -108,7 +108,7 @@ process to complete." (goto-char (point-min)) ;; Instrument for bug#80166. (when (getenv "EMACS_EMBA_CI") - (message "stderr\n%s" (buffer-string)) + (message "stderr\n%s" (buffer-string))) (looking-at "hello stderr!")))))) (ert-deftest process-test-stderr-filter () From f9080e9bc08367b2bdc8779975dd7d7945f36859 Mon Sep 17 00:00:00 2001 From: "Basil L. Contovounesios" Date: Tue, 27 Jan 2026 15:13:15 +0100 Subject: [PATCH 20/74] Always unset lisp_data when freeing images Historically only the GIF code did this (since it stores animation metadata in lisp_data), and recently the WebP code followed suit. The benefit of clearing lisp_data is not 100% clear (to me: bug#66221#41), but it probably can't hurt, so do it unconditionally for all image types to simplify conditional compilation and avoid warnings (bug#80266). * src/image.c (image_clear_image): Set lisp_data to nil. [HAVE_GIF || HAVE_WEBP] (gif_clear_image): [HAVE_IMAGEMAGICK] (imagemagick_clear_image): Remove, replacing all uses with image_clear_image. --- src/image.c | 34 ++++------------------------------ 1 file changed, 4 insertions(+), 30 deletions(-) diff --git a/src/image.c b/src/image.c index 5a4bc3024c3..ccbf5db028f 100644 --- a/src/image.c +++ b/src/image.c @@ -2131,6 +2131,7 @@ image_clear_image_1 (struct frame *f, struct image *img, int flags) static void image_clear_image (struct frame *f, struct image *img) { + img->lisp_data = Qnil; block_input (); image_clear_image_1 (f, img, (CLEAR_IMAGE_PIXMAP @@ -9653,24 +9654,6 @@ static const struct image_keyword gif_format[GIF_LAST] = {":background", IMAGE_STRING_OR_NIL_VALUE, 0} }; -#endif - -#if defined HAVE_GIF || defined HAVE_WEBP - -/* Free X resources of GIF image IMG which is used on frame F. - Also used by other image types. */ - -static void -gif_clear_image (struct frame *f, struct image *img) -{ - img->lisp_data = Qnil; - image_clear_image (f, img); -} - -#endif /* defined HAVE_GIF || defined HAVE_WEBP */ - -#if defined (HAVE_GIF) - /* Return true if OBJECT is a valid GIF image specification. */ static bool @@ -10900,15 +10883,6 @@ static struct image_keyword imagemagick_format[IMAGEMAGICK_LAST] = {":crop", IMAGE_DONT_CHECK_VALUE_TYPE, 0} }; -/* Free X resources of imagemagick image IMG which is used on frame F. */ - -static void -imagemagick_clear_image (struct frame *f, - struct image *img) -{ - image_clear_image (f, img); -} - /* Return true if OBJECT is a valid IMAGEMAGICK image specification. Do this by calling parse_image_spec and supplying the keywords that identify the IMAGEMAGICK format. */ @@ -12954,7 +12928,7 @@ static struct image_type const image_types[] = #endif #ifdef HAVE_IMAGEMAGICK { SYMBOL_INDEX (Qimagemagick), imagemagick_image_p, imagemagick_load, - imagemagick_clear_image }, + image_clear_image }, #endif #ifdef HAVE_RSVG { SYMBOL_INDEX (Qsvg), svg_image_p, svg_load, image_clear_image, @@ -12965,7 +12939,7 @@ static struct image_type const image_types[] = IMAGE_TYPE_INIT (init_png_functions) }, #endif #if defined HAVE_GIF - { SYMBOL_INDEX (Qgif), gif_image_p, gif_load, gif_clear_image, + { SYMBOL_INDEX (Qgif), gif_image_p, gif_load, image_clear_image, IMAGE_TYPE_INIT (init_gif_functions) }, #endif #if defined HAVE_TIFF @@ -12982,7 +12956,7 @@ static struct image_type const image_types[] = IMAGE_TYPE_INIT (init_xpm_functions) }, #endif #if defined HAVE_WEBP - { SYMBOL_INDEX (Qwebp), webp_image_p, webp_load, gif_clear_image, + { SYMBOL_INDEX (Qwebp), webp_image_p, webp_load, image_clear_image, IMAGE_TYPE_INIT (init_webp_functions) }, #endif { SYMBOL_INDEX (Qxbm), xbm_image_p, xbm_load, image_clear_image }, From 8c84a2ae71665f0f45adfe96d55a66bd944d343c Mon Sep 17 00:00:00 2001 From: Michael Albinus Date: Wed, 28 Jan 2026 12:43:38 +0100 Subject: [PATCH 21/74] New Tramp test * test/lisp/net/tramp-tests.el (tramp-test45-force-remote-file-error): New test. --- test/lisp/net/tramp-tests.el | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/test/lisp/net/tramp-tests.el b/test/lisp/net/tramp-tests.el index 20e76e5fe9b..bbfe15d2f59 100644 --- a/test/lisp/net/tramp-tests.el +++ b/test/lisp/net/tramp-tests.el @@ -8259,6 +8259,42 @@ process sentinels. They shall not disturb each other." ;; (tramp--test-deftest-direct-async-process tramp-test45-asynchronous-requests ;; 'unstable) +;; This test is inspired by Bug#49954 and Bug#60534. +(ert-deftest tramp-test45-force-remote-file-error () + "Force `remote-file-error'." + :tags '(:expensive-test :tramp-asynchronous-processes :unstable) + ;; It shall run only if selected explicitly. + (skip-unless + (eq (ert--stats-selector ert--current-run-stats) + (ert-test-name (ert--stats-current-test ert--current-run-stats)))) + (skip-unless (tramp--test-enabled)) + (skip-unless (tramp--test-sh-p)) + + (let ((default-directory ert-remote-temporary-file-directory) + ;; Do not cache Tramp properties. + (remote-file-name-inhibit-cache t) + (p (start-file-process-shell-command + "test" (generate-new-buffer "test" 'inhibit-buffer-hooks) + "while true; do echo test; sleep 0.2; done"))) + + (set-process-filter + p (lambda (&rest _) + (message "filter %s" default-directory) + (directory-files default-directory) + (dired-uncache default-directory))) + + (run-at-time + 0 0.2 (lambda () + (message "timer %s" default-directory) + (directory-files default-directory) + (dired-uncache default-directory))) + + (while t + (accept-process-output) + (message "main %s" default-directory) + (directory-files default-directory) + (dired-uncache default-directory)))) + (ert-deftest tramp-test46-dired-compress-file () "Check that Tramp (un)compresses normal files." (skip-unless (tramp--test-enabled)) From 08b7739cf12a2a40f4445565f21e4fc3d4739b99 Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Wed, 28 Jan 2026 14:09:12 +0200 Subject: [PATCH 22/74] ; Fix MS-Windows build broken by recent treesit.c changes * src/treesit.c (ts_tree_cursor_copy) [WINDOWSNT]: Remove, as it is no longer used. (ts_tree_cursor_goto_previous_sibling) [WINDOWSNT]: Add. (Bug#80108) --- src/treesit.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/treesit.c b/src/treesit.c index e9ae1ad3605..231da968fe1 100644 --- a/src/treesit.c +++ b/src/treesit.c @@ -81,11 +81,11 @@ along with GNU Emacs. If not, see . */ #undef ts_query_predicates_for_pattern #undef ts_query_string_value_for_id #undef ts_set_allocator -#undef ts_tree_cursor_copy #undef ts_tree_cursor_current_node #undef ts_tree_cursor_delete #undef ts_tree_cursor_goto_first_child #undef ts_tree_cursor_goto_first_child_for_byte +#undef ts_tree_cursor_goto_previous_sibling #undef ts_tree_cursor_goto_next_sibling #undef ts_tree_cursor_goto_parent #undef ts_tree_cursor_new @@ -153,12 +153,12 @@ DEF_DLL_FN (const char *, ts_query_string_value_for_id, (const TSQuery *, uint32_t, uint32_t *)); DEF_DLL_FN (void, ts_set_allocator, (void *(*)(size_t), void *(*)(size_t, size_t), void *(*)(void *, size_t), void (*)(void *))); -DEF_DLL_FN (TSTreeCursor, ts_tree_cursor_copy, (const TSTreeCursor *)); DEF_DLL_FN (TSNode, ts_tree_cursor_current_node, (const TSTreeCursor *)); DEF_DLL_FN (void, ts_tree_cursor_delete, (const TSTreeCursor *)); DEF_DLL_FN (bool, ts_tree_cursor_goto_first_child, (TSTreeCursor *)); DEF_DLL_FN (int64_t, ts_tree_cursor_goto_first_child_for_byte, (TSTreeCursor *, uint32_t)); DEF_DLL_FN (bool, ts_tree_cursor_goto_next_sibling, (TSTreeCursor *)); +DEF_DLL_FN (bool, ts_tree_cursor_goto_previous_sibling, (TSTreeCursor *)); DEF_DLL_FN (bool, ts_tree_cursor_goto_parent, (TSTreeCursor *)); DEF_DLL_FN (TSTreeCursor, ts_tree_cursor_new, (TSNode)); DEF_DLL_FN (void, ts_tree_delete, (TSTree *)); @@ -221,12 +221,12 @@ init_treesit_functions (void) LOAD_DLL_FN (library, ts_query_predicates_for_pattern); LOAD_DLL_FN (library, ts_query_string_value_for_id); LOAD_DLL_FN (library, ts_set_allocator); - LOAD_DLL_FN (library, ts_tree_cursor_copy); LOAD_DLL_FN (library, ts_tree_cursor_current_node); LOAD_DLL_FN (library, ts_tree_cursor_delete); LOAD_DLL_FN (library, ts_tree_cursor_goto_first_child); LOAD_DLL_FN (library, ts_tree_cursor_goto_first_child_for_byte); LOAD_DLL_FN (library, ts_tree_cursor_goto_next_sibling); + LOAD_DLL_FN (library, ts_tree_cursor_goto_previous_sibling); LOAD_DLL_FN (library, ts_tree_cursor_goto_parent); LOAD_DLL_FN (library, ts_tree_cursor_new); LOAD_DLL_FN (library, ts_tree_delete); @@ -283,12 +283,12 @@ init_treesit_functions (void) #define ts_query_predicates_for_pattern fn_ts_query_predicates_for_pattern #define ts_query_string_value_for_id fn_ts_query_string_value_for_id #define ts_set_allocator fn_ts_set_allocator -#define ts_tree_cursor_copy fn_ts_tree_cursor_copy #define ts_tree_cursor_current_node fn_ts_tree_cursor_current_node #define ts_tree_cursor_delete fn_ts_tree_cursor_delete #define ts_tree_cursor_goto_first_child fn_ts_tree_cursor_goto_first_child #define ts_tree_cursor_goto_first_child_for_byte fn_ts_tree_cursor_goto_first_child_for_byte #define ts_tree_cursor_goto_next_sibling fn_ts_tree_cursor_goto_next_sibling +#define ts_tree_cursor_goto_previous_sibling fn_ts_tree_cursor_goto_previous_sibling #define ts_tree_cursor_goto_parent fn_ts_tree_cursor_goto_parent #define ts_tree_cursor_new fn_ts_tree_cursor_new #define ts_tree_delete fn_ts_tree_delete From 43d6907ad9c8fae70d885132a552fe3672f498e8 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Wed, 28 Jan 2026 16:13:47 +0000 Subject: [PATCH 23/74] Move outstanding changes commands from 'o' to 'T' The main reason for this is that then these commands can have the same bindings in VC-Dir buffers that they have under vc-prefix-map. 'T' is a good mnemonic for "Topic" and a serviceable mnemonic for "outsTanding". * lisp/vc/vc-hooks.el (vc-prefix-map): Move 'o' to 'T'. * lisp/vc/vc-dir.el (vc-dir-mode-map): New 'T' bindings. --- doc/emacs/maintaining.texi | 3 +++ doc/emacs/vc1-xtra.texi | 24 ++++++++++++------------ etc/NEWS | 3 ++- lisp/vc/vc-dir.el | 2 ++ lisp/vc/vc-hooks.el | 4 ++-- 5 files changed, 21 insertions(+), 15 deletions(-) diff --git a/doc/emacs/maintaining.texi b/doc/emacs/maintaining.texi index f1090d4b43f..aebe31b478e 100644 --- a/doc/emacs/maintaining.texi +++ b/doc/emacs/maintaining.texi @@ -1712,6 +1712,9 @@ Do an incremental regular expression search on the fileset Apart from acting on multiple files, these commands behave much like their single-buffer counterparts (@pxref{Search}). +@c Outstanding changes commands under 'T' are not mentioned because +@c these are an advanced feature documented only in vc1-xtra.texi. + The VC Directory buffer additionally defines some branch-related commands starting with the prefix @kbd{b}: diff --git a/doc/emacs/vc1-xtra.texi b/doc/emacs/vc1-xtra.texi index 8ffd6506dbe..655402b61ba 100644 --- a/doc/emacs/vc1-xtra.texi +++ b/doc/emacs/vc1-xtra.texi @@ -298,11 +298,11 @@ yet merged into the target branch. @cindex outstanding changes @table @kbd -@item C-x v o = +@item C-x v T = Display diffs of changes to the VC fileset since the merge base of this branch and its upstream counterpart (@code{vc-diff-outgoing-base}). -@item C-x v o D +@item C-x v T D Display all changes since the merge base of this branch and its upstream counterpart (@code{vc-root-diff-outgoing-base}). @end table @@ -321,17 +321,17 @@ unpushed commits and uncommitted changes in your working tree. In many cases the reason these changes are not pushed yet is that they are not finished: the changes committed so far don't make sense in isolation. -@kindex C-x v o = +@kindex C-x v T = @findex vc-diff-outgoing-base -@kindex C-x v o D +@kindex C-x v T D @findex vc-root-diff-outgoing-base -Type @kbd{C-x v o D} (@code{vc-root-diff-outgoing-base}) to display a +Type @kbd{C-x v T D} (@code{vc-root-diff-outgoing-base}) to display a summary of all these changes, committed and uncommitted. This summary is in the form of a diff of what committing and pushing (@pxref{Pulling / Pushing}) all these changes would do to the upstream repository. You -can use @kbd{C-x v o =} (@code{vc-diff-outgoing-base}) instead to limit +can use @kbd{C-x v T =} (@code{vc-diff-outgoing-base}) instead to limit the display of changes to the current VC fileset. (The difference -between @w{@kbd{C-x v o D}} and @w{@kbd{C-x v o =}} is like the +between @w{@kbd{C-x v T D}} and @w{@kbd{C-x v T =}} is like the difference between @kbd{C-x v D} and @kbd{C-x v =} (@pxref{Old Revisions}).)@footnote{Another point of comparison is that these commands are like @w{@kbd{C-x v O =}} (@code{vc-fileset-diff-outgoing}) @@ -359,12 +359,12 @@ upstream repository's development trunk. That means committed changes on the topic branch that haven't yet been merged into the trunk, plus uncommitted changes. -When the current branch is a topic branch and you type @kbd{C-x v o D}, +When the current branch is a topic branch and you type @kbd{C-x v T D}, Emacs displays a summary of all the changes that are outstanding against the trunk to which the current branch will be merged. This summary is in the form of a diff of what committing and pushing all the changes, @emph{and} subsequently merging the topic branch, would do to the trunk. -As above, you can use @kbd{C-x v o =} instead to limit the display of +As above, you can use @kbd{C-x v T =} instead to limit the display of changes to the current VC fileset. This functionality relies on Emacs correctly detecting whether the @@ -379,7 +379,7 @@ The variables @code{vc-trunk-branch-regexps} and @code{vc-topic-branch-regexps} contain lists of regular expressions matching the names of branches that should always be considered trunk and topic branches, respectively. You can also specify prefix arguments -to @kbd{C-x v o D} and @kbd{C-x v o =}. Here is a summary of how to use +to @kbd{C-x v T D} and @kbd{C-x v T =}. Here is a summary of how to use these controls: @enumerate @@ -425,7 +425,7 @@ described. E.g., if the value of @code{vc-trunk-branch-regexps} is branch. @item -Supply a double prefix argument, i.e. @w{@kbd{C-u C-u C-x v o @dots{}}}, +Supply a double prefix argument, i.e. @w{@kbd{C-u C-u C-x v T @dots{}}}, and Emacs will treat the current branch as a trunk, no matter what. This is useful when you simply want to obtain a diff of all outgoing changes (@pxref{VC Change Log}) plus uncommitted changes. @@ -433,7 +433,7 @@ changes (@pxref{VC Change Log}) plus uncommitted changes. @item @cindex outgoing base, version control Finally, you can take full manual control by supplying a single prefix -argument, i.e. @w{@kbd{C-u C-x v o @dots{}}}. Emacs will prompt you for +argument, i.e. @w{@kbd{C-u C-x v T @dots{}}}. Emacs will prompt you for the @dfn{outgoing base}, which is the upstream location for which the changes are destined once they are no longer outstanding. diff --git a/etc/NEWS b/etc/NEWS index 9d36f6c3d96..65c8c62dec5 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -2746,11 +2746,12 @@ current VC fileset. +++ *** New commands to report diffs of outstanding changes. -'C-x v o =' ('vc-diff-outgoing-base') and 'C-x v o D' +'C-x v T =' ('vc-diff-outgoing-base') and 'C-x v T D' ('vc-root-diff-outgoing-base') report diffs of changes since the merge base with the remote branch, including uncommitted changes. They are useful to view all outstanding (unmerged, unpushed) changes on the current branch. +They are also available as 'T =' and 'T D' in VC-Dir buffers. +++ *** New user option 'vc-use-incoming-outgoing-prefixes'. diff --git a/lisp/vc/vc-dir.el b/lisp/vc/vc-dir.el index 303cfd93ba2..b9176d8a2f6 100644 --- a/lisp/vc/vc-dir.el +++ b/lisp/vc/vc-dir.el @@ -397,6 +397,8 @@ That is, refreshing the VC-Dir buffer also hides `up-to-date' and (define-key map (kbd "M-s a M-C-s") #'vc-dir-isearch-regexp) (define-key map "G" #'vc-dir-ignore) (define-key map "@" #'vc-revert) + (define-key map "T=" #'vc-diff-outgoing-base) + (define-key map "TD" #'vc-root-diff-outgoing-base) (let ((branch-map (make-sparse-keymap))) (define-key map "b" branch-map) diff --git a/lisp/vc/vc-hooks.el b/lisp/vc/vc-hooks.el index e867654409c..a6e07e02de9 100644 --- a/lisp/vc/vc-hooks.el +++ b/lisp/vc/vc-hooks.el @@ -1018,8 +1018,8 @@ In the latter case, VC mode is deactivated for this buffer." "O" #'vc-root-log-outgoing "M L" #'vc-log-mergebase "M D" #'vc-diff-mergebase - "o =" #'vc-diff-outgoing-base - "o D" #'vc-root-diff-outgoing-base + "T =" #'vc-diff-outgoing-base + "T D" #'vc-root-diff-outgoing-base "m" #'vc-merge "r" #'vc-retrieve-tag "s" #'vc-create-tag From b370a076b92dae176d772ba70894b0f6e963bb1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Kryger?= Date: Tue, 27 Jan 2026 11:45:28 +0000 Subject: [PATCH 24/74] Create package-vc-tests repositories once per tests run (bug#80235) * test/lisp/emacs-lisp/package-vc-tests.el (package-vc-tests-repos): New variable. (package-vc-tests-create-repository): Add argument `repos-dir'. (package-vc-tests-make-temp-dir): Create a temporary directory with prefix. (package-vc-with-tests-environment): Use `package-vc-tests-make-temp-dir' to create a temporary directory for package test. Use `package-vc-tests-repos' to cache test package repository. (package-vc-tests-preserve-pkg-artifacts-p): Detect when to preserve package temporary files. (package-vc-tests-environment-tear-down): Use `package-vc-tests-preserve-pkg-artifacts-p'. Use plural there are more than one buffer. Report temporary directory with test repository. (package-vc-tests-add-ert-run-tests-listener): Wrap listener in args with custom functionality for `package-vc-tests'. On tests run start reset `package-vc-tests-repos' cache. On tests run end delete temporary directories. --- test/lisp/emacs-lisp/package-vc-tests.el | 126 +++++++++++++++++------ 1 file changed, 97 insertions(+), 29 deletions(-) diff --git a/test/lisp/emacs-lisp/package-vc-tests.el b/test/lisp/emacs-lisp/package-vc-tests.el index 01c08ca7d3f..38ecb338da5 100644 --- a/test/lisp/emacs-lisp/package-vc-tests.el +++ b/test/lisp/emacs-lisp/package-vc-tests.el @@ -65,6 +65,8 @@ of symbols, then preserve temporary directories and buffers for each package that matches a symbol in the list. When this variable is t then preserve all temporary directories.") +(defvar package-vc-tests-repos (make-hash-table)) + (defvar package-vc-tests-dir) (defvar package-vc-tests-packages) (defvar package-vc-tests-repository) @@ -169,12 +171,11 @@ When LISP-DIR is non-nil place the NAME file under LISP-DIR." (error "Failed to invoke sed on %s" in-file)) (vc-git-command nil 0 nil "add" "."))) -(defun package-vc-tests-create-repository (suffix &optional lisp-dir) - "Create a test package repository with SUFFIX. +(defun package-vc-tests-create-repository (suffix repos-dir &optional lisp-dir) + "Create a test package repository with SUFFIX in REPOS-DIR. If LISP-DIR is non-nil place sources of the package in LISP-DIR." (let* ((name (format "test-package-%s" suffix)) - (repo-dir (expand-file-name (file-name-concat "repo" name) - package-vc-tests-dir))) + (repo-dir (expand-file-name name repos-dir))) (make-directory (expand-file-name (or lisp-dir ".") repo-dir) t) (let ((default-directory repo-dir) (process-environment @@ -399,6 +400,11 @@ names." (not (member lisp-dir '("lisp" "src"))) (list :lisp-dir lisp-dir))))) +(defun package-vc-tests-make-temp-dir (prefix) + "Create temp directory with PREFIX." + (expand-file-name + (make-temp-file prefix t (format-time-string "-%Y%m%d.%H%M%S.%3N")))) + (defun package-vc-with-tests-environment (pkg function) "Call FUNCTION with no arguments within a test environment set up for PKG." ;; Create a test package sources repository, based on skeleton files @@ -406,10 +412,7 @@ names." ;; that: ;; (let* ((package-vc-tests-dir - (expand-file-name - (make-temp-file "package-vc-tests-" - t - (format-time-string "-%Y%m%d.%H%M%S.%3N")))) + (package-vc-tests-make-temp-dir "package-vc-tests-")) ;; - packages are installed into test directory (package-user-dir (expand-file-name "elpa" package-vc-tests-dir)) @@ -428,13 +431,25 @@ names." (package-vc-tests-packages (package-vc-tests-packages)) ;; - create a test package bundle (package-vc-tests-repository - (let* ((pkg-name (symbol-name pkg)) - (suffix (and (string-match - (rx ?- (group (1+ (not ?-))) eos) - pkg-name) - (match-string 1 pkg-name)))) - (package-vc-tests-create-repository - suffix (cadr (alist-get pkg package-vc-tests-packages))))) + (or + (gethash pkg package-vc-tests-repos) + (let* ((pkg-name (symbol-name pkg)) + (suffix (and (string-match + (rx ?- (group (1+ (not ?-))) eos) + pkg-name) + (match-string 1 pkg-name))) + (repos-dir + (or (gethash 'repos-dir package-vc-tests-repos) + (puthash 'repos-dir + (package-vc-tests-make-temp-dir + "package-vc-tests-repos-") + package-vc-tests-repos)))) + (puthash pkg + (package-vc-tests-create-repository + suffix + repos-dir + (cadr (alist-get pkg package-vc-tests-packages))) + package-vc-tests-repos)))) ;; - find all packages that are present in a test ELPA (package-vc-tests-elpa-packages (cl-loop @@ -495,6 +510,12 @@ names." (package-vc-allow-build-commands t)) (funcall function))) +(defun package-vc-tests-preserve-pkg-artifacts-p (pkg) + "Return non nil if files and buffers for PKG should be preserved." + (or (memq package-vc-tests-preserve-artifacts `(t ,pkg)) + (and (listp package-vc-tests-preserve-artifacts) + (memq pkg package-vc-tests-preserve-artifacts)))) + (defun package-vc-tests-environment-tear-down (pkg) "Tear down test environment for PKG. Unbind package defined symbols, and remove package defined features and @@ -538,27 +559,74 @@ when PKG matches `package-vc-tests-preserve-artifacts'." (package-vc-tests-log-buffer-name pkg type))) '(doc make))))) - (if (or (memq package-vc-tests-preserve-artifacts `(t ,pkg)) - (and (listp package-vc-tests-preserve-artifacts) - (memq pkg package-vc-tests-preserve-artifacts))) + (if (package-vc-tests-preserve-pkg-artifacts-p pkg) (let ((buffers - (mapconcat (lambda (buffer) - (with-current-buffer buffer - (let* ((old-name (buffer-name)) - (new-name (make-temp-name - (string-trim old-name)))) - (rename-buffer new-name) - (concat old-name " -> " new-name)))) - buffers - ", "))) + (if buffers + (format " and %s: %s" + (if (cdr buffers) "buffers" "buffer") + (mapconcat + (lambda (buffer) + (with-current-buffer buffer + (let* ((old-name (buffer-name)) + (new-name (make-temp-name + (string-trim old-name)))) + (rename-buffer new-name) + (format "`%s' -> `%s'" + old-name new-name)))) + buffers + ", ")) + "")) + (repo-dir (car (gethash pkg package-vc-tests-repos)))) (message - "package-vc-tests: preserving temporary directory: %s%s" + "package-vc-tests: preserving temporary %s: %s%s%s" + (if repo-dir "directories" "directory") package-vc-tests-dir - (and buffers (format " and buffers: %s" buffers)))) + (if repo-dir (format " and %s" repo-dir) "") + buffers)) (delete-directory package-vc-tests-dir t) (dolist (buffer buffers) (kill-buffer buffer))))) +;; Tests create a repository for a package only once per a tests run. +;; The repository location is cached in `package-vc-tests-repos'. To +;; support development, clear the cache on start of each tests run, such +;; that the package repository contains files from the source code. +;; When tests run completes delete repositories accounting for +;; `package-vc-tests-preserve-artifacts', which see. + +(defun package-vc-tests-add-ert-run-tests-listener (args) + "Add `package-vc-tests' repositories cleanup to listener in ARGS." + (if-let* ((listener (cadr args)) + ((functionp listener))) + (cl-list* + (car args) + (lambda (event-type &rest event-args) + (cl-case event-type + (run-started + (clrhash package-vc-tests-repos)) + (run-ended + (when-let* ((repos-dir (gethash 'repos-dir + package-vc-tests-repos)) + ((file-directory-p repos-dir))) + (if package-vc-tests-preserve-artifacts + (progn + (dolist (pkg (package-vc-tests-packages)) + (unless + (package-vc-tests-preserve-pkg-artifacts-p pkg) + (when-let* ((repo-dir + (car (gethash pkg package-vc-tests-repos))) + ((file-directory-p repo-dir))) + (delete-directory repo-dir t)))) + (when (directory-empty-p repos-dir) + (delete-directory repos-dir))) + (delete-directory repos-dir t))))) + (apply listener (cons event-type event-args))) + (drop 2 args)) + args)) + +(advice-add #'ert-run-tests + :filter-args #'package-vc-tests-add-ert-run-tests-listener) + (defun package-vc-tests-with-installed (pkg function) "Call FUNCTION with PKG installed in a test environment. FUNCTION should have no arguments." From fbe4d649c30234a143dc092ff4931e56f74de073 Mon Sep 17 00:00:00 2001 From: Stefan Monnier Date: Wed, 28 Jan 2026 13:43:36 -0500 Subject: [PATCH 25/74] (loaddefs-generate--make-autoload): Try harder to find `autoload-macro` * lisp/emacs-lisp/loaddefs-gen.el (loaddefs-generate--make-autoload): Try and (auto)load the macro in case that defines `autoload-macro`. Simplify the code for the `loaddefs--defining-macros` case. --- lisp/emacs-lisp/loaddefs-gen.el | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/lisp/emacs-lisp/loaddefs-gen.el b/lisp/emacs-lisp/loaddefs-gen.el index ede9a9fe8a0..60d250b564f 100644 --- a/lisp/emacs-lisp/loaddefs-gen.el +++ b/lisp/emacs-lisp/loaddefs-gen.el @@ -155,7 +155,10 @@ scanning for autoloads and will be in the `load-path'." ;; employing :autoload-end to omit unneeded forms). (defconst loaddefs--defining-macros '( transient-define-prefix transient-define-suffix transient-define-infix - transient-define-argument transient-define-group)) + transient-define-argument + ;; FIXME: How can this one make sense? It doesn't put anything + ;; into `symbol-function'! + transient-define-group)) (defvar loaddefs--load-error-files nil) (defun loaddefs-generate--make-autoload (form file &optional expansion) @@ -237,7 +240,7 @@ expand)' among their `declare' forms." (push file loaddefs--load-error-files) ; do not attempt again (warn "loaddefs-gen: load error\n\t%s" e))))) (and (macrop car) - (eq 'expand (function-get car 'autoload-macro)) + (eq 'expand (function-get car 'autoload-macro 'macro)) (setq expand (let ((load-true-file-name file) (load-file-name file)) (macroexpand-1 form))) @@ -249,12 +252,7 @@ expand)' among their `declare' forms." ;; directly. ((memq car loaddefs--defining-macros) (let* ((name (nth 1 form)) - (args (pcase car - ((or 'transient-define-prefix 'transient-define-suffix - 'transient-define-infix 'transient-define-argument - 'transient-define-group) - (nth 2 form)) - (_ t))) + (args (nth 2 form)) (body (nthcdr (or (function-get car 'doc-string-elt) 3) form)) (doc (if (stringp (car body)) (pop body)))) ;; Add the usage form at the end where describe-function-1 @@ -263,18 +261,7 @@ expand)' among their `declare' forms." ;; `define-generic-mode' quotes the name, so take care of that (loaddefs-generate--shorten-autoload `(autoload ,(if (listp name) name (list 'quote name)) - ,file ,doc - ,(or (and (memq car '( transient-define-prefix - transient-define-suffix - transient-define-infix - transient-define-argument - transient-define-group)) - t) - (and (eq (car-safe (car body)) 'interactive) - ;; List of modes or just t. - (or (if (nthcdr 2 (car body)) - (list 'quote (nthcdr 2 (car body))) - t)))))))) + ,file ,doc t)))) ;; For defclass forms, use `eieio-defclass-autoload'. ((eq car 'defclass) From d44b855b0ccb9d97261735316b25cbccbb150550 Mon Sep 17 00:00:00 2001 From: Dmitry Gutov Date: Wed, 28 Jan 2026 21:55:40 +0200 Subject: [PATCH 26/74] xref-matches-in-directory: Don't error on "Binary file ... matches" * lisp/progmodes/xref.el (xref-matches-in-directory): Consider the "Binary file ... matches" message (bug#80246). --- lisp/progmodes/xref.el | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lisp/progmodes/xref.el b/lisp/progmodes/xref.el index df9e00a0d36..d2817a95b17 100644 --- a/lisp/progmodes/xref.el +++ b/lisp/progmodes/xref.el @@ -1900,7 +1900,9 @@ If DELIMITED is `symbol', only select matches that span full symbols." ;; exit with non-zero. "No matches" and "Grep program not found" ;; are all the same to it. (when (and (/= (point-min) (point-max)) - (not (looking-at grep-re))) + (not (looking-at grep-re)) + ;; See also this check in `xref-matches-in-files'. + (not (looking-at ".*[bB]inary file.* matches"))) (user-error "Search failed with status %d: %s" status (buffer-string))) (while (re-search-forward grep-re nil t) (push (list (string-to-number (match-string line-group)) From dfc2a66ad8fc0892bbe66ddd32fec34d8dfa1821 Mon Sep 17 00:00:00 2001 From: Dmitry Gutov Date: Wed, 28 Jan 2026 23:48:04 +0200 Subject: [PATCH 27/74] xref-matches-in-directory and xref-matches-in-files: More consistency * lisp/progmodes/xref.el (xref--parse-hits, xref--sort-hits): Extract from xref-matches-in-directory and xref-matches-in-files. Use in both for better consistency between these functions. --- lisp/progmodes/xref.el | 78 +++++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/lisp/progmodes/xref.el b/lisp/progmodes/xref.el index d2817a95b17..d2dd4167725 100644 --- a/lisp/progmodes/xref.el +++ b/lisp/progmodes/xref.el @@ -1894,22 +1894,9 @@ If DELIMITED is `symbol', only select matches that span full symbols." (setq default-directory dir) (setq status (process-file-shell-command command nil t)) - (goto-char (point-min)) - ;; Can't use the exit status: Grep exits with 1 to mean "no - ;; matches found". Find exits with 1 if any of the invocations - ;; exit with non-zero. "No matches" and "Grep program not found" - ;; are all the same to it. - (when (and (/= (point-min) (point-max)) - (not (looking-at grep-re)) - ;; See also this check in `xref-matches-in-files'. - (not (looking-at ".*[bB]inary file.* matches"))) - (user-error "Search failed with status %d: %s" status (buffer-string))) - (while (re-search-forward grep-re nil t) - (push (list (string-to-number (match-string line-group)) - (concat local-dir (substring (match-string file-group) 1)) - (buffer-substring-no-properties (point) (line-end-position))) - hits))) - (xref--convert-hits (nreverse hits) hits-regexp))) + (setq hits (xref--parse-hits grep-re line-group file-group status + local-dir))) + (xref--convert-hits (xref--sort-hits hits) hits-regexp))) (define-obsolete-function-alias 'xref-collect-matches @@ -2035,29 +2022,42 @@ to control which program to use when looking for matches." nil shell-command-switch command)))) - (goto-char (point-min)) - (when (and (/= (point-min) (point-max)) - (not (looking-at grep-re)) - ;; TODO: Show these matches as well somehow? - ;; Matching both Grep's and Ripgrep 13's messages. - (not (looking-at ".*[bB]inary file.* matches"))) - (user-error "Search failed with status %d: %s" status - (buffer-substring (point-min) (line-end-position)))) - (while (re-search-forward grep-re nil t) - (push (list (string-to-number (match-string line-group)) - (match-string file-group) - (buffer-substring-no-properties (point) (line-end-position))) - hits))) - ;; By default, ripgrep's output order is non-deterministic - ;; (https://github.com/BurntSushi/ripgrep/issues/152) - ;; because it does the search in parallel. - ;; Grep's output also comes out in seemingly arbitrary order, - ;; though stable one. Let's sort both for better UI. - (setq hits - (sort (nreverse hits) - (lambda (h1 h2) - (string< (cadr h1) (cadr h2))))) - (xref--convert-hits hits regexp))) + (setq hits (xref--parse-hits grep-re line-group file-group status))) + (xref--convert-hits (xref--sort-hits hits) regexp))) + +(defun xref--parse-hits ( grep-re line-group file-group status + &optional parent-dir) + (let (hits) + (goto-char (point-min)) + ;; Can't use the exit status: Grep exits with 1 to mean "no + ;; matches found". Find exits with 1 if any of the invocations + ;; exit with non-zero. "No matches" and "Grep program not found" + ;; are all the same to it. + (when (and (/= (point-min) (point-max)) + (not (looking-at grep-re)) + ;; TODO: Show these matches as well somehow? + ;; Matching both Grep's and Ripgrep 13's messages. + (not (looking-at ".*[bB]inary file.* matches"))) + (user-error "Search failed with status %d: %s" status + (buffer-substring (point-min) (line-end-position)))) + (while (re-search-forward grep-re nil t) + (push (list (string-to-number (match-string line-group)) + (if parent-dir + (concat parent-dir (substring (match-string file-group) 1)) + (match-string file-group)) + (buffer-substring-no-properties (point) (line-end-position))) + hits)) + (nreverse hits))) + +(defun xref--sort-hits (hits) + ;; By default, ripgrep's output order is non-deterministic + ;; (https://github.com/BurntSushi/ripgrep/issues/152) + ;; because it does the search in parallel. + ;; Grep's output also comes out in seemingly arbitrary order, + ;; though stable one. Let's sort both for better UI. + (sort hits + (lambda (h1 h2) + (string< (cadr h1) (cadr h2))))) (defun xref--process-file-region ( start end program &optional buffer display From f949d5ab62fbd21ff310b79cf7df3d806ab081c3 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Thu, 29 Jan 2026 11:20:48 +0800 Subject: [PATCH 28/74] ; Update Android dependencies --- admin/download-android-deps.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/admin/download-android-deps.sh b/admin/download-android-deps.sh index e40392381d7..9223ba86f06 100644 --- a/admin/download-android-deps.sh +++ b/admin/download-android-deps.sh @@ -61,7 +61,7 @@ download_tarball () # 1c8f3b0cbad474da0ab09018c4ecf2119ac4a52d pixman-0.38.4-emacs.tar.gz # b687c8439d51634d921674dd009645e24873ca36 rsvg-2.40.21-emacs.tar.gz # eda251614598aacb06f5984a0a280833de456b29 tiff-4.5.1-emacs.tar.gz -# c00d0ea9c6e848f5cce350cb3ed742024f2bdb8b tree-sitter-0.20.7-emacs.tar.gz +# 4cbc3ae7ae600db3787619d7994c659d9253a752 tree-sitter-0.26.3-emacs.tar.gz download_tarball "giflib-5.2.1-emacs.tar.gz" "giflib-5.2.1" \ "a407c568961d729bb2d0175a34e0d4ed4a269978" @@ -90,8 +90,8 @@ download_tarball "libtasn1-4.19.0-emacs.tar.gz" "libtasn1-4.19.0" \ "fdc827211075d9b70a8ba6ceffa02eb48d6741e9" download_tarball "libselinux-3.6-emacs.tar.gz" "libselinux-3.6" \ "8361966e19fe25ae987b08799f1442393ae6366b" -download_tarball "tree-sitter-0.20.7-emacs.tar.gz" "tree-sitter-0.20.7" \ - "c00d0ea9c6e848f5cce350cb3ed742024f2bdb8b" +download_tarball "tree-sitter-0.26.3-emacs.tar.gz" "tree-sitter-0.26.3" \ + "4cbc3ae7ae600db3787619d7994c659d9253a752" download_tarball "harfbuzz-7.1.0-emacs.tar.gz" "harfbuzz-7.1.0" \ "22dc71d503ab2eb263dc8411de9da1db144520f5" download_tarball "tiff-4.5.1-emacs.tar.gz" "tiff-4.5.1" \ From 1bbc7d955adc18cbcb92904bd3ecf0179104ad54 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Thu, 29 Jan 2026 11:25:57 +0800 Subject: [PATCH 29/74] ; Update Android dependencies again --- admin/download-android-deps.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/admin/download-android-deps.sh b/admin/download-android-deps.sh index 9223ba86f06..f2578e284d9 100644 --- a/admin/download-android-deps.sh +++ b/admin/download-android-deps.sh @@ -61,7 +61,7 @@ download_tarball () # 1c8f3b0cbad474da0ab09018c4ecf2119ac4a52d pixman-0.38.4-emacs.tar.gz # b687c8439d51634d921674dd009645e24873ca36 rsvg-2.40.21-emacs.tar.gz # eda251614598aacb06f5984a0a280833de456b29 tiff-4.5.1-emacs.tar.gz -# 4cbc3ae7ae600db3787619d7994c659d9253a752 tree-sitter-0.26.3-emacs.tar.gz +# 9d032de89c874354c22d304f7e968f4ca6de8c0a tree-sitter-0.26.3-emacs.tar.gz download_tarball "giflib-5.2.1-emacs.tar.gz" "giflib-5.2.1" \ "a407c568961d729bb2d0175a34e0d4ed4a269978" @@ -91,7 +91,7 @@ download_tarball "libtasn1-4.19.0-emacs.tar.gz" "libtasn1-4.19.0" \ download_tarball "libselinux-3.6-emacs.tar.gz" "libselinux-3.6" \ "8361966e19fe25ae987b08799f1442393ae6366b" download_tarball "tree-sitter-0.26.3-emacs.tar.gz" "tree-sitter-0.26.3" \ - "4cbc3ae7ae600db3787619d7994c659d9253a752" + "9d032de89c874354c22d304f7e968f4ca6de8c0a" download_tarball "harfbuzz-7.1.0-emacs.tar.gz" "harfbuzz-7.1.0" \ "22dc71d503ab2eb263dc8411de9da1db144520f5" download_tarball "tiff-4.5.1-emacs.tar.gz" "tiff-4.5.1" \ From 80551807d4bad5fd9f03255b2bdb369ce5f78a7c Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Thu, 29 Jan 2026 10:29:40 +0200 Subject: [PATCH 30/74] ; Fix package-vc-tests for older versions of Git * test/lisp/emacs-lisp/package-vc-tests.el (package-vc-tests-create-repository): Fix commands for older versions of Git. --- test/lisp/emacs-lisp/package-vc-tests.el | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/lisp/emacs-lisp/package-vc-tests.el b/test/lisp/emacs-lisp/package-vc-tests.el index 38ecb338da5..5ae36e79bcc 100644 --- a/test/lisp/emacs-lisp/package-vc-tests.el +++ b/test/lisp/emacs-lisp/package-vc-tests.el @@ -184,7 +184,8 @@ If LISP-DIR is non-nil place sources of the package in LISP-DIR." (format "GIT_AUTHOR_NAME=%s" name) (format "GIT_COMMITTER_NAME=%s" name)) process-environment))) - (vc-git-command nil 0 nil "init" "-b" "master") + (vc-git-command nil 0 nil "init") + (vc-git-command nil 0 nil "checkout" "-b" "master") (package-vc-tests-add suffix "test-package-SUFFIX-lib-v0.1.el.in" lisp-dir) (package-vc-tests-add From 792097d74761bf844c12b8f5916df19e252d5d1b Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Thu, 29 Jan 2026 13:38:22 +0200 Subject: [PATCH 31/74] Avoid interference between child frame deletion and recentering * src/frame.c (delete_frame) [HAVE_X_WINDOWS]: Block input while child frame is displayed, and process the X events triggered by that later. Patch by Byakuren (https://web.liminal.cafe/~byakuren/). (Bug#76186) Copyright-paperwork-exempt: yes --- src/frame.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/frame.c b/src/frame.c index ba342b15723..d197e4d5351 100644 --- a/src/frame.c +++ b/src/frame.c @@ -2887,6 +2887,15 @@ delete_frame (Lisp_Object frame, Lisp_Object force) promise that the terminal of the frame must be valid until we have called the window-system-dependent frame destruction routine. */ + /* Remember if this was a GUI child frame, so we can + process pending window system events after destruction. */ + bool was_gui_child_frame = FRAME_WINDOW_P (f) && FRAME_PARENT_FRAME (f); +#ifdef HAVE_X_WINDOWS + /* Save the X display before the frame is destroyed, so we can + sync with the X server afterwards. */ + Display *child_frame_display = (was_gui_child_frame && FRAME_X_P (f) + ? FRAME_X_DISPLAY (f) : NULL); +#endif { struct terminal *terminal; block_input (); @@ -2896,6 +2905,24 @@ delete_frame (Lisp_Object frame, Lisp_Object force) f->terminal = 0; /* Now the frame is dead. */ unblock_input (); + /* When a GUI child frame is deleted, the window system may + generate events that affect the parent frame (e.g. + ConfigureNotify, Expose, etc.). We need to sync with the + X server to ensure all events from the frame destruction + have been received, then process them to ensure subsequent + operations like `recenter' see up-to-date window state. + (Bug#76186) */ +#ifdef HAVE_X_WINDOWS + if (child_frame_display) + { + block_input (); + XSync (child_frame_display, False); + unblock_input (); + } +#endif + if (was_gui_child_frame) + swallow_events (false); + /* Clear markers and overlays set by F on behalf of an input method. */ #ifdef HAVE_TEXT_CONVERSION From c07ffa21884edae0bf241eb68c44114639a2a1a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Engdeg=C3=A5rd?= Date: Thu, 29 Jan 2026 11:07:31 +0100 Subject: [PATCH 32/74] * lisp/tutorial.el (tutorial--describe-nonstandard-key): add space --- lisp/tutorial.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp/tutorial.el b/lisp/tutorial.el index 113d13d11c2..f98a13b8a4a 100644 --- a/lisp/tutorial.el +++ b/lisp/tutorial.el @@ -153,7 +153,7 @@ options: " you can use " (if (string-match-p "^the .*menus?$" where) "" - "the key") + "the key ") where (format-message " to get the function `%s'." db)))) (fill-region (point-min) (point))))) From 495f6b412de244a87cc82a46bab26a86b83e8b15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Engdeg=C3=A5rd?= Date: Thu, 29 Jan 2026 11:41:19 +0100 Subject: [PATCH 33/74] tutorial.el: don't mutate quoted list * lisp/tutorial.el (tutorial--default-keys): Don't sort quoted list in-place. Sort at compile time, not load time. Uniform key representation (vectors) so that the default comparison can be used. Eliminate unnecessary backquote. (tutorial--sort-keys): Remove. --- lisp/tutorial.el | 57 ++++++------------------------------------------ 1 file changed, 7 insertions(+), 50 deletions(-) diff --git a/lisp/tutorial.el b/lisp/tutorial.el index f98a13b8a4a..c071c1ff1d8 100644 --- a/lisp/tutorial.el +++ b/lisp/tutorial.el @@ -159,54 +159,11 @@ options: (fill-region (point-min) (point))))) (help-print-return-message)))) -(defun tutorial--sort-keys (left right) - "Sort predicate for use with `tutorial--default-keys'. -This is a predicate function to `sort'. - -The sorting is for presentation purpose only and is done on the -key sequence. - -LEFT and RIGHT are the elements to compare." - (let ((x (append (cadr left) nil)) - (y (append (cadr right) nil))) - ;; Skip the front part of the key sequences if they are equal: - (while (and x y - (listp x) (listp y) - (equal (car x) (car y))) - (setq x (cdr x)) - (setq y (cdr y))) - ;; Try to make a comparison that is useful for presentation (this - ;; could be made nicer perhaps): - (let ((cx (car x)) - (cy (car y))) - ;;(message "x=%s, y=%s;;;; cx=%s, cy=%s" x y cx cy) - (cond - ;; Lists? Then call this again - ((and cx cy - (listp cx) - (listp cy)) - (tutorial--sort-keys cx cy)) - ;; Are both numbers? Then just compare them - ((and (wholenump cx) - (wholenump cy)) - (> cx cy)) - ;; Is one of them a number? Let that be bigger then. - ((wholenump cx) - t) - ((wholenump cy) - nil) - ;; Are both symbols? Compare the names then. - ((and (symbolp cx) - (symbolp cy)) - (string< (symbol-name cy) - (symbol-name cx))))))) - (defconst tutorial--default-keys - ;; On window system, `suspend-emacs' is replaced in the default keymap. - (let* ((suspend-emacs 'suspend-frame) - (default-keys + (eval-when-compile + (let ((default-keys ;; The first few are not mentioned but are basic: - `((ESC-prefix [27]) + '((ESC-prefix [27]) (Control-X-prefix [?\C-x]) (mode-specific-command-prefix [?\C-c]) (save-buffers-kill-terminal [?\C-x ?\C-c]) @@ -227,7 +184,7 @@ LEFT and RIGHT are the elements to compare." (move-end-of-line [?\C-e]) (backward-sentence [?\M-a]) (forward-sentence [?\M-e]) - (newline "\r") + (newline [?\C-m]) (beginning-of-buffer [?\M-<]) (end-of-buffer [?\M->]) (universal-argument [?\C-u]) @@ -245,7 +202,7 @@ LEFT and RIGHT are the elements to compare." ;; * INSERTING AND DELETING ;; C-u 8 * to insert ********. - (delete-backward-char "\d") + (delete-backward-char [?\C-?]) (delete-char [?\C-d]) (backward-kill-word [?\M-\d]) (kill-word [?\M-d]) @@ -309,8 +266,8 @@ LEFT and RIGHT are the elements to compare." ;; * CONCLUSION ;;(iconify-or-deiconify-frame [?\C-z]) - (,suspend-emacs [?\C-z])))) - (sort default-keys 'tutorial--sort-keys)) + (suspend-frame [?\C-z])))) + (sort default-keys :key #'cadr))) "Default Emacs key bindings that the tutorial depends on.") (defun tutorial--detailed-help (button) From 4dc7d6056f654dfce90ff815301ccf702f3b66f2 Mon Sep 17 00:00:00 2001 From: Spencer Baugh Date: Thu, 29 Jan 2026 11:58:51 +0000 Subject: [PATCH 34/74] Use frame-pixel-width/height to determine if frame is landscape * lisp/window.el (window--frame-landscape-p): New function. (split-window-sensibly): Call it (bug#80053). --- lisp/window.el | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lisp/window.el b/lisp/window.el index 7d866d6475d..b5feed0c30c 100644 --- a/lisp/window.el +++ b/lisp/window.el @@ -7584,6 +7584,16 @@ strategy." (with-selected-window window (split-window-right)))) +(defun window--frame-landscape-p (&optional frame) + "Non-nil if FRAME is wider than it is tall. + +On text frames, uses a heuristic for character height and width." + (if (display-graphic-p frame) + (> (frame-pixel-width frame) (frame-pixel-height frame)) + ;; On a terminal, displayed characters are usually roughly twice as + ;; tall as they are wide. + (> (frame-width frame) (* 2 (frame-height frame))))) + (defun split-window-sensibly (&optional window) "Split WINDOW in a way suitable for `display-buffer'. The variable `split-window-preferred-direction' prescribes an order of @@ -7624,7 +7634,7 @@ split." (or (if (or (eql split-window-preferred-direction 'horizontal) (and (eql split-window-preferred-direction 'longest) - (> (frame-width) (frame-height)))) + (window--frame-landscape-p (window-frame window)))) (or (window--try-horizontal-split window) (window--try-vertical-split window)) (or (window--try-vertical-split window) From 38d0ac8f6714d107044fb1e156cfaf49887ca094 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Thu, 29 Jan 2026 14:37:58 +0000 Subject: [PATCH 35/74] ; * lisp/window.el (window--frame-landscape-p): Improve docstring. --- lisp/window.el | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lisp/window.el b/lisp/window.el index b5feed0c30c..3a1ebd16fa6 100644 --- a/lisp/window.el +++ b/lisp/window.el @@ -7586,8 +7586,9 @@ strategy." (defun window--frame-landscape-p (&optional frame) "Non-nil if FRAME is wider than it is tall. - -On text frames, uses a heuristic for character height and width." +This means actually wider on the screen, not character-wise. +On text frames, use the heuristic that characters are roughtly twice as +tall as they are wide." (if (display-graphic-p frame) (> (frame-pixel-width frame) (frame-pixel-height frame)) ;; On a terminal, displayed characters are usually roughly twice as From 3937833fff0c31f9f6c71badcec2e5d43f7e5eba Mon Sep 17 00:00:00 2001 From: Dmitry Gutov Date: Thu, 29 Jan 2026 17:11:17 +0200 Subject: [PATCH 36/74] xref-find-backend: Error instead of returning nil * lisp/progmodes/xref.el (xref-find-backend): Signal error when we can't find a backend to use (bug#80246). --- lisp/progmodes/xref.el | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lisp/progmodes/xref.el b/lisp/progmodes/xref.el index d2dd4167725..84a3fa4dfba 100644 --- a/lisp/progmodes/xref.el +++ b/lisp/progmodes/xref.el @@ -247,7 +247,9 @@ generic functions.") ;;;###autoload (defun xref-find-backend () - (run-hook-with-args-until-success 'xref-backend-functions)) + (or + (run-hook-with-args-until-success 'xref-backend-functions) + (user-error "No Xref backend available"))) (cl-defgeneric xref-backend-definitions (backend identifier) "Find definitions of IDENTIFIER. From 0ab5db015f7eed3ffdf85ccbb75ff48e2c44b5a1 Mon Sep 17 00:00:00 2001 From: Spencer Baugh Date: Thu, 29 Jan 2026 16:45:15 +0000 Subject: [PATCH 37/74] (minibuffer-message): Do not block while displaying message. * lisp/minibuffer.el (minibuffer--message-overlay) (minibuffer--message-timer): New variables. (minibuffer--delete-message-overlay): New function. (minibuffer-message): Use a timer and 'pre-command-hook' to clear message overlay instead of blocking with 'sit-for'. (bug#79510) * etc/NEWS: Document the change. --- etc/NEWS | 7 ++++++ lisp/minibuffer.el | 63 ++++++++++++++++++++++++---------------------- 2 files changed, 40 insertions(+), 30 deletions(-) diff --git a/etc/NEWS b/etc/NEWS index 65c8c62dec5..4af778c990c 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -3918,6 +3918,13 @@ Binding 'inhibit-message' to a non-nil value will now suppress both the display of messages and the clearing of the echo area, such as caused by calling 'message' with a nil argument. +--- +** 'minibuffer-message' no longer blocks while displaying message +'minibuffer-message' now uses a timer to clear the message printed to +the minibuffer, instead of waiting with 'sit-for' and then clearing it. +This makes 'minibuffer-message' usable in Lisp programs which want to +print a message and then continue to perform work. + ** Special Events +++ diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el index 12827cacfe2..0904a592eb4 100644 --- a/lisp/minibuffer.el +++ b/lisp/minibuffer.el @@ -797,6 +797,19 @@ for use at QPOS." (defvar minibuffer-message-properties nil "Text properties added to the text shown by `minibuffer-message'.") +(defvar minibuffer--message-overlay nil) + +(defvar minibuffer--message-timer nil) + +(defun minibuffer--delete-message-overlay () + (when (overlayp minibuffer--message-overlay) + (delete-overlay minibuffer--message-overlay) + (setq minibuffer--message-overlay nil)) + (when (timerp minibuffer--message-timer) + (cancel-timer minibuffer--message-timer) + (setq minibuffer--message-timer nil)) + (remove-hook 'pre-command-hook #'minibuffer--delete-message-overlay)) + (defun minibuffer-message (message &rest args) "Temporarily display MESSAGE at the end of minibuffer text. This function is designed to be called from the minibuffer, i.e., @@ -814,13 +827,9 @@ through `format-message'. If some of the minibuffer text has the `minibuffer-message' text property, MESSAGE is shown at that position instead of EOB." (if (not (minibufferp (current-buffer) t)) - (progn - (if args - (apply #'message message args) - (message "%s" message)) - (prog1 (sit-for (or minibuffer-message-timeout 1000000)) - (message nil))) + (apply #'message message args) ;; Clear out any old echo-area message to make way for our new thing. + (minibuffer--delete-message-overlay) (message nil) (setq message (if (and (null args) (string-match-p "\\` *\\[.+\\]\\'" message)) @@ -834,30 +843,24 @@ property, MESSAGE is shown at that position instead of EOB." (setq message (apply #'propertize message minibuffer-message-properties))) ;; Put overlay either on `minibuffer-message' property, or at EOB. (let* ((ovpos (minibuffer--message-overlay-pos)) - (ol (make-overlay ovpos ovpos nil t t)) - ;; A quit during sit-for normally only interrupts the sit-for, - ;; but since minibuffer-message is used at the end of a command, - ;; at a time when the command has virtually finished already, a C-g - ;; should really cause an abort-recursive-edit instead (i.e. as if - ;; the C-g had been typed at top-level). Binding inhibit-quit here - ;; is an attempt to get that behavior. - (inhibit-quit t)) - (unwind-protect - (progn - (unless (zerop (length message)) - ;; The current C cursor code doesn't know to use the overlay's - ;; marker's stickiness to figure out whether to place the cursor - ;; before or after the string, so let's spoon-feed it the pos. - (put-text-property 0 1 'cursor t message)) - (overlay-put ol 'after-string message) - ;; Make sure the overlay with the message is displayed before - ;; any other overlays in that position, in case they have - ;; resize-mini-windows set to nil and the other overlay strings - ;; are too long for the mini-window width. This makes sure the - ;; temporary message will always be visible. - (overlay-put ol 'priority 1100) - (sit-for (or minibuffer-message-timeout 1000000))) - (delete-overlay ol))))) + (ol (make-overlay ovpos ovpos nil t t))) + (unless (zerop (length message)) + ;; The current C cursor code doesn't know to use the overlay's + ;; marker's stickiness to figure out whether to place the cursor + ;; before or after the string, so let's spoon-feed it the pos. + (put-text-property 0 1 'cursor t message)) + (overlay-put ol 'after-string message) + ;; Make sure the overlay with the message is displayed before + ;; any other overlays in that position, in case they have + ;; resize-mini-windows set to nil and the other overlay strings + ;; are too long for the mini-window width. This makes sure the + ;; temporary message will always be visible. + (overlay-put ol 'priority 1100) + (setq minibuffer--message-overlay ol + minibuffer--message-timer + (run-at-time (or minibuffer-message-timeout 1000000) nil + #'minibuffer--delete-message-overlay)) + (add-hook 'pre-command-hook #'minibuffer--delete-message-overlay)))) (defcustom minibuffer-message-clear-timeout nil "How long to display an echo-area message when the minibuffer is active. From 60b9435ad7192d9b3759777cd987c301a98796c1 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Thu, 29 Jan 2026 17:00:46 +0000 Subject: [PATCH 38/74] ; Fix/improve two comments. --- lisp/vc/diff-mode.el | 2 +- lisp/vc/vc.el | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el index 5286a079b4c..5c0fb5fba4c 100644 --- a/lisp/vc/diff-mode.el +++ b/lisp/vc/diff-mode.el @@ -2359,7 +2359,7 @@ applied. Other non-nil values are reserved." (while (pcase-let ((`(,buf ,line-offset ,pos ,_src ,dst ,switched) (diff-find-source-location nil reverse test))) ;; FIXME: Should respect `diff-apply-hunk-to-backup-file' - ;; similarly to how `diff-apply-buffer' does. + ;; similarly to how `diff-apply-hunk' does. ;; Prompt for each relevant file. (cond ((and line-offset (not switched)) (push (cons pos dst) diff --git a/lisp/vc/vc.el b/lisp/vc/vc.el index 0ce4ce56363..cc3aa2d7f01 100644 --- a/lisp/vc/vc.el +++ b/lisp/vc/vc.el @@ -4342,11 +4342,19 @@ BACKEND is the VC backend." (let* ((outgoing-base (vc-call-backend (or backend (vc-deduce-backend)) 'topic-outgoing-base)) - ;; If OUTGOING-BASE is non-nil then it isn't possible to - ;; specify an empty string in response to the prompt, which - ;; normally means to treat the current branch as a trunk. - ;; That's okay because you can use a double prefix argument - ;; to force treating the current branch as a trunk. + ;; If OUTGOING-BASE is non-nil then 'C-u C-x v T ... RET' is + ;; how the user can force Emacs to treat the current branch + ;; as a topic while having Emacs automatically determine the + ;; outgoing base with which to do so (otherwise, forcing + ;; Emacs to treat the current branch as a topic if it thinks + ;; it's a trunk requires specifying an outgoing base which + ;; will have that effect). + ;; + ;; In this case that OUTGOING-BASE is non-nil, it isn't + ;; possible to specify an empty string as the outgoing base, + ;; which normally means that Emacs should treat the current + ;; branch as a trunk. That's okay because you can use a + ;; double prefix argument to achieve that. (res (read-string (if outgoing-base (format-prompt "Upstream location/branch" outgoing-base) From 12e53dfafe097d053e78f83788a5c44320a3d370 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Thu, 29 Jan 2026 17:01:32 +0000 Subject: [PATCH 39/74] New C-x v T l and C-x v T L commands * lisp/vc/vc.el (vc-log-outgoing-base) (vc-root-log-outgoing-base): New commands. * lisp/vc/vc-dir.el (vc-dir-mode-map): * lisp/vc/vc-hooks.el (vc-prefix-map): Bind them. * doc/emacs/vc1-xtra.texi (Outstanding Changes): * etc/NEWS: Document them. --- doc/emacs/vc1-xtra.texi | 43 +++++++++++++++++++++++-------- etc/NEWS | 10 +++++--- lisp/vc/vc-dir.el | 2 ++ lisp/vc/vc-hooks.el | 2 ++ lisp/vc/vc.el | 57 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 99 insertions(+), 15 deletions(-) diff --git a/doc/emacs/vc1-xtra.texi b/doc/emacs/vc1-xtra.texi index 655402b61ba..2bb695025db 100644 --- a/doc/emacs/vc1-xtra.texi +++ b/doc/emacs/vc1-xtra.texi @@ -303,17 +303,26 @@ Display diffs of changes to the VC fileset since the merge base of this branch and its upstream counterpart (@code{vc-diff-outgoing-base}). @item C-x v T D -Display all changes since the merge base of this branch and its upstream -counterpart (@code{vc-root-diff-outgoing-base}). +Display a diff of all changes since the merge base of this branch and +its upstream counterpart (@code{vc-root-diff-outgoing-base}). + +@item C-x v T l +Display log messages for changes to the VC fileset since the merge base +of this branch and its upstream counterpart +(@code{vc-log-outgoing-base}). + +@item C-x v T L +Display log messages for all changes since the merge base of this branch +and its upstream counterpart (@code{vc-root-log-outgoing-base}). @end table For decentralized version control systems (@pxref{VCS Repositories}), -these commands provide specialized versions of @kbd{C-x v M D} (see -@pxref{Merge Bases}) which also take into account the state of upstream -repositories. These commands are useful both when working on a single -branch and when developing features on a separate branch -(@pxref{Branches}). These two cases are conceptually distinct, and so -we will introduce them separately. +these commands provide specialized versions of @kbd{C-x v M L} and +@w{@kbd{C-x v M D}} (see @pxref{Merge Bases}) which also take into +account the state of upstream repositories. These commands are useful +both when working on a single branch and when developing features on a +separate branch (@pxref{Branches}). These two cases are conceptually +distinct, and so we will introduce them separately. First, consider working on a single branch. @dfn{Outstanding changes} are those which you haven't yet pushed upstream. This includes both @@ -340,6 +349,16 @@ include uncommitted changes in the reported diffs. Like those other commands, you can use a prefix argument to specify a particular upstream location.} +@kindex C-x v T l +@findex vc-log-outgoing-base +@kindex C-x v T L +@findex vc-root-log-outgoing-base +Type @kbd{C-x v T L} (@code{vc-root-log-outgoing-base}) to display a +summary of the same changes in the form of a revision log; this does not +include uncommitted changes. You can use @kbd{C-x v T l} +(@code{vc-log-outgoing-base}) instead to limit the display of changes to +the current VC fileset. + Second, consider developing a feature on a separate branch. Call this the @dfn{topic branch},@footnote{What we mean by a topic branch is any shorter-lived branch used for work which will later be merged into a @@ -365,7 +384,9 @@ the trunk to which the current branch will be merged. This summary is in the form of a diff of what committing and pushing all the changes, @emph{and} subsequently merging the topic branch, would do to the trunk. As above, you can use @kbd{C-x v T =} instead to limit the display of -changes to the current VC fileset. +changes to the current VC fileset. @kbd{C-x v T L} and @kbd{C-x v T l} +show the corresponding revision logs, excluding uncommitted changes as +above. This functionality relies on Emacs correctly detecting whether the current branch is a trunk or a topic branch, and in the latter case, @@ -379,8 +400,8 @@ The variables @code{vc-trunk-branch-regexps} and @code{vc-topic-branch-regexps} contain lists of regular expressions matching the names of branches that should always be considered trunk and topic branches, respectively. You can also specify prefix arguments -to @kbd{C-x v T D} and @kbd{C-x v T =}. Here is a summary of how to use -these controls: +to @kbd{C-x v T @dots{}}. Here is a summary of how to use these +controls: @enumerate @item diff --git a/etc/NEWS b/etc/NEWS index 4af778c990c..99fc1754bad 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -2745,13 +2745,15 @@ include were committed and will be pushed. current VC fileset. +++ -*** New commands to report diffs of outstanding changes. +*** New commands to report information about outstanding changes. 'C-x v T =' ('vc-diff-outgoing-base') and 'C-x v T D' ('vc-root-diff-outgoing-base') report diffs of changes since the merge base with the remote branch, including uncommitted changes. -They are useful to view all outstanding (unmerged, unpushed) changes on -the current branch. -They are also available as 'T =' and 'T D' in VC-Dir buffers. +'C-x v T l' ('vc-log-outgoing-base') and 'C-x v T L' +('vc-root-log-outgoing-base') show the corresponding revision logs. +These are useful to view all outstanding (unmerged, unpushed) changes on +the current branch. They are also available as 'T =', 'T D', 'T l' and +'T L' in VC-Dir buffers. +++ *** New user option 'vc-use-incoming-outgoing-prefixes'. diff --git a/lisp/vc/vc-dir.el b/lisp/vc/vc-dir.el index b9176d8a2f6..5781ddc45d9 100644 --- a/lisp/vc/vc-dir.el +++ b/lisp/vc/vc-dir.el @@ -397,6 +397,8 @@ That is, refreshing the VC-Dir buffer also hides `up-to-date' and (define-key map (kbd "M-s a M-C-s") #'vc-dir-isearch-regexp) (define-key map "G" #'vc-dir-ignore) (define-key map "@" #'vc-revert) + (define-key map "Tl" #'vc-log-outgoing-base) + (define-key map "TL" #'vc-root-log-outgoing-base) (define-key map "T=" #'vc-diff-outgoing-base) (define-key map "TD" #'vc-root-diff-outgoing-base) diff --git a/lisp/vc/vc-hooks.el b/lisp/vc/vc-hooks.el index a6e07e02de9..f3519465c07 100644 --- a/lisp/vc/vc-hooks.el +++ b/lisp/vc/vc-hooks.el @@ -1018,6 +1018,8 @@ In the latter case, VC mode is deactivated for this buffer." "O" #'vc-root-log-outgoing "M L" #'vc-log-mergebase "M D" #'vc-diff-mergebase + "T l" #'vc-log-outgoing-base + "T L" #'vc-root-log-outgoing-base "T =" #'vc-diff-outgoing-base "T D" #'vc-root-diff-outgoing-base "m" #'vc-merge diff --git a/lisp/vc/vc.el b/lisp/vc/vc.el index cc3aa2d7f01..770906ff6cc 100644 --- a/lisp/vc/vc.el +++ b/lisp/vc/vc.el @@ -3403,6 +3403,63 @@ When called from Lisp, optional argument FILESET overrides the fileset." nil (called-interactively-p 'interactive)))) +;;;###autoload +(defun vc-log-outgoing-base (&optional upstream-location fileset) + "Show log for the VC fileset since the merge base with UPSTREAM-LOCATION. +The merge base with UPSTREAM-LOCATION means the common ancestor of the +working revision and UPSTREAM-LOCATION. + +When unspecified, UPSTREAM-LOCATION is the outgoing base. +For a trunk branch this is always the place \\[vc-push] would push to. +For a topic branch, query the backend for an appropriate outgoing base. +See `vc-trunk-branch-regexps' and `vc-topic-branch-regexps' regarding +the difference between trunk and topic branches. + +When called interactively with a prefix argument, prompt for +UPSTREAM-LOCATION. In some version control systems, UPSTREAM-LOCATION +can be a remote branch name. + +When called interactively with a \\[universal-argument] \\[universal-argument] \ +prefix argument, always +use the place to which \\[vc-push] would push to as the outgoing base, +i.e., treat this branch as a trunk branch even if Emacs thinks it is a +topic branch. + +When called from Lisp, optional argument FILESET overrides the fileset." + (interactive (let ((fileset (vc-deduce-fileset t))) + (list (vc--maybe-read-outgoing-base (car fileset)) + fileset))) + (let* ((fileset (or fileset (vc-deduce-fileset t))) + (backend (car fileset))) + (vc-print-log-internal backend (cadr fileset) nil nil + (vc--outgoing-base-mergebase backend + upstream-location)))) + +;;;###autoload +(defun vc-root-log-outgoing-base (&optional upstream-location) + "Show log of revisions since the merge base with UPSTREAM-LOCATION. +The merge base with UPSTREAM-LOCATION means the common ancestor of the +working revision and UPSTREAM-LOCATION. + +When unspecified, UPSTREAM-LOCATION is the outgoing base. +For a trunk branch this is always the place \\[vc-push] would push to. +For a topic branch, query the backend for an appropriate outgoing base. +See `vc-trunk-branch-regexps' and `vc-topic-branch-regexps' regarding +the difference between trunk and topic branches. + +When called interactively with a prefix argument, prompt for +UPSTREAM-LOCATION. In some version control systems, UPSTREAM-LOCATION +can be a remote branch name. + +When called interactively with a \\[universal-argument] \\[universal-argument] \ +prefix argument, always +use the place to which \\[vc-push] would push to as the outgoing base, +i.e., treat this branch as a trunk branch even if Emacs thinks it is a +topic branch." + (interactive (list (vc--maybe-read-outgoing-base))) + (vc--with-backend-in-rootdir "VC revision log" + (vc-log-outgoing-base upstream-location `(,backend (,rootdir))))) + (declare-function ediff-load-version-control "ediff" (&optional silent)) (declare-function ediff-vc-internal "ediff-vers" (rev1 rev2 &optional startup-hooks)) From 88d787d97c9f1b932fb38aab031c35adea1076e8 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Thu, 29 Jan 2026 17:03:58 +0000 Subject: [PATCH 40/74] ; * lisp/ldefs-boot.el: Regenerate. --- lisp/ldefs-boot.el | 104 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 88 insertions(+), 16 deletions(-) diff --git a/lisp/ldefs-boot.el b/lisp/ldefs-boot.el index e20d8609e88..c5fbb5a3e22 100644 --- a/lisp/ldefs-boot.el +++ b/lisp/ldefs-boot.el @@ -2699,10 +2699,10 @@ from `browse-url-elinks-wrapper'. (fn URL &optional NEW-WINDOW)" t) (autoload 'browse-url-button-open "browse-url" "\ Follow the link under point using `browse-url'. -If EXTERNAL (the prefix if used interactively), open with the -external browser instead of the default one. +If SECONDARY (the prefix if used interactively), open with the +secondary browser instead of the default one. -(fn &optional EXTERNAL MOUSE-EVENT)" t) +(fn &optional SECONDARY MOUSE-EVENT)" t) (autoload 'browse-url-button-open-url "browse-url" "\ Open URL using `browse-url'. If `current-prefix-arg' is non-nil, use @@ -5133,7 +5133,7 @@ List of directories to search for source files named in error messages. Elements should be directory names, not file names of directories. The value nil as an element means to try the default directory.") (custom-autoload 'compilation-search-path "compile" t) -(defvar compile-command "make -k " "\ +(defvar compile-command (format "make -k -j%d " (ceiling (num-processors) 1.5)) "\ Last shell command used to do a compilation; default for next compilation. Sometimes it is useful for files to supply local values for this variable. @@ -13691,8 +13691,6 @@ evaluate the variable `flymake-mode'. The mode's hook is called both when the mode is enabled and when it is disabled. -\\{flymake-mode-map} - (fn &optional ARG)" t) (autoload 'flymake-mode-on "flymake" "\ Turn Flymake mode on.") @@ -24735,6 +24733,21 @@ If optional argument NOCONFIRM is non-nil, or when invoked with a prefix argument, don't ask for confirmation to install packages. (fn &optional NOCONFIRM)" t) +(autoload 'package-delete "package" "\ +Delete package PKG-DESC. + +Argument PKG-DESC is the full description of the package, for example as +obtained by `package-get-descriptor'. Interactively, prompt the user +for the package name and version. + +When package is used elsewhere as dependency of another package, +refuse deleting it and return an error. +If prefix argument FORCE is non-nil, package will be deleted even +if it is used elsewhere. +If NOSAVE is non-nil, the package is not removed from +`package-selected-packages'. + +(fn PKG-DESC &optional FORCE NOSAVE)" t) (autoload 'package-reinstall "package" "\ Reinstall package PKG. PKG should be either a symbol, the package name, or a `package-desc' @@ -24779,11 +24792,18 @@ short description. (defcustom package-quickstart-file (locate-user-emacs-file "package-quickstart.el") "\ Location of the file used to speed up activation of packages at startup." :type 'file :group 'applications :initialize #'custom-initialize-delay :version "27.1") (custom-autoload 'package-quickstart-file "package" t) +(autoload 'package-browse-url "package" "\ +Open the website of the package under point in a browser. +`browse-url' is used to determine the browser to be used. If +SECONDARY (interactively, the prefix), use the secondary browser. +DESC must be a `package-desc' object. + +(fn DESC &optional SECONDARY)" t) (autoload 'package-report-bug "package" "\ Prepare a message to send to the maintainers of a package. DESC must be a `package-desc' object. -(fn DESC)" '(package-menu-mode)) +(fn DESC)" t) (register-definition-prefixes "package" '("bad-signature" "define-package" "describe-package-1" "package-")) @@ -25815,6 +25835,10 @@ Note that this function doesn't work if DELTA is larger than the height of the current window. (fn DELTA)") +(autoload 'pixel-scroll-interpolate-down "pixel-scroll" "\ +Interpolate a scroll downwards by one page." t) +(autoload 'pixel-scroll-interpolate-up "pixel-scroll" "\ +Interpolate a scroll upwards by one page." t) (defvar pixel-scroll-precision-mode nil "\ Non-nil if Pixel-Scroll-Precision mode is enabled. See the `pixel-scroll-precision-mode' command @@ -27749,8 +27773,6 @@ evaluate the variable `rectangle-mark-mode'. The mode's hook is called both when the mode is enabled and when it is disabled. -\\{rectangle-mark-mode-map} - (fn &optional ARG)" t) (register-definition-prefixes "rect" '("apply-on-rectangle" "clear-rectangle-line" "delete-" "extract-rectangle-" "killed-rectangle" "ope" "rectangle-" "spaces-string" "string-rectangle-")) @@ -36590,6 +36612,50 @@ topic branch. (With a double prefix argument, this command is like When called from Lisp, optional argument FILESET overrides the fileset. (fn &optional UPSTREAM-LOCATION FILESET)" t) +(autoload 'vc-log-outgoing-base "vc" "\ +Show log for the VC fileset since the merge base with UPSTREAM-LOCATION. +The merge base with UPSTREAM-LOCATION means the common ancestor of the +working revision and UPSTREAM-LOCATION. + +When unspecified, UPSTREAM-LOCATION is the outgoing base. +For a trunk branch this is always the place \\[vc-push] would push to. +For a topic branch, query the backend for an appropriate outgoing base. +See `vc-trunk-branch-regexps' and `vc-topic-branch-regexps' regarding +the difference between trunk and topic branches. + +When called interactively with a prefix argument, prompt for +UPSTREAM-LOCATION. In some version control systems, UPSTREAM-LOCATION +can be a remote branch name. + +When called interactively with a \\[universal-argument] \\[universal-argument] prefix argument, always +use the place to which \\[vc-push] would push to as the outgoing base, +i.e., treat this branch as a trunk branch even if Emacs thinks it is a +topic branch. + +When called from Lisp, optional argument FILESET overrides the fileset. + +(fn &optional UPSTREAM-LOCATION FILESET)" t) +(autoload 'vc-root-log-outgoing-base "vc" "\ +Show log of revisions since the merge base with UPSTREAM-LOCATION. +The merge base with UPSTREAM-LOCATION means the common ancestor of the +working revision and UPSTREAM-LOCATION. + +When unspecified, UPSTREAM-LOCATION is the outgoing base. +For a trunk branch this is always the place \\[vc-push] would push to. +For a topic branch, query the backend for an appropriate outgoing base. +See `vc-trunk-branch-regexps' and `vc-topic-branch-regexps' regarding +the difference between trunk and topic branches. + +When called interactively with a prefix argument, prompt for +UPSTREAM-LOCATION. In some version control systems, UPSTREAM-LOCATION +can be a remote branch name. + +When called interactively with a \\[universal-argument] \\[universal-argument] prefix argument, always +use the place to which \\[vc-push] would push to as the outgoing base, +i.e., treat this branch as a trunk branch even if Emacs thinks it is a +topic branch. + +(fn &optional UPSTREAM-LOCATION)" t) (autoload 'vc-version-ediff "vc" "\ Show differences between REV1 and REV2 of FILES using ediff. This compares two revisions of the files in FILES. Currently, @@ -37320,7 +37386,7 @@ step during initialization." t) ;;; Generated autoloads from progmodes/verilog-mode.el -(push '(verilog-mode 2025 11 8 248496848) package--builtin-versions) +(push '(verilog-mode 2026 1 18 88738971) package--builtin-versions) (autoload 'verilog-mode "verilog-mode" "\ Major mode for editing Verilog code. \\ @@ -39641,6 +39707,14 @@ list. Delete FRAME2 if the merge completed successfully and return FRAME1. (fn &optional FRAME1 FRAME2 VERTICAL)" t) +(autoload 'window-get-split-combination "window-x" "\ +Return window combination suitable for `split-frame'. + +WINDOW is the main window in which the combination should be derived. +ARG is the argument passed to `split-frame'. Return a +combination of windows `split-frame' is considered to split off. + +(fn WINDOW ARG)") (autoload 'split-frame "window-x" "\ Split windows of specified FRAME into two separate frames. FRAME must be a live frame and defaults to the selected frame. ARG @@ -39975,12 +40049,9 @@ output of this command when the backend is etags. (define-key ctl-x-5-map "." #'xref-find-definitions-other-frame) (autoload 'xref-references-in-directory "xref" "\ Find all references to SYMBOL in directory DIR. +See `xref-references-in-directory-function' for the implementation. Return a list of xref values. -This function uses the Semantic Symbol Reference API, see -`semantic-symref-tool-alist' for details on which tools are used, -and when. - (fn SYMBOL DIR)") (autoload 'xref-matches-in-directory "xref" "\ Find all matches for REGEXP in directory DIR. @@ -39988,8 +40059,9 @@ Return a list of xref values. Only files matching some of FILES and none of IGNORES are searched. FILES is a string with glob patterns separated by spaces. IGNORES is a list of glob patterns for files to ignore. +If DELIMITED is `symbol', only select matches that span full symbols. -(fn REGEXP FILES DIR IGNORES)") +(fn REGEXP FILES DIR IGNORES &optional DELIMITED)") (autoload 'xref-matches-in-files "xref" "\ Find all matches for REGEXP in FILES. Return a list of xref values. @@ -40087,7 +40159,7 @@ Enable `yaml-ts-mode' when its grammar is available. Also propose to install the grammar when `treesit-enabled-modes' is t or contains the mode name.") (when (boundp 'treesit-major-mode-remap-alist) (add-to-list 'auto-mode-alist '("\\.ya?ml\\'" . yaml-ts-mode-maybe)) (add-to-list 'treesit-major-mode-remap-alist '(yaml-mode . yaml-ts-mode))) -(register-definition-prefixes "yaml-ts-mode" '("yaml-ts-mode--")) +(register-definition-prefixes "yaml-ts-mode" '("yaml-ts-mode-")) ;;; Generated autoloads from yank-media.el From b9bfe461b250728fb1f6d5df0ce657bffd873e59 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Thu, 29 Jan 2026 17:08:55 +0000 Subject: [PATCH 41/74] ; Fix minibuffer-message NEWS entry. --- etc/NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/NEWS b/etc/NEWS index 99fc1754bad..87023ade5fd 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -3921,7 +3921,7 @@ the display of messages and the clearing of the echo area, such as caused by calling 'message' with a nil argument. --- -** 'minibuffer-message' no longer blocks while displaying message +** 'minibuffer-message' no longer blocks while displaying message. 'minibuffer-message' now uses a timer to clear the message printed to the minibuffer, instead of waiting with 'sit-for' and then clearing it. This makes 'minibuffer-message' usable in Lisp programs which want to From eca025334ed607656bb6ce5a14acd596270dabc1 Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Thu, 29 Jan 2026 22:13:02 +0200 Subject: [PATCH 42/74] ; Fix last change * etc/NEWS: * doc/lispref/windows.texi (Choosing Window Options): * doc/emacs/windows.texi (Window Choice): Improve documentation of 'split-window-preferred-direction'. --- doc/emacs/windows.texi | 12 ++++++++++-- doc/lispref/windows.texi | 35 +++++++++++++++++++++++++---------- etc/NEWS | 19 ++++++++++++++----- 3 files changed, 49 insertions(+), 17 deletions(-) diff --git a/doc/emacs/windows.texi b/doc/emacs/windows.texi index 937ea386650..8500e3b7731 100644 --- a/doc/emacs/windows.texi +++ b/doc/emacs/windows.texi @@ -519,6 +519,8 @@ selected frame, and display the buffer in that new window. @vindex split-height-threshold @vindex split-width-threshold @vindex split-window-preferred-direction +@cindex portrait frame +@cindex landscape frame The split can be either vertical or horizontal, depending on the variables @code{split-height-threshold} and @code{split-width-threshold}. These variables should have integer @@ -528,8 +530,14 @@ window's height, the split puts the new window below. Otherwise, if split puts the new window on the right. If neither condition holds, Emacs tries to split so that the new window is below---but only if the window was not split before (to avoid excessive splitting). Whether -Emacs tries first to split vertically or horizontally, is -determined by the value of @code{split-window-preferred-direction}. +Emacs tries first to split vertically or horizontally when both +conditions hold is determined by the value of +@code{split-window-preferred-direction}. Its default is @code{longest}, +which means to split vertically if the window's frame is taller than it +is wide (a @dfn{portrait} frame), and split horizontally if its wider +than it's tall (a @dfn{landscape} frame). The values @code{vertical} +and @code{horizontal} always prefer, respectively, the vertical or the +horizontal split. @item Otherwise, display the buffer in a window previously showing it. diff --git a/doc/lispref/windows.texi b/doc/lispref/windows.texi index 169f15cc898..d804c34250f 100644 --- a/doc/lispref/windows.texi +++ b/doc/lispref/windows.texi @@ -4122,16 +4122,19 @@ window. If @var{window} cannot be split, it returns @code{nil}. If @var{window} is omitted or @code{nil}, it defaults to the selected window. -This function obeys the usual rules that determine when a window may -be split (@pxref{Splitting Windows}). It first tries to split by -placing the new window below, subject to the restriction imposed by -@code{split-height-threshold} (see below), in addition to any other -restrictions. If that fails, it tries to split by placing the new -window to the right, subject to @code{split-width-threshold} (see -below). If that also fails, and the window is the only window on its -frame, this function again tries to split and place the new window -below, disregarding @code{split-height-threshold}. If this fails as -well, this function gives up and returns @code{nil}. +This function obeys the usual rules that determine when a window may be +split (@pxref{Splitting Windows}). It first tries either a vertical +split by placing the new window below, subject to the restriction +imposed by @code{split-height-threshold} (see below), or a horizontal +split that places the new window to the right, subject to +@code{split-width-threshold}, in addition to any other restrictions. +Whether it tries first to split vertically or horizontally depends on +the value of the user option @code{split-window-preferred-direction}. +If splitting along the first dimension fails, it tries to split along +the other dimension. If that also fails, and the window is the only +window on its frame, this function again tries to split and place the +new window below, disregarding @code{split-height-threshold}. If this +fails as well, this function gives up and returns @code{nil}. @end defun @defopt split-height-threshold @@ -4150,6 +4153,18 @@ window has at least that many columns. If the value is @code{nil}, that means not to split this way. @end defopt +@defopt split-window-preferred-direction +This variable determines the first dimension along which +@code{split-window-sensibly} tries to split the window, if the window +could be split both vertically and horizontally, as determined by the +values of @code{split-height-threshold} and +@code{split-width-threshold}. The default value is @code{longest}, +which means to split vertically if the height of the window's frame is +greater or equal to its width, and horizontally otherwise. The values +@code{vertical} and @code{horizontal} specify the direction in which to +attempt the first split. +@end defopt + @defopt even-window-sizes This variable, if non-@code{nil}, causes @code{display-buffer} to even window sizes whenever it reuses an existing window, and that window is diff --git a/etc/NEWS b/etc/NEWS index 87023ade5fd..1ed41795fe3 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -416,11 +416,20 @@ for which you can use '(category . tex-shell)'. +++ *** New user option 'split-window-preferred-direction'. -Users can now choose in which direction Emacs tries to split first: -vertically or horizontally. The new default is to prefer to split -horizontally if the frame is landscape and vertically if it is portrait. -You can customize this option to 'vertical' to restore Emacs's old -behavior of always preferring vertical splits. +Functions called by 'display-buffer' split the selected window when they +need to create a new window. A window can be split either vertically, +one below the other, or horizontally, side by side. This new option +determines which direction will be tried first, when both directions are +possible according to the values of 'split-width-threshold' and +'split-height-threshold'. The default value is 'longest', which means +to prefer to split horizontally if the window's frame is a "landscape" +frame, and vertically if it is a "portrait" frame. (A frame is +considered to be "portrait" if its vertical dimension in pixels is +greater or equal to its horizontal dimension, otherwise it's considered +to be "landscape".) Previous versions of Emacs always tried to split +vertically first, so to get previous behavior, you can customize this +option to 'vertical'. The value 'horizontal' always prefers the +horizontal split. +++ *** New argument INDIRECT for 'get-buffer-window-list'. From 31944efb826ea02ea7197012f84b00f903df66bb Mon Sep 17 00:00:00 2001 From: Spencer Baugh Date: Mon, 22 Dec 2025 16:25:26 -0500 Subject: [PATCH 43/74] eager-display *Completions* again after completion failure If the completion table requests eager-update (so *Completions* should be updated as the user types, when already displayed) then *Completions* will be dismissed automatically if the user types something which isn't a completion. Previously, *Completions* wouldn't be redisplayed until the user requests it again. Now, if the completion table also enables eager-display in addition to eager-update, then automatically redisplay *Completions* after it disappears. * lisp/minibuffer.el (completions--start-eager-display): Add REQUIRE-EAGER-UPDATE argument and don't run if Completions is already displayed. (completions--after-change): Call 'completions--start-eager-display'. (minibuffer-completion-help): Add the 'completions--after-change' hook earlier, and let it remove itself (bug#80055). --- lisp/minibuffer.el | 41 ++++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el index 0904a592eb4..fc193fe54f0 100644 --- a/lisp/minibuffer.el +++ b/lisp/minibuffer.el @@ -2777,18 +2777,27 @@ so that the update is less likely to interfere with user typing." ;; If we got interrupted, try again the next time the user is idle. (completions--start-eager-display)))) -(defun completions--start-eager-display () +(defun completions--start-eager-display (&optional require-eager-update) "Maybe display the *Completions* buffer when the user is next idle. Only displays if `completion-eager-display' is t, or if eager display -has been requested by the completion table." - (when completion-eager-display - (when (or (eq completion-eager-display t) - (completion-metadata-get - (completion-metadata - (buffer-substring-no-properties (minibuffer-prompt-end) (point)) - minibuffer-completion-table minibuffer-completion-predicate) - 'eager-display)) +has been requested by the completion table. + +When REQUIRE-EAGER-UPDATE is non-nil, also require eager-display to be +requested by the completion table." + (when (and completion-eager-display + ;; If it's already displayed, don't display it again. + (not (get-buffer-window "*Completions*" 0))) + (when (let ((metadata + (completion-metadata + (buffer-substring-no-properties (minibuffer-prompt-end) (point)) + minibuffer-completion-table minibuffer-completion-predicate))) + (and + (or (eq completion-eager-display t) + (completion-metadata-get metadata 'eager-display)) + (or (not require-eager-update) + (eq completion-eager-update t) + (completion-metadata-get metadata 'eager-update)))) (setq completion-eager-display--timer (run-with-idle-timer 0 nil #'completions--eager-display))))) @@ -2800,13 +2809,16 @@ has been requested by the completion table." (defun completions--after-change (_start _end _old-len) "Update displayed *Completions* buffer after change in buffer contents." - (when (or completion-auto-deselect completion-eager-update) - (when-let* ((window (minibuffer--completions-visible))) + (if (not (or (minibufferp nil t) completion-in-region-mode)) + (remove-hook 'after-change-functions #'completions--after-change t) + (when-let* ((window (get-buffer-window "*Completions*" 0))) (when completion-auto-deselect (with-selected-window window (completions--deselect))) (when completion-eager-update - (add-hook 'post-command-hook #'completions--post-command-update))))) + (add-hook 'post-command-hook #'completions--post-command-update))) + (when (minibufferp nil t) + (completions--start-eager-display t)))) (defun minibuffer-completion-help (&optional start end) "Display a list of possible completions of the current minibuffer contents." @@ -2824,6 +2836,8 @@ has been requested by the completion table." (- (point) start) md))) (message nil) + (when (or completion-auto-deselect completion-eager-update) + (add-hook 'after-change-functions #'completions--after-change nil t)) (if (or (null completions) (and (not (consp (cdr completions))) (equal (car completions) string))) @@ -2831,7 +2845,6 @@ has been requested by the completion table." ;; If there are no completions, or if the current input is already ;; the sole completion, then hide (previous&stale) completions. (minibuffer-hide-completions) - (remove-hook 'after-change-functions #'completions--after-change t) (if completions (completion--message "Sole completion") (unless completion-fail-discreetly @@ -2897,8 +2910,6 @@ has been requested by the completion table." (body-function . ,#'(lambda (window) (with-current-buffer mainbuf - (when (or completion-auto-deselect completion-eager-update) - (add-hook 'after-change-functions #'completions--after-change nil t)) ;; Remove the base-size tail because `sort' requires a properly ;; nil-terminated list. (when last (setcdr last nil)) From 3584a762b8cbfb6e13011827ec5934f039344d0f Mon Sep 17 00:00:00 2001 From: Juri Linkov Date: Fri, 30 Jan 2026 09:27:12 +0200 Subject: [PATCH 44/74] New key 'M-j' for 'icomplete-mode' (bug#62108) * lisp/icomplete.el (icomplete-exit): New alias for 'icomplete-fido-exit'. (icomplete-minibuffer-map): Bind it to "M-j" . * lisp/replace.el (multi-occur--prompt): Show "M-j" bound to 'icomplete-exit' in 'icomplete-mode'. --- etc/NEWS | 4 ++++ lisp/icomplete.el | 3 +++ lisp/replace.el | 3 +++ 3 files changed, 10 insertions(+) diff --git a/etc/NEWS b/etc/NEWS index 1ed41795fe3..1838a1ec3e5 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -3057,6 +3057,10 @@ Meant to be given a global binding convenient to the user. Example: ** Icomplete +*** New key 'M-j' for 'icomplete-mode' and 'icomplete-vertical-mode'. +Like 'M-j' in 'fido-mode', it can exit the minibuffer with a selected +candidate even when 'icomplete-show-matches-on-no-input' is non-nil. + *** New user options for 'icomplete-vertical-mode'. New user options have been added to enhance 'icomplete-vertical-mode': diff --git a/lisp/icomplete.el b/lisp/icomplete.el index 6de3dd0b50a..c1d9556e24d 100644 --- a/lisp/icomplete.el +++ b/lisp/icomplete.el @@ -242,6 +242,7 @@ Used to implement the option `icomplete-show-matches-on-no-input'.") :doc "Keymap used by `icomplete-mode' in the minibuffer." "C-M-i" #'icomplete-force-complete "C-j" #'icomplete-force-complete-and-exit + "M-j" #'icomplete-exit "C-." #'icomplete-forward-completions "C-," #'icomplete-backward-completions " " #'icomplete-ret) @@ -455,6 +456,8 @@ if that doesn't produce a completion match." (minibuffer-complete-and-exit) (exit-minibuffer))) +(defalias 'icomplete-exit #'icomplete-fido-exit) + (defun icomplete-fido-backward-updir () "Delete char before or go up directory, like `ido-mode'." (interactive) diff --git a/lisp/replace.el b/lisp/replace.el index 933249d824c..d8b27544128 100644 --- a/lisp/replace.el +++ b/lisp/replace.el @@ -1878,6 +1878,9 @@ is not modified." (bound-and-true-p ido-everywhere)) (substitute-command-keys "(\\\\[ido-select-text] to end): ")) + ((bound-and-true-p icomplete-mode) + (substitute-command-keys + "(\\\\[icomplete-exit] to end): ")) ((bound-and-true-p fido-mode) (substitute-command-keys "(\\\\[icomplete-fido-exit] to end): ")) From 1ff0c58fee23356b3a3ef1c7fee24e22fa020356 Mon Sep 17 00:00:00 2001 From: Juri Linkov Date: Fri, 30 Jan 2026 09:41:42 +0200 Subject: [PATCH 45/74] New function 'checkdoc-batch' (bug#80199) * lisp/emacs-lisp/checkdoc.el (checkdoc--batch-flag): New variable. (checkdoc-rogue-spaces, checkdoc-message-text): Use it along the check for interactive calls to be able to collect errors in the diagnostic buffer. (checkdoc-show-diagnostics): Don't show the diagnostic buffer when 'checkdoc--batch-flag' is non-nil. (checkdoc-batch): New function to check the buffer and print the content of the diagnostic buffer. --- etc/NEWS | 5 +++++ lisp/emacs-lisp/checkdoc.el | 29 +++++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/etc/NEWS b/etc/NEWS index 1838a1ec3e5..eca0c070783 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -2087,6 +2087,11 @@ for docstrings where symbols 'nil' and 't' are in quotes. In most cases, having it enabled leads to a large amount of false positives. +--- +*** New function 'checkdoc-batch'. +It checks the buffer in batch mode, prints all found errors +and signals the first found error. + *** New file-local variable 'lisp-indent-local-overrides'. This variable can be used to locally override the indent specification of symbols. diff --git a/lisp/emacs-lisp/checkdoc.el b/lisp/emacs-lisp/checkdoc.el index c9f9082a27a..fd226b89fda 100644 --- a/lisp/emacs-lisp/checkdoc.el +++ b/lisp/emacs-lisp/checkdoc.el @@ -381,6 +381,9 @@ large number of libraries means it is impractical to fix all of these warnings masse. In almost any other case, setting this to anything but t is likely to be counter-productive.") +(defvar checkdoc--batch-flag nil + "Non-nil in batch mode.") + (defun checkdoc-list-of-strings-p (obj) "Return t when OBJ is a list of strings." (declare (obsolete list-of-strings-p "29.1")) @@ -1063,12 +1066,13 @@ Optional argument INTERACT permits more interactive fixing." (e (checkdoc-rogue-space-check-engine nil nil interact)) (checkdoc-generate-compile-warnings-flag (or take-notes checkdoc-generate-compile-warnings-flag))) - (if (not (called-interactively-p 'interactive)) + (if (not (or (called-interactively-p 'interactive) checkdoc--batch-flag)) e (if e (message "%s" (checkdoc-error-text e)) (checkdoc-show-diagnostics) - (message "Space Check: done."))))) + (if (called-interactively-p 'interactive) + (message "Space Check: done.")))))) ;;;###autoload (defun checkdoc-message-text (&optional take-notes) @@ -1081,7 +1085,7 @@ Optional argument TAKE-NOTES causes all errors to be logged." (checkdoc-generate-compile-warnings-flag (or take-notes checkdoc-generate-compile-warnings-flag))) (setq e (checkdoc-message-text-search)) - (if (not (called-interactively-p 'interactive)) + (if (not (or (called-interactively-p 'interactive) checkdoc--batch-flag)) e (if e (user-error "%s" (checkdoc-error-text e)) @@ -2819,7 +2823,7 @@ function called to create the messages." (defun checkdoc-show-diagnostics () "Display the checkdoc diagnostic buffer in a temporary window." - (if checkdoc-pending-errors + (if (and checkdoc-pending-errors (not checkdoc--batch-flag)) (let* ((b (get-buffer checkdoc-diagnostic-buffer)) (win (if b (display-buffer b)))) (when win @@ -2832,6 +2836,23 @@ function called to create the messages." (setq checkdoc-pending-errors nil) nil))) + +;;;###autoload +(defun checkdoc-batch () + "Check current buffer in batch mode. +Report any errors and signal the first found error." + (when noninteractive + (let ((checkdoc-autofix-flag nil) + (checkdoc--batch-flag t)) + (checkdoc-current-buffer t) + (when checkdoc-pending-errors + (when-let* ((b (get-buffer checkdoc-diagnostic-buffer))) + (with-current-buffer b + (princ (buffer-string))) + (terpri)) + (checkdoc-current-buffer))))) + + (defun checkdoc-get-keywords () "Return a list of package keywords for the current file." (save-excursion From 077b33ef7d3e9d3841382ecf0d6d683d0f157100 Mon Sep 17 00:00:00 2001 From: Juri Linkov Date: Fri, 30 Jan 2026 09:53:30 +0200 Subject: [PATCH 46/74] * lisp/progmodes/eglot.el (eglot-server-programs): Use "elp" for erlang-mode. erlang_ls has been archived in favour of erlang-language-platform. Suggested by Alan & Kim Zimmerman (bug#79943). --- lisp/progmodes/eglot.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 80099a26ee8..251b4e58e38 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -308,7 +308,7 @@ automatically)." (racket-mode . ("racket" "-l" "racket-langserver")) ((latex-mode plain-tex-mode context-mode texinfo-mode bibtex-mode tex-mode) . ,(eglot-alternatives '("digestif" "texlab"))) - (erlang-mode . ("erlang_ls" "--transport" "stdio")) + (erlang-mode . ("elp" "server")) (wat-mode . ("wat_server")) ((yaml-ts-mode yaml-mode) . ("yaml-language-server" "--stdio")) ((toml-ts-mode conf-toml-mode) . ("tombi" "lsp")) From 705c0e3729bf53db9e84ae7c8b932ebc3b2da934 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Fri, 30 Jan 2026 11:21:27 +0000 Subject: [PATCH 47/74] Bind 's' to diff-split-hunk in read-only diff-mode * lisp/vc/diff-mode.el (diff-mode-shared-map): Bind 's' to 'diff-split-hunk'. * etc/NEWS: Document the change. --- etc/NEWS | 3 +++ lisp/vc/diff-mode.el | 1 + 2 files changed, 4 insertions(+) diff --git a/etc/NEWS b/etc/NEWS index eca0c070783..78d627068c4 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -2293,6 +2293,9 @@ region overlaps. Otherwise, they have their existing behavior. With a prefix argument, it now reverse-applies hunks. This matches the existing prefix argument to 'diff-apply-hunk'. +--- +*** 's' is now bound to 'diff-split-hunk' in read-only Diff mode buffers. + ** Ediff +++ diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el index 5c0fb5fba4c..f57f39de37c 100644 --- a/lisp/vc/diff-mode.el +++ b/lisp/vc/diff-mode.el @@ -218,6 +218,7 @@ See also `diff-mode-read-only-map'." "" #'diff-goto-source "o" #'diff-goto-source ; other-window " " #'undo-ignore-read-only + "s" #'diff-split-hunk ;; The foregoing commands don't affect buffers beyond this one. ;; The following command is the only one that has a single-letter From d0daaead22f37df587113281ebdfd0d4c94636cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20T=C3=A1vora?= Date: Fri, 30 Jan 2026 12:35:14 +0000 Subject: [PATCH 48/74] Eglot: recall diagnostics froms unopened files on session start This is exclusively for the benefit of rust-analyzer, which sends publishDiagnostics for all project files upfront, and never republishes them on 'didOpen'. See https://github.com/joaotavora/eglot/issues/1531. * lisp/progmodes/eglot.el (eglot--flymake-handle-push): Simplify. Don't flymake-list-only-diagnostics here. Save original diagnostic in flymake-list-only-diagnostics setting. (eglot--on-shutdown): Cleanup flymake-list-only-diagnostics. (eglot--flymake-report-push+pulled): Hack in data from flymake-list-only-diagnostics. --- lisp/progmodes/eglot.el | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 251b4e58e38..28ee14c67cb 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -1438,6 +1438,12 @@ PRESERVE-BUFFERS as in `eglot-shutdown', which see." (maphash (lambda (f s) (when (eq s server) (remhash f eglot--servers-by-xrefed-file))) eglot--servers-by-xrefed-file) + ;; Cleanup entries in 'flymake-list-only-diagnostics' + (setq flymake-list-only-diagnostics + (cl-delete-if + (lambda (x) (eq server + (get-text-property 0 'eglot--server (car x)))) + flymake-list-only-diagnostics)) (cond ((eglot--shutdown-requested server) t) ((not (eglot--inhibit-autoreconnect server)) @@ -3422,11 +3428,8 @@ object. The originator of this \"push\" is usually either regular (with-current-buffer buffer (if (and version (/= version eglot--docver)) (cl-return-from eglot--flymake-handle-push)) - (setq - ;; if no explicit version received, assume it's current. - version eglot--docver - flymake-list-only-diagnostics - (assoc-delete-all path flymake-list-only-diagnostics)) + ;; if no explicit version received, assume it's current. + (setq version eglot--docver) (funcall then diagnostics)) (cl-loop for diag-spec across diagnostics @@ -3437,12 +3440,13 @@ object. The originator of this \"push\" is usually either regular (flymake-make-diagnostic path (cons line char) nil (eglot--flymake-diag-type severity) - (list source code message)))) + (list source code message) + `((eglot-lsp-diag . ,diag-spec))))) into diags finally - (setq flymake-list-only-diagnostics - (assoc-delete-all path flymake-list-only-diagnostics)) - (push (cons path diags) flymake-list-only-diagnostics)))) + (setf (alist-get (propertize path 'eglot--server server) + flymake-list-only-diagnostics nil nil #'equal) + diags)))) (cl-defun eglot--flymake-pull (&aux (server (eglot--current-server-or-lose)) (origin (current-buffer))) @@ -3506,6 +3510,17 @@ MODE is like `eglot--flymake-report-1'." (pushed-outdated-p (and pushed-docver (< pushed-docver eglot--docver)))) "Push previously collected diagnostics to `eglot--flymake-report-fn'. If KEEP, knowingly push a dummy do-nothing update." + ;; Maybe hack in diagnostics we previously may have saved in + ;; `flymake-list-only-diagnostics', pushed for this file before it was + ;; visited (github#1531). + (when-let* ((hack (and (<= eglot--docver 0) + (null eglot--pushed-diagnostics) + (cdr (assoc (buffer-file-name) + flymake-list-only-diagnostics))))) + (cl-loop + for x in hack + collect (alist-get 'eglot-lsp-diag (flymake-diagnostic-data x)) into res + finally (setq eglot--pushed-diagnostics `(,(vconcat res) ,eglot--docver)))) (eglot--widening (if (and (null eglot--pulled-diagnostics) pushed-outdated-p) ;; Here, we don't have anything interesting to give to Flymake. From 93bba3797e4c9fc69b93ee4ab6c561d76199d1cf Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Fri, 30 Jan 2026 13:35:50 +0000 Subject: [PATCH 49/74] Factor out vc-git--branch-remotes * lisp/vc/vc-git.el (vc-git--branch-remotes): New function. (vc-git-trunk-or-topic-p): Use it. * test/lisp/vc/vc-git-tests.el (vc-git-test-branch-remotes): New test. --- lisp/vc/vc-git.el | 42 +++++++++++++++++++++++------------- test/lisp/vc/vc-git-tests.el | 35 ++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 15 deletions(-) diff --git a/lisp/vc/vc-git.el b/lisp/vc/vc-git.el index 73db9c0f181..d40ce0de528 100644 --- a/lisp/vc/vc-git.el +++ b/lisp/vc/vc-git.el @@ -772,24 +772,36 @@ or an empty string if none." (vc-git--out-match '("symbolic-ref" "HEAD") "^\\(refs/heads/\\)?\\(.+\\)$" 2)) +(defun vc-git--branch-remotes () + "Return alist of configured remote branches for current branch. +If there is a configured upstream, return the remote-tracking branch +with key `upstream'. If there is a distinct configured push remote, +return the remote-tracking branch there with key `push'. +A configured push remote that's just the same as the upstream remote is +ignored because that means we're not actually in a triangular workflow." + ;; Possibly we could simplify this using @{push}, but that may involve + ;; an unwanted dependency on the setting of push.default. + (cl-flet ((get (key) + (string-trim-right (vc-git--out-str "config" key)))) + (let* ((branch (vc-git-working-branch)) + (pull (get (format "branch.%s.remote" branch))) + (merge (get (format "branch.%s.merge" branch))) + (push (get (format "branch.%s.pushRemote" branch))) + (push (if (string-empty-p push) + (get "remote.pushDefault") + push)) + (alist (and (not (string-empty-p pull)) + (not (string-empty-p merge)) + `((upstream . ,(format "%s/%s" pull merge)))))) + (if (or (string-empty-p push) (equal push pull)) + alist + (cl-acons 'push (format "%s/%s" push branch) alist))))) + (defun vc-git-trunk-or-topic-p () "Return `topic' if branch has distinct pull and push remotes, else nil. This is able to identify topic branches for certain forge workflows." - (let* ((branch (vc-git-working-branch)) - (merge (string-trim-right - (vc-git--out-str "config" (format "branch.%s.remote" - branch)))) - (push (string-trim-right - (vc-git--out-str "config" (format "branch.%s.pushRemote" - branch)))) - (push (if (string-empty-p push) - (string-trim-right - (vc-git--out-str "config" "remote.pushDefault")) - push))) - (and (plusp (length merge)) - (plusp (length push)) - (not (equal merge push)) - 'topic))) + (let ((remotes (vc-git--branch-remotes))) + (and (assq 'upstream remotes) (assq 'push remotes) 'topic))) (defun vc-git-topic-outgoing-base () "Return the outgoing base for the current branch as a string. diff --git a/test/lisp/vc/vc-git-tests.el b/test/lisp/vc/vc-git-tests.el index fe55cc75d6f..1552608071e 100644 --- a/test/lisp/vc/vc-git-tests.el +++ b/test/lisp/vc/vc-git-tests.el @@ -194,4 +194,39 @@ is absent." ("Tracking" . ,main-branch) ("Remote" . "none (tracking local branch)"))))))))) +(ert-deftest vc-git-test-branch-remotes () + "Test behavior of `vc-git--branch-remotes'." + (skip-unless (executable-find vc-git-program)) + (vc-git-test--with-repo repo + (let ((main-branch (vc-git-test--start-branch))) + (should (null (vc-git--branch-remotes))) + (vc-git--out-ok "config" + (format "branch.%s.remote" main-branch) + "origin") + (should (null (vc-git--branch-remotes))) + (vc-git--out-ok "config" + (format "branch.%s.merge" main-branch) + main-branch) + (let ((alist (vc-git--branch-remotes))) + (should (assq 'upstream alist)) + (should (null (assq 'push alist)))) + (vc-git--out-ok "config" + (format "branch.%s.pushRemote" main-branch) + "fork") + (let ((alist (vc-git--branch-remotes))) + (should (assq 'upstream alist)) + (should (equal (cdr (assq 'push alist)) + (concat "fork/" main-branch)))) + (vc-git--out-ok "config" "unset" + (format "branch.%s.pushRemote" main-branch)) + (vc-git--out-ok "config" "remote.pushDefault" "fork") + (let ((alist (vc-git--branch-remotes))) + (should (assq 'upstream alist)) + (should (equal (cdr (assq 'push alist)) + (concat "fork/" main-branch)))) + (vc-git--out-ok "config" "remote.pushDefault" "origin") + (let ((alist (vc-git--branch-remotes))) + (should (assq 'upstream alist)) + (should (null (assq 'push alist))))))) + ;;; vc-git-tests.el ends here From 83db778195a6137ce427e34aa09a57b01ed9f283 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Fri, 30 Jan 2026 13:49:35 +0000 Subject: [PATCH 50/74] ; Fix last change. --- lisp/vc/vc-git.el | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lisp/vc/vc-git.el b/lisp/vc/vc-git.el index d40ce0de528..b852cd5b28e 100644 --- a/lisp/vc/vc-git.el +++ b/lisp/vc/vc-git.el @@ -785,7 +785,9 @@ ignored because that means we're not actually in a triangular workflow." (string-trim-right (vc-git--out-str "config" key)))) (let* ((branch (vc-git-working-branch)) (pull (get (format "branch.%s.remote" branch))) - (merge (get (format "branch.%s.merge" branch))) + (merge (string-remove-prefix "refs/heads/" + (get (format "branch.%s.merge" + branch)))) (push (get (format "branch.%s.pushRemote" branch))) (push (if (string-empty-p push) (get "remote.pushDefault") From b4a18e466e76a859c30679c0987bd4323daa3ed7 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Fri, 30 Jan 2026 13:50:28 +0000 Subject: [PATCH 51/74] vc-git-topic-outgoing-base: Respect a configure push remote * lisp/vc/vc-git.el (vc-git-topic-outgoing-base): If there is a configured push remote, return tracking branch as outgoing base. --- lisp/vc/vc-git.el | 85 +++++++++++++++++++++++++---------------------- 1 file changed, 46 insertions(+), 39 deletions(-) diff --git a/lisp/vc/vc-git.el b/lisp/vc/vc-git.el index b852cd5b28e..cb0c7021940 100644 --- a/lisp/vc/vc-git.el +++ b/lisp/vc/vc-git.el @@ -809,47 +809,54 @@ This is able to identify topic branches for certain forge workflows." "Return the outgoing base for the current branch as a string. This works by considering the current branch as a topic branch (whether or not it actually is). -Requires that the corresponding trunk exists as a local branch. -The algorithm employed is as follows. Find all merge bases between the -current branch and other local branches. Each of these is a commit on -the current branch. Use `git merge-base --independent' on them all to -find the topologically most recent. Take the branch for which that -commit is a merge base with the current branch to be the branch into -which the current branch will eventually be merged. Find its upstream. -(If there is more than one branch whose merge base with the current -branch is that same topologically most recent commit, try them -one-by-one, accepting the first that has an upstream.)" - (cl-flet ((get-line () (buffer-substring (point) (pos-eol)))) - (let* ((branches (vc-git-branches)) - (current (pop branches)) - merge-bases) - (with-temp-buffer - (dolist (branch branches) - (erase-buffer) - (when (vc-git--out-ok "merge-base" "--all" branch current) - (goto-char (point-min)) - (while (not (eobp)) - (push branch - (alist-get (get-line) merge-bases nil nil #'equal)) - (forward-line 1)))) - (erase-buffer) - (unless (apply #'vc-git--out-ok "merge-base" "--independent" - (mapcar #'car merge-bases)) - (error "`git merge-base --independent' failed")) - ;; If 'git merge-base --independent' printed more than one line, - ;; just pick the first. - (goto-char (point-min)) - (catch 'ret - (dolist (target (cdr (assoc (get-line) merge-bases))) +If there is a distinct push remote for this branch, assume the target +for outstanding changes is the tracking branch, so return that. + +Otherwise, fall back to the following algorithm, which requires that the +corresponding trunk exists as a local branch. Find all merge bases +between the current branch and other local branches. Each of these is a +commit on the current branch. Use `git merge-base --independent' on +them all to find the topologically most recent. Take the branch for +which that commit is a merge base with the current branch to be the +branch into which the current branch will eventually be merged. Find +its upstream. (If there is more than one branch whose merge base with +the current branch is that same topologically most recent commit, try +them one-by-one, accepting the first that has an upstream.)" + (let ((remotes (vc-git--branch-remotes))) + (if-let* ((_ (assq 'push remotes)) + (upstream (assq 'upstream remotes))) + (cdr upstream) + (cl-flet ((get-line () (buffer-substring (point) (pos-eol)))) + (let* ((branches (vc-git-branches)) + (current (pop branches)) + merge-bases) + (with-temp-buffer + (dolist (branch branches) + (erase-buffer) + (when (vc-git--out-ok "merge-base" "--all" branch current) + (goto-char (point-min)) + (while (not (eobp)) + (push branch (alist-get (get-line) merge-bases + nil nil #'equal)) + (forward-line 1)))) (erase-buffer) - (when (vc-git--out-ok "for-each-ref" - "--format=%(upstream:short)" - (concat "refs/heads/" target)) - (goto-char (point-min)) - (let ((outgoing-base (get-line))) - (unless (string-empty-p outgoing-base) - (throw 'ret outgoing-base)))))))))) + (unless (apply #'vc-git--out-ok "merge-base" "--independent" + (mapcar #'car merge-bases)) + (error "`git merge-base --independent' failed")) + ;; If 'git merge-base --independent' printed more than one + ;; line, just pick the first. + (goto-char (point-min)) + (catch 'ret + (dolist (target (cdr (assoc (get-line) merge-bases))) + (erase-buffer) + (when (vc-git--out-ok "for-each-ref" + "--format=%(upstream:short)" + (concat "refs/heads/" target)) + (goto-char (point-min)) + (let ((outgoing-base (get-line))) + (unless (string-empty-p outgoing-base) + (throw 'ret outgoing-base)))))))))))) (defun vc-git-dir--branch-headers () "Return headers for branch-related information." From c1029c88a88091099d9dc6a16dd3667736547fd5 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Fri, 30 Jan 2026 13:52:37 +0000 Subject: [PATCH 52/74] ; vc-git-topic-outgoing-base: Merge let into if-let*. --- lisp/vc/vc-git.el | 66 +++++++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/lisp/vc/vc-git.el b/lisp/vc/vc-git.el index cb0c7021940..b1a60aeeb23 100644 --- a/lisp/vc/vc-git.el +++ b/lisp/vc/vc-git.el @@ -823,40 +823,40 @@ branch into which the current branch will eventually be merged. Find its upstream. (If there is more than one branch whose merge base with the current branch is that same topologically most recent commit, try them one-by-one, accepting the first that has an upstream.)" - (let ((remotes (vc-git--branch-remotes))) - (if-let* ((_ (assq 'push remotes)) - (upstream (assq 'upstream remotes))) - (cdr upstream) - (cl-flet ((get-line () (buffer-substring (point) (pos-eol)))) - (let* ((branches (vc-git-branches)) - (current (pop branches)) - merge-bases) - (with-temp-buffer - (dolist (branch branches) - (erase-buffer) - (when (vc-git--out-ok "merge-base" "--all" branch current) - (goto-char (point-min)) - (while (not (eobp)) - (push branch (alist-get (get-line) merge-bases - nil nil #'equal)) - (forward-line 1)))) + (if-let* ((remotes (vc-git--branch-remotes)) + (_ (assq 'push remotes)) + (upstream (assq 'upstream remotes))) + (cdr upstream) + (cl-flet ((get-line () (buffer-substring (point) (pos-eol)))) + (let* ((branches (vc-git-branches)) + (current (pop branches)) + merge-bases) + (with-temp-buffer + (dolist (branch branches) (erase-buffer) - (unless (apply #'vc-git--out-ok "merge-base" "--independent" - (mapcar #'car merge-bases)) - (error "`git merge-base --independent' failed")) - ;; If 'git merge-base --independent' printed more than one - ;; line, just pick the first. - (goto-char (point-min)) - (catch 'ret - (dolist (target (cdr (assoc (get-line) merge-bases))) - (erase-buffer) - (when (vc-git--out-ok "for-each-ref" - "--format=%(upstream:short)" - (concat "refs/heads/" target)) - (goto-char (point-min)) - (let ((outgoing-base (get-line))) - (unless (string-empty-p outgoing-base) - (throw 'ret outgoing-base)))))))))))) + (when (vc-git--out-ok "merge-base" "--all" branch current) + (goto-char (point-min)) + (while (not (eobp)) + (push branch (alist-get (get-line) merge-bases + nil nil #'equal)) + (forward-line 1)))) + (erase-buffer) + (unless (apply #'vc-git--out-ok "merge-base" "--independent" + (mapcar #'car merge-bases)) + (error "`git merge-base --independent' failed")) + ;; If 'git merge-base --independent' printed more than one + ;; line, just pick the first. + (goto-char (point-min)) + (catch 'ret + (dolist (target (cdr (assoc (get-line) merge-bases))) + (erase-buffer) + (when (vc-git--out-ok "for-each-ref" + "--format=%(upstream:short)" + (concat "refs/heads/" target)) + (goto-char (point-min)) + (let ((outgoing-base (get-line))) + (unless (string-empty-p outgoing-base) + (throw 'ret outgoing-base))))))))))) (defun vc-git-dir--branch-headers () "Return headers for branch-related information." From ae7761598dc95a65491cfff978bb5119ccdc6b57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Engdeg=C3=A5rd?= Date: Fri, 30 Jan 2026 15:42:53 +0100 Subject: [PATCH 53/74] Pass lazy doc string to 'defalias' * lisp/emacs-lisp/bytecomp.el (byte-compile-file-form-defalias): We correctly emit a lazy-loaded doc string but then passed a literal string to 'defalias' by mistake; fix that. Saves 40 KiB in .elc files. --- lisp/emacs-lisp/bytecomp.el | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lisp/emacs-lisp/bytecomp.el b/lisp/emacs-lisp/bytecomp.el index 52bc20af173..edfd9491a2f 100644 --- a/lisp/emacs-lisp/bytecomp.el +++ b/lisp/emacs-lisp/bytecomp.el @@ -5141,7 +5141,8 @@ binding slots have been popped." (when (stringp doc) (setq rest (byte-compile--list-with-n rest 0 - (byte-compile--docstring doc (nth 0 form) name))))) + (byte-compile--docstring doc (nth 0 form) name))) + (setq form (nconc (take 3 form) rest)))) (pcase-let* ;; `macro' is non-nil if it defines a macro. ;; `fun' is the function part of `arg' (defaults to `arg'). From fcdd8678f97e98b2afc38f1e999559eff726972a Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Fri, 30 Jan 2026 15:06:52 +0000 Subject: [PATCH 54/74] Make diff-hunk-kill respect an active region * lisp/vc/diff-mode.el (diff--revert-kill-hunks): New workhorse routine. (diff-hunk-kill, diff-revert-and-kill-hunk): Call it. (diff-hunk-kill): New BEG and END parameters and interactive form. * doc/emacs/files.texi (Diff Mode): * etc/NEWS: Document the change. --- doc/emacs/files.texi | 3 +- etc/NEWS | 7 ++- lisp/vc/diff-mode.el | 137 ++++++++++++++++++++++++++++--------------- lisp/vc/vc-git.el | 2 +- 4 files changed, 98 insertions(+), 51 deletions(-) diff --git a/doc/emacs/files.texi b/doc/emacs/files.texi index 567c1492518..a9bcee0b060 100644 --- a/doc/emacs/files.texi +++ b/doc/emacs/files.texi @@ -1835,7 +1835,8 @@ the start of the @var{n}th previous file. @findex diff-hunk-kill @item M-k -Kill the hunk at point (@code{diff-hunk-kill}). +Kill the hunk at point (@code{diff-hunk-kill}). If the region is +active, kills all hunks the region overlaps. @findex diff-file-kill @item M-K diff --git a/etc/NEWS b/etc/NEWS index 78d627068c4..be507f525ba 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -2284,9 +2284,10 @@ one as before. This makes them different from 'vc-diff' and *** 'diff-apply-hunk' now supports creating and deleting files. +++ -*** 'diff-apply-hunk' and 'diff-apply-buffer' now consider the region. -If the region is active, these commands now apply all hunks that the -region overlaps. Otherwise, they have their existing behavior. +*** Diff mode's application and killing commands now consider the region. +If the region is active, 'diff-apply-hunk', 'diff-apply-buffer' and +'diff-hunk-kill' now apply or kill all hunks that the region overlaps. +Otherwise, they have their existing behavior. +++ *** 'diff-apply-buffer' can reverse-apply. diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el index f57f39de37c..28e29cf36c5 100644 --- a/lisp/vc/diff-mode.el +++ b/lisp/vc/diff-mode.el @@ -883,31 +883,19 @@ If the prefix ARG is given, restrict the view to the current file instead." (goto-char (point-min)) (re-search-forward diff-hunk-header-re nil t))) -(defun diff-hunk-kill () - "Kill the hunk at point." - (interactive) - (if (not (diff--some-hunks-p)) - (error "No hunks") - (diff-beginning-of-hunk t) - (let* ((hunk-bounds (diff-bounds-of-hunk)) - (file-bounds (ignore-errors (diff-bounds-of-file))) - ;; If the current hunk is the only one for its file, kill the - ;; file header too. - (bounds (if (and file-bounds - (progn (goto-char (car file-bounds)) - (= (progn (diff-hunk-next) (point)) - (car hunk-bounds))) - (progn (goto-char (cadr hunk-bounds)) - ;; bzr puts a newline after the last hunk. - (while (looking-at "^\n") - (forward-char 1)) - (= (point) (cadr file-bounds)))) - file-bounds - hunk-bounds)) - (inhibit-read-only t)) - (apply #'kill-region bounds) - (goto-char (car bounds)) - (ignore-errors (diff-beginning-of-hunk t))))) +(defun diff-hunk-kill (&optional beg end) + "Kill the hunk at point. +When killing the last hunk left for a file, kill the file header too. +Interactively, if the region is active, kill all hunks that the region +overlaps. + +When called from Lisp with optional arguments BEG and END non-nil, kill +all hunks overlapped by the region from BEG to END as though called +interactively with an active region delimited by BEG and END." + (interactive "R") + (when (xor beg end) + (error "Invalid call to `diff-hunk-kill'")) + (diff--revert-kill-hunks beg end nil)) ;; This is not `diff-kill-other-hunks' because we might need to make ;; copies of file headers in order to ensure the new kill ring entry @@ -2283,6 +2271,83 @@ With a prefix argument, try to REVERSE the hunk." :type 'boolean :version "31.1") +(defun diff--revert-kill-hunks (beg end revertp) + "Workhorse routine for killing hunks, after possibly reverting them. +If BEG and END are nil, kill the hunk at point. +Otherwise kill all hunks overlapped by region delimited by BEG and END. +When killing a hunk that's the only one remaining for its file, kill the +file header too. +If REVERTP is non-nil, reverse-apply hunks before killing them." + ;; With BEG and END non-nil, we push each hunk to the kill ring + ;; separately. If we want to push to the kill ring just once, we have + ;; to decide how to handle file headers such that the meanings of the + ;; hunks in the kill ring entry, considered as a whole patch, do not + ;; deviate too far from the meanings the hunks had in this buffer. + ;; + ;; For example, if we have a single hunk for one file followed by + ;; multiple hunks for another file, and we naïvely kill the single + ;; hunk and the first of the multiple hunks, our kill ring entry will + ;; be a patch applying those two hunks to the first file. This is + ;; because killing the single hunk will have brought its file header + ;; with it, but not so killing the second hunk. So we will have put + ;; together hunks that were previously for two different files. + ;; + ;; One option is to *copy* every file header that the region overlaps + ;; (and that we will not kill, because we are leaving other hunks for + ;; that file behind). But then the text this command pushes to the + ;; kill ring would be different from the text it removes from the + ;; buffer, which would be unintuitive for an Emacs kill command. + ;; + ;; An alternative might be to have restrictions as follows: + ;; + ;; Interactively, if the region is active, try to kill all hunks that the + ;; region overlaps. This works when either + ;; - all the hunks the region overlaps are for the same file; or + ;; - the last hunk the region overlaps is the last hunk for its file. + ;; These restrictions are so that the text added to the kill ring does not + ;; merge together hunks for different files under a single file header. + ;; + ;; We would error out if neither property is met. When either holds, + ;; any file headers the region overlaps are ones we should kill. + (unless (diff--some-hunks-p) + (error "No hunks")) + (if beg + (save-excursion + (goto-char beg) + (setq beg (car (diff-bounds-of-hunk))) + (goto-char end) + (unless (looking-at diff-hunk-header-re) + (setq end (cadr (diff-bounds-of-hunk))))) + (pcase-setq `(,beg ,end) (diff-bounds-of-hunk))) + (when (or (not revertp) (null (diff-apply-buffer beg end t))) + (goto-char end) + (when-let* ((pos (diff--at-diff-header-p))) + (goto-char pos)) + (setq beg (copy-marker beg) end (point-marker)) + (unwind-protect + (cl-loop initially (goto-char beg) + for (hunk-beg hunk-end) = (diff-bounds-of-hunk) + for file-bounds = (ignore-errors (diff-bounds-of-file)) + for (file-beg file-end) = file-bounds + for inhibit-read-only = t + if (and file-bounds + (progn + (goto-char file-beg) + (diff-hunk-next) + (eq (point) hunk-beg)) + (progn + (goto-char hunk-end) + ;; bzr puts a newline after the last hunk. + (while (looking-at "^\n") (forward-char 1)) + (eq (point) file-end))) + do (kill-region file-beg file-end) (goto-char file-beg) + else do (kill-region hunk-beg hunk-end) (goto-char hunk-beg) + do (ignore-errors (diff-beginning-of-hunk t)) + until (or (< (point) (marker-position beg)) + (eql (point) (marker-position end)))) + (set-marker beg nil) + (set-marker end nil)))) + (defun diff-revert-and-kill-hunk (&optional beg end) "Reverse-apply and then kill the hunk at point. Save changed buffer. Interactively, if the region is active, reverse-apply and kill all @@ -2308,27 +2373,7 @@ BEG and END." (error "Invalid call to `diff-revert-and-kill-hunk'")) (when (or (not diff-ask-before-revert-and-kill-hunk) (y-or-n-p "Really reverse-apply and kill hunk(s)?")) - (if beg - (save-excursion - (goto-char beg) - (setq beg (car (diff-bounds-of-hunk))) - (goto-char end) - (unless (looking-at diff-hunk-header-re) - (setq end (cadr (diff-bounds-of-hunk))))) - (pcase-setq `(,beg ,end) (diff-bounds-of-hunk))) - (when (null (diff-apply-buffer beg end t)) - ;; Use `diff-hunk-kill' because it properly handles file headers. - (goto-char end) - (when-let* ((pos (diff--at-diff-header-p))) - (goto-char pos)) - (setq beg (copy-marker beg) end (point-marker)) - (unwind-protect - (cl-loop initially (goto-char beg) - do (diff-hunk-kill) - until (or (< (point) (marker-position beg)) - (eql (point) (marker-position end)))) - (set-marker beg nil) - (set-marker end nil))))) + (diff--revert-kill-hunks beg end t))) (defun diff-apply-buffer (&optional beg end reverse test-or-no-save) "Apply the diff in the entire diff buffer. diff --git a/lisp/vc/vc-git.el b/lisp/vc/vc-git.el index b1a60aeeb23..d6a7145b34e 100644 --- a/lisp/vc/vc-git.el +++ b/lisp/vc/vc-git.el @@ -811,7 +811,7 @@ This works by considering the current branch as a topic branch (whether or not it actually is). If there is a distinct push remote for this branch, assume the target -for outstanding changes is the tracking branch, so return that. +for outstanding changes is the tracking branch, and return that. Otherwise, fall back to the following algorithm, which requires that the corresponding trunk exists as a local branch. Find all merge bases From 53a3883bf664e0128cc8a74145ddfd8b115d828d Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Fri, 30 Jan 2026 15:56:58 +0000 Subject: [PATCH 55/74] vc--incoming-revision: Signal error on cache hit * lisp/vc/vc.el (vc--incoming-revision): Signal an error instead of returning nil on a cache hit (bug#80270). (vc--outgoing-base-mergebase): Simplify, given that vc--incoming-revision now handles the error case. --- lisp/vc/vc.el | 47 ++++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/lisp/vc/vc.el b/lisp/vc/vc.el index 770906ff6cc..14da03cda1d 100644 --- a/lisp/vc/vc.el +++ b/lisp/vc/vc.el @@ -3330,15 +3330,13 @@ to which `vc-push' would push as UPSTREAM-LOCATION, unconditionally. (This is passed when the user invokes an outgoing base command with a \\`C-u C-u' prefix argument; see `vc--maybe-read-outgoing-base'.) REFRESH is passed on to `vc--incoming-revision'." - (if-let* ((incoming - (vc--incoming-revision backend - (pcase upstream-location - ('t nil) - ('nil (vc--outgoing-base backend)) - (_ upstream-location)) - refresh))) - (vc-call-backend backend 'mergebase incoming) - (user-error "No incoming revision -- local-only branch?"))) + (vc-call-backend backend 'mergebase + (vc--incoming-revision backend + (pcase upstream-location + ('t nil) + ('nil (vc--outgoing-base backend)) + (_ upstream-location)) + refresh))) ;;;###autoload (defun vc-root-diff-outgoing-base (&optional upstream-location) @@ -4435,20 +4433,23 @@ BACKEND is the VC backend." ;; Do store `nil', before signaling an error, if there is no incoming ;; revision, because that's also something that can be slow to ;; determine and so should be remembered. - (if-let* ((_ (not refresh)) - (record (assoc upstream-location - (vc--repo-getprop backend 'vc-incoming-revision)))) - (cdr record) - (let ((res (vc-call-backend backend 'incoming-revision - upstream-location refresh))) - (if-let* ((alist (vc--repo-getprop backend 'vc-incoming-revision))) - (setf (alist-get upstream-location alist nil nil #'equal) - res) - (vc--repo-setprop backend - 'vc-incoming-revision - `((,upstream-location . ,res)))) - (or res - (user-error "No incoming revision -- local-only branch?"))))) + (or (if-let* ((_ (not refresh)) + (record (assoc upstream-location + (vc--repo-getprop backend + 'vc-incoming-revision)))) + (cdr record) + (let ((res (vc-call-backend backend 'incoming-revision + upstream-location refresh))) + (if-let* ((alist (vc--repo-getprop backend + 'vc-incoming-revision))) + (setf (alist-get upstream-location alist + nil nil #'equal) + res) + (vc--repo-setprop backend + 'vc-incoming-revision + `((,upstream-location . ,res)))) + res)) + (user-error "No incoming revision -- local-only branch?"))) ;;;###autoload (defun vc-root-log-incoming (&optional upstream-location) From 69a2b9fa17054794723455fbac84beb51290dfa1 Mon Sep 17 00:00:00 2001 From: Daniel Colascione Date: Fri, 30 Jan 2026 12:30:40 -0500 Subject: [PATCH 56/74] xsettings: honor GDK DPI scaling values Some XWayland setups only report DPI changes through GDK xsettings, so Emacs missed DPI updates there. Recognize the GDK DPI and scaling settings and use them to compute the effective DPI. * src/xsettings.c (parse_settings): Recognize Gdk/UnscaledDPI and Gdk/WindowScalingFactor. Use them to compute DPI when present. --- src/xsettings.c | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/xsettings.c b/src/xsettings.c index 25edce78841..71cd6a9ad6c 100644 --- a/src/xsettings.c +++ b/src/xsettings.c @@ -187,6 +187,8 @@ store_tool_bar_style_changed (const char *newstyle, #ifndef HAVE_PGTK #if defined USE_CAIRO || defined HAVE_XFT #define XSETTINGS_FONT_NAME "Gtk/FontName" +#define XSETTINGS_GDK_DPI_NAME "Gdk/UnscaledDPI" +#define XSETTINGS_GDK_WSCALE_NAME "Gdk/WindowScalingFactor" #endif #define XSETTINGS_TOOL_BAR_STYLE "Gtk/ToolbarStyle" #endif @@ -626,6 +628,15 @@ parse_settings (unsigned char *prop, int bytes_parsed = 0; int settings_seen = 0; int i = 0; +#if defined USE_CAIRO || defined HAVE_XFT + /* Some X environments, e.g. XWayland, communicate DPI changes only + through the GDK xsettings values and not the regular Xft one, so + recognize both schemes. We want to see both the GDK window scaling + factor and the post-scaling DPI so we can compute our desired + actual DPI. */ + int gdk_unscaled_dpi = 0; + int gdk_window_scale = 0; +#endif /* First 4 bytes is a serial number, skip that. */ @@ -668,7 +679,9 @@ parse_settings (unsigned char *prop, want_this = strcmp (XSETTINGS_TOOL_BAR_STYLE, name) == 0; #if defined USE_CAIRO || defined HAVE_XFT if ((nlen > 6 && memcmp (name, "Xft/", 4) == 0) - || strcmp (XSETTINGS_FONT_NAME, name) == 0) + || strcmp (XSETTINGS_FONT_NAME, name) == 0 + || strcmp (XSETTINGS_GDK_DPI_NAME, name) == 0 + || strcmp (XSETTINGS_GDK_WSCALE_NAME, name) == 0) want_this = true; #endif @@ -769,6 +782,10 @@ parse_settings (unsigned char *prop, settings->seen |= SEEN_DPI; settings->dpi = ival / 1024.0; } + else if (strcmp (name, XSETTINGS_GDK_DPI_NAME) == 0) + gdk_unscaled_dpi = ival; + else if (strcmp (name, XSETTINGS_GDK_WSCALE_NAME) == 0) + gdk_window_scale = ival; else if (strcmp (name, "Xft/lcdfilter") == 0) { settings->seen |= SEEN_LCDFILTER; @@ -786,6 +803,19 @@ parse_settings (unsigned char *prop, } } +#if defined USE_CAIRO || defined HAVE_XFT + if (gdk_unscaled_dpi > 0 && gdk_window_scale > 0) + { + /* Override any previous DPI settings. GDK ones are intended to + be authoritative. + See + https://mail.gnome.org/archives/commits-list/2013-June/msg06726.html + */ + settings->seen |= SEEN_DPI; + settings->dpi = gdk_window_scale * gdk_unscaled_dpi / 1024.0; + } +#endif + return settings_seen; } #endif From e68239773ce6f6ff85795e37030f9b4f03f5942f Mon Sep 17 00:00:00 2001 From: Daniel Mendler Date: Fri, 5 May 2023 15:31:58 +0200 Subject: [PATCH 57/74] pixel-scroll: Avoid loading `cua-base' CUA is not necessarily used together with `pixel-scroll-precision-mode'. Make `pixel-scroll-interpolate-down' and `pixel-scroll-interpolate-up' independent and avoid loading cua-base. * lisp/pixel-scroll.el (pixel-scroll-interpolate-up) (pixel-scroll-interpolate-down): Do not use `cua-scroll-down' and `cua-scroll-up'; replace them with inline code. (Bug#80245) --- lisp/pixel-scroll.el | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/lisp/pixel-scroll.el b/lisp/pixel-scroll.el index dbb532f691b..23e63add994 100644 --- a/lisp/pixel-scroll.el +++ b/lisp/pixel-scroll.el @@ -90,7 +90,6 @@ (require 'mwheel) (require 'subr-x) (require 'ring) -(require 'cua-base) (defvar pixel-wait 0 "Idle time on each step of pixel scroll specified in second. @@ -831,7 +830,13 @@ It is a vector of the form [ VELOCITY TIME SIGN ]." ;; since we want exactly 1 ;; page to be scrolled. nil 1) - (cua-scroll-up))) + (cond + ((eobp) + (scroll-up)) ; signal error + (t + (condition-case nil + (scroll-up) + (end-of-buffer (goto-char (point-max)))))))) ;;;###autoload (defun pixel-scroll-interpolate-up () @@ -840,7 +845,13 @@ It is a vector of the form [ VELOCITY TIME SIGN ]." (if pixel-scroll-precision-interpolate-page (pixel-scroll-precision-interpolate (window-text-height nil t) nil 1) - (cua-scroll-down))) + (cond + ((bobp) + (scroll-down)) ; signal error + (t + (condition-case nil + (scroll-down) + (beginning-of-buffer (goto-char (point-min)))))))) ;;;###autoload (define-minor-mode pixel-scroll-precision-mode From a0748d9791c7dbf8604ef9312c1b889cb87a42ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Marks?= Date: Fri, 23 Jan 2026 17:45:09 -0500 Subject: [PATCH 58/74] New function 'truncate-string-pixelwise' (bug#80244) This function will truncate a string on a pixelwise basis in a work buffer and using a binary search rather than brute force. * lisp/emacs-lisp/subr-x.el (work-buffer--prepare-pixelwise): New defun helper function. (string-pixel-width): Use the helper function. (truncate-string-pixelwise): New defun. * test/lisp/misc-tests.el (misc-test-truncate-string-pixelwise): (misc-test-truncate-string-pixelwise-unicode): New test. * doc/lispref/display.texi (Size of Displayed Text): Document the function. * etc/NEWS: Announce the function. --- doc/lispref/display.texi | 34 +++++++++++++ etc/NEWS | 8 +++ lisp/emacs-lisp/subr-x.el | 103 +++++++++++++++++++++++++++++++------- test/lisp/misc-tests.el | 80 +++++++++++++++++++++++++++++ 4 files changed, 207 insertions(+), 18 deletions(-) diff --git a/doc/lispref/display.texi b/doc/lispref/display.texi index 4211b435db5..464c0badc36 100644 --- a/doc/lispref/display.texi +++ b/doc/lispref/display.texi @@ -2243,6 +2243,9 @@ means hide the excess parts of @var{string} with a @code{display} text property (@pxref{Display Property}) showing the ellipsis, instead of actually truncating the string. +See also the function @code{truncate-string-pixelwise} for pixel-level +resolution. + @example @group (truncate-string-to-width "\tab\t" 12 4) @@ -2440,6 +2443,37 @@ non-@code{nil}, use any face remappings (@pxref{Face Remapping}) from that buffer when computing the width of @var{string}. @end defun +@defun truncate-string-pixelwise string max-pixels &optional buffer ellipsis ellipsis-pixels +This is a convenience function that uses @code{window-text-pixel-size} +to truncate @var{string} to @var{max-pixels} pixels. Caveat: if you +call this function to measure the width of a string with embedded +newlines, it will then return the width of the widest substring that +does not include newlines. The meaning of this result is the widest +line taken by the string if inserted into a buffer. If @var{buffer} is +non-@code{nil}, use any face remappings (@pxref{Face Remapping}) from +that buffer when computing the width of @var{string}. + +If @var{ellipsis} is non-@code{nil}, it should be a string which will +replace the end of @var{string} when it is truncated. In this case, +more characters will be removed from @var{string} to free enough space +for @var{ellipsis} to fit within @var{max-pixels} pixels. However, if +the pixel width of @var{string} is less than the pixel width of +@var{ellipsis}, @var{ellipsis} will not be appended to the result. If +@var{ellipsis} is non-@code{nil} and not a string, it stands for the +value returned by the function @code{truncate-string-ellipsis}, +described above. + +If @var{ellipsis-pixels} is non-@code{nil} and @var{ellipsis} is +non-@code{nil}, it should be the number of pixels of @var{ellipsis} that +you should precompute using @code{string-pixel-width}, specifying the +same buffer. This is useful to avoid the cost of recomputing this value +repeatedly when you have many strings to truncate using the same +ellipsis string. + +See also the function @code{truncate-string-to-width} for +character-level resolution. +@end defun + @defun line-pixel-height This function returns the height in pixels of the line at point in the selected window. The value includes the line spacing of the line diff --git a/etc/NEWS b/etc/NEWS index be507f525ba..2cf91cfd5f7 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -3847,6 +3847,14 @@ It has been obsolete since Emacs 26.1. Use the group 'text' instead. If supplied, 'string-pixel-width' will use any face remappings from BUFFER when computing the string's width. ++++ +** New function 'truncate-string-pixelwise'. +This function truncates a string to the specified maximum number of +pixels rather than by characters, as in 'truncate-string-to-width', and +respects face remappings if BUFFER is specified. You can also specify +an optional ellipsis string to append, similar to +'truncate-string-to-width'. + --- ** New macro 'with-work-buffer'. This macro is similar to the already existing macro 'with-temp-buffer', diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el index 8d04958487f..db854863b32 100644 --- a/lisp/emacs-lisp/subr-x.el +++ b/lisp/emacs-lisp/subr-x.el @@ -37,6 +37,7 @@ (eval-when-compile (require 'cl-lib)) +(require 'mule-util) (defmacro internal--thread-argument (first? &rest forms) "Internal implementation for `thread-first' and `thread-last'. @@ -357,6 +358,29 @@ buffer when possible, instead of creating a new one on each call." (progn ,@body) (work-buffer--release ,work-buffer)))))) +(defun work-buffer--prepare-pixelwise (string buffer) + "Set up the current buffer to correctly compute STRING's pixel width. +Call this with a work buffer as the current buffer. +BUFFER is the originating buffer and if non-nil, make the current +buffer's (work buffer) face remappings match it." + (when buffer + (dolist (v '(face-remapping-alist + char-property-alias-alist + default-text-properties)) + (if (local-variable-p v buffer) + (set (make-local-variable v) + (buffer-local-value v buffer))))) + ;; Avoid deactivating the region as side effect. + (let (deactivate-mark) + (insert string)) + ;; If `display-line-numbers' is enabled in internal + ;; buffers (e.g. globally), it breaks width calculation + ;; (bug#59311). Disable `line-prefix' and `wrap-prefix', + ;; for the same reason. + (add-text-properties + (point-min) (point-max) + '(display-line-numbers-disable t line-prefix "" wrap-prefix ""))) + ;;;###autoload (defun string-pixel-width (string &optional buffer) "Return the width of STRING in pixels. @@ -371,26 +395,69 @@ substring that does not include newlines." ;; Keeping a work buffer around is more efficient than creating a ;; new temporary buffer. (with-work-buffer - ;; Setup current buffer to correctly compute pixel width. - (when buffer - (dolist (v '(face-remapping-alist - char-property-alias-alist - default-text-properties)) - (if (local-variable-p v buffer) - (set (make-local-variable v) - (buffer-local-value v buffer))))) - ;; Avoid deactivating the region as side effect. - (let (deactivate-mark) - (insert string)) - ;; If `display-line-numbers' is enabled in internal - ;; buffers (e.g. globally), it breaks width calculation - ;; (bug#59311). Disable `line-prefix' and `wrap-prefix', - ;; for the same reason. - (add-text-properties - (point-min) (point-max) - '(display-line-numbers-disable t line-prefix "" wrap-prefix "")) + (work-buffer--prepare-pixelwise string buffer) (car (buffer-text-pixel-size nil nil t))))) +;;;###autoload +(defun truncate-string-pixelwise (string max-pixels &optional buffer + ellipsis ellipsis-pixels) + "Return STRING truncated to fit within MAX-PIXELS. +If BUFFER is non-nil, use the face remappings, alternative and default +properties from that buffer when determining the width. +If you call this function to measure pixel width of a string +with embedded newlines, it returns the width of the widest +substring that does not include newlines. + +If ELLIPSIS is non-nil, it should be a string which will replace the end +of STRING if it extends beyond MAX-PIXELS, unless the pixel width of +STRING is equal to or less than the pixel width of ELLIPSIS. If it is +non-nil and not a string, then ELLIPSIS defaults to +`truncate-string-ellipsis', or to three dots when it's nil. + +If ELLIPSIS-PIXELS is non-nil, it is the pixel width of ELLIPSIS, and +can be used to avoid the cost of recomputing this for multiple calls to +this function using the same ELLIPSIS." + (declare (important-return-value t)) + (if (zerop (length string)) + 0 + ;; Keeping a work buffer around is more efficient than creating a + ;; new temporary buffer. + (with-work-buffer + (work-buffer--prepare-pixelwise string buffer) + (set-window-buffer nil (current-buffer) 'keep-margins) + ;; Use a binary search to prune the number of calls to + ;; `window-text-pixel-size'. + ;; These are 1-based buffer indexes. + (let* ((low 1) + (high (1+ (length string))) + mid) + (when (> (car (window-text-pixel-size nil 1 high)) max-pixels) + (when (and ellipsis (not (stringp ellipsis))) + (setq ellipsis (truncate-string-ellipsis))) + (setq ellipsis-pixels (if ellipsis + (if ellipsis-pixels + ellipsis-pixels + (string-pixel-width ellipsis buffer)) + 0)) + (let ((adjusted-pixels + (if (> max-pixels ellipsis-pixels) + (- max-pixels ellipsis-pixels) + max-pixels))) + (while (<= low high) + (setq mid (floor (+ low high) 2)) + (if (<= (car (window-text-pixel-size nil 1 mid)) + adjusted-pixels) + (setq low (1+ mid)) + (setq high (1- mid)))))) + (set-window-buffer nil buffer 'keep-margins) + (if mid + ;; Binary search ran. + (if (and ellipsis (> max-pixels ellipsis-pixels)) + (concat (substring string 0 (1- high)) ellipsis) + (substring string 0 (1- high))) + ;; Fast path. + string))))) + ;;;###autoload (defun string-glyph-split (string) "Split STRING into a list of strings representing separate glyphs. diff --git a/test/lisp/misc-tests.el b/test/lisp/misc-tests.el index b6f5f01ad2a..5d0b9ae0604 100644 --- a/test/lisp/misc-tests.el +++ b/test/lisp/misc-tests.el @@ -25,6 +25,7 @@ (require 'ert) (require 'misc) +(require 'mule-util) (defmacro with-misc-test (original result &rest body) (declare (indent 2)) @@ -243,5 +244,84 @@ (setq-default display-line-numbers dln)) (should (= w0 w1)))) +;; Exercise `truncate-string-pixelwise' with strings of the same +;; characters of differing widths, with and without ellipses, in varying +;; faces, and varying face heights and compare results to each +;; character's measured width. +(ert-deftest misc-test-truncate-string-pixelwise () + (dolist (c '(?W ?X ?y ?1)) + (dolist (ellipsis `(nil "..." ,(truncate-string-ellipsis))) + (dolist (face '(fixed-pitch variable-pitch)) + (dolist (height '(1.0 0.5 1.5)) + (with-temp-buffer + (setq-local face-remapping-alist `((,face . default))) + (face-remap-add-relative 'default :height height) + (let ((char-pixels (string-pixel-width + (make-string 1 c) (current-buffer)))) + (dotimes (i 20) + (setq i (1+ i)) + (should (eq i (length + (truncate-string-pixelwise + (make-string (* i 2) c) + (* i char-pixels) + (current-buffer) + ellipsis)))))))))))) + +;; Exercise `truncate-string-pixelwise' with varying unicode strings, in +;; varying faces, and varying face heights and compare results to a +;; naive `string-pixel-width' based string truncate function. +(ert-deftest misc-test-truncate-string-pixelwise-unicode () + :tags '(:expensive-test) + (skip-when noninteractive) + (let ((max-pixels 500) + (truncate-string-naive (lambda (string pixels buffer) + (while (and (length> string 0) + (> (string-pixel-width string buffer) pixels)) + (setq string (substring string 0 (1- (length string))))) + string)) + (strings (list + "foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz" + (concat "話說天下大勢,分久必合,合久必分:周末七國分爭,并入於秦。" + "及秦滅之後,楚、漢分爭,又并入於漢。漢朝自高祖斬白蛇而起義," + "一統天下。後來光武中興,傳至獻帝,遂分為三國。推其致亂之由," + "殆始於桓、靈二帝。桓帝禁錮善類,崇信宦官。及桓帝崩,靈帝即位," + "大將軍竇武、太傅陳蕃,共相輔佐。時有宦官曹節等弄權,竇武、陳蕃謀誅之," + "作事不密,反為所害。中涓自此愈橫") + (concat "короче теперь если по русски написать все четко или все равно" + " короче теперь если по русски написать все четко или все равно" + " короче теперь если по русски написать все четко или все равно" + " короче теперь если по русски написать все четко или все равно") + "будет разрыв строки непонятно где🏁🚩🎌🏴🏳️ 🏳️ <200d>🌈🏳️ <200d>⚧️🏴<200d>☠️" + (apply #'concat (make-list 200 "\u0065\u0301 ")) ; composed é \u00E9 + (let ((woman-loves-man ; 👩‍❤️‍👨 + (concat "\N{WOMAN}" + "\N{ZERO WIDTH JOINER}" + "\N{HEAVY BLACK HEART}" + "\N{VARIATION SELECTOR-16}" + "\N{ZERO WIDTH JOINER}" + "\N{MAN}" + " "))) + (apply #'concat (make-list 200 woman-loves-man))) + (propertize (let ((varying-height-string + (mapconcat + #'identity + (list "AWi!" + (propertize "foo" 'face '(:height 2.5)) + (propertize "bar" 'face '(:height 0.5)) + (propertize "baz" 'face '(:height 1.0))) + " "))) + (apply #'concat (make-list 100 varying-height-string))) + 'face 'variable-pitch)))) + (dolist (face '(fixed-pitch variable-pitch)) + (dolist (height '(1.0 0.5 1.5)) + (with-temp-buffer + (setq-local face-remapping-alist `((,face . default))) + (face-remap-add-relative 'default :height height) + (dolist (string strings) + (should (eq (length (funcall truncate-string-naive + string max-pixels (current-buffer))) + (length (truncate-string-pixelwise + string max-pixels (current-buffer))))))))))) + (provide 'misc-tests) ;;; misc-tests.el ends here From f081afe23df938e027904d94664e3c5fe3f6b76c Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Sat, 31 Jan 2026 11:24:53 +0200 Subject: [PATCH 59/74] ; Improve documentation of 'condition-case-unless-debug' * doc/lispref/control.texi (Handling Errors): * lisp/subr.el (condition-case-unless-debug): Improve the documentation of 'condition-case-unless-debug'. (Bug#80234) --- doc/lispref/control.texi | 4 +++- lisp/subr.el | 8 +++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/doc/lispref/control.texi b/doc/lispref/control.texi index a4776030cf2..7c3f29c7226 100644 --- a/doc/lispref/control.texi +++ b/doc/lispref/control.texi @@ -2344,7 +2344,9 @@ the other usual filtering mechanisms say it should. @xref{Error Debugging}. The macro @code{condition-case-unless-debug} provides another way to handle debugging of such forms. It behaves exactly like @code{condition-case}, unless the variable @code{debug-on-error} is -non-@code{nil}, in which case it does not handle any errors at all. +non-@code{nil}, in which case it causes Emacs to enter the debugger +before executing any applicable handler. (The applicable handler, if +any, will still run when the debugger exits.) @end defmac Once Emacs decides that a certain handler handles the error, it diff --git a/lisp/subr.el b/lisp/subr.el index 40325c30326..fcf03dd4f67 100644 --- a/lisp/subr.el +++ b/lisp/subr.el @@ -5445,9 +5445,11 @@ If BODY finishes, `while-no-input' returns whatever value BODY produced." (t val))))))) (defmacro condition-case-unless-debug (var bodyform &rest handlers) - "Like `condition-case' except that it does not prevent debugging. -More specifically if `debug-on-error' is set then the debugger will be invoked -even if this catches the signal." + "Like `condition-case', except that it does not prevent debugging. +More specifically, if `debug-on-error' is set, then the debugger will +be invoked even if some handler catches the signal. +Note that this doesn't prevent the handler from executing, it just +causes the debugger to be called before running the handler." (declare (debug condition-case) (indent 2)) `(condition-case ,var ,bodyform From 1652e36c6c303f2934f6b03fd5b37088dac3d6e2 Mon Sep 17 00:00:00 2001 From: Jens Schmidt Date: Sun, 25 Jan 2026 13:57:21 +0100 Subject: [PATCH 60/74] ; Fix documentaion of 'seq-intersection' * doc/lispref/sequences.texi (Sequence Functions): * lisp/emacs-lisp/seq.el (seq-intersection): Fix documentaion of 'seq-intersection'. (Bug#80257) --- doc/lispref/sequences.texi | 6 +++--- lisp/emacs-lisp/seq.el | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/lispref/sequences.texi b/doc/lispref/sequences.texi index 4de739aa915..853b577c910 100644 --- a/doc/lispref/sequences.texi +++ b/doc/lispref/sequences.texi @@ -1110,9 +1110,9 @@ instead of the default @code{equal}. @cindex sequences, intersection of @cindex intersection of sequences This function returns a copy of @var{sequence1} from which the -elements that appear in @var{sequence2} where removed. If the optional -argument @var{function} is non-@code{nil}, it is a function of two -arguments to use to compare elements instead of the default +elements that do not appear in @var{sequence2} were removed. If the +optional argument @var{function} is non-@code{nil}, it is a function of +two arguments to use to compare elements instead of the default @code{equal}. @example diff --git a/lisp/emacs-lisp/seq.el b/lisp/emacs-lisp/seq.el index 4963624ee2d..881fae951b6 100644 --- a/lisp/emacs-lisp/seq.el +++ b/lisp/emacs-lisp/seq.el @@ -567,7 +567,7 @@ This does not modify SEQUENCE1 or SEQUENCE2." ;;;###autoload (cl-defgeneric seq-intersection (sequence1 sequence2 &optional testfn) - "Return copy of SEQUENCE1 with elements that appear in SEQUENCE2 removed. + "Return copy of SEQUENCE1 with elements that do not appear in SEQUENCE2 removed. \"Equality\" of elements is defined by the function TESTFN, which defaults to `equal'. This does not modify SEQUENCE1 or SEQUENCE2." From 39dc99518c6a481df1cc544c02461bd2eaff955f Mon Sep 17 00:00:00 2001 From: Kierin Bell Date: Fri, 23 Jan 2026 18:47:04 -0500 Subject: [PATCH 61/74] Add new input method for Tuscarora * lisp/leim/quail/iroquoian.el: New input method "tuscarora-postfix". * etc/NEWS: Announce the new input method. (Bug#80264) --- etc/NEWS | 8 +- lisp/leim/quail/iroquoian.el | 198 ++++++++++++++++++++++++++++++++--- 2 files changed, 190 insertions(+), 16 deletions(-) diff --git a/etc/NEWS b/etc/NEWS index 2cf91cfd5f7..8cd29a5659f 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -853,10 +853,10 @@ Northern Iroquoian language family: 'mohawk-postfix' (Mohawk [Kanien’kéha / Kanyen’kéha / Onkwehonwehnéha]), 'oneida-postfix' (Oneida [Onʌyote’a·ká· / Onyota’a:ká: / Ukwehuwehnéha]), 'cayuga-postfix' (Cayuga [Gayogo̱ho:nǫhnéha:ˀ]), 'onondaga-postfix' (Onondaga -[Onųdaʔgegáʔ]), and 'seneca-postfix' (Seneca [Onödowá’ga:’]). -Additionally, there is a general-purpose 'haudenosaunee-postfix' input -method to facilitate writing in the orthographies of the five languages -simultaneously. +[Onųdaʔgegáʔ]), 'seneca-postfix' (Seneca [Onödowá’ga:’]), and +'tuscarora-postfix' (Tuscarora [Skarù·ręʔ]). Additionally, there is a +general-purpose 'haudenosaunee-postfix' input method to facilitate +writing in the orthographies of the six languages simultaneously. --- *** New input methods for languages based on Burmese. diff --git a/lisp/leim/quail/iroquoian.el b/lisp/leim/quail/iroquoian.el index 0bd822217b3..748fadf1d09 100644 --- a/lisp/leim/quail/iroquoian.el +++ b/lisp/leim/quail/iroquoian.el @@ -24,7 +24,7 @@ ;; This file implements input methods for Northern Iroquoian languages. -;; Input methods are implemented for all Five Nations Iroquois +;; Input methods are implemented for the following Northern Iroquoian ;; languages: ;; - Mohawk (Kanien’kéha / Kanyen’kéha / Onkwehonwehnéha) @@ -32,6 +32,7 @@ ;; - Onondaga (Onųdaʔgegáʔ) ;; - Cayuga (Gayogo̱ho:nǫhnéha:ˀ) ;; - Seneca (Onödowá’ga:’) +;; - Tuscarora (Skarù·ręʔ) ;; A composite input method for all of the languages above is also ;; defined: `haudenosaunee-postfix'. @@ -39,7 +40,6 @@ ;; Input methods are not yet implemented for the remaining Northern ;; Iroquoian languages, including: -;; - Tuscarora (Skarù:ręʔ) ;; - Wendat (Huron) / Wyandot ;;; Code: @@ -798,6 +798,159 @@ simultaneously using the input method `haudenosaunee-postfix'." iroquoian-seneca-vowel-alist)) (quail-defrule key trans)) + +;;; Tuscarora + +;; +;; The primary community orthography used for Tuscarora follows that +;; used in Blair Rudes's dictionary (see below). +;; +;; Reference work for Tuscarora orthography: +;; +;; Blair Rudes. 1999. Tuscarora-English/English-Tuscarora +;; dictionary. Toronto: University of Toronto Press. +;; + +(defconst iroquoian-tuscarora-modifier-alist + '(("::" ?\N{MIDDLE DOT})) + "Alist of rules for modifier letters in Tuscarora input methods. +Entries are as with rules in `quail-define-rules'.") + +(defconst iroquoian-tuscarora-vowel-alist + '(("a'" ?á) + ("a`" ?à) + ("A'" ?Á) + ("A`" ?À) + ("e'" ?é) + ("e`" ?è) + ("E'" ?É) + ("E`" ?È) + ("i'" ?í) + ("i`" ?ì) + ("I'" ?Í) + ("I`" ?Ì) + ("u'" ?ú) + ("u`" ?ù) + ("U'" ?Ú) + ("U`" ?Ù) + ("e," ?ę) + ("e,'" ["ę́"]) + ("e,`" ["ę̀"]) + ("E," ?Ę) + ("E,'" ["Ę́"]) + ("E,`" ["Ę̀"]) + + ("a''" ["a'"]) + ("a``" ["a`"]) + ("A''" ["A'"]) + ("A``" ["A`"]) + ("e''" ["e'"]) + ("e``" ["e`"]) + ("E''" ["E'"]) + ("E``" ["E`"]) + ("i''" ["i'"]) + ("i``" ["i`"]) + ("I''" ["I'"]) + ("I``" ["I`"]) + ("u''" ["u'"]) + ("u``" ["u`"]) + ("U''" ["U'"]) + ("U``" ["U`"]) + + ("e,," ["e,"]) + ("e,''" ["ę'"]) + ("e,``" ["ę`"]) + ("E,," ["E,"]) + ("E,''" ["Ę'"]) + ("E,``" ["Ę`"])) + "Alist of rules for vowel letters in Tuscarora input methods. +Entries are as with rules in `quail-define-rules'.") + +(defconst iroquoian-tuscarora-consonant-alist + '((";;" ?\N{LATIN LETTER GLOTTAL STOP}) + ("c/" ?č) + ("c//" ["c/"]) + ("C/" ?Č) + ("C//" ["C/"]) + ("t/" ?θ) + ("t//" ["t/"])) + "Alist of rules for consonant letters in Tuscarora input methods. +Entries are as with rules in `quail-define-rules'.") + +(defconst iroquoian-tuscarora-exception-alist + '(("_" ?\N{COMBINING LOW LINE}) + ("__" ?_)) + "Alist of rules for phonological exception marking in Tuscarora input methods. +Entries are as with rules in `quail-define-rules'.") + +(quail-define-package + "tuscarora-postfix" "Tuscarora" "TUS<" t + "Tuscarora (Skarù·ręʔ) input method with postfix modifiers + +Modifiers: + +| Key | Translation | Description | +|-----+-------------+--------------------------| +| :: | · | Vowel length | + +Stress diacritics: + +| Key | Description | Example | +|------+--------------+---------| +| \\=' | Acute accent | a' -> á | +| \\=` | Grave accent | a` -> à | + +Doubling the postfix separates the letter and the postfix. + +Vowels: + +| Key | Translation | Description | +|-----+-------------+---------------------------------| +| e, | ę | Mid front nasal vowel | +| E, | Ę | Mid front nasal vowel (capital) | + +a, e, i, and u are bound to a single key. + +Consonants: + +| Key | Translation | Description | +|-------+-------------+------------------------------------| +| ;; | ˀ | Glottal stop | +| c/ | č | Postalveolar affricate | +| C/ | Č | Postalveolar affricate (capital) | +| t/ | θ | Voiceless dental fricative | + +h, k, n, r, s, t, w, and y are bound to a single key. + +b, l, m, and p are used rarely in loanwords. They are also each bound +to a single key. + +Stress exception markers: + +| Key | Description | Example | +|-----+--------------------+----------| +| _ | Combining low line | a_ -> a̲ | + +Note: Not all fonts can properly display a combining low line on all +letters. + +Underlining has been used by some to indicate that vowels behave +exceptionally with regard to stress placement. Alternatively, markup or +other methods can be used to create an underlining effect. + +To enter a plain underscore, type the underscore twice. + +All Haudenosaunee languages, including Tuscarora can be input +simultaneously using the input method `haudenosaunee-postfix'." + nil t nil nil nil nil nil nil nil nil t) + +(pcase-dolist (`(,key ,trans) + (append iroquoian-tuscarora-modifier-alist + iroquoian-tuscarora-consonant-alist + iroquoian-tuscarora-vowel-alist + iroquoian-tuscarora-exception-alist)) + (quail-defrule key trans)) + ;;; Haudenosaunee (composite Northern Iroquoian) @@ -857,7 +1010,8 @@ simultaneously using the input method `haudenosaunee-postfix'." iroquoian-oneida-modifier-alist iroquoian-onondaga-modifier-alist iroquoian-cayuga-modifier-alist - iroquoian-seneca-modifier-alist)) + iroquoian-seneca-modifier-alist + iroquoian-tuscarora-modifier-alist)) "Alist of rules for modifier letters in Haudenosaunee input methods. Entries are as with rules in `quail-define-rules'.") @@ -866,7 +1020,8 @@ Entries are as with rules in `quail-define-rules'.") iroquoian-oneida-vowel-alist iroquoian-onondaga-vowel-alist iroquoian-cayuga-vowel-alist - iroquoian-seneca-vowel-alist)) + iroquoian-seneca-vowel-alist + iroquoian-tuscarora-vowel-alist)) "Alist of rules for vowel letters in Haudenosaunee input methods. Entries are as with rules in `quail-define-rules'.") @@ -879,16 +1034,17 @@ Entries are as with rules in `quail-define-rules'.") iroquoian-oneida-consonant-alist iroquoian-onondaga-consonant-alist iroquoian-cayuga-consonant-alist - iroquoian-seneca-consonant-alist) + iroquoian-seneca-consonant-alist + iroquoian-tuscarora-consonant-alist) (lambda (c1 c2) (equal (car c1) (car c2)))) "Alist of rules for consonant letters in Haudenosaunee input methods. Entries are as with rules in `quail-define-rules'.") -(defconst iroquoian-haudenosaunee-devoicing-alist +(defconst iroquoian-haudenosaunee-exception-alist '(("_" ?\N{COMBINING LOW LINE}) ("__" ?_)) - "Alist of rules for devoicing characters in Haudenosaunee input methods. + "Alist of rules for phonological exception markers in Haudenosaunee input methods. Entries are as with rules in `quail-define-rules'.") (defconst iroquoian-haudenosaunee-nasal-alist iroquoian-onondaga-nasal-alist @@ -906,6 +1062,7 @@ This input method can be used to enter the following languages: - Cayuga (Gayogo̱ho:nǫhnéha:ˀ) - Onondaga (Onųdaʔgegáʔ) - Seneca (Onödowá’ga:’) +- Tuscarora (Skarù·ręʔ) Modifiers: @@ -989,6 +1146,12 @@ Vowels: | a\" | ä | Low front vowel | | A\" | Ä | Low front vowel (capital) | | Single-key vowels: a e i o u | +|----------------------------------------------------------------------| +| Tuscarora | +| -------------------------------------------------------------------- | +| e, | ę | Mid front nasal vowel | +| E, | Ę | Mid front nasal vowel (capital) | +| Single-key vowels: a e i u | Consonants: @@ -1023,8 +1186,16 @@ Consonants: | s/ | š | Voiceless postalveolar fricative | | S/ | Š | Voiceless postalveolar fricative (capital) | | Single-key consonants: d g h j k n s t w y z (b m p) | +|----------------------------------------------------------------------| +| Tuscarora | +| -------------------------------------------------------------------- | +| ;: | ʔ | Glottal stop (alternate) | +| c/ | č | Postalveolar affricate | +| C/ | Č | Postalveolar affricate (capital) | +| t/ | θ | Voiceless dental fricative | +| Single-key consonants: h k n r s t w y (b l m p) | -Devoicing: +Phonological exception markers: | Key | Description | Examples | |-----+------------------------+------------------------------| @@ -1035,8 +1206,10 @@ Note: Not all fonts can properly display a combining low line on all letters and a combining macron below on all vowels. Underlining is commonly used in Oneida to indicate devoiced syllables on -pre-pausal forms (also called utterance-final forms). Alternatively, -markup or other methods can be used to create an underlining effect. +pre-pausal forms (also called utterance-final forms), and it has been +used in some Tuscarora orthographies to indicate that vowels behave +exceptionally with regard to stress placement. Alternatively, markup or +other methods can be used to create an underlining effect. To enter a plain underscore, the underscore twice. @@ -1046,7 +1219,8 @@ To enter a plain hyphen after a vowel, simply type the hyphen twice. There are individual input methods for each of the languages that can be entered with this input method: `mohawk-postfix', `oneida-postfix', -`onondaga-postfix', `cayuga-postfix', `seneca-postfix'." +`onondaga-postfix', `cayuga-postfix', `seneca-postfix', +`tuscarora-postfix'.." nil t nil nil nil nil nil nil nil nil t) (pcase-dolist (`(,key ,trans) @@ -1054,7 +1228,7 @@ entered with this input method: `mohawk-postfix', `oneida-postfix', iroquoian-haudenosaunee-consonant-alist iroquoian-haudenosaunee-nasal-alist iroquoian-haudenosaunee-vowel-alist - iroquoian-haudenosaunee-devoicing-alist)) + iroquoian-haudenosaunee-exception-alist)) (quail-defrule key trans)) (provide 'iroquoian) From cd152ea6114cda81b839a465af5ebd09fb342b09 Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Sat, 31 Jan 2026 12:06:53 +0200 Subject: [PATCH 62/74] ; Fix last change * lisp/leim/quail/iroquoian.el (iroquoian-haudenosaunee-exception-alist): Doc fix. --- lisp/leim/quail/iroquoian.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp/leim/quail/iroquoian.el b/lisp/leim/quail/iroquoian.el index 748fadf1d09..66aea7da38a 100644 --- a/lisp/leim/quail/iroquoian.el +++ b/lisp/leim/quail/iroquoian.el @@ -1044,7 +1044,7 @@ Entries are as with rules in `quail-define-rules'.") (defconst iroquoian-haudenosaunee-exception-alist '(("_" ?\N{COMBINING LOW LINE}) ("__" ?_)) - "Alist of rules for phonological exception markers in Haudenosaunee input methods. + "Rules' alist for phonological exception markers in Haudenosaunee input methods. Entries are as with rules in `quail-define-rules'.") (defconst iroquoian-haudenosaunee-nasal-alist iroquoian-onondaga-nasal-alist From 049eefa611912c6894c5fdeef2127ab2165e0144 Mon Sep 17 00:00:00 2001 From: "Jacob S. Gordon" Date: Mon, 26 Jan 2026 16:20:00 -0500 Subject: [PATCH 63/74] display-time: Add option to customize help-echo format This option controls the format of the help-echo when hovering over the time display in mode line. (Bug#80143) * lisp/time.el (display-time-help-echo-format): Add option. (display-time-string-forms): Use it. * etc/NEWS (Time): Announce the new option. --- etc/NEWS | 5 +++++ lisp/time.el | 16 +++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/etc/NEWS b/etc/NEWS index 8cd29a5659f..8929fcc1215 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -3338,6 +3338,11 @@ each refresh. The sort direction can be controlled by using a cons cell of a format string and a boolean. Alternatively, a sorting function can be provided directly. +--- +*** New user option 'display-time-help-echo-format'. +This option controls the format of the help echo when hovering over the +time. + ** Fill +++ diff --git a/lisp/time.el b/lisp/time.el index c78a51e9f97..f553ebab413 100644 --- a/lisp/time.el +++ b/lisp/time.el @@ -177,6 +177,18 @@ depend on `display-time-day-and-date' and `display-time-24hr-format'." :type '(choice (const :tag "Default" nil) string)) +(defcustom display-time-help-echo-format "%a %b %e, %Y" + "Format for the help echo when hovering over the time in the mode line. +Use the function `customize-variable' to choose a common format, and/or +see the function `format-time-string' for an explanation of the syntax." + :version "31.1" + :type `(choice + ,@(mapcar #'(lambda (fmt) + (list 'const + ':tag (format-time-string fmt 0 "UTC") fmt)) + '("%a %b %e, %Y" "%F (%a)" "%a %D")) + (string :tag "Format string"))) + (defcustom display-time-string-forms '((if (and (not display-time-format) display-time-day-and-date) (format-time-string "%a %b %e " now) @@ -186,7 +198,9 @@ depend on `display-time-day-and-date' and `display-time-24hr-format'." (if display-time-24hr-format "%H:%M" "%-I:%M%p")) now) 'face 'display-time-date-and-time - 'help-echo (format-time-string "%a %b %e, %Y" now)) + 'help-echo (format-time-string (if (stringp display-time-help-echo-format) + display-time-help-echo-format + "%a %b %e, %Y") now)) load (if mail ;; Build the string every time to act on customization. From 046f5ef018997e8ef60d2157864ed7d02934d81f Mon Sep 17 00:00:00 2001 From: Boris Buliga Date: Sat, 31 Jan 2026 12:18:08 +0200 Subject: [PATCH 64/74] Fix macOS 26 (Tahoe) scrolling lag and input handling issues macOS 26 introduced new event processing behavior that causes scrolling lag and input handling problems in Emacs. This patch disables two features via NSUserDefaults when built against the macOS 26 SDK: - NSEventConcurrentProcessingEnabled - NSApplicationUpdateCycleEnabled This fix is based on the equivalent patch in emacs-mac by Mitsuharu Yamamoto. See: https://bitbucket.org/mituharu/emacs-mac/commits/e52ebfd * src/nsterm.m (ns_term_init): Disable problematic event processing when built for macOS 26+. (Bug#80268) --- src/nsterm.m | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/nsterm.m b/src/nsterm.m index ca06195a798..c852b70be74 100644 --- a/src/nsterm.m +++ b/src/nsterm.m @@ -5838,6 +5838,15 @@ static Lisp_Object ns_new_font (struct frame *f, Lisp_Object font_object, ns_pending_service_names = [[NSMutableArray alloc] init]; ns_pending_service_args = [[NSMutableArray alloc] init]; +#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 260000 + /* Disable problematic event processing on macOS 26 (Tahoe) to avoid + scrolling lag and input handling issues. These are undocumented + options as of macOS 26.0. */ + [NSUserDefaults.standardUserDefaults + registerDefaults:@{@"NSEventConcurrentProcessingEnabled" : @"NO", + @"NSApplicationUpdateCycleEnabled" : @"NO"}]; +#endif + /* Start app and create the main menu, window, view. Needs to be here because ns_initialize_display_info () uses AppKit classes. The view will then ask the NSApp to stop and return to Emacs. */ From f7edfdcfd465f3ac9b0f870b2bf0b9a3a234bd9d Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Sat, 31 Jan 2026 12:59:17 +0200 Subject: [PATCH 65/74] ; Fix documentation of 'help-fns-describe-function-functions' * lisp/help-fns.el (help-fns-describe-function-functions): Doc fix (bug#80291). --- lisp/help-fns.el | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lisp/help-fns.el b/lisp/help-fns.el index 6cbc75f92fb..73066dd6f3d 100644 --- a/lisp/help-fns.el +++ b/lisp/help-fns.el @@ -40,8 +40,8 @@ (defvar help-fns-describe-function-functions nil "List of functions to run in help buffer in `describe-function'. -Those functions will be run after the header line and argument -list was inserted, and before the documentation is inserted. +Those functions will be run after the header line, the argument +list, and the function's documentation are inserted. The functions will be called with one argument: the function's symbol. They can assume that a newline was output just before they were called, and they should terminate any of their own output with a newline. From b75bfa219ecc8baed724b1da989582c5c6ed753e Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Sat, 31 Jan 2026 13:13:57 +0200 Subject: [PATCH 66/74] ; * admin/authors.el (authors-aliases): Add Boris Buliga. --- admin/authors.el | 1 + 1 file changed, 1 insertion(+) diff --git a/admin/authors.el b/admin/authors.el index afcc56c6003..41653b8bddf 100644 --- a/admin/authors.el +++ b/admin/authors.el @@ -69,6 +69,7 @@ files.") (nil "BlaCk_Void" "alstjr7375@daum\\.net") (nil "bug-gnu-emacs@gnu\\.org") ; mistake ("Björn Torkelsson" "Bjorn Torkelsson") + ("Boris Buliga" "boris@d12frosted\\.io") (nil "brandon\\.irizarry@gmail\\.com") ("Brian Fox" "Brian J\\. Fox") ("Brian P Templeton" "BT Templeton") From 346f1bda6bf07776587bacb19f19d82ee026220c Mon Sep 17 00:00:00 2001 From: Michael Albinus Date: Sat, 31 Jan 2026 13:37:56 +0100 Subject: [PATCH 67/74] Improve connection-local variables documentation. * doc/emacs/custom.texi (Connection Variables): * doc/lispref/variables.texi (Applying Connection Local Variables): Improve documentation. --- doc/emacs/custom.texi | 78 ++++++++++++++++++++++++++++---------- doc/lispref/variables.texi | 13 +++++++ 2 files changed, 72 insertions(+), 19 deletions(-) diff --git a/doc/emacs/custom.texi b/doc/emacs/custom.texi index 7663e2b21df..d79bcf3fe0f 100644 --- a/doc/emacs/custom.texi +++ b/doc/emacs/custom.texi @@ -1615,24 +1615,30 @@ your preference, such as @code{ws-butler-mode}. @cindex per-connection local variables Most of the variables reflect the situation on the local machine. -Often, they must use a different value when you operate in buffers -with a remote default directory. Think about the behavior when -calling @code{shell} -- on your local machine, you might use -@file{/bin/bash} and rely on termcap, but on a remote machine, it may -be @file{/bin/ksh} and terminfo. +Often, they must use a different value when you operate in buffers with +a remote default directory. Think about the behavior when calling +@code{shell} --- on your local machine, you might use @file{/bin/bash} +and rely on termcap, but on a remote machine, it may be @file{/bin/ksh} +and terminfo. - This can be accomplished with @dfn{connection-local variables}. -Directory and file local variables override connection-local -variables. Unsafe connection-local variables are handled in the same -way as unsafe file-local variables (@pxref{Safe File Variables}). + This can be accomplished with @dfn{connection-local variables}. Such +variables are declared depending on the value of +@code{default-directory} of the current buffer. When a buffer has a +remote @code{default-directory}, and there exist a connection-local +variable which matches @code{default-directory}, this alternative value +of the variable is used. Directory and file local variables override +connection-local variables. Unsafe connection-local variables are +handled in the same way as unsafe file-local variables (@pxref{Safe File +Variables}). @findex connection-local-set-profile-variables @findex connection-local-set-profiles - Connection-local variables are declared as a group of -variables/value pairs in a @dfn{profile}, using the + Connection-local variables are declared as a group of variables/value +pairs in a @dfn{profile}, using the @code{connection-local-set-profile-variables} function. The function -@code{connection-local-set-profiles} activates profiles for a given -criteria, identifying a remote machine: +@code{connection-local-set-profiles} declares profiles for a given +criteria (the first argument), identifying a remote machine with respect +to @code{default-directory} of the current buffer: @example (connection-local-set-profile-variables 'remote-terminfo @@ -1654,12 +1660,46 @@ criteria, identifying a remote machine: This code declares three different profiles, @code{remote-terminfo}, @code{remote-ksh}, and @code{remote-bash}. The profiles -@code{remote-terminfo} and @code{remote-ksh} are applied to all -buffers which have a remote default directory matching the regexp -@code{"remotemachine"} as host name. Such a criteria can also -discriminate for the properties @code{:protocol} (this is the Tramp -method) or @code{:user} (a remote user name). The @code{nil} criteria -matches all buffers with a remote default directory. +@code{remote-terminfo} and @code{remote-ksh} are applied to all buffers +which have a remote @code{default-directory} matching the string +@code{"remotemachine"} as host name. + + Criteria, the first argument of @code{connection-local-set-profiles}, +specifies, how the profiles match @code{default-directory}. It is a +plist identifying a connection and the application using this +connection. Property names might be @code{:application}, +@code{:protocol}, @code{:user} and @code{:machine}. The property value +of @code{:application} is a symbol, all other property values are +strings. In general the symbol @code{tramp} should be used as +@code{:application} value. Some packages use a different +@code{:application} (for example @code{eshell} or @code{vc-git}); they +say it in their documentation then. All properties are optional. + + The other properties are used for checking @code{default-directory}. +The propertiy @code{:protocol} is used for the method a remote +@code{default-directory} uses, the property +@code{:user} is the remote user name, and the property @code{:machine} +is the remote host name. All checks are performed via +@code{string-equal}. The @code{nil} criteria matches all buffers +with a remote default directory. + + Connection-local variables are not activated by default. A package +which uses connection-local variables must activate them for a given +buffer, specifying for which @code{:application} it uses them. +@xref{Applying Connection Local Variables,,, elisp, The Emacs Lisp +Reference Manual}, for details. + + After the above definition of profiles and their activation, any +connection made by Tramp to the @samp{remotemachine} system will use + +@itemize +@item @code{t} as the connection-specific value of @code{system-uses-terminfo}, +@item @samp{dumb-emacs-ansi} as the connection-specific value of +@code{comint-terminfo-terminal}, +@item @samp{/bin/ksh} as the connection-specific value of as +@code{shell-file-name}, +@item @samp{-c} as the connection-specific value of @code{shell-command-switch}. +@end itemize Be careful when declaring different profiles with the same variable, and setting these profiles to criteria which could match in parallel. diff --git a/doc/lispref/variables.texi b/doc/lispref/variables.texi index 29a272eec92..2f5ee037f3e 100644 --- a/doc/lispref/variables.texi +++ b/doc/lispref/variables.texi @@ -2653,6 +2653,19 @@ This macro returns the connection-local value of @var{symbol} for If @var{symbol} does not have a connection-local binding, the value is the default binding of the variable. + +The difference to @code{with-connection-local@{-application@}-variables} +is, that @code{symbol} is not set buffer-local. A typical usage pattern +is to use only the the connection value of a variable if it exists, and +not to use its default value otherwise (using @code{my-app-variable} +initialized above): + +@lisp +(if (connection-local-p my-app-variable 'my-app) + (connection-local-value my-app-variable 'my-app) + ;; Something else. + ) +@end lisp @end defmac @defvar enable-connection-local-variables From e08efecd96bbe1fa15fc0d94f631acadef6566de Mon Sep 17 00:00:00 2001 From: Jens Schmidt Date: Sun, 25 Jan 2026 13:57:21 +0100 Subject: [PATCH 68/74] Improve documentation of 'seq-difference' * doc/lispref/sequences.texi (Sequence Functions): * lisp/emacs-lisp/seq.el (seq-difference): Clarify the documentation of 'seq-difference'. (Bug#80257) --- doc/lispref/sequences.texi | 9 +++++---- lisp/emacs-lisp/seq.el | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/doc/lispref/sequences.texi b/doc/lispref/sequences.texi index 853b577c910..afee255346c 100644 --- a/doc/lispref/sequences.texi +++ b/doc/lispref/sequences.texi @@ -1125,10 +1125,11 @@ two arguments to use to compare elements instead of the default @defun seq-difference sequence1 sequence2 &optional function - This function returns a list of the elements that appear in -@var{sequence1} but not in @var{sequence2}. If the optional argument -@var{function} is non-@code{nil}, it is a function of two arguments to -use to compare elements instead of the default @code{equal}. + This function returns a copy of @var{sequence1} from which the +elements that appear in @var{sequence2} were removed. If the optional +argument @var{function} is non-@code{nil}, it is a function of two +arguments to use to compare elements instead of the default +@code{equal}. @example @group diff --git a/lisp/emacs-lisp/seq.el b/lisp/emacs-lisp/seq.el index 881fae951b6..e0a41b380b5 100644 --- a/lisp/emacs-lisp/seq.el +++ b/lisp/emacs-lisp/seq.el @@ -579,7 +579,7 @@ This does not modify SEQUENCE1 or SEQUENCE2." '())) (cl-defgeneric seq-difference (sequence1 sequence2 &optional testfn) - "Return list of all the elements that appear in SEQUENCE1 but not in SEQUENCE2. + "Return copy of SEQUENCE1 with elements that appear in SEQUENCE2 removed. \"Equality\" of elements is defined by the function TESTFN, which defaults to `equal'. This does not modify SEQUENCE1 or SEQUENCE2." From 385bcc6117349363a562871815005c87f9483590 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Engdeg=C3=A5rd?= Date: Sat, 31 Jan 2026 15:52:26 +0100 Subject: [PATCH 69/74] Fix lazy doc string fontify bug in elisp-byte-code-mode (bug#80292) * lisp/progmodes/elisp-mode.el (elisp-byte-code-syntax-propertize): Reset point to just after the start of the previous match so that we don't skip past the end of the lazy string, which can happen if it's zero-length; that could lead to an infinite loop. --- lisp/progmodes/elisp-mode.el | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lisp/progmodes/elisp-mode.el b/lisp/progmodes/elisp-mode.el index c4fb6946aeb..14cc5abd6a8 100644 --- a/lisp/progmodes/elisp-mode.el +++ b/lisp/progmodes/elisp-mode.el @@ -1783,7 +1783,9 @@ and `eval-expression-print-level'. (funcall (syntax-propertize-rules (emacs-lisp-byte-code-comment-re - (1 (prog1 "< b" (elisp--byte-code-comment end (point)))))) + (1 (prog1 "< b" + (goto-char (match-end 1)) + (elisp--byte-code-comment end (point)))))) start end)) ;;;###autoload From 87dfb040b08adf22cea41115adaa913997c04437 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Engdeg=C3=A5rd?= Date: Sat, 31 Jan 2026 15:55:47 +0100 Subject: [PATCH 70/74] Don't produce zero-length lazy strings * lisp/emacs-lisp/bytecomp.el (byte-compile--docstring): There is no gain from making an empty string lazy. (It also contributed to bug#80292.) --- lisp/emacs-lisp/bytecomp.el | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lisp/emacs-lisp/bytecomp.el b/lisp/emacs-lisp/bytecomp.el index edfd9491a2f..949a1a5e517 100644 --- a/lisp/emacs-lisp/bytecomp.el +++ b/lisp/emacs-lisp/bytecomp.el @@ -1856,7 +1856,8 @@ It is too wide if it has any lines longer than the largest of ;; The native compiler doesn't use those dynamic docstrings. (not byte-native-compiling) ;; Docstrings can only be dynamic when compiling a file. - byte-compile--\#$) + byte-compile--\#$ + (not (equal doc ""))) ; empty lazy strings are pointless (let* ((byte-pos (with-memoization ;; Reuse a previously written identical docstring. ;; This is not done out of thriftiness but to try and From 967294d2cb4db828b514293f4b32d8ca7caadf39 Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Sun, 1 Feb 2026 08:49:57 +0200 Subject: [PATCH 71/74] Fix desktop saving and restoring in daemon sessions * lisp/desktop.el (desktop--check-dont-save): Don't save daemon's initial frame. * lisp/frameset.el (frameset-restore): Don't try deleting the daemon's initial frame. (Bug#80294) --- lisp/desktop.el | 10 ++++++++-- lisp/frameset.el | 15 ++++++++++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/lisp/desktop.el b/lisp/desktop.el index 74961032303..df98079b1c2 100644 --- a/lisp/desktop.el +++ b/lisp/desktop.el @@ -1064,13 +1064,19 @@ DIRNAME must be the directory in which the desktop file will be saved." ;; ---------------------------------------------------------------------------- (defun desktop--check-dont-save (frame) - (not (frame-parameter frame 'desktop-dont-save))) + (and (not (frame-parameter frame 'desktop-dont-save)) + ;; Don't save daemon initial frames, since we cannot (and don't + ;; need to) restore them. + (not (and (daemonp) + (equal (terminal-name (frame-terminal frame)) + "initial_terminal"))))) (defconst desktop--app-id `(desktop . ,desktop-file-version)) (defun desktop-save-frameset () "Save the state of existing frames in `desktop-saved-frameset'. -Frames with a non-nil `desktop-dont-save' parameter are not saved." +Frames with a non-nil `desktop-dont-save' parameter are not saved. +Likewise the initial frame of a daemon sesion." (setq desktop-saved-frameset (and desktop-restore-frames (frameset-save nil diff --git a/lisp/frameset.el b/lisp/frameset.el index 85a90f67c68..e11a1da7e9b 100644 --- a/lisp/frameset.el +++ b/lisp/frameset.el @@ -1362,9 +1362,18 @@ All keyword parameters default to nil." ;; Clean up the frame list (when cleanup-frames (let ((map nil) - (cleanup (if (eq cleanup-frames t) - (lambda (frame action) - (when (memq action '(:rejected :ignored)) + (cleanup + (if (eq cleanup-frames t) + (lambda (frame action) + (when (and (memq action '(:rejected :ignored)) + ;; Don't try deleting the daemon's initial + ;; frame, as that would only trigger + ;; warnings. + (not + (and (daemonp) + (equal (terminal-name (frame-terminal + frame)) + "initial_terminal")))) (delete-frame frame))) cleanup-frames))) (maphash (lambda (frame _action) (push frame map)) frameset--action-map) From ac07913bd81db8ffdab2754cc6f75158460ee02b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Engdeg=C3=A5rd?= Date: Sun, 1 Feb 2026 12:46:45 +0100 Subject: [PATCH 72/74] ; * lisp/progmodes/elisp-mode.el: slightly better rescanning point Suggested by Stefan Monnier. --- lisp/progmodes/elisp-mode.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp/progmodes/elisp-mode.el b/lisp/progmodes/elisp-mode.el index 14cc5abd6a8..f5c3dc3fbb2 100644 --- a/lisp/progmodes/elisp-mode.el +++ b/lisp/progmodes/elisp-mode.el @@ -1784,7 +1784,7 @@ and `eval-expression-print-level'. (syntax-propertize-rules (emacs-lisp-byte-code-comment-re (1 (prog1 "< b" - (goto-char (match-end 1)) + (goto-char (match-end 2)) (elisp--byte-code-comment end (point)))))) start end)) From 2652e11930125b5b5e28f3f01e73b07eacb5ef75 Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Sun, 1 Feb 2026 17:26:56 +0200 Subject: [PATCH 73/74] Minor improvements in vertical cusror motion * src/xdisp.c (move_it_vertically_backward): Zero out cached value of line height, to avoid using stale and incorrect values. (try_window_reusing_current_matrix): Fix conditions for changes in tab-line height. Reported by Michael Heerdegen in https://lists.gnu.org/archive/html/help-gnu-emacs/2026-01/msg00163.html This improves the scrolling a little bit, but doesn't solve the problem entirely. --- src/xdisp.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/xdisp.c b/src/xdisp.c index fa826c366dd..22e178fcdc9 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -11300,6 +11300,7 @@ move_it_vertically_backward (struct it *it, int dy) int line_height; RESTORE_IT (&it3, &it3, it3data); + last_height = 0; y1 = line_bottom_y (&it3); line_height = y1 - y0; RESTORE_IT (it, it, it2data); @@ -21673,8 +21674,9 @@ try_window_reusing_current_matrix (struct window *w) return false; /* If top-line visibility has changed, give up. */ - if (window_wants_tab_line (w) - != MATRIX_TAB_LINE_ROW (w->current_matrix)->mode_line_p) + if (!w->current_matrix->header_line_p + && (window_wants_tab_line (w) + != MATRIX_TAB_LINE_ROW (w->current_matrix)->mode_line_p)) return false; /* If top-line visibility has changed, give up. */ From 8f5badc26bc47b0964f299cdb81354aff3e952b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Engdeg=C3=A5rd?= Date: Sun, 1 Feb 2026 18:16:44 +0100 Subject: [PATCH 74/74] * etc/symbol-releases.eld: 'any' and 'all' added in Emacs 31 --- etc/symbol-releases.eld | 2 ++ 1 file changed, 2 insertions(+) diff --git a/etc/symbol-releases.eld b/etc/symbol-releases.eld index 3c666423cc0..225e7d86cff 100644 --- a/etc/symbol-releases.eld +++ b/etc/symbol-releases.eld @@ -9,6 +9,8 @@ ;; TYPE being `fun' or `var'. ( + ("31.1" fun any) + ("31.1" fun all) ("30.1" fun dired-click-to-select-mode) ("30.1" var dired-click-to-select-mode) ("29.1" fun plistp)