From 81629b2b2ba282136bc5055bbaa0302321306e61 Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Sun, 4 May 2025 09:47:49 +0300 Subject: [PATCH 001/102] ; * lisp/gnus/mail-source.el (mail-sources): Fix a typo (bug#78235). --- lisp/gnus/mail-source.el | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lisp/gnus/mail-source.el b/lisp/gnus/mail-source.el index daba2f3fd20..554db48800d 100644 --- a/lisp/gnus/mail-source.el +++ b/lisp/gnus/mail-source.el @@ -201,8 +201,8 @@ Leave mails for this many days" :value 14))))) (string :tag "Program")) (group :inline t (const :format "" - :value :authenticator) - (choice :tag "Authenticator" + :value :authentication) + (choice :tag "Authentication" :value login ,@mail-source-imap-authenticators)) (group :inline t From b172a1478c16067d88b3a79eed857f265cafb1b7 Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Mon, 5 May 2025 14:51:56 +0300 Subject: [PATCH 002/102] ; * doc/lispref/tips.texi (Library Headers): Fix wording (bug#78253). --- doc/lispref/tips.texi | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/lispref/tips.texi b/doc/lispref/tips.texi index f0238276501..3088decc1b2 100644 --- a/doc/lispref/tips.texi +++ b/doc/lispref/tips.texi @@ -1213,10 +1213,10 @@ lines. Here is a table of them: @table @samp @item ;;; Commentary: This begins introductory comments that explain how the library works. -It should come right after the copying permissions, terminated by a -@samp{Change Log}, @samp{History} or @samp{Code} comment line. This -text is used by the Finder package, so it should make sense in that -context. +It should come right after the copying permissions, and is terminated by +one of the comment lines described below: @samp{Change Log}, +@samp{History} or @samp{Code}. This text is used by the Finder package, +so it should make sense in that context. @item ;;; Change Log: This begins an optional log of changes to the file over time. Don't From ed7b55f6bf1b8d0ceb40c9e196d8b31fd6afc21e Mon Sep 17 00:00:00 2001 From: Michael Albinus Date: Tue, 6 May 2025 12:22:45 +0200 Subject: [PATCH 003/102] Adapt Tramp tests * test/lisp/net/tramp-tests.el (tramp-test29-start-file-process) (tramp-test30-make-process): Adapt tests. --- test/lisp/net/tramp-tests.el | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/lisp/net/tramp-tests.el b/test/lisp/net/tramp-tests.el index e22f1afc18b..2c95cf97ff4 100644 --- a/test/lisp/net/tramp-tests.el +++ b/test/lisp/net/tramp-tests.el @@ -5401,6 +5401,12 @@ This tests also `make-symbolic-link', `file-truename' and `add-name-to-file'." (with-timeout (10 (tramp--test-timeout-handler)) (while (< (- (point-max) (point-min)) (length "foo")) (while (accept-process-output proc 0 nil t)))) + ;; Some `cat' implementations do not support the `cat -' + ;; call. We skip then. + (skip-unless + (not + (string-match-p (rx "cat: -: input file is output file\n") + (buffer-string)))) (should (string-match-p "foo" (buffer-string)))) ;; Cleanup. @@ -5595,6 +5601,12 @@ If UNSTABLE is non-nil, the test is tagged as `:unstable'." (with-timeout (10 (tramp--test-timeout-handler)) (while (< (- (point-max) (point-min)) (length "foo")) (while (accept-process-output proc 0 nil t)))) + ;; Some `cat' implementations do not support the `cat -' + ;; call. We skip then. + (skip-unless + (not + (string-match-p (rx "cat: -: input file is output file\n") + (buffer-string)))) (should (string-match-p "foo" (buffer-string)))) ;; Cleanup. From 0b4eb525b69822c6b503982ef49980c72c8d0232 Mon Sep 17 00:00:00 2001 From: Yuan Fu Date: Tue, 6 May 2025 20:57:06 -0700 Subject: [PATCH 004/102] Make treesit--simple-indent-eval more permissive (bug#78065) * lisp/treesit.el (treesit--simple-indent-eval): Allow EXP to be anything, so higher-order indent presets can take anything as an argument: t, nil, symbols, keywords, etc. --- lisp/treesit.el | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lisp/treesit.el b/lisp/treesit.el index a26625eca57..2b5989fcaee 100644 --- a/lisp/treesit.el +++ b/lisp/treesit.el @@ -1902,14 +1902,12 @@ the function." ;; `functionp'. ((alist-get exp treesit-simple-indent-presets)) ((functionp exp) exp) - ((symbolp exp) - (if (null exp) - exp - ;; Matchers only return lambdas, anchors only return - ;; integer, so we should never see a variable. - (signal 'treesit-indent-error - (list "Couldn't find the preset corresponding to expression" - exp)))) + ;; There are higher-order presets that take arguments, like + ;; (nth-sibling 1 t), so it's possible for exp to be something + ;; other than numbers and functions. Don't signal an error if + ;; exp isn't a function nor a number. In fact, allow exp to be + ;; any symbol or keyword, so users can define higher-order + ;; presets that takes keyword or symbol as arguments. (t exp))) ;; This variable might seem unnecessary: why split From c522428b33c8a34b7309b6166f35255bc4f2447c Mon Sep 17 00:00:00 2001 From: Stefan Monnier Date: Thu, 8 May 2025 10:51:16 -0400 Subject: [PATCH 005/102] lisp/bs.el (bs--goto-current-buffer): Fix thinko in last commit --- lisp/bs.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp/bs.el b/lisp/bs.el index ac4da0b5c05..0a799b6d514 100644 --- a/lisp/bs.el +++ b/lisp/bs.el @@ -572,7 +572,7 @@ SORT-DESCRIPTION is an element of `bs-sort-functions'." "Go to line which represents the current buffer. Actually, it goes to the line which begins with the character in `bs-string-current' or `bs-string-current-marked'." - (let ((regexp (concat "\\`" + (let ((regexp (concat "^" (regexp-opt (list bs-string-current bs-string-current-marked)))) point) From ceba490da921399393200e704520d313eb1ac5c8 Mon Sep 17 00:00:00 2001 From: Stefan Monnier Date: Thu, 8 May 2025 17:11:05 -0400 Subject: [PATCH 006/102] cl-types: Improve error messages * lisp/emacs-lisp/cl-extra.el (cl--derived-type-generalizers): Check that the type is valid and fully defined. * lisp/emacs-lisp/cl-lib.el (cl-generic-generalizers) : Don't delegate to another method just because the type is invalid. * lisp/emacs-lisp/cl-preloaded.el (cl--define-derived-type): Minor simplification, and improvement to an error message. --- lisp/emacs-lisp/cl-extra.el | 9 ++++++++ lisp/emacs-lisp/cl-lib.el | 5 +---- lisp/emacs-lisp/cl-preloaded.el | 39 ++++++++++++--------------------- 3 files changed, 24 insertions(+), 29 deletions(-) diff --git a/lisp/emacs-lisp/cl-extra.el b/lisp/emacs-lisp/cl-extra.el index 60e0eb07677..1fe0411062f 100644 --- a/lisp/emacs-lisp/cl-extra.el +++ b/lisp/emacs-lisp/cl-extra.el @@ -1082,6 +1082,15 @@ TYPES is an internal argument." ;;;###autoload (defun cl--derived-type-generalizers (type) + ;; Make sure this derived type can be used without arguments. + (let ((expander (or (get type 'cl-deftype-handler) + (error "Type %S lacks cl-deftype-handler" type)))) + ;; Check that the type can be used without arguments. + (funcall expander) + ;; Check that we have a precomputed predicate since that's what + ;; `cl-types-of' uses. + (unless (get type 'cl-deftype-satisfies) + (error "Type %S lacks cl-deftype-satisfies" type))) ;; Add a new dispatch type to the dispatch list, then ;; synchronize with `cl--derived-type-list' so that both lists follow ;; the same type precedence order. diff --git a/lisp/emacs-lisp/cl-lib.el b/lisp/emacs-lisp/cl-lib.el index b64b9c17e12..f1b3b8fdbcc 100644 --- a/lisp/emacs-lisp/cl-lib.el +++ b/lisp/emacs-lisp/cl-lib.el @@ -568,10 +568,7 @@ If ALIST is non-nil, the new pairs are prepended to it." (declare-function cl--derived-type-generalizers "cl-extra" (type)) (cl-defmethod cl-generic-generalizers :extra "derived-types" (type) "Support for dispatch on derived types, i.e. defined with `cl-deftype'." - (if (and (symbolp type) (cl-derived-type-class-p (cl--find-class type)) - ;; Make sure this derived type can be used without arguments. - (let ((expander (get type 'cl-deftype-handler))) - (and expander (with-demoted-errors "%S" (funcall expander))))) + (if (and (symbolp type) (cl-derived-type-class-p (cl--find-class type))) (cl--derived-type-generalizers type) (cl-call-next-method)))) diff --git a/lisp/emacs-lisp/cl-preloaded.el b/lisp/emacs-lisp/cl-preloaded.el index e4b467ceb24..d6962ba1dee 100644 --- a/lisp/emacs-lisp/cl-preloaded.el +++ b/lisp/emacs-lisp/cl-preloaded.el @@ -505,37 +505,26 @@ PARENTS is a list of types NAME is a subtype of, or nil." ;; "complement" another declaration of the same type, ;; so maybe we should turn this into a warning (and ;; not overwrite the `cl--find-class' in that case)? - (error "Type in another class: %S" (type-of class)))) + (error "Type %S already in another class: %S" name (type-of class)))) ;; Setup a type descriptor for NAME. (setf (cl--find-class name) (cl--derived-type-class-make name (function-documentation expander) parents)) (define-symbol-prop name 'cl-deftype-handler expander) (when predicate - (define-symbol-prop name 'cl-deftype-satisfies predicate)) - ;; Record new type. The constructor of the class - ;; `cl-type-class' already ensures that parent types must be - ;; defined before their "child" types (i.e. already added to - ;; the `cl--derived-type-list' for types defined with `cl-deftype'). - ;; So it is enough to simply push a new type at the beginning - ;; of the list. - ;; Redefinition is more complicated, because child types may - ;; be in the list, so moving the type to the head can be - ;; incorrect. The "cheap" solution is to leave the list - ;; unchanged (and hope the redefinition doesn't change the - ;; hierarchy too much). - ;; Side note: Redefinitions introduce other problems as well - ;; because the class object's `parents` slot contains - ;; references to `cl--class` objects, so after a redefinition - ;; via (setf (cl--find-class FOO) ...), the children's - ;; `parents` slots point to the old class object. That's a - ;; problem that affects all types and that we don't really try - ;; to solve currently. - (or (memq name cl--derived-type-list) - ;; Exclude types that can't be used without arguments. - ;; They'd signal errors in `cl-types-of'! - (not predicate) - (push name cl--derived-type-list)))) + (define-symbol-prop name 'cl-deftype-satisfies predicate) + ;; If the type can be used without arguments, record it for + ;; use by `cl-types-of'. + ;; The order in `cl--derived-type-list' is important, but the + ;; constructor of the class `cl-type-class' already ensures that + ;; parent types must be defined before their "child" types + ;; (i.e. already added to the `cl--derived-type-list' for types + ;; defined with `cl-deftype'). So it is enough to simply push + ;; a new type at the beginning of the list. + ;; Redefinition is a can of worms anyway, so we don't try to be clever + ;; in that case. + (or (memq name cl--derived-type-list) + (push name cl--derived-type-list))))) ;; Make sure functions defined with cl-defsubst can be inlined even in ;; packages which do not require CL. We don't put an autoload cookie From a7dffc2ea38583db39eaec5f21fd0d8fa5571ab4 Mon Sep 17 00:00:00 2001 From: Stephen Gildea Date: Thu, 8 May 2025 14:48:22 -0700 Subject: [PATCH 007/102] Document 'time-stamp-time-zone' in Emacs Manual * doc/emacs/files.texi (Time Stamp Customization): Document time-stamp-time-zone. --- doc/emacs/files.texi | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/emacs/files.texi b/doc/emacs/files.texi index 517e2a1fdd0..d7a8822771c 100644 --- a/doc/emacs/files.texi +++ b/doc/emacs/files.texi @@ -1107,13 +1107,15 @@ End: --> @end example -@vindex time-stamp-format By default the time stamp is formatted according to your locale setting (@pxref{Environment}) and time zone (@pxref{Time of Day,,, elisp, The Emacs Lisp Reference Manual}). +@vindex time-stamp-time-zone +Set @code{time-stamp-time-zone} to override the time zone used. +@vindex time-stamp-format See the built-in documentation for the variable @code{time-stamp-format} -for specifics and other variables that affect the formatting. +for specifics on formatting and other variables that affect it. @node Time Stamps for One File @subsubsection Forcing Time Stamps for One File From 64eb60bd91dbeed458d5a410e6dc9ab4e9e0a10e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20T=C3=A1vora?= Date: Thu, 8 May 2025 12:15:24 +0100 Subject: [PATCH 008/102] Flymake: tweak 'fancy' eol diagnostic display When calculating the face for boxdraw chars, protect against diagnostics with an empty first line. Also don't inherit from :default, which seems to mess up the background color when using hl-line-mode. * lisp/progmodes/flymake.el (flymake--eol-draw-fancy): Tweak --- lisp/progmodes/flymake.el | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lisp/progmodes/flymake.el b/lisp/progmodes/flymake.el index 2d30ec3f5bd..8a072b94a17 100644 --- a/lisp/progmodes/flymake.el +++ b/lisp/progmodes/flymake.el @@ -2376,11 +2376,11 @@ some of this variable's contents the diagnostic listings.") for height-to-clear = 0 then ret for i from 0 for adjust = (* i 2) - for face = `(:inherit default - :foreground - ,(face-attribute - (get-text-property 0 'face text) - :foreground nil t)) + for face = `(:foreground + ,(face-attribute + (or (get-text-property 0 'face text) + 'flymake-error) + :foreground nil t)) for text-beg-col = (max (- (max 30 (+ line-beg-col 5)) adjust) (+ line-beg-col 1)) for text-end-col = (max 100 (+ text-beg-col 40)) for ret = (flymake--eol-draw-fancy-1 From 7617c7a6e417d25807537c58bbe9c05376bbcece Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20T=C3=A1vora?= Date: Thu, 8 May 2025 21:06:41 +0100 Subject: [PATCH 009/102] Eglot: fix navigation in eglot-hierarchy-mode (bug#78250) * lisp/progmodes/eglot.el (eglot--hierarchy-label): Take PARENT-URI. Rework. (eglot--hierarchy-2): Rework. --- lisp/progmodes/eglot.el | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index d33b0b05fd4..04d3f74a6cb 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -4601,7 +4601,7 @@ If NOERROR, return predicate, else erroring function." map) "Keymap active in labels Eglot hierarchy buffers.") -(defun eglot--hierarchy-label (node) +(defun eglot--hierarchy-label (node parent-uri) (eglot--dbind ((HierarchyItem) name uri _detail ((:range item-range))) node (with-temp-buffer (insert (propertize @@ -4617,7 +4617,7 @@ If NOERROR, return predicate, else erroring function." 'keymap eglot-hierarchy-label-map 'action (lambda (_btn) - (pop-to-buffer (find-file-noselect (eglot-uri-to-path uri))) + (pop-to-buffer (find-file-noselect (eglot-uri-to-path (or parent-uri uri)))) (eglot--goto (or (elt @@ -4657,20 +4657,21 @@ If NOERROR, return predicate, else erroring function." (cl-labels ((expander-for (node) (lambda (_widget) (mapcar - #'convert + (lambda (child) + (convert child (plist-get node :uri))) (eglot--hierarchy-children node)))) - (convert (node) + (convert (node parent-uri) (let ((w (widget-convert 'tree-widget - :tag (eglot--hierarchy-label node) + :tag (eglot--hierarchy-label node parent-uri) :expander (expander-for node)))) (widget-put w :empty-icon (widget-get w :leaf-icon)) w))) (let ((inhibit-read-only t)) (erase-buffer) - (mapc (lambda (r) - (let ((w (widget-create (convert r)))) + (mapc (lambda (root) + (let ((w (widget-create (convert root nil)))) (widget-apply-action w))) eglot--hierarchy-roots) (goto-char (point-min)))) From e929d6df772e19d29b6e396e3a3330d4c9d7fe21 Mon Sep 17 00:00:00 2001 From: Paul Eggert Date: Thu, 8 May 2025 18:54:14 -0700 Subject: [PATCH 010/102] Update from Gnulib by running admin/merge-gnulib --- lib/cdefs.h | 2 +- lib/strftime.c | 68 ++++++++++++++++++++++++++++++--------------- m4/gnulib-common.m4 | 4 +-- 3 files changed, 48 insertions(+), 26 deletions(-) diff --git a/lib/cdefs.h b/lib/cdefs.h index 65da09dc096..2682c092f0e 100644 --- a/lib/cdefs.h +++ b/lib/cdefs.h @@ -497,7 +497,7 @@ # endif #endif -/* ISO C99 also allows to declare arrays as non-overlapping. The syntax is +/* ISO C99 also allows declaring arrays as non-overlapping. The syntax is array_name[restrict] GCC 3.1 and clang support this. This syntax is not usable in C++ mode. */ diff --git a/lib/strftime.c b/lib/strftime.c index 27feb1d7289..64a1f0456a2 100644 --- a/lib/strftime.c +++ b/lib/strftime.c @@ -14,10 +14,14 @@ You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . */ +/* If FPRINTFTIME is set to 1, this file defines a function with a + 'FILE *fp' parameter instead of two 'char *s, size_t max' parameters. */ #ifndef FPRINTFTIME # define FPRINTFTIME 0 #endif +/* If USE_C_LOCALE is set to 1, this file defines a function that uses the + "C" locale, regardless of the current locale. */ #ifndef USE_C_LOCALE # define USE_C_LOCALE 0 #endif @@ -38,6 +42,14 @@ # include "time-internal.h" #endif +/* Whether the system supports no localized output at all, that is, whether + strftime's output does not depend on the current locale. */ +#if defined __ANDROID__ +# define HAVE_ONLY_C_LOCALE 1 +#else +# define HAVE_ONLY_C_LOCALE 0 +#endif + /* Whether to require GNU behavior for AM and PM indicators, even on other platforms. This matters only in non-C locales. The default is to require it; you can override this via @@ -46,12 +58,12 @@ #ifndef REQUIRE_GNUISH_STRFTIME_AM_PM # define REQUIRE_GNUISH_STRFTIME_AM_PM true #endif -#if USE_C_LOCALE +#if HAVE_ONLY_C_LOCALE || USE_C_LOCALE # undef REQUIRE_GNUISH_STRFTIME_AM_PM # define REQUIRE_GNUISH_STRFTIME_AM_PM false #endif -#if USE_C_LOCALE +#if HAVE_ONLY_C_LOCALE || USE_C_LOCALE # include "c-ctype.h" #else # include @@ -302,7 +314,7 @@ enum pad_style # define TOUPPER(Ch, L) __toupper_l (Ch, L) # define TOLOWER(Ch, L) __tolower_l (Ch, L) # else -# if USE_C_LOCALE +# if HAVE_ONLY_C_LOCALE || USE_C_LOCALE # define TOUPPER(Ch, L) c_toupper (Ch) # define TOLOWER(Ch, L) c_tolower (Ch) # else @@ -379,7 +391,8 @@ memcpy_uppcase (CHAR_T *dest, const CHAR_T *src, size_t len LOCALE_PARAM) #define HAVE_NATIVE_TIME_Z \ (USE_C_LOCALE && HAVE_STRFTIME_L ? HAVE_STRFTIME_LZ : HAVE_STRFTIME_Z) -#if USE_C_LOCALE && HAVE_STRFTIME_L +#if (!HAVE_ONLY_C_LOCALE || !HAVE_STRUCT_TM_TM_ZONE) \ + && USE_C_LOCALE && HAVE_STRFTIME_L /* Cache for the C locale object. Marked volatile so that different threads see the same value @@ -398,7 +411,10 @@ c_locale (void) #endif -#if HAVE_NATIVE_TIME_Z +#if !defined _LIBC \ + && (!(HAVE_ONLY_C_LOCALE || (USE_C_LOCALE && !HAVE_STRFTIME_L)) \ + || !HAVE_STRUCT_TM_TM_ZONE) \ + && HAVE_NATIVE_TIME_Z /* On NetBSD a null tz has undefined behavior, so use a non-null tz. Cache the UTC time zone object in a volatile variable for improved @@ -818,7 +834,7 @@ iso_week_days (int yday, int wday) } -#if !defined _NL_CURRENT && (USE_C_LOCALE && !HAVE_STRFTIME_L) +#if !defined _NL_CURRENT && (HAVE_ONLY_C_LOCALE || (USE_C_LOCALE && !HAVE_STRFTIME_L)) static CHAR_T const c_weekday_names[][sizeof "Wednesday"] = { L_("Sunday"), L_("Monday"), L_("Tuesday"), L_("Wednesday"), @@ -861,7 +877,8 @@ static size_t __strftime_internal (STREAM_OR_CHAR_T *, STRFTIME_ARG (size_t) extra_args_spec LOCALE_PARAM); #if !defined _LIBC \ - && (!(USE_C_LOCALE && !HAVE_STRFTIME_L) || !HAVE_STRUCT_TM_TM_ZONE) + && (!(HAVE_ONLY_C_LOCALE || (USE_C_LOCALE && !HAVE_STRFTIME_L)) \ + || !HAVE_STRUCT_TM_TM_ZONE) /* Make sure we're calling the actual underlying strftime. In some cases, time.h contains something like @@ -1028,7 +1045,12 @@ get_tm_zone (timezone_t tz, char *ubuf, int ubufsize, int modifier, *TP is computed with a totally different time zone. This is bogus: though POSIX allows bad behavior like this, POSIX does not require it. Do the right thing instead. */ - return tp->tm_zone; + const char *ret = tp->tm_zone; +# if defined __ANDROID__ + if (!ret) + ret = ""; +# endif + return ret; #else if (!tz) return "UTC"; @@ -1125,7 +1147,7 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize) # define am_len STRLEN (a_month) # define aam_len STRLEN (a_altmonth) # define ap_len STRLEN (ampm) -#elif USE_C_LOCALE && !HAVE_STRFTIME_L +#elif HAVE_ONLY_C_LOCALE || (USE_C_LOCALE && !HAVE_STRFTIME_L) /* The English abbreviated weekday names are just the first 3 characters of the English full weekday names. */ # define a_wkday \ @@ -1383,7 +1405,7 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize) to_uppcase = true; to_lowcase = false; } -#if defined _NL_CURRENT || (USE_C_LOCALE && !HAVE_STRFTIME_L) +#if defined _NL_CURRENT || HAVE_ONLY_C_LOCALE || (USE_C_LOCALE && !HAVE_STRFTIME_L) cpy (aw_len, a_wkday); break; #else @@ -1398,7 +1420,7 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize) to_uppcase = true; to_lowcase = false; } -#if defined _NL_CURRENT || (USE_C_LOCALE && !HAVE_STRFTIME_L) +#if defined _NL_CURRENT || HAVE_ONLY_C_LOCALE || (USE_C_LOCALE && !HAVE_STRFTIME_L) cpy (STRLEN (f_wkday), f_wkday); break; #else @@ -1420,7 +1442,7 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize) else cpy (am_len, a_month); break; -#elif USE_C_LOCALE && !HAVE_STRFTIME_L +#elif HAVE_ONLY_C_LOCALE || (USE_C_LOCALE && !HAVE_STRFTIME_L) cpy (am_len, a_month); break; #else @@ -1444,7 +1466,7 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize) else cpy (STRLEN (f_month), f_month); break; -#elif USE_C_LOCALE && !HAVE_STRFTIME_L +#elif HAVE_ONLY_C_LOCALE || (USE_C_LOCALE && !HAVE_STRFTIME_L) cpy (STRLEN (f_month), f_month); break; #else @@ -1461,7 +1483,7 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize) NLW(ERA_D_T_FMT))) != '\0'))) subfmt = (const CHAR_T *) _NL_CURRENT (LC_TIME, NLW(D_T_FMT)); -#elif USE_C_LOCALE && !HAVE_STRFTIME_L +#elif HAVE_ONLY_C_LOCALE || (USE_C_LOCALE && !HAVE_STRFTIME_L) subfmt = L_("%a %b %e %H:%M:%S %Y"); #elif defined _WIN32 && !defined __CYGWIN__ /* On native Windows, "%c" is "%d/%m/%Y %H:%M:%S" by default. */ @@ -1500,7 +1522,7 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize) } break; -#if !defined _LIBC && !(USE_C_LOCALE && !HAVE_STRFTIME_L) +#if !defined _LIBC && !(HAVE_ONLY_C_LOCALE || (USE_C_LOCALE && !HAVE_STRFTIME_L)) underlying_strftime: { char ubuf[1024]; /* enough for any single format in practice */ @@ -1600,7 +1622,7 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize) # endif break; } -#elif USE_C_LOCALE && !HAVE_STRFTIME_L +#elif HAVE_ONLY_C_LOCALE || (USE_C_LOCALE && !HAVE_STRFTIME_L) #else goto underlying_strftime; #endif @@ -1624,7 +1646,7 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize) != L_('\0')))) subfmt = (const CHAR_T *) _NL_CURRENT (LC_TIME, NLW(D_FMT)); goto subformat; -#elif USE_C_LOCALE && !HAVE_STRFTIME_L +#elif HAVE_ONLY_C_LOCALE || (USE_C_LOCALE && !HAVE_STRFTIME_L) subfmt = L_("%m/%d/%y"); goto subformat; #else @@ -1702,7 +1724,7 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize) break; } } -#elif USE_C_LOCALE && !HAVE_STRFTIME_L +#elif HAVE_ONLY_C_LOCALE || (USE_C_LOCALE && !HAVE_STRFTIME_L) #else goto underlying_strftime; #endif @@ -1850,7 +1872,7 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize) to_uppcase = false; to_lowcase = true; } -#if defined _NL_CURRENT || (USE_C_LOCALE && !HAVE_STRFTIME_L) +#if defined _NL_CURRENT || HAVE_ONLY_C_LOCALE || (USE_C_LOCALE && !HAVE_STRFTIME_L) cpy (ap_len, ampm); break; #else @@ -1871,7 +1893,7 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize) == L_('\0')) subfmt = L_("%I:%M:%S %p"); goto subformat; -#elif USE_C_LOCALE && !HAVE_STRFTIME_L +#elif HAVE_ONLY_C_LOCALE || (USE_C_LOCALE && !HAVE_STRFTIME_L) subfmt = L_("%I:%M:%S %p"); goto subformat; #elif ((defined __APPLE__ && defined __MACH__) || defined __FreeBSD__ \ @@ -1933,7 +1955,7 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize) != L_('\0')))) subfmt = (const CHAR_T *) _NL_CURRENT (LC_TIME, NLW(T_FMT)); goto subformat; -#elif USE_C_LOCALE && !HAVE_STRFTIME_L +#elif HAVE_ONLY_C_LOCALE || (USE_C_LOCALE && !HAVE_STRFTIME_L) subfmt = L_("%H:%M:%S"); goto subformat; #else @@ -2043,7 +2065,7 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize) pad = yr_spec; goto subformat; } -#elif USE_C_LOCALE && !HAVE_STRFTIME_L +#elif HAVE_ONLY_C_LOCALE || (USE_C_LOCALE && !HAVE_STRFTIME_L) #else goto underlying_strftime; #endif @@ -2067,7 +2089,7 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize) DO_NUMBER (2, (era->offset + delta * era->absolute_direction)); } -#elif USE_C_LOCALE && !HAVE_STRFTIME_L +#elif HAVE_ONLY_C_LOCALE || (USE_C_LOCALE && !HAVE_STRFTIME_L) #else goto underlying_strftime; #endif diff --git a/m4/gnulib-common.m4 b/m4/gnulib-common.m4 index d5bf2fb358e..a07c7dd5a89 100644 --- a/m4/gnulib-common.m4 +++ b/m4/gnulib-common.m4 @@ -1630,7 +1630,7 @@ dnl dnl This macro sets two variables: dnl - gl_cv_onwards_func_ to yes / no / "future OS version" dnl - ac_cv_func_ to yes / no / no -dnl The first variable allows to distinguish all three cases. +dnl The first variable allows distinguishing all three cases. dnl The second variable is set, so that an invocation dnl gl_CHECK_FUNCS_ANDROID([func], [[#include ]]) dnl can be used as a drop-in replacement for @@ -1683,7 +1683,7 @@ dnl dnl This macro sets two variables: dnl - gl_cv_onwards_func_ to yes / no / "future OS version" dnl - ac_cv_func_ to yes / no / no -dnl The first variable allows to distinguish all three cases. +dnl The first variable allows distinguishing all three cases. dnl The second variable is set, so that an invocation dnl gl_CHECK_FUNCS_MACOS([func], [[#include ]]) dnl can be used as a drop-in replacement for From 004187350238a05a5c7f818c9feb648c792fe9db Mon Sep 17 00:00:00 2001 From: Martin Rudalics Date: Fri, 9 May 2025 09:36:00 +0200 Subject: [PATCH 011/102] Fix infinite looping in 'next-frame' and associates (Bug#77985) * src/frame.c (next_frame): Rewrite to avoid infinite looping if FRAME itself does not qualify as candidate frame (Bug#77985). (Fnext_frame, Fprevious_frame): Adjust do-strings. * lisp/frame.el (other-frame): Adjust doc-string. (frame-list-1): New function. (make-frame-names-alist): Rewrite using 'frame-list-1' instead of 'next-frame' (Bug#77985). (delete-other-frames): Rewrite using 'frame-list' instead of 'next-frame'. * doc/lispref/frames.texi (Finding All Frames): Minor clarifications for 'frame-list' and 'next-frame'. --- doc/lispref/frames.texi | 17 +++++--- lisp/frame.el | 84 +++++++++++++++++++++++--------------- src/frame.c | 89 +++++++++++++++++++++++++++-------------- 3 files changed, 121 insertions(+), 69 deletions(-) diff --git a/doc/lispref/frames.texi b/doc/lispref/frames.texi index 73e6b6268d4..0ad4f52bfc0 100644 --- a/doc/lispref/frames.texi +++ b/doc/lispref/frames.texi @@ -2886,15 +2886,16 @@ the @code{delete-frame} call in a @code{condition-case} form. @defun frame-list This function returns a list of all the live frames, i.e., those that have not been deleted. It is analogous to @code{buffer-list} for -buffers, and includes frames on all terminals. The list that you get -is newly created, so modifying the list doesn't have any effect on the -internals of Emacs. +buffers, and includes frames on all terminals with the exception of +tooltip frames (@pxref{Tooltips}). The list that you get is newly +created, so modifying the list doesn't have any effect on the internals +of Emacs. @end defun @defun visible-frame-list This function returns a list of just the currently visible frames. -@xref{Visibility of Frames}. Frames on text terminals always count as -visible, even though only the selected one is actually displayed. +@xref{Visibility of Frames}. Frames on text terminals will count as +visible even though only the selected one is actually displayed. @end defun @defun frame-list-z-order &optional display @@ -2917,7 +2918,7 @@ This function lets you cycle conveniently through all the frames on a specific terminal from an arbitrary starting point. It returns the frame following @var{frame}, in the list of all live frames, on @var{frame}'s terminal. The argument @var{frame} must specify a live -frame and defaults to the selected frame. It never returns a frame +frame and defaults to the selected frame. It does not return a frame whose @code{no-other-frame} parameter (@pxref{Frame Interaction Parameters}) is non-@code{nil}. @@ -2937,6 +2938,10 @@ minibuffer window. @item anything else Consider all frames. @end table + +If this function does not find a suitable frame, it returns @var{frame} +even if it would not qualify according to the @var{minibuf} argument or +its @code{no-other-frame} parameter. @end defun @defun previous-frame &optional frame minibuf diff --git a/lisp/frame.el b/lisp/frame.el index cf8aff826c1..6a746023902 100644 --- a/lisp/frame.el +++ b/lisp/frame.el @@ -1226,10 +1226,11 @@ recently selected windows nor the buffer list." (set-mouse-position frame (1- (frame-width frame)) 0)))) (defun other-frame (arg) - "Select the ARGth different visible frame on current display, and raise it. -All frames are arranged in a cyclic order. -This command selects the frame ARG steps away in that order. -A negative ARG moves in the opposite order. + "Select the ARGth visible frame on current display, and raise it. +All frames are arranged in a cyclic order. This command selects the +frame ARG steps away from the selected frame in that order. A negative +ARG moves in the opposite order. It does not select a minibuffer-only +frame. To make this command work properly, you must tell Emacs how the system (or the window manager) generally handles focus-switching @@ -1291,18 +1292,38 @@ Calls `suspend-emacs' if invoked from the controlling tty device, (suspend-tty))) (t (suspend-emacs)))) -(defun make-frame-names-alist () - ;; Only consider the frames on the same display. - (let* ((current-frame (selected-frame)) - (falist - (cons - (cons (frame-parameter current-frame 'name) current-frame) nil)) - (frame (next-frame nil 0))) - (while (not (eq frame current-frame)) - (progn - (push (cons (frame-parameter frame 'name) frame) falist) - (setq frame (next-frame frame 0)))) - falist)) +(defun frame-list-1 (&optional frame) + "Return list of all live frames starting with FRAME. +The optional argument FRAME must specify a live frame and defaults to +the selected frame. Tooltip frames are not included." + (let* ((frame (window-normalize-frame frame)) + (frames (frame-list))) + (unless (eq (car frames) frame) + (let ((tail frames)) + (while tail + (if (eq (cadr tail) frame) + (let ((head (cdr tail))) + (setcdr tail nil) + (setq frames (nconc head frames)) + (setq tail nil)) + (setq tail (cdr tail)))))) + frames)) + +(defun make-frame-names-alist (&optional frame) + "Return alist of frame names and frames starting with FRAME. +Only visible or iconified frames on the same terminal as FRAME are +listed. Frames with a non-nil `no-other-frame' parameter are not +listed. The optional argument FRAME must specify a live frame and +defaults to the selected frame." + (let ((frames (frame-list-1 frame)) + (terminal (frame-parameter frame 'terminal)) + alist) + (dolist (frame frames) + (when (and (frame-visible-p frame) + (eq (frame-parameter frame 'terminal) terminal) + (not (frame-parameter frame 'no-other-frame))) + (push (cons (frame-parameter frame 'name) frame) alist))) + (nreverse alist))) (defvar frame-name-history nil) (defun select-frame-by-name (name) @@ -2816,32 +2837,29 @@ deleting them." (interactive "i\nP") (setq frame (window-normalize-frame frame)) (let ((minibuffer-frame (window-frame (minibuffer-window frame))) - (this (next-frame frame t)) (parent (frame-parent frame)) - next) + (frames (frame-list))) ;; In a first round consider minibuffer-less frames only. - (while (not (eq this frame)) - (setq next (next-frame this t)) - (unless (or (eq (window-frame (minibuffer-window this)) this) + (dolist (this frames) + (unless (or (eq this frame) + (eq this minibuffer-frame) + (eq (window-frame (minibuffer-window this)) this) ;; When FRAME is a child frame, delete its siblings ;; only. (and parent (not (eq (frame-parent this) parent))) - ;; Do not delete a child frame of FRAME. - (eq (frame-parent this) frame)) - (if iconify (iconify-frame this) (delete-frame this))) - (setq this next)) + ;; Do not delete frame descending from FRAME. + (frame-ancestor-p frame this)) + (if iconify (iconify-frame this) (delete-frame this)))) ;; In a second round consider all remaining frames. - (setq this (next-frame frame t)) - (while (not (eq this frame)) - (setq next (next-frame this t)) - (unless (or (eq this minibuffer-frame) + (dolist (this frames) + (unless (or (eq this frame) + (eq this minibuffer-frame) ;; When FRAME is a child frame, delete its siblings ;; only. (and parent (not (eq (frame-parent this) parent))) - ;; Do not delete a child frame of FRAME. - (eq (frame-parent this) frame)) - (if iconify (iconify-frame this) (delete-frame this))) - (setq this next)))) + ;; Do not delete frame descending from FRAME. + (frame-ancestor-p frame this)) + (if iconify (iconify-frame this) (delete-frame this)))))) (defvar undelete-frame--deleted-frames nil "Internal variable used by `undelete-frame--save-deleted-frame'.") diff --git a/src/frame.c b/src/frame.c index 7dc9202d6f6..f2acb19c77d 100644 --- a/src/frame.c +++ b/src/frame.c @@ -2205,24 +2205,39 @@ candidate_frame (Lisp_Object candidate, Lisp_Object frame, Lisp_Object minibuf) static Lisp_Object next_frame (Lisp_Object frame, Lisp_Object minibuf) { - Lisp_Object f, tail; - int passed = 0; + Lisp_Object f, tail, next = Qnil; + bool passed = false; eassume (CONSP (Vframe_list)); - while (passed < 2) - FOR_EACH_FRAME (tail, f) - { - if (passed) - { - f = candidate_frame (f, frame, minibuf); - if (!NILP (f)) - return f; - } - if (EQ (frame, f)) - passed++; - } - return frame; + FOR_EACH_FRAME (tail, f) + { + if (EQ (f, frame)) + /* If we encounter FRAME, set PASSED to true. */ + passed = true; + else + { + f = candidate_frame (f, frame, minibuf); + + if (!NILP (f)) + { + if (passed) + /* If we passed FRAME already, return first suitable + candidate following it. */ + return f; + else if (NILP (next)) + /* If we didn't pass FRAME and have no suitable + candidate yet, set NEXT to the first suitable + candidate preceding FRAME. */ + next = f; + } + } + } + + /* We have scanned all frames. Return first candidate preceding FRAME + if we have found one. Otherwise return FRAME regardless of whether + it is a suitable candidate or not. */ + return NILP (next) ? frame : next; } /* Return the previous frame in the frame list before FRAME. */ @@ -2237,21 +2252,26 @@ prev_frame (Lisp_Object frame, Lisp_Object minibuf) FOR_EACH_FRAME (tail, f) { if (EQ (frame, f) && !NILP (prev)) + /* If we encounter FRAME and already have found a suitable + candidate preceding it, return that candidate. */ return prev; + f = candidate_frame (f, frame, minibuf); + if (!NILP (f)) + /* PREV is always the last suitable candidate we found. */ prev = f; } /* We've scanned the entire list. */ if (NILP (prev)) /* We went through the whole frame list without finding a single - acceptable frame. Return the original frame. */ + acceptable frame. Return FRAME. */ return frame; else - /* There were no acceptable frames in the list before FRAME; otherwise, - we would have returned directly from the loop. Since PREV is the last - acceptable frame in the list, return it. */ + /* There were no acceptable frames in the list before FRAME; + otherwise, we would have returned directly from the loop. Since + PREV is the last suitable frame in the list, return it. */ return prev; } @@ -2259,7 +2279,7 @@ prev_frame (Lisp_Object frame, Lisp_Object minibuf) DEFUN ("next-frame", Fnext_frame, Snext_frame, 0, 2, 0, doc: /* Return the next frame in the frame list after FRAME. Only frames on the same terminal as FRAME are included in the list -of candidate frames. If omitted, FRAME defaults to the selected frame. +of candidate frames. FRAME defaults to the selected frame. If MINIFRAME is nil (the default), include all frames except minibuffer-only frames. @@ -2271,7 +2291,9 @@ If MINIFRAME is `visible', include only visible frames. If MINIFRAME is 0, include only visible and iconified frames. -If MINIFRAME is any other value, include all frames. */) +If MINIFRAME is any other value, include all frames. + +Return FRAME if no suitable next frame is found. */) (Lisp_Object frame, Lisp_Object miniframe) { if (NILP (frame)) @@ -2282,15 +2304,22 @@ If MINIFRAME is any other value, include all frames. */) DEFUN ("previous-frame", Fprevious_frame, Sprevious_frame, 0, 2, 0, doc: /* Return the previous frame in the frame list before FRAME. -It considers only frames on the same terminal as FRAME. -By default, skip minibuffer-only frames. -If omitted, FRAME defaults to the selected frame. -If optional argument MINIFRAME is nil, exclude minibuffer-only frames. -If MINIFRAME is a window, include only its own frame -and any frame now using that window as the minibuffer. -If MINIFRAME is `visible', include all visible frames. -If MINIFRAME is 0, include all visible and iconified frames. -Otherwise, include all frames. */) +Only frames on the same terminal as FRAME are included in the list +of candidate frames. FRAME defaults to the selected frame. + +If MINIFRAME is nil (the default), include all frames except +minibuffer-only frames. + +If MINIFRAME is a window, include only its own frame and any frame now +using that window as the minibuffer. + +If MINIFRAME is `visible', include only visible frames. + +If MINIFRAME is 0, include only visible and iconified frames. + +If MINIFRAME is any other value, include all frames. + +Return FRAME if no suitable previous frame is found. */) (Lisp_Object frame, Lisp_Object miniframe) { if (NILP (frame)) From 3739b86f5af654ec0ae3e47a3662e19ea79d2b3c Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Sat, 5 Apr 2025 10:58:35 +0800 Subject: [PATCH 012/102] vc-exec-after: New PROC argument * lisp/vc/vc-dispatcher.el (vc-exec-after): New PROC argument. * lisp/vc/vc-hg.el (vc-exec-after): Update declaration. --- lisp/vc/vc-dispatcher.el | 17 ++++++++++------- lisp/vc/vc-hg.el | 2 +- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lisp/vc/vc-dispatcher.el b/lisp/vc/vc-dispatcher.el index 0a733336edd..a454a0bfc78 100644 --- a/lisp/vc/vc-dispatcher.el +++ b/lisp/vc/vc-dispatcher.el @@ -256,15 +256,18 @@ Another is that undo information is not kept." 'help-echo "A command is in progress in this buffer")))) -(defun vc-exec-after (code &optional success) - "Eval CODE when the current buffer's process is done. -If the current buffer has no process, just evaluate CODE. -Else, add CODE to the process' sentinel. +(defun vc-exec-after (code &optional success proc) + "Execute CODE when PROC, or the current buffer's process, is done. CODE should be a function of no arguments. -If SUCCESS, it should be a process object. Only run CODE if the -SUCCESS process has a zero exit code." - (let ((proc (get-buffer-process (current-buffer)))) +The optional PROC argument specifies the process Emacs should wait for +before executing CODE. It defaults to the current buffer's process. +If PROC is nil and the current buffer has no process, just evaluate +CODE. Otherwise, add CODE to the process's sentinel. + +If SUCCESS, it should be a process object. +Only run CODE if the SUCCESS process has a zero exit code." + (let ((proc (or proc (get-buffer-process (current-buffer))))) (cond ;; If there's no background process, just execute the code. ;; We used to explicitly call delete-process on exited processes, diff --git a/lisp/vc/vc-hg.el b/lisp/vc/vc-hg.el index b7200da0914..a18c463c848 100644 --- a/lisp/vc/vc-hg.el +++ b/lisp/vc/vc-hg.el @@ -1381,7 +1381,7 @@ REV is the revision to check out into WORKFILE." ;; Follows vc-hg-command (or vc-do-async-command), which uses vc-do-command ;; from vc-dispatcher. -(declare-function vc-exec-after "vc-dispatcher" (code &optional success)) +(declare-function vc-exec-after "vc-dispatcher" (code &optional success proc)) ;; Follows vc-exec-after. (declare-function vc-set-async-update "vc-dispatcher" (process-buffer)) From 8e02537d0be3cfdeaaf7764e2ef2db8b66de542a Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Sat, 5 Apr 2025 10:58:35 +0800 Subject: [PATCH 013/102] New vc-async-checkin user option * lisp/vc/vc.el (vc-async-checkin): New option. (vc-checkin): Don't use with-vc-properties on or display messages around asynchronous checkins. * lisp/vc/vc-git.el (vc-git-checkin): * lisp/vc/vc-hg.el (vc-hg-checkin, vc-hg-checkin-patch): Perform an async checkin operation when vc-async-checkin is non-nil. * doc/emacs/vc1-xtra.texi (General VC Options): * etc/NEWS: Document the new option. * lisp/vc/vc-dispatcher.el (vc-wait-for-process-before-save): New function. (vc-set-async-update): If the current buffer visits a file, call vc-refresh-state. * lisp/vc/vc-hg.el (vc-wait-for-process-before-save): Autoload. (vc-hg--async-command, vc-hg--async-buffer, vc-hg--command-1): New utilities, partially factored out of vc-hg-command. (vc-hg-merge-branch): Use vc-hg--async-command, thereby newly respecting vc-hg-global-switches. --- doc/emacs/vc1-xtra.texi | 19 ++++++++++ etc/NEWS | 4 ++ lisp/vc/vc-dispatcher.el | 66 ++++++++++++++++++++++++-------- lisp/vc/vc-git.el | 69 ++++++++++++++++++++------------- lisp/vc/vc-hg.el | 82 ++++++++++++++++++++++++++++------------ lisp/vc/vc.el | 63 ++++++++++++++++++++---------- 6 files changed, 219 insertions(+), 84 deletions(-) diff --git a/doc/emacs/vc1-xtra.texi b/doc/emacs/vc1-xtra.texi index 5c448e741f2..b4e1e82dcb9 100644 --- a/doc/emacs/vc1-xtra.texi +++ b/doc/emacs/vc1-xtra.texi @@ -380,6 +380,25 @@ appropriate version control system. If @code{vc-command-messages} is non-@code{nil}, VC displays messages to indicate which shell commands it runs, and additional messages when the commands finish. +@vindex vc-async-checkin + Normally checkin operations are done synchronously; that is, Emacs +waits until the checkin has completed before doing anything else. This +can be inconvenient for repositories in which the checkin operation is +slow, such as Git repositories where you check in changes to very large +files, or Mercurial repositories with a very large number of files. + + For those backends which support it, setting @code{vc-async-checkin} +to non-nil switches to doing checkin operations asynchronously. This is +particularly useful as a directory local variable in repositories where +checkin operations are slow +(@pxref{Directory Local Variables,,,elisp,GNU Emacs Lisp Reference Manual}). + + While an asynchronous checkin operation is in progress, if you use +@kbd{C-x C-s} to save a buffer visiting any file within the current VC +tree, then the operation reverts to a synchronous checkin and Emacs +waits for it to complete before saving the buffer. This is to avoid +nondeterminism regarding exactly what changes get checked in. + @node RCS and SCCS @subsubsection Options for RCS and SCCS diff --git a/etc/NEWS b/etc/NEWS index d1b0189da0c..5c2a004164c 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1752,6 +1752,10 @@ were added, removed or edited, Emacs would refuse to proceed. Now Emacs prompts to first register the unregistered files, so that all files in the fileset are in a compatible state for a checkin. ++++ +*** New user option 'vc-async-checkin' to enable async checkin operations. +Currently only supported by the Git and Mercurial backends. + --- *** New 'log-edit-hook' option to display diff of changes to commit. You can customize 'log-edit-hook' to include its new diff --git a/lisp/vc/vc-dispatcher.el b/lisp/vc/vc-dispatcher.el index a454a0bfc78..27837ddace1 100644 --- a/lisp/vc/vc-dispatcher.el +++ b/lisp/vc/vc-dispatcher.el @@ -294,6 +294,41 @@ Only run CODE if the SUCCESS process has a zero exit code." (declare (indent 0) (debug (def-body))) `(vc-exec-after (lambda () ,@body))) +(defun vc-wait-for-process-before-save (proc message) + "Make Emacs wait for PROC before saving buffers under current VC tree. +If waiting for PROC takes more than a second, display MESSAGE. + +This is used to implement `vc-async-checkin'. It effectively switches +to a synchronous checkin in the case that the user asks to save a buffer +under the tree in which the checkin operation is running. + +The hook installed by this function will make Emacs unconditionally wait +for PROC if the root of the current VC tree couldn't be determined, and +whenever writing out a buffer which doesn't have any `buffer-file-name' +yet." + (letrec ((root (vc-root-dir)) + (hook + (lambda () + (cond ((not (process-live-p proc)) + (remove-hook 'before-save-hook hook)) + ((or (and buffer-file-name + (or (not root) + (file-in-directory-p buffer-file-name + root))) + ;; No known buffer file name but we are saving: + ;; perhaps writing out a `special-mode' buffer. + ;; A `before-save-hook' cannot know whether or + ;; not it'll be written out under ROOT. + ;; Err on the side of switching to synchronous. + (not buffer-file-name)) + (with-delayed-message (1 message) + (while (process-live-p proc) + (when (input-pending-p) + (discard-input)) + (sit-for 0.05))) + (remove-hook 'before-save-hook hook)))))) + (add-hook 'before-save-hook hook))) + (defvar vc-filter-command-function #'list "Function called to transform VC commands before execution. The function is called inside the buffer in which the command @@ -525,23 +560,24 @@ asynchronous VC command has completed. PROCESS-BUFFER is the buffer for the asynchronous VC process. If the current buffer is a VC Dir buffer, call `vc-dir-refresh'. -If the current buffer is a Dired buffer, revert it." +If the current buffer is a Dired buffer, revert it. +If the current buffer visits a file, call `vc-refresh-state'." (let* ((buf (current-buffer)) (tick (buffer-modified-tick buf))) - (cond - ((derived-mode-p 'vc-dir-mode) - (with-current-buffer process-buffer - (vc-run-delayed - (if (buffer-live-p buf) - (with-current-buffer buf - (vc-dir-refresh)))))) - ((derived-mode-p 'dired-mode) - (with-current-buffer process-buffer - (vc-run-delayed - (and (buffer-live-p buf) - (= (buffer-modified-tick buf) tick) - (with-current-buffer buf - (revert-buffer))))))))) + (cl-macrolet ((run-delayed (&rest body) + `(with-current-buffer process-buffer + (vc-run-delayed + (when (buffer-live-p buf) + (with-current-buffer buf + ,@body)))))) + (cond ((derived-mode-p 'vc-dir-mode) + (run-delayed (vc-dir-refresh))) + ((derived-mode-p 'dired-mode) + (run-delayed + (when (= (buffer-modified-tick buf) tick) + (revert-buffer)))) + (buffer-file-name + (run-delayed (vc-refresh-state))))))) ;; These functions are used to ensure that the view the user sees is up to date ;; even if the dispatcher client mode has messed with file contents (as in, diff --git a/lisp/vc/vc-git.el b/lisp/vc/vc-git.el index 4acacaff203..53dedb05320 100644 --- a/lisp/vc/vc-git.el +++ b/lisp/vc/vc-git.el @@ -1209,32 +1209,49 @@ It is based on `log-edit-mode', and has Git-specific extensions." (vc-git-command nil 0 nil "apply" "--cached" patch-file) (delete-file patch-file)))) (when to-stash (vc-git--stash-staged-changes to-stash))) - ;; When operating on the whole tree, better pass "-a" than ".", - ;; since "." fails when we're committing a merge. - (apply #'vc-git-command nil 0 - (if (and only (not vc-git-patch-string)) files) - (nconc (if msg-file (list "commit" "-F" - (file-local-name msg-file)) - (list "commit" "-m")) - (let ((args - (vc-git--log-edit-extract-headers comment))) - (when msg-file - (let ((coding-system-for-write - (or pcsw vc-git-commits-coding-system))) - (write-region (car args) nil msg-file)) - (setq args (cdr args))) - args) - (unless vc-git-patch-string - (if only (list "--only" "--") '("-a"))))) - (if (and msg-file (file-exists-p msg-file)) (delete-file msg-file)) - (when to-stash - (let ((cached (make-nearby-temp-file "git-cached"))) - (unwind-protect - (progn (with-temp-file cached - (vc-git-command t 0 nil "stash" "show" "-p")) - (vc-git-command nil 0 nil "apply" "--cached" cached)) - (delete-file cached)) - (vc-git-command nil 0 nil "stash" "drop"))))) + (let ((files (and only (not vc-git-patch-string) files)) + (args (vc-git--log-edit-extract-headers comment)) + (buffer (format "*vc-git : %s*" (expand-file-name root))) + (post + (lambda () + (when (and msg-file (file-exists-p msg-file)) + (delete-file msg-file)) + (when to-stash + (let ((cached (make-nearby-temp-file "git-cached"))) + (unwind-protect + (progn + (with-temp-file cached + (vc-git-command t 0 nil "stash" "show" "-p")) + (vc-git-command nil 0 "apply" "--cached" cached)) + (delete-file cached)) + (vc-git-command nil 0 nil "stash" "drop")))))) + (when msg-file + (let ((coding-system-for-write + (or pcsw vc-git-commits-coding-system))) + (write-region (car args) nil msg-file)) + (setq args (cdr args))) + (setq args (nconc (if msg-file + (list "commit" "-F" + (file-local-name msg-file)) + (list "commit" "-m")) + args + ;; When operating on the whole tree, better pass + ;; "-a" than ".", since "." fails when we're + ;; committing a merge. + (and (not vc-git-patch-string) + (if only (list "--only" "--") '("-a"))))) + (if vc-async-checkin + (progn (vc-wait-for-process-before-save + (apply #'vc-do-async-command buffer root + vc-git-program (nconc args files)) + "Finishing checking in files...") + (with-current-buffer buffer + (vc-run-delayed + (vc-compilation-mode 'git) + (funcall post))) + (vc-set-async-update buffer)) + (apply #'vc-git-command nil 0 files args) + (funcall post))))) (defun vc-git--stash-staged-changes (files) "Stash only the staged changes to FILES." diff --git a/lisp/vc/vc-hg.el b/lisp/vc/vc-hg.el index a18c463c848..5c0758b93b2 100644 --- a/lisp/vc/vc-hg.el +++ b/lisp/vc/vc-hg.el @@ -1181,25 +1181,42 @@ If toggling on, also insert its message into the buffer." "Major mode for editing Hg log messages. It is based on `log-edit-mode', and has Hg-specific extensions.") +(autoload 'vc-wait-for-process-before-save "vc-dispatcher") + (defun vc-hg-checkin (files comment &optional _rev) "Hg-specific version of `vc-backend-checkin'. REV is ignored." - (apply #'vc-hg-command nil 0 files - (nconc (list "commit" "-m") - (vc-hg--extract-headers comment)))) + (let ((args (nconc (list "commit" "-m") + (vc-hg--extract-headers comment)))) + (if vc-async-checkin + (let ((buffer (vc-hg--async-buffer))) + (vc-wait-for-process-before-save + (apply #'vc-hg--async-command buffer (nconc args files)) + "Finishing checking in files...") + (with-current-buffer buffer + (vc-run-delayed + (vc-compilation-mode 'hg))) + (vc-set-async-update buffer)) + (apply #'vc-hg-command nil 0 files args)))) (defun vc-hg-checkin-patch (patch-string comment) (let ((patch-file (make-temp-file "hg-patch"))) (write-region patch-string nil patch-file) (unwind-protect - (progn + (let ((args (list "update" + "--merge" "--tool" "internal:local" + "tip"))) (apply #'vc-hg-command nil 0 nil (nconc (list "import" "--bypass" patch-file "-m") (vc-hg--extract-headers comment))) - (vc-hg-command nil 0 nil - "update" - "--merge" "--tool" "internal:local" - "tip")) + (if vc-async-checkin + (let ((buffer (vc-hg--async-buffer))) + (apply #'vc-hg--async-command buffer args) + (with-current-buffer buffer + (vc-run-delayed + (vc-compilation-mode 'hg))) + (vc-set-async-update buffer)) + (apply #'vc-hg-command nil 0 nil args))) (delete-file patch-file)))) (defun vc-hg--extract-headers (comment) @@ -1543,15 +1560,14 @@ call \"hg push -r REVS\" to push the specified revisions REVS." (defun vc-hg-merge-branch () "Prompt for revision and merge it into working directory. This runs the command \"hg merge\"." - (let* ((root (vc-hg-root default-directory)) - (buffer (format "*vc-hg : %s*" (expand-file-name root))) - ;; Disable pager. - (process-environment (cons "HGPLAIN=1" process-environment)) - (branch (vc-read-revision "Revision to merge: "))) - (apply #'vc-do-async-command buffer root vc-hg-program + (let ((buffer (vc-hg--async-buffer)) + (branch (vc-read-revision "Revision to merge: "))) + (apply #'vc-hg--async-command buffer (append '("--config" "ui.report_untrusted=0" "merge") - (unless (string= branch "") (list branch)))) - (with-current-buffer buffer (vc-run-delayed (vc-compilation-mode 'hg))) + (and (not (string-empty-p branch)) (list branch)))) + (with-current-buffer buffer + (vc-run-delayed + (vc-compilation-mode 'hg))) (vc-set-async-update buffer))) (defun vc-hg-prepare-patch (rev) @@ -1571,15 +1587,33 @@ This runs the command \"hg merge\"." "A wrapper around `vc-do-command' for use in vc-hg.el. This function differs from `vc-do-command' in that it invokes `vc-hg-program', and passes `vc-hg-global-switches' to it before FLAGS." + (vc-hg--command-1 #'vc-do-command + (list (or buffer "*vc*") + okstatus vc-hg-program file-or-list) + flags)) + +(defun vc-hg--async-command (buffer &rest args) + "Wrapper around `vc-do-async-command' like `vc-hg-command'." + (vc-hg--command-1 #'vc-do-async-command + (list buffer (vc-hg-root default-directory) + vc-hg-program) + args)) + +(defun vc-hg--async-buffer () + "Buffer passed to `vc-do-async-command' by vg-hg.el commands. +Intended for use via the `vc-hg--async-command' wrapper." + (format "*vc-hg : %s*" + (expand-file-name (vc-hg-root default-directory)))) + +(defun vc-hg--command-1 (fun args flags) ;; Disable pager. - (let ((process-environment (cons "HGPLAIN=1" process-environment)) - (flags (append '("--config" "ui.report_untrusted=0") flags))) - (apply #'vc-do-command (or buffer "*vc*") - okstatus vc-hg-program file-or-list - (if (stringp vc-hg-global-switches) - (cons vc-hg-global-switches flags) - (append vc-hg-global-switches - flags))))) + (let ((process-environment (cons "HGPLAIN=1" process-environment))) + (apply fun (append args + '("--config" "ui.report_untrusted=0") + (if (stringp vc-hg-global-switches) + (cons vc-hg-global-switches flags) + (append vc-hg-global-switches + flags)))))) (defun vc-hg-root (file) (vc-find-root file ".hg")) diff --git a/lisp/vc/vc.el b/lisp/vc/vc.el index f58290ccd69..dcb32e9da67 100644 --- a/lisp/vc/vc.el +++ b/lisp/vc/vc.el @@ -1002,6 +1002,24 @@ the URL-REGEXP of the association." :value-type ,vc-cloneable-backends-custom-type) :version "31.1") +(defcustom vc-async-checkin nil + "If non-nil, checkin operations should be done asynchronously. + +This is useful to set as a directory local variable in repositories +where the VCS in use performs checkin operations slowly. +For example, Git is slow when committing changes to very large files, +and Mercurial can be slow when there is a very large number of files. + +While an asynchronous checkin operation is in progress, Emacs installs a +`before-save-hook' to switch back to a synchronous checkin if you ask to +save buffers under the current VC tree. This is to avoid nondeterminism +regarding exactly what changes get checked in. + +Not supported by all backends." + :type 'boolean + :safe #'booleanp + :version "31.1") + ;; File property caching @@ -1857,26 +1875,33 @@ Runs the normal hooks `vc-before-checkin-hook' and `vc-checkin-hook'." (lambda () (vc-call-backend backend 'log-edit-mode)) (lambda (files comment) - (message "Checking in %s..." (vc-delistify files)) ;; "This log message intentionally left almost blank". - ;; RCS 5.7 gripes about white-space-only comments too. - (or (and comment (string-match "[^\t\n ]" comment)) - (setq comment "*** empty log message ***")) - (with-vc-properties - files - ;; We used to change buffers to get local value of - ;; vc-checkin-switches, but 'the' local buffer is - ;; not a well-defined concept for filesets. - (progn - (if patch-string - (vc-call-backend backend 'checkin-patch patch-string comment) - (vc-call-backend backend 'checkin files comment rev)) - (mapc #'vc-delete-automatic-version-backups files)) - `((vc-state . up-to-date) - (vc-checkout-time . ,(file-attribute-modification-time - (file-attributes file))) - (vc-working-revision . nil))) - (message "Checking in %s...done" (vc-delistify files))) + ;; RCS 5.7 gripes about whitespace-only comments too. + (unless (and comment (string-match "[^\t\n ]" comment)) + (setq comment "*** empty log message ***")) + (cl-labels ((do-it () + ;; We used to change buffers to get local value of + ;; `vc-checkin-switches', but the (singular) local + ;; buffer is not well defined for filesets. + (if patch-string + (vc-call-backend backend 'checkin-patch + patch-string comment) + (vc-call-backend backend 'checkin + files comment rev)) + (mapc #'vc-delete-automatic-version-backups files))) + (if (and vc-async-checkin + ;; Backends which support `vc-async-checkin'. + (memq backend '(Git Hg))) + ;; Rely on `vc-set-async-update' to update properties. + (do-it) + (message "Checking in %s..." (vc-delistify files)) + (with-vc-properties files (do-it) + `((vc-state . up-to-date) + (vc-checkout-time + . ,(file-attribute-modification-time + (file-attributes file))) + (vc-working-revision . nil))) + (message "Checking in %s...done" (vc-delistify files))))) 'vc-checkin-hook backend patch-string)) From 295c38b03d065d00361ca07355b82bf279ebd6b1 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Fri, 9 May 2025 11:00:47 +0100 Subject: [PATCH 014/102] Create new test/lisp/vc/vc-tests/ for vc.el tests * test/lisp/vc/vc-tests.el: Move ... * test/lisp/vc/vc-tests/vc-tests.el: ... to here. * test/lisp/vc/vc-misc-tests.el: Move ... * test/lisp/vc/vc-tests/vc-test-misc.el: ... to here. --- test/lisp/vc/{vc-misc-tests.el => vc-tests/vc-test-misc.el} | 6 +++--- test/lisp/vc/{ => vc-tests}/vc-tests.el | 0 2 files changed, 3 insertions(+), 3 deletions(-) rename test/lisp/vc/{vc-misc-tests.el => vc-tests/vc-test-misc.el} (94%) rename test/lisp/vc/{ => vc-tests}/vc-tests.el (100%) diff --git a/test/lisp/vc/vc-misc-tests.el b/test/lisp/vc/vc-tests/vc-test-misc.el similarity index 94% rename from test/lisp/vc/vc-misc-tests.el rename to test/lisp/vc/vc-tests/vc-test-misc.el index d19dda36d2f..22015358972 100644 --- a/test/lisp/vc/vc-misc-tests.el +++ b/test/lisp/vc/vc-tests/vc-test-misc.el @@ -1,4 +1,4 @@ -;;; vc-misc-tests.el --- backend-agnostic VC tests -*- lexical-binding:t -*- +;;; vc-test-misc.el --- backend-agnostic VC tests -*- lexical-binding:t -*- ;; Copyright (C) 2025 Free Software Foundation, Inc. @@ -63,5 +63,5 @@ (should (equal (test-it `(Git ("missing" ,temp "present"))) missing+present)))))) -(provide 'vc-misc-tests) -;;; vc-misc-tests.el ends here +(provide 'vc-test-misc) +;;; vc-test-misc.el ends here diff --git a/test/lisp/vc/vc-tests.el b/test/lisp/vc/vc-tests/vc-tests.el similarity index 100% rename from test/lisp/vc/vc-tests.el rename to test/lisp/vc/vc-tests/vc-tests.el From b87608c9c7fd3a7a2a2fb3803d6038fa888602c4 Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Fri, 9 May 2025 14:43:03 +0300 Subject: [PATCH 015/102] ; Fix VC tests * test/lisp/vc/vc-tests/vc-tests.el (vc-test-hg06-version-diff): Skip on MS-Windows in batch mode. * test/lisp/vc/vc-git-tests.el (vc-git-test--start-branch): More portable command for showing the current branch ("--show-current" is only available since Git 2.22). --- test/lisp/vc/vc-git-tests.el | 2 +- test/lisp/vc/vc-tests/vc-tests.el | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/test/lisp/vc/vc-git-tests.el b/test/lisp/vc/vc-git-tests.el index 3cb12d5f86e..eca7baaa88e 100644 --- a/test/lisp/vc/vc-git-tests.el +++ b/test/lisp/vc/vc-git-tests.el @@ -122,7 +122,7 @@ agnostic of init.defaultbranch." (write-region "hello" nil "README") (vc-git-test--run "add" "README") (vc-git-test--run "commit" "-mFirst") - (string-trim (vc-git-test--run "branch" "--show-current"))) + (string-trim (vc-git-test--run "rev-parse" "--abbrev-ref" "HEAD"))) (defun vc-git-test--dir-headers (headers) "Return an alist of header values as they would appear in `vc-dir'. diff --git a/test/lisp/vc/vc-tests/vc-tests.el b/test/lisp/vc/vc-tests/vc-tests.el index e7852d53f99..632a6a792bd 100644 --- a/test/lisp/vc/vc-tests/vc-tests.el +++ b/test/lisp/vc/vc-tests/vc-tests.el @@ -785,6 +785,11 @@ This checks also `vc-backend' and `vc-responsible-backend'." ;; `vc-mtn.el' gives me: ;; "Failed (status 1): mtn commit -m Testing vc-version-diff\n\n foo" (skip-when (memq ',backend '(Mtn))) + ;; `vc-hg.el' gives me, only on MS-Windows and only in batch mode: + ;; "Failed (status 255): hg --config ui.report_untrusted=0 commit -m Testing vc-version-diff\n\n foo" + (skip-when (and (memq ',backend '(Hg)) + (eq system-type 'windows-nt) + noninteractive)) (vc-test--version-diff ',backend)) )))) From 2102554a486e457cf020a03b72276e532afc98ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vincent=20Bela=C3=AFche?= Date: Fri, 9 May 2025 14:30:50 +0200 Subject: [PATCH 016/102] ix bugs when a SES buffer has named cells, and another don't. * lisp/ses.el (ses-list-named-cells): Give a message when the list is empty. (ses--unbind-cell-name): Do not throw error in a SES buffer w/o any named cell for gethash from 'ses--named-cell-hashmap' being 'nil'. --- lisp/ses.el | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lisp/ses.el b/lisp/ses.el index d23cefc53f4..aa3eb1bad40 100644 --- a/lisp/ses.el +++ b/lisp/ses.el @@ -504,7 +504,7 @@ This can alter PLIST." (setq ses--ses-buffer-list (delq buf ses--ses-buffer-list))) (t (with-current-buffer buf - (when (gethash name ses--named-cell-hashmap) + (when (and ses--named-cell-hashmap (gethash name ses--named-cell-hashmap)) (setq used-elsewhere t buffer-list nil)))))) (unless used-elsewhere @@ -3452,7 +3452,7 @@ while in the SES buffer." ((minibufferp) ses--completion-table) ((derived-mode-p 'help-mode) nil) (t (user-error "Not in a SES buffer"))))) - (when named-cell-hashmap + (if named-cell-hashmap (let ((ses--list-orig-buffer (or ses--list-orig-buffer (current-buffer)))) (help-setup-xref (list (lambda (named-cell-hashmap buffer) @@ -3474,7 +3474,8 @@ while in the SES buffer." (princ "\n")) named-cell-hashmap)) (with-current-buffer standard-output - (buffer-string))))))) + (buffer-string))))) + (message "No named cell found"))) ;;---------------------------------------------------------------------------- From 63f73de839b3142a3587ccb6edecc78d5232bced Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Fri, 9 May 2025 16:38:16 +0100 Subject: [PATCH 017/102] ; * doc/emacs/vc1-xtra.texi (General VC Options): Fix markup. --- doc/emacs/vc1-xtra.texi | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/emacs/vc1-xtra.texi b/doc/emacs/vc1-xtra.texi index b4e1e82dcb9..975499cc787 100644 --- a/doc/emacs/vc1-xtra.texi +++ b/doc/emacs/vc1-xtra.texi @@ -388,10 +388,10 @@ slow, such as Git repositories where you check in changes to very large files, or Mercurial repositories with a very large number of files. For those backends which support it, setting @code{vc-async-checkin} -to non-nil switches to doing checkin operations asynchronously. This is -particularly useful as a directory local variable in repositories where -checkin operations are slow -(@pxref{Directory Local Variables,,,elisp,GNU Emacs Lisp Reference Manual}). +to non-@code{nil} switches to doing checkin operations asynchronously. +This is particularly useful as a directory local variable in +repositories where checkin operations are slow (@pxref{Directory Local +Variables,,,elisp,GNU Emacs Lisp Reference Manual}). While an asynchronous checkin operation is in progress, if you use @kbd{C-x C-s} to save a buffer visiting any file within the current VC From 1a2c29b5317357ba451fdd6f308675a1c6cbba2e Mon Sep 17 00:00:00 2001 From: Michael Albinus Date: Sat, 10 May 2025 10:05:51 +0200 Subject: [PATCH 018/102] Improve Tramp's make-process handling for Solaris * lisp/net/tramp-sh.el (tramp-sh-handle-make-process): Disable buffering also for remote Solaris hosts. Reported by Stacey Marshall . --- lisp/net/tramp-sh.el | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lisp/net/tramp-sh.el b/lisp/net/tramp-sh.el index 9d74c2fd088..046eef791ac 100644 --- a/lisp/net/tramp-sh.el +++ b/lisp/net/tramp-sh.el @@ -3115,7 +3115,9 @@ will be used." (let ((pid (tramp-send-command-and-read v "echo $$"))) (setq p (tramp-get-connection-process v)) (process-put p 'remote-pid pid)) - (when (memq connection-type '(nil pipe)) + (when + (or (memq connection-type '(nil pipe)) + (tramp-check-remote-uname v tramp-sunos-unames)) ;; Disable carriage return to newline ;; translation. This does not work on ;; macOS, see Bug#50748. @@ -3131,6 +3133,9 @@ will be used." ;; should set a timeout ;; instead. See `tramp-pipe-stty-settings'. ;; (Bug#62093) + ;; On Solaris, the maximum line length + ;; depends also on MAX_CANON (256). So we + ;; disable buffering as well. ;; FIXME: Shall we rather use "stty raw"? (tramp-send-command v (format From 013ed9799e68d83b478320b0a31180f8047d13b1 Mon Sep 17 00:00:00 2001 From: Manuel Giraud Date: Mon, 21 Apr 2025 19:01:25 +0200 Subject: [PATCH 019/102] Fall back `eww-switch-to-buffer' to `eww' * lisp/net/eww.el (eww-switch-to-buffer): When no EWW buffers exist, call `eww' instead. (eww-list-buffers): Do not pop to *eww buffers* when there is no EWW buffers. (Bug#77967) --- doc/misc/eww.texi | 3 +++ etc/NEWS | 5 +++++ lisp/net/eww.el | 38 ++++++++++++++++++++++---------------- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/doc/misc/eww.texi b/doc/misc/eww.texi index 5be59972811..d092fc1f230 100644 --- a/doc/misc/eww.texi +++ b/doc/misc/eww.texi @@ -259,6 +259,9 @@ to quickly kill, flip through and switch to specific EWW buffer. To switch EWW buffers through a minibuffer prompt, press @kbd{s} (@code{eww-switch-to-buffer}). +The @code{eww-switch-to-buffer} command will fallback to @code{eww} +when there is no EWW buffers. + @findex eww-browse-with-external-browser @vindex browse-url-secondary-browser-function @vindex eww-use-external-browser-for-content-type diff --git a/etc/NEWS b/etc/NEWS index 5c2a004164c..2dca61740d4 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1257,6 +1257,11 @@ content-type of Web pages which don't have a valid 'Content-Type' header. The default value is a function that considers a page with an HTML 'doctype' declaration to have context-type "text/html". ++++ +*** 'eww-switch-to-buffer' falls back to 'eww'. +When there is no EWW buffers, 'eww-switch-to-buffer' falls back to +calling 'eww'. + ** CC mode +++ diff --git a/lisp/net/eww.el b/lisp/net/eww.el index 7edd1acbcf9..c11e378c714 100644 --- a/lisp/net/eww.el +++ b/lisp/net/eww.el @@ -2321,25 +2321,29 @@ If CHARSET is nil then use UTF-8." (eww-reload nil charset))) (defun eww-switch-to-buffer () - "Prompt for an EWW buffer to display in the selected window." + "Prompt for an EWW buffer to display in the selected window. +If no such buffer exist, fallback to calling `eww'." (interactive nil eww-mode) (let ((completion-extra-properties `(:annotation-function ,(lambda (buf) (with-current-buffer buf (format " %s" (eww-current-url)))))) - (curbuf (current-buffer))) - (pop-to-buffer-same-window - (read-buffer "Switch to EWW buffer: " - (cl-loop for buf in (nreverse (buffer-list)) + (curbuf (current-buffer)) + (list (cl-loop for buf in (nreverse (buffer-list)) if (with-current-buffer buf (derived-mode-p 'eww-mode)) - return buf) - t - (lambda (bufn) - (setq bufn (if (consp bufn) (cdr bufn) (get-buffer bufn))) - (and (with-current-buffer bufn - (derived-mode-p 'eww-mode)) - (not (eq bufn curbuf)))))))) + return buf))) + (if list + (pop-to-buffer-same-window + (read-buffer "Switch to EWW buffer: " + list + t + (lambda (bufn) + (setq bufn (if (consp bufn) (cdr bufn) (get-buffer bufn))) + (and (with-current-buffer bufn + (derived-mode-p 'eww-mode)) + (not (eq bufn curbuf)))))) + (call-interactively 'eww)))) (defun eww-toggle-fonts () "Toggle whether to use monospaced or font-enabled layouts." @@ -2718,10 +2722,12 @@ see)." (defun eww-list-buffers () "Pop a buffer with a list of eww buffers." (interactive) - (with-current-buffer (get-buffer-create "*eww buffers*") - (eww-buffers-mode) - (eww--list-buffers-display-table)) - (pop-to-buffer "*eww buffers*")) + (if (null (eww-buffer-list)) + (message "No EWW buffers.") + (with-current-buffer (get-buffer-create "*eww buffers*") + (eww-buffers-mode) + (eww--list-buffers-display-table)) + (pop-to-buffer "*eww buffers*"))) (defun eww--list-buffers-display-table (&optional _ignore-auto _noconfirm) "Display a table with the list of eww buffers. From 0d493864cee2ea1d7661d51b973db58667b6b65b Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Sat, 10 May 2025 13:11:22 +0300 Subject: [PATCH 020/102] Fix indentation of XML comments * lisp/nxml/nxml-mode.el (nxml-compute-indent-in-delimited-token): Fix indentation in XML comments with empty lines. Patch by John Ciolfi . (Bug#73206) --- lisp/nxml/nxml-mode.el | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lisp/nxml/nxml-mode.el b/lisp/nxml/nxml-mode.el index 7acc19b9058..1d2471cc1fa 100644 --- a/lisp/nxml/nxml-mode.el +++ b/lisp/nxml/nxml-mode.el @@ -1522,6 +1522,8 @@ of the line. This expects the xmltok-* variables to be set up as by ((progn (goto-char pos) (forward-line -1) + (while (looking-at "^[[:blank:]]*$") + (forward-line -1)) (<= (point) xmltok-start)) (goto-char (+ xmltok-start (length open-delim))) (when (and (string= open-delim "