From a24ff52a79b4ef91fa36cba1406e4112b24fa167 Mon Sep 17 00:00:00 2001 From: Eshel Yaron Date: Thu, 30 Apr 2026 21:19:19 +0200 Subject: [PATCH 01/50] New variable 'completion-preview-is-calling' * lisp/completion-preview.el (completion-preview-is-calling): New variable. (completion-preview--capf-wrapper): Bind it to t when calling the CAPF. * etc/NEWS: Announce it. --- etc/NEWS | 5 +++++ lisp/completion-preview.el | 13 ++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/etc/NEWS b/etc/NEWS index c54941382c3..8f266f2d670 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -4489,6 +4489,11 @@ 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. +--- +** New variable 'completion-preview-is-calling'. +Completion functions (in 'completion-at-point-functions') can check this +variable to tell if they are being called by Completion Preview mode. + ** Special Events +++ diff --git a/lisp/completion-preview.el b/lisp/completion-preview.el index ff348ebf9af..907068fdb67 100644 --- a/lisp/completion-preview.el +++ b/lisp/completion-preview.el @@ -523,9 +523,20 @@ candidates or if there are multiple matching completions and (setq sorted (cdr sorted))) (list (substring string 0 base) common suffixes)))))) +(defvar completion-preview-is-calling nil + "Non-nil while Completion Preview mode is calling a completion function. + +Completion functions (in `completion-at-point-functions') can check this +variable and adjust their behavior for completion preview. +For example, a completion function can skip calculating annotations for +candidates it produces for display in the completion preview, which does +not make use of such annotations.") + (defun completion-preview--capf-wrapper (capf) "Translate return value of CAPF to properties for completion preview overlay." - (let ((res (ignore-errors (funcall capf)))) + (let ((res (ignore-errors + (let ((completion-preview-is-calling t)) + (funcall capf))))) (and (consp res) (not (functionp res)) (seq-let (beg end table &rest plist) res From 40b6f0180b378d26fac2f20b1974d89807ea2b44 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Fri, 3 Apr 2026 17:35:42 -0700 Subject: [PATCH 02/50] ; Tweak some ERC tests and related utilities * test/lisp/erc/erc-scenarios-keep-place-indicator.el (erc-scenarios-keep-place-indicator--follow): Intersperse more `redisplay' calls to update the indicator's overlay. * test/lisp/erc/erc-tests.el (erc-tests--assert-printed-in-subprocess): Wrap CODE form in keyword sentinel. (erc--find-mode, erc--essential-hook-ordering): Use modified interface. (erc--find-group--real, erc--find-group/realistic): Rename former to latter and run in subprocess. (erc--update-modules/realistic): Redo to run in subprocess instead of mocking. * test/lisp/erc/resources/erc-d/erc-d-t.el (erc-d-t-kill-related-buffers): Don't bother canceling `erc-server-flood-timer', even in an actual ERC buffer, since `erc-server-send-queue' first checks whether its BUFFER argument is still live before sending anything to the process. Also, don't bother collecting buffers only to immediately kill them. * test/lisp/erc/resources/erc-d/erc-d.el (erc-d--filter): Always clear remainder. Otherwise, partial emissions from the peer that aren't terminated by a newline will confuse subsequent processing. * test/lisp/erc/resources/erc-scenarios-common.el (erc-scenarios-common--run-in-term): Look for a library called `erc-tests-compat', which ERC uses in its external CI to provide compatibility shims of definitions too obscure or unfit for inclusion in the Compat package on ELPA. * test/lisp/erc/resources/erc-tests-common.el (erc-tests-common-kill-buffers): Also kill non-`erc-mode' buffers whose names match a scheme used by ERC for work buffers. Allow for the EXTRA-BUFFERS argument to possibly contain killed and null buffers. --- .../erc/erc-scenarios-keep-place-indicator.el | 5 + test/lisp/erc/erc-tests.el | 113 ++++++++++-------- test/lisp/erc/resources/erc-d/erc-d-t.el | 28 ++--- test/lisp/erc/resources/erc-d/erc-d.el | 3 +- .../erc/resources/erc-scenarios-common.el | 7 ++ test/lisp/erc/resources/erc-tests-common.el | 13 +- 6 files changed, 100 insertions(+), 69 deletions(-) diff --git a/test/lisp/erc/erc-scenarios-keep-place-indicator.el b/test/lisp/erc/erc-scenarios-keep-place-indicator.el index 332247667eb..ee2729ddf8f 100644 --- a/test/lisp/erc/erc-scenarios-keep-place-indicator.el +++ b/test/lisp/erc/erc-scenarios-keep-place-indicator.el @@ -79,6 +79,7 @@ (switch-to-buffer (erc-d-t-wait-for 10 (get-buffer "#spam"))) ; lower (other-window 1) (switch-to-buffer "#spam") ; upper + (redisplay) (erc-scenarios-common-say "one") (funcall expect 10 "Ay, the heads") @@ -94,6 +95,7 @@ ;; Lower window is still centered at start. (other-window 1) (switch-to-buffer "#chan") + (redisplay) (save-excursion (goto-char (window-point)) (should (looking-back (rx " tester, welcome!"))) @@ -107,8 +109,10 @@ (other-window 1) ; upper still at indicator, switches first (switch-to-buffer "#spam") + (redisplay) (other-window 1) (switch-to-buffer "#spam") ; lower follows, speaks to sync + (redisplay) (erc-scenarios-common-say "two") (funcall expect 10 " Cause they take") (goto-char (point-max)) @@ -116,6 +120,7 @@ ;; Upper switches back first, finds indicator gone. (other-window 1) (switch-to-buffer "#chan") + (redisplay) (save-excursion (goto-char (window-point)) (should (looking-back (rx " tester, welcome!"))) diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el index 6d0172e98fc..dd439e73fc9 100644 --- a/test/lisp/erc/erc-tests.el +++ b/test/lisp/erc/erc-tests.el @@ -3729,11 +3729,14 @@ keyword :result." (get-buffer-create (concat "*" (symbol-name (ert-test-name (ert-running-test))) "*")) (unwind-protect - (let ((proc (erc-tests-common-create-subprocess code '("-batch") nil))) + (let ((proc (erc-tests-common-create-subprocess + `(,@(butlast code) (prin1 (list :result ,@(last code)))) + '("-batch") nil))) (while (accept-process-output proc 10)) (goto-char (point-min)) - (search-forward "(:result " nil t) - (unless (equal (ignore-errors (read (current-buffer))) expected) + (unless (equal (and (search-forward "(:result " nil t) + (read (current-buffer))) + expected) (ert-fail (list "Mismatch" :expected expected :buffer-string (buffer-string))))) @@ -3755,18 +3758,15 @@ keyword :result." (unless (keywordp mod) (push (if-let* ((mode (erc--find-mode mod))) mod (list :missing mod)) moded))) - (prin1 (list :result - (sort moded (lambda (a b) - (string< (symbol-name a) (symbol-name b))))))) + (sort moded (lambda (a b) (string< (symbol-name a) (symbol-name b))))) erc-tests--modules)) (ert-deftest erc--essential-hook-ordering () (erc-tests--assert-printed-in-subprocess '(progn (erc-update-modules) - (prin1 (list :result - (list :erc-insert-modify-hook erc-insert-modify-hook - :erc-send-modify-hook erc-send-modify-hook)))) + (list :erc-insert-modify-hook erc-insert-modify-hook + :erc-send-modify-hook erc-send-modify-hook)) '( :erc-insert-modify-hook (erc-controls-highlight ; 0 erc-button-add-buttons ; 30 @@ -3798,26 +3798,37 @@ keyword :result." (should (eq (erc--find-group 'foo nil) 'erc)) (should (eq (erc--find-group 'fake 'baz) 'erc-foo)))) -(ert-deftest erc--find-group--real () - :tags '(:unstable) - (require 'erc-services) - (require 'erc-stamp) - (require 'erc-sound) - (require 'erc-page) - (require 'erc-join) - (require 'erc-capab) - (require 'erc-pcomplete) - (should (eq (erc--find-group 'services 'nickserv) 'erc-services)) - (should (eq (erc--find-group 'stamp 'timestamp) 'erc-stamp)) - (should (eq (erc--find-group 'sound 'ctcp-sound) 'erc-sound)) - (should (eq (erc--find-group 'page 'ctcp-page) 'erc-page)) - (should (eq (erc--find-group 'autojoin) 'erc-autojoin)) - (should (eq (erc--find-group 'pcomplete 'Completion) 'erc-pcomplete)) - (should (eq (erc--find-group 'capab-identify) 'erc-capab)) - (should (eq (erc--find-group 'completion) 'erc-pcomplete)) - ;; No group specified. - (should (eq (erc--find-group 'smiley nil) 'erc)) - (should (eq (erc--find-group 'unmorse nil) 'erc))) +(ert-deftest erc--find-group/realistic () + (erc-tests--assert-printed-in-subprocess + '(progn + (require 'erc-services) + (require 'erc-stamp) + (require 'erc-sound) + (require 'erc-page) + (require 'erc-join) + (require 'erc-capab) + (require 'erc-pcomplete) + (list (erc--find-group 'services 'nickserv) + (erc--find-group 'stamp 'timestamp) + (erc--find-group 'sound 'ctcp-sound) + (erc--find-group 'page 'ctcp-page) + (erc--find-group 'autojoin) + (erc--find-group 'pcomplete 'Completion) + (erc--find-group 'completion) + (erc--find-group 'capab-identify) + ;; No group specified. + (erc--find-group 'smiley nil) + (erc--find-group 'unmorse nil))) + '(erc-services + erc-stamp + erc-sound + erc-page + erc-autojoin + erc-pcomplete + erc-pcomplete + erc-capab + erc + erc))) (ert-deftest erc--sort-modules () (should (equal (erc--sort-modules '(networks foo fill bar fill stamp bar)) @@ -3920,28 +3931,32 @@ keyword :result." "(req . explicit-feature-lib)"))))))) (ert-deftest erc--update-modules/realistic () - (let ((calls nil) - ;; Module `pcomplete' "resolves" to `completion'. - (erc-modules '(pcomplete autojoin networks))) - (cl-letf (((symbol-function 'require) - (lambda (s &rest _) (push (cons 'req s) calls))) + (erc-tests--assert-printed-in-subprocess + '(progn + (require 'ert) + (require 'erc) + (should (featurep 'erc-networks)) + (should-not erc-networks-mode) + ;; The pcomplete module isn't loaded, and the non-alias form of + ;; its command isn't autoloaded, so `erc--find-mode' will do so. + (should-not (featurep 'erc-pcomplete)) + (should-not (intern-soft "erc-pcomplete-mode")) + ;; The join module is autoloaded. + (should-not (featurep 'erc-join)) + (should (fboundp 'erc-autojoin-mode)) + (should-not (boundp 'erc-autojoin-mode)) - ;; Spoof global module detection. - ((symbol-function 'custom-variable-p) - (lambda (v) - (memq v '(erc-autojoin-mode erc-networks-mode - erc-completion-mode)))) - ;; Mock and spy real builtins. - ((symbol-function 'erc-autojoin-mode) - (lambda (n) (push (cons 'autojoin n) calls))) - ((symbol-function 'erc-networks-mode) - (lambda (n) (push (cons 'networks n) calls))) - ((symbol-function 'erc-completion-mode) - (lambda (n) (push (cons 'completion n) calls)))) + ;; These are all global modules, so no return value is expected. + (let ((erc-modules (cons (seq-random-elt '(completion pcomplete)) + '(networks autojoin)))) + (should-not (erc--update-modules erc-modules))) - (should-not (erc--update-modules erc-modules)) ; no locals - (should (equal (nreverse calls) - '((completion . 1) (autojoin . 1) (networks . 1))))))) + (list erc-networks-mode + (featurep 'erc-pcomplete) + (featurep 'erc-join) + (symbol-value (intern-soft "erc-pcomplete-mode")) + (bound-and-true-p erc-autojoin-mode))) + '(t t t t t))) (ert-deftest erc--merge-local-modes () (cl-letf (((get 'erc-b-mode 'erc-module) 'b) diff --git a/test/lisp/erc/resources/erc-d/erc-d-t.el b/test/lisp/erc/resources/erc-d/erc-d-t.el index dbfe9181887..f6a9464d219 100644 --- a/test/lisp/erc/resources/erc-d/erc-d-t.el +++ b/test/lisp/erc/resources/erc-d/erc-d-t.el @@ -28,22 +28,20 @@ (defun erc-d-t-kill-related-buffers () "Kill all erc- or erc-d- related buffers." - (let (buflist) - (dolist (buf (buffer-list)) - (with-current-buffer buf - (when (or erc-d-u--process-buffer - (derived-mode-p 'erc-mode 'erc-dcc-chat-mode)) - (push buf buflist)))) - (dolist (buf buflist) - (when (and (boundp 'erc-server-flood-timer) - (timerp erc-server-flood-timer)) - (cancel-timer erc-server-flood-timer)) - (when-let* ((proc (get-buffer-process buf))) - (delete-process proc)) - (when (buffer-live-p buf) + (dolist (buf (buffer-list)) + (with-current-buffer buf + (when (or erc-d-u--process-buffer + (derived-mode-p 'erc-mode + 'erc-dcc-chat-mode) + (string-match (rx bot + (? " ") "*erc" (in "- ") (+ nonl) "*" + eot) + (buffer-name))) + (when-let* ((proc (get-buffer-process buf))) + (delete-process proc)) (kill-buffer buf)))) - (while (when-let* ((buf (pop erc-d-u--canned-buffers))) - (kill-buffer buf)))) + (while-let ((buf (pop erc-d-u--canned-buffers))) + (kill-buffer buf))) (defun erc-d-t-silence-around (orig &rest args) "Run ORIG function with ARGS silently. diff --git a/test/lisp/erc/resources/erc-d/erc-d.el b/test/lisp/erc/resources/erc-d/erc-d.el index 040980c906f..7397c5dbbc2 100644 --- a/test/lisp/erc/resources/erc-d/erc-d.el +++ b/test/lisp/erc/resources/erc-d/erc-d.el @@ -462,8 +462,7 @@ including line delimiters." (substring string (match-end 0)))) (erc-d--log process line nil) (ring-insert queue (erc-d-i--parse-message line nil)))) - (when string - (setf (process-get process :stashed-input) string)))) + (setf (process-get process :stashed-input) string))) ;; Misc process properties: ;; diff --git a/test/lisp/erc/resources/erc-scenarios-common.el b/test/lisp/erc/resources/erc-scenarios-common.el index f0b2afdc8b0..fa187cc59f7 100644 --- a/test/lisp/erc/resources/erc-scenarios-common.el +++ b/test/lisp/erc/resources/erc-scenarios-common.el @@ -267,6 +267,10 @@ Dialog resource directories are located by expanding the variable (package-initialize)))) (require 'erc) (cl-assert (equal erc-version ,erc-version) t))) + ;; Load test-related compat shims too niche for Compat, such as + ;; a <31 definition of `ert-with-buffer-selected'. + (tcompat (and (featurep 'erc-tests-compat) + (locate-library "erc-tests-compat"))) ;; Make subprocess terminal bigger than controlling. (buf (cl-letf (((symbol-function 'window-screen-lines) (lambda () (car erc-scenarios-common--term-size))) @@ -277,6 +281,9 @@ Dialog resource directories are located by expanding the variable nil `(,@(or init '("-Q")) "-nw" "-eval" ,(format "%S" setup) "-l" ,file-name + ,@(and tcompat + (list "-L" (file-name-directory tcompat) + "-l" tcompat)) "-eval" ,(format "%S" cmd))))) (proc (get-buffer-process buf)) (err (lambda () diff --git a/test/lisp/erc/resources/erc-tests-common.el b/test/lisp/erc/resources/erc-tests-common.el index f27cf2f3271..525bc2ed868 100644 --- a/test/lisp/erc/resources/erc-tests-common.el +++ b/test/lisp/erc/resources/erc-tests-common.el @@ -81,11 +81,18 @@ Assign the result to `erc-server-process' in the current buffer." ;; To facilitate automatic testing when a fake-server has already ;; been created by an earlier ERT test. (kill-buffer-query-functions nil)) - (dolist (buf (erc-buffer-list)) - (kill-buffer buf)) + (mapc #'kill-buffer + (match-buffers + `(or ,@(static-if (>= emacs-major-version 30) + '((derived-mode erc-mode erc-dcc-chat-mode)) + '((major-mode . erc-mode) (major-mode . erc-dcc-chat-mode))) + ,(rx bot (? ?\s) "*erc" (in "- ") (+ nonl) ?* eot)))) (named-let doit ((buffers extra-buffers)) (dolist (buf buffers) - (if (consp buf) (doit buf) (kill-buffer buf)))))) + (if (consp buf) + (doit buf) + (when (buffer-live-p buf) + (kill-buffer buf))))))) (defun erc-tests-common-with-process-input-spy (test-fn) "Mock `erc-process-input-line' and call TEST-FN. From c7bca9f3405239fc6b08d13144b6c7728996c505 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Sun, 19 Apr 2026 18:28:02 -0700 Subject: [PATCH 03/50] Define variable alias for erc-completion-mode * lisp/erc/erc-pcomplete.el (erc-completion-mode): New alias for the variable `erc-pcomplete-mode' to complement the function alias of the corresponding minor-mode command. Given that user code operating on the value of `erc-modules' needs to detect whether members are active, it only makes sense to address this specially. Before this change, applying `bound-and-true-p' to `erc-completion-mode' would always return nil, even when the module was active. For normal aliases, that's indeed expected because `define-erc-module' doesn't automatically define a mode variable for a non-nil ALIAS argument. Although in the long term it probably should, while also obsoleting the "new" alias simultaneously, that would likely break some user code and doubtless complicate things majorly for `pcomplete', the one in-tree outlier. The latter module would then require changing either its preferred name, the one advertised by `erc-modules' in its Custom definition, to `pcomplete' or its primary name to `completion'. Both are quite churn-inducing. --- lisp/erc/erc-pcomplete.el | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lisp/erc/erc-pcomplete.el b/lisp/erc/erc-pcomplete.el index e5c72cda796..df94097a00f 100644 --- a/lisp/erc/erc-pcomplete.el +++ b/lisp/erc/erc-pcomplete.el @@ -61,6 +61,11 @@ add this string to nicks completed." ;;;###autoload(put 'completion 'erc--feature 'erc-pcomplete) ;;;###autoload(autoload 'erc-completion-mode "erc-pcomplete" nil t) (put 'completion 'erc-group 'erc-pcomplete) +;; For historical reasons, (the downcased version of) this module's +;; alias is the canonical name used by `erc-modules'. But user code +;; still needs to detect whether the module is enabled based on that +;; name alone, hence this variable alias. +(defvaralias 'erc-completion-mode 'erc-pcomplete-mode) (define-erc-module pcomplete Completion "In ERC Completion mode, the TAB key does completion whenever possible." ((add-hook 'erc-mode-hook #'pcomplete-erc-setup) From ba2a150740691146454955055067d3285edc52d5 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Tue, 28 Apr 2026 14:44:45 -0700 Subject: [PATCH 04/50] Restore erc-last-saved-position from previous session * lisp/erc/erc-log.el (erc-log-setup-logging): Restore `erc-last-saved-position' from previous session. By default, a non-/QUIT disconnect does not write out any remaining buffer text to logs, instead leaving it until the buffer or Emacs is killed. But if a successful reconnect occurs beforehand, the uncommitted portion must be seen to somehow. Before this change, it would be lost because the function `erc-initialize-log-marker' remakes the marker at the prompt instead of recovering the previous value as now done here. Moreover, the traditional workaround of customizing `erc-log-write-after-insert' and `erc-log-write-after-send' to t should not be required to prevent gaps in any decent IRC client. * test/lisp/erc/erc-scenarios-log.el (erc-scenarios-log--reconnect): New function. (erc-scenarios-log--reconnect/auto, erc-scenarios-log--reconnect/manual): New tests. ;; * test/lisp/erc/resources/join/reconnect/foonet-again.eld: Add QUIT. --- lisp/erc/erc-log.el | 6 + test/lisp/erc/erc-scenarios-log.el | 112 ++++++++++++++++++ .../resources/join/reconnect/foonet-again.eld | 4 + 3 files changed, 122 insertions(+) diff --git a/lisp/erc/erc-log.el b/lisp/erc/erc-log.el index 01ea1a3d3c8..2bd1304210e 100644 --- a/lisp/erc/erc-log.el +++ b/lisp/erc/erc-log.el @@ -256,6 +256,12 @@ This function is destined to be run from `erc-connect-pre-hook'. The current buffer is given by BUFFER." (when (erc-logging-enabled buffer) (with-current-buffer buffer + (when-let* ((erc-last-saved-position) + (priors (or erc--server-reconnecting erc--target-priors)) + (val (alist-get 'erc-last-saved-position priors)) + (_ (eq buffer (marker-buffer val)))) + ;; Will have been initialized by `erc-initialize-log-marker'. + (move-marker erc-last-saved-position val)) (auto-save-mode -1) (setq buffer-file-name nil) (add-hook 'write-file-functions #'erc-save-buffer-in-logs nil t) diff --git a/test/lisp/erc/erc-scenarios-log.el b/test/lisp/erc/erc-scenarios-log.el index 063cdfdcfd4..7452062e3c5 100644 --- a/test/lisp/erc/erc-scenarios-log.el +++ b/test/lisp/erc/erc-scenarios-log.el @@ -460,4 +460,116 @@ (erc-truncate-mode -1) (when noninteractive (delete-directory tempdir :recursive)))) +;; These tests check whether logs contain gaps when reconnecting. +(defun erc-scenarios-log--reconnect (autop) + + (erc-scenarios-common-with-cleanup + ((erc-scenarios-common-dialog "join/reconnect") + (dumb-server (erc-d-run "localhost" t 'foonet 'foonet-again)) + (tempdir (make-temp-file "erc-tests-log." t nil nil)) + (erc-log-channels-directory tempdir) + (erc-modules `(log ,@erc-modules)) + (erc-timestamp-format-left "\n[@@DATE__STAMP@@]\n") + (port (process-contact dumb-server :service)) + (erc-server-auto-reconnect autop) + (erc-server-flood-penalty 0.1) + (expect (erc-d-t-make-expecter)) + ;; Bind these so they'll be killed on teardown. + (server-log-buffer (get-buffer-create "*erc-log FooNet*")) + (chan-log-buffer (get-buffer-create "*erc-log #chan*")) + (spam-log-buffer (get-buffer-create "*erc-log #spam*"))) + + (ert-info ("Connect") + (with-current-buffer (erc :server "127.0.0.1" + :port port + :nick "tester" + :password "changeme" + :full-name "tester") + (funcall expect 10 "debug mode"))) + + (ert-info ("#chan populated") + (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#chan")) + (funcall expect 10 "@@DATE__STAMP@@") + (funcall expect 10 " tester, welcome"))) + + (ert-info ("#spam populated") + (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#spam")) + (funcall expect 10 "@@DATE__STAMP@@") + (funcall expect 10 " tester, welcome"))) + + (ert-info ("Reconnect") + (with-current-buffer "FooNet" + (funcall expect 10 "Connection failed!") + + (if autop + (funcall expect 10 "Reconnecting") + (erc-scenarios-common-say "/reconnect")) + + (funcall expect 10 "Welcome") + (funcall expect 10 "debug mode"))) + + (with-current-buffer "#chan" + (funcall expect -0.01 "@@DATE__STAMP@@") + (funcall expect 10 " bob: Well, this")) + + (with-current-buffer "#spam" + (funcall expect -0.01 "@@DATE__STAMP@@") + (funcall expect 10 " bob: Our queen and all")) + + (with-current-buffer "FooNet" + (erc-scenarios-common-say "/quit") + (funcall expect 10 "Quit")) + + (with-current-buffer "FooNet" + (let ((file (erc-current-logfile (current-buffer)))) + (with-current-buffer server-log-buffer + (insert-file-contents file) + (funcall expect 1 "@@DATE__STAMP@@") + (funcall expect 1 "*** Welcome to the foonet") + (funcall expect 1 "debug mode") + (funcall expect 1 "*** Connection failed!") + ;; Full output again on reconnect. + (funcall expect -0.01 "@@DATE__STAMP@@") ; but no stamp + (funcall expect 1 "*** Welcome to the foonet") + (funcall expect 1 "debug mode")))) + + (with-current-buffer "#chan" + (let ((file (erc-current-logfile (current-buffer)))) + (with-current-buffer chan-log-buffer + (insert-file-contents file) + (funcall expect 1 "@@DATE__STAMP@@") + (funcall expect 1 "*** You have joined channel #chan") + (funcall expect 1 " tester, welcome!") + ;; No stamp on reconnect. + (funcall expect -0.01 "@@DATE__STAMP@@") + (funcall expect 1 "*** You have joined channel #chan") + (funcall expect 1 " bob: Well, this is the forest")))) + + (with-current-buffer "#spam" + (let ((file (erc-current-logfile (current-buffer)))) + (with-current-buffer spam-log-buffer + (insert-file-contents file) + (funcall expect 1 "@@DATE__STAMP@@") + (funcall expect 1 "*** You have joined channel #spam") + (funcall expect 1 " tester, welcome!") + ;; No stamp on reconnect. + (funcall expect -0.01 "@@DATE__STAMP@@") + (funcall expect 1 "*** You have joined channel #spam") + (funcall expect 1 " bob: Our queen and all her elves come")))) + + (erc-log-mode -1) + + (if noninteractive + (delete-directory tempdir :recursive) + (add-hook 'kill-emacs-hook + (lambda () (delete-directory tempdir :recursive)))))) + +(ert-deftest erc-scenarios-log--reconnect/auto () + :tags '(:expensive-test) + (erc-scenarios-log--reconnect 'autop)) + +(ert-deftest erc-scenarios-log--reconnect/manual () + :tags '(:expensive-test) + (erc-scenarios-log--reconnect nil)) + ;;; erc-scenarios-log.el ends here diff --git a/test/lisp/erc/resources/join/reconnect/foonet-again.eld b/test/lisp/erc/resources/join/reconnect/foonet-again.eld index f1fcc439cc3..e530634f73c 100644 --- a/test/lisp/erc/resources/join/reconnect/foonet-again.eld +++ b/test/lisp/erc/resources/join/reconnect/foonet-again.eld @@ -43,3 +43,7 @@ (0 ":irc.foonet.org 329 tester #spam 1620104779") (0.1 ":bob!~u@rz2v467q4rwhy.irc PRIVMSG #spam :alice: Signior Iachimo will not from it. Pray, let us follow 'em.") (0.1 ":alice!~u@rz2v467q4rwhy.irc PRIVMSG #spam :bob: Our queen and all her elves come here anon.")) + +((quit 10 "QUIT :\2ERC\2") + (0.07 ":tester!~u@h3f95zveyc38a.irc QUIT :Quit: \2ERC\2 5.5 (IRC client for GNU Emacs 30.0.50)") + (0.01 "ERROR :Quit: \2ERC\2 5.5 (IRC client for GNU Emacs 30.0.50)")) From cf9728c4be8f50c4a176b78cee6e23e1e485788a Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Sun, 19 Oct 2025 05:29:18 -0700 Subject: [PATCH 05/50] Only perform erc-log-insert-log-on-open setup once * etc/ERC-NEWS: Add entry for option `erc-log-insert-log-on-open'. Mention deprecation of `erc-log-setup-logging'. * lisp/erc/erc-log.el (erc-log-insert-log-on-open): Expand type from boolean to choice of boolean, predicate, and new function item `erc-log-new-target-buffer-p'. (erc-log-mode, erc-log-enable, erc-log-disable): Replace `erc-log-setup-logging' on `erc-connect-pre-hook' with `erc-log--insert-log-on-open' at depth 80. Replace calls to `erc-log-setup-logging' with ones to `erc-log--setup'. (erc-log-new-target-buffer-p): New function. While the name could perhaps do more to indicate that it's only useful when called from `erc-open', that's the only place ERC typically sets up new target buffers. (erc-log-setup-logging): Deprecate and replace body with adapter that calls `erc-log--setup' and `erc-log--insert-log-on-open'. (erc-log--setup): New function whose body is mostly from the "nondestructive" portion of `erc-log-setup-logging'. Instead of moving the `erc-saved-last-position' marker to the old value, expect it not to have been initialized by `erc-initialize-log-marker' because `erc-connect-pre-hook' now runs after this code, which is now incorporated more judiciously into the module's "enable body". (erc-log--insert-log-on-open): New function whose body is mostly from the "destructive" portion of the previous incarnation of `erc-log-setup-logging'. Unlike the original, if option `erc-log-insert-log-on-open' is a function, call it to decide whether to insert a log file atop its buffer. Also, don't advance the marker `erc-last-saved-position' to the prompt area, where it can get stuck and prevent logs from being saved when `erc-log-insert-log-on-open' is non-nil. Thanks to Libera user Lionyx for reporting the bug, which has been around since at least ERC 5.4 and Emacs 28.1. (erc-log-disable-logging): Remove `erc-save-buffer-in-logs' from `write-file-functions' to undo its buffer-local addition in what is now `erc--log-setup'. * test/lisp/erc/erc-scenarios-log-options.el: New file. (Bug#79665) --- etc/ERC-NEWS | 17 ++ lisp/erc/erc-log.el | 85 +++++--- test/lisp/erc/erc-scenarios-log-options.el | 214 +++++++++++++++++++++ 3 files changed, 293 insertions(+), 23 deletions(-) create mode 100644 test/lisp/erc/erc-scenarios-log-options.el diff --git a/etc/ERC-NEWS b/etc/ERC-NEWS index 9ede2566e6d..082c10702f3 100644 --- a/etc/ERC-NEWS +++ b/etc/ERC-NEWS @@ -14,8 +14,25 @@ GNU Emacs since Emacs version 22.1. * Changes in ERC 5.6.2 +** Option 'erc-log-insert-log-on-open' can be a function. +Rather than insert redundant logs into all buffers when reconnecting, +which is what happens when this option is set to t, ERC 5.6.2 allows +users to exercise more control by specifying a predicate. The provided +'erc-log-new-target-buffer-p' tells ERC to only insert logs when +creating a new target buffer, such as when issuing a "/JOIN" or a +"/QUERY" or when connecting for the first time with autojoin configured. + ** Changes in the library API. +*** Function 'erc-log-setup-logging' deprecated. +In order to ensure proper buffer-local setup, the 'log' module has +always run this function somewhat indiscriminately and overly often. +This might be fine were it not for the function's interest in the option +'erc-log-insert-log-on-open' and its consequent altering of buffer text +in a manner only conducive to buffer creation. The module now conducts +such business in a tidier and more internal fashion that no longer has +any use for the function nor its presence in 'erc-connect-pre-hook'. + *** Accessors like 'erc-channel-user-voice' may ignore assignments. ERC now silently ignores attempts to enable certain status flags on 'erc-channel-user' objects if the connection's "PREFIX" parameter omits diff --git a/lisp/erc/erc-log.el b/lisp/erc/erc-log.el index 2bd1304210e..e7160aa482f 100644 --- a/lisp/erc/erc-log.el +++ b/lisp/erc/erc-log.el @@ -157,8 +157,16 @@ arguments." (const :tag "Disable logging" nil))) (defcustom erc-log-insert-log-on-open nil - "Insert log file contents into the buffer if a log file exists." - :type 'boolean) + "Insert an existing log file's contents into its associated buffer. +A legacy value of t does so upon connecting and reconnecting in all +buffers, often in an overlapping and redundant fashion. A value of +`erc-log-new-target-buffer-p' does so in new target buffers only. If +set to an arbitrary predicate, ERC calls it with no args in the +associated buffer." + :type '(choice boolean + (function-item :tag "Only new target buffers" + erc-log-new-target-buffer-p) + (function "User-defined predicate taking no args"))) (defcustom erc-save-buffer-on-part t "Save the channel buffer content using `erc-save-buffer-in-logs' on PART. @@ -231,10 +239,9 @@ also be a predicate function. To only log when you are not set away, use: (add-hook 'erc-quit-hook #'erc-conditional-save-queries) (add-hook 'erc-part-hook #'erc-conditional-save-buffer) ;; append, so that 'erc-initialize-log-marker runs first - (add-hook 'erc-connect-pre-hook #'erc-log-setup-logging 'append) - ;; FIXME use proper local "setup" function and major-mode hook. - (dolist (buffer (erc-buffer-list)) - (erc-log-setup-logging buffer)) + (add-hook 'erc-connect-pre-hook #'erc-log--insert-log-on-open 80) + (add-hook 'erc-mode-hook #'erc-log--setup) + (unless erc--updating-modules-p (erc-buffer-do #'erc-log--setup)) (erc--modify-local-map t "C-c C-l" #'erc-save-buffer-in-logs)) ;; disable ((remove-hook 'erc-insert-post-hook #'erc-save-buffer-in-logs) @@ -244,42 +251,74 @@ also be a predicate function. To only log when you are not set away, use: (remove-hook 'kill-emacs-hook #'erc-log-save-all-buffers) (remove-hook 'erc-quit-hook #'erc-conditional-save-queries) (remove-hook 'erc-part-hook #'erc-conditional-save-buffer) - (remove-hook 'erc-connect-pre-hook #'erc-log-setup-logging) - (dolist (buffer (erc-buffer-list)) - (erc-log-disable-logging buffer)) + (remove-hook 'erc-connect-pre-hook #'erc-log--insert-log-on-open) + (remove-hook 'erc-mode-hook #'erc-log--setup) + (erc-buffer-do #'erc-log--setup) (erc--modify-local-map nil "C-c C-l" #'erc-save-buffer-in-logs))) -;;; functionality referenced from erc.el +(defun erc-log-new-target-buffer-p () + "Return non-nil during `erc-open' if the current buffer is a new target. +That is, return nil if it's a server buffer or a target being +reassociated from a previous session." + (and (erc-target) (null erc--target-priors))) + +;; This function served double duty as the local setup function for both +;; idempotent tasks and destructive ones typically confined to +;; `erc-open'. The caller was implicitly tasked with selectively +;; inhibiting the destructive portion by binding +;; `erc-log-insert-log-on-open' to nil when calling it, which led to +;; bugs. (defun erc-log-setup-logging (buffer) "Setup the buffer-local logging variables in the current buffer. This function is destined to be run from `erc-connect-pre-hook'. The current buffer is given by BUFFER." - (when (erc-logging-enabled buffer) - (with-current-buffer buffer - (when-let* ((erc-last-saved-position) - (priors (or erc--server-reconnecting erc--target-priors)) - (val (alist-get 'erc-last-saved-position priors)) - (_ (eq buffer (marker-buffer val)))) - ;; Will have been initialized by `erc-initialize-log-marker'. - (move-marker erc-last-saved-position val)) + (declare (obsolete "use `erc-log-mode' or mimic `erc-log--setup'" "31.1")) + (with-current-buffer buffer + (let ((erc-log-mode t)) + (erc-log--setup) + (erc-log--insert-log-on-open)))) + +;; This module's differs from other global modules in that it allows for +;; effectively disabling itself in a subset of buffers by setting the +;; option `erc-enable-logging' locally to nil. Though not +;; permanent-local, this option's variable is never explicitly killed +;; when the module is disabled, such as via its mode command. +(defun erc-log--setup () + "Perform buffer-local setup for ERC's log module." + (if erc-log-mode + (when (erc-logging-enabled) + ;; If reconnecting, preserve `erc-last-saved-position' from prev + ;; session and preempt `erc-initialize-log-marker' in `erc-open'. + (unless erc-last-saved-position + (when-let* ((priors (or erc--server-reconnecting erc--target-priors)) + (val (alist-get 'erc-last-saved-position priors)) + (_ (eq (current-buffer) (marker-buffer val)))) + (setq erc-last-saved-position val))) (auto-save-mode -1) (setq buffer-file-name nil) (add-hook 'write-file-functions #'erc-save-buffer-in-logs nil t) (add-function :before (local 'erc--clear-function) - #'erc-log--save-on-clear '((depth . 50))) - (when erc-log-insert-log-on-open + #'erc-log--save-on-clear '((depth . 50)))) + (erc-log-disable-logging (current-buffer)))) + +(defun erc-log--insert-log-on-open (&rest _) + "Conditionally perform insertion for `erc-log-insert-log-on-open'." + (when (if (functionp erc-log-insert-log-on-open) + (funcall erc-log-insert-log-on-open) + erc-log-insert-log-on-open) + (with-silent-modifications + (progn (ignore-errors (save-excursion (goto-char (point-min)) - (insert-file-contents (erc-current-logfile))) - (move-marker erc-last-saved-position - (1- (point-max)))))))) + (insert-file-contents (erc-current-logfile)))))))) (defun erc-log-disable-logging (buffer) "Disable logging in BUFFER." (when (erc-logging-enabled buffer) (with-current-buffer buffer (remove-function (local 'erc--clear-function) #'erc-log--save-on-clear) + (remove-hook 'write-file-functions #'erc-save-buffer-in-logs t) (setq buffer-offer-save nil erc-enable-logging nil)))) diff --git a/test/lisp/erc/erc-scenarios-log-options.el b/test/lisp/erc/erc-scenarios-log-options.el new file mode 100644 index 00000000000..c1fe946e844 --- /dev/null +++ b/test/lisp/erc/erc-scenarios-log-options.el @@ -0,0 +1,214 @@ +;;; erc-scenarios-log-options.el --- erc-log options scenarios -*- lexical-binding: t -*- + +;; Copyright (C) 2026 Free Software Foundation, Inc. + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see . + +;;; Commentary: + +;;; Code: + +(require 'ert-x) +(eval-and-compile + (let ((load-path (cons (ert-resource-directory) load-path))) + (require 'erc-scenarios-common))) + +(require 'erc-log) + +;; This checks that logs are only inserted in new buffers. +(defun erc-scenarios-log-options--insert-on-open (test-fn) + + (erc-scenarios-common-with-cleanup + ((erc-scenarios-common-dialog "join/reconnect") + (dumb-server (erc-d-run "localhost" t 'foonet 'foonet-again)) + (tempdir (make-temp-file "erc-tests-log." t nil nil)) + (erc-log-channels-directory tempdir) + (erc-modules `(log ,@erc-modules)) + (erc-timestamp-format-left "\n[@@DATE__STAMP@@]\n") + (port (process-contact dumb-server :service)) + (erc-server-auto-reconnect t) + (erc-server-flood-penalty 0.1) + (expect (erc-d-t-make-expecter)) + ;; Bind these so they'll be killed on teardown. + (server-log-buffer (get-buffer-create "*erc-log FooNet*")) + (chan-log-buffer (get-buffer-create "*erc-log #chan*")) + (uh-suffix (format "!tester@127.0.0.1:%d.txt" port))) + + (let ((file (concat "127.0.0.1:" (number-to-string port) uh-suffix))) + (with-temp-file (expand-file-name file tempdir) + (insert "\n@@OLD__BEG@@\n" "file: " file "\n@@OLD__END@@\n\n"))) + + (with-temp-file (expand-file-name (concat "foonet" uh-suffix) tempdir) + (insert "\n@@OLD__BEG@@\n" + "file: " (concat "foonet" uh-suffix) + "\n@@OLD__END@@\n\n")) + + (with-temp-file (expand-file-name (concat "#chan" uh-suffix) tempdir) + (insert "\n@@OLD__BEG@@\n" + "file: " (concat "#chan" uh-suffix) + "\n@@OLD__END@@\n\n")) + + (ert-info ("Connect") + (with-current-buffer (erc :server "127.0.0.1" + :port port + :nick "tester" + :password "changeme" + :full-name "tester") + (funcall expect 10 "debug mode"))) + + (ert-info ("#chan populated") + (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#chan")) + (funcall expect 10 "@@DATE__STAMP@@") + (funcall expect 10 " tester, welcome"))) + + (ert-info ("Reconnect") + (with-current-buffer "FooNet" + (funcall expect 10 "Connection failed!") + (funcall expect 10 "Reconnecting") + (funcall expect 10 "Welcome") + (funcall expect 10 "debug mode"))) + + (with-current-buffer "#chan" + (funcall expect -0.01 "@@DATE__STAMP@@") + (funcall expect 10 " bob: Well, this")) + + (with-current-buffer "FooNet" + (erc-scenarios-common-say "/quit") + (funcall expect 10 "ERROR")) + + ;; Ensure no redundant logging. + (with-current-buffer "FooNet" + (let ((file (erc-current-logfile (current-buffer)))) + (with-current-buffer server-log-buffer + (insert-file-contents file) + (funcall expect 1 "@@DATE__STAMP@@") + (funcall expect -0.01 "@@DATE__STAMP@@") + ;; Full output again on reconnect. + (funcall expect 1 "*** Welcome to the foonet") + (funcall expect 1 "debug mode") + (funcall expect 1 "*** Connection failed!") + (funcall expect 1 "*** Welcome to the foonet") + (funcall expect 1 "debug mode")))) + + (with-current-buffer "#chan" + (let ((file (erc-current-logfile (current-buffer)))) + (with-current-buffer chan-log-buffer + (insert-file-contents file) + (funcall expect 1 "@@OLD__BEG@@") + (funcall expect -0.01 "@@OLD__BEG@@") ; only appearance + (funcall expect 1 "file: #chan") + (funcall expect 1 "@@OLD__END@@") + (funcall expect 1 "@@DATE__STAMP@@") + (funcall expect -0.01 "@@DATE__STAMP@@") + ;; Full output again on reconnect. + (funcall expect 1 "*** You have joined channel #chan") + (funcall expect 1 " tester, welcome!") + (funcall expect -0.01 "@@DATE__STAMP@@") + (funcall expect 1 "*** You have joined channel #chan") + (funcall expect 1 " bob: Well, this is the forest")))) + + (funcall test-fn expect) + (erc-log-mode -1) + + (if noninteractive + (delete-directory tempdir :recursive) + (add-hook 'kill-emacs-hook + (lambda () (delete-directory tempdir :recursive)))))) + +;; This shows that the legacy default value of t ends up inserting +;; existing log content on reconnect as well, leading to redundant +;; insertions. +(ert-deftest erc-scenarios-log-options--insert-on-open/default () + (let ((erc-log-insert-log-on-open t)) + (erc-scenarios-log-options--insert-on-open + (lambda (expect) + + (with-current-buffer "*erc-log FooNet*" + (funcall expect 1 "@@OLD__BEG@@" (point-min)) + (funcall expect -0.01 "@@OLD__BEG@@") ; not repeated + (funcall expect 1 "file: foonet") + (funcall expect 1 "@@OLD__END@@") + (funcall expect 1 "@@DATE__STAMP@@")) + + ;; For server buffers, the file name changes because the buffer + ;; is renamed after the network is announced during the initial + ;; session. + (with-current-buffer "FooNet" + + (funcall expect 1 "@@OLD__BEG@@" (point-min)) + ;; Existing log contents inserted once per connection (most + ;; recent first). + (funcall expect 1 "file: foonet") + (funcall expect 1 "@@OLD__END@@") + + ;; Insertion from initial connection last. + (funcall expect 1 "@@OLD__BEG@@") + (funcall expect 1 "file: 127.0.0.1") + (funcall expect -0.01 "@@OLD__BEG@@") + (funcall expect 1 "@@OLD__END@@") + + (funcall expect 1 "@@DATE__STAMP@@")) + + (ert-info ("Repeated in #chan") + (with-current-buffer "#chan" + (funcall expect 1 "@@OLD__BEG@@" (point-min)) + (funcall expect 1 "file: #chan") + (funcall expect 1 "@@OLD__END@@") + + ;; Existing log is indeed repeated in full once per connection. + (funcall expect 1 "@@OLD__BEG@@") + (funcall expect -0.01 "@@OLD__BEG@@") + (funcall expect 1 "file: #chan") + (funcall expect 1 "@@OLD__END@@") + + (funcall expect 1 "@@DATE__STAMP@@"))))))) + +(ert-deftest erc-scenarios-log-options--insert-on-open/target-p () + (let ((erc-log-insert-log-on-open #'erc-log-new-target-buffer-p)) + (erc-scenarios-log-options--insert-on-open + (lambda (expect) + + (ert-info ("Absent from server buffer") + (with-current-buffer "FooNet" + ;; No insertions in the server buffer. + (funcall expect -0.01 "@@OLD__BEG@@" (point-min)) + (funcall expect 1 "@@DATE__STAMP@@"))) + + (ert-info ("Once in server log") + ;; No redundancies in the server's log file, though previously + ;; existing content is obviously present. + (with-current-buffer "*erc-log FooNet*" + (funcall expect 1 "@@OLD__BEG@@" (point-min)) + (funcall expect -0.01 "@@OLD__BEG@@") ; no repeats + (funcall expect 1 "file: foonet") + (funcall expect 1 "@@OLD__END@@") + (funcall expect 1 "@@DATE__STAMP@@"))) + + ;; Also, as asserted in the fixture body, the associated log file + ;; for #chan has no redundancy. + (ert-info ("Not repeated in #chan") + (with-current-buffer "#chan" + (funcall expect 1 "@@OLD__BEG@@" (point-min)) + (funcall expect 1 "file: #chan") + (funcall expect 1 "@@OLD__END@@") + + ;; Existing scrollback only inserted at most once per buffer. + (funcall expect -0.01 "@@OLD__BEG@@") + (funcall expect 1 "@@DATE__STAMP@@"))))))) + +(require 'erc-log) + +;;; erc-scenarios-log-options.el ends here From da4ab3d7381e7f5b196d5df44e6c7d6afd7856ed Mon Sep 17 00:00:00 2001 From: Paul Eggert Date: Thu, 30 Apr 2026 17:55:43 -0700 Subject: [PATCH 06/50] Pacify GCC 16.0.1 -Wanalyzer-null-dereference in xdisp.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * src/xdisp.c (glyph_string_containing_background_width): Although it’s not immediately obvious whether the GCC 16 warning is valid, adding an eassume here shouldn’t hurt. --- src/xdisp.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/xdisp.c b/src/xdisp.c index a05e5bda48a..4c46d7711be 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -31382,7 +31382,10 @@ glyph_string_containing_background_width (struct glyph_string *s) { if (s->cmp) while (s->cmp_from) - s = s->prev; + { + s = s->prev; + eassume (s); + } return s; } From a6a3b32208c5763de20c874da1a0d8af47e4dd93 Mon Sep 17 00:00:00 2001 From: Dmitry Gutov Date: Fri, 1 May 2026 02:19:40 +0300 Subject: [PATCH 07/50] Try to resize or resize-and-move child frames in one update This pertains to X11 toolkit builds. Other ports (including PGTK) seem to have mostly atomic window updates already. * src/xterm.c (x_set_window_size_1): Resize the Xt widget eagerly, so the next redraw is not clipped. Update the "desired" Cairo surface dimensions. Skip waiting for next XEvent. Do all that for child frames only. Update old comments (bug#80662). (x_set_window_size_and_position_1): Same. Also clear the widget's cached position coordinates (we don't keep them up to date). (x_set_window_size, x_set_window_size_and_position): Skip redrawing the border on child frames, it will happen during redisplay anyway. * src/gtkutil.c (xg_frame_set_size_and_position): On child frames, resize the GTK widget hierarchy immediately. Update the "desired" Cairo surface dimensions. Skip waiting for events. * src/widget.c (EmacsFrameResize): When resize should be a no-op, exit early (minor optimization). (EmacsFrameExpose): Redraw the border on the last Expose event. * src/xdisp.c (clear_garbaged_frames): Don't redraw borders here. * src/xfns.c (x_window): Undo the previous change in bit_gravity in the no-toolkit build. StaticGravity works the best for it thanks to no nesting in window configuration. --- src/gtkutil.c | 16 ++++++++++- src/widget.c | 6 +++++ src/xdisp.c | 5 ---- src/xfns.c | 2 +- src/xterm.c | 75 +++++++++++++++++++++++++++++++++++---------------- 5 files changed, 74 insertions(+), 30 deletions(-) diff --git a/src/gtkutil.c b/src/gtkutil.c index b01bb6804ed..fd83943e033 100644 --- a/src/gtkutil.c +++ b/src/gtkutil.c @@ -1421,6 +1421,16 @@ xg_frame_set_size_and_position (struct frame *f, int width, int height) #ifndef HAVE_PGTK gdk_window_move_resize (gwin, x, y, outer_width, outer_height); + if (FRAME_PARENT_FRAME (f)) + { + /* Resize all inner widgets and Cairo surface right away so the + next redisplay drawing isn't clipped to the old size. */ + GtkAllocation alloc = {0, 0, outer_width, outer_height}; + gtk_widget_size_allocate (FRAME_GTK_OUTER_WIDGET (f), &alloc); +#ifdef USE_CAIRO + x_cr_update_surface_desired_size (f, width, height); +#endif + } #else if (FRAME_GTK_OUTER_WIDGET (f)) gdk_window_move_resize (gwin, x, y, outer_width, outer_height); @@ -1432,7 +1442,11 @@ xg_frame_set_size_and_position (struct frame *f, int width, int height) SET_FRAME_GARBAGED (f); cancel_mouse_face (f); - if (FRAME_VISIBLE_P (f)) + /* For child frames, don't wait for events — that would flush the X + buffer and might show outdated contents in the frame. Same for + invisible frames: this way is faster and x_make_frame_visible will + wait for event anyway. */ + if (FRAME_VISIBLE_P (f) && !FRAME_PARENT_FRAME (f)) { /* Must call this to flush out events */ (void)gtk_events_pending (); diff --git a/src/widget.c b/src/widget.c index e767b006e3f..b843bca1fb9 100644 --- a/src/widget.c +++ b/src/widget.c @@ -428,6 +428,10 @@ EmacsFrameResize (Widget widget) ew->core.width, ew->core.height, f->new_width, f->new_height); + if (FRAME_PIXEL_WIDTH (f) == ew->core.width + && FRAME_PIXEL_HEIGHT (f) == ew->core.height) + return; + change_frame_size (f, ew->core.width, ew->core.height, false, true, false); @@ -495,6 +499,8 @@ EmacsFrameExpose (Widget widget, XEvent *event, Region region) expose_frame (f, event->xexpose.x, event->xexpose.y, event->xexpose.width, event->xexpose.height); + if (event->xexpose.count == 0) + x_clear_under_internal_border (f); flush_frame (f); } diff --git a/src/xdisp.c b/src/xdisp.c index 4c46d7711be..4f3b23f1698 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -13661,11 +13661,6 @@ clear_garbaged_frames (void) if (is_tty_frame (f)) current_matrices_cleared = true; -#ifdef HAVE_WINDOW_SYSTEM - if (FRAME_WINDOW_P (f) - && FRAME_RIF (f)->clear_under_internal_border) - FRAME_RIF (f)->clear_under_internal_border (f); -#endif fset_redisplay (f); f->garbaged = false; f->resized_p = false; diff --git a/src/xfns.c b/src/xfns.c index 3cf9bbc22f0..423209436c4 100644 --- a/src/xfns.c +++ b/src/xfns.c @@ -4483,7 +4483,7 @@ x_window (struct frame *f) attributes.background_pixel = FRAME_BACKGROUND_PIXEL (f); attributes.border_pixel = f->output_data.x->border_pixel; - attributes.bit_gravity = NorthWestGravity; + attributes.bit_gravity = StaticGravity; attributes.backing_store = NotUseful; attributes.save_under = True; attributes.event_mask = STANDARD_EVENT_SET; diff --git a/src/xterm.c b/src/xterm.c index 4de0b24081f..ee727d163f5 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -28484,6 +28484,19 @@ x_set_window_size_1 (struct frame *f, bool change_gravity, f->win_gravity = NorthWestGravity; x_wm_set_size_hint (f, 0, false); +#ifdef USE_X_TOOLKIT + if (FRAME_PARENT_FRAME (f) && f->output_data.x->widget) + { + /* Resize all inner widgets and Cairo surface right away so the + next redisplay drawing isn't clipped to the old size. */ + XtResizeWidget (f->output_data.x->widget, + width, height + FRAME_MENUBAR_HEIGHT (f), 0); +#ifdef USE_CAIRO + x_cr_update_surface_desired_size (f, width, height); +#endif + } + else +#endif XResizeWindow (FRAME_X_DISPLAY (f), FRAME_OUTER_WINDOW (f), width, height + FRAME_MENUBAR_HEIGHT (f)); @@ -28499,30 +28512,25 @@ x_set_window_size_1 (struct frame *f, bool change_gravity, if (!NILP (Vx_lax_frame_positioning)) return; - /* Now, strictly speaking, we can't be sure that this is accurate, + /* Now, strictly speaking, we can't be sure that this is final, but the window manager will get around to dealing with the size change request eventually, and we'll hear how it went when the ConfigureNotify event gets here. - We could just not bother storing any of this information here, - and let the ConfigureNotify event set everything up, but that - might be kind of confusing to the Lisp code, since size changes - wouldn't be reported in the frame parameters until some random - point in the future when the ConfigureNotify event arrives. - - Pass true for DELAY since we can't run Lisp code inside of - a BLOCK_INPUT. */ - - /* But the ConfigureNotify may in fact never arrive, and then this is - not right if the frame is visible. Instead wait (with timeout) - for the ConfigureNotify. */ - if (FRAME_VISIBLE_P (f)) + We could just let the ConfigureNotify update everything, but + waiting creates an implicit X flush which might flicker with + outdated contents in the frame. For child frames, the window + manager is not a concern and it's better to finish quickly. */ + if (FRAME_VISIBLE_P (f) && !FRAME_PARENT_FRAME (f)) { + /* The event may create delayed size change (delayed because we + can't run Lisp code inside of a BLOCK_INPUT) which will be + applied right after by do_pending_window_change. */ x_wait_for_event (f, ConfigureNotify); if (CONSP (frame_size_history)) frame_size_history_extra - (f, build_string ("x_set_window_size_1, visible"), + (f, build_string ("x_set_window_size_1, waited for event"), FRAME_PIXEL_WIDTH (f), FRAME_PIXEL_HEIGHT (f), width, height, f->new_width, f->new_height); } @@ -28530,7 +28538,7 @@ x_set_window_size_1 (struct frame *f, bool change_gravity, { if (CONSP (frame_size_history)) frame_size_history_extra - (f, build_string ("x_set_window_size_1, invisible"), + (f, build_string ("x_set_window_size_1, not waited for event"), FRAME_PIXEL_WIDTH (f), FRAME_PIXEL_HEIGHT (f), width, height, f->new_width, f->new_height); @@ -28561,7 +28569,8 @@ x_set_window_size (struct frame *f, bool change_gravity, x_set_window_size_1 (f, change_gravity, width, height); #else /* not USE_GTK */ x_set_window_size_1 (f, change_gravity, width, height); - x_clear_under_internal_border (f); + if (!FRAME_PARENT_FRAME (f)) + x_clear_under_internal_border (f); #endif /* not USE_GTK */ /* If cursor was outside the new size, mark it as off. */ @@ -28586,16 +28595,35 @@ x_set_window_size_and_position_1 (struct frame *f, int width, int height) x_wm_set_size_hint (f, 0, false); - XMoveResizeWindow (FRAME_X_DISPLAY (f), FRAME_OUTER_WINDOW (f), - x, y, width, height + FRAME_MENUBAR_HEIGHT (f)); +#ifdef USE_X_TOOLKIT + if (FRAME_PARENT_FRAME (f) && f->output_data.x->widget) + { + /* Clear widget's position coordinates because it only sends + changed values with its XConfigureWindow command. And these + are likely outdated because XtDispatchEvent does not save them. + The alternative would be to always use XtMoveWidget instead of + XMoveWindow. */ + f->output_data.x->widget->core.x = -1; + f->output_data.x->widget->core.y = -1; + /* Resize all inner widgets and Cairo surface right away so the + next redisplay drawing isn't clipped to the old size. */ + XtConfigureWidget (f->output_data.x->widget, + x, y, width, height + FRAME_MENUBAR_HEIGHT (f), 0); +#ifdef USE_CAIRO + x_cr_update_surface_desired_size (f, width, height); +#endif + } + else +#endif + XMoveResizeWindow (FRAME_X_DISPLAY (f), FRAME_OUTER_WINDOW (f), + x, y, width, height + FRAME_MENUBAR_HEIGHT (f)); SET_FRAME_GARBAGED (f); - if (FRAME_VISIBLE_P (f)) + /* Same as x_set_window_size_1. */ + if (FRAME_VISIBLE_P (f) && !FRAME_PARENT_FRAME (f)) x_wait_for_event (f, ConfigureNotify); else - /* Call adjust_frame_size right away as with GTK. It might be - tempting to clear out f->new_width and f->new_height here. */ adjust_frame_size (f, FRAME_PIXEL_TO_TEXT_WIDTH (f, width), FRAME_PIXEL_TO_TEXT_HEIGHT (f, height), 5, 0, Qx_set_window_size_1); @@ -28615,7 +28643,8 @@ x_set_window_size_and_position (struct frame *f, int width, int height) x_set_window_size_and_position_1 (f, width, height); #endif /* USE_GTK */ - x_clear_under_internal_border (f); + if (!FRAME_PARENT_FRAME (f)) + x_clear_under_internal_border (f); /* If cursor was outside the new size, mark it as off. */ mark_window_cursors_off (XWINDOW (FRAME_ROOT_WINDOW (f))); From 83b19f4d0f406cb92bd78f9a3ae42e6311d50e1c Mon Sep 17 00:00:00 2001 From: Dmitry Gutov Date: Fri, 1 May 2026 02:33:18 +0300 Subject: [PATCH 08/50] Don't wait out the whole event timeout unnecessarily * src/xterm.c (x_wait_for_event): If f->wait_event_type has been cleared (in handle_one_xevent), don't go into pselect wait. * src/androidterm.c (android_wait_for_event): Likewise. --- src/androidterm.c | 5 ++++- src/xterm.c | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/androidterm.c b/src/androidterm.c index ac1f2c88569..a74b595d499 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -2260,10 +2260,13 @@ android_wait_for_event (struct frame *f, int eventtype) { pending_signals = true; totally_unblock_input (); - /* XTread_socket is called after unblock. */ + /* android_read_socket is called after unblock. */ block_input (); interrupt_input_blocked = level; + if (!f->wait_event_type) + break; + time_now = current_timespec (); if (timespec_cmp (tmo_at, time_now) < 0) break; diff --git a/src/xterm.c b/src/xterm.c index ee727d163f5..fc4fa4f2fc7 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -28455,6 +28455,9 @@ x_wait_for_event (struct frame *f, int eventtype) block_input (); interrupt_input_blocked = level; + if (!f->wait_event_type) + break; + FD_ZERO (&fds); FD_SET (fd, &fds); From 049a94b4e565a87e05a24d007385729f9fbf12d1 Mon Sep 17 00:00:00 2001 From: Dmitry Gutov Date: Fri, 1 May 2026 03:49:09 +0300 Subject: [PATCH 09/50] Remove the effect of x_gtk_resize_child_frames=hide * src/gtkutil.c (xg_frame_set_char_size): Remove the effect of x_gtk_resize_child_frames, effectively reverting that part of c49d379f17bc (bug#80662). * src/pgtkfns.c (syms_of_pgtkfns): Remove defsyms Qhide and Qresize_mode, never used with PGTK. * src/xfns.c (x-gtk-resize-child-frames): Update docstring. --- src/gtkutil.c | 56 +-------------------------------------------------- src/pgtkfns.c | 2 -- src/xfns.c | 4 ---- 3 files changed, 1 insertion(+), 61 deletions(-) diff --git a/src/gtkutil.c b/src/gtkutil.c index fd83943e033..73e93c38906 100644 --- a/src/gtkutil.c +++ b/src/gtkutil.c @@ -1181,8 +1181,6 @@ xg_frame_set_char_size (struct frame *f, int width, int height) int outer_height = height + FRAME_TOOLBAR_HEIGHT (f) + FRAME_MENUBAR_HEIGHT (f); int outer_width = width + FRAME_TOOLBAR_WIDTH (f); - bool was_visible = false; - bool hide_child_frame; #ifndef HAVE_PGTK gtk_window_get_size (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), @@ -1251,58 +1249,6 @@ xg_frame_set_char_size (struct frame *f, int width, int height) outer_width, gheight); } #endif - else if (FRAME_PARENT_FRAME (f) && FRAME_VISIBLE_P (f)) - { - was_visible = true; -#ifndef HAVE_PGTK - hide_child_frame = EQ (x_gtk_resize_child_frames, Qhide); -#else - hide_child_frame = false; -#endif - - if (outer_width != gwidth || outer_height != gheight) - { - if (hide_child_frame) - { - block_input (); -#ifndef HAVE_PGTK - gtk_widget_hide (FRAME_GTK_OUTER_WIDGET (f)); -#else - gtk_widget_hide (FRAME_WIDGET (f)); -#endif - unblock_input (); - } - -#ifndef HAVE_PGTK - gtk_window_resize (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), - outer_width, outer_height); -#else - if (FRAME_GTK_OUTER_WIDGET (f)) - { - gtk_window_resize (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), - outer_width, outer_height); - } - else - { - gtk_widget_set_size_request (FRAME_GTK_WIDGET (f), - outer_width, outer_height); - } -#endif - - if (hide_child_frame) - { - block_input (); -#ifndef HAVE_PGTK - gtk_widget_show_all (FRAME_GTK_OUTER_WIDGET (f)); -#else - gtk_widget_show_all (FRAME_WIDGET (f)); -#endif - unblock_input (); - } - - fullscreen = Qnil; - } - } else { #ifndef HAVE_PGTK @@ -1333,7 +1279,7 @@ xg_frame_set_char_size (struct frame *f, int width, int height) size as fast as possible. For unmapped windows, we can set rows/cols. When the frame is mapped again we will (hopefully) get the correct size. */ - if (FRAME_VISIBLE_P (f) && !was_visible) + if (FRAME_VISIBLE_P (f)) { if (CONSP (frame_size_history)) frame_size_history_extra diff --git a/src/pgtkfns.c b/src/pgtkfns.c index 53fe5fbc9d0..4257dc45f98 100644 --- a/src/pgtkfns.c +++ b/src/pgtkfns.c @@ -3778,8 +3778,6 @@ syms_of_pgtkfns (void) DEFSYM (Qframe_title_format, "frame-title-format"); DEFSYM (Qicon_title_format, "icon-title-format"); DEFSYM (Qdark, "dark"); - DEFSYM (Qhide, "hide"); - DEFSYM (Qresize_mode, "resize-mode"); DEFVAR_LISP ("x-cursor-fore-pixel", Vx_cursor_fore_pixel, doc: /* SKIP: real doc in xfns.c. */); diff --git a/src/xfns.c b/src/xfns.c index 423209436c4..7427144b103 100644 --- a/src/xfns.c +++ b/src/xfns.c @@ -10484,10 +10484,6 @@ default and usually works with most desktops. Some desktop environments however, may refuse to resize a child frame when Emacs is built with GTK3. For those environments, the two settings below are provided. -If this equals the symbol `hide', Emacs temporarily hides the child -frame during resizing. This approach seems to work reliably, may -however induce some flicker when the frame is made visible again. - If this equals the symbol `resize-mode', Emacs uses GTK's resize mode to always trigger an immediate resize of the child frame. This method is deprecated by GTK and may not work in future versions of that toolkit. From 73fe7a7097da69897c04f8000a60a4f24cd6e468 Mon Sep 17 00:00:00 2001 From: Dmitry Gutov Date: Fri, 1 May 2026 03:58:17 +0300 Subject: [PATCH 10/50] Simplify the fullscreen adjustment in xg_frame_set_char_size * src/gtkutil.c (xg_frame_set_char_size): Simplify. Use the fullscreen value check to alter the values of outer_width and outer_height rather than have several larger branches (bug#80662). --- src/gtkutil.c | 57 +++++++++++---------------------------------------- 1 file changed, 12 insertions(+), 45 deletions(-) diff --git a/src/gtkutil.c b/src/gtkutil.c index 73e93c38906..00ec0a5b728 100644 --- a/src/gtkutil.c +++ b/src/gtkutil.c @@ -1218,56 +1218,23 @@ xg_frame_set_char_size (struct frame *f, int width, int height) remain unchanged but giving the frame back its normal size will be broken ... */ if (EQ (fullscreen, Qfullwidth) && width == FRAME_PIXEL_WIDTH (f)) -#ifndef HAVE_PGTK - gtk_window_resize (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), - gwidth, outer_height); -#else - if (FRAME_GTK_OUTER_WIDGET (f)) - { - gtk_window_resize (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), - gwidth, outer_height); - } - else - { - gtk_widget_set_size_request (FRAME_GTK_WIDGET (f), - gwidth, outer_height); - } -#endif + outer_width = gwidth; else if (EQ (fullscreen, Qfullheight) && height == FRAME_PIXEL_HEIGHT (f)) -#ifndef HAVE_PGTK - gtk_window_resize (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), - outer_width, gheight); -#else - if (FRAME_GTK_OUTER_WIDGET (f)) - { - gtk_window_resize (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), - outer_width, gheight); - } - else - { - gtk_widget_set_size_request (FRAME_GTK_WIDGET (f), - outer_width, gheight); - } -#endif + outer_height = gheight; else - { + fullscreen = Qnil; + #ifndef HAVE_PGTK - gtk_window_resize (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), - outer_width, outer_height); + gtk_window_resize (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), + outer_width, outer_height); #else - if (FRAME_GTK_OUTER_WIDGET (f)) - { - gtk_window_resize (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), - outer_width, outer_height); - } - else - { - gtk_widget_set_size_request (FRAME_GTK_WIDGET (f), - outer_width, outer_height); - } + if (FRAME_GTK_OUTER_WIDGET (f)) + gtk_window_resize (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), + outer_width, outer_height); + else + gtk_widget_set_size_request (FRAME_GTK_WIDGET (f), + outer_width, outer_height); #endif - fullscreen = Qnil; - } SET_FRAME_GARBAGED (f); cancel_mouse_face (f); From 6cd5b16dd0e92ce0819530052c7983593a427bb3 Mon Sep 17 00:00:00 2001 From: Dmitry Gutov Date: Fri, 1 May 2026 04:42:57 +0300 Subject: [PATCH 11/50] Resize child frames with GTK3 immediately too * src/gtkutil.c (xg_frame_set_char_size): Resize child frame and its GTK widget hierarchy immediately. Update the "desired" Cairo surface dimensions. Skip waiting for events. (bug#80662) Remove the unnecessary call to clear_under_internal_border. --- src/gtkutil.c | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/gtkutil.c b/src/gtkutil.c index 00ec0a5b728..8c01f26ef2f 100644 --- a/src/gtkutil.c +++ b/src/gtkutil.c @@ -1200,9 +1200,6 @@ xg_frame_set_char_size (struct frame *f, int width, int height) } #endif - /* Do this before resize, as we don't know yet if we will be resized. */ - FRAME_RIF (f)->clear_under_internal_border (f); - outer_height /= xg_get_scale (f); outer_width /= xg_get_scale (f); @@ -1225,13 +1222,26 @@ xg_frame_set_char_size (struct frame *f, int width, int height) fullscreen = Qnil; #ifndef HAVE_PGTK - gtk_window_resize (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), - outer_width, outer_height); + if (FRAME_PARENT_FRAME (f)) + { + gdk_window_resize (gtk_widget_get_window (FRAME_GTK_OUTER_WIDGET (f)), + outer_width, outer_height); + /* Resize all inner widgets and Cairo surface right away so the + next redisplay drawing isn't clipped to the old size. */ + GtkAllocation alloc = {0, 0, outer_width, outer_height}; + gtk_widget_size_allocate (FRAME_GTK_OUTER_WIDGET (f), &alloc); +#ifdef USE_CAIRO + x_cr_update_surface_desired_size (f, width, height); +#endif + } + else + gtk_window_resize (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), + outer_width, outer_height); #else if (FRAME_GTK_OUTER_WIDGET (f)) gtk_window_resize (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), outer_width, outer_height); - else + else /* PGTK child frame. */ gtk_widget_set_size_request (FRAME_GTK_WIDGET (f), outer_width, outer_height); #endif @@ -1246,7 +1256,7 @@ xg_frame_set_char_size (struct frame *f, int width, int height) size as fast as possible. For unmapped windows, we can set rows/cols. When the frame is mapped again we will (hopefully) get the correct size. */ - if (FRAME_VISIBLE_P (f)) + if (FRAME_VISIBLE_P (f) && !FRAME_PARENT_FRAME (f)) { if (CONSP (frame_size_history)) frame_size_history_extra @@ -1347,7 +1357,7 @@ xg_frame_set_size_and_position (struct frame *f, int width, int height) #else if (FRAME_GTK_OUTER_WIDGET (f)) gdk_window_move_resize (gwin, x, y, outer_width, outer_height); - else + else /* PGTK child frame. */ gtk_widget_set_size_request (FRAME_GTK_WIDGET (f), outer_width, outer_height); #endif From 19696dbc24fc765604121ac4e73cbf354080f199 Mon Sep 17 00:00:00 2001 From: Yuan Fu Date: Thu, 30 Apr 2026 21:29:36 -0700 Subject: [PATCH 12/50] ; Add comment thing for c-ts-mode * lisp/progmodes/c-ts-mode.el (c-ts-mode--thing-settings): Add comment thing. --- lisp/progmodes/c-ts-mode.el | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lisp/progmodes/c-ts-mode.el b/lisp/progmodes/c-ts-mode.el index 591000cb440..c8120d5752c 100644 --- a/lisp/progmodes/c-ts-mode.el +++ b/lisp/progmodes/c-ts-mode.el @@ -1274,7 +1274,8 @@ if `c-ts-mode-emacs-sources-support' is non-nil." "goto_statement" "case_statement"))) (text ,(regexp-opt '("comment" - "raw_string_literal")))) + "raw_string_literal"))) + (comment "comment")) "`treesit-thing-settings' for both C and C++.") ;;; Support for FOR_EACH_* macros From dd3f0053d25188c1f230b4b8dfeee9709f3e1c5a Mon Sep 17 00:00:00 2001 From: Eshel Yaron Date: Fri, 1 May 2026 11:22:14 +0200 Subject: [PATCH 13/50] ; (external-completion-table): Fix a couple of typos. --- lisp/external-completion.el | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lisp/external-completion.el b/lisp/external-completion.el index aa23c949241..a361710b686 100644 --- a/lisp/external-completion.el +++ b/lisp/external-completion.el @@ -67,8 +67,8 @@ may be a shell utility, an inferior process, an http server, etc. Given a pattern string, the external tool matches it to an arbitrarily large set of candidates. Since the full set doesn't need to be transferred to Emacs's address space, this often -results in much faster overall experience, at the expense of the -convenience of offered by other completion styles. +results in a much faster overall experience, at the expense of the +convenience offered by other completion styles. CATEGORY is a symbol uniquely naming the external tool. This function links CATEGORY to the style `external', by modifying @@ -95,7 +95,7 @@ non-list. METADATA is an alist of additional properties such as `cycle-sort-function' to associate with CATEGORY. This means -that the caller may still retain control the sorting of the +that the caller may still retain control of the sorting of the candidates while the tool controls the matching. Optional TRY-COMPLETION-FUNCTION helps some frontends partially @@ -105,7 +105,7 @@ ALL-COMPLETIONS), where PATTERN and POINT are as described above and ALL-COMPLETIONS are gathered by LOOKUP for these arguments (this function ensures LOOKUP isn't called more than needed). If you know the matching method that the external tool -using, TRY-COMPLETION-FUNCTION may return a cons +is using, TRY-COMPLETION-FUNCTION may return a cons cell (EXPANDED-PATTERN . NEW-POINT). For example, if the tool is completing by prefix, one could call `try-completion' to find the largest common prefix in ALL-COMPLETIONS and then return that as From d80c9e534d78c8e07f9e32092762cc426403aa8b Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Fri, 1 May 2026 10:46:16 +0100 Subject: [PATCH 14/50] ; project.el: Use when-let* not if-let* where appropriate. --- lisp/progmodes/project.el | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/lisp/progmodes/project.el b/lisp/progmodes/project.el index e7078d8e972..9a9dc6df186 100644 --- a/lisp/progmodes/project.el +++ b/lisp/progmodes/project.el @@ -2222,10 +2222,10 @@ With some possible metadata (to be decided).") "Initialize `project--list' if it isn't already initialized." (when (eq project--list 'unset) (project--read-project-list) - (if-let* ((pred (alist-get 'list-first-read project-prune-zombie-projects)) - ((consp project--list)) - (inhibit-message t)) - (project--delete-zombie-projects pred)))) + (when-let* ((pred (alist-get 'list-first-read project-prune-zombie-projects)) + ((consp project--list)) + (inhibit-message t)) + (project--delete-zombie-projects pred)))) (defun project--write-project-list () "Save `project--list' in `project-list-file'." @@ -2234,10 +2234,10 @@ With some possible metadata (to be decided).") (insert ";;; -*- lisp-data -*-\n") (let ((print-length nil) (print-level nil)) - (if-let* ((pred (alist-get 'list-write project-prune-zombie-projects)) - ((consp project--list)) - (inhibit-message t)) - (project--delete-zombie-projects pred)) + (when-let* ((pred (alist-get 'list-write project-prune-zombie-projects)) + ((consp project--list)) + (inhibit-message t)) + (project--delete-zombie-projects pred)) (pp (mapcar (lambda (elem) (let ((name (car elem))) (list (if (file-remote-p name) name @@ -2323,9 +2323,9 @@ Unless REQUIRE-KNOWN is non-nil, it's also possible to enter an arbitrary directory not in the list of known projects. If ALLOW-EMPTY is non-nil, it is possible to exit with no input." (project--ensure-read-project-list) - (if-let* ((pred (alist-get 'prompt project-prune-zombie-projects)) - (inhibit-message t)) - (project--delete-zombie-projects pred)) + (when-let* ((pred (alist-get 'prompt project-prune-zombie-projects)) + (inhibit-message t)) + (project--delete-zombie-projects pred)) (let* ((dir-choice "... (choose a dir)") (choices ;; XXX: Just using this for the category (for the substring @@ -2367,9 +2367,9 @@ function; see `project-prompter' for more details. Unless REQUIRE-KNOWN is non-nil, it's also possible to enter an arbitrary directory not in the list of known projects. If ALLOW-EMPTY is non-nil, it is possible to exit with no input." - (if-let* ((pred (alist-get 'prompt project-prune-zombie-projects)) - (inhibit-message t)) - (project--delete-zombie-projects pred)) + (when-let* ((pred (alist-get 'prompt project-prune-zombie-projects)) + (inhibit-message t)) + (project--delete-zombie-projects pred)) (let* ((dir-choice "... (choose a dir)") project--name-history (choices From 6583cc4fdfc43a59059ace281dcb24b70280c4f2 Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Fri, 1 May 2026 16:00:26 +0200 Subject: [PATCH 15/50] Update to Transient v0.13.1-10-gc168d396 --- doc/misc/transient.texi | 4 ++-- lisp/transient.el | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/misc/transient.texi b/doc/misc/transient.texi index b0eebec3071..35544b2917c 100644 --- a/doc/misc/transient.texi +++ b/doc/misc/transient.texi @@ -31,7 +31,7 @@ General Public License for more details. @finalout @titlepage @title Transient User and Developer Manual -@subtitle for version 0.13.0 +@subtitle for version 0.13.1 @author Jonas Bernoulli @page @vskip 0pt plus 1filll @@ -53,7 +53,7 @@ resource to get over that hurdle is Psionic K's interactive tutorial, available at @uref{https://github.com/positron-solutions/transient-showcase}. @noindent -This manual is for Transient version 0.13.0. +This manual is for Transient version 0.13.1. @insertcopying @end ifnottex diff --git a/lisp/transient.el b/lisp/transient.el index e04437dd914..ae49a38579d 100644 --- a/lisp/transient.el +++ b/lisp/transient.el @@ -5,7 +5,7 @@ ;; Author: Jonas Bernoulli ;; URL: https://github.com/magit/transient ;; Keywords: extensions -;; Version: 0.13.0 +;; Version: 0.13.1 ;; SPDX-License-Identifier: GPL-3.0-or-later @@ -39,7 +39,7 @@ ;;; Code: ;;;; Frontmatter -(defconst transient-version "v0.13.0-10-g5b2ff26f-builtin") +(defconst transient-version "v0.13.1-10-gc168d396-builtin") (require 'cl-lib) (require 'eieio) @@ -5523,11 +5523,11 @@ search instead." 2) lisp-imenu-generic-expression :test #'equal) -(defvar overriding-text-conversion-style) - (defun transient--suspend-text-conversion-style () - (when (and text-conversion-style - (bound-and-true-p overriding-text-conversion-style)) + (when (and (bound-and-true-p text-conversion-style) + (bound-and-true-p overriding-text-conversion-style) + ;; Somehow the above does not silence the compiler. + (boundp 'overriding-text-conversion-style)) (letrec ((suspended overriding-text-conversion-style) (fn (lambda () (setq overriding-text-conversion-style nil) From 92788f3be43b43f82876b9d0e263d5e55d00ec0e Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Fri, 1 May 2026 22:06:03 +0200 Subject: [PATCH 16/50] Update to Transient v0.13.2-10-gf7894ca4 --- doc/misc/transient.texi | 4 ++-- lisp/transient.el | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/misc/transient.texi b/doc/misc/transient.texi index 35544b2917c..f454d81c63e 100644 --- a/doc/misc/transient.texi +++ b/doc/misc/transient.texi @@ -31,7 +31,7 @@ General Public License for more details. @finalout @titlepage @title Transient User and Developer Manual -@subtitle for version 0.13.1 +@subtitle for version 0.13.2 @author Jonas Bernoulli @page @vskip 0pt plus 1filll @@ -53,7 +53,7 @@ resource to get over that hurdle is Psionic K's interactive tutorial, available at @uref{https://github.com/positron-solutions/transient-showcase}. @noindent -This manual is for Transient version 0.13.1. +This manual is for Transient version 0.13.2. @insertcopying @end ifnottex diff --git a/lisp/transient.el b/lisp/transient.el index ae49a38579d..20b61d0f4c3 100644 --- a/lisp/transient.el +++ b/lisp/transient.el @@ -5,7 +5,7 @@ ;; Author: Jonas Bernoulli ;; URL: https://github.com/magit/transient ;; Keywords: extensions -;; Version: 0.13.1 +;; Version: 0.13.2 ;; SPDX-License-Identifier: GPL-3.0-or-later @@ -39,7 +39,7 @@ ;;; Code: ;;;; Frontmatter -(defconst transient-version "v0.13.1-10-gc168d396-builtin") +(defconst transient-version "v0.13.2-10-gf7894ca4-builtin") (require 'cl-lib) (require 'eieio) From 0179e3e062b9287bc5b9812e4be0cbe7eac49ac0 Mon Sep 17 00:00:00 2001 From: Paul Eggert Date: Fri, 1 May 2026 17:59:14 -0700 Subject: [PATCH 17/50] Work around GCC bug 125116 * configure.ac: When configured with --enable-gcc-warnings, use the -Wno-analyzer-allocation-size option if available. This works around a false positive bug in GCC 16.0.1 20260321 (Red Hat 16.0.1-0) x86-64; see . --- configure.ac | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index a3ba5e69a2f..eeaaaa53a3d 100644 --- a/configure.ac +++ b/configure.ac @@ -1790,7 +1790,9 @@ AS_IF([test $gl_gcc_warnings = no], ;; esac AS_IF([test $gl_gcc_warnings = yes], - [WERROR_CFLAGS=-Werror], + [WERROR_CFLAGS=-Werror + # Work around GCC bug 125116. + gl_WARN_ADD([-Wno-analyzer-allocation-size])], [# Use -fanalyzer and related options only if --enable-gcc-warnings, # as they slow GCC considerably. nw="$nw -fanalyzer -Wno-analyzer-double-free -Wno-analyzer-malloc-leak" From 1b14d6f92b1b8a692ae4654699abeee03fc6b289 Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Sat, 2 May 2026 10:03:53 +0300 Subject: [PATCH 18/50] * src/.gdbinit: Ignore SIGPIPE. (Bug#80911) --- src/.gdbinit | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/.gdbinit b/src/.gdbinit index f9ecb2cef5a..a82fedf3027 100644 --- a/src/.gdbinit +++ b/src/.gdbinit @@ -41,15 +41,13 @@ handle SIGTSTP nopass handle SIGUSR1 noprint pass handle SIGUSR2 noprint pass +# Similarly with SIGPIPE (happens, e.g., with GnuTLS). +handle SIGPIPE nostop noprint pass + # Don't pass SIGALRM to Emacs. This makes problems when # debugging. handle SIGALRM ignore -# On selection send failed. -if defined_HAVE_PGTK - handle SIGPIPE nostop noprint -end - # Helper command to get the pointer to the C struct that holds the data # of a Lisp object given as argument, by removing the GC and type-tag bits. # Stores the result in $ptr. From 17f9f0c97d342305b4d97f9b11892def97c751e9 Mon Sep 17 00:00:00 2001 From: Philip Kaludercic Date: Fri, 1 May 2026 19:19:25 +0200 Subject: [PATCH 19/50] * etc/themes/newcomers-presets-theme.el: Fix checkdoc issue --- etc/themes/newcomers-presets-theme.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/themes/newcomers-presets-theme.el b/etc/themes/newcomers-presets-theme.el index 2c69f26da4c..2163631324e 100644 --- a/etc/themes/newcomers-presets-theme.el +++ b/etc/themes/newcomers-presets-theme.el @@ -19,7 +19,7 @@ ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs. If not, see . -;;; Commentary +;;; Commentary: ;; This theme configures user options that we can reasonably expect the ;; average, new user to want to enable, but would otherwise be unlikely From 87da929eb50c614a5e1c4e8cba5279b8b1d5c04c Mon Sep 17 00:00:00 2001 From: Philip Kaludercic Date: Sat, 2 May 2026 10:03:33 +0200 Subject: [PATCH 20/50] Don't break line when inserting tags * lisp/textmodes/sgml-mode.el (html-tag-alist): Remove '\n' from the completion rules for "code" tag. (Bug#80771) --- lisp/textmodes/sgml-mode.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp/textmodes/sgml-mode.el b/lisp/textmodes/sgml-mode.el index 2170fcea6e9..42aa1ed4d3b 100644 --- a/lisp/textmodes/sgml-mode.el +++ b/lisp/textmodes/sgml-mode.el @@ -2034,7 +2034,7 @@ This takes effect when first loading the library.") ("caption" ("valign" ("top") ("bottom"))) ("center" \n) ("cite") - ("code" \n) + ("code") ("datalist" \n) ("dd" ,(not sgml-xml-mode)) ("del" nil ("cite") ("datetime")) From cb21b7d71f4ef0f5df160c5fabdb9622416283e2 Mon Sep 17 00:00:00 2001 From: Philip Kaludercic Date: Sat, 2 May 2026 10:24:10 +0200 Subject: [PATCH 21/50] Mark myself as maintainer of sgml-mode * admin/MAINTAINERS: Update my entry. * lisp/textmodes/sgml-mode.el: Update "Maintainer" header. --- admin/MAINTAINERS | 1 + lisp/textmodes/sgml-mode.el | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/admin/MAINTAINERS b/admin/MAINTAINERS index 41bc780d1cc..3ffa21c0d45 100644 --- a/admin/MAINTAINERS +++ b/admin/MAINTAINERS @@ -235,6 +235,7 @@ Philip Kaludercic lisp/emacs-lisp/package.el lisp/emacs-lisp/package-vc.el lisp/emacs-lisp/compat.el + lisp/textmodes/sgml-mode.el Yuan Fu src/treesit.c diff --git a/lisp/textmodes/sgml-mode.el b/lisp/textmodes/sgml-mode.el index 42aa1ed4d3b..c95386c16ac 100644 --- a/lisp/textmodes/sgml-mode.el +++ b/lisp/textmodes/sgml-mode.el @@ -4,7 +4,7 @@ ;; Foundation, Inc. ;; Author: James Clark -;; Maintainer: emacs-devel@gnu.org +;; Maintainer: Philip Kaludercic ;; Adapted-By: ESR, Daniel Pfeiffer , ;; F.Potorti@cnuce.cnr.it ;; Keywords: text, hypermedia, comm, languages From 4795e83a69484de276c1e2b0b2d9a04525d9b05c Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Fri, 1 May 2026 11:33:41 +0100 Subject: [PATCH 22/50] Project prompters always default to current project, if any * lisp/progmodes/project.el (project-prompter) (project-prompt-project-dir, project-prompt-project-name): Delete ALLOW-EMPTY parameter. Default to the current project if there is one. * lisp/vc/vc.el (project-root): Declare. (vc--prompt-other-working-tree): Replace ALLOW-EMPTY parameter with new ALLOW-CURRENT parameter. (vc-working-tree-switch-project): Allow selecting the current working tree, for symmetry with project-switch-project. * etc/NEWS: Update. --- etc/NEWS | 5 +- lisp/progmodes/project.el | 108 ++++++++++++++++++++------------------ lisp/vc/vc.el | 40 +++++++------- 3 files changed, 80 insertions(+), 73 deletions(-) diff --git a/etc/NEWS b/etc/NEWS index 8f266f2d670..f195e0a9b51 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -976,11 +976,10 @@ It is equivalent to running 'project-any-command' with The prompt now displays the chosen project on which to invoke a command. --- -*** 'project-prompter' values may be called with up to four arguments. +*** 'project-prompter' values may be called with up to three arguments. These allow callers of the value of 'project-prompter' to specify a prompt string; prompt the user to choose between a subset of all the -known projects; disallow returning arbitrary directories; and allow -returning an empty string. +known projects; and disallow returning arbitrary directories. See the docstring of 'project-prompter' for a full specification of these new optional arguments. diff --git a/lisp/progmodes/project.el b/lisp/progmodes/project.el index 9a9dc6df186..7e794330b1f 100644 --- a/lisp/progmodes/project.el +++ b/lisp/progmodes/project.el @@ -212,8 +212,8 @@ When it is non-nil, `project-current' will always skip prompting too.") (defcustom project-prompter #'project-prompt-project-dir "Function to call to prompt for a project. -The function is called either with no arguments or with up to four -optional arguments: (&optional PROMPT PREDICATE REQUIRE-KNOWN ALLOW-EMPTY). +The function is called either with no arguments or with up to three +optional arguments: (&optional PROMPT PREDICATE REQUIRE-KNOWN). PROMPT is the prompt string to use. @@ -231,12 +231,14 @@ may allow the user to input arbitrary directories. If PREDICATE and REQUIRE-KNOWN are both non-nil, the value of `project-prompter' should not return any project root directory for which PREDICATE returns nil. -If ALLOW-EMPTY is non-nil, then irrespective of REQUIRE-KNOWN, the user -may enter nothing (i.e., just type RET). -In this case the function should return \"\". Conventionally this is -used to allow the user to select the current project. -Callers should append something like \" (empty for current project)\" to -PROMPT when passing ALLOW-EMPTY non-nil." +The function must always return a valid project. + +If there is a current project, it satisfies PREDICATE (or PREDICATE is +nil), and the method of prompting involves a default selection, then +this default selection should be the current project root. For example +if the function uses `completing-read' then the current project, if any, +should be passed as the DEF argument to `completing-read', and returned +in the case that the user replies with empty input." :type '(choice (const :tag "Prompt for a project directory" project-prompt-project-dir) (const :tag "Prompt for a project name" @@ -1416,7 +1418,7 @@ directories listed in `vc-directory-exclusion-list'." project "Find file" all-files nil 'file-name-history suggested-filename))) - (if (string= file "") + (if (string-empty-p file) (user-error "You didn't specify the file") (find-file file)))) @@ -1845,7 +1847,7 @@ Return non-nil if PROJECT is not a remote project." '(metadata . ((category . project-buffer) (cycle-sort-function . identity)))) ((and (eq action t) - (equal string "")) ;Pcm completion or empty prefix. + (string-empty-p string)) ;Pcm completion or empty prefix. (let* ((all (complete-with-action action buffers string pred)) (non-internal (cl-remove-if (lambda (b) (= (aref b 0) ?\s)) all))) (if (null non-internal) @@ -2311,8 +2313,7 @@ the project list." (defvar project--dir-history) -(defun project-prompt-project-dir - (&optional prompt predicate require-known allow-empty) +(defun project-prompt-project-dir (&optional prompt predicate require-known) "Prompt the user for a directory that is one of the known project roots. The project is chosen among projects known from the project list, see `project-list-file'. @@ -2320,13 +2321,17 @@ If PROMPT is non-nil, use it as the prompt string. If PREDICATE is non-nil, filter possible project choices using this function; see `project-prompter' for more details. Unless REQUIRE-KNOWN is non-nil, it's also possible to enter an -arbitrary directory not in the list of known projects. -If ALLOW-EMPTY is non-nil, it is possible to exit with no input." +arbitrary directory not in the list of known projects." (project--ensure-read-project-list) (when-let* ((pred (alist-get 'prompt project-prune-zombie-projects)) (inhibit-message t)) (project--delete-zombie-projects pred)) (let* ((dir-choice "... (choose a dir)") + (current (and-let* ((p (project-current)) + (_ (or (null predicate) + (funcall predicate + (project-root p))))) + (project-root p))) (choices ;; XXX: Just using this for the category (for the substring ;; completion style). @@ -2334,30 +2339,29 @@ If ALLOW-EMPTY is non-nil, it is possible to exit with no input." (if require-known project--list (append project--list `(,dir-choice))))) (project--dir-history (project-known-project-roots)) - pr-dir) - (cl-loop - do (setq pr-dir - (let (history-add-new-input) - (completing-read (if prompt - ;; TODO: Use `format-prompt' (Emacs 28.1+) - (format "%s: " (substitute-command-keys prompt)) - "Select project: ") - choices - (and predicate - (lambda (choice) - (or (equal choice dir-choice) - (funcall predicate choice)))) - t nil 'project--dir-history))) - ;; If the user simply pressed RET, do this again until they don't. - while (and (not allow-empty) (equal pr-dir ""))) + (pr-dir "")) + (while (string-empty-p pr-dir) + ;; If the user simply pressed RET (and CURRENT is nil), do this + ;; again until they don't. + (setq pr-dir + (let (history-add-new-input) + (completing-read + ;; Emacs 28.1+: Use `format-prompt'. + (cond (prompt (format "%s: " prompt)) + (current "Select project (default current project): ") + (t "Select project: ")) + choices (and predicate + (lambda (choice) + (or (equal choice dir-choice) + (funcall predicate choice)))) + t nil 'project--dir-history current)))) (if (equal pr-dir dir-choice) (read-directory-name "Select directory: " default-directory nil t) pr-dir))) (defvar project--name-history) -(defun project-prompt-project-name - (&optional prompt predicate require-known allow-empty) +(defun project-prompt-project-name (&optional prompt predicate require-known) "Prompt the user for a project, by name, that is one of the known project roots. The project is chosen among projects known from the project list, see `project-list-file'. @@ -2365,13 +2369,17 @@ If PROMPT is non-nil, use it as the prompt string. If PREDICATE is non-nil, filter possible project choices using this function; see `project-prompter' for more details. Unless REQUIRE-KNOWN is non-nil, it's also possible to enter an -arbitrary directory not in the list of known projects. -If ALLOW-EMPTY is non-nil, it is possible to exit with no input." +arbitrary directory not in the list of known projects." (when-let* ((pred (alist-get 'prompt project-prune-zombie-projects)) (inhibit-message t)) (project--delete-zombie-projects pred)) (let* ((dir-choice "... (choose a dir)") project--name-history + (current (and-let* ((p (project-current)) + (_ (or (null predicate) + (funcall predicate + (project-root p))))) + (project-name p))) (choices (let (ret) ;; Iterate in reverse order so project--name-history is in @@ -2390,22 +2398,22 @@ If ALLOW-EMPTY is non-nil, it is possible to exit with no input." (table (project--file-completion-table (reverse (if require-known choices (cons dir-choice choices))))) - pr-name) - (cl-loop - do (setq pr-name - (let (history-add-new-input) - (completing-read (if prompt - (format "%s: " prompt) - "Select project: ") - table nil t nil 'project--name-history))) - ;; If the user simply pressed RET, do this again until they don't. - while (and (not allow-empty) (equal pr-name ""))) - (pcase pr-name - ("" "") - ((pred (equal dir-choice)) (read-directory-name "Select directory: " - default-directory nil t)) - (_ (let ((proj (assoc pr-name choices))) - (if (stringp proj) proj (project-root (cdr proj)))))))) + (pr-name "")) + (while (string-empty-p pr-name) + ;; If the user simply pressed RET (and CURRENT is nil), do this + ;; again until they don't. + (setq pr-name + (let (history-add-new-input) + (completing-read + ;; Emacs 28.1+: Use `format-prompt'. + (cond (prompt (format "%s: " prompt)) + (current "Select project (default current project): ") + (t "Select project: ")) + table nil t nil 'project--name-history current)))) + (if (equal pr-name dir-choice) + (read-directory-name "Select directory: " default-directory nil t) + (let ((proj (assoc pr-name choices))) + (if (stringp proj) proj (project-root (cdr proj))))))) ;;;###autoload (defun project-known-project-roots () diff --git a/lisp/vc/vc.el b/lisp/vc/vc.el index 3d8c4bf4f1c..303926b8159 100644 --- a/lisp/vc/vc.el +++ b/lisp/vc/vc.el @@ -5732,15 +5732,14 @@ When called from Lisp, BACKEND is the VC backend." (dired directory)) (defvar project-prompter) +(declare-function project-root "project") -(defun vc--prompt-other-working-tree (backend prompt &optional allow-empty) +(defun vc--prompt-other-working-tree (backend prompt &optional allow-current) "Invoke `project-prompter' to choose another working tree. BACKEND is the VC backend. PROMPT is the prompt string for `project-prompter'. -If ALLOW-EMPTY is non-nil, empty input means the current working tree. -In typical usage ALLOW-EMPTY non-nil means that it makes sense to apply -the caller's operation to the current working tree." - ;; If there are no other working trees and ALLOW-EMPTY is non-nil, we +If ALLOW-CURRENT is non-nil, allow selecting the current working tree." + ;; If there are no other working trees and ALLOW-CURRENT is non-nil we ;; still invoke the `project-prompter' and require the user to type ;; \\`RET', even though it's redundant. Doing it this way means that ;; invoking the command on the current working tree works the same @@ -5752,25 +5751,25 @@ the caller's operation to the current working tree." ;; stopping to look at the echo area. (let ((trees (vc-call-backend backend 'known-other-working-trees)) res) - (unless (or trees allow-empty) - (user-error - (substitute-command-keys - "No other working trees. Use \\[vc-add-working-tree] to add one"))) (require 'project) + (cond* ((bind-and* (_ allow-current) + (p (project-current))) + (push (project-root p) trees)) + ((null trees) + (user-error + (substitute-command-keys + "No other working trees. Use \\[vc-add-working-tree] to add one")))) (dolist (tree trees) (when-let* ((p (project-current nil tree))) (project-remember-project p nil t))) (setq res (funcall project-prompter - (if allow-empty - (format "%s (empty for this working tree)" - prompt) + (if allow-current + (concat prompt " (default current working tree)") prompt) - (if trees - (lambda (k &optional _v) - (member (or (car-safe k) k) trees)) - #'ignore) - t allow-empty)) + (lambda (k &optional _v) + (member (or (car-safe k) k) trees)) + 'require-known)) (if (string-empty-p res) (vc-root-dir) res))) (defvar project-current-directory-override) @@ -5801,7 +5800,8 @@ Prompts for the directory file name of the other working tree." (interactive (list (vc--prompt-other-working-tree (vc-responsible-backend default-directory) - "Other working tree to switch to"))) + "Other working tree to switch to" + 'allow-current))) (project-switch-project dir)) ;;;###autoload @@ -5814,7 +5814,7 @@ BACKEND is the VC backend." (let ((backend (vc-responsible-backend default-directory))) (list backend (vc--prompt-other-working-tree backend "Delete working tree" - 'allow-empty)))) + 'allow-current)))) (let* ((delete-this (file-in-directory-p default-directory directory)) (directory (expand-file-name directory)) (default-directory @@ -5860,7 +5860,7 @@ BACKEND is the VC backend." (let ((backend (vc-responsible-backend default-directory))) (list backend (vc--prompt-other-working-tree backend "Relocate working tree" - 'allow-empty) + 'allow-current) (read-directory-name "New location for working tree: " (file-name-parent-directory (vc-root-dir)))))) (let* ((move-this (file-in-directory-p default-directory from)) From 2207a588997c3ccd15ad51cf41c4bbdd8b73191f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20T=C3=A1vora?= Date: Sat, 2 May 2026 09:53:20 +0100 Subject: [PATCH 23/50] Eglot: find well behaved UTF char for code actions (bug#80326) * lisp/progmodes/eglot.el (eglot-code-action-indicator): No lighbulb, no fancy lightning bolt, just use zigzags which seem to display well on typical fonts and typically have a width of 1. --- lisp/progmodes/eglot.el | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 13d578d550a..b41fd3d2212 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -625,12 +625,12 @@ Note additionally: :package-version '(Eglot . "1.19")) (defcustom eglot-code-action-indicator - (cl-loop for c in '(?💡 ?⚡?✓ ?α ??) + (cl-loop for c in '(?↯ ?⭍ ?✓ ?α ??) when (char-displayable-p c) return (make-string 1 c)) "Indicator string for code action suggestions." :type (let ((basic-choices - (cl-loop for c in '(?💡 ?⚡?✓ ?α ??) + (cl-loop for c in '(?↯ ?⭍ ?✓ ?α ??) when (char-displayable-p c) collect `(const :tag ,(format "Use `%c'" c) ,(make-string 1 c))))) From e575817e8feffd3db3b4fe953e0cc8640481acd1 Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Sat, 2 May 2026 12:35:48 +0300 Subject: [PATCH 24/50] ; Improve documentation of Emacs server-client protocol * lib-src/emacsclient.c (main): * lisp/server.el (server-process-filter): Document the protocol requirements regarding the terminating newline. (Bug#80807) --- lib-src/emacsclient.c | 14 +++++++++++--- lisp/server.el | 4 +++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/lib-src/emacsclient.c b/lib-src/emacsclient.c index 134c2217650..0769c94a89d 100644 --- a/lib-src/emacsclient.c +++ b/lib-src/emacsclient.c @@ -2257,6 +2257,11 @@ main (int argc, char **argv) char *p = recv_buf; for (char *end_p = p; end_p < recv_buf + nrecv; p = end_p) { + /* An unquoted newline ends a server command. Keep reading, + possibly growing the buffer, until a buffer with a newline + is received. This handles commands with arbitrary-long + arguments (actually needed in 'print' and 'error' commands, + which are followed by strings). */ end_p = memchr (p, '\n', recv_buf + nrecv - p); if (!end_p) break; @@ -2288,7 +2293,8 @@ main (int argc, char **argv) } else if (strprefix ("-print ", p)) { - /* -print STRING: Print STRING on the terminal. */ + /* -print STRING: Print STRING, preceeded by a newline, on + the terminal. */ if (!suppress_output) { char *str = unquote_argument (p + strlen ("-print ")); @@ -2299,8 +2305,10 @@ main (int argc, char **argv) } else if (strprefix ("-print-nonl ", p)) { - /* -print-nonl STRING: Print STRING on the terminal. - Used to continue a preceding -print command. */ + /* -print-nonl STRING: Print STRING on the terminal + without a preceding newlin. Used to continue a + preceding -print command. Nowadays used only for + servers in Emacs versions before 31. */ if (!suppress_output) { char *str = unquote_argument (p + strlen ("-print-nonl ")); diff --git a/lisp/server.el b/lisp/server.el index c2aa854a4bb..b912b185275 100644 --- a/lisp/server.el +++ b/lisp/server.el @@ -1143,7 +1143,9 @@ The following commands are accepted by the client: `-print-nonl STRING' Print STRING on stdout. Used to continue a preceding -print command that would be too big to send - in a single message. + in a single message. Unused in the server since Emacs 31; + mentioned here only for completeness, because the client + needs to support it when it connects to older Emacsen. `-error DESCRIPTION' Signal an error and delete process PROC. From 25659d5a75e837bdd2197b11fac1d02969a7da06 Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Sat, 2 May 2026 13:10:33 +0300 Subject: [PATCH 25/50] ; * lisp/emacs-lisp/pcase.el (pcase-exhaustive): Doc fix. --- lisp/emacs-lisp/pcase.el | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lisp/emacs-lisp/pcase.el b/lisp/emacs-lisp/pcase.el index 6126679e870..166b346fbbe 100644 --- a/lisp/emacs-lisp/pcase.el +++ b/lisp/emacs-lisp/pcase.el @@ -228,10 +228,9 @@ Emacs Lisp manual for more information and examples." ;;;###autoload (defmacro pcase-exhaustive (exp &rest cases) "The exhaustive version of `pcase' (which see). -If EXP fails to match any of the patterns in CASES, an error is -signaled. +If EXP fails to match any of the patterns in CASES, signal an error. -In contrast, `pcase' will return nil if there is no match, but +In contrast, `pcase' will return nil if there is no match, but will not signal an error." (declare (indent 1) (debug pcase)) (let* ((x (gensym "x")) From 0d0891c1bb8a6cb29f26c243afd6edaf1d94092a Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Sat, 2 May 2026 13:26:46 +0300 Subject: [PATCH 26/50] ; Fix an issue with counting glyphs on TTY frames * src/dispnew.c (fake_current_matrices): Fix the 'used' count of glyphs for the right margin and the text-area. (Bug#80900) --- src/dispnew.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/dispnew.c b/src/dispnew.c index 736647408cb..284a0eb175f 100644 --- a/src/dispnew.c +++ b/src/dispnew.c @@ -1937,6 +1937,11 @@ fake_current_matrices (Lisp_Object window) { r->used[LEFT_MARGIN_AREA] = m->left_margin_glyphs; r->used[RIGHT_MARGIN_AREA] = m->right_margin_glyphs; + /* Non-rightmost windows have the border glyph at the + end of the right margin, if any, in addition to the + glyphs reserved for the margin itself. */ + if (m->right_margin_glyphs > 0 && !WINDOW_RIGHTMOST_P (w)) + r->used[RIGHT_MARGIN_AREA]++; r->used[TEXT_AREA] = (m->matrix_w - r->used[LEFT_MARGIN_AREA] - r->used[RIGHT_MARGIN_AREA]); From d51a4722316efe0960994d371e1859099894d1ca Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Sat, 2 May 2026 14:17:14 +0300 Subject: [PATCH 27/50] ; * src/sfnt.c (sfnt_read_name_table): Avoid 32-bit overflow. --- src/sfnt.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/sfnt.c b/src/sfnt.c index d9cf1fa1213..f778179a5ff 100644 --- a/src/sfnt.c +++ b/src/sfnt.c @@ -5792,6 +5792,10 @@ sfnt_read_name_table (int fd, struct sfnt_offset_subtable *subtable) if (directory->length < required) return NULL; + /* Avoid overflow in xmalloc argument below. */ + if (directory->length > UINT_MAX - sizeof *name) + return NULL; + /* Allocate enough to hold the name table and variable length data. */ name = xmalloc (sizeof *name + directory->length); From b376c405aa8d0bec62e38b0dd526b6db31c2e350 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Fri, 1 May 2026 15:23:27 +0200 Subject: [PATCH 28/50] ; Add a test for sqlite-close * test/src/sqlite-tests.el (sqlite-closed-db): Add test. (Bug#80908) Copyright-paperwork-exempt: yes --- test/src/sqlite-tests.el | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/src/sqlite-tests.el b/test/src/sqlite-tests.el index a9fab7da681..bcd80124dfd 100644 --- a/test/src/sqlite-tests.el +++ b/test/src/sqlite-tests.el @@ -185,6 +185,14 @@ (sqlite-close db) (should-error (sqlite-select db "select * from test6")))) +(ert-deftest sqlite-closed-db () + "Verify that `sqlite-close' on a closed database is a no-op." + (skip-unless (sqlite-available-p)) + (let (db) + (setq db (sqlite-open)) + (should (eq (sqlite-close db) + (sqlite-close db))))) + (ert-deftest sqlite-load-extension () (skip-unless (sqlite-available-p)) (skip-unless (fboundp 'sqlite-load-extension)) From 939e5956d98e8fa5aae974a5bb17d9cf0488f06d Mon Sep 17 00:00:00 2001 From: Eshel Yaron Date: Sat, 2 May 2026 13:28:05 +0200 Subject: [PATCH 29/50] Demote 'completion-preview-is-calling' See discussion at https://yhetil.org/emacs/87pl3egzww.fsf@mail.eshelyaron.com/ * lisp/completion-preview.el (completion-preview-is-calling): Rename to... (completion-preview--is-calling): ...this. (completion-preview--capf-wrapper): Update accordingly. * etc/NEWS: Remove announcement. --- etc/NEWS | 5 ----- lisp/completion-preview.el | 12 +++--------- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/etc/NEWS b/etc/NEWS index f195e0a9b51..dd9db73285c 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -4488,11 +4488,6 @@ 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. ---- -** New variable 'completion-preview-is-calling'. -Completion functions (in 'completion-at-point-functions') can check this -variable to tell if they are being called by Completion Preview mode. - ** Special Events +++ diff --git a/lisp/completion-preview.el b/lisp/completion-preview.el index 907068fdb67..9ff726f3707 100644 --- a/lisp/completion-preview.el +++ b/lisp/completion-preview.el @@ -523,19 +523,13 @@ candidates or if there are multiple matching completions and (setq sorted (cdr sorted))) (list (substring string 0 base) common suffixes)))))) -(defvar completion-preview-is-calling nil - "Non-nil while Completion Preview mode is calling a completion function. - -Completion functions (in `completion-at-point-functions') can check this -variable and adjust their behavior for completion preview. -For example, a completion function can skip calculating annotations for -candidates it produces for display in the completion preview, which does -not make use of such annotations.") +(defvar completion-preview--is-calling nil + "Non-nil while Completion Preview mode is calling a completion function.") (defun completion-preview--capf-wrapper (capf) "Translate return value of CAPF to properties for completion preview overlay." (let ((res (ignore-errors - (let ((completion-preview-is-calling t)) + (let ((completion-preview--is-calling t)) (funcall capf))))) (and (consp res) (not (functionp res)) From e420725935836298f0083c2a83e2c4fa3f0b4375 Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Sat, 2 May 2026 14:34:59 +0300 Subject: [PATCH 30/50] ; * lisp/emacs-lisp/subr-x.el (work-buffer--release): Autoload (bug#80947). --- lisp/emacs-lisp/subr-x.el | 1 + 1 file changed, 1 insertion(+) diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el index be86051ff2f..d5a39b77c2e 100644 --- a/lisp/emacs-lisp/subr-x.el +++ b/lisp/emacs-lisp/subr-x.el @@ -322,6 +322,7 @@ automatically killed, which means that in a such case buffer (generate-new-buffer " *work*" t)))) +;;;###autoload (defun work-buffer--release (buffer) "Release work BUFFER." (if (buffer-live-p buffer) From e05fab5775c96f8f88eab8d75dea40253bfb78eb Mon Sep 17 00:00:00 2001 From: Stephen Berman Date: Sat, 2 May 2026 15:11:37 +0200 Subject: [PATCH 31/50] Fix 'vc-dir-resynch-file' (bug#80803) * lisp/vc/vc-dir.el (vc-dir-resynch-file): Apply 'file-truename' instead of 'expand-file-name' to FNAME argument to prevent spurious display of symlinked files in *vc-dir* buffer. --- lisp/vc/vc-dir.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp/vc/vc-dir.el b/lisp/vc/vc-dir.el index 580539b6706..21658312a13 100644 --- a/lisp/vc/vc-dir.el +++ b/lisp/vc/vc-dir.el @@ -1305,7 +1305,7 @@ that file." (defun vc-dir-resynch-file (&optional fname) "Update the entries for FNAME in any directory buffers that list it." - (let ((file (expand-file-name (or fname buffer-file-name))) + (let ((file (file-truename (or fname buffer-file-name))) (drop '())) (save-current-buffer ;; look for a vc-dir buffer that might show this file. From 7c708160668340c6b095a29f65f168adcb95d0b7 Mon Sep 17 00:00:00 2001 From: Eshel Yaron Date: Sat, 2 May 2026 21:43:24 +0200 Subject: [PATCH 32/50] Remove 'completion-preview--is-calling' We decided to leave it out for now, see discussion at https://yhetil.org/emacs/jwvqzntzvvg.fsf-monnier+emacs@gnu.org/ This brings completion-preview.el back to how it were before commit a24ff52a79b. * lisp/completion-preview.el (completion-preview--is-calling): Delete. (completion-preview--capf-wrapper): Adapt. --- lisp/completion-preview.el | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lisp/completion-preview.el b/lisp/completion-preview.el index 9ff726f3707..ff348ebf9af 100644 --- a/lisp/completion-preview.el +++ b/lisp/completion-preview.el @@ -523,14 +523,9 @@ candidates or if there are multiple matching completions and (setq sorted (cdr sorted))) (list (substring string 0 base) common suffixes)))))) -(defvar completion-preview--is-calling nil - "Non-nil while Completion Preview mode is calling a completion function.") - (defun completion-preview--capf-wrapper (capf) "Translate return value of CAPF to properties for completion preview overlay." - (let ((res (ignore-errors - (let ((completion-preview--is-calling t)) - (funcall capf))))) + (let ((res (ignore-errors (funcall capf)))) (and (consp res) (not (functionp res)) (seq-let (beg end table &rest plist) res From d0f72ef864920a7f5029bf36c20403dae78cc099 Mon Sep 17 00:00:00 2001 From: Manuel Giraud Date: Mon, 20 Apr 2026 14:21:51 +0200 Subject: [PATCH 33/50] Gnus logo in server buffer's mode line (bug#80850) * lisp/gnus/gnus-srvr.el (gnus-server-mode): Initialize needed format specifications and call 'gnus-set-mode-line' for the server buffer. * lisp/gnus/gnus-sum.el (gnus-set-mode-line): Handle when WHERE is set to 'server. * lisp/gnus/gnus.el (gnus-updated-mode-lines): Add server. --- lisp/gnus/gnus-srvr.el | 3 +++ lisp/gnus/gnus-sum.el | 13 ++++++++----- lisp/gnus/gnus.el | 5 +++-- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/lisp/gnus/gnus-srvr.el b/lisp/gnus/gnus-srvr.el index 312862df165..b4802cb72d4 100644 --- a/lisp/gnus/gnus-srvr.el +++ b/lisp/gnus/gnus-srvr.el @@ -31,6 +31,7 @@ (require 'gnus-int) (require 'gnus-range) (require 'gnus-cloud) +(require 'gnus-sum) (autoload 'gnus-group-read-ephemeral-search-group "nnselect") @@ -250,6 +251,8 @@ The following commands are available: \\{gnus-server-mode-map}" (when (gnus-visual-p 'server-menu 'menu) (gnus-server-make-menu-bar)) + (gnus-update-format-specifications nil 'server 'server-mode) + (gnus-set-mode-line 'server) (gnus-simplify-mode-line) (gnus-set-default-directory) (setq mode-line-process nil) diff --git a/lisp/gnus/gnus-sum.el b/lisp/gnus/gnus-sum.el index 0fdb97d496c..cf635db0f02 100644 --- a/lisp/gnus/gnus-sum.el +++ b/lisp/gnus/gnus-sum.el @@ -6161,16 +6161,19 @@ If WHERE is `summary', the summary mode line format will be used." (symbol-value (intern (format "gnus-%s-mode-line-format-spec" where)))) (let (mode-string) - ;; We evaluate this in the summary buffer since these - ;; variables are buffer-local to that buffer. - (with-current-buffer gnus-summary-buffer + ;; We evaluate this in the summary or server buffer (depending on + ;; WHERE) since these variables are buffer-local to these buffers. + (with-current-buffer (if (eq where 'server) + gnus-server-buffer + gnus-summary-buffer) ;; We bind all these variables that are used in the `eval' form ;; below. (let* ((mformat (symbol-value (intern (format "gnus-%s-mode-line-format-spec" where)))) - (gnus-tmp-group-name (gnus-mode-string-quote - gnus-newsgroup-name)) + (gnus-tmp-group-name (and gnus-newsgroup-name + (gnus-mode-string-quote + gnus-newsgroup-name))) (gnus-tmp-article-number (or gnus-current-article 0)) (gnus-tmp-unread gnus-newsgroup-unreads) (gnus-tmp-unread-and-unticked (length gnus-newsgroup-unreads)) diff --git a/lisp/gnus/gnus.el b/lisp/gnus/gnus.el index b6c307f9037..bcc8b8744f8 100644 --- a/lisp/gnus/gnus.el +++ b/lisp/gnus/gnus.el @@ -1420,7 +1420,7 @@ this variable. I think." (gnus-redefine-select-method-widget) -(defcustom gnus-updated-mode-lines '(group article summary tree) +(defcustom gnus-updated-mode-lines '(group article summary tree server) "List of buffers that should update their mode lines. The list may contain the symbols `group', `article', `tree' and `summary'. If the corresponding symbol is present, Gnus will keep @@ -1430,7 +1430,8 @@ If this variable is nil, screen refresh may be quicker." :type '(set (const group) (const article) (const summary) - (const tree))) + (const tree) + (const server))) (defcustom gnus-mode-non-string-length 30 "Max length of mode-line non-string contents. From 963d2ebffbfc7ead85c24b6df119846cdfba10ac Mon Sep 17 00:00:00 2001 From: Matto Fransen Date: Sun, 3 May 2026 12:06:54 +0100 Subject: [PATCH 34/50] ; * doc/lispintro/emacs-lisp-intro.texi: Update *Backtrace* outputs. Copyright-paperwork-exempt: yes --- doc/lispintro/emacs-lisp-intro.texi | 61 ++++++++++++++++++----------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/doc/lispintro/emacs-lisp-intro.texi b/doc/lispintro/emacs-lisp-intro.texi index 85aa8107232..fc1da41af8b 100644 --- a/doc/lispintro/emacs-lisp-intro.texi +++ b/doc/lispintro/emacs-lisp-intro.texi @@ -1366,8 +1366,11 @@ following in it: ---------- Buffer: *Backtrace* ---------- Debugger entered--Lisp error: (void-function this) (this is an unquoted list) - eval((this is an unquoted list) nil) + (progn (this is an unquoted list)) + eval((progn (this is an unquoted list)) t) elisp--eval-last-sexp(nil) + #f(compiled-function () #)() + handler-bind-1(#f(compiled-function () #) (error) eval-expression--debug) eval-last-sexp(nil) funcall-interactively(eval-last-sexp nil) call-interactively(eval-last-sexp nil nil) @@ -1807,6 +1810,8 @@ Debugger entered--Lisp error: (void-function fill-column) (fill-column) eval((fill-column) nil) elisp--eval-last-sexp(nil) + #f(compiled-function () #)() + handler-bind-1(#f(compiled-function () #) (error) eval-expression--debug) eval-last-sexp(nil) funcall-interactively(eval-last-sexp nil) call-interactively(eval-last-sexp nil nil) @@ -1843,8 +1848,10 @@ says: @group ---------- Buffer: *Backtrace* ---------- Debugger entered--Lisp error: (void-variable +) - eval(+) + eval(+ nil) elisp--eval-last-sexp(nil) + #f(compiled-function () #)() + handler-bind-1(#f(compiled-function () #) (error) eval-expression--debug) eval-last-sexp(nil) funcall-interactively(eval-last-sexp nil) call-interactively(eval-last-sexp nil nil) @@ -2099,15 +2106,16 @@ You will create and enter a @file{*Backtrace*} buffer that says: @smallexample @group ---------- Buffer: *Backtrace* ---------- -Debugger entered--Lisp error: - (wrong-type-argument number-or-marker-p hello) +Debugger entered--Lisp error: (wrong-type-argument number-or-marker-p hello) +(2 hello) eval((+ 2 'hello) nil) - elisp--eval-last-sexp(t) + elisp--eval-last-sexp(nil) + #f(compiled-function () #)() + handler-bind-1(#f(compiled-function () #) (error) eval-expression--debug) eval-last-sexp(nil) - funcall-interactively(eval-print-last-sexp nil) - call-interactively(eval-print-last-sexp nil nil) - command-execute(eval-print-last-sexp) + funcall-interactively(eval-last-sexp nil) + call-interactively(eval-last-sexp nil nil) + command-execute(eval-last-sexp) ---------- Buffer: *Backtrace* ---------- @end group @end smallexample @@ -17938,13 +17946,16 @@ Debugger entered--Lisp error: (void-function 1=) (let ((total 0)) (while (> number 0) (setq total ...) (setq number ...)) total) triangle-bugged(4) + eval((triangle-bugged 4) nil) @end group @group - eval((triangle-bugged 4) nil) - eval-expression((triangle-bugged 4) nil nil 127) - funcall-interactively(eval-expression (triangle-bugged 4) nil nil 127) - call-interactively(eval-expression nil nil) - command-execute(eval-expression) + elisp--eval-last-sexp(nil) + #f(compiled-function () #)() + handler-bind-1(#f(compiled-function () #) (error) eval-expression--debug) + eval-last-sexp(nil) + funcall-interactively(eval-last-sexp nil) + call-interactively(eval-last-sexp nil nil) + command-execute(eval-last-sexp) ---------- Buffer: *Backtrace* ---------- @end group @end smallexample @@ -18042,10 +18053,13 @@ Debugger entered--entering a function: eval((triangle-bugged 5) nil) @end group @group - eval-expression((triangle-bugged 5) nil nil 127) - funcall-interactively(eval-expression (triangle-bugged 5) nil nil 127) - call-interactively(eval-expression nil nil) - command-execute(eval-expression) + elisp--eval-last-sexp(nil) + #f(compiled-function () #)() + handler-bind-1(#f(compiled-function () #) (error) eval-expression--debug) + eval-last-sexp(nil) + funcall-interactively(eval-last-sexp nil) + call-interactively(eval-last-sexp nil nil) + command-execute(eval-last-sexp) ---------- Buffer: *Backtrace* ---------- @end group @end smallexample @@ -18094,12 +18108,15 @@ Debugger entered--beginning evaluation of function call form: (setq number ...)) total) * triangle-bugged(5) eval((triangle-bugged 5) nil) -@group @end group - eval-expression((triangle-bugged 5) nil nil 127) - funcall-interactively(eval-expression (triangle-bugged 5) nil nil 127) - call-interactively(eval-expression nil nil) - command-execute(eval-expression) +@group + elisp--eval-last-sexp(nil) + #f(compiled-function () #)() + handler-bind-1(#f(compiled-function () #) (error) eval-expression--debug) + eval-last-sexp(nil) + funcall-interactively(eval-last-sexp nil) + call-interactively(eval-last-sexp nil nil) + command-execute(eval-last-sexp) ---------- Buffer: *Backtrace* ---------- @end group @end smallexample From 5beeb4446f14cd72e1a08c8da22d1b1a05cb6a04 Mon Sep 17 00:00:00 2001 From: Juri Linkov Date: Sun, 3 May 2026 18:38:43 +0300 Subject: [PATCH 35/50] * lisp/treesit.el (treesit-outline-level): Add guard condition. --- lisp/treesit.el | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lisp/treesit.el b/lisp/treesit.el index 4579d416f34..baebdbb31dc 100644 --- a/lisp/treesit.el +++ b/lisp/treesit.el @@ -4454,12 +4454,14 @@ For BOUND, MOVE, BACKWARD, LOOKING-AT, see the descriptions in (setq level (1+ level))) ;; Continue counting the host nodes. - (dolist (parser (mapcar #'cdr (treesit-parsers-at (point) nil t '(global local)))) - (let* ((node (treesit-node-at (point) parser)) - (lang (treesit-parser-language parser)) - (pred (alist-get lang treesit-aggregated-outline-predicate))) - (while (setq node (treesit-parent-until node pred)) - (setq level (1+ level))))) + (when treesit-aggregated-outline-predicate + (dolist (parser (mapcar #'cdr (treesit-parsers-at + (point) nil t '(global local)))) + (let* ((node (treesit-node-at (point) parser)) + (lang (treesit-parser-language parser)) + (pred (alist-get lang treesit-aggregated-outline-predicate))) + (while (setq node (treesit-parent-until node pred)) + (setq level (1+ level)))))) level)) From a1739bc75e1b12b458e338ed9a22aa3adbbea384 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vincent=20Bela=C3=AFche?= Date: Tue, 2 Jan 2024 00:28:55 +0100 Subject: [PATCH 36/50] ; * doc/translations/fr/misc/ses-fr.texi: Typo. --- doc/translations/fr/misc/ses-fr.texi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/translations/fr/misc/ses-fr.texi b/doc/translations/fr/misc/ses-fr.texi index ead9ed33609..10bb20420a9 100644 --- a/doc/translations/fr/misc/ses-fr.texi +++ b/doc/translations/fr/misc/ses-fr.texi @@ -58,7 +58,7 @@ modify this GNU manual.'' @top @acronym{SES}: Simple Emacs Spreadsheet @display -@acronym{SES} est mode majeur de GNU Emacs pour éditer des fichiers +@acronym{SES} est un mode majeur de GNU Emacs pour éditer des fichiers tableur, c.-à-d.@: des fichiers contenant une grille rectangulaire de cellules. Les valeurs des cellules sont spécifiées par des formules pouvant se référer aux valeurs d’autres cellules. @@ -70,7 +70,7 @@ Pour les rapports d’anomalie, utiliser @kbd{M-x report-emacs-bug}. @insertcopying @menu -* Boniment: Sales Pitch. Pourquoi utiliser @acronym{SES}? +* Boniment: Sales Pitch. Pourquoi utiliser @acronym{SES} ? * Tuto: Quick Tutorial. Une introduction sommaire * Les bases: The Basics. Les commandes de base du tableur * Fonctions avancées: Advanced Features. Vous voulez en savoir plus ? From e682959b6b05fc9b174b2ded11fe38d38e5e8463 Mon Sep 17 00:00:00 2001 From: Stefan Monnier Date: Sun, 3 May 2026 13:32:51 -0400 Subject: [PATCH 37/50] (package--builtin-alist): Don't use `defconst` since we later change it * lisp/emacs-lisp/package.el (package--builtin-alist): Use `defvar`. --- lisp/emacs-lisp/package.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp/emacs-lisp/package.el b/lisp/emacs-lisp/package.el index e1486418876..73d9357a839 100644 --- a/lisp/emacs-lisp/package.el +++ b/lisp/emacs-lisp/package.el @@ -468,7 +468,7 @@ BI-DESC should be a `package--bi-desc' object." :summary (package--bi-desc-summary bi-desc) :dir 'builtin)) -(defconst package--builtin-alist nil) +(defvar package--builtin-alist nil) (defun package--builtin-alist () "Return a alist of built-in packages in the form of `package-alist'. The alist doesn't include the pseudo-package for Emacs." From fe58f45782cb2e61af8ac6f870494f659a6b6ea0 Mon Sep 17 00:00:00 2001 From: Philip Kaludercic Date: Sun, 3 May 2026 17:45:15 +0200 Subject: [PATCH 38/50] Allow changing SGML "quick keys" after loading sgml-mode.el * lisp/textmodes/sgml-mode.el (sgml-quick-keys): Replace with user option, that adjusts 'sgml-mode-map'. (sgml-mode-map): Don't insert quick keys. (html-quick-keys): Replace with user option, that adjusts 'html-mode-map'. (html-mode-map): Don't insert quick keys. --- lisp/textmodes/sgml-mode.el | 171 ++++++++++++++++++++---------------- 1 file changed, 94 insertions(+), 77 deletions(-) diff --git a/lisp/textmodes/sgml-mode.el b/lisp/textmodes/sgml-mode.el index c95386c16ac..854da2c8535 100644 --- a/lisp/textmodes/sgml-mode.el +++ b/lisp/textmodes/sgml-mode.el @@ -114,49 +114,57 @@ Including ?- makes double dashes into comment delimiters, but they are really only supposed to delimit comments within DTD definitions. So we normally turn it off.") -(defvar sgml-quick-keys nil - "Use <, >, &, /, SPC and `sgml-specials' keys \"electrically\" when non-nil. -This takes effect when first loading the `sgml-mode' library.") - (defvar sgml-mode-map (let ((map (make-keymap))) ;`sparse' doesn't allow binding to charsets. - (define-key map "\C-c\C-i" #'sgml-tags-invisible) + (define-key map (kbd "C-c C-i") #'sgml-tags-invisible) (define-key map "/" #'sgml-slash) - (define-key map "\C-c\C-n" #'sgml-name-char) - (define-key map "\C-c\C-t" #'sgml-tag) - (define-key map "\C-c\C-a" #'sgml-attributes) - (define-key map "\C-c\C-b" #'sgml-skip-tag-backward) - (define-key map [?\C-c left] #'sgml-skip-tag-backward) - (define-key map "\C-c\C-f" #'sgml-skip-tag-forward) - (define-key map [?\C-c right] #'sgml-skip-tag-forward) - (define-key map "\C-c\C-d" #'sgml-delete-tag) - (define-key map "\C-c\^?" #'sgml-delete-tag) - (define-key map "\C-c?" #'sgml-tag-help) - (define-key map "\C-c]" #'sgml-close-tag) - (define-key map "\C-c/" #'sgml-close-tag) + (define-key map (kbd "C-c C-n") #'sgml-name-char) + (define-key map (kbd "C-c C-t") #'sgml-tag) + (define-key map (kbd "C-c C-a") #'sgml-attributes) + (define-key map (kbd "C-c C-b") #'sgml-skip-tag-backward) + (define-key map (kbd "C-c ") #'sgml-skip-tag-backward) + (define-key map (kbd "C-c C-f") #'sgml-skip-tag-forward) + (define-key map (kbd "C-c ") #'sgml-skip-tag-forward) + (define-key map (kbd "C-c C-d") #'sgml-delete-tag) + (define-key map (kbd "C-c ^ ?") #'sgml-delete-tag) + (define-key map (kbd "C-c ?") #'sgml-tag-help) + (define-key map (kbd "C-c ]") #'sgml-close-tag) + (define-key map (kbd "C-c /") #'sgml-close-tag) ;; Redundant keybindings, for consistency with TeX mode. - (define-key map "\C-c\C-o" #'sgml-tag) - (define-key map "\C-c\C-e" #'sgml-close-tag) + (define-key map (kbd "C-c C-o") #'sgml-tag) + (define-key map (kbd "C-c C-e") #'sgml-close-tag) - (define-key map "\C-c8" #'sgml-name-8bit-mode) - (define-key map "\C-c\C-v" #'sgml-validate) - (when sgml-quick-keys - (define-key map "&" #'sgml-name-char) - (define-key map "<" #'sgml-tag) - (define-key map " " #'sgml-auto-attributes) - (define-key map ">" #'sgml-maybe-end-tag) - (when (memq ?\" sgml-specials) - (define-key map "\"" #'sgml-name-self)) - (when (memq ?' sgml-specials) - (define-key map "'" #'sgml-name-self))) - (let ((c 127) - (map (nth 1 map))) - (while (< (setq c (1+ c)) 256) - (aset map c #'sgml-maybe-name-self))) + (define-key map (kbd "C-c 8") #'sgml-name-8bit-mode) + (define-key map (kbd "C-c C-v") #'sgml-validate) + + (cl-loop for c from 128 upto 255 + do (define-key map (string c) #'sgml-maybe-name-self)) map) "Keymap for SGML mode. See also `sgml-specials'.") +(defcustom sgml-quick-keys nil + "Use <, >, &, /, SPC and `sgml-specials' keys \"electrically\" when non-nil. +By setting the option to `indent', Emacs will eagerly reindent the +current line when you manually close a tag." + :set (lambda (sym val) + (set-default sym val) + (dolist (bind `(("&" . ,#'sgml-name-char) + ("<" . ,#'sgml-tag) + ("\s" . ,#'sgml-auto-attributes) + (">" . ,#'sgml-maybe-end-tag) + ("\"" . ,(and (memq ?\" sgml-specials) #'sgml-name-self)) + ("'" . ,(and (memq ?' sgml-specials) #'sgml-name-self)))) + (if (and val (cdr bind)) + (define-key sgml-mode-map (car bind) (cdr bind)) + (define-key sgml-mode-map (car bind) nil t))) + (custom-reevaluate-setting 'html-quick-keys)) + :type '(choice (const :tag "Enabled" t) + (const :tag "Enabled, and indent when closing tags" indent) + ;; Omit `close' because `electric-pair-mode' already + ;; takes care of paring "<" and ">". + (const :tag "Disabled" nil))) + (easy-menu-define sgml-mode-menu sgml-mode-map "Menu for SGML mode." '("SGML" @@ -1792,54 +1800,63 @@ Currently just returns (EMPTY-TAGS UNCLOSED-TAGS)." :type 'hook :options '(html-autoview-mode)) -(defvar html-quick-keys sgml-quick-keys - "Use C-c X combinations for quick insertion of frequent tags when non-nil. -This defaults to `sgml-quick-keys'. -This takes effect when first loading the library.") - (defvar html-mode-map (let ((map (make-sparse-keymap))) - (set-keymap-parent map sgml-mode-map) - (define-key map "\C-c6" #'html-headline-6) - (define-key map "\C-c5" #'html-headline-5) - (define-key map "\C-c4" #'html-headline-4) - (define-key map "\C-c3" #'html-headline-3) - (define-key map "\C-c2" #'html-headline-2) - (define-key map "\C-c1" #'html-headline-1) - (define-key map "\C-c\r" #'html-paragraph) - (define-key map "\C-c\n" #'html-line) - (define-key map "\C-c\C-c-" #'html-horizontal-rule) - (define-key map "\C-c\C-co" #'html-ordered-list) - (define-key map "\C-c\C-cu" #'html-unordered-list) - (define-key map "\C-c\C-cr" #'html-radio-buttons) - (define-key map "\C-c\C-cc" #'html-checkboxes) - (define-key map "\C-c\C-cl" #'html-list-item) - (define-key map "\C-c\C-ch" #'html-href-anchor) - (define-key map "\C-c\C-cf" #'html-href-anchor-file) - (define-key map "\C-c\C-cn" #'html-name-anchor) - (define-key map "\C-c\C-c#" #'html-id-anchor) - (define-key map "\C-c\C-ci" #'html-image) - (when html-quick-keys - (define-key map "\C-cp" #'html-paragraph) - (define-key map "\C-c-" #'html-horizontal-rule) - (define-key map "\C-cd" #'html-div) - (define-key map "\C-co" #'html-ordered-list) - (define-key map "\C-cu" #'html-unordered-list) - (define-key map "\C-cr" #'html-radio-buttons) - (define-key map "\C-cc" #'html-checkboxes) - (define-key map "\C-cl" #'html-list-item) - (define-key map "\C-ch" #'html-href-anchor) - (define-key map "\C-cf" #'html-href-anchor-file) - (define-key map "\C-cn" #'html-name-anchor) - (define-key map "\C-c#" #'html-id-anchor) - (define-key map "\C-ci" #'html-image) - (define-key map "\C-cs" #'html-span)) - (define-key map "\C-c\C-s" #'html-autoview-mode) - (define-key map "\C-c\C-v" #'browse-url-of-buffer) - (define-key map "\M-o" 'facemenu-keymap) + (set-keymap-parent map sgml-mode-map) + (define-key map (kbd "C-c 6") #'html-headline-6) + (define-key map (kbd "C-c 5") #'html-headline-5) + (define-key map (kbd "C-c 4") #'html-headline-4) + (define-key map (kbd "C-c 3") #'html-headline-3) + (define-key map (kbd "C-c 2") #'html-headline-2) + (define-key map (kbd "C-c 1") #'html-headline-1) + (define-key map (kbd "C-c C-m") #'html-paragraph) + (define-key map (kbd "C-c C-j") #'html-line) + (define-key map (kbd "C-c C-c -") #'html-horizontal-rule) + (define-key map (kbd "C-c C-c o") #'html-ordered-list) + (define-key map (kbd "C-c C-c u") #'html-unordered-list) + (define-key map (kbd "C-c C-c r") #'html-radio-buttons) + (define-key map (kbd "C-c C-c c") #'html-checkboxes) + (define-key map (kbd "C-c C-c l") #'html-list-item) + (define-key map (kbd "C-c C-c h") #'html-href-anchor) + (define-key map (kbd "C-c C-c f") #'html-href-anchor-file) + (define-key map (kbd "C-c C-c n") #'html-name-anchor) + (define-key map (kbd "C-c C-c #") #'html-id-anchor) + (define-key map (kbd "C-c C-c i") #'html-image) + (define-key map (kbd "C-c C-s") #'html-autoview-mode) + (define-key map (kbd "C-c C-v") #'browse-url-of-buffer) + (define-key map (kbd "M-o") 'facemenu-keymap) map) "Keymap for commands for use in HTML mode.") +(defcustom html-quick-keys sgml-quick-keys + "Use C-c X combinations for quick insertion of frequent tags when non-nil. +This defaults to `sgml-quick-keys', which see." + :set (lambda (sym val) + (set-default sym val) + (dolist (bind `((,(kbd "C-c p") . ,#'html-paragraph) + (,(kbd "C-c -") . ,#'html-horizontal-rule) + (,(kbd "C-c d") . ,#'html-div) + (,(kbd "C-c o") . ,#'html-ordered-list) + (,(kbd "C-c u") . ,#'html-unordered-list) + (,(kbd "C-c r") . ,#'html-radio-buttons) + (,(kbd "C-c c") . ,#'html-checkboxes) + (,(kbd "C-c l") . ,#'html-list-item) + (,(kbd "C-c h") . ,#'html-href-anchor) + (,(kbd "C-c f") . ,#'html-href-anchor-file) + (,(kbd "C-c n") . ,#'html-name-anchor) + (,(kbd "C-c #") . ,#'html-id-anchor) + (,(kbd "C-c i") . ,#'html-image) + (,(kbd "C-c s") . ,#'html-span))) + (if val + (define-key html-mode-map (car bind) (cdr bind)) + (define-key html-mode-map (car bind) nil t)))) + :set-after '(sgml-quick-keys) + :type '(choice (const :tag "Enabled" t) + (const :tag "Enabled, and indent when closing tags" indent) + ;; Omit `close' because `electric-pair-mode' already + ;; takes care of paring "<" and ">". + (const :tag "Disabled" nil))) + (easy-menu-define html-mode-menu html-mode-map "Menu for HTML mode." '("HTML" From 7a710c3c0e856630814a97fe3555c0e79c6d0e83 Mon Sep 17 00:00:00 2001 From: Philip Kaludercic Date: Sun, 3 May 2026 17:46:23 +0200 Subject: [PATCH 39/50] Allow setting 'sgml-xml-mode' buffer locally * lisp/textmodes/sgml-mode.el (sgml-xml-mode): Add :safe property. --- lisp/textmodes/sgml-mode.el | 1 + 1 file changed, 1 insertion(+) diff --git a/lisp/textmodes/sgml-mode.el b/lisp/textmodes/sgml-mode.el index 854da2c8535..e736342007b 100644 --- a/lisp/textmodes/sgml-mode.el +++ b/lisp/textmodes/sgml-mode.el @@ -73,6 +73,7 @@ When 2, attribute indentation looks like this: It is set to be buffer-local when the file has a DOCTYPE or an XML declaration." :type 'boolean + :safe #'booleanp :version "22.1") (define-obsolete-variable-alias 'sgml-transformation From dbc2e073c0554e46d1bd2cb2514037ad1aa76e1f Mon Sep 17 00:00:00 2001 From: Philip Kaludercic Date: Sun, 3 May 2026 17:47:11 +0200 Subject: [PATCH 40/50] Refine SGML offset user option types * lisp/textmodes/sgml-mode.el (sgml-basic-offset) (sgml-attribute-offset): Allow natural numbers, not arbitrary integers. --- lisp/textmodes/sgml-mode.el | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lisp/textmodes/sgml-mode.el b/lisp/textmodes/sgml-mode.el index e736342007b..39e785e66a2 100644 --- a/lisp/textmodes/sgml-mode.el +++ b/lisp/textmodes/sgml-mode.el @@ -47,8 +47,8 @@ (defcustom sgml-basic-offset 2 "Specifies the basic indentation level for `sgml-indent-line'." - :type 'integer - :safe #'integerp) + :type 'natnum + :safe #'natnump) (defcustom sgml-attribute-offset 0 "Specifies a delta for attribute indentation in `sgml-indent-line'. @@ -65,8 +65,8 @@ When 2, attribute indentation looks like this: attribute=\"value\"> " :version "25.1" - :type 'integer - :safe #'integerp) + :type 'natnum + :safe #'natnump) (defcustom sgml-xml-mode nil "When non-nil, tag insertion functions will be XML-compliant. From c704ae0ffc5ac39eced99968ff3cff2fadd3d3bb Mon Sep 17 00:00:00 2001 From: Philip Kaludercic Date: Sun, 3 May 2026 17:48:07 +0200 Subject: [PATCH 41/50] Implement 'sgml-name-8bit-mode' as a proper minor mode * lisp/textmodes/sgml-mode.el (sgml-name-8bit-mode): Merge 'defcustom' and 'defun' into a 'define-minor-mode'. --- lisp/textmodes/sgml-mode.el | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/lisp/textmodes/sgml-mode.el b/lisp/textmodes/sgml-mode.el index 39e785e66a2..7221bc2f019 100644 --- a/lisp/textmodes/sgml-mode.el +++ b/lisp/textmodes/sgml-mode.el @@ -220,7 +220,7 @@ current line when you manually close a tag." table) "Syntax table used to parse SGML tags.") -(defcustom sgml-name-8bit-mode nil +(define-minor-mode sgml-name-8bit-mode "When non-nil, insert non-ASCII characters as named entities." :type 'boolean) @@ -783,14 +783,6 @@ Uses `sgml-char-names'." (sgml-name-char last-command-event) (self-insert-command 1))) -(defun sgml-name-8bit-mode () - "Toggle whether to insert named entities instead of non-ASCII characters. -This only works for Latin-1 input." - (interactive) - (setq sgml-name-8bit-mode (not sgml-name-8bit-mode)) - (message "sgml name entity mode is now %s" - (if sgml-name-8bit-mode "ON" "OFF"))) - ;; When an element of a skeleton is a string "str", it is passed ;; through `skeleton-transformation-function' and inserted. ;; If "str" is to be inserted literally, one should obtain it as From 77e968b97c074ae61c98170ac086f8d8e7f97ae7 Mon Sep 17 00:00:00 2001 From: Philip Kaludercic Date: Sun, 3 May 2026 17:51:15 +0200 Subject: [PATCH 42/50] Raise an error if 'sgml-validate-command' is not configured * lisp/textmodes/sgml-mode.el (sgml-validate-command): If no known program is installed, fall back to nil as the default value instead of a warning message. --- lisp/textmodes/sgml-mode.el | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lisp/textmodes/sgml-mode.el b/lisp/textmodes/sgml-mode.el index 7221bc2f019..b85ba5c26d4 100644 --- a/lisp/textmodes/sgml-mode.el +++ b/lisp/textmodes/sgml-mode.el @@ -286,12 +286,11 @@ Currently, only Latin-1 characters are supported.") ((executable-find "onsgmls") ;; onsgmls is the community version of `nsgmls' ;; hosted on https://openjade.sourceforge.net/ - "onsgmls -s") - (t "Install (o)nsgmls, tidy, or some other SGML validator, and set `sgml-validate-command'")) + "onsgmls -s")) "The command to validate an SGML document. The file name of current buffer file name will be appended to this, separated by a space." - :type 'string + :type '(choice (const :tag "Unset" nil) string) :version "21.1") (defvar sgml-saved-validate-command nil From 29f2c6eee030cd060fcd4bca8a3eb0827bc68a78 Mon Sep 17 00:00:00 2001 From: Philip Kaludercic Date: Sun, 3 May 2026 17:51:52 +0200 Subject: [PATCH 43/50] Use 'read-shell-command' to read SGML validation command * lisp/textmodes/sgml-mode.el (sgml-validate): Update interactive spec. --- lisp/textmodes/sgml-mode.el | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lisp/textmodes/sgml-mode.el b/lisp/textmodes/sgml-mode.el index b85ba5c26d4..aaf05e4df1a 100644 --- a/lisp/textmodes/sgml-mode.el +++ b/lisp/textmodes/sgml-mode.el @@ -1199,13 +1199,14 @@ with output going to the buffer `*compilation*'. You can then use the command \\[next-error] to find the next error message and move to the line in the SGML document that caused it." (interactive - (list (read-string "Validate command: " - (or sgml-saved-validate-command - (concat sgml-validate-command - " " - (when-let* ((name (buffer-file-name))) - (shell-quote-argument - (file-name-nondirectory name)))))))) + (list (read-shell-command "Validate command: " + (or sgml-saved-validate-command + sgml-validate-command + (concat sgml-validate-command + " " + (when-let* ((name (buffer-file-name))) + (shell-quote-argument + (file-name-nondirectory name)))))))) (setq sgml-saved-validate-command command) (save-some-buffers (not compilation-ask-about-save) nil) (compilation-start command)) From 9f6cf73b8e69a142a6ebeb4ed2258162007b1b3e Mon Sep 17 00:00:00 2001 From: Philip Kaludercic Date: Sun, 3 May 2026 17:58:20 +0200 Subject: [PATCH 44/50] Prevent indentation within whitespace sensitive HTML tags * lisp/textmodes/sgml-mode.el (sgml-whitespace-sensitive-tags): Add new variable. (sgml-calculate-indent): Check if in the context of a tag specified by 'sgml-whitespace-sensitive-tags'. (html-mode): Set 'sgml-whitespace-sensitive-tags' to not adjust the indentation within
 and