commit e381cf1fc97fc1c0bab1816476dd6f73a628b238 Author: Jim Porter Date: Fri Aug 15 13:44:03 2025 -0700 Allow child processes to continue after EPIPE This ensures that if the child process closed its stdin and Emacs tries to write to it, the process can still do any remaining work and exit normally. In practice, this can occur with commands like "head(1)" (bug#79079). * src/fileio.c (file_for_stream): New function, extracted from... (Fset_binary_mode): ... here. (Ffile__close_stream): New function. * src/process.c (send_process): When encountering EPIPE, only close the fd for the pipe to the child process's stdin. * lisp/eshell/esh-io.el (eshell-output-object-to-target): Don't check for process liveness anymore. * test/src/process-tests.el (process-tests/broken-pipe): New function. (process-tests/broken-pipe/pipe, process-tests/broken-pipe/pty) (process-tests/broken-pipe/pipe-stdin) (process-tests/broken-pipe/pty-stdin): New tests. * etc/NEWS: Announce this change. diff --git a/etc/NEWS b/etc/NEWS index 795ac6f5c3d..e6fd8a7f747 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -30,6 +30,13 @@ applies, and please also update docstrings as needed. * Changes in Emacs 32.1 +--- +** Emacs no longer kills child processes after EPIPE. +Previously, Emacs would immediately kill a child process and set its +exit status to 256 if sending input to that process returned EPIPE. +Now when this happens, Emacs closes the file descriptor to write to the +child process, but allows it to continue execution as normal. + * Editing Changes in Emacs 32.1 diff --git a/lisp/eshell/esh-io.el b/lisp/eshell/esh-io.el index ea7dbb2e122..395a641aed6 100644 --- a/lisp/eshell/esh-io.el +++ b/lisp/eshell/esh-io.el @@ -741,18 +741,14 @@ Returns what was actually sent, or nil if nothing was sent.") "Output OBJECT to the process TARGET." (unless (stringp object) (setq object (eshell-stringify object))) - (condition-case err + (condition-case _ (process-send-string target object) (error - ;; If `process-send-string' raises an error and the process has - ;; finished, treat it as a broken pipe. Otherwise, just re-raise - ;; the signal. NOTE: When running Emacs in batch mode - ;; (e.g. during regression tests), Emacs can abort due to SIGPIPE - ;; here. Maybe `process-send-string' should handle SIGPIPE even - ;; in batch mode (bug#66186). - (if (process-live-p target) - (signal err) - (signal 'eshell-pipe-broken (list target))))) + ;; NOTE: When running Emacs in batch mode (e.g. during regression + ;; tests), Emacs can abort due to SIGPIPE here. Maybe + ;; `process-send-string' should handle SIGPIPE even in batch mode + ;; (bug#66186). + (signal 'eshell-pipe-broken (list target)))) object) (cl-defmethod eshell-output-object-to-target (object diff --git a/src/fileio.c b/src/fileio.c index eb64b59fcf2..e6a6670ff9d 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -6572,6 +6572,19 @@ before any other event (mouse or keypress) is handled. */) } +static FILE * +file_for_stream (Lisp_Object stream) +{ + if (EQ (stream, Qstdin)) + return stdin; + else if (EQ (stream, Qstdout)) + return stdout; + else if (EQ (stream, Qstderr)) + return stderr; + else + xsignal2 (Qerror, build_string ("unsupported stream"), stream); +} + DEFUN ("set-binary-mode", Fset_binary_mode, Sset_binary_mode, 2, 2, 0, doc: /* Switch STREAM to binary I/O mode or text I/O mode. STREAM can be one of the symbols `stdin', `stdout', or `stderr'. @@ -6593,18 +6606,9 @@ On Posix systems, this function always returns non-nil, and has no effect except for flushing STREAM's data. */) (Lisp_Object stream, Lisp_Object mode) { - FILE *fp = NULL; - int binmode; - CHECK_SYMBOL (stream); - if (EQ (stream, Qstdin)) - fp = stdin; - else if (EQ (stream, Qstdout)) - fp = stdout; - else if (EQ (stream, Qstderr)) - fp = stderr; - else - xsignal2 (Qerror, build_string ("unsupported stream"), stream); + FILE *fp = file_for_stream (stream); + int binmode; binmode = NILP (mode) ? O_TEXT : O_BINARY; if (fp != stdin) @@ -6612,6 +6616,22 @@ effect except for flushing STREAM's data. */) return (set_binary_mode (fileno (fp), binmode) == O_BINARY) ? Qt : Qnil; } + +DEFUN ("file--close-stream", Ffile__close_stream, + Sfile__close_stream, 1, 1, 0, + doc: /* Close the standard STREAM of the Emacs process. +STREAM can be one of the symbols `stdin', `stdout', or `stderr'. + +This function is primarily intended for testing process machinery within +Emacs. */) + (Lisp_Object stream) +{ + CHECK_SYMBOL (stream); + FILE *fp = file_for_stream (stream); + fclose (fp); + return Qnil; +} + #ifndef DOS_NT @@ -7047,6 +7067,7 @@ This includes interactive calls to `delete-file' and defsubr (&Snext_read_file_uses_dialog_p); defsubr (&Sset_binary_mode); + defsubr (&Sfile__close_stream); #ifndef DOS_NT defsubr (&Sfile_system_info); diff --git a/src/process.c b/src/process.c index d46be48821f..9ea2b66533b 100644 --- a/src/process.c +++ b/src/process.c @@ -6922,10 +6922,8 @@ send_process (Lisp_Object proc, const char *buf, ptrdiff_t len, } else if (errno == EPIPE) { - p->raw_status_new = 0; - pset_status (p, list2 (Qexit, make_fixnum (256))); - p->tick = ++process_tick; - deactivate_process (proc); + close_process_fd (&p->open_fd[WRITE_TO_SUBPROCESS]); + p->outfd = -1; error ("Process %s no longer connected to pipe; closed it", SDATA (p->name)); } diff --git a/test/src/process-tests.el b/test/src/process-tests.el index e854d3d3b87..1b1a9dfb07f 100644 --- a/test/src/process-tests.el +++ b/test/src/process-tests.el @@ -1054,6 +1054,69 @@ Return nil if FILENAME doesn't exist." (process-exit-status proc) events)))))) +(defun process-tests/broken-pipe (connection-type) + "Test handling of broken pipes; see bug#79079. +This test runs a shell script that reads a line of text and closes +stdin. We send two lines of text to the script; the second should +signal an error indicating that the pipe has been closed. The script +should also run to completion, printing out the line of text it read." + (with-temp-buffer + (let ((saw-error nil) + (proc (make-process + :name "test" :buffer (current-buffer) + :command `(,(expand-file-name invocation-name + invocation-directory) + "-Q" "--batch" "--eval" + ,(prin1-to-string + '(let ((line (read-string ""))) + (file--close-stream 'stdin) + (message "closed stream") + (sit-for 1) + (message "%s" line)))) + :connection-type 'pipe))) + (process-send-string proc "hello\n") + (while (not (string-prefix-p "closed stream\n" (buffer-string))) + (accept-process-output)) + (condition-case err + (process-send-string proc "extra\n") + (error + (setq saw-error t) + (should (string-match + (rx bos "Process test" (? "<" (+ digit) ">") + " no longer connected to pipe; closed it" + eos) + (error-message-string err))))) + (unless saw-error + (ert-fail "Expected error from `process-send-string'")) + ;; Wait for the process to finish, and check results. + (while (eq (process-status proc) 'run) + (accept-process-output)) + (accept-process-output) + (should (eq (process-status proc) 'exit)) + (should (eq (process-exit-status proc) 0)) + (should (string-match + (rx bos "closed stream\nhello\n\nProcess test" + (? "<" (+ digit) ">") " finished\n" eos) + (buffer-string)))))) + +;; These tests only works when running Emacs interactively, since we +;; don't catch SIGPIPE in batch mode. TODO: Fixing bug#66186 would +;; probably allow running these tests in batch mode. +(when (not noninteractive) + (ert-deftest process-tests/broken-pipe/pipe () + (process-tests/broken-pipe 'pipe)) + + ;; Emacs doesn't support PTYs on MS-Windows. + (unless (memq system-type '(ms-dos windows-nt)) + (ert-deftest process-tests/broken-pipe/pty () + (process-tests/broken-pipe 'pty)) + + (ert-deftest process-tests/broken-pipe/pipe-stdin () + (process-tests/broken-pipe '(pipe . pty))) + + (ert-deftest process-tests/broken-pipe/pty-stdin () + (process-tests/broken-pipe '(pty . pipe))))) + (ert-deftest process-num-processors () "Sanity checks for num-processors." (should (equal (num-processors) (num-processors))) commit a557bf69b49ada1e777f9f031e975ce15ccbc2e7 Author: Jim Porter Date: Sun May 17 15:35:44 2026 -0700 Ensure that process-tests clean up test processes * test/src/process-tests.el (start-process-should-not-modify-arguments): Clean up test process. (process-test--check-pipe-process): New macro... (process-test-make-pipe-process-no-buffer): ... call it. diff --git a/test/src/process-tests.el b/test/src/process-tests.el index 55657a23fa9..e854d3d3b87 100644 --- a/test/src/process-tests.el +++ b/test/src/process-tests.el @@ -189,13 +189,17 @@ process to complete." ;; convert forward slashes to backslashes. (expand-file-name (executable-find "attrib.exe"))) (_ "/bin//sh"))) - (samepath (copy-sequence path))) - ;; Make sure 'start-process' actually goes all the way and invokes - ;; the program. - (should (process-live-p (condition-case nil - (start-process "" nil path) - (error nil)))) - (should (equal path samepath))))) + (samepath (copy-sequence path)) + (process (condition-case nil + (start-process "" nil path) + (error nil)))) + (unwind-protect + (progn + ;; Make sure 'start-process' actually goes all the way and + ;; invokes the program. + (should (process-live-p process)) + (should (equal path samepath))) + (delete-process process))))) (ert-deftest make-process/noquery-stderr () "Checks that Bug#30031 is fixed." @@ -1056,11 +1060,18 @@ Return nil if FILENAME doesn't exist." (should (integerp (num-processors))) (should (< 0 (num-processors)))) +(defmacro process-test--check-pipe-process (args should-have-buffer) + `(let ((pipe-process (make-pipe-process ,@args))) + (unwind-protect + (,(if should-have-buffer 'should 'should-not) + (process-buffer pipe-process)) + (delete-process pipe-process)))) + (ert-deftest process-test-make-pipe-process-no-buffer () "Test that a pipe process can be created without a buffer." - (should (process-buffer (make-pipe-process :name "test"))) - (should (process-buffer (make-pipe-process :name "test" :buffer "test"))) - (should-not (process-buffer (make-pipe-process :name "test" :buffer nil)))) + (process-test--check-pipe-process (:name "test") t) + (process-test--check-pipe-process (:name "test" :buffer "test") t) + (process-test--check-pipe-process (:name "test" :buffer nil) nil)) (provide 'process-tests) ;;; process-tests.el ends here commit 7626993c6fce9a9a71d4406ffdb1d14c462675b6 Author: Paul Eggert Date: Sun May 17 10:05:11 2026 -0700 Remove SAFE_ALLOCA_LISP_EXTRA * src/lisp.h (SAFE_ALLOCA_LISP_EXTRA): Remove. It is no longer used, it makes life more difficult in the feature/igc3 branch, and having it around tempted me to start using it again. diff --git a/src/lisp.h b/src/lisp.h index ee50cc777c6..a5082146da7 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -5608,7 +5608,7 @@ extern void init_system_name (void); systems to detect stack exhaustion and enlarge the stack as needed; this thus risks hitting a segfault where none should have happened. (This problem is real in deeply-recursive cases, - but these do happen in Emacs, e.g. in regexp search or during GC.) */ + e.g., in regexp search or during GC.) */ enum MAX_ALLOCA { MAX_ALLOCA = 16 * 1024 }; @@ -5697,32 +5697,23 @@ safe_free_unbind_to (specpdl_ref count, specpdl_ref sa_count, Lisp_Object val) # pragma GCC diagnostic ignored "-Wanalyzer-allocation-size" #endif -/* Set BUF to point to an allocated array of NELT Lisp_Objects, - immediately followed by EXTRA spare bytes. */ +/* Set BUF to point to an allocated array of NELT Lisp_Objects. */ -#define SAFE_ALLOCA_LISP_EXTRA(buf, nelt, extra) \ +#define SAFE_ALLOCA_LISP(buf, nelt) \ do { \ ptrdiff_t alloca_nbytes; \ if (ckd_mul (&alloca_nbytes, nelt, word_size) \ - || ckd_add (&alloca_nbytes, alloca_nbytes, extra) \ || SIZE_MAX < alloca_nbytes) \ memory_full (SIZE_MAX); \ else if (alloca_nbytes <= sa_avail) \ (buf) = AVAIL_ALLOCA (alloca_nbytes); \ else \ { \ - /* Although only the first nelt words need clearing, \ - typically EXTRA is 0 or small so just use xzalloc; \ - this is simpler and often faster. */ \ (buf) = xzalloc (alloca_nbytes); \ record_unwind_protect_array (buf, nelt); \ } \ } while (false) -/* Set BUF to point to an allocated array of NELT Lisp_Objects. */ - -#define SAFE_ALLOCA_LISP(buf, nelt) SAFE_ALLOCA_LISP_EXTRA (buf, nelt, 0) - /* If USE_STACK_LISP_OBJECTS, define macros and functions that allocate some Lisp objects on the C stack. As the storage is not commit 24f9e6a6936203a07d93372cf835074d6c3f185a Author: Paul Eggert Date: Sun May 17 09:52:49 2026 -0700 Make styled_format more compatible with igc * src/editfns.c (styled_format): Don’t call SAFE_ALLOCA_LISP_EXTRA, as this makes life more difficult in the feature/igc3 branch. Also, allocate another byte for the format string trailing '\0', so that we don’t rely in the str2num trick with trailing '\1'. Problems reported by Pip Cet (Bug#81057#32). Also, check alloca size more exactly. Ameliorate the extra conditional branches by doing all the internal size calculations before a conditional branch on overflow. diff --git a/src/editfns.c b/src/editfns.c index 168e897262a..cad41a36f7f 100644 --- a/src/editfns.c +++ b/src/editfns.c @@ -3498,29 +3498,30 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) /* Upper bound on number of format specs. Each uses at least 2 chars. */ ptrdiff_t nspec_bound = SCHARS (args[0]) >> 1; - /* Allocate auxiliary tables in one go, in the order: - spec_arguments, info, format_start, discarded. - Because nspec_bound <= formatlen / 2, EXTRA is an upper bound on - (nspec_bound * sizeof *info {for info} + formatlen {for - format_start} + formatlen {for discarded}). */ - ptrdiff_t extra; - if (ckd_mul (&extra, formatlen, 2 + (sizeof *info + 1) / 2)) - memory_full (SIZE_MAX); /* One argument belonging to each spec; but needs to be allocated separately so GC doesn't free the strings (bug#75754). */ Lisp_Object *spec_arguments; - SAFE_ALLOCA_LISP_EXTRA (spec_arguments, nspec_bound, extra); + SAFE_ALLOCA_LISP (spec_arguments, nspec_bound); + /* Allocate other auxiliary tables in one go, in the order: + info[nspec_bound], format_start[formatlen + 1], discarded[formatlen]. */ + ptrdiff_t info_size, format_and_discarded_size, alloca_size; + bool v = ckd_mul (&info_size, nspec_bound, sizeof *info); + v |= ckd_add (&format_and_discarded_size, formatlen + 1, formatlen); + v |= ckd_add (&alloca_size, info_size, format_and_discarded_size); + v |= SIZE_MAX < alloca_size; + if (v) + memory_full (SIZE_MAX); /* The info table. */ - static_assert (alignof (struct info) <= alignof (Lisp_Object)); - info = (struct info *) &spec_arguments[nspec_bound]; - /* The format string's bytes, sans trailing '\0'. */ + info = SAFE_ALLOCA (alloca_size); + /* A copy of the format string's bytes, needed because the original + may not survive GC. */ char *format_start = memcpy (&info[nspec_bound], - SSDATA (args[0]), formatlen); + SSDATA (args[0]), formatlen + 1); /* discarded[I] is: 1 if byte I of the format string was not copied into the output. 2 if byte I was not the first byte of its character. 0 otherwise. */ - char *discarded = memset (&format_start[formatlen], 0, formatlen); + char *discarded = memset (&format_start[formatlen + 1], 0, formatlen); /* Try to determine whether the result should be multibyte. This is not always right; sometimes the result needs to be multibyte @@ -4405,7 +4406,7 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) } return_val: - /* If we allocated BUF or auxiliary tables with malloc, free it too. */ + /* If we allocated BUF or auxiliary tables with malloc, free them too. */ SAFE_FREE (); return val; commit f599a922770839306a0e655af0426f4bae83420f Author: Paul Eggert Date: Sun May 17 00:58:02 2026 -0700 Shrink styled_format's frame quite a bit Problem reported by Helmut Eller (Bug#81057). On x86-64 this patch shrinks USEFUL_PRECISION_MAX from 16382 to 1074, SPRINTF_BUFSIZE from 21318 to 1386, and sizeof initial_buffer from 22318 to 2386. This also fixes a problem reported privately by Pip Cet: exactly formatting the smallest positive IEEE double 2**-1074 with %f needs %.1074f and 1074 is DBL_MANT_DIG - DBL_MIN_EXP, not merely 1 - DBL_MIN_EXP. * src/editfns.c (USEFUL_PRECISION_MAX, SPRINTF_BUFSIZE): Base these on double, not long double, since long double is not the worst case (it is used only for converted u?intmax_t). (USEFUL_PRECISION_MAX): Add DBL_MANT_DIG - 1 as per Pip Cet. diff --git a/src/editfns.c b/src/editfns.c index 07ecbf76da3..168e897262a 100644 --- a/src/editfns.c +++ b/src/editfns.c @@ -3442,15 +3442,16 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) /* Maximum precision for a %f conversion such that the trailing output digit might be nonzero. Any precision larger than this will not yield useful information. */ - USEFUL_PRECISION_MAX = ((1 - LDBL_MIN_EXP) + USEFUL_PRECISION_MAX = ((DBL_MANT_DIG - DBL_MIN_EXP) * (FLT_RADIX == 2 || FLT_RADIX == 10 ? 1 : FLT_RADIX == 16 ? 4 : -1)), /* Maximum number of bytes (including terminating null) generated by any format, if precision is no more than USEFUL_PRECISION_MAX. - On all practical hosts, %Lf is the worst case. */ - SPRINTF_BUFSIZE = (sizeof "-." + (LDBL_MAX_10_EXP + 1) + On all practical hosts %f is the worst case, as %Lf is used only + on arguments exactly representable as intmax_t or uintmax_t. */ + SPRINTF_BUFSIZE = (sizeof "-." + (DBL_MAX_10_EXP + 1) + USEFUL_PRECISION_MAX) }; static_assert (USEFUL_PRECISION_MAX > 0); commit cf693ce0592191b7b1c38114614980687bbbc1a2 Author: Paul Eggert Date: Sun May 17 00:11:35 2026 -0700 Grow styled_format's frame somewhat * src/editfns.c (styled_format): Don’t subtract sizeof initial_buffer from sa_avail (bug#81057). diff --git a/src/editfns.c b/src/editfns.c index 3a1eb3e860a..07ecbf76da3 100644 --- a/src/editfns.c +++ b/src/editfns.c @@ -3472,7 +3472,9 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) Lisp_Object val; bool arg_intervals = false; USE_SAFE_ALLOCA; - sa_avail -= sizeof initial_buffer; + /* Do not bother doing "sa_avail -= sizeof initial_buffer;" here, + as it is OK to go somewhat over MAX_ALLOCA bytes + for this particular function's stack frame. */ /* Information recorded for each format spec. */ struct info commit 1fae14a022f8ec81eb2ed3f3972cdecf2b428ac5 Author: Paul Eggert Date: Sat May 16 23:30:53 2026 -0700 Streamline styled_format aux allocation * src/editfns.c (styled_format): Streamline allocation of auxiliary tables, by allocating them all in one go rather than via separate alloca / mallocs. diff --git a/src/editfns.c b/src/editfns.c index 52c45d76074..3a1eb3e860a 100644 --- a/src/editfns.c +++ b/src/editfns.c @@ -3490,29 +3490,34 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) CHECK_STRING (args[0]); bool multibyte_format = STRING_MULTIBYTE (args[0]); ptrdiff_t formatlen = SBYTES (args[0]); - char *format_start = SAFE_ALLOCA (formatlen + 1); - memcpy (format_start, SSDATA (args[0]), formatlen + 1); bool fmt_props = !!string_intervals (args[0]); /* Upper bound on number of format specs. Each uses at least 2 chars. */ ptrdiff_t nspec_bound = SCHARS (args[0]) >> 1; - /* Allocate the info and discarded tables. */ - ptrdiff_t info_size, alloca_size; - if (ckd_mul (&info_size, nspec_bound, sizeof *info) - || ckd_add (&alloca_size, formatlen, info_size) - || SIZE_MAX < alloca_size) + /* Allocate auxiliary tables in one go, in the order: + spec_arguments, info, format_start, discarded. + Because nspec_bound <= formatlen / 2, EXTRA is an upper bound on + (nspec_bound * sizeof *info {for info} + formatlen {for + format_start} + formatlen {for discarded}). */ + ptrdiff_t extra; + if (ckd_mul (&extra, formatlen, 2 + (sizeof *info + 1) / 2)) memory_full (SIZE_MAX); - info = SAFE_ALLOCA (alloca_size); /* One argument belonging to each spec; but needs to be allocated separately so GC doesn't free the strings (bug#75754). */ Lisp_Object *spec_arguments; - SAFE_ALLOCA_LISP (spec_arguments, nspec_bound); - /* discarded[I] is 1 if byte I of the format - string was not copied into the output. - It is 2 if byte I was not the first byte of its character. */ - char *discarded = (char *) &info[nspec_bound]; - memset (discarded, 0, formatlen); + SAFE_ALLOCA_LISP_EXTRA (spec_arguments, nspec_bound, extra); + /* The info table. */ + static_assert (alignof (struct info) <= alignof (Lisp_Object)); + info = (struct info *) &spec_arguments[nspec_bound]; + /* The format string's bytes, sans trailing '\0'. */ + char *format_start = memcpy (&info[nspec_bound], + SSDATA (args[0]), formatlen); + /* discarded[I] is: + 1 if byte I of the format string was not copied into the output. + 2 if byte I was not the first byte of its character. + 0 otherwise. */ + char *discarded = memset (&format_start[formatlen], 0, formatlen); /* Try to determine whether the result should be multibyte. This is not always right; sometimes the result needs to be multibyte @@ -4397,7 +4402,7 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) } return_val: - /* If we allocated BUF or INFO with malloc, free it too. */ + /* If we allocated BUF or auxiliary tables with malloc, free it too. */ SAFE_FREE (); return val; commit 84e646f0b320bbb3560706cf588d4cc3a50f4abd Author: Eli Zaretskii Date: Sat May 16 18:18:54 2026 +0300 ; * etc/NEWS: Fix last change. diff --git a/etc/NEWS b/etc/NEWS index 1224f079ca0..795ac6f5c3d 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -39,12 +39,12 @@ applies, and please also update docstrings as needed. ** Package --- -*** Package-vc can copy local changes from existing installations -When installing the latest release of a package, package-vc will propose -copying files from an existing, tarball installation of the same package -with the same version. This is useful if you have started making local -changes to your tarball installation, and then decide to check out the -repository to prepare a patch for the package maintainer. +*** Package-vc can copy local changes from existing installations. +When installing the latest release of a package, 'package-vc' will +propose copying files from an existing tarball installation of the same +package with the same version. This is useful if you have started +making local changes to your tarball installation, and then decided to +check out the repository to prepare a patch for the package maintainer. * New Modes and Packages in Emacs 32.1 commit b13450973abb19b2bf0d7a96516ce23c385e7713 Author: Philip Kaludercic Date: Sat May 16 16:31:13 2026 +0200 Copy changes from tarballs when installing VC packages * etc/NEWS: Document change. * lisp/emacs-lisp/package-vc.el (package-vc--clone): Implement logic to find and copy files from a previous installation. (package-vc-install): Indicate the effect of the prefix argument in the prompt. diff --git a/etc/NEWS b/etc/NEWS index 73a4ad72180..1224f079ca0 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -36,6 +36,16 @@ applies, and please also update docstrings as needed. * Changes in Specialized Modes and Packages in Emacs 32.1 +** Package + +--- +*** Package-vc can copy local changes from existing installations +When installing the latest release of a package, package-vc will propose +copying files from an existing, tarball installation of the same package +with the same version. This is useful if you have started making local +changes to your tarball installation, and then decide to check out the +repository to prepare a patch for the package maintainer. + * New Modes and Packages in Emacs 32.1 diff --git a/lisp/emacs-lisp/package-vc.el b/lisp/emacs-lisp/package-vc.el index ff39786e8bd..2641fafdcd9 100644 --- a/lisp/emacs-lisp/package-vc.el +++ b/lisp/emacs-lisp/package-vc.el @@ -725,7 +725,25 @@ attribute in PKG-SPEC." ;; Check out the latest release if requested (when (eq rev :last-release) (if-let* ((release-rev (package-vc--release-rev pkg-desc))) - (vc-retrieve-tag dir release-rev) + (progn + (vc-retrieve-tag dir release-rev) + (when-let* ((vers (version-to-list + (lm-package-version (package-vc--main-file pkg-desc)))) + (prev-desc (package-get-descriptor + name 'installed + (lambda (desc) + (version-list-= (package-desc-version desc) + vers)))) + (_ (yes-or-no-p "Copy files from previous installation?"))) + (let* ((remove (seq-remove + #'file-exists-p + (let ((default-directory dir)) + (mapcar #'expand-file-name '("REAME-elpa")))))) + (copy-directory + (file-name-as-directory (package-desc-dir prev-desc)) + (file-name-as-directory dir) + nil 'parents 'copy-contents) + (mapc #'delete-file remove)))) (message "No release revision was found, continuing..."))))) (defvar package-vc-non-code-file-names @@ -941,7 +959,10 @@ installs takes precedence." ;; symbols for completion. (package-vc--archives-initialize) (let* ((name-or-url (package-vc--read-package-name - "Fetch and install package: " t)) + (if current-prefix-arg + "Fetch and install latest release of package: " + "Fetch and install package: ") + t)) (name (file-name-base (directory-file-name name-or-url)))) (when (string-empty-p name) (user-error "Empty package name")) commit 407b5ce7ab29ecb2e2c2150d1b4f8d4c1c51c92b Merge: 21cda148c79 cf96e9cb5a5 Author: Eli Zaretskii Date: Sat May 16 07:24:17 2026 -0400 Merge from origin/emacs-31 cf96e9cb5a5 ; Fix byte-compilation warnings in non-Tree-Sitter builds 23575adc7be ; * doc/lispref/variables.texi (Local Variables): Fix typ... 8b6fb2f6465 ; * doc/lispref/variables.texi (Local Variables): Fix 'na... d3c72b83890 ; * src/xdisp.c (display_line): Fix commentary (bug#80693). a981517b72e Fill margins with 'margin' face on truncated screen lines 8e374990357 ; * doc/lispref/os.texi (Init File): Fix markup (bug#81049). f4c326c378a ; * src/sfnt.c (sfnt_read_cmap_format_12): Assert there's... bf89ee6d078 ; * etc/PROBLEMS: Cursor not shown on Windows with system... 20500d62006 ; htmlfontify: Handle 'reset' face attribute value (bug#8... d0d657fa902 ; Minor Tramp cleanup 93ea0d7d289 ; Improve documentation of VC commands in Dired 318084829c5 Eglot: adjust reference to completion frontends in manual 2a166c2dbdb Eldoc: display documentation in visual-line-mode aba60ad0c5b Eglot: prefer markdown-ts-view-mode for markup rendering ... 689c3bd5088 Use 'read-multiple-choice' in 'markdown-ts-mode' (bug#81027) 71809ee5df5 Fix 'markdown-ts-code-span' face (bug#81026) 286833e401d Add read-only 'markdown-ts-view-mode' (bug#81023) b39c123490b Fix strikethrough in 'markdown-ts-mode' (bug#80991) 0be998d4bc0 Fix code-span in headings in 'markdown-ts-mode' (bug#80979) a00beb3a31b Make 'markdown-ts-inline-images' buffer local and test fo... a0c05029fd1 * etc/NEWS: Mention new user option tramp-propagate-emacs... 2e71d2c709f Propagate EMACSCLIENT_TRAMP to remote hosts with Tramp ff96db93f23 keyboard-tests.el: Try and fix the failure on EMBA ce3098752cf doc: Remove long obsolete references to `package-initialize` 9bc04b001ac vc-next-action: Call vc-delete-file on FILESET-ONLY-FILES 13039e3442b ; touch-up last commit: copyright and comments c2a24dcec8b ; update msys2 build helper for Emacs 31 & UCRT 3630baae720 hideshow: Support new 'margin' face for margin indicators... 20d17df3f4f Use the new 'margin' face in Flymake (bug#80693) 07f2bbc905d vc-dir-resynch-file: Pass down non-truename'd FILE commit 21cda148c7918df35f344823724099f2c5128411 Merge: 87e4687749f a8f67a1f067 Author: Eli Zaretskii Date: Sat May 16 07:17:51 2026 -0400 ; Merge from origin/emacs-31 The following commit was skipped: a8f67a1f067 Change ERC version for Emacs 31 to 5.6.2.31.1 commit 87e4687749f29a3495fc335e99991837b8170725 Merge: 025ecf9e7b4 7eab6ef3cee Author: Eli Zaretskii Date: Sat May 16 07:17:51 2026 -0400 Merge from origin/emacs-31 7eab6ef3cee Fix 'sgml-parse-tag-backward' to handle tags in comments 09dc864b0b8 Fix eww-submit for forms with no action (bug#80918) 0e7a24d9313 * lisp/progmodes/hideshow.el (hs--set-variable): Use 'set... f12b01582db Fix Completions buffer disappearing with tmm-menubar (bug... 519fd832111 Fix secrets.el when Emacs is a flatpak 9e4ea934f23 Fix 'prepare-user-lisp' to follow symlinks e613e38021e Update "timeout" to 2.1.6 196fd80689e [GTK3, HiDPI] Fix width/height round-trip through Configu... acc07f1a030 [GTK3] On Expose, repaint the border before the content 5323eebcffc Test read-passwd behavior (bug#80838) 01c5990dd06 Fix nested read-passwd calls (bug#80838) 027043df257 ; * lisp/gnus/message.el (message-server-alist): Doc fix ... 3b608b233ed Fix terminal emulation of "ESC [ K" sequence 6a605c65a83 Fix vertical-motion across overlay strings with embedded ... e4d529c67b6 ; Fix last change d54faa0f1bf Mark gnus-dbus.el as obsolete 9bf2a19bb21 Move gnus-dbus.el to obsolete/gnus-dbus.el 984024daf3c Gnus: Use new sleep library d7c130972e0 ; * lisp/term/pgtk-win.el (icon-map-list): Fix :type. 5579893ed7c ; Don't block/unblock input in text_extents methods 547b1ee7b6d Fix Rmail behavior wrt globalized minor modes 6ba05106f4e Fix display images in the display margins 56f27dd9f06 Eglot: fix eglot--sig-info with non-UTF-32 positionEncoding 543d8a7a9d7 [NS] Fix deprecated variable (bug#80985) # Conflicts: # etc/NEWS commit cf96e9cb5a5a03fc17f3d11c80c9dc482a58107d Author: Eli Zaretskii Date: Sat May 16 14:13:56 2026 +0300 ; Fix byte-compilation warnings in non-Tree-Sitter builds * lisp/progmodes/eglot.el (treesit-grammar-location): * lisp/treesit.el (treesit-grammar-location): Declare. diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index bf851684c90..3d2c267650e 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -720,6 +720,7 @@ This can be useful when using docker to run a language server.") (if (>= emacs-major-version 27) (executable-find command remote) (executable-find command))) +(declare-function treesit-grammar-location "treesit.c") (defun eglot--accepted-formats () (if (and (not eglot-prefer-plaintext) (or (fboundp 'gfm-view-mode) diff --git a/lisp/treesit.el b/lisp/treesit.el index 5253439a9dd..e2e62bb71a2 100644 --- a/lisp/treesit.el +++ b/lisp/treesit.el @@ -133,6 +133,8 @@ in a Emacs not built with tree-sitter library." (declare-function treesit-parser-remove-notifier "treesit.c") + (declare-function treesit-grammar-location "treesit.c") + (defvar treesit-thing-settings) (defvar treesit-major-mode-remap-alist) (defvar treesit-extra-load-path))) commit 23575adc7be741e8d859227ad78a605b391ae46e Author: Eli Zaretskii Date: Sat May 16 13:43:53 2026 +0300 ; * doc/lispref/variables.texi (Local Variables): Fix types (bug#81004). diff --git a/doc/lispref/variables.texi b/doc/lispref/variables.texi index 10d0927312e..8779cf88917 100644 --- a/doc/lispref/variables.texi +++ b/doc/lispref/variables.texi @@ -282,7 +282,7 @@ previous example is equivalent to using nested @code{let} bindings: @end defspec -@defspec letrec (bindings@dots{}) forms@dots{} +@defmac letrec (bindings@dots{}) forms@dots{} This special form is like @code{let*}, but all the variables are bound before any of the local values are computed. The values are then assigned to the locally bound variables. This is useful only when @@ -299,11 +299,11 @@ being run once: (remove-hook 'post-command-hook hookfun)))) (add-hook 'post-command-hook hookfun)) @end lisp -@end defspec +@end defmac @cindex dynamic binding, temporarily @cindex dynamic let-binding -@defspec dlet (bindings@dots{}) forms@dots{} +@defmac dlet (bindings@dots{}) forms@dots{} This special form is like @code{let}, but it binds all variables dynamically. This is rarely useful---you usually want to bind normal variables lexically, and special variables (i.e., variables that are @@ -315,7 +315,7 @@ that certain variables are dynamically bound (@pxref{Dynamic Binding}), but it's impractical to @code{defvar} these variables. @code{dlet} will temporarily make the bound variables special, execute the forms, and then make the variables non-special again. -@end defspec +@end defmac @defmac named-let name bindings &rest body This special form is a looping construct inspired from the @@ -353,7 +353,7 @@ itself, as is the case in the recursive call to @code{sum} above. @code{named-let} can be used only when lexical-binding is enabled. @xref{Lexical Binding}. -@end defspec +@end defmac Here is a complete list of the other facilities that create local bindings: commit 8b6fb2f64655be62fb42dbeaf8c6ea945acf6a11 Author: Eli Zaretskii Date: Sat May 16 13:16:09 2026 +0300 ; * doc/lispref/variables.texi (Local Variables): Fix 'named-let'. diff --git a/doc/lispref/variables.texi b/doc/lispref/variables.texi index abb2c883f78..10d0927312e 100644 --- a/doc/lispref/variables.texi +++ b/doc/lispref/variables.texi @@ -317,7 +317,7 @@ Binding}), but it's impractical to @code{defvar} these variables. the forms, and then make the variables non-special again. @end defspec -@defspec named-let name bindings &rest body +@defmac named-let name bindings &rest body This special form is a looping construct inspired from the Scheme language. It is similar to @code{let}: It binds the variables in @var{bindings}, and then evaluates @var{body}. However, commit d3c72b83890f6893ff9603d19d0e615d678fcb1f Author: Eli Zaretskii Date: Sat May 16 11:54:43 2026 +0300 ; * src/xdisp.c (display_line): Fix commentary (bug#80693). diff --git a/src/xdisp.c b/src/xdisp.c index ca8fa5dffee..c1d6fedb553 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -26643,13 +26643,11 @@ display_line (struct it *it, int cursor_vpos) /* If the default face is remapped or the 'margin' face has a non-default background, and the window has display margins, - and no glyphs were written yet to the margins on this screen - line, fill the margin area so that the margins use the - correct background. Placed here, after the if/else-if chain - above, so it fires for all three truncation paths: TTY/no-fringe - truncation glyph, GUI newline-overflow-into-fringe, and GUI - regular truncation where the indicator is drawn as a fringe - bitmap. */ + extend the face in the margin area so that the margins use + the correct background. This handles all three truncation + paths: TTY/no-fringe truncation glyph, GUI + newline-overflow-into-fringe, and GUI regular truncation + where the indicator is drawn as a fringe bitmap. */ { int margin_face_id = lookup_basic_face (it->w, it->f, MARGIN_FACE_ID); commit a981517b72e8d5e07a8ea6cf31b2fd6dcda4ec1f Author: Andrea Alberti Date: Wed May 13 17:45:54 2026 +0200 Fill margins with 'margin' face on truncated screen lines * src/xdisp.c (display_line): Remove the unnecessary condition that row->used of the margin areas is zero, for when we call 'extend_face_to_end_of_line'. (Bug#80693) diff --git a/src/xdisp.c b/src/xdisp.c index b485d9ccf40..ca8fa5dffee 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -26657,10 +26657,8 @@ display_line (struct it *it, int cursor_vpos) != DEFAULT_FACE_ID || FACE_FROM_ID (it->f, margin_face_id)->background != FRAME_BACKGROUND_PIXEL (it->f)) - && ((WINDOW_LEFT_MARGIN_WIDTH (it->w) > 0 - && it->glyph_row->used[LEFT_MARGIN_AREA] == 0) - || (WINDOW_RIGHT_MARGIN_WIDTH (it->w) > 0 - && it->glyph_row->used[RIGHT_MARGIN_AREA] == 0))) + && (WINDOW_LEFT_MARGIN_WIDTH (it->w) > 0 + || WINDOW_RIGHT_MARGIN_WIDTH (it->w) > 0)) extend_face_to_end_of_line (it); } commit 8e374990357426c24d870f65051e591ca1c75b21 Author: Manuel Giraud Date: Fri May 15 12:45:39 2026 +0200 ; * doc/lispref/os.texi (Init File): Fix markup (bug#81049). diff --git a/doc/lispref/os.texi b/doc/lispref/os.texi index 72a1fdc7878..809fa36098a 100644 --- a/doc/lispref/os.texi +++ b/doc/lispref/os.texi @@ -411,8 +411,8 @@ that the early init file is loaded much earlier during the startup process, so you can use it to customize some things that are initialized before loading the regular init file. For example, you can customize the process of initializing the package system, by -setting variables such as @var{package-load-list} or -@var{package-enable-at-startup}. @xref{Package Installation,,, +setting variables such as @code{package-load-list} or +@code{package-enable-at-startup}. @xref{Package Installation,,, emacs,The GNU Emacs Manual}. @cindex default init file commit f4c326c378aa25251ebd8e29db4d1ce42d26eaaa Author: Eli Zaretskii Date: Fri May 15 15:00:35 2026 +0300 ; * src/sfnt.c (sfnt_read_cmap_format_12): Assert there's no overflow. diff --git a/src/sfnt.c b/src/sfnt.c index f778179a5ff..ab6a2d5e7bc 100644 --- a/src/sfnt.c +++ b/src/sfnt.c @@ -736,6 +736,7 @@ sfnt_read_cmap_format_12 (int fd, return NULL; /* Allocate a buffer of sufficient size. */ + eassert (length < UINT32_MAX - sizeof *format12); format12 = xmalloc (length + sizeof *format12); format12->format = header->format; format12->reserved = header->length; commit bf89ee6d078c684bb0e527a057eb871b1d8d81ee Author: Eli Zaretskii Date: Fri May 15 10:20:31 2026 +0300 ; * etc/PROBLEMS: Cursor not shown on Windows with system caret (bug#81047). diff --git a/etc/PROBLEMS b/etc/PROBLEMS index 340e99c0425..2ae82292e04 100644 --- a/etc/PROBLEMS +++ b/etc/PROBLEMS @@ -3262,6 +3262,30 @@ To turn the Windows Magnifier off, click "Start->All Programs", or "Accessibility" and click "Magnifier". In the Magnifier Settings dialog that opens, click "Exit". +** Cursor is invisible, or appears at times and then disappears + +This is known to happen if 'w32-use-visible-system-caret' is non-nil. +That variable is nil by default, but if your system has the "Speech +Recognition" feature enabled, Emacs automatically sets this variable +non-nil at startup to allow the screen reader to read the relevant part +of the Emacs display and dictate it. + +To turn this off on modern Windows systems, go to "Settings -> +Accessibility -> Speech", and turn off "Voice access". If you need to +leave this accessibility feature turned on, you can alternatively set +'w32-use-visible-system-caret' to the nil value in your init file: + + (setq w32-use-visible-system-caret nil) + +If you do need to see the system caret in Emacs windows, you can instead +work around this problem by disabling double-buffering in your init +file: + + (set-frame-parameter nil 'inhibit-double-buffering nil) + +Note that inhibiting double-buffering might cause the Emacs display to +flicker in some cases. + ** Problems with mouse-tracking and focus management There are problems with display if mouse-tracking is enabled and the commit 025ecf9e7b45aa7a8d5825559f0e6226bfebb9d2 Author: Philip Kaludercic Date: Wed May 13 10:48:38 2026 +0200 * lisp/net/rcirc.el (rcirc-monospace-text): Inherit 'fixed-pitch' diff --git a/lisp/net/rcirc.el b/lisp/net/rcirc.el index c067c2472bb..a7cf5c0db37 100644 --- a/lisp/net/rcirc.el +++ b/lisp/net/rcirc.el @@ -3933,7 +3933,7 @@ PROCESS is the process object for the current connection." :group 'faces) (defface rcirc-monospace-text - '((t :family "Monospace")) + '((t :inherit fixed-pitch)) "Face used for monospace text in messages.") (defface rcirc-my-nick ; font-lock-function-name-face commit 0fb9d096e38a0d1317fd819f6bccca6cdeb86e77 Author: Richard M. Stallman Date: Wed Apr 22 15:41:40 2026 -0400 The summary scan should include the current msg and run to end. * lisp/mail/rmailsum.el (rmail-new-summary-1): If we are before, or close to, msg number rmail-summary-starting-message, start the search a little before there. diff --git a/lisp/mail/rmailsum.el b/lisp/mail/rmailsum.el index 8bfd3d91fa0..52dd9aaaf1e 100644 --- a/lisp/mail/rmailsum.el +++ b/lisp/mail/rmailsum.el @@ -789,7 +789,11 @@ message." (sumbuf (rmail-get-create-summary-buffer))) ;; Scan the messages, getting their summary strings ;; and putting the list of them in SUMMARY-MSGS. - (let ((msgnum rmail-summary-starting-message) + (let ((msgnum (min rmail-summary-starting-message + ;; If we are before, or close to, msg number + ;; rmail-summary-starting-message, + ;; start the search a little before there. + (max 1 (floor (* .9 rmail-current-message))))) (main-buffer (current-buffer)) (total rmail-total-messages) (inhibit-read-only t)) commit 20500d62006c0302f686f036821ec5d8e4bb808b Author: Eshel Yaron Date: Thu May 14 17:07:30 2026 +0200 ; htmlfontify: Handle 'reset' face attribute value (bug#81032) * lisp/htmlfontify.el (hfy-face-to-style-i): Add special handling for the 'reset' face attribute value. diff --git a/lisp/htmlfontify.el b/lisp/htmlfontify.el index e4d838d2968..7298dd447e8 100644 --- a/lisp/htmlfontify.el +++ b/lisp/htmlfontify.el @@ -1000,6 +1000,12 @@ to be merged by the user - `hfy-flatten-style' should do this." parent (hfy-face-to-style-i (hfy-face-attr-for-class v hfy-display-class)))))) + ;; The special value `reset' stands for the value of the + ;; corresponding attribute (KEY) of the ‘default’ face. + (when (eq val 'reset) + (setq val (plist-get + (hfy-face-attr-for-class 'default hfy-display-class) + key))) (setq this (if val (cl-case key (:family (hfy-family val)) commit d0d657fa9027a1a4fa4ce151f65f3bcac1e2d535 Author: Michael Albinus Date: Thu May 14 12:31:04 2026 +0200 ; Minor Tramp cleanup * lisp/net/tramp.el (tramp-unquote-shell-quote-argument): Do not expand remote file names w/o a localname. * test/lisp/net/tramp-tests.el (tramp--test-supports-environment-variables-p): New defun. (tramp-test33-environment-variables): Use it. diff --git a/lisp/net/tramp.el b/lisp/net/tramp.el index ffcf8f4929c..fc897fb2a7c 100644 --- a/lisp/net/tramp.el +++ b/lisp/net/tramp.el @@ -7406,7 +7406,8 @@ T1 and T2 are time values (as returned by `current-time' for example)." Suppress `shell-file-name'. This is needed on w32 systems, which would use a wrong quoting for local file names. See `w32-shell-name'." (let (shell-file-name) - (shell-quote-argument (file-name-unquote s)))) + ;; Do not expand remote file names w/o a localname. + (shell-quote-argument (file-name-unquote s 'top)))) ;; Currently (as of Emacs 20.5), the function `shell-quote-argument' ;; does not deal well with newline characters. Newline is replaced by diff --git a/test/lisp/net/tramp-tests.el b/test/lisp/net/tramp-tests.el index d6eb782df3d..c0ad7205c5d 100644 --- a/test/lisp/net/tramp-tests.el +++ b/test/lisp/net/tramp-tests.el @@ -6524,8 +6524,7 @@ INPUT, if non-nil, is a string sent to the process." "Check that remote processes set / unset environment variables properly." :tags '(:expensive-test) (skip-unless (tramp--test-enabled)) - (skip-unless (tramp--test-sh-p)) - (skip-unless (not (tramp--test-crypt-p))) + (skip-unless (tramp--test-supports-environment-variables-p)) (dolist (this-shell-command-to-string (append @@ -7797,6 +7796,11 @@ This requires restrictions of file name syntax." (tramp--test-sh-p) (tramp--test-smb-p) (tramp--test-sudoedit-p))) +(defun tramp--test-supports-environment-variables-p () + "Return whether setting environment variables is supported." + (and (tramp--test-sh-p) + (not (tramp--test-crypt-p)))) + (defun tramp--test-check-files (&rest files) "Run a simple but comprehensive test over every file in FILES." (dolist (quoted (if (tramp--test-expensive-test-p) '(nil t) '(nil))) commit 93ea0d7d289ad384a5d71aefe9e68551abcffd24 Author: Eli Zaretskii Date: Thu May 14 13:12:21 2026 +0300 ; Improve documentation of VC commands in Dired * doc/emacs/dired.texi (Misc Dired Features): Move VC reference from here... (Operating on Files): ...to here. Add other VC commands supported by Dired. * doc/emacs/maintaining.texi (Old Revisions, VC Change Log): Fix cross-references. diff --git a/doc/emacs/dired.texi b/doc/emacs/dired.texi index 9abef21f459..1991ab0858a 100644 --- a/doc/emacs/dired.texi +++ b/doc/emacs/dired.texi @@ -761,7 +761,7 @@ that operates on the marked files finishes. @cindex operating on files in Dired This section describes the basic Dired commands to operate on one file -or several files. All of these commands are capital letters; all of +or several files. Many of these commands are capital letters; all of them use the minibuffer, either to read an argument or to ask for confirmation, before they act. All of them let you specify the files to manipulate in these ways: @@ -1022,6 +1022,21 @@ single archive anywhere on the file system. The default archive is controlled by the @code{dired-compress-directory-default-suffix} user option. Also see @code{dired-compress-files-alist}. +@cindex Dired and version control +@findex dired-vc-next-action +@kindex C-x v v @r{(Dired)} +@item C-x v v +@itemx C-x v = @r{(Dired)} +@itemx C-x v l @r{(Dired)} + If the directory you are visiting is under version control +(@pxref{Version Control}), then the normal VC commands will operate on +the selected files. For example, @kbd{C-x v v} invokes +@code{dired-vc-next-action}, which does the same as +@code{vc-next-action} in a buffer visiting a file under version control +(@pxref{Basic VC Editing}). Similarly, @kbd{C-x v =} shows the diffs +between the marked files and their committed versions, and @code{C-x v l} +shows the VC change history for the marked files. + @findex epa-dired-do-decrypt @kindex :d @r{(Dired)} @cindex decrypting files (in Dired) @@ -1947,11 +1962,6 @@ file/directory listings. To change this, customize the options @code{dired-hide-details-hide-symlink-targets} and @code{dired-hide-details-hide-information-lines}, respectively. -@cindex Dired and version control - If the directory you are visiting is under version control -(@pxref{Version Control}), then the normal VC diff and log commands -will operate on the selected files. - @findex dired-compare-directories The command @kbd{M-x dired-compare-directories} is used to compare the current Dired buffer with another directory. It marks all the files diff --git a/doc/emacs/maintaining.texi b/doc/emacs/maintaining.texi index 2400440f951..a1825c5e515 100644 --- a/doc/emacs/maintaining.texi +++ b/doc/emacs/maintaining.texi @@ -862,7 +862,7 @@ v} to check-out the file and start editing it. Compare the work files in the current VC fileset with the versions you started from (@code{vc-diff}). With a prefix argument, prompt for two revisions of the current VC fileset and compare them. You can also -call this command from a Dired buffer (@pxref{Dired}). +call this command from a Dired buffer (@pxref{Operating on Files}). @ifnottex @item M-x vc-ediff @@ -1137,9 +1137,9 @@ Buffer}). If invoked from a buffer visiting a file, the current fileset consists of that single file, and point in the displayed @file{*vc-change-log*} buffer is centered at the revision of that file. If invoked from a VC Directory buffer (@pxref{VC Directory -Mode}) or from a Dired buffer (@pxref{Dired}), the fileset consists of -all the marked files, defaulting to the file shown on the current line -in the directory buffer if no file is marked. +Mode}) or from a Dired buffer (@pxref{Operating on Files}), the fileset +consists of all the marked files, defaulting to the file shown on the +current line in the directory buffer if no file is marked. If the fileset includes one or more directories, the resulting @file{*vc-change-log*} buffer shows a short log of changes (one line commit 318084829c580e7579905b281d8373ed73f1f1b1 Author: João Távora Date: Thu May 14 10:53:04 2026 +0100 Eglot: adjust reference to completion frontends in manual * doc/misc/eglot.texi (Eglot Features): Rework. diff --git a/doc/misc/eglot.texi b/doc/misc/eglot.texi index 59cecbbc0c4..d501fe32d5d 100644 --- a/doc/misc/eglot.texi +++ b/doc/misc/eglot.texi @@ -474,11 +474,11 @@ Code reformatting via the @code{eglot-format} and related commands supported and is activated automatically as you type. @item -If a completion package such as the Company package (a popular -third-party completion package providing @code{company-mode}), is -installed, Eglot enhances it by providing completion candidates based on -the language server's analysis of the source code. (Company can be -installed from GNU ELPA.) +Eglot enhances symbol completion front-ends by providing completion +candidates based on the language server's understanding of the source +code (@pxref{Symbol Completion,,, emacs, GNU Emacs Manual}). The +Company package, installable from GNU ELPA, is a popular package known +to work well with Eglot. @item If YASnippet, a popular third-party package for automatic insertion of commit 2a166c2dbdb9f0406828f44827f109d0fb8a187f Author: João Távora Date: Thu May 14 09:35:38 2026 +0100 Eldoc: display documentation in visual-line-mode Documentation is overwhelmingly prose and intended to be viewed, not edited. Using visual-line-mode allows members of 'eldoc-doc-functions' to provide long lines that correctly fill to the window width. * lisp/emacs-lisp/eldoc.el (eldoc--format-doc-buffer): Use visual-line-mode. diff --git a/lisp/emacs-lisp/eldoc.el b/lisp/emacs-lisp/eldoc.el index 77ff567954f..55f9fb1988b 100644 --- a/lisp/emacs-lisp/eldoc.el +++ b/lisp/emacs-lisp/eldoc.el @@ -530,6 +530,7 @@ If INTERACTIVE, display it. Else, return said buffer." (things-reported-on)) (special-mode) (erase-buffer) + (visual-line-mode) (setq-local nobreak-char-display nil) (cl-loop for (docs . rest) on docs for (this-doc . plist) = docs commit aba60ad0c5be8f657230649df9b78db534027a20 Author: João Távora Date: Fri May 8 22:22:49 2026 +0100 Eglot: prefer markdown-ts-view-mode for markup rendering (bug#80127) Eglot previously needed gfm-view-mode from markdown-mode.el to render Markdown from LSP servers. It now prefers markdown-ts-view-mode when available. * lisp/progmodes/eglot.el (eglot--accepted-formats): Recognize markdown-ts-view-mode as a Markdown renderer. (eglot--format-markup): Rework with cl-labels; prefer markdown-ts-view-mode over gfm-view-mode. * doc/misc/eglot.texi (Eglot Features): Don't mention markdown-mode directly. * etc/EGLOT-NEWS: Mention change diff --git a/doc/misc/eglot.texi b/doc/misc/eglot.texi index 3deabb894c0..59cecbbc0c4 100644 --- a/doc/misc/eglot.texi +++ b/doc/misc/eglot.texi @@ -488,12 +488,11 @@ completion package to instantiate these snippets using YASnippet. (YASnippet can be installed from GNU ELPA.) @item -If the popular third-party package @code{markdown-mode} is installed, -and the server provides at-point documentation formatted as Markdown in +When the server provides at-point documentation formatted as Markdown in addition to plain text, Eglot arranges for the ElDoc package to enrich -this text with fontifications and other nice formatting before -displaying it to the user. This makes the documentation shown by ElDoc -look nicer on display. +this text with fontifications, hyperlinks and other nice formatting +before displaying it to the user. This makes the documentation shown by +ElDoc look nicer on display. @item In addition to enabling and enhancing other features and packages, Eglot diff --git a/etc/EGLOT-NEWS b/etc/EGLOT-NEWS index 4e01f32cecd..eb4040d107e 100644 --- a/etc/EGLOT-NEWS +++ b/etc/EGLOT-NEWS @@ -32,6 +32,12 @@ New key bindings: 'k' shuts down, 'r' reconnects, 'e' visits the events buffer, 'w' shows workspace configuration, and 'RET' invokes 'eglot-describe-connection'. +** Eglot uses new built-in 'markdown-ts-mode' of Emacs 31 (bug#80127) + +This means that on newer versions of Emacs the external +'markdown-mode.el' package does not need to be installed to render +Markdown content. + * Changes in Eglot 1.23 (2/4/2026) diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index e97b1749b79..bf851684c90 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -721,8 +721,12 @@ This can be useful when using docker to run a language server.") (executable-find command))) (defun eglot--accepted-formats () - (if (and (not eglot-prefer-plaintext) (fboundp 'gfm-view-mode)) - ["markdown" "plaintext"] ["plaintext"])) + (if (and (not eglot-prefer-plaintext) + (or (fboundp 'gfm-view-mode) + (and (fboundp 'markdown-ts-view-mode) + (treesit-grammar-location 'markdown)))) + ["markdown" "plaintext"] + ["plaintext"])) (defconst eglot--uri-path-allowed-chars (let ((vec (copy-sequence url-path-allowed-chars))) @@ -2225,48 +2229,51 @@ Doubles as an indicator of snippet support." (unless (bound-and-true-p yas-minor-mode) (yas-minor-mode 1)) (apply #'yas-expand-snippet args))))) -(defun eglot--format-markup (markup &optional mode) +(cl-defun eglot--format-markup + (markup &optional mode + &aux string lang render extract + (built-in (and (fboundp 'markdown-ts-view-mode) + (treesit-grammar-location 'markdown)))) "Format MARKUP according to LSP's spec. -MARKUP is either an LSP MarkedString or MarkupContent object." - (let (string render-mode language) - (cond ((stringp markup) - (setq string markup - render-mode (or mode 'gfm-view-mode))) - ((setq language (plist-get markup :language)) - ;; Deprecated MarkedString - (setq string (concat "```" language "\n" - (plist-get markup :value) "\n```") - render-mode (or mode 'gfm-view-mode))) - (t - ;; MarkupContent - (setq string (plist-get markup :value) - render-mode - (or mode - (pcase (plist-get markup :kind) - ("markdown" 'gfm-view-mode) - ("plaintext" 'text-mode) - (_ major-mode)))))) +MARKUP is either an LSP MarkedString or MarkupContent object. +If MODE, force MODE to be used for fontifying MARKUP." + (cl-labels + ((gfm-extract () + ;; For `gfm-view-mode', the `invisible' regions are set to + ;; `markdown-markup'. Set them to 't' on extraction, since + ;; this has actual meaning in the "*eldoc*" buffer where we're + ;; taking this string (#bug79552). + (cl-loop with inhibit-read-only = t + for from = (point-min) then to + while (< from (point-max)) + for inv = (get-text-property from 'invisible) + for to = (or (next-single-property-change from 'invisible) + (point-max)) + when inv + do (put-text-property from to 'invisible t))) + (calc2 (forced-mode) + (cond + (forced-mode `(,forced-mode)) + (built-in `(,#'markdown-ts-view-mode)) + ((fboundp 'gfm-view-mode) `(,#'gfm-view-mode #'gfm-extract)) + (t `(#'text-mode)))) + (calc (s &optional (forced-mode mode) &aux (x (calc2 forced-mode))) + (setq string s render (car x) extract (or (cadr x) #'buffer-string)))) + (cond ((stringp markup) (calc string)) ; plain string + ((setq lang (plist-get markup :language)) ; deprecated MarkedString + (calc (format "```%s\n%s\n```" lang (plist-get markup :value)))) + (t (calc (plist-get markup :value) ; Assume MarkupContent + (or mode (pcase (plist-get markup :kind) + ("markdown" nil) + ("plaintext" 'text-mode) + (_ major-mode)))))) (with-temp-buffer (setq-local markdown-fontify-code-blocks-natively t) (insert string) - (let ((inhibit-message t) - (message-log-max nil)) - (ignore-errors (delay-mode-hooks (funcall render-mode))) + (let ((inhibit-message t) (message-log-max nil)) + (ignore-errors (delay-mode-hooks (funcall render))) (font-lock-ensure) - (goto-char (point-min)) - (let ((inhibit-read-only t)) - ;; If `render-mode' is `gfm-view-mode', the `invisible' - ;; regions are set to `markdown-markup'. Set them to 't' - ;; instead, since this has actual meaning in the "*eldoc*" - ;; buffer where we're taking this string (#bug79552). - (cl-loop for from = (point) then to - while (< from (point-max)) - for inv = (get-text-property from 'invisible) - for to = (or (next-single-property-change from 'invisible) - (point-max)) - when inv - do (put-text-property from to 'invisible t))) - (string-trim (buffer-string)))))) + (string-trim (funcall extract)))))) (defun eglot--read-server (prompt &optional dont-if-just-the-one) "Read a running Eglot server from minibuffer using PROMPT. commit 689c3bd5088c385fda6ce8054eb659737080d694 Author: Rahul Martim Juliato Date: Tue May 12 00:16:55 2026 -0300 Use 'read-multiple-choice' in 'markdown-ts-mode' (bug#81027) Replace the "c" character-prompt interactive spec with 'read-multiple-choice', which presents named options instead of requiring users to decode the prompt string and type a single character. * lisp/textmodes/markdown-ts-mode.el (markdown-ts-table-align-column): Use 'read-multiple-choice'. Adjust ALIGN docstring punctuation. * lisp/textmodes/markdown-ts-mode-x.el (markdown-ts-toc-insert-template): Use 'read-multiple-choice'. diff --git a/lisp/textmodes/markdown-ts-mode-x.el b/lisp/textmodes/markdown-ts-mode-x.el index 1327191828e..1296e3567a3 100644 --- a/lisp/textmodes/markdown-ts-mode-x.el +++ b/lisp/textmodes/markdown-ts-mode-x.el @@ -736,7 +736,11 @@ is nil and the command is run interactively, prompt for a template. The basic template uses all defaults and is likely the best choice for most uses. The complete template illustrates all parameters set to their defaults and is useful as a starting point to customize a table." - (interactive "cTemplate [b]asic [c]omplete:") + (interactive + (list (car (read-multiple-choice + "Table of contents template" + '((?b "basic") + (?c "complete")))))) (pcase char (?b (insert "\n" diff --git a/lisp/textmodes/markdown-ts-mode.el b/lisp/textmodes/markdown-ts-mode.el index 12e2e7c37f9..d2f4fcd8fa7 100644 --- a/lisp/textmodes/markdown-ts-mode.el +++ b/lisp/textmodes/markdown-ts-mode.el @@ -4392,13 +4392,18 @@ Note: To compute the column, point must be within the column and cannot be on the leading or trailing whitespace or on a column delimiter. ALIGN can be one of the symbols `left', `center', `right' or nil for -unspecified or the characters l, c, or r. +unspecified, or the characters l, c, or r. If ALIGN is nil, assume unspecified. Make the alignment string a minimum of 5 characters to accommodate Markdown conventions. If point is not at a table, do nothing." - (interactive "cAlign column [l]eft [c]enter [r]ight [u]nspecified:") + (interactive + (list (car (read-multiple-choice + "Align column" '((?l "left") + (?c "center") + (?r "right") + (?u "unspecified")))))) (markdown-ts--barf-if-not-mode 'markdown-ts-table-align-column) (setq align (if (characterp align) (pcase align (?l 'left) (?c 'center) (?r 'right)) commit 71809ee5df5965e304bbbb02828b2de1f89cbd81 Author: Rahul Martim Juliato Date: Tue May 12 00:14:02 2026 -0300 Fix 'markdown-ts-code-span' face (bug#81026) * lisp/textmodes/markdown-ts-mode.el (markdown-ts-code-span): Inherit from 'font-lock-keyword-face' rather than 'font-lock-string-face'. diff --git a/lisp/textmodes/markdown-ts-mode.el b/lisp/textmodes/markdown-ts-mode.el index d57b7aa98ef..12e2e7c37f9 100644 --- a/lisp/textmodes/markdown-ts-mode.el +++ b/lisp/textmodes/markdown-ts-mode.el @@ -639,7 +639,7 @@ newline." "Face for Markdown link destinations (URLs)." :version "31.1") -(defface markdown-ts-code-span '((t (:inherit font-lock-string-face))) +(defface markdown-ts-code-span '((t (:inherit font-lock-keyword-face))) "Face for Markdown inline code spans." :version "31.1") commit 286833e401d7058a8968ee3e023b526b0a9a6883 Author: Rahul Martim Juliato Date: Tue May 12 00:03:16 2026 -0300 Add read-only 'markdown-ts-view-mode' (bug#81023) This new derived mode is intended for consumers that render Markdown content for display rather than editing, such as Eglot and Eldoc when showing documentation popups and buffers. It pre-sets the relevant customizations (markup hidden, inline images on, hard-line-break markup hidden, native code-block fontification, table and code-block context minor modes off), makes the buffer read-only, and uses its own keymap derived from 'special-mode-map' so navigation keys behave like a viewer. A pre-init hook lets callers normalize buffer content before the grammar parses it; 'markdown-ts-add-final-newline' is the default so that markup depending on a terminating newline parses correctly. 'markdown-ts-buffer-string' returns the rendered buffer string with overlay faces flattened into text properties, which is useful for callers that capture the rendered output. Along the way: 'list_marker_parenthesis' is now recognized as an ordered list marker; the strikethrough query is simplified to a single rule; thematic breaks span the window via an ':extend' underline when the face supports it. * lisp/textmodes/markdown-ts-mode.el (markdown-ts-view-mode): New read-only derived mode. (markdown-ts-view-mode-map): New keymap. (markdown-ts-view-mode-pre-init-hook): New hook, defaulting to 'markdown-ts-add-final-newline'. (markdown-ts-mode--initialize): New helper, factored out of 'markdown-ts-mode' so 'markdown-ts-view-mode' can reuse the parser readiness and setup logic after overriding local variables. (markdown-ts-mode): Call 'markdown-ts-mode--initialize'. (markdown-ts-add-final-newline): New function. (markdown-ts-buffer-string): New function. (markdown-ts-unordered-list-marker): New defcustom. (markdown-ts-hard-line-break-backslash) (markdown-ts-hard-line-break-space): Accept the symbol 'hide. (markdown-ts--fontify-hard-line-break): Honor 'hide. (markdown-ts--fontify-atx-heading) (markdown-ts--fontify-setext-heading) (markdown-ts--fontify-atx-delimiter) (markdown-ts--fontify-unordered-list-marker) (markdown-ts--list-item-depth): New functions supporting clean rendering when markup is hidden. (markdown-ts--fontify-thematic-break): Use ':extend' underline span when the face supports it. (markdown-ts--resolve-display-value): Accept non-cons values. (markdown-ts--list-ordered-item-p): Also recognize 'list_marker_parenthesis'. (markdown-ts--range-settings): Mark markdown-inline embed as ':local t'. (markdown-ts--set-up): Create the markdown-inline parser only in the inline setup branch; drop the redundant 'markdown-ts-hide-markup' make-local-variable. (markdown-ts--treesit-settings): Route atx and setext headings to their dedicated fontifiers; route unordered list markers through 'markdown-ts--fontify-unordered-list-marker'; move 'strikethrough' to the simpler paragraph-inline query. diff --git a/lisp/textmodes/markdown-ts-mode.el b/lisp/textmodes/markdown-ts-mode.el index 8646842cfc3..d57b7aa98ef 100644 --- a/lisp/textmodes/markdown-ts-mode.el +++ b/lisp/textmodes/markdown-ts-mode.el @@ -315,6 +315,28 @@ Remote images are skipped by default for security." :version "31.1" :package-version "1.0") +(defcustom markdown-ts-unordered-list-marker '(("● " . "- ") + ("○ " . "- ") + ("◼ " . "- ") + ("• " . "- ")) + "If markup is hidden, display these for an unordered list marker. +Each list item marker's depth in its list controls its selected string +starting at the first element and cycling through the others for deeper +items. The list will be cycle around back to the beginning if there are +insufficient strings to represent deep levels. + +Note that the default strings have trailing spaces. + +Value forms: + - (list (cons (PREFERRED . FALLBACK)) ...): where PREFERRED is used if + its first character passes `char-displayable-p', otherwise FALLBACK. + - nil: display the raw markup." + :type '(choice (repeat (cons (string :tag "Preferred (GUI)") + (string :tag "Fallback (TTY)"))) + (const :tag "Display original markup" nil)) + :version "31.1" + :package-version "1.0") + (defcustom markdown-ts-checked-checkbox '("☑" . "+") "If markup is hidden, display this for a checked task list marker. Value forms: @@ -367,10 +389,14 @@ Consulted only when `markdown-ts-unchecked-checkbox' is the symbol (defcustom markdown-ts-thematic-break-character '(?─ . ?-) "If markup is hidden, display this character for thematic breaks. -It is repeated to fill the window width. +It is repeated to fill the window width. This assumes a static window +width. +You may prefer an `:extend' attribute on the +`markdown-ts-thematic-break' which will span window width dynamically +using an underline, in which case this character is ignored. The value is a cons (PREFERRED . FALLBACK): PREFERRED is used if it passes `char-displayable-p', otherwise FALLBACK is used. -nil displays the raw markup." +Use nil to display the raw markup." :type '(choice (cons (character :tag "Preferred (GUI)") (character :tag "Fallback (TTY)")) (const :tag "Display original markup" nil)) @@ -384,7 +410,8 @@ The value is a cons (PREFERRED . FALLBACK): PREFERRED is used if it passes nil keeps the raw markup." :type '(choice (cons (character :tag "Preferred (GUI)") (character :tag "Fallback (TTY)")) - (const :tag "Display original markup" nil)) + (const :tag "Display original markup" nil) + (const :tag "Hide markup" hide)) :version "31.1" :package-version "1.0") @@ -404,7 +431,8 @@ The value can be: :type '(choice (character :tag "Display specified character (no repetition)") (string :tag "Display specified string (no repetition)") (function :tag "Function from count to display string") - (const :tag "Display original markup" nil)) + (const :tag "Display original markup" nil) + (const :tag "Hide markup" hide)) :version "31.1" :package-version "1.0") @@ -536,6 +564,18 @@ Set to nil to disable the lighter." :version "31.1" :package-version "1.0") +(defcustom markdown-ts-view-mode-pre-init-hook (list #'markdown-ts-add-final-newline) + "Hooks run before `markdown-ts-view-mode` initialization. +Functions on this list are intended to amend buffer content for +`markdown-ts-view-mode' and tree-sitter Markdown grammar compatibility. + +For example, `markdown-ts-add-final-newline' ensures the grammar +correctly parses markup at the end of the buffer that depends on a final +newline." + :type '(hook) + :version "31.1" + :package-version "1.0") + ;;; Faces: (defgroup markdown-ts-faces nil @@ -763,7 +803,6 @@ shadow-colored block." (ts typescript-ts-mode) (yml yaml-ts-mode)) "Extra mappings from code block language tags to major modes. - Entries here are only needed when the language tag in a fenced code block does NOT match the conventional mode name derivation, e.g. the user writes \\=`\\=`\\=`ts instead of \\=`\\=`\\=`typescript, or @@ -796,7 +835,6 @@ conventional font-lock. `markdown-ts-mode' itself is one of them.") (defun markdown-ts--fontify-delimiter (node override start end &rest _) "Fontify delimiter NODE and optionally hide its markup. - NODE is the tree-sitter node representing the delimiter. OVERRIDE, START, and END are passed through to `treesit-fontify-with-override'." @@ -807,6 +845,26 @@ OVERRIDE, START, and END are passed through to (put-text-property (treesit-node-start node) (treesit-node-end node) 'invisible 'markdown-ts--markup))) +(defun markdown-ts--fontify-atx-delimiter (node override start end &rest _) + "Fontify atx_heading delimiter NODE and optionally hide its markup. +NODE is the tree-sitter node representing the delimiter. +Leading whitespace between the delimiter and the heading text is hidden +along with the delimiter when hiding markup. +OVERRIDE, START, and END are passed through to +`treesit-fontify-with-override'." + (treesit-fontify-with-override + (treesit-node-start node) (treesit-node-end node) + 'markdown-ts-delimiter override start end) + (when markdown-ts-hide-markup + (put-text-property (treesit-node-start node) + (save-excursion + (goto-char (treesit-node-end node)) + (re-search-forward "[^[:blank:]]" (pos-eol) 'no-error) + (if (eq (point) (pos-eol)) + (point) + (1- (point)))) + 'invisible 'markdown-ts--markup))) + (defvar url-mail-command) ; url/url-vars.el (defun markdown-ts--make-link-button (beg end url) @@ -1021,57 +1079,89 @@ Pushes the mark before moving so `C-u C-SPC' returns. Signals (recenter)) (user-error "No heading for fragment: #%s" id))) -(defun markdown-ts--fontify-heading (node _override _start _end &rest _) - "Apply the heading face across NODE. +(defun markdown-ts--fontify-atx-heading (node _override _start _end &rest _) + "Apply the heading face across an atx_heading NODE. Layer the face on top of child sub-nodes (e.g. an inline link) so their own faces are preserved. Strip any prior copy of the face first so it does not accumulate when the heading is refontified or its level/type changes during editing. - -For ATX headings, also fontify any optional trailing closing-`#' -sequence as a delimiter. The tree-sitter grammar does not produce a -separate node for these; per CommonMark they are decorative and -must be preceded by a space or tab." - (let* ((type (treesit-node-type node)) - (n-start (treesit-node-start node)) +Do not fontify the header's trailing newline. +Elide trailing whitespace when hiding markup. +Fontify any optional trailing closing-`#' sequence as a delimiter. The +tree-sitter grammar does not produce a separate node for these; per +CommonMark they are decorative and must be preceded by a space or tab." + (let* ((n-start (treesit-node-start node)) (n-end (treesit-node-end node)) - (face (cond - ((equal type "setext_heading") - 'markdown-ts-setext-heading) - (t - (let ((marker (treesit-node-child node 0))) - (intern (format "markdown-ts-heading-%d" - (length (treesit-node-text marker t))))))))) + (face (let ((marker (treesit-node-child node 0))) + (intern (format "markdown-ts-heading-%d" + (length (treesit-node-text marker t))))))) (font-lock--remove-face-from-text-property n-start n-end 'face face) - (font-lock-append-text-property n-start n-end 'face face) - (when (string-prefix-p "atx_" type) - (save-excursion - (goto-char n-end) - (skip-chars-backward " \t\n" n-start) - (let ((line-end (point))) - (skip-chars-backward " \t" n-start) - (let ((trailing-end (point))) - (skip-chars-backward "#" n-start) - (let ((trailing-start (point))) - (when (and (< trailing-start trailing-end) - (> trailing-start n-start) - (memq (char-before trailing-start) '(?\s ?\t))) - (font-lock--remove-face-from-text-property - trailing-start trailing-end - 'face 'markdown-ts-delimiter) - (font-lock-prepend-text-property - trailing-start trailing-end - 'face 'markdown-ts-delimiter) - (when markdown-ts-hide-markup - ;; Also hide the space(s) preceding the closer and any - ;; trailing whitespace, so the heading looks clean. - (let ((hide-start (save-excursion - (goto-char trailing-start) - (skip-chars-backward " \t" n-start) - (point)))) - (put-text-property hide-start line-end - 'invisible - 'markdown-ts--markup))))))))))) + (font-lock-append-text-property n-start (1- n-end) 'face face) + (save-excursion + (goto-char n-end) + (skip-chars-backward "[:space:]" n-start) + (let ((trailing-end (point))) + (skip-chars-backward "#" n-start) + (let ((trailing-start (point))) + (cond ((and (< trailing-start trailing-end) + (> trailing-start n-start) + (memq (char-before trailing-start) '(?\s ?\t))) + ;; Identify the optional trailing closing-# sequence, + ;; fontify it as a delimiter, and remove whitespace + ;; between the heading text and the delimiter. The + ;; grammar omits a node for this run despite CommonMark. + (font-lock--remove-face-from-text-property + trailing-start trailing-end + 'face 'markdown-ts-delimiter) + (font-lock-prepend-text-property + trailing-start trailing-end + 'face 'markdown-ts-delimiter) + (when markdown-ts-hide-markup + (let ((hide-start (save-excursion + (goto-char trailing-start) + (skip-chars-backward "[:space:]" n-start) + (point)))) + (put-text-property hide-start (pos-eol) + 'invisible 'markdown-ts--markup)))) + (markdown-ts-hide-markup + ;; Hide trailing whitespace in the nominal case. + (put-text-property trailing-end (pos-eol) + 'invisible 'markdown-ts--markup)))))))) + +(defun markdown-ts--fontify-setext-heading (node _override _start _end &rest _) + "Apply the heading face across a setext NODE. +Layer the face on top of child sub-nodes (e.g. an inline link) so +their own faces are preserved. Strip any prior copy of the face +first so it does not accumulate when the heading is refontified or +its level/type changes during editing. +Apply the face to the setext heading_content separately from the +underline rather than treat them as a single range. This avoids putting +the face on the heading_content newline. If `markdown-ts-hide-markup' +is non-nil, hide the underline line entirely by setting its line-height +text property to 0. +Elide trailing whitespace when hiding markup." + (let* ((n-start (treesit-node-start node)) + (n-end (treesit-node-end node)) + (content (treesit-node-child node 0 'named)) + (content-start (treesit-node-start content)) + (content-end (treesit-node-end content)) + (underline (treesit-node-child node 1 'named)) + (underline-start (treesit-node-start underline)) + (underline-end (treesit-node-end underline)) + (face 'markdown-ts-setext-heading)) + (font-lock--remove-face-from-text-property n-start n-end 'face face) + ;; 1- content-end avoids the newline so it hides correctly. + (font-lock-append-text-property content-start (1- content-end) 'face face) + (font-lock-append-text-property underline-start underline-end 'face face) + (when markdown-ts-hide-markup + ;; Hide heading_content trailing spaces. + (put-text-property (save-excursion + (goto-char content-end) + (skip-chars-backward "[:space:]" content-start) + (point)) + content-end + 'invisible 'markdown-ts--markup) + (put-text-property underline-start underline-end 'line-height 0)))) (defun markdown-ts--fontify-link-node (node override start end &rest _) "Fontify link or image text NODE as a clickable button. @@ -1261,12 +1351,51 @@ OVERRIDE, START, and END are passed through to (defun markdown-ts--resolve-display-value (val) "Resolve VAL, a cons (PREFERRED . FALLBACK), to a displayable value. -Return PREFERRED if its first character passes `char-displayable-p', -otherwise return FALLBACK. Return nil if VAL is nil." - (when val - (let* ((preferred (car val)) - (ch (if (characterp preferred) preferred (aref preferred 0)))) - (if (char-displayable-p ch) (car val) (cdr val))))) +PREFERRED and FALLBACK can be a character or a string. Return PREFERRED +if it, or its first character, is `char-displayable-p', otherwise return +FALLBACK. +If VAL is not a cons or is nil, return VAL." + (if (consp val) + (let* ((preferred (car val)) + (ch (if (characterp preferred) + preferred + (aref preferred 0)))) + (if (char-displayable-p ch) + (car val) + (cdr val))) + val)) + +(defun markdown-ts--list-item-depth (node) + "Compute the depth of list NODE relative to its parents. +NODE can be a list, list_item, or one of the list_marker_'s. +If NODE is not in a list, return -1." + (let ((depth -1)) + (while (and node + (not (equal (treesit-node-type node) "section"))) + (when (equal (treesit-node-type node) "list") + (setq depth (1+ depth))) + (setq node (treesit-node-parent node))) + depth)) + +(defun markdown-ts--fontify-unordered-list-marker (node override start end &rest _) + "Fontify unordered list marker NODE, show a symbol when markup is hidden. +OVERRIDE, START, and END are passed through to +`treesit-fontify-with-override'." + (let* ((node-start (treesit-node-start node)) + (node-end (treesit-node-end node)) + (face 'markdown-ts-list-marker)) + (treesit-fontify-with-override node-start node-end face + override start end) + (cond (markdown-ts-hide-markup + (let* ((depth (markdown-ts--list-item-depth node)) + (value (if markdown-ts-unordered-list-marker + (nth (mod depth (length markdown-ts-unordered-list-marker)) + markdown-ts-unordered-list-marker) + nil)) + (display-spec (markdown-ts--resolve-display-value value))) + (put-text-property node-start node-end 'display display-spec))) + (t + (remove-text-properties node-start node-end '(display nil)))))) (defun markdown-ts--fontify-checkbox (node override start end &rest _) "Fontify task list checkbox NODE, show a Unicode symbol when markup is hidden. @@ -1310,6 +1439,9 @@ A backslash break gets `markdown-ts-hard-line-break-backslash' (or its backslash break is replaced by a single `markdown-ts-hard-line-break' glyph; a trailing-spaces break replaces each space with the glyph, so the run of pilcrows fills the line up to the newline. +If `markdown-ts-hard-line-break-backslash' or +`markdown-ts-hard-line-break-space' are the symbol `hide', hide the +markup entirely. OVERRIDE, START, and END are passed through to `treesit-fontify-with-override'." (let* ((node-start (treesit-node-start node)) @@ -1363,21 +1495,24 @@ OVERRIDE, START, and END are passed through to ((stringp spec) spec) ((functionp spec) (funcall spec (- region-end region-start)))))) - (when (and (stringp str) - (> (length str) 0) - (char-displayable-p (aref str 0))) - (put-text-property region-start (1+ region-start) - 'display str) - ;; For the trailing-spaces variant, hide the remaining - ;; spaces in the run so the line doesn't end with leftover - ;; whitespace after the substituted glyph. Each position - ;; gets its own empty-string `display' so cursor placement - ;; stays unambiguous. - (unless backslash - (let ((i (1+ region-start))) - (while (< i region-end) - (put-text-property i (1+ i) 'display "") - (setq i (1+ i)))))))))) + (if (eq spec 'hide) + (put-text-property region-start region-end + 'invisible 'markdown-ts--markup) + (when (and (stringp str) + (> (length str) 0) + (char-displayable-p (aref str 0))) + (put-text-property region-start (1+ region-start) + 'display str) + ;; For the trailing-spaces variant, hide the remaining + ;; spaces in the run so the line doesn't end with leftover + ;; whitespace after the substituted glyph. Each position + ;; gets its own empty-string `display' so cursor placement + ;; stays unambiguous. + (unless backslash + (let ((i (1+ region-start))) + (while (< i region-end) + (put-text-property i (1+ i) 'display "") + (setq i (1+ i))))))))))) (defun markdown-ts--fontify-thematic-break (node override start end &rest _) "Fontify thematic break NODE and show a line when markup is hidden. @@ -1388,22 +1523,27 @@ OVERRIDE, START, and END are passed through to (treesit-fontify-with-override node-start node-end 'markdown-ts-thematic-break override start end) - (let ((char (markdown-ts--resolve-display-value - markdown-ts-thematic-break-character))) - (if (and markdown-ts-hide-markup char (char-displayable-p char)) - (let* ((col (save-excursion (goto-char node-start) - (current-column))) - ;; Span if the face has non-nil :extend. - (span-length (if (face-attribute 'markdown-ts-thematic-break - :extend nil 'default) - (- (window-body-width) col) - 12))) - (put-text-property node-start node-end - 'display - (concat - (make-string span-length char) - "\n"))) - (remove-text-properties node-start node-end '(display nil)))))) + (if markdown-ts-hide-markup + (cond + ((and (display-supports-face-attributes-p '(:extend t)) + (face-attribute 'markdown-ts-thematic-break + :extend nil 'default)) + (put-text-property node-start node-end + 'display + (propertize "\n" 'face '(:extend t :underline t)))) + (t + (when-let* ((char (markdown-ts--resolve-display-value + markdown-ts-thematic-break-character)) + (_ (char-displayable-p char))) + (let* ((col (save-excursion (goto-char node-start) + (current-column))) + (span-length (max 12 (- (window-body-width) col)))) + (put-text-property node-start node-end + 'display + (concat + (make-string span-length char) + "\n")))))) + (remove-text-properties node-start node-end '(display nil))))) (defun markdown-ts--fontify-code-block (node _override _start _end &rest _) "Fontify code block content NODE with a background overlay. @@ -1637,18 +1777,18 @@ Skip matches already inside tree-sitter link or autolink nodes." :language 'markdown :feature 'heading - '(((atx_heading) @markdown-ts--fontify-heading) - ((setext_heading) @markdown-ts--fontify-heading)) + '(((atx_heading) @markdown-ts--fontify-atx-heading) + ((setext_heading) @markdown-ts--fontify-setext-heading)) :language 'markdown :feature 'heading :override 'prepend - '((atx_h1_marker) @markdown-ts--fontify-delimiter - (atx_h2_marker) @markdown-ts--fontify-delimiter - (atx_h3_marker) @markdown-ts--fontify-delimiter - (atx_h4_marker) @markdown-ts--fontify-delimiter - (atx_h5_marker) @markdown-ts--fontify-delimiter - (atx_h6_marker) @markdown-ts--fontify-delimiter + '((atx_h1_marker) @markdown-ts--fontify-atx-delimiter + (atx_h2_marker) @markdown-ts--fontify-atx-delimiter + (atx_h3_marker) @markdown-ts--fontify-atx-delimiter + (atx_h4_marker) @markdown-ts--fontify-atx-delimiter + (atx_h5_marker) @markdown-ts--fontify-atx-delimiter + (atx_h6_marker) @markdown-ts--fontify-atx-delimiter (setext_h1_underline) @markdown-ts--fontify-delimiter (setext_h2_underline) @markdown-ts--fontify-delimiter) @@ -1657,9 +1797,9 @@ Skip matches already inside tree-sitter link or autolink nodes." '(((thematic_break) @markdown-ts--fontify-thematic-break) ((html_block) @markdown-ts-html-block) ((indented_code_block) @markdown-ts-indented-code-block) - (list_item (list_marker_star) @markdown-ts-list-marker) - (list_item (list_marker_plus) @markdown-ts-list-marker) - (list_item (list_marker_minus) @markdown-ts-list-marker) + (list_item (list_marker_star) @markdown-ts--fontify-unordered-list-marker) + (list_item (list_marker_plus) @markdown-ts--fontify-unordered-list-marker) + (list_item (list_marker_minus) @markdown-ts--fontify-unordered-list-marker) (list_item (list_marker_dot) @markdown-ts-list-marker) (list_item (list_marker_parenthesis) @markdown-ts-list-marker) (list_item (task_list_marker_unchecked) @markdown-ts--fontify-checkbox) @@ -1714,33 +1854,13 @@ Skip matches already inside tree-sitter link or autolink nodes." '(((code_span) @markdown-ts-code-span) ((code_span_delimiter) @markdown-ts--fontify-delimiter)) - :language 'markdown-inline - :override 'append - :feature 'paragraph-inline - ;; Order matters: most specific to least specific. - '(;; ~ x ~ (strikethrough (emphasis_delimiter) ) : ( (emphasis_delimiter)) - ;; ^^^ - ;; inline text needs to be ignored - ((strikethrough ((emphasis_delimiter) - :anchor - _ @foo - (emphasis_delimiter)) - (:match "\\`[~[:print:]]\\'" @foo) - ) - @default) - ;; ~~x~~ (strikethrough (emphasis_delimiter) (strikethrough (emphasis_delimiter) (emphasis_delimiter)) (emphasis_delimiter)) - ((strikethrough (emphasis_delimiter) (strikethrough (emphasis_delimiter) (emphasis_delimiter)) (emphasis_delimiter)) - @markdown-ts-strikethrough) - ;; ~x~ (strikethrough (emphasis_delimiter) (emphasis_delimiter)) - ((strikethrough (emphasis_delimiter) (emphasis_delimiter)) - @markdown-ts-strikethrough)) - :language 'markdown-inline :override 'append :feature 'paragraph-inline '(((link_destination) @markdown-ts--fontify-link-destination) ((emphasis) @markdown-ts-emphasis) ((strong_emphasis) @markdown-ts-bold) + ((strikethrough) @markdown-ts-strikethrough) (inline_link (link_text) @markdown-ts--fontify-link-node) (full_reference_link (link_text) @markdown-ts--fontify-link-node) (full_reference_link (link_label) @markdown-ts--fontify-link-node) @@ -2073,7 +2193,8 @@ indentation, which tree-sitter may include in the node." (defun markdown-ts--list-ordered-item-p (item) "Return non-nil if ITEM is an ordered (numbered) list item." (let ((marker (treesit-node-child item 0))) - (equal (treesit-node-type marker) "list_marker_dot"))) + (member (treesit-node-type marker) + '("list_marker_dot" "list_marker_parenthesis")))) (defun markdown-ts--list-promote-or-demote (demote) "Change nesting of the list item at point. @@ -4631,15 +4752,16 @@ If point is not at a table, do nothing." "Return range settings for `markdown-ts-mode'." (apply #'treesit-range-rules - `(:embed markdown-inline - :host markdown + `( :embed markdown-inline + :host markdown + :local t ((inline) @markdown-inline) ,@(when markdown-ts-fontify-code-blocks-natively - '(:embed markdown-ts--code-block-ts-language - :host markdown - :local t - ((fenced_code_block (info_string (language) @language) - (code_fence_content) @content))))))) + '( :embed markdown-ts--code-block-ts-language + :host markdown + :local t + ((fenced_code_block (info_string (language) @language) + (code_fence_content) @content))))))) (defun markdown-ts--remove-image-overlays () "Remove all inline image overlays from the current buffer." @@ -4924,6 +5046,27 @@ On a heading, call `outline-cycle'. Otherwise do nothing." "M-RET" #'markdown-ts-insert-list-item "TAB" #'markdown-ts-outline-cycle) +(defvar-keymap markdown-ts-view-mode-map + :doc "Keymap for `markdown-ts-view-mode'." + :parent special-mode-map + :menu nil + "g" #'ignore ; Override special-mode-map #'revert-buffer + "C-c C-n" #'outline-next-heading + "n" #'outline-next-heading + "C-c C-p" #'outline-previous-heading + "p" #'outline-previous-heading + "C-c C-u" #'outline-up-heading + "u" #'outline-up-heading + "C-c C-f" #'outline-forward-same-level + "f" #'outline-forward-same-level + "C-c C-b" #'outline-backward-same-level + "b" #'outline-backward-same-level + "C-c C-x C-m" #'markdown-ts-toggle-hide-markup + "C-c C-x C-v" #'markdown-ts-toggle-inline-images + "C-c C-v n" #'markdown-ts-move-to-next-code-block + "C-c C-v p" #'markdown-ts-move-to-previous-code-block + "TAB" #'markdown-ts-outline-cycle) + (defvar-keymap markdown-ts-code-block-in-context-mode-map :doc "Keymap for `markdown-ts-code-block-in-context-mode'. These override keys in `markdown-ts-mode-map' to support executing their @@ -5047,7 +5190,6 @@ NOTE: Call this function only when the treesit `markdown' and (setq-local adaptive-fill-function #'markdown-ts--adaptive-fill) ;; Create and configure the parsers. - (treesit-parser-create 'markdown-inline) (setq treesit-primary-parser (treesit-parser-create 'markdown)) @@ -5059,11 +5201,12 @@ NOTE: Call this function only when the treesit `markdown' and (image-preview error))) (cond (markdown-ts--set-up-inline - (setq-local treesit-range-settings - (treesit-range-rules - :embed 'markdown-inline - :host 'markdown - '((inline) @markdown-inline)))) + (treesit-parser-create 'markdown-inline) + (setq-local treesit-range-settings + (treesit-range-rules + :embed 'markdown-inline + :host 'markdown + '((inline) @markdown-inline)))) (t ;; Range settings differ in the master buffer vs. inline above. (setq-local treesit-range-settings (markdown-ts--range-settings)) @@ -5092,7 +5235,6 @@ NOTE: Call this function only when the treesit `markdown' and #'markdown-ts--outline-view-change nil t)) (progn - (make-local-variable 'markdown-ts-hide-markup) (make-local-variable 'font-lock-extra-managed-props) (dolist (prop '(invisible display button category action help-echo)) (add-to-list 'font-lock-extra-managed-props prop))) @@ -5209,10 +5351,8 @@ With a prefix argument, ARG, if needed, install parsers for `html', (require 'toml-ts-mode) (treesit-install-language-grammar 'toml)))) -;;;###autoload -(define-derived-mode markdown-ts-mode text-mode "Markdown" - "Major mode for editing Markdown using tree-sitter grammar. -NOTE: See `markdown-ts--set-up-inline'." +(defun markdown-ts-mode--initialize () + "Invoke this from major mode definitions after local variable set up." (treesit-ensure-installed 'markdown) (treesit-ensure-installed 'markdown-inline) ;; Bypass `treesit-max-buffer-size' so the mode activates in large @@ -5221,16 +5361,57 @@ NOTE: See `markdown-ts--set-up-inline'." ;; they are installed. Revisit if `treesit-parser-create' gains its ;; own buffer-size guard (see bug#80909). (let ((treesit-max-buffer-size most-positive-fixnum)) - (if (treesit-ready-p '(markdown markdown-inline) t) - (markdown-ts--set-up) - (warn "markdown-ts-mode cannot be set up; using fundamental-mode. + (cond ((treesit-ready-p '(markdown markdown-inline) t) + (markdown-ts--set-up)) + (t + (warn "markdown-ts-mode cannot be set up; using fundamental-mode. The tree-sitter parsers `markdown' and `markdown-inline' were not found. Use the command `markdown-ts-mode-install-parsers' to install them. With a prefix argument, it can also install optional parsers.") - (fundamental-mode)))) + (fundamental-mode))))) + +;;;###autoload +(define-derived-mode markdown-ts-mode text-mode "Markdown" + "Major mode for editing Markdown using tree-sitter grammar. +NOTE: See `markdown-ts--set-up-inline'." + (markdown-ts-mode--initialize)) (derived-mode-add-parents 'markdown-ts-mode '(markdown-mode)) +;;; View mode: + +;;;###autoload +(define-derived-mode markdown-ts-view-mode + nil ; Intentionally left blank. + "Markdown View" + "Major mode for read-only viewing Markdown using tree-sitter grammar." + ;; NOTE: `markdown-ts-mode' is manually added as a parent to avoid + ;; invoking its initialization before we set override variables. + (setq-local markdown-ts-menu-bar-show nil) + (setq-local markdown-ts-hide-markup t) + (setq-local markdown-ts-inline-images t) + (setq-local markdown-ts-hard-line-break-backslash 'hide) + (setq-local markdown-ts-hard-line-break-space 'hide) + (setq-local markdown-ts-fontify-code-blocks-natively t) + (setq-local markdown-ts-enable-code-block-context-mode nil) + (setq-local markdown-ts-enable-table-mode nil) + (run-hooks 'markdown-ts-view-mode-pre-init-hook) + (markdown-ts-mode--initialize) + (setq buffer-read-only t)) + +(derived-mode-add-parents 'markdown-ts-view-mode '(markdown-ts-mode special-mode)) + +;;; Mode utilities: + +;;;###autoload +(defun markdown-ts-buffer-string () + "Like `buffer-string', and convert overlay properties to text properties." + (let ((str (buffer-string))) + (dolist (ov (overlays-in (point-min) (point-max)) str) + (when-let* ((face (overlay-get ov 'face))) + (font-lock-append-text-property + (overlay-start ov) (overlay-end ov) 'face face str))))) + (defun markdown-ts--barf-if-not-mode (&optional context) "Signal an error if the current buffer is not a `markdown-ts-mode' buffer. Prefix the error message with CONTEXT." @@ -5238,6 +5419,18 @@ Prefix the error message with CONTEXT." (user-error "%sis valid only in `markdown-ts-mode' buffers" (if context (format "%s: " context) "")))) +(defun markdown-ts-add-final-newline () + "Add a final newline to the current buffer, if necessary." + ;; Inspired by files.el. + (let ((inhibit-read-only t)) + (when (or (eq (buffer-size) 0) + (and (/= (char-after (1- (point-max))) ?\n) + (not (and (eq selective-display t) + (= (char-after (1- (point-max))) ?\r))))) + (save-excursion + (goto-char (point-max)) + (insert ?\n))))) + (define-minor-mode markdown-ts-code-block-in-context-mode "Minor mode enabled if point is within a fenced code block. This enables the keymap `markdown-ts-code-block-in-context-mode-map'." commit b39c123490b3baeb6c8f88908f3ff854ed2f2fee Author: Rahul Martim Juliato Date: Fri May 8 19:23:07 2026 -0300 Fix strikethrough in 'markdown-ts-mode' (bug#80991) * lisp/textmodes/markdown-ts-mode.el (markdown-ts--treesit-settings): Add a new font-lock block for 'paragraph-inline' that handles strikethrough nodes more carefully. diff --git a/lisp/textmodes/markdown-ts-mode.el b/lisp/textmodes/markdown-ts-mode.el index 8e28be036b7..8646842cfc3 100644 --- a/lisp/textmodes/markdown-ts-mode.el +++ b/lisp/textmodes/markdown-ts-mode.el @@ -154,6 +154,17 @@ ;; content. Lowercase `' works as a workaround. ;; See . ;; +;; - The grammar parses solo tildes, incorrectly applying strikethrough. +;; For example, writing: +;; +;; I see ~approximately four lights. +;; I do not see ~approximately five lights. +;; +;; Results in strikethrough incorrectly starting at the first +;; ~approximately and extending till the tilde at the second +;; ~approximately. +;; See . +;; ;; - Superscript (`^text^') and subscript (`~text~') syntax is not ;; supported by the grammar. No EXTENSION_ build flag exists for ;; this. This is Pandoc / PHP Markdown Extra syntax, not CommonMark @@ -1703,13 +1714,33 @@ Skip matches already inside tree-sitter link or autolink nodes." '(((code_span) @markdown-ts-code-span) ((code_span_delimiter) @markdown-ts--fontify-delimiter)) + :language 'markdown-inline + :override 'append + :feature 'paragraph-inline + ;; Order matters: most specific to least specific. + '(;; ~ x ~ (strikethrough (emphasis_delimiter) ) : ( (emphasis_delimiter)) + ;; ^^^ + ;; inline text needs to be ignored + ((strikethrough ((emphasis_delimiter) + :anchor + _ @foo + (emphasis_delimiter)) + (:match "\\`[~[:print:]]\\'" @foo) + ) + @default) + ;; ~~x~~ (strikethrough (emphasis_delimiter) (strikethrough (emphasis_delimiter) (emphasis_delimiter)) (emphasis_delimiter)) + ((strikethrough (emphasis_delimiter) (strikethrough (emphasis_delimiter) (emphasis_delimiter)) (emphasis_delimiter)) + @markdown-ts-strikethrough) + ;; ~x~ (strikethrough (emphasis_delimiter) (emphasis_delimiter)) + ((strikethrough (emphasis_delimiter) (emphasis_delimiter)) + @markdown-ts-strikethrough)) + :language 'markdown-inline :override 'append :feature 'paragraph-inline '(((link_destination) @markdown-ts--fontify-link-destination) ((emphasis) @markdown-ts-emphasis) ((strong_emphasis) @markdown-ts-bold) - ((strikethrough) @markdown-ts-strikethrough) (inline_link (link_text) @markdown-ts--fontify-link-node) (full_reference_link (link_text) @markdown-ts--fontify-link-node) (full_reference_link (link_label) @markdown-ts--fontify-link-node) commit 0be998d4bc03b4ea12e506363d41b7e9ceeefc04 Author: Rahul Martim Juliato Date: Thu May 7 11:24:30 2026 -0300 Fix code-span in headings in 'markdown-ts-mode' (bug#80979) * lisp/textmodes/markdown-ts-mode.el (markdown-ts--treesit-settings): Move the 'code_span' and 'code_span_delimiter' font-lock rules into a separate block with ':override prepend' instead of 'append'. The heading feature (level 1) applies its face via 'font-lock-append-text-property', so a code span that later appends 'markdown-ts-code-span' ends up with '(markdown-ts-heading-N markdown-ts-code-span)', where the heading face takes priority and the code-span face is suppressed. Prepending ensures 'markdown-ts-code-span' appears first in the face list and wins visually. diff --git a/lisp/textmodes/markdown-ts-mode.el b/lisp/textmodes/markdown-ts-mode.el index 56fd98d86b8..8e28be036b7 100644 --- a/lisp/textmodes/markdown-ts-mode.el +++ b/lisp/textmodes/markdown-ts-mode.el @@ -1697,12 +1697,16 @@ Skip matches already inside tree-sitter link or autolink nodes." :override 'append '((fenced_code_block (code_fence_content) @markdown-ts--fontify-code-block)) + :language 'markdown-inline + :override 'prepend + :feature 'paragraph-inline + '(((code_span) @markdown-ts-code-span) + ((code_span_delimiter) @markdown-ts--fontify-delimiter)) + :language 'markdown-inline :override 'append :feature 'paragraph-inline '(((link_destination) @markdown-ts--fontify-link-destination) - ((code_span) @markdown-ts-code-span) - ((code_span_delimiter) @markdown-ts--fontify-delimiter) ((emphasis) @markdown-ts-emphasis) ((strong_emphasis) @markdown-ts-bold) ((strikethrough) @markdown-ts-strikethrough) commit a00beb3a31bd998cba547a6b06d662799bdc28c6 Author: Stéphane Marks Date: Thu May 7 08:09:28 2026 -0400 Make 'markdown-ts-inline-images' buffer local and test for GUI (bug#80978) * lisp/textmodes/markdown-ts-mode.el (markdown-ts-inline-images): Now buffer local. (markdown-ts--fontify-image): Defensively test for graphical display before rendering images. diff --git a/lisp/textmodes/markdown-ts-mode.el b/lisp/textmodes/markdown-ts-mode.el index 05f016ab377..56fd98d86b8 100644 --- a/lisp/textmodes/markdown-ts-mode.el +++ b/lisp/textmodes/markdown-ts-mode.el @@ -279,6 +279,8 @@ use that string instead." (defcustom markdown-ts-inline-images nil "Non-nil means display inline images below image links." :type 'boolean + :local t + :safe #'booleanp :version "31.1" :package-version "1.0") @@ -1524,6 +1526,7 @@ Remote images are controlled by (<= (overlay-end ov) search-end)) (delete-overlay ov))) (when (and markdown-ts-inline-images + (display-images-p) ;; Don't create image overlays for nodes inside ;; folded (outline-invisible) headings, since the ;; images wouldn't be visible and could interfere commit a0c05029fd18c33f897d0c8ee3424735b53fe645 Author: Michael Albinus Date: Wed May 13 18:46:31 2026 +0200 * etc/NEWS: Mention new user option tramp-propagate-emacsclient-tramp. diff --git a/etc/NEWS b/etc/NEWS index c0e05deed2e..a746ca7b1a3 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -2451,6 +2451,13 @@ This can be used by external ELPA packages for performance optimizations in special cases. For more information, see "(tramp) New operations" in the Tramp manual. ++++ +*** New user option 'tramp-propagate-emacsclient-tramp'. +When this option is non-nil, Tramp propagates the environment variable +EMACSCLIENT_TRAMP with a proper value to remote processes. This is +helpful if you want to start emacsclient on a remote host from a process +started inside Emacs. + ** Isearch and Replace *** Typing 'd' during 'query-replace' shows the diff buffer with replacements. commit 2e71d2c709f003cd33597d106e8a483500ec99c9 Author: Michael Albinus Date: Wed May 13 18:39:04 2026 +0200 Propagate EMACSCLIENT_TRAMP to remote hosts with Tramp * doc/misc/tramp.texi (Remote processes): Explain `tramp-propagate-emacsclient-tramp'. * lisp/net/tramp.el (tramp-remote-process-environment): Adapt docstring. (tramp-propagate-emacsclient-tramp): New defcustom. (tramp-handle-make-process): * lisp/net/tramp-sh.el (tramp-sh-handle-make-process) (tramp-sh-handle-process-file): Use it. * test/lisp/net/tramp-tests.el (tramp-test33-environment-variables): Adapt test. diff --git a/doc/misc/tramp.texi b/doc/misc/tramp.texi index 8428109fde7..ae65cf2a620 100644 --- a/doc/misc/tramp.texi +++ b/doc/misc/tramp.texi @@ -4227,6 +4227,14 @@ called is local or remote, since @value{tramp} would add just the @env{HGPLAIN} setting and local processes would take whole value of @code{process-environment} along with the new value of @env{HGPLAIN}. +@vindex tramp-propagate-emacsclient-tramp +@vindex EMACSCLIENT_TRAMP@r{, environment variable} +If you set the user option @code{tramp-propagate-emacsclient-tramp} to +a non-@code{nil} value, the environment variable +@env{EMACSCLIENT_TRAMP} will be set to a value which allows to call +@command{emacsclient} from a process running on the remote +host. @xref{emacsclient Options, , , emacs}. + For integrating other Emacs packages so @value{tramp} can execute remotely, please file a bug report. @xref{Bug Reports}. diff --git a/lisp/net/tramp-sh.el b/lisp/net/tramp-sh.el index 92002b854b3..7b90ae9c11b 100644 --- a/lisp/net/tramp-sh.el +++ b/lisp/net/tramp-sh.el @@ -3091,6 +3091,11 @@ will be used." (if (string-search "=" elt) (setq env (append env `(,elt))) (setq uenv (cons elt uenv)))))) + (env (if tramp-propagate-emacsclient-tramp + (setenv-internal + env "EMACSCLIENT_TRAMP" + (tramp-make-tramp-file-name v 'noloc) 'keep) + env)) (env (setenv-internal env "INSIDE_EMACS" (tramp-inside-emacs) 'keep)) ;; Environment is too large. Keep it here. @@ -3340,6 +3345,10 @@ will be used." (if (string-search "=" elt) (setq env (append env `(,elt))) (setq uenv (cons elt uenv))))) + (when tramp-propagate-emacsclient-tramp + (setq env (setenv-internal + env "EMACSCLIENT_TRAMP" + (tramp-make-tramp-file-name v 'noloc) 'keep))) (setq env (setenv-internal env "INSIDE_EMACS" (tramp-inside-emacs) 'keep)) (when env (setq command diff --git a/lisp/net/tramp.el b/lisp/net/tramp.el index 6219d097f4f..ffcf8f4929c 100644 --- a/lisp/net/tramp.el +++ b/lisp/net/tramp.el @@ -1528,12 +1528,21 @@ The PATH environment variable should be set via `tramp-remote-path'. The TERM environment variable should be set via `tramp-terminal-type'. +The EMACSCLIENT_TRAMP environment variable will be set accordingly, if +`tramp-propagate-emacsclient-tramp' is non-nil. + The INSIDE_EMACS environment variable will automatically be set based on the Tramp and Emacs versions, and should not be set here." :version "26.1" :type '(repeat string) :link '(info-link :tag "Tramp manual" "(tramp) Remote processes")) +(defcustom tramp-propagate-emacsclient-tramp nil + "Whether to propagate the EMACSCLIENT_TRAMP environment variable." + :version "31.1" + :type 'boolean + :link '(info-link :tag "Tramp manual" "(tramp) Remote processes")) + ;;; Internal Variables: ;;;###tramp-autoload @@ -5509,6 +5518,13 @@ processes." (env (if sh-file-name-handler-p (setenv-internal env "TERM" tramp-terminal-type 'keep) env)) + ;; Add EMACSCLIENT_TRAMP. + (env (if (and tramp-propagate-emacsclient-tramp + sh-file-name-handler-p) + (setenv-internal + env "EMACSCLIENT_TRAMP" + (tramp-make-tramp-file-name v 'noloc) 'keep) + env)) ;; Add INSIDE_EMACS. (env (setenv-internal env "INSIDE_EMACS" (tramp-inside-emacs) 'keep)) (env (mapcar #'tramp-shell-quote-argument (delq nil env))) diff --git a/test/lisp/net/tramp-tests.el b/test/lisp/net/tramp-tests.el index c76d6d54c3d..d6eb782df3d 100644 --- a/test/lisp/net/tramp-tests.el +++ b/test/lisp/net/tramp-tests.el @@ -6555,6 +6555,21 @@ INPUT, if non-nil, is a string sent to the process." (funcall this-shell-command-to-string "echo \"${INSIDE_EMACS:-bla}\"")))) + ;; Check EMACSCLIENT_TRAMP. + (setenv "EMACSCLIENT_TRAMP") + (let ((tramp-propagate-emacsclient-tramp t)) + (should + (string-equal + (format "%s\n" (tramp-make-tramp-file-name tramp-test-vec 'noloc)) + (funcall + this-shell-command-to-string "echo \"${EMACSCLIENT_TRAMP:-bla}\"")))) + (let (tramp-propagate-emacsclient-tramp) + (should + (string-equal + "bla\n" + (funcall + this-shell-command-to-string "echo \"${EMACSCLIENT_TRAMP:-bla}\"")))) + ;; Set a value. (let ((process-environment (cons (concat envvar "=foo") process-environment))) commit ff96db93f23d17a1dcdc12aec4067007b5a4e18e Author: Stefan Monnier Date: Wed May 13 10:45:38 2026 -0400 keyboard-tests.el: Try and fix the failure on EMBA * test/src/keyboard-tests.el (keyboard-sigint-to-quit): Fix a small race condition and avoid `sit-for` returning early. diff --git a/test/src/keyboard-tests.el b/test/src/keyboard-tests.el index e4a1bf36a63..b64b20fb6cb 100644 --- a/test/src/keyboard-tests.el +++ b/test/src/keyboard-tests.el @@ -92,11 +92,11 @@ `(,(expand-file-name invocation-name invocation-directory) "-Q" "--batch" "--eval" ,(prin1-to-string - `(progn (setq kill-emacs-on-sigint nil) - (message "Ready!") - (condition-case nil - (dotimes (_ 3) (sit-for 1)) - (quit (message "%s" ,exit-msg))))))))) + `(condition-case nil + (progn (setq kill-emacs-on-sigint nil) + (message "Ready!") + (sleep-for 3)) + (quit (message "%s" ,exit-msg)))))))) (while (progn (accept-process-output proc 1.0) (goto-char (point-min)) (not (re-search-forward "Ready!" nil t))) commit ce3098752cfb8cbb57c4f643088a2e6b16919ea7 Author: Stefan Monnier Date: Wed May 13 10:34:03 2026 -0400 doc: Remove long obsolete references to `package-initialize` Since Emacs-27, `package-initialize` is for internal use only, and callers should either call `package-activate-all` instead (cheaper and faster) or do nothing at all (because the other functions should trigger the needed initialization automatically as needed). * doc/lispref/package.texi (Packaging Basics): Delete `package-initialize`. * doc/misc/eglot.texi (Reporting bugs): Don't recommend using `package-initialize`. * doc/misc/org.org (Using Emacs packaging system): Simplify the command line since both `(require 'package)` and `(package-initialize)` are redundant here. diff --git a/doc/lispref/package.texi b/doc/lispref/package.texi index 8f3f5fe79b0..3097ed02345 100644 --- a/doc/lispref/package.texi +++ b/doc/lispref/package.texi @@ -133,15 +133,6 @@ init file, and any code that should run after it in the primary init file (@pxref{Init File,,, emacs, The GNU Emacs Manual}). @end defun -@deffn Command package-initialize &optional no-activate -This function initializes Emacs's internal record of which packages are -installed, and then calls @code{package-activate-all}. - -The optional argument @var{no-activate}, if non-@code{nil}, causes -Emacs to update its record of installed packages without actually -making them available. -@end deffn - @node Simple Packages @section Simple Packages @cindex single file package diff --git a/doc/misc/eglot.texi b/doc/misc/eglot.texi index 0b6be34c41f..3deabb894c0 100644 --- a/doc/misc/eglot.texi +++ b/doc/misc/eglot.texi @@ -1969,7 +1969,7 @@ directory is a way to tell the maintainers about ELPA package versions. @item Include a recipe to replicate the problem with @emph{a clean Emacs run}. -The invocation @code{emacs -Q -f package-initialize} starts Emacs with +The invocation @code{emacs -Q -f package-activate-all} starts Emacs with no configuration and initializes the ELPA packages. A very minimal @file{.emacs} initialization file (10 lines or less) is also acceptable and good means to describe changes to variables. diff --git a/doc/misc/org.org b/doc/misc/org.org index ab0f8a9c1a6..8644109a6a1 100644 --- a/doc/misc/org.org +++ b/doc/misc/org.org @@ -125,7 +125,7 @@ To avoid interference with the built-in Org mode, you can use the command line (you need Emacs 30 or later): #+begin_src sh -emacs -Q -batch -eval "(progn (require 'package) (package-initialize) (package-refresh-contents) (package-upgrade 'org))" +emacs -Q -batch -eval "(progn (package-refresh-contents) (package-upgrade 'org))" #+end_src This approach has the advantage of isolating the upgrade process from commit 9bc04b001ac93bd926715b1a9aac0b8e4e46b1ae Author: Sean Whitton Date: Wed May 13 13:15:23 2026 +0100 vc-next-action: Call vc-delete-file on FILESET-ONLY-FILES * lisp/vc/vc.el (vc-next-action): Call vc-delete-file on FILESET-ONLY-FILES, not FILES (bug#80998). diff --git a/lisp/vc/vc.el b/lisp/vc/vc.el index 4a6ae7e4290..8f0e9e5bdc4 100644 --- a/lisp/vc/vc.el +++ b/lisp/vc/vc.el @@ -1719,7 +1719,7 @@ from which to check out the file(s)." (t (vc-register vc-fileset)))) ((eq state 'missing) - (vc-delete-file files)) + (vc-delete-file fileset-only-files)) ;; Files are up-to-date, or need a merge and user specified a revision ((or (eq state 'up-to-date) (and verbose (eq state 'needs-update))) (cond commit 13039e3442b8da51a284643be43c4f21d2f2d08d Author: Corwin Brust Date: Wed May 13 01:06:17 2026 -0500 ; touch-up last commit: copyright and comments diff --git a/admin/nt/dist-build/build-dep-zips.py b/admin/nt/dist-build/build-dep-zips.py index f70f083b999..493e7616fa9 100755 --- a/admin/nt/dist-build/build-dep-zips.py +++ b/admin/nt/dist-build/build-dep-zips.py @@ -1,6 +1,6 @@ #!/usr/bin/python3 -## Copyright (C) 2017-2023 Free Software Foundation, Inc. +## Copyright (C) 2017-2026 Free Software Foundation, Inc. ## This file is part of GNU Emacs. @@ -48,30 +48,14 @@ mingw-w64-x86_64-sqlite3'''.split() # Emacs style path to dependancy DLLs on build system -#DLL_SRC="c:/msys64/mingw64/bin" DLL_SRC="mingw64" OUT_TAG="" # libraries we never include DLL_SKIP=["libgccjit-0.dll"] -# Report first existing file for entries in dynamic-library-alist -# ELISP_PROG=""" -# (message "%s" (mapconcat 'identity (remove nil -# (mapcar (lambda(lib) -# (seq-find -# (lambda(file) -# (file-exists-p -# (file-name-concat "{}" -# file))) -# (cdr lib))) -# dynamic-library-alist) -# ) "\\n")) -# """.format(DLL_SRC) - ## Options DRY_RUN=False -# NEW_EMACS="bin/emacs.exe" def check_output_maybe(*args,**kwargs): if(DRY_RUN): @@ -84,7 +68,6 @@ def check_output_maybe(*args,**kwargs): # entry point def gather_deps(): - os.mkdir("x86_64") os.chdir("x86_64") @@ -132,18 +115,12 @@ def init_deps(): liblcms2-2.dll libgccjit-0.dll libtree-sitter-0.26.dll'''.split() - # job_args=[NEW_EMACS, "--batch", "--eval", ELISP_PROG] - # #print("args: ", job_args) - # return subprocess.check_output(job_args, stderr=subprocess.STDOUT - # ).decode('utf-8').splitlines() # Return all second order dependencies def full_dll_dependency(dlls): deps = [dll_dependency(dep) for dep in dlls] return set(sum(deps, []) + dlls) -#xs = filter(lambda x: x.attribute == value, xs) - # Dependencies for a given DLL def dll_dependency(dll): output = check_output(["/mingw64/bin/ntldd", "--recursive", @@ -172,12 +149,18 @@ def ntldd_munge(out): ## Packages to fiddle with ## Source for gcc-libs is part of gcc -SKIP_SRC_PKGS=["mingw-w64-gcc-libs"] #, "mingw-w64-x86_64-libwinpthread-git"] -SKIP_DEP_PKGS=["mingw-w64-glib2", "mingw-w64-x86_64-cc-libs", "mingw-w64-ca-certificates-20211016-3"] #, "mingw-w64-x86_64-libwinpthread-git"] +SKIP_SRC_PKGS=["mingw-w64-gcc-libs"] +SKIP_DEP_PKGS=["mingw-w64-glib2", "mingw-w64-x86_64-cc-libs", "mingw-w64-ca-certificates-20211016-3"] + +## A few source packages don't follow typical naming conventions. +## Handle transformation from formulaic name to actual name MUNGE_SRC_PKGS={ "mingw-w64-libwinpthread":"mingw-w64-winpthreads", "mingw-w64-gettext-runtime":"mingw-w64-gettext" } + +## As above, but for source packages of second order deps +## Empty as of 30.0.50 (May, 2026), last used for Emacs 30.2 MUNGE_DEP_PKGS={ #"mingw-w64-x86_64-libwinpthread":"mingw-w64-x86_64-libwinpthread-git", #"mingw-w64-x86_64-libtre": "mingw-w64-x86_64-libtre-git", @@ -188,7 +171,8 @@ def ntldd_munge(out): # "mingw-w64-brotli": ".src.tar.gz", } -## Currently no packages seem to require this! +## Pick up packages only when building for a given architecture +## Currently no packages seem to require this! Unused since Emacs 26 ARCH_PKGS=[] def immediate_deps(pkg): @@ -274,7 +258,7 @@ def gather_source(deps): ## Switch names if necessary pkg_name = MUNGE_SRC_PKGS.get(pkg_name,pkg_name) - ## src archive is usually a .tar.gz + ## src archive is usually a .tar.zst if pkg_name in SRC_EXT.keys(): src_ext = SRC_EXT[pkg_name] else: commit c2a24dcec8bac200e142badd5ff6ec1542ce38c6 Author: Corwin Brust Date: Wed May 13 00:46:31 2026 -0500 ; update msys2 build helper for Emacs 31 & UCRT diff --git a/admin/nt/dist-build/build-dep-zips.py b/admin/nt/dist-build/build-dep-zips.py index 5c760fb21ce..f70f083b999 100755 --- a/admin/nt/dist-build/build-dep-zips.py +++ b/admin/nt/dist-build/build-dep-zips.py @@ -1,6 +1,6 @@ #!/usr/bin/python3 -## Copyright (C) 2017-2026 Free Software Foundation, Inc. +## Copyright (C) 2017-2023 Free Software Foundation, Inc. ## This file is part of GNU Emacs. @@ -25,12 +25,12 @@ from subprocess import check_output ## Constants -EMACS_MAJOR_VERSION= os.getenv('EMACS_MAJOR_VERSION') or "30" +EMACS_MAJOR_VERSION= os.getenv('EMACS_MAJOR_VERSION') or "31" # Base URI for the package sources mapped in PKG_REQ SRC_REPO="https://repo.msys2.org/mingw/sources" -# Map items in `dynamic-library-alist' to source packages +# Map items in `dynamic-library-alist' to source pakages PKG_REQ='''mingw-w64-x86_64-giflib mingw-w64-x86_64-gnutls mingw-w64-x86_64-harfbuzz @@ -47,8 +47,10 @@ mingw-w64-x86_64-tree-sitter mingw-w64-x86_64-sqlite3'''.split() -# Emacs style path to dependency DLLs on build system -DLL_SRC="c:/msys64/mingw64/bin" +# Emacs style path to dependancy DLLs on build system +#DLL_SRC="c:/msys64/mingw64/bin" +DLL_SRC="mingw64" +OUT_TAG="" # libraries we never include DLL_SKIP=["libgccjit-0.dll"] @@ -95,12 +97,12 @@ def gather_deps(): if dep not in DLL_SKIP: if args.l != True: print("Adding dep", dep) - check_output_maybe(["cp /mingw64/bin/{} .".format(dep)], shell=True) + check_output_maybe(["cp /{}/bin/{} .".format(DLL_SRC,dep)], shell=True) else: if args.l != True: print("Skipping dep", dep) - zipfile="../emacs-{}-{}deps.zip".format(EMACS_MAJOR_VERSION, DATE) + zipfile="../emacs-{}{}-{}deps.zip".format(EMACS_MAJOR_VERSION, OUT_TAG, DATE) tmpfile="{}.tmp".format(zipfile) print("Zipping deps in", os.getcwd(), "as", tmpfile) check_output_maybe("zip -9vr {} *.dll".format(tmpfile), shell=True) @@ -110,7 +112,7 @@ def gather_deps(): print("Deps updated in", os.getcwd(), "as", zipfile) os.chdir("../") -# Return dependencies listed in Emacs +# Return dependancies listed in Emacs def init_deps(): return '''libXpm-nox4.dll libpng16-16.dll @@ -125,11 +127,11 @@ def init_deps(): libgio-2.0-0.dll libgobject-2.0-0.dll libgnutls-30.dll -libxml2-2.dll +libxml2-16.dll zlib1.dll liblcms2-2.dll libgccjit-0.dll -libtree-sitter.dll'''.split() +libtree-sitter-0.26.dll'''.split() # job_args=[NEW_EMACS, "--batch", "--eval", ELISP_PROG] # #print("args: ", job_args) # return subprocess.check_output(job_args, stderr=subprocess.STDOUT @@ -170,78 +172,20 @@ def ntldd_munge(out): ## Packages to fiddle with ## Source for gcc-libs is part of gcc -SKIP_SRC_PKGS=["mingw-w64-gcc-libs"] -SKIP_DEP_PKGS=["mingw-w64-glib2", "mingw-w64-ca-certificates-20211016-3"] +SKIP_SRC_PKGS=["mingw-w64-gcc-libs"] #, "mingw-w64-x86_64-libwinpthread-git"] +SKIP_DEP_PKGS=["mingw-w64-glib2", "mingw-w64-x86_64-cc-libs", "mingw-w64-ca-certificates-20211016-3"] #, "mingw-w64-x86_64-libwinpthread-git"] MUNGE_SRC_PKGS={ - "mingw-w64-libwinpthread-git":"mingw-w64-winpthreads-git", + "mingw-w64-libwinpthread":"mingw-w64-winpthreads", "mingw-w64-gettext-runtime":"mingw-w64-gettext" } MUNGE_DEP_PKGS={ - "mingw-w64-x86_64-libwinpthread":"mingw-w64-x86_64-libwinpthread-git", - "mingw-w64-x86_64-libtre": "mingw-w64-x86_64-libtre-git", + #"mingw-w64-x86_64-libwinpthread":"mingw-w64-x86_64-libwinpthread-git", + #"mingw-w64-x86_64-libtre": "mingw-w64-x86_64-libtre-git", } + +# usual source ext is now tar.zst; this overrides that.. SRC_EXT={ - "mingw-w64-freetype": ".src.tar.zst", - "mingw-w64-fribidi": ".src.tar.zst", - "mingw-w64-glib2": ".src.tar.zst", - "mingw-w64-harfbuzz": ".src.tar.zst", - "mingw-w64-libunistring": ".src.tar.zst", - "mingw-w64-winpthreads-git": ".src.tar.zst", - "mingw-w64-ca-certificates": ".src.tar.zst", - "mingw-w64-libxml2": ".src.tar.zst", - "mingw-w64-ncurses": ".src.tar.zst", - "mingw-w64-openssl": ".src.tar.zst", - "mingw-w64-pango": ".src.tar.zst", - "mingw-w64-python": ".src.tar.zst", - "mingw-w64-sqlite3": ".src.tar.zst", - "mingw-w64-xpm-nox": ".src.tar.zst", - "mingw-w64-xz": ".src.tar.zst", - "mingw-w64-bzip2": ".src.tar.zst", - "mingw-w64-cairo": ".src.tar.zst", - "mingw-w64-expat": ".src.tar.zst", - "mingw-w64-fontconfig": ".src.tar.zst", - "mingw-w64-gdk-pixbuf2": ".src.tar.zst", - "mingw-w64-giflib": ".src.tar.zst", - "mingw-w64-gmp": ".src.tar.zst", - "mingw-w64-gnutls": ".src.tar.zst", - "mingw-w64-graphite2": ".src.tar.zst", - "mingw-w64-jbigkit": ".src.tar.zst", - "mingw-w64-lcms2": ".src.tar.zst", - "mingw-w64-lerc": ".src.tar.zst", - "mingw-w64-libdatrie": ".src.tar.zst", - "mingw-w64-libffi": ".src.tar.zst", - "mingw-w64-libiconv": ".src.tar.zst", - "mingw-w64-libiconv": ".src.tar.zst", - "mingw-w64-libpng": ".src.tar.zst", - "mingw-w64-librsvg": ".src.tar.zst", - "mingw-w64-libsystre": ".src.tar.zst", - "mingw-w64-libtasn": ".src.tar.zst", - "mingw-w64-libthai": ".src.tar.zst", - "mingw-w64-libtiff": ".src.tar.zst", - "mingw-w64-libtre-git": ".src.tar.zst", - "mingw-w64-libwebp": ".src.tar.zst", - "mingw-w64-mpdecimal": ".src.tar.zst", - "mingw-w64-nettle": ".src.tar.zst", - "mingw-w64-p11-kit": ".src.tar.zst", - "mingw-w64-pcre": ".src.tar.zst", - "mingw-w64-pixman": ".src.tar.zst", - "mingw-w64-python-packaging": ".src.tar.zst", - "mingw-w64-readline": ".src.tar.zst", - "mingw-w64-tcl": ".src.tar.zst", - "mingw-w64-termcap": ".src.tar.zst", - "mingw-w64-tk": ".src.tar.zst", - "mingw-w64-tree-sitter": ".src.tar.zst", - "mingw-w64-tzdata": ".src.tar.zst", - "mingw-w64-wineditline": ".src.tar.zst", - "mingw-w64-zlib": ".src.tar.zst", - "mingw-w64-zstd": ".src.tar.zst", - "mingw-w64-brotli": ".src.tar.zst", - "mingw-w64-gettext": ".src.tar.zst", - "mingw-w64-libdeflate": ".src.tar.zst", - "mingw-w64-libidn2": ".src.tar.zst", - "mingw-w64-libjpeg-turbo": ".src.tar.zst", - "mingw-w64-libtasn1": ".src.tar.zst", - "mingw-w64-pcre2": ".src.tar.zst", +# "mingw-w64-brotli": ".src.tar.gz", } ## Currently no packages seem to require this! @@ -296,7 +240,7 @@ def download_source(tarball): ) print("Downloading {}... done".format(tarball)) - print("Copying {} from local".format(tarball)) + #print("Copying {} from local".format(tarball)) shutil.copyfile("../emacs-src-cache/{}".format(tarball), "{}".format(tarball)) @@ -311,6 +255,7 @@ def gather_source(deps): os.chdir("emacs-src") for pkg in deps: + #print("Parsing pkg name and version from {}".format(pkg)) pkg_name_and_version= \ check_output(["pacman","-Q", pkg]).decode("utf-8").strip() @@ -321,7 +266,7 @@ def gather_source(deps): pkg_version=pkg_name_components[1] ## source pkgs don't have an architecture in them - pkg_name = re.sub(r"x86_64-","",pkg_name) + pkg_name = re.sub(r"(ucrt-)?x86_64-","",pkg_name) if(pkg_name in SKIP_SRC_PKGS): continue @@ -333,13 +278,14 @@ def gather_source(deps): if pkg_name in SRC_EXT.keys(): src_ext = SRC_EXT[pkg_name] else: - src_ext = ".src.tar.gz" + src_ext = ".src.tar.zst" tarball = "{}-{}{}".format(pkg_name,pkg_version,src_ext) download_source(tarball) - srczip="../emacs-{}-{}deps-mingw-w64-src.zip".format(EMACS_MAJOR_VERSION,DATE) + srczip="../emacs-{}{}-{}deps-mingw-w64-src.zip".format( + EMACS_MAJOR_VERSION,OUT_TAG, DATE) tmpzip="{}.tmp".format(srczip) print("Zipping Dsrc in", os.getcwd(), "as", tmpzip) check_output_maybe("zip -9 {} *".format(tmpzip), shell=True) @@ -367,6 +313,9 @@ def clean(): #parser.add_argument("emacs", help="emacs executable") +parser.add_argument("-u", help="UCRT64 build", + action="store_true") + parser.add_argument("-s", help="snapshot build", action="store_true") @@ -382,16 +331,21 @@ def clean(): parser.add_argument("-l", help="list dependencies", action="store_true") -parser.add_argument("-e", help="extract direct dependencies", +parser.add_argument("-e", help="extract direct dependancies", action="store_true") args = parser.parse_args() do_all=not (args.c or args.r) + #NEW_EMACS=args.emacs DRY_RUN=args.d +if( args.u ): + DLL_SRC="ucrt64" + OUT_TAG="-ucrt" + if( args.e ): print("\n".join(init_deps())) commit ec7a5f85c934d83239c1a77e5122b288c4cd0b25 Author: F. Jason Park Date: Wed May 1 07:21:01 2024 -0700 Run module setup in ERC query buffers on reconnect * etc/ERC-NEWS: Mention change. * lisp/erc/erc.el (erc-connection-established): Apply `erc--open-target' to all existing query buffers after they've been reassociated by the erc-networks logic. diff --git a/etc/ERC-NEWS b/etc/ERC-NEWS index de27b8f07e7..d28c0670a73 100644 --- a/etc/ERC-NEWS +++ b/etc/ERC-NEWS @@ -16,6 +16,12 @@ GNU Emacs since Emacs version 22.1. ** Changes in the library API. +*** Module setup runs in query buffers on reconnect. +A module's setup would always run in channel buffers on reconnect, due +to channels being rejoined, but query buffers lacked a similar +opportunity to reinitialize their state for the new session. This is no +longer the case. + *** Local modules activate in preferred order instead of in reverse. In recent versions, ERC has enabled local modules in the reverse order of that produced by the "set" function used by 'setopt' and the Custom diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index 6781c3d117d..591ca1f5fbe 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -6840,9 +6840,16 @@ See also: `erc-echo-notice-in-user-buffers', (erc-update-mode-line) (erc-set-initial-user-mode nick buffer) (erc-server-setup-periodical-ping buffer) - (when erc-unhide-query-prompt - (erc-with-all-buffers-of-server erc-server-process nil - (when (and erc--target (not (erc--target-channel-p erc--target))) + ;; Run mode hooks on all reclaimed query buffers. + (let ((buffer (current-buffer)) + (erc-join-buffer 'bury) + erc-active-buffer) + (erc-with-all-buffers-of-server erc-server-process + #'erc-query-buffer-p + (let ((target (erc-target))) + (with-current-buffer buffer + (erc--open-target target))) + (when erc-unhide-query-prompt (erc--unhide-prompt)))) (run-hook-with-args 'erc-after-connect server nick))))) commit 606c0b22e4ee2d01de9bcf064205716dca1ef8b3 Author: F. Jason Park Date: Tue Feb 11 22:02:16 2025 -0800 ; Make reconnect detection more readable in erc-open * lisp/erc/erc.el (erc-open): Bind `erc--server-reconnecting' instead of relying on a confusing single-use variable. diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index 755b00f375c..6781c3d117d 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -2634,13 +2634,18 @@ side effect of setting the current buffer to the one it returns. Use (old-recon-count erc-server-reconnect-count) (old-point nil) (delayed-modules nil) - (continued-session (or erc--server-reconnecting - erc--target-priors - (and-let* (((not target)) - (m (buffer-local-value - 'erc-input-marker buffer)) - ((marker-position m))) - (buffer-local-variables buffer))))) + (erc--server-reconnecting + (or erc--server-reconnecting + ;; Interpret an entry-point invocation reassociated with + ;; an existing session via explicit ID as an "implied" + ;; reconnection, but only if it at least tried to connect. + (and-let* ((id) (connect) ; `target' must be null + (m (buffer-local-value 'erc-input-marker buffer)) + ((marker-position m)) + ((buffer-local-value 'erc-server-process buffer)) + (netid (buffer-local-value 'erc-networks--id buffer))) + (cl-assert (string-equal (erc-networks--id-given netid) id)) + (buffer-local-variables buffer))))) (when connect (run-hook-with-args 'erc-before-connect server port nick)) (set-buffer buffer) (setq old-point (point)) @@ -2705,7 +2710,8 @@ side effect of setting the current buffer to the one it returns. Use (when erc-log-p (get-buffer-create (concat "*ERC-DEBUG: " server "*")))) - (erc--initialize-markers old-point continued-session) + (erc--initialize-markers old-point (or erc--server-reconnecting + erc--target-priors)) (erc-determine-parameters server port nick full-name user passwd) (save-excursion (run-mode-hooks) (dolist (mod (car delayed-modules)) commit 1613f2e65263f01339be4107271d4abcee79edc5 Author: F. Jason Park Date: Sun Sep 21 15:52:16 2025 -0700 Preserve order of local ERC modules for activation * etc/ERC-NEWS: Add new section for ERC 5.7. * lisp/erc/erc.el (erc--update-modules): Reverse returned list. * test/lisp/erc/erc-tests.el (erc--update-modules/local): Update. diff --git a/etc/ERC-NEWS b/etc/ERC-NEWS index 082c10702f3..de27b8f07e7 100644 --- a/etc/ERC-NEWS +++ b/etc/ERC-NEWS @@ -11,6 +11,20 @@ This file is about changes in ERC, the powerful, modular, and extensible IRC (Internet Relay Chat) client distributed with GNU Emacs since Emacs version 22.1. + +* Changes in ERC 5.7 + +** Changes in the library API. + +*** Local modules activate in preferred order instead of in reverse. +In recent versions, ERC has enabled local modules in the reverse order +of that produced by the "set" function used by 'setopt' and the Custom +UI for the option 'erc-modules'. Specifically, Built-in locals were +activated in reverse lexicographic order after third-party ones, which +were simply reversed as given. Now, just like with global modules, ERC +preserves the preferred order when activating local modules for new +sessions. + * Changes in ERC 5.6.2 diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index d14d85913b3..755b00f375c 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -2467,7 +2467,7 @@ invocations by third-party packages.") (defun erc--update-modules (modules) (let (local-modes) - (dolist (module modules local-modes) + (dolist (module modules (nreverse local-modes)) (if-let* ((mode (erc--find-mode module))) (if (custom-variable-p mode) (funcall mode 1) diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el index 35997a83de1..f2f874717e9 100644 --- a/test/lisp/erc/erc-tests.el +++ b/test/lisp/erc/erc-tests.el @@ -3990,7 +3990,7 @@ keyword :result." ;; Returns local modules. (should (equal (mapcar #'symbol-name (erc--update-modules erc-modules)) - '("erc-lo2-mode" "erc-lo1-mode"))) + '("erc-lo1-mode" "erc-lo2-mode"))) ;; Requiring `erc-lo2' defines `erc-lo2-mode'. (should (equal (mapcar #'prin1-to-string (funcall get-calls)) commit 76f5181bc6af50dd7eab6deb75d83fd8e83e50e4 Author: F. Jason Park Date: Fri Nov 28 16:21:57 2025 -0800 Improve source NUH handling in ERC * lisp/erc/erc.el (erc--user-nuh-message-types): New variable. (erc--shuffle-nuh-nickward, erc--interpret-nuh): Replace former with latter, whose behavior is easier to predict. * test/lisp/erc/erc-tests.el (erc--interpret-nuh): New test. diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index 335896db13d..d14d85913b3 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -7991,11 +7991,28 @@ See associated unit test for precise behavior." (match-string 2 string) (match-string 3 string)))) -(defun erc--shuffle-nuh-nickward (nick login host) - "Interpret results of `erc--parse-nuh', promoting loners to nicks." - (cond (nick (cl-assert (null login)) (list nick login host)) - ((and (null login) host) (list host nil nil)) - ((and login (null host)) (list login nil nil)))) +(defvar erc--user-nuh-message-types + '(PRIVMSG JOIN PART QUIT NICK KICK TOPIC AWAY ACCOUNT TAGMSG)) + +(defun erc--interpret-nuh (nuh &optional cmd noerrorp) + "Return new NUH triple with non-nil nickname or host component, or signal. +If CMD is null or appears in `erc--user-nuh-message-types', promote a +lone host to a lone nick. With NOERRORP, return a copy of NUH instead +of signaling." + (pcase-let ((`(,nick ,login ,host) nuh)) + (cond (nick (list nick login host)) + ((and (null login) host) + (if (or (null cmd) (memq cmd erc--user-nuh-message-types)) + (list host nil nil) + (list nil nil host))) + ((and login + (let ((types (or (erc--get-isupport-entry 'CHANTYPES 'single) + erc--fallback-channel-prefixes))) + (not (seq-some (lambda (c) (seq-contains-p types c #'eq)) + login)))) + (list login nil host)) + (noerrorp (list nick login host)) + (t (error "Failed to interpret: %s" nuh))))) (defun erc-extract-nick (string) "Return the nick corresponding to a user specification STRING. diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el index 3900f5d4880..35997a83de1 100644 --- a/test/lisp/erc/erc-tests.el +++ b/test/lisp/erc/erc-tests.el @@ -674,6 +674,39 @@ ;; No fallback behavior. (should-not (erc--parse-nuh "abc\nde!fg@xy"))) +;; NUH interpretation rules: +;; +;; 1. "a@b" or "a!b" - "a" is the nick and "b" is the host. Can't have +;; a login without a nick and a host. +;; +;; 2. "a" - either a nick or a host, depending on message type. The +;; presence of a "." does not imply a host because some IRC-adjacent +;; bridges allow nicks to contain dots, and a host can be a host +;; name, like "localhost" without a domain structure. Nick-only +;; types include PRIVMSG, JOIN, PART, QUIT, NICK, KICK, TOPIC, AWAY, +;; ACCOUNT, and TAGMSG. MODE can be either but is usually a nick +;; unless recovering from a netsplit or as a response to a ChanServ +;; OP. NOTICE can be either but is always a nick when directed to a +;; channel. +;; +;; 3. "a!", "a!@", "a@", "!a@", "@a", etc. are pathological. +;; +(ert-deftest erc--interpret-nuh () + (should (equal (erc--interpret-nuh (erc--parse-nuh "a@b")) + '("a" nil "b"))) + (should (equal (erc--interpret-nuh (erc--parse-nuh "a!b")) + '("a" nil "b"))) + (should (equal (erc--interpret-nuh (erc--parse-nuh "B..o..b")) + '("B..o..b" nil nil))) + (should (equal (erc--interpret-nuh (erc--parse-nuh "gnu.org")) + '("gnu.org" nil nil))) + (should (equal (erc--interpret-nuh (erc--parse-nuh "localhost")) + '("localhost" nil nil))) + + ;; Reject login containing CHANTYPE chars. + (should (equal (erc--parse-nuh "a&b@c") '(nil "a&b" "c"))) + (should-error (erc--interpret-nuh '(nil "a&b" "c")))) + (ert-deftest erc--parsed-prefix () ;; Effectively a no-op in a non-ERC buffer. (should-not (erc--parsed-prefix)) commit aa316285846b4758c57deea13214dd87f41334da Author: F. Jason Park Date: Sun May 3 18:31:40 2026 -0700 Refactor erc--warn-once-before-connect * lisp/erc/erc-button.el (erc-button--display-error-notice-with-keys): Ensure tail of let-bound `erc-insert-post-hook' is a list. * lisp/erc/erc-truncate.el (erc-truncate--warn-about-logging): Use `erc--warn-once-before-connect' instead of calling `erc-button--display-error-notice-with-keys-and-warn' directly. * lisp/erc/erc.el (erc--warn-once-before-connect-function) (erc--warn-once-before-connect-calls): New variables. (erc--warn-once-before-connect): Refactor for readability, incorporating new definitions above for flexibility and to help ensure uniqueness. diff --git a/lisp/erc/erc-button.el b/lisp/erc/erc-button.el index 875ceec111a..3cb2d527cd4 100644 --- a/lisp/erc/erc-button.el +++ b/lisp/erc/erc-button.el @@ -857,7 +857,7 @@ non-strings, concatenate leading string members before applying (cons (lambda () (setq string (buffer-substring (point-min) (1- (point-max))))) - erc-insert-post-hook)) + (ensure-list erc-insert-post-hook))) (erc-button-alist `((,(rx "\\[" (group (+ (not "]"))) "]") 0 erc-button--display-error-with-buttons diff --git a/lisp/erc/erc-truncate.el b/lisp/erc/erc-truncate.el index 8323eb92235..340584e26db 100644 --- a/lisp/erc/erc-truncate.el +++ b/lisp/erc/erc-truncate.el @@ -98,9 +98,11 @@ for other purposes should customize either `erc-enable-logging' or (erc-log--check-legacy-implicit-enabling-by-truncate)) ;; Emit a real Emacs warning because the message may be ;; truncated away before it can be read if merely inserted. - (erc-button--display-error-notice-with-keys-and-warn - "The `truncate' module no longer enables logging implicitly." - " See the doc string for `erc-truncate-mode' for details."))) + (let ((erc--warn-once-before-connect-function + #'erc-button--display-error-notice-with-keys-and-warn)) + (erc--warn-once-before-connect 'erc-truncate-mode + "The `truncate' module no longer enables logging implicitly." + " See the doc string for `erc-truncate-mode' for details.")))) ;;;###autoload (defun erc-truncate-buffer-to-size (size &optional buffer) diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index 6157b0d2777..335896db13d 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -1682,42 +1682,58 @@ capabilities." (add-hook hook fun -95 t) fun)) +(defvar erc--warn-once-before-connect-function + #'erc-button--display-error-notice-with-keys + "Function to display an \"error notice\". +See `erc-button--display-error-notice-with-keys' for expected args.") + +(defvar-local erc--warn-once-before-connect-calls () + "Alist of (INTEGER . t) for `erc--warn-once-before-connect'.") + (defun erc--warn-once-before-connect (mode-var &rest args) - "Display an \"error notice\" once. -Expect ARGS to be `erc-button--display-error-notice-with-keys' -compatible parameters, except without any leading buffers or processes. -If the current buffer has an `erc-server-process', print the notice -immediately. Otherwise, if it's a server buffer without a process, -arrange to do so on `erc-connect-pre-hook'. In non-ERC buffers, so long -as MODE-VAR belongs to a global module, try again at most once the next -time `erc-mode-hook' runs for any connection." + "Display an \"error notice\" once per session (logical connection). +Defer to `erc--warn-once-before-connect-function' to do the displaying. +If the current buffer is associated with a live server buffer and a +non-nil `erc-server-process', even if no longer live, display the +notice. Do so soon rather than immediately if called by +`erc-display-message' indirectly. If in a server buffer that's yet to +dial, arrange to try again on `erc-connect-pre-hook'. Otherwise, in +non-ERC buffers, if MODE-VAR belongs to a global module, try again at +most once the next time `erc-mode-hook' runs anywhere. If a message is +displayed, inhibit duplicates by adding a hash of MODE-VAR and ARGS to +`erc--warn-once-before-connect-calls'." (declare (indent 1)) (cl-assert (stringp (car args))) - (if (derived-mode-p 'erc-mode) - (unless - (or (erc-with-server-buffer ; needs `erc-server-process' - (let ((fn - (lambda (buffer) - (erc-with-buffer (buffer) - (apply #'erc-button--display-error-notice-with-keys - buffer args))))) - (if erc--msg-props - (run-at-time nil nil fn (current-buffer)) - (funcall fn (current-buffer)))) - t) - erc--target) ; unlikely - (let (hook) - (setq hook - (lambda (_) - (remove-hook 'erc-connect-pre-hook hook t) - (apply #'erc-button--display-error-notice-with-keys args))) - (add-hook 'erc-connect-pre-hook hook nil t))) - (when (custom-variable-p mode-var) - (let (hook) - (setq hook (lambda () - (remove-hook 'erc-mode-hook hook) - (apply #'erc--warn-once-before-connect 'erc-fake args))) - (add-hook 'erc-mode-hook hook))))) + (letrec ((warn-fn erc--warn-once-before-connect-function) + (hook-name nil) + (hook-fn + (lambda (&rest _) + (when hook-name + (remove-hook hook-name hook-fn (local-variable-p hook-name))) + (let ((erc--warn-once-before-connect-function warn-fn)) + (apply #'erc--warn-once-before-connect 'erc-fake args))))) + (cond + ((not (derived-mode-p 'erc-mode)) + (when (custom-variable-p mode-var) + (add-hook (setq hook-name 'erc-mode-hook) hook-fn))) + ;; Has a live server buffer with a non-nil `erc-server-process'. + ((erc-with-server-buffer + (let ((do-fn (lambda () + (with-memoization + ;; Use `sxhash' to avoid weak references. + (alist-get (sxhash-equal (cons mode-var args)) + erc--warn-once-before-connect-calls) + (apply warn-fn args) + t)))) ; for side-effects only + (if erc--msg-props ; escape `erc-display-message' call stack + (run-at-time nil nil (lambda (buffer) + (erc-with-buffer (buffer) + (funcall do-fn))) + (current-buffer)) + (funcall do-fn))))) + ;; A server buffer with a null `erc-server-process'. + ((null erc--target) + (add-hook (setq hook-name 'erc-connect-pre-hook) hook-fn 0 t))))) (defun erc-server-buffer () "Return the server buffer for the current buffer's process. commit f3da59a8c55f8fbf3f14589286ee2d8c775de74c Author: F. Jason Park Date: Tue Sep 16 18:43:58 2025 -0700 Improve isolation of some ERC test environments * lisp/erc/erc.el (erc--lwarn): During tests where the variable `erc--warnings-buffer-name' is non-nil, don't display the Warnings buffer, and inhibit messages for the benefit of batch runs. * test/lisp/erc/erc-tests.el (erc--modify-local-map): Protect various hooks from module-setup code. * test/lisp/erc/resources/erc-tests-common.el (erc-tests-common-equal-with-props): Act more like `equal-including-properties' in accepting arbitrary objects rather than just strings. (erc-tests-common-with-global-modules): New macro. (erc-tests-common-frozen-options): New variable. (erc-tests-common-with-frozen-options): New macro. (erc-tests-common-make-server-buf): Accept a buffer for the NAME arg. (erc-tests-common-assert-get-inserted-msg-readonly-with): Instead of shadowing, use macro to protect calling environment from effects of activating global module. diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index d70a1f11ede..6157b0d2777 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -3028,8 +3028,17 @@ message instead, to make debugging easier." (defun erc--lwarn (type level format-string &rest args) "Issue a warning of TYPE and LEVEL with FORMAT-STRING and ARGS." - (let ((message (substitute-command-keys - (apply #'format-message format-string args)))) + (let ((message (with-temp-buffer + (insert (substitute-command-keys + (apply #'format-message format-string args))) + (delete-indentation (point-min) (point-max)) + (buffer-string))) + (inhibit-message (or inhibit-message + (and erc--warnings-buffer-name t))) + (display-buffer-overriding-action + (if erc--warnings-buffer-name + '(display-buffer-no-window (allow-no-window . t)) + display-buffer-overriding-action))) (display-warning type message level erc--warnings-buffer-name))) ;;; Debugging the protocol diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el index 2b8e6b3ecca..3900f5d4880 100644 --- a/test/lisp/erc/erc-tests.el +++ b/test/lisp/erc/erc-tests.el @@ -1429,49 +1429,51 @@ #s(erc--target-channel-local "&Bitlbee" &bitlbee nil))))) (ert-deftest erc--modify-local-map () - (when (and (bound-and-true-p erc-irccontrols-mode) - (fboundp 'erc-irccontrols-mode)) - (erc-irccontrols-mode -1)) - (when (and (bound-and-true-p erc-match-mode) - (fboundp 'erc-match-mode)) - (erc-match-mode -1)) - (let* (calls - (inhibit-message noninteractive) - (cmd-foo (lambda () (interactive) (push 'foo calls))) - (cmd-bar (lambda () (interactive) (push 'bar calls)))) - - (ert-info ("Add non-existing") - (erc--modify-local-map t "C-c C-c" cmd-foo "C-c C-k" cmd-bar) - (with-temp-buffer - (set-window-buffer (selected-window) (current-buffer)) - (use-local-map erc-mode-map) - (execute-kbd-macro "\C-c\C-c") - (execute-kbd-macro "\C-c\C-k")) - (should (equal calls '(bar foo)))) - (setq calls nil) - - (ert-info ("Add existing") ; Attempt to swap definitions fails - (erc--modify-local-map t "C-c C-c" cmd-bar "C-c C-k" cmd-foo) - (with-temp-buffer - (set-window-buffer (selected-window) (current-buffer)) - (use-local-map erc-mode-map) - (execute-kbd-macro "\C-c\C-c") - (execute-kbd-macro "\C-c\C-k")) - (should (equal calls '(bar foo)))) - (setq calls nil) - - (ert-info ("Remove existing") - (erc--modify-local-map nil "C-c C-c" cmd-foo "C-c C-k" cmd-bar) - (with-temp-buffer - (set-window-buffer (selected-window) (current-buffer)) - (use-local-map erc-mode-map) - (cl-letf (((symbol-function 'undefined) - (lambda () - (push (key-description (this-single-command-keys)) - calls)))) - (execute-kbd-macro "\C-c\C-c") - (execute-kbd-macro "\C-c\C-k"))) - (should (equal calls '("C-c C-k" "C-c C-c")))))) + (erc-tests-common-with-frozen-options + (erc-tests-common-with-global-modules (irccontrols match) + (let* ((calls ()) + (erc-mode-map (copy-keymap erc-mode-map)) + (inhibit-message noninteractive) + (cmd-foo (lambda () (interactive) (push 'foo calls))) + (cmd-bar (lambda () (interactive) (push 'bar calls)))) + + (when (bound-and-true-p erc-irccontrols-mode) + (erc-irccontrols-mode -1)) + (when (bound-and-true-p erc-match-mode) + (erc-match-mode -1)) + + (ert-info ("Add non-existing") + (erc--modify-local-map t "C-c C-c" cmd-foo "C-c C-k" cmd-bar) + (with-temp-buffer + (set-window-buffer (selected-window) (current-buffer)) + (use-local-map erc-mode-map) + (execute-kbd-macro "\C-c\C-c") + (execute-kbd-macro "\C-c\C-k")) + (should (equal calls '(bar foo)))) + (setq calls nil) + + (ert-info ("Add existing") ; Attempt to swap definitions fails + (erc--modify-local-map t "C-c C-c" cmd-bar "C-c C-k" cmd-foo) + (with-temp-buffer + (set-window-buffer (selected-window) (current-buffer)) + (use-local-map erc-mode-map) + (execute-kbd-macro "\C-c\C-c") + (execute-kbd-macro "\C-c\C-k")) + (should (equal calls '(bar foo)))) + (setq calls nil) + + (ert-info ("Remove existing") + (erc--modify-local-map nil "C-c C-c" cmd-foo "C-c C-k" cmd-bar) + (with-temp-buffer + (set-window-buffer (selected-window) (current-buffer)) + (use-local-map erc-mode-map) + (cl-letf (((symbol-function 'undefined) + (lambda () + (push (key-description (this-single-command-keys)) + calls)))) + (execute-kbd-macro "\C-c\C-c") + (execute-kbd-macro "\C-c\C-k"))) + (should (equal calls '("C-c C-k" "C-c C-c")))))))) (ert-deftest erc-ring-previous-command-base-case () (ert-info ("Create ring when nonexistent and do nothing") diff --git a/test/lisp/erc/resources/erc-tests-common.el b/test/lisp/erc/resources/erc-tests-common.el index 525bc2ed868..382f2855fbd 100644 --- a/test/lisp/erc/resources/erc-tests-common.el +++ b/test/lisp/erc/resources/erc-tests-common.el @@ -46,12 +46,67 @@ (require 'erc-d-i))) (defmacro erc-tests-common-equal-with-props (a b) - "Compare strings A and B for equality including text props. + "Compare sequences A and B for equality including text props. Use `ert-equal-including-properties' on older Emacsen." - (list (if (< emacs-major-version 29) - 'ert-equal-including-properties - 'equal-including-properties) - a b)) + (if (>= emacs-major-version 29) + `(equal-including-properties ,a ,b) + (list #'named-let 'doit `((a ,a) + (b ,b)) + '(cond ((and (stringp a) (stringp b)) + (ert-equal-including-properties a b)) + ((and (sequencep a) (sequencep b) (= (length a) (length b))) + (seq-every-p (pcase-lambda (`(,a . ,b)) (doit a b)) + (cl-mapcar #'cons a b))) + (t (equal a b)))))) + +(defmacro erc-tests-common-with-global-modules (module &rest body) + "Run BODY with entry state for global MODULE(s) restored on exit." + (declare (indent 1)) + (if (consp module) + ;; Flattening this would make stack traces less noisy but would + ;; also neglect modules that require one another. However, as + ;; yet, there are no global modules that do this. + (setq body `(erc-tests-common-with-global-modules + ,(erc--solo (cdr module)) + ,@body) + module (car module)) + (setq body (macroexp-progn body) + module (erc--normalize-module-symbol module))) + (let ((mode-symbol (intern (concat "erc-" (symbol-name module) "-mode"))) + (value-var (make-symbol "value"))) + `(let ((,value-var (bound-and-true-p ,mode-symbol))) + (unwind-protect + (let ((erc-modules erc-modules)) + ,body) + (unless (eq ,value-var (bound-and-true-p ,mode-symbol)) + (let ((erc--inside-mode-toggle-p t)) + (funcall #',mode-symbol (if ,value-var +1 -1)))))))) + +(defvar erc-tests-common-frozen-options + '(erc-modules + erc-mode-map + erc-mode-hook + erc-insert-pre-hook + erc-insert-modify-hook + erc-insert-post-hook + erc-insert-done-hook + erc-pre-send-functions + erc-send-modify-hook + erc-send-post-hook + erc-send-completed-hook) + "Common insert-hook options and related variables.") + +(defmacro erc-tests-common-with-frozen-options (&rest body) + "Save and compare snapshot of insert-hook options around BODY." + (let ((values-var (make-symbol "values"))) + `(let ((,values-var ())) + (dolist (sym erc-tests-common-frozen-options) + (push (cons sym (sxhash-equal (symbol-value sym))) ,values-var)) + (prog1 (progn ,@body) + (dolist (item ,values-var) + (let ((value (symbol-value (car item)))) + (ert-info ((format "Option %S" (list :s (car item) :v value))) + (should (equal (sxhash-equal value) (cdr item)))))))))) ;; Caller should probably shadow `erc-insert-modify-hook' or populate ;; user tables for erc-button. @@ -94,6 +149,13 @@ Assign the result to `erc-server-process' in the current buffer." (when (buffer-live-p buf) (kill-buffer buf))))))) +;; Note that this fixture is relatively low level. It's not needed +;; merely to call `erc-send-current-line' without emitting anything to +;; the fake server process because the send queue won't run before the +;; test exits. If that's ever not the case, such as when waiting with +;; `sit-for' or similar after `erc-server-send' has run, you can +;; suppress `erc-server-send-queue' by binding `erc-server-flood-margin' +;; to a large negative number. (defun erc-tests-common-with-process-input-spy (test-fn) "Mock `erc-process-input-line' and call TEST-FN. Shadow `erc--input-review-functions' and `erc-pre-send-functions' @@ -126,7 +188,7 @@ recently passed to the mocked `erc-process-input-line'. Make "Return a server buffer named NAME, creating it if necessary. Use NAME for the network and the session server as well." (with-current-buffer (if name - (get-buffer-create name) + (setq name (buffer-name (get-buffer-create name))) (and (string-search "temp" (buffer-name)) (setq name "foonet") (buffer-name))) @@ -247,13 +309,9 @@ For simplicity, assume string evaluates to itself." ;; `erc-tests-common-assert-get-inserted-msg/basic', to work. (defun erc-tests-common-assert-get-inserted-msg-readonly-with (assert-fn test-fn) - (defvar erc-readonly-mode) - (defvar erc-readonly-mode-hook) - (let ((erc-readonly-mode nil) - (erc-readonly-mode-hook nil) - (erc-send-post-hook erc-send-post-hook) - (erc-insert-post-hook erc-insert-post-hook)) - (erc-readonly-mode +1) + (erc-tests-common-with-global-modules readonly + (let ((erc--inside-mode-toggle-p t)) + (erc-readonly-mode +1)) (funcall assert-fn test-fn))) (defun erc-tests--common-display-message (orig &rest args) commit 88e8b8c073e8b1b9e90d26fab2998c863d3bba62 Author: F. Jason Park Date: Tue Sep 16 18:43:58 2025 -0700 ; Remove some forward declarations from ERC tests * lisp/erc/erc.el (erc--insert-before-markers-transplanting-hidden): Don't deliberately return anything because the return value is undefined. The only existing call site, in `erc-insert-line', invokes the function indirectly via `erc--insert-line-function' and discards the result. * test/lisp/erc/erc-scenarios-join-display-context.el: Require `erc-join'. * test/lisp/erc/erc-scenarios-log.el: Remove forward declarations and require `erc-stamp'. * test/lisp/erc/erc-scenarios-match.el: Require `erc-match'. * test/lisp/erc/erc-scenarios-misc.el (erc-scenarios-base-kill-server-track): Suppress unwanted newline in test output. * test/lisp/erc/erc-scenarios-sasl.el (erc-scenarios-sasl--plain-fail): Don't redundantly bind `erc--warnings-buffer-name' or create a buffer for it. Use literal value assigned by test fixture instead. * test/lisp/erc/erc-scenarios-services-misc.el: Require `erc-services'. * test/lisp/erc/erc-tests.el (erc-handle-irc-url): Remove redundant `save-excursion'. (erc-tests--modules): Reflow into single column for easier management of divergent WIP patch sets. This should help minimize manual surgery when applying them atop one another. * test/lisp/erc/resources/erc-scenarios-common.el: Remove unused `require's and forward declarations from top of file. (erc-scenarios-common--make-bindings): Remove the non-existent `erc-auth-source-parameters-join-function' and add `erc--warnings-buffer-name'. The latter's buffer, if created, will be killed automatically when the test body exits. (erc-scenarios-common-with-cleanup): Generate locally scoped defvars to be used in a manner similar to `dlet' for user options yet to be loaded. (erc-scenarios-common--assert-date-stamps): Don't run when `erc-stamp' isn't loaded. diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index e3872d6d36a..d70a1f11ede 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -3749,7 +3749,8 @@ the inserted version of STRING." (new (and before (erc--solo (cl-intersection b a))))) (when new (erc--remove-from-prop-value-list (1- (point)) (point) 'invisible a)) - (prog1 (insert-before-markers string) + (progn + (insert-before-markers string) (when new (erc--merge-prop (1- (point)) (point) 'invisible new))))) diff --git a/test/lisp/erc/erc-scenarios-join-display-context.el b/test/lisp/erc/erc-scenarios-join-display-context.el index 07d7ff1519e..e525eb59f82 100644 --- a/test/lisp/erc/erc-scenarios-join-display-context.el +++ b/test/lisp/erc/erc-scenarios-join-display-context.el @@ -26,6 +26,8 @@ (let ((load-path (cons (ert-resource-directory) load-path))) (require 'erc-scenarios-common))) +(require 'erc-join) + ;; This module uses the list `erc-join--requested-channels' to detect ;; whether a JOIN response was likely triggered by an outgoing JOIN ;; emitted on behalf of `erc-autojoin-channels-alist'. When a related diff --git a/test/lisp/erc/erc-scenarios-log.el b/test/lisp/erc/erc-scenarios-log.el index 7452062e3c5..4f190ae898e 100644 --- a/test/lisp/erc/erc-scenarios-log.el +++ b/test/lisp/erc/erc-scenarios-log.el @@ -28,8 +28,7 @@ (require 'erc-log) (require 'erc-truncate) - -(defvar erc-timestamp-format-left) +(require 'erc-stamp) (ert-deftest erc-scenarios-log--kill-hook () :tags '(:expensive-test) @@ -327,9 +326,6 @@ (funcall expect 1 "loathed enemy") (funcall expect -0.001 "please your lordship"))))) -(defvar erc-insert-timestamp-function) -(declare-function erc-insert-timestamp-left "erc-stamp" (string)) - (ert-deftest erc-scenarios-log--save-buffer-in-logs/truncate-on-save () :tags '(:expensive-test) (with-suppressed-warnings ((obsolete erc-truncate-buffer-on-save)) diff --git a/test/lisp/erc/erc-scenarios-match.el b/test/lisp/erc/erc-scenarios-match.el index bc5ebe27ce9..b22cff18c46 100644 --- a/test/lisp/erc/erc-scenarios-match.el +++ b/test/lisp/erc/erc-scenarios-match.el @@ -24,10 +24,7 @@ (let ((load-path (cons (ert-resource-directory) load-path))) (require 'erc-scenarios-common))) -(eval-when-compile - (require 'erc-join) - (require 'erc-match)) - +(require 'erc-match) (require 'erc-stamp) (require 'erc-fill) diff --git a/test/lisp/erc/erc-scenarios-misc.el b/test/lisp/erc/erc-scenarios-misc.el index e973c912cf9..ef6e2fd3392 100644 --- a/test/lisp/erc/erc-scenarios-misc.el +++ b/test/lisp/erc/erc-scenarios-misc.el @@ -236,7 +236,8 @@ (set-process-query-on-exit-flag erc-server-process nil) (kill-buffer)) (should-not (eq (current-buffer) (get-buffer "#chan"))) ; *temp* - (ert-simulate-command '(erc-track-switch-buffer 1)) ; No longer signals + (let ((inhibit-message noninteractive)) + (ert-simulate-command '(erc-track-switch-buffer 1))) ; doesn't signal (should (eq (current-buffer) (get-buffer "#chan")))))) ;;; erc-scenarios-misc.el ends here diff --git a/test/lisp/erc/erc-scenarios-sasl.el b/test/lisp/erc/erc-scenarios-sasl.el index c7ea3e46997..edd0dcf3fa0 100644 --- a/test/lisp/erc/erc-scenarios-sasl.el +++ b/test/lisp/erc/erc-scenarios-sasl.el @@ -149,8 +149,6 @@ (erc-modules (cons 'sasl erc-modules)) (erc-sasl-password "wrong") (erc-sasl-mechanism 'plain) - (erc--warnings-buffer-name "*ERC test warnings*") - (warnings-buffer (get-buffer-create erc--warnings-buffer-name)) (inhibit-message noninteractive) (expect (erc-d-t-make-expecter))) @@ -164,7 +162,7 @@ (funcall expect 20 "Connection failed!") (should-not (erc-server-process-alive))) - (with-current-buffer warnings-buffer + (with-current-buffer "*ERC test warnings*" (funcall expect 10 "please review SASL settings"))) (when noninteractive diff --git a/test/lisp/erc/erc-scenarios-services-misc.el b/test/lisp/erc/erc-scenarios-services-misc.el index bc6522ae110..6a7d3c2f882 100644 --- a/test/lisp/erc/erc-scenarios-services-misc.el +++ b/test/lisp/erc/erc-scenarios-services-misc.el @@ -24,8 +24,7 @@ (let ((load-path (cons (ert-resource-directory) load-path))) (require 'erc-scenarios-common))) -(eval-when-compile (require 'erc-join) - (require 'erc-services)) +(require 'erc-services) (ert-deftest erc-scenarios-services-password () :tags '(:expensive-test) diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el index dd439e73fc9..2b8e6b3ecca 100644 --- a/test/lisp/erc/erc-tests.el +++ b/test/lisp/erc/erc-tests.el @@ -3602,8 +3602,7 @@ (should-not calls)) (ert-info ("Known network, existing chan with key") - (save-excursion - (with-current-buffer "foonet" (erc--open-target "#chan"))) + (with-current-buffer "foonet" (erc--open-target "#chan")) (erc-handle-irc-url "irc.foonet.org" nil "#chan?sec" nil nil "irc") (should (equal '("#chan" "sec") (pop calls))) (should-not calls)) @@ -3685,12 +3684,45 @@ (should (= 0 (erc-channel-user-status u)))))) (defconst erc-tests--modules - '( autoaway autojoin bufbar button capab-identify - command-indicator completion dcc fill identd - imenu irccontrols keep-place list log match menu move-to-prompt netsplit - networks nickbar nicks noncommands notifications notify page readonly - replace ring sasl scrolltobottom services smiley sound - spelling stamp track truncate unmorse xdcc)) + '(autoaway + autojoin + bufbar + button + capab-identify + command-indicator + completion + dcc + fill + identd + imenu + irccontrols + keep-place + list log + match + menu + move-to-prompt + netsplit + networks + nickbar + nicks + noncommands + notifications + notify + page + readonly + replace + ring + sasl + scrolltobottom + services + smiley + sound + spelling + stamp + track + truncate + unmorse + xdcc)) ;; Ensure that `:initialize' doesn't change the ordering of the ;; members because otherwise the widget's state is "edited". diff --git a/test/lisp/erc/resources/erc-scenarios-common.el b/test/lisp/erc/resources/erc-scenarios-common.el index fa187cc59f7..aa18bbf7b21 100644 --- a/test/lisp/erc/resources/erc-scenarios-common.el +++ b/test/lisp/erc/resources/erc-scenarios-common.el @@ -93,13 +93,6 @@ (require 'erc) -(eval-when-compile (require 'erc-join) - (require 'erc-services) - (require 'erc-fill)) - -(declare-function erc-network "erc-networks") -(defvar erc-network) - (defvar erc-scenarios-common--resources-dir (expand-file-name "../" (ert-resource-directory))) @@ -149,76 +142,96 @@ (auth-source-do-cache nil) (timer-list (copy-sequence timer-list)) (timer-idle-list (copy-sequence timer-idle-list)) - (erc-auth-source-parameters-join-function nil) + ;; This binding exists to protect the default value because ERC + ;; adds all joined channels automatically. (erc-autojoin-channels-alist nil) (erc-server-auto-reconnect nil) (erc-after-connect nil) (erc-last-input-time 0) (erc-d-linger-secs 10) + ;; This buffer, if created by `erc--lwarn', will be killed before + ;; `erc-scenarios-common-with-cleanup' exits. + (erc--warnings-buffer-name "*ERC test warnings*") ,@bindings))) (defmacro erc-scenarios-common-with-cleanup (bindings &rest body) "Provide boilerplate cleanup tasks after calling BODY with BINDINGS. - -If an `erc-d' process exists, wait for it to start before running BODY. -If `erc-autojoin-mode' mode is bound, restore it during cleanup if -disabled by BODY. Other defaults common to these test cases are added -below and can be overridden, except when wanting the \"real\" default -value, which must be looked up or captured outside of the calling form. - -When running tests tagged as serially runnable while interactive -and the flag `erc-scenarios-common--graphical-p' is non-nil, run -teardown tasks normally inhibited when interactive. That is, -behave almost as if `noninteractive' were also non-nil, and -ensure buffers and other resources are destroyed on completion. - -Dialog resource directories are located by expanding the variable -`erc-scenarios-common-dialog' or its value in BINDINGS." +Shadow various options and variables used by ERC with values more +suitable for test purposes. These can be overridden in the \"varlist\" +BINDINGS. Upon exiting, kill buffers and delete processes created by +ERC, as well as any bound to variables in BINDINGS. However, adding +items not referenced in BODY for this purpose alone can confuse readers. + +Avoid taking special care to restore the effect of activating global +modules in BODY. However, as a special case, if the variable +`erc-autojoin-mode' mode is bound, restore its minor-mode activation +state during teardown if modified by BODY. + +Additionally, prepare the environment for an `erc-d' test server. If an +`erc-d' process exists, wait for it to start before running BODY. +Locate dialog resource directories by expanding the variable +`erc-scenarios-common-dialog' or its value in BINDINGS. + +If the flag `erc-scenarios-common--graphical-p' is non-nil and a test is +tagged as interactive-aware, usually via the \"ERC_TESTS_GRAPHICAL\" +environment variable, run teardown tasks normally inhibited when +interactive. That is, behave almost as if `noninteractive' were also +non-nil, and ensure buffers and other resources are destroyed on +completion." (declare (indent 1)) (let* ((orig-autojoin-mode (make-symbol "orig-autojoin-mode")) (combined `((,orig-autojoin-mode (bound-and-true-p erc-autojoin-mode)) - ,@(erc-scenarios-common--make-bindings bindings)))) - - `(erc-d-t-with-cleanup (,@combined) - - (ert-info ("Restore autojoin, etc., kill ERC buffers") + ,@(erc-scenarios-common--make-bindings bindings))) + (dynvars ())) + + ;; Declare "erc-" variables dynamic in test scope. + (dolist (binder combined) + (setq binder (ensure-list binder)) + (when (and (string-prefix-p "erc-" (symbol-name (car binder))) + (not (special-variable-p (car binder)))) + (push `(defvar ,(car binder)) dynvars))) + `(let (_) + ,@dynvars + (erc-d-t-with-cleanup (,@combined) + + (ert-info ("Restore autojoin, etc., kill ERC buffers") + (dolist (buf (buffer-list)) + (when-let* ((erc-d-u--process-buffer) + (proc (get-buffer-process buf))) + (delete-process proc))) + + (erc-scenarios-common--remove-silence) + + (when erc-scenarios-common-extra-teardown + (ert-info ("Running extra teardown") + (funcall erc-scenarios-common-extra-teardown))) + + (erc-buffer-do #'erc-scenarios-common--assert-date-stamps) + (when (and (boundp 'erc-autojoin-mode) + (not (eq erc-autojoin-mode ,orig-autojoin-mode))) + (erc-autojoin-mode (if ,orig-autojoin-mode +1 -1))) + + (when (or noninteractive erc-scenarios-common--graphical-p) + (when noninteractive + (erc-scenarios-common--print-trace)) + (erc-d-t-kill-related-buffers) + (delete-other-windows))) + + (erc-scenarios-common--add-silence) + + (ert-info ("Wait for dumb server") (dolist (buf (buffer-list)) - (when-let* ((erc-d-u--process-buffer) - (proc (get-buffer-process buf))) - (delete-process proc))) - - (erc-scenarios-common--remove-silence) - - (when erc-scenarios-common-extra-teardown - (ert-info ("Running extra teardown") - (funcall erc-scenarios-common-extra-teardown))) - - (erc-buffer-do #'erc-scenarios-common--assert-date-stamps) - (when (and (boundp 'erc-autojoin-mode) - (not (eq erc-autojoin-mode ,orig-autojoin-mode))) - (erc-autojoin-mode (if ,orig-autojoin-mode +1 -1))) - - (when (or noninteractive erc-scenarios-common--graphical-p) - (when noninteractive - (erc-scenarios-common--print-trace)) - (erc-d-t-kill-related-buffers) - (delete-other-windows))) - - (erc-scenarios-common--add-silence) - - (ert-info ("Wait for dumb server") - (dolist (buf (buffer-list)) - (with-current-buffer buf - (when erc-d-u--process-buffer - (erc-d-t-search-for 3 "Starting"))))) + (with-current-buffer buf + (when erc-d-u--process-buffer + (erc-d-t-search-for 3 "Starting"))))) - (ert-info ("Activate erc-debug-irc-protocol") - (unless (and (or noninteractive erc-scenarios-common--graphical-p) - (not erc-debug-irc-protocol)) - (erc-toggle-debug-irc-protocol))) + (ert-info ("Activate erc-debug-irc-protocol") + (unless (and (or noninteractive erc-scenarios-common--graphical-p) + (not erc-debug-irc-protocol)) + (erc-toggle-debug-irc-protocol))) - ,@body))) + ,@body)))) (defvar erc-scenarios-common--term-size '(34 . 80)) (declare-function term-char-mode "term" nil) @@ -336,9 +349,11 @@ See Info node `(emacs) Term Mode' for the various commands." (defun erc-scenarios-common--assert-date-stamps () "Ensure all date stamps are accounted for." - (dolist (stamp erc-stamp--date-stamps) - (should (eq 'datestamp (get-text-property (erc-stamp--date-marker stamp) - 'erc--msg))))) + (defvar erc-stamp--date-stamps) + (when (fboundp 'erc-stamp--date-marker) + (dolist (stamp erc-stamp--date-stamps) + (should (eq 'datestamp (get-text-property (erc-stamp--date-marker stamp) + 'erc--msg)))))) (defun erc-scenarios-common-assert-initial-buf-name (id port) ;; Assert no limbo period when explicit ID given commit 08dc13ea94f13b0801579a87c7332838d22ca6d7 Author: F. Jason Park Date: Mon May 11 22:16:09 2026 -0700 Change ERC version to 5.7-git * doc/misc/erc.texi: Change ERCVER to 5.7 without a "-git" suffix. * lisp/erc/erc.el (erc-version): Change working version to 5.7-git. Do the same for the package "Version" header. Bump required Compat version to 31. Add new 5.7 to Emacs 32.1 mapping in top-level modification of `customize-package-emacs-version-alist'. diff --git a/doc/misc/erc.texi b/doc/misc/erc.texi index ef4b85da485..03767a2e68c 100644 --- a/doc/misc/erc.texi +++ b/doc/misc/erc.texi @@ -3,7 +3,7 @@ @setfilename ../../info/erc.info @settitle ERC Manual @documentlanguage en -@set ERCVER 5.6.2 +@set ERCVER 5.7 @set ERCDIST as distributed with Emacs @value{EMACSVER} @include docstyle.texi @syncodeindex fn cp diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index 6dd60cf82a9..e3872d6d36a 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -12,8 +12,8 @@ ;; David Edmondson (dme@dme.org) ;; Michael Olson (mwolson@gnu.org) ;; Kelvin White (kwhite@gnu.org) -;; Version: 5.6.2 -;; Package-Requires: ((emacs "27.1") (compat "29.1.4.5")) +;; Version: 5.7-git +;; Package-Requires: ((emacs "27.1") (compat "31")) ;; Keywords: IRC, chat, client, Internet ;; URL: https://www.gnu.org/software/emacs/erc.html @@ -70,7 +70,7 @@ (require 'auth-source) (eval-when-compile (require 'subr-x)) -(defconst erc-version "5.6.2" +(defconst erc-version "5.7-git" "This version of ERC.") (defvar erc-official-location @@ -89,7 +89,8 @@ ("5.5" . "29.1") ("5.6" . "30.1") ("5.6.1" . "31.1") - ("5.6.2" . "31.1"))) + ("5.6.2" . "31.1") + ("5.7" . "32.1"))) (defgroup erc nil "Emacs Internet Relay Chat client." commit 3630baae720842152c4d0df6692fabe8b3908203 Author: Elias Gabriel Perez Date: Sun May 10 10:14:21 2026 -0600 hideshow: Support new 'margin' face for margin indicators. (Bug#80693) * lisp/progmodes/hideshow.el (hs-indicator-hide): Remove 'default' face. (hs--make-indicators-overlays): Rework. diff --git a/lisp/progmodes/hideshow.el b/lisp/progmodes/hideshow.el index f781a82b105..44b50584bf4 100644 --- a/lisp/progmodes/hideshow.el +++ b/lisp/progmodes/hideshow.el @@ -299,7 +299,7 @@ use that face for the ellipsis instead." :version "31.1") (defface hs-indicator-hide - '((t :inherit (shadow default))) + '((t :inherit shadow)) "Face used in hideshow indicator to indicate a hidden block." :version "31.1") @@ -1094,14 +1094,16 @@ the overlay: `invisible' `hs'. Also, depending on variable `(left-fringe ,fringe-type ,face-or-icon))) ;; Margins ('margin - (propertize - "+" 'display - `((margin left-margin) - ,(or (plist-get (icon-elements face-or-icon) 'image) - (propertize (icon-string face-or-icon) - 'keymap hs-indicators-map))) - 'face face-or-icon - 'keymap hs-indicators-map)) + (let* ((icon-elements (icon-elements face-or-icon))) + (propertize + "+" 'display + `((margin left-margin) + ,(or (plist-get icon-elements 'image) + (propertize (plist-get icon-elements 'string) + 'face `(,face-or-icon margin) + 'keymap hs-indicators-map))) + 'face `(,face-or-icon margin) + 'keymap hs-indicators-map))) ;; EOL string ('nil (concat commit 20d17df3f4ffc0214cf86f49ed4dc35810bafd7d Author: Juri Linkov Date: Tue May 12 18:42:42 2026 +0300 Use the new 'margin' face in Flymake (bug#80693) * lisp/progmodes/flymake.el (flymake--bs-display): Use the 'margin' face when it's available. diff --git a/lisp/progmodes/flymake.el b/lisp/progmodes/flymake.el index f62f9f5ce3c..823aa4fe673 100644 --- a/lisp/progmodes/flymake.el +++ b/lisp/progmodes/flymake.el @@ -2428,7 +2428,10 @@ symbol `fringes' or the symbol `margins'." `((margin ,flymake-margin-indicator-position) ,(propertize indicator-car - 'face `(:inherit (,(cdr valuelist) default)) + 'face `(:inherit (,(cdr valuelist) + ,(if (facep 'margin) + 'margin + 'default))) 'mouse-face 'highlight 'help-echo "Open Flymake diagnostics" 'keymap (let ((map (make-sparse-keymap))) commit 98c28606d2a67ceab4c6cb03a17eb756e809b31b Author: Andreas Schwab Date: Tue May 12 17:10:35 2026 +0200 ; Fix typo diff --git a/lisp/url/url-cookie.el b/lisp/url/url-cookie.el index 2cc67a030ce..67f6d1e216d 100644 --- a/lisp/url/url-cookie.el +++ b/lisp/url/url-cookie.el @@ -304,7 +304,7 @@ i.e. 1970-1-1) are loaded as expiring one year from now instead." (let ((system-time-locale "C")) (format-time-string "%a %b %d %H:%M:%S %Y GMT" (time-add nil (read max-age)) - t))) + t)))) (setq expires (cdr-safe (assoc-string "expires" args t)))) (while (consp trusted) (if (string-match (car trusted) current-url) commit 306f4d1660829fc4c56d8a56f3a28044dbd99d68 Author: Andreas Schwab Date: Tue May 12 15:47:44 2026 +0200 url-cookie: use C locale when formatting expiry times The expiry time needs to be reconizable by parse-time-string, which is only guaranteed with the C locale. * lisp/url/url-cookie.el (url-cookie-parse-file-netscape): Use C locale for formatted expiry time. (url-cookie-handle-set-cookie): Likewise. diff --git a/lisp/url/url-cookie.el b/lisp/url/url-cookie.el index 153d39213b4..2cc67a030ce 100644 --- a/lisp/url/url-cookie.el +++ b/lisp/url/url-cookie.el @@ -103,12 +103,13 @@ i.e. 1970-1-1) are loaded as expiring one year from now instead." ;; reuse a browser session, so to prevent the ;; cookie from being detected as expired straight ;; away, make it expire a year from now - (expires (format-time-string - "%d %b %Y %T [GMT]" - (let ((s (string-to-number (nth 4 fields)))) - (if (and (zerop s) long-session) - (time-add nil (* 365 24 60 60)) - s)))) + (expires (let ((system-time-locale "C")) + (format-time-string + "%d %b %Y %T [GMT]" + (let ((s (string-to-number (nth 4 fields)))) + (if (and (zerop s) long-session) + (time-add nil (* 365 24 60 60)) + s))))) (key (nth 5 fields)) (val (nth 6 fields))) (incf n) @@ -300,9 +301,10 @@ i.e. 1970-1-1) are loaded as expiring one year from now instead." (expires nil)) (if (and max-age (string-match "\\`-?[0-9]+\\'" max-age)) (setq expires (ignore-errors - (format-time-string "%a %b %d %H:%M:%S %Y GMT" - (time-add nil (read max-age)) - t))) + (let ((system-time-locale "C")) + (format-time-string "%a %b %d %H:%M:%S %Y GMT" + (time-add nil (read max-age)) + t))) (setq expires (cdr-safe (assoc-string "expires" args t)))) (while (consp trusted) (if (string-match (car trusted) current-url) commit 07f2bbc905d8c8ba6fa676db30ed226f2e4e3ae3 Author: Sean Whitton Date: Tue May 12 09:49:53 2026 +0100 vc-dir-resynch-file: Pass down non-truename'd FILE * lisp/vc/vc-dir.el (vc-dir-recompute-file-state): Delete recently introduced TRUENAME parameter. (vc-dir-resynch-file): Pass the file name from before calling file-truename to vc-dir-recompute-file-state. diff --git a/lisp/vc/vc-dir.el b/lisp/vc/vc-dir.el index 3c9222d725f..2d6f8ee97d0 100644 --- a/lisp/vc/vc-dir.el +++ b/lisp/vc/vc-dir.el @@ -1261,12 +1261,9 @@ that file." (vc-dir-fileinfo->state crt-data)) result)) (nreverse result))) -(defun vc-dir-recompute-file-state (fname def-dir &optional truename) - "Compute state of FNAME known to live inside DEF-DIR. -If TRUENAME is non-nil, FNAME is a truename, DEF-DIR not necessarily." - (let* ((file-short (file-relative-name - fname (if truename (file-truename def-dir) def-dir))) - (fname (if truename (expand-file-name file-short def-dir) fname)) +(defun vc-dir-recompute-file-state (fname def-dir) + "Compute state of FNAME known to live inside DEF-DIR." + (let* ((file-short (file-relative-name fname def-dir)) (_remove-me-when-CVS-works (when (eq vc-dir-backend 'CVS) ;; FIXME: Warning: UGLY HACK. The CVS backend caches the state @@ -1309,8 +1306,9 @@ If TRUENAME is non-nil, FNAME is a truename, DEF-DIR not necessarily." (defun vc-dir-resynch-file (&optional fname) "Update the entries for FNAME in any directory buffers that list it." - (let ((file (file-truename (or fname buffer-file-name))) - (drop '())) + (let* ((file (or fname buffer-file-name)) + (file-tn (file-truename file)) + (drop '())) (save-current-buffer ;; look for a vc-dir buffer that might show this file. (dolist (status-buf vc-dir-buffers) @@ -1328,17 +1326,15 @@ If TRUENAME is non-nil, FNAME is a truename, DEF-DIR not necessarily." ;; `default-directory' in order to do its work, ;; but that's irrelevant to us here. (buffer-local-toplevel-value 'default-directory)))) - (when (file-in-directory-p file ddir) - (if (file-directory-p file) + (when (file-in-directory-p file-tn ddir) + (if (file-directory-p file-tn) (progn - (vc-dir-resync-directory-files file) + (vc-dir-resync-directory-files file-tn) (ewoc-set-hf vc-ewoc (vc-dir-headers vc-dir-backend ddir) "")) (let* ((complete-state - ;; Make sure 'vc-dir-recompute-file-state' - ;; knows about the truename nature of 'file' - ;; (bug#80967). - (vc-dir-recompute-file-state file ddir t)) + ;; Pass FILE not FILE-TN here. See bug#80967. + (vc-dir-recompute-file-state file ddir)) (state (cadr complete-state))) (vc-dir-update (list complete-state) commit c68f3237bea2b38a327f891b4517bee4e317daec Author: Michael Albinus Date: Tue May 12 08:48:43 2026 +0200 Fix file-name-non-special implementation of get-file-buffer * lisp/files.el (file-name-non-special): Fix `get-file-buffer'. (Bug#80718) * test/lisp/files-tests.el (files-tests-file-name-non-special--temp-file-prefixes): Extend list. (w32-downcase-file-names): Declare. (files-tests-file-name-non-special-get-file-buffer): Adapt test. diff --git a/lisp/files.el b/lisp/files.el index 9b1fc09fcfa..22313b71635 100644 --- a/lisp/files.el +++ b/lisp/files.el @@ -8742,6 +8742,9 @@ arguments as the running Emacs)." (file-in-directory-p 0 1) (make-symbolic-link 0 1) (add-name-to-file 0 1) + ;; `get-file-buffer' shall simply run the + ;; original function. + (get-file-buffer) ;; These file-notify-* operations take a ;; descriptor. (file-notify-rm-watch) diff --git a/test/lisp/files-tests.el b/test/lisp/files-tests.el index 55011cd461a..822e031e0bf 100644 --- a/test/lisp/files-tests.el +++ b/test/lisp/files-tests.el @@ -401,7 +401,8 @@ be $HOME." (append '("foo" "$foo" "~foo") ;; No amount of quoting will allow creation of a file name ;; with an embedded '*' on MS-Windows and MS-DOS. - (if (not (memq system-type '(windows-nt ms-dos))) '("foo*bar"))) + (if (not (memq system-type '(windows-nt ms-dos))) + '("foo*bar" "foo?bar"))) "Prefixes to be tested for `file-name-non-special' tests.") (ert-deftest files-tests-file-name-non-special--subprocess () @@ -696,6 +697,8 @@ unquoted file names." (tmpdir nospecial-dir t) (should-error (directory-files-and-attributes nospecial-dir)))) +(defvar w32-downcase-file-names) + (ert-deftest files-tests-directory-files-recursively-w32 () "Test MS-Windows specific features of `directory-files-recursively'." (skip-unless (eq system-type 'windows-nt)) @@ -1054,12 +1057,16 @@ unquoted file names." (ert-deftest files-tests-file-name-non-special-get-file-buffer () ;; Make sure these buffers don't exist. (files-tests--with-temp-non-special (tmpfile nospecial) + (find-file-noselect nospecial) (let ((fbuf (get-file-buffer nospecial))) - (if fbuf (kill-buffer fbuf)) + (should (get-file-buffer nospecial)) + (kill-buffer fbuf) (should-not (get-file-buffer nospecial)))) (files-tests--with-temp-non-special-and-file-name-handler (tmpfile nospecial) + (find-file-noselect nospecial) (let ((fbuf (get-file-buffer nospecial))) - (if fbuf (kill-buffer fbuf)) + (should (get-file-buffer nospecial)) + (kill-buffer fbuf) (should-not (get-file-buffer nospecial))))) (ert-deftest files-tests-file-name-non-special-insert-directory () commit 6d347d983480c17293da0d430360d98cfc8bd805 Author: F. Jason Park Date: Mon May 11 11:54:38 2026 -0700 Release ERC 5.6.2 * lisp/erc/erc.el: Change "Version" package header from 5.6.2-git to 5.6.2. The ERCVER variable in doc/misc/erc.texi is already there. Retain `customize-package-emacs-version-alist' mapping to Emacs 31.1 even though master has already advanced to 32.0.50. (erc-version): Change version from 5.6.2-git to 5.6.2. diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index 6facb7966b0..6dd60cf82a9 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -12,7 +12,7 @@ ;; David Edmondson (dme@dme.org) ;; Michael Olson (mwolson@gnu.org) ;; Kelvin White (kwhite@gnu.org) -;; Version: 5.6.2-git +;; Version: 5.6.2 ;; Package-Requires: ((emacs "27.1") (compat "29.1.4.5")) ;; Keywords: IRC, chat, client, Internet ;; URL: https://www.gnu.org/software/emacs/erc.html @@ -70,7 +70,7 @@ (require 'auth-source) (eval-when-compile (require 'subr-x)) -(defconst erc-version "5.6.2-git" +(defconst erc-version "5.6.2" "This version of ERC.") (defvar erc-official-location commit a8f67a1f06700044b0d19413af7f5427b20eda0e Author: F. Jason Park Date: Mon May 11 11:33:55 2026 -0700 Change ERC version for Emacs 31 to 5.6.2.31.1 * doc/misc/erc.texi: Change ERCVER to 5.6.2.31.1. * lisp/erc/erc.el: Change "Version" package header to 5.6.2.31.1. Don't update the `customize-package-emacs-version-alist' entry because this is not a GNU ELPA release. (erc-version): Change version to 5.6.2.31.1. Do not merge to master. diff --git a/doc/misc/erc.texi b/doc/misc/erc.texi index ef4b85da485..5e41ccf0eef 100644 --- a/doc/misc/erc.texi +++ b/doc/misc/erc.texi @@ -3,7 +3,7 @@ @setfilename ../../info/erc.info @settitle ERC Manual @documentlanguage en -@set ERCVER 5.6.2 +@set ERCVER 5.6.2.31.1 @set ERCDIST as distributed with Emacs @value{EMACSVER} @include docstyle.texi @syncodeindex fn cp diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index 6facb7966b0..d37862406e9 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -12,7 +12,7 @@ ;; David Edmondson (dme@dme.org) ;; Michael Olson (mwolson@gnu.org) ;; Kelvin White (kwhite@gnu.org) -;; Version: 5.6.2-git +;; Version: 5.6.2.31.1 ;; Package-Requires: ((emacs "27.1") (compat "29.1.4.5")) ;; Keywords: IRC, chat, client, Internet ;; URL: https://www.gnu.org/software/emacs/erc.html @@ -70,7 +70,7 @@ (require 'auth-source) (eval-when-compile (require 'subr-x)) -(defconst erc-version "5.6.2-git" +(defconst erc-version "5.6.2.31.1" "This version of ERC.") (defvar erc-official-location commit 7eab6ef3cee22c5f3ada55f1a68e29cb3f23da45 Author: Philip Kaludercic Date: Mon May 11 23:43:27 2026 +0200 Fix 'sgml-parse-tag-backward' to handle tags in comments * lisp/textmodes/sgml-mode.el (sgml--find-<>-backward): Ignore SGML tags that happen to occur within comments. This also means that the contents of comments are not indented, but also do not affect the indentation of tags following the comments as well. (Bug#80841) diff --git a/lisp/textmodes/sgml-mode.el b/lisp/textmodes/sgml-mode.el index be14462d1a0..c9f38d1ee47 100644 --- a/lisp/textmodes/sgml-mode.el +++ b/lisp/textmodes/sgml-mode.el @@ -1397,7 +1397,13 @@ Returns t if found, nil otherwise." (while (re-search-backward "[<>]" limit 'move) ;; If this character has "open" or "close" syntax, then we've ;; found the one we want. - (when (memq (syntax-class (syntax-after (point))) '(4 5)) + (when (and (memq (syntax-class (syntax-after (point))) '(4 5)) + ;; We want to ignore tags in comments. We could also + ;; check `syntax-ppss', but that can become expensive + ;; in a busy loop, so we re-use the face instead. + (not (memq (get-text-property (point) 'face) + '(font-lock-comment-delimiter-face + font-lock-comment-face)))) (throw 'found t))))) (defun sgml-parse-tag-backward (&optional limit) commit 09dc864b0b86e6db292b51983db7086d7a5cc53c Author: Aidan Coyle Date: Wed Apr 29 11:17:44 2026 -0500 Fix eww-submit for forms with no action (bug#80918) * lisp/net/eww.el (eww-submit): If a form does not specify an action the assumed action is the current URL. If the current URL has an existing query part, that part must be replaced by the form values, rather than appended to. Copyright-paperwork-exempt: yes diff --git a/lisp/net/eww.el b/lisp/net/eww.el index 70d70185688..9acbaa52fa9 100644 --- a/lisp/net/eww.el +++ b/lisp/net/eww.el @@ -2246,11 +2246,12 @@ Interactively, EVENT is the value of `last-nonmenu-event'." (plist-get eww-data :url))))))) (eww-browse-url (concat - (if (cdr (assq :action form)) - (shr-expand-url (cdr (assq :action form)) (plist-get eww-data :url)) - (plist-get eww-data :url)) - "?" - (mm-url-encode-www-form-urlencoded values)))))) + (shr-expand-url + (or (cdr (assq :action form)) + (car (url-path-and-query (url-generic-parse-url (plist-get eww-data :url))))) + (plist-get eww-data :url)) + "?" + (mm-url-encode-www-form-urlencoded values)))))) (defun eww-browse-with-external-browser (&optional url) "Browse the current URL with an external browser. commit 0e7a24d93131924837f93dd6b3ea27e6585d1bd1 Author: Elias Gabriel Perez Date: Wed Apr 15 10:58:28 2026 -0600 * lisp/progmodes/hideshow.el (hs--set-variable): Use 'set-local' (bug#80999) diff --git a/lisp/progmodes/hideshow.el b/lisp/progmodes/hideshow.el index 34a3fe97da3..f781a82b105 100644 --- a/lisp/progmodes/hideshow.el +++ b/lisp/progmodes/hideshow.el @@ -1228,9 +1228,9 @@ DEFAULT is a value to use as fallback." (val (if (integerp nth) (nth nth old-lookup) (funcall nth old-lookup)))) - (set (make-local-variable var) val) + (set-local var val) (when default - (set (make-local-variable var) default))))) + (set-local var default))))) ;; TODO: When `hs-special-modes-alist' is removed, `hs-grok-mode-type' ;; and `hs--set-variable' will no longer be necessary, but commit f12b01582db7ca834e60b21760d18db3f2c8bf54 Author: Juri Linkov Date: Mon May 11 20:30:15 2026 +0300 Fix Completions buffer disappearing with tmm-menubar (bug#80995) * lisp/minibuffer.el (completions--start-background-update): Cancel a possible leftover timer (e.g. from the previous after-change hook) that would suppress the display of *Completions* when 'completion-eager-update' is nil. (completions--after-change): Don't start background update when not required to automatically update *Completions*. diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el index 636d0951645..e8fb479bc85 100644 --- a/lisp/minibuffer.el +++ b/lisp/minibuffer.el @@ -2791,6 +2791,9 @@ Whether we update the buffer is based on `completion-eager-display' and `eager-display' and `eager-update'. If FORCE-EAGER-UPDATE is non-nil, we only check eager-display." + (when (and force-eager-update completions--background-update-timer) + (cancel-timer completions--background-update-timer) + (setq completions--background-update-timer nil)) (unless completions--background-update-timer (setq completions--background-update-timer (run-with-idle-timer @@ -2811,7 +2814,10 @@ has been requested by the completion table." (when completion-auto-deselect (with-selected-window window (completions--deselect)))) - (completions--start-background-update))) + (when (or completion-in-region-mode + (completions--should-show-p + (completion--field-metadata (minibuffer-prompt-end)))) + (completions--start-background-update)))) (defun minibuffer-completion-help (&optional start end) "Display a list of possible completions of the current minibuffer contents." commit 519fd832111c5d10e67db11866557f2881083449 Author: Michael Albinus Date: Mon May 11 18:11:24 2026 +0200 Fix secrets.el when Emacs is a flatpak * doc/misc/dbus.texi (Flatpak integration): New chapter. * lisp/net/secrets.el (top): Protect against wrong signals in the flatpak case. (Bug#80977) diff --git a/doc/misc/dbus.texi b/doc/misc/dbus.texi index 1f9d571a3b0..8764fcade90 100644 --- a/doc/misc/dbus.texi +++ b/doc/misc/dbus.texi @@ -66,6 +66,7 @@ another. An overview of D-Bus can be found at * Errors and Events:: Errors and events. * Monitoring Messages:: Monitoring messages. * File Descriptors:: Handle file descriptors. +* Flatpak integration:: Integration with flatpak * Index:: Index including concepts, functions, variables. * GNU Free Documentation License:: The license for this documentation. @@ -2302,6 +2303,38 @@ instance have acquired a file descriptor as well. Example: @end defun +@node Flatpak integration +@chapter Integration with flatpak + +@c https://docs.flatpak.org/en/latest/sandbox-permissions.html +@c TODO: This needs more input. + +If you run the Emacs flatpak program, there are restrictions. By +default, there is limited access to the session D-Bus, and no access to +the system D-Bus. You must enable access to services living outside the +sandbox like + +@example +# flatpak override --talk-name=org.freedesktop.secrets org.gnu.emacs +@end example + +@samp{org.gnu.emacs} is the Emacs flatpak application, and +@samp{org.freedesktop.secrets} is a service you want to talk to, for +example. + +Access to the entire bus with @samp{--socket=system-bus} or +@samp{--socket=session-bus} stops the filtering and using them is a +security risk. So they must be avoided. + +@c Bug#80977. +Service names might be mapped when arriving Emacs. For example, you +will see the @samp{org.freedesktop.DBus.NameOwnerChanged} signal for +service @samp{org.freedesktop.portal.Flatpak}, even if you have +registered the signal for another namespace. + +@c TODO: What about portals? + + @node Index @unnumbered Index diff --git a/lisp/net/secrets.el b/lisp/net/secrets.el index 0d585ffa261..973fd6bebab 100644 --- a/lisp/net/secrets.el +++ b/lisp/net/secrets.el @@ -913,11 +913,14 @@ to their attributes." :session dbus-service-dbus dbus-path-dbus dbus-interface-dbus "NameOwnerChanged" (lambda (&rest args) - (when secrets-debug (message "Secret Service has changed: %S" args)) - (setq secrets-session-path secrets-empty-path - secrets-prompt-signal nil - secrets-collection-paths nil)) - secrets-service) + ;; The flatpak version of Emacs shows also signals from + ;; "org.freedesktop.portal.Flatpak". (Bug#80977) + (when (and (stringp (car args)) (string-equal secrets-service (car args))) + (when secrets-debug (message "Secret Service has changed: %S" args)) + (setq secrets-session-path secrets-empty-path + secrets-prompt-signal nil + secrets-collection-paths nil))) + :arg-namespace secrets-service) ;; We want to refresh our cache, when there is a change in ;; collections. commit 9e4ea934f23ffdc988bc10104169b99a44502e20 Author: Philip Kaludercic Date: Mon May 11 15:34:24 2026 +0200 Fix 'prepare-user-lisp' to follow symlinks * lisp/startup.el (prepare-user-lisp): Call 'directory-files-recursively' with a non-nil value for FOLLOW-SYMLINKS. This was the intended way for the function to operate, during the planning phase, so that users could structure their User Lisp directory by linking in Lisp directories from other parts of their file system. diff --git a/lisp/startup.el b/lisp/startup.el index e4c20d4b592..e6f2087604f 100644 --- a/lisp/startup.el +++ b/lisp/startup.el @@ -1265,7 +1265,7 @@ unconditionally." (backup-inhibited t) (dirs (list dir))) (add-to-list 'load-path (directory-file-name dir)) - (dolist (file (directory-files-recursively dir "" t pred)) + (dolist (file (directory-files-recursively dir "" t pred t)) (cond ((and (file-regular-p file) (string-suffix-p ".el" file)) (unless just-activate commit e613e38021e35f30831827cd216dd28378b94950 Author: Philip Kaludercic Date: Fri May 8 13:42:54 2026 +0200 Update "timeout" to 2.1.6 See https://lists.gnu.org/archive/html/emacs-devel/2026-05/msg00033.html. diff --git a/lisp/emacs-lisp/timeout.el b/lisp/emacs-lisp/timeout.el index b5ea819a6e2..5accb5b7e24 100644 --- a/lisp/emacs-lisp/timeout.el +++ b/lisp/emacs-lisp/timeout.el @@ -1,10 +1,10 @@ ;;; timeout.el --- Throttle or debounce Elisp functions -*- lexical-binding: t; -*- -;; Copyright (C) 2023-2026 Free Software Foundation, Inc. +;; Copyright (C) 2023-2026 Free Software Foundation, Inc. ;; Author: Karthik Chikmagalur ;; Keywords: convenience, extensions -;; Version: 2.1 +;; Version: 2.1.6 ;; Package-Requires: ((emacs "24.4")) ;; URL: https://github.com/karthink/timeout @@ -58,6 +58,9 @@ ;;; Code: (require 'nadvice) +(define-obsolete-function-alias 'timeout-throttle! 'timeout-throttle "v2.0") +(define-obsolete-function-alias 'timeout-debounce! 'timeout-debounce "v2.0") + (defsubst timeout--eval-value (value) "Eval a VALUE. If value is a function (either lambda or a callable symbol), eval the @@ -109,9 +112,13 @@ This is intended for use as function advice." "Debounce calls to this function." (prog1 default (if (timerp debounce-timer) - (timer-set-idle-time debounce-timer (timeout--eval-value delay-value)) + (progn + (cancel-timer debounce-timer) + (timer-set-time + debounce-timer (time-add nil (timeout--eval-value delay-value))) + (timer-activate debounce-timer)) (setq debounce-timer - (run-with-idle-timer + (run-with-timer (timeout--eval-value delay-value) nil (lambda (buf) (cancel-timer debounce-timer) @@ -206,7 +213,7 @@ previous successful call is returned." (unless (and throttle-timer (timerp throttle-timer)) (setq result (apply func args)) (setq throttle-timer - (run-with-timer + (run-with-timer (timeout--eval-value throttle-value) nil (lambda () (cancel-timer throttle-timer) @@ -238,9 +245,13 @@ returned." (cadr (interactive-form func)))) (prog1 default (if (timerp debounce-timer) - (timer-set-idle-time debounce-timer (timeout--eval-value delay-value)) + (progn + (cancel-timer debounce-timer) + (timer-set-time + debounce-timer (time-add nil (timeout--eval-value delay-value))) + (timer-activate debounce-timer)) (setq debounce-timer - (run-with-idle-timer + (run-with-timer (timeout--eval-value delay-value) nil (lambda (buf) (cancel-timer debounce-timer) @@ -259,9 +270,13 @@ returned." "\n\nDebounce calls to this function")) (prog1 default (if (timerp debounce-timer) - (timer-set-idle-time debounce-timer (timeout--eval-value delay-value)) + (progn + (cancel-timer debounce-timer) + (timer-set-time + debounce-timer (time-add nil (timeout--eval-value delay-value))) + (timer-activate debounce-timer)) (setq debounce-timer - (run-with-idle-timer + (run-with-timer (timeout--eval-value delay-value) nil (lambda (buf) (cancel-timer debounce-timer) commit 196fd80689e3274352fea165a6d8c63b9257d105 Author: Dmitry Gutov Date: Mon May 11 03:32:29 2026 +0300 [GTK3, HiDPI] Fix width/height round-trip through ConfigureNotify * src/gtkutil.c (xg_frame_set_char_size) (xg_frame_set_size_and_position): Truncate WIDTH and HEIGHT to be multiples of the scale factor (bug#80662). diff --git a/src/gtkutil.c b/src/gtkutil.c index ce91d2a189b..daa3fd1b993 100644 --- a/src/gtkutil.c +++ b/src/gtkutil.c @@ -1181,6 +1181,7 @@ 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); + int scale = xg_get_scale (f); #ifndef HAVE_PGTK gtk_window_get_size (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), @@ -1200,8 +1201,10 @@ xg_frame_set_char_size (struct frame *f, int width, int height) } #endif - outer_height /= xg_get_scale (f); - outer_width /= xg_get_scale (f); + outer_height /= scale; + outer_width /= scale; + height = outer_height * scale; + width = outer_width * scale; xg_wm_set_size_hint (f, 0, 0); @@ -1328,6 +1331,9 @@ xg_frame_set_size_and_position (struct frame *f, int width, int height) outer_height /= scale; outer_width /= scale; + height = outer_height * scale; + width = outer_width * scale; + x /= scale; y /= scale; commit acc07f1a0301e4e797728c170e652290d0721927 Author: Dmitry Gutov Date: Mon May 11 02:03:30 2026 +0300 [GTK3] On Expose, repaint the border before the content * src/xterm.c (handle_one_xevent): Move the x_clear_under_internal_border call before expose_frame, for less chance of implicit flush to screen in between (bug#80662). diff --git a/src/xterm.c b/src/xterm.c index c021d06dd5d..1401693541c 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -19931,14 +19931,14 @@ handle_one_xevent (struct x_display_info *dpyinfo, x_clear_area (f, event->xexpose.x, event->xexpose.y, event->xexpose.width, event->xexpose.height); + /* Paint the border before content (few operations, less + chance for a compositor sync in between). */ + x_clear_under_internal_border (f); #endif expose_frame (f, event->xexpose.x, event->xexpose.y, event->xexpose.width, event->xexpose.height); #ifndef USE_TOOLKIT_SCROLL_BARS x_scroll_bar_handle_exposure (f, (XEvent *) event); -#endif -#ifdef USE_GTK - x_clear_under_internal_border (f); #endif } #ifndef USE_TOOLKIT_SCROLL_BARS commit 5323eebcffcc21bbc5eb681353baee70ef5c236e Author: Pip Cet Date: Sat May 2 16:59:34 2026 +0000 Test read-passwd behavior (bug#80838) * test/lisp/auth-source-tests.el (auth-source-test--displayed-string): (auth-source-test-read-passwd): (auth-source-test-read-passwd-revealed): (auth-source-test-read-passwd-nested): New. diff --git a/test/lisp/auth-source-tests.el b/test/lisp/auth-source-tests.el index 7da3fed70be..9a58d15484d 100644 --- a/test/lisp/auth-source-tests.el +++ b/test/lisp/auth-source-tests.el @@ -571,5 +571,118 @@ machine c1 port c2 user c3 password c4\n" :user '("a" "b") :host '("example.org") :port '("irc" "ftp" "https" 123))))) +(defun auth-source-test--displayed-string (string) + "Apply `display' properties of STRING and return the displayed string." + (let ((i 0) + res) + (while i + (let ((display (get-text-property i 'display string)) + (i0 i)) + (setq i (next-single-property-change i 'display string)) + (if display + (push display res) + (push (substring string i0 i) res)))) + (apply #'concat (nreverse res)))) + +(ert-deftest auth-source-test-read-passwd () + "Check that a password read with `read-passwd' isn't visible by default." + (let* ((cursor-in-echo-area t) + (screenshot (intern "ert--screenshot")) + (keys `[,@"secret" + ;; fake input event to capture the current minibuf string + ,screenshot + ;; leave outer prompt + ,@(kbd "RET")]) + (minibuffer-string nil) + (command-screenshot + (lambda () + (interactive) + (setq minibuffer-string (buffer-string))))) + (unwind-protect + (progn + (define-key global-map `[,screenshot] command-screenshot) + (ert-simulate-keys keys + (should (equal (read-passwd "Test: ") "secret")))) + (define-key global-map `[,screenshot] command-screenshot t)) + ;; check that the secret's there + (should (equal "Test: secret" minibuffer-string)) + ;; now simulate what redisplay does to hide the password + (setq minibuffer-string + (auth-source-test--displayed-string minibuffer-string)) + ;; check that the secret is not visible + (should (equal "Test: ******" minibuffer-string)))) + +(ert-deftest auth-source-test-read-passwd-revealed () + "Check that a password read with `read-passwd' can be made visible." + (let* ((cursor-in-echo-area t) + (screenshot (intern "ert--screenshot")) + (keys `[,@"secret" + ;; TAB: reveals the password + ,@(kbd "TAB") + ;; fake input event to capture the current minibuf string + ,screenshot + ;; leave outer prompt + ,@(kbd "RET")]) + (minibuffer-string nil) + (command-screenshot + (lambda () + (interactive) + (setq minibuffer-string (buffer-string))))) + (unwind-protect + (progn + (define-key global-map `[,screenshot] command-screenshot) + (ert-simulate-keys keys + (should (equal (read-passwd "Test: ") "secret")))) + (define-key global-map `[,screenshot] command-screenshot t)) + ;; check that the secret's there + (should (equal "Test: secret" minibuffer-string)) + ;; now simulate what redisplay does to hide the password + (setq minibuffer-string + (auth-source-test--displayed-string minibuffer-string)) + ;; check that the secret is visible once more + (should (equal "Test: secret" minibuffer-string)))) + +(ert-deftest auth-source-test-read-passwd-nested () + "Check that nested `read-passwd' calls do not reveal the password." + (let* ((cursor-in-echo-area t) + (trigger-nested (intern "ert--trigger-nested")) + (screenshot (intern "ert--screenshot")) + (keys `[,@"secret" + ;; fake input event to trigger a nested prompt + ,trigger-nested + ,@"SECRET" + ;; leave nested prompt + ,@(kbd "RET") + ;; fake input event to capture the current minibuf string + ,screenshot + ;; leave outer prompt + ,@(kbd "RET")]) + (inner-password nil) + (command-trigger-nested + (lambda () + (interactive) + (setq inner-password (read-passwd "inner prompt: ")))) + (minibuffer-string nil) + (command-screenshot + (lambda () + (interactive) + (setq minibuffer-string (buffer-string))))) + (unwind-protect + (progn + (define-key global-map `[,screenshot] command-screenshot) + (define-key global-map `[,trigger-nested] command-trigger-nested) + (ert-simulate-keys keys + (should (equal (read-passwd "Test: ") "secret")))) + (define-key global-map `[,screenshot] command-screenshot t) + (define-key global-map `[,trigger-nested] command-trigger-nested t)) + (should (equal inner-password "SECRET")) + ;; check that the secret's there + (should (equal "Test: secret" minibuffer-string)) + ;; now simulate what redisplay does to hide the password + (setq minibuffer-string + (auth-source-test--displayed-string minibuffer-string)) + ;; check that the secret has been hidden + (should (equal "Test: ******" minibuffer-string)))) + (provide 'auth-source-tests) ;;; auth-source-tests.el ends here commit 01c5990dd064211a27022a94ee0ec3fd8b74ce06 Author: Pip Cet Date: Sat May 2 16:58:38 2026 +0000 Fix nested read-passwd calls (bug#80838) Calls to 'read-passwd' may be nested. The old code didn't handle that, because some of the state was global and we'd end up revealing passwords. The new code still has global state, but it has been changed so that we hide rather than reveal passwords when we enter or leave a nested read-passwd prompt. * lisp/auth-source.el (read-passwd--hide-password): Removed. (read-passwd--password-hidden): New. (read-passwd-toggle-visibility): Add optional FORCE argument. (read-passwd--mini-buffers): New variable. (read-passwd-mode): Don't modify mode line when nested. Hide password when returning to nested minibuffer or entering a new one. diff --git a/lisp/auth-source.el b/lisp/auth-source.el index 6cdea11c1ee..bc660b2f4ab 100644 --- a/lisp/auth-source.el +++ b/lisp/auth-source.el @@ -2602,14 +2602,14 @@ point is moved into the passwords (see `authinfo-hide-elements'). (defvar read-passwd--mode-line-icon nil "Propertized mode line icon for showing/hiding passwords.") -(defvar read-passwd--hide-password t - "Toggle whether password should be hidden in minibuffer.") +(defvar read-passwd--password-hidden nil + "Flag indicating whether password in minibuffer is hidden.") (defun read-passwd--hide-password () "Make password in minibuffer hidden or visible." (let ((beg (minibuffer-prompt-end))) (dotimes (i (1+ (- (buffer-size) beg))) - (if read-passwd--hide-password + (if read-passwd--password-hidden (put-text-property (+ i beg) (+ 1 i beg) 'display (string (or read-hide-char ?*))) (remove-list-of-text-properties (+ i beg) (+ 1 i beg) '(display))) @@ -2617,9 +2617,10 @@ point is moved into the passwords (see `authinfo-hide-elements'). (+ i beg) (+ 1 i beg) 'help-echo "C-u: Clear password\nTAB: Toggle password visibility")))) -(defun read-passwd-toggle-visibility () +(defun read-passwd-toggle-visibility (&optional force) "Toggle minibuffer contents visibility. -Adapt also mode line." +Adapt also mode line. If optional FORCE is non-nil, hide the minibuffer +contents." (interactive) (let ((win (active-minibuffer-window))) (unless win (error "No active minibuffer")) @@ -2627,12 +2628,13 @@ Adapt also mode line." ;; mini-buffer. (with-current-buffer (window-buffer win) (when (memq 'read-passwd-mode local-minor-modes) - (setq read-passwd--hide-password (not read-passwd--hide-password)) + (setq read-passwd--password-hidden + (or force (not read-passwd--password-hidden))) (setq read-passwd--mode-line-icon `(:propertize ,(if icon-preference (icon-string - (if read-passwd--hide-password + (if read-passwd--password-hidden 'read-passwd--show-password-icon 'read-passwd--hide-password-icon)) "") @@ -2652,6 +2654,9 @@ Adapt also mode line." "C-u" #'delete-minibuffer-contents ;bug#12570 "TAB" #'read-passwd-toggle-visibility) +(defvar read-passwd--mini-buffers nil + "List of minibuffers where `read-passwd' is active.") + (define-minor-mode read-passwd-mode "Toggle visibility of password in minibuffer." :group 'mode-line @@ -2659,21 +2664,25 @@ Adapt also mode line." :keymap read-passwd-map :version "30.1" - (setq read-passwd--hide-password nil) - (or global-mode-string (setq global-mode-string '(""))) - - (let ((mode-string '(:eval read-passwd--mode-line-icon))) - (if read-passwd-mode - ;; Add `read-passwd--mode-line-icon'. - (or (member mode-string global-mode-string) - (setq global-mode-string - (append global-mode-string (list mode-string)))) - ;; Remove `read-passwd--mode-line-icon'. - (setq global-mode-string - (delete mode-string global-mode-string)))) - + (unless read-passwd-mode + (setq read-passwd--mini-buffers + (delq (current-buffer) read-passwd--mini-buffers))) + (unless read-passwd--mini-buffers + (let ((mode-string '(:eval read-passwd--mode-line-icon))) + (if read-passwd-mode + ;; Add `read-passwd--mode-line-icon'. + (or (member mode-string global-mode-string) + (setq global-mode-string + (append global-mode-string (list mode-string)))) + ;; Remove `read-passwd--mode-line-icon'. + (setq global-mode-string + (delete mode-string global-mode-string))))) (when read-passwd-mode - (read-passwd-toggle-visibility))) + (push (current-buffer) read-passwd--mini-buffers)) + ;; Always hide the current password. + (when read-passwd--mini-buffers + (with-current-buffer (car read-passwd--mini-buffers) + (read-passwd-toggle-visibility t)))) (defvar overriding-text-conversion-style) commit 027043df257848cb79390f4585a19e478a86568b Author: Augusto Stoffel Date: Sun May 10 15:09:05 2026 +0200 ; * lisp/gnus/message.el (message-server-alist): Doc fix (bug#80880). diff --git a/lisp/gnus/message.el b/lisp/gnus/message.el index 0879f3be1b4..671c3fdc1bc 100644 --- a/lisp/gnus/message.el +++ b/lisp/gnus/message.el @@ -4401,6 +4401,9 @@ a non-nil value when called in the message buffer without any arguments. If METHOD is nil in this case, the return value of the function will be inserted instead. +For an explanation of the \"X-Message-SMTP-Method\" header, see +Info node `(message) Mail Variables'. + Note: if the buffer already has a \"X-Message-SMTP-Method\" header, these rules are ignored, and the header is left unchanged." commit 66729f3e5080f8853393e5f88ce6f062b45164b7 Author: Eshel Yaron Date: Fri May 8 12:45:00 2026 +0200 New variable 'completion-frontend-properties' (bug#80990) Allow completion "frontends" to provide extra information that the backends they call can use to adjust or optimize their behavior. See some relevant discussion at https://yhetil.org/emacs/jwv7bpl28y6.fsf-monnier+emacs@gnu.org/ * lisp/minibuffer.el (completion-frontend-properties): New variable. (completion-lazy-hilit-p): New function. (completion-hilit-commonality, completion-lazy-hilit) (completion-pcm--hilit-commonality) (completion-flex-all-completions): Use it instead of checking the 'completion-lazy-hilit' variable directly. * lisp/completion-preview.el (completion-preview--capf-wrapper): Bind 'completion-frontend-properties'. (completion-preview--try-table): Add comment. * etc/NEWS: Announce 'completion-frontend-properties'. diff --git a/etc/NEWS b/etc/NEWS index edb682a4968..73a4ad72180 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -45,6 +45,13 @@ applies, and please also update docstrings as needed. * Lisp Changes in Emacs 32.1 +--- +** New variable 'completion-frontend-properties'. +This variable generalizes the 'completion-lazy-hilit' variable added in +Emacs 30. It allows Lisp programs that present completion candidates +("completion frontends") to provide additional information which can be +used to adjust or optimize completion candidates computation. + * Changes in Emacs 32.1 on Non-Free Operating Systems diff --git a/lisp/completion-preview.el b/lisp/completion-preview.el index ff348ebf9af..c38bcd70654 100644 --- a/lisp/completion-preview.el +++ b/lisp/completion-preview.el @@ -499,7 +499,12 @@ candidates or if there are multiple matching completions and (sort-fn (or (completion-metadata-get md 'cycle-sort-function) (completion-metadata-get md 'display-sort-function) completion-preview-sort-function)) - (all (let ((completion-lazy-hilit t) + (all (let (;; This is somewhat redundant since we also specify + ;; non-nil `lazy-highlight' in + ;; `completion-frontend-properties', but we keep it + ;; for compatibility with backends that do not know + ;; about `completion-frontend-properties' yet. + (completion-lazy-hilit t) ;; FIXME: This does not override styles prescribed ;; by the completion category via ;; e.g. `completion-category-defaults'. @@ -525,7 +530,9 @@ candidates or if there are multiple matching completions and (defun completion-preview--capf-wrapper (capf) "Translate return value of CAPF to properties for completion preview overlay." - (let ((res (ignore-errors (funcall capf)))) + (let* ((completion-frontend-properties '((no-annotations . t) + (lazy-highlight . t))) + (res (ignore-errors (funcall capf)))) (and (consp res) (not (functionp res)) (seq-let (beg end table &rest plist) res diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el index 636d0951645..744e20cf444 100644 --- a/lisp/minibuffer.el +++ b/lisp/minibuffer.el @@ -2586,7 +2586,7 @@ and with BASE-SIZE appended as the last element." com-str-len (1+ com-str-len) 'face 'completions-first-difference str)) str))) - (if completion-lazy-hilit + (if (completion-lazy-hilit-p) (setq completion-lazy-hilit-fn hilit-fn) (setq completions (mapcar @@ -2604,6 +2604,11 @@ and with BASE-SIZE appended as the last element." completions))) (nconc completions base-size)))) +(defun completion-lazy-hilit-p () + "Return non-nil if the completion frontend supports lazy highlighting." + (or completion-lazy-hilit + (alist-get 'lazy-highlight completion-frontend-properties))) + (defun display-completion-list (completions &optional common-substring group-fun) "Display the list of completions, COMPLETIONS, using `standard-output'. Each element may be just a symbol or string @@ -3198,6 +3203,23 @@ Also respects the obsolete wrapper hook `completion-in-region-functions'. (delq (assq 'completion-in-region-mode minor-mode-map-alist) minor-mode-map-alist)) +(defvar completion-frontend-properties nil + "Alist of properties describing the current completion frontend. + +Frontends may let-bind this variable while calling a completion backend +to provide information which the backend can use to optimize or adjust +its behavior. More specifically, frontends should bind this variable +when calling `completion-try-completion' or `completion-all-completions'. + +Currently known frontend properties are: + +- \\+`no-annotations': if non-nil, the frontend ignores any + `annotation-function'/`affixation-function'. + +- \\+`lazy-highlight': If non-nil, the front-end does not require + `completion-all-completions' completions to be highlighted and knows + to call the function `completion-lazy-hilit' as needed instead.") + (defvar completion-at-point-functions '(tags-completion-at-point-function) "Special hook to find the completion table for the entity at point. Each function on this hook is called in turn without any argument and @@ -4501,9 +4523,10 @@ strings with the `face' property.") (defun completion-lazy-hilit (str) "Return a copy of completion candidate STR that is `face'-propertized. -See documentation of the variable `completion-lazy-hilit' for more -details." - (if (and completion-lazy-hilit completion-lazy-hilit-fn) +Apply `completion-lazy-hilit-fn' if it is set and the frontend supports +lazy highlighting (see `completion-lazy-hilit-p'), otherwise return STR +as is." + (if (and (completion-lazy-hilit-p) completion-lazy-hilit-fn) (funcall completion-lazy-hilit-fn (copy-sequence str)) str)) @@ -4556,7 +4579,7 @@ see) for later lazy highlighting." (re (completion-pcm--segments->regex segments 'group)) (point-idx (completion-pcm--segments-point-idx segments))) (setq completion-pcm--regexp re) - (cond (completion-lazy-hilit + (cond ((completion-lazy-hilit-p) (setq completion-lazy-hilit-fn (lambda (str) (completion--hilit-from-re str re point-idx))) completions) @@ -5036,7 +5059,7 @@ usual. Returns (ALL PAT PREFIX SUFFIX)." (1+ special-match) (+ 2 special-match) 'completions-first-difference nil str)))) str)) - (unless completion-lazy-hilit + (unless (completion-lazy-hilit-p) (setq all (mapcar completion-lazy-hilit-fn all))) ;; Store pattern for adjust-metadata to use (setq completion-flex--pattern-str pattern-str) commit 3b608b233edb87eacbd50b6afc9d3273d23b1e24 Author: Pip Cet Date: Wed May 6 18:09:25 2026 +0000 Fix terminal emulation of "ESC [ K" sequence * lisp/term.el (term-erase-in-line): Don't immediately delete the newly inserted characters. diff --git a/lisp/term.el b/lisp/term.el index 15ba310a73a..79697170338 100644 --- a/lisp/term.el +++ b/lisp/term.el @@ -4128,10 +4128,11 @@ all pending output has been dealt with.")) ;; contain a space, to force the previous line to continue to wrap. ;; We could do this always, but it seems preferable to not add the ;; extra space when wrapped is false. - (when wrapped - (insert-before-markers ? )) - (insert-before-markers ?\n) - (delete-region saved-point (point))) + (let ((deletion-point (point))) + (when wrapped + (insert-before-markers ? )) + (insert-before-markers ?\n) + (delete-region saved-point deletion-point))) (put-text-property saved-point (point) 'font-lock-face 'default) (goto-char saved-point)))) commit 6a605c65a836d70b5765b173e1a706137554d6d6 Author: Eli Zaretskii Date: Sat May 9 14:59:54 2026 +0300 Fix vertical-motion across overlay strings with embedded newlines * src/indent.c (Fvertical_motion): Handle the case of an overlay string on invisible text at point. (Bug#80989) diff --git a/src/indent.c b/src/indent.c index 9721c95dcf7..e8513fbf6f2 100644 --- a/src/indent.c +++ b/src/indent.c @@ -2295,6 +2295,7 @@ buffer, whether or not it is currently displayed in some window. */) double start_col UNINIT; int start_x UNINIT; int to_x = -1; + ptrdiff_t ovl_start = -1; bool start_x_given = !NILP (cur_col); if (start_x_given) @@ -2327,13 +2328,29 @@ buffer, whether or not it is currently displayed in some window. */) { const char *s = SSDATA (it.string); const char *e = s + SBYTES (it.string); + Lisp_Object prop; + ptrdiff_t ovl_idx = + it.current.overlay_string_index >= 0 + ? it.current.overlay_string_index % OVERLAY_STRING_CHUNK_SIZE + : -1; + + /* If this is a string from an overlay, compute where that + overlay starts. */ + if (!it.string_from_display_prop_p && ovl_idx >= 0) + ovl_start = OVERLAY_START (it.string_overlays[ovl_idx]); disp_string_at_start_p = /* If it.area is anything but TEXT_AREA, we need not bother about the display string, as it doesn't affect cursor positioning. */ it.area == TEXT_AREA - && it.string_from_display_prop_p + && (it.string_from_display_prop_p + /* Overlay string on invisible text has the same effect + on display and cursor movement as a display string. */ + || (ovl_start >= BEGV + && (prop = Fget_char_property (make_fixnum (ovl_start), + Qinvisible, window), + TEXT_PROP_MEANS_INVISIBLE (prop)))) /* A display string on anything but buffer text (e.g., on an overlay string) doesn't affect cursor positioning. */ && (it.sp > 0 && it.stack[it.sp - 1].method == GET_FROM_BUFFER); @@ -2456,6 +2473,13 @@ buffer, whether or not it is currently displayed in some window. */) if ((nlines < 0 && IT_CHARPOS (it) > BEGV) || (nlines == 0 && !(start_x_given && start_x <= to_x))) move_it_by_lines (&it, max (PTRDIFF_MIN, nlines)); + /* If we haven't moved due to an overlay string on invisible + text, back up past that overlay string. */ + if (IT_CHARPOS (it) == it_start + && disp_string_at_start_p + && ovl_start >= BEGV + && it_overshoot_count > 0) + move_it_by_lines (&it, -it_overshoot_count); } else if (overshoot_handled) { commit e4d529c67b6a751c3a0858b057afafd1b37cd30a Author: Michael Albinus Date: Sat May 9 13:17:06 2026 +0200 ; Fix last change * doc/misc/gnus.texi (System Sleep Integration): Move @anchor up. * etc/NEWS: gnus-dbus.el is obsolete. Presentational fixes and improvements. * lisp/gnus/gnus-start.el (gnus-close-on-sleep): Add :version. (gnus-sleep-handler): Use `ignore-errors'. diff --git a/doc/misc/gnus.texi b/doc/misc/gnus.texi index fffe81eac06..fa436e0b87d 100644 --- a/doc/misc/gnus.texi +++ b/doc/misc/gnus.texi @@ -26680,11 +26680,11 @@ CloudSynchronizationDataPack(TM)s. It's easiest to set this from the Server buffer (@pxref{Gnus Cloud Setup}). @end defvar +@c Section name changed from this in Emacs 31. @c +@c This anchor allows old links to continue working. @c +@anchor{D-Bus Integration} @node System Sleep Integration @section System Sleep Integration -@c Section name changed from this in Emacs 31. @c -@c This anchor allows old links to continue working. @c -@anchor{D-Bus Integration} @cindex system sleep @cindex closing servers automatically @cindex hung connections diff --git a/etc/NEWS b/etc/NEWS index 1fe24bd61e4..c0e05deed2e 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -795,7 +795,7 @@ docstring for the new option. See the file "etc/ORG-NEWS" for user-visible changes in Org. +++ -** New user option 'compilation-search-extra-path' +** New user option 'compilation-search-extra-path'. compile.el will now use paths specified in both 'compilation-search-extra-path' and 'compilation-search-path', when doing search. 'compilation-search-extra-path' is consulted first. @@ -1967,13 +1967,16 @@ Gnus, see "(gnus) Symbolic Prefixes" in the Gnus manual. --- *** Sorting selected groups is now possible with 'gnus-topic-mode'. +--- +*** gnus-dbus.el is now obsolete. + +++ *** System sleep integration is now independent of D-Bus. The system sleep integration previously provided by customizing the variable 'gnus-dbus-close-on-sleep' is now deprecated. A new system -using the builtin sleep library is now available by customizing -'gnus-close-on-sleep'. This will work on all systems that the sleep -library supports. +using the builtin 'system-sleep' library is now available by customizing +'gnus-close-on-sleep'. This will work on all systems that the +'system-sleep' library supports. ** Sieve @@ -2819,7 +2822,7 @@ another branch. --- *** VC Annotate for Mercurial repositories shows changeset hashes. To restore showing revision numbers instead of changeset hashes, -customize the new option vc-hg-annotate-show-revision-numbers to +customize the new user option 'vc-hg-annotate-show-revision-numbers' to non-nil. +++ @@ -4613,7 +4616,7 @@ singleton list. By default it kills Emacs, as before, but 'kill-emacs-on-sigint' can be set to nil to change that. The response to SIGINT in interactive sessions is unaffected, -e.g. in a normal GUI session it still kills Emacs whereas in a terminal +e.g., in a normal GUI session it still kills Emacs whereas in a terminal it causes 'quit' since it is used for 'C-g'. +++ @@ -4622,6 +4625,7 @@ While it is marginally more efficient than ':after' or ':before', the main purpose is to make the intention more obvious when the advice modifies only the interactive form and not the actual behavior of the function. + * Changes in Emacs 31.1 on Non-Free Operating Systems diff --git a/lisp/gnus/gnus-start.el b/lisp/gnus/gnus-start.el index ba308990407..62faa26468d 100644 --- a/lisp/gnus/gnus-start.el +++ b/lisp/gnus/gnus-start.el @@ -734,6 +734,7 @@ the first newsgroup." (defcustom gnus-close-on-sleep nil "When non-nil, close Gnus servers on system sleep." + :version "31.1" :type 'boolean :group 'gnus-start) @@ -744,9 +745,7 @@ See `gnus-close-on-sleep' to enable this functionality. SLEEP-EVENT is checked to ensure this is only run before sleep." (when (and (eq 'pre-sleep (sleep-event-state sleep-event)) (gnus-alive-p)) - (condition-case nil - (gnus-close-all-servers) - (error nil)))) + (ignore-errors (gnus-close-all-servers)))) (defun gnus-no-server-1 (&optional arg child) "Read network news. commit d54faa0f1bff14a8a8f79bba8fa9d496f5ff4544 Author: Morgan Smith Date: Wed May 6 20:37:37 2026 -0400 Mark gnus-dbus.el as obsolete This functionality has been replaced by the new sleep library which supports more then just DBUS systems. * lisp/obsolete/gnus-dbus.el: Add Obsolete-since header. Add commentary. (gnus-dbus-close-on-sleep, gnus-dbus-sleep-registration-object) (gnus-dbus-register-sleep-signal gnus-dbus-sleep-handler) (gnus-dbus-unregister-sleep-signal): Mark as obsolete. diff --git a/lisp/obsolete/gnus-dbus.el b/lisp/obsolete/gnus-dbus.el index a985e44c5b8..a62cf8a0f97 100644 --- a/lisp/obsolete/gnus-dbus.el +++ b/lisp/obsolete/gnus-dbus.el @@ -3,6 +3,7 @@ ;; Copyright (C) 2020-2026 Free Software Foundation, Inc. ;; Author: Eric Abrahamsen +;; Obsolete-since: 31.1 ;; This file is part of GNU Emacs. @@ -21,6 +22,10 @@ ;;; Commentary: +;; This library is obsolete. +;; +;; Use `gnus-close-on-sleep' instead. + ;; This library contains some Gnus integration for systems using DBUS. ;; At present it registers a signal to close all Gnus servers before ;; system sleep or hibernation. @@ -31,17 +36,29 @@ (require 'dbus) (declare-function gnus-close-all-servers "gnus-start") -(defcustom gnus-dbus-close-on-sleep nil - "When non-nil, close Gnus servers on system sleep." - :group 'gnus-dbus - :type 'boolean) +;; (defcustom gnus-dbus-close-on-sleep nil +;; "When non-nil, close Gnus servers on system sleep." +;; :group 'gnus-dbus +;; :type 'boolean) + +;; It is suggested in the elisp documention that we create variable +;; alias's before executing the `defcustom'. To reliably accomplish +;; that in this case we would have to edit gnus-start.el which I don't +;; want to do. So I've done this instead. +(require 'gnus-start) ;; Run defcustom for `gnus-close-on-sleep' +(when (bound-and-true-p gnus-dbus-close-on-sleep) + (setq gnus-close-on-sleep gnus-dbus-close-on-sleep)) +(with-suppressed-warnings ((suspicious nil)) ;; Doesn't like aliasing bound variables + (define-obsolete-variable-alias 'gnus-dbus-close-on-sleep 'gnus-close-on-sleep "31.1")) (defvar gnus-dbus-sleep-registration-object nil "Object returned from `dbus-register-signal'. Used to unregister the signal.") +(make-obsolete-variable 'gnus-dbus-sleep-registration-object nil "31.1") (defun gnus-dbus-register-sleep-signal () "Use `dbus-register-signal' to close servers on sleep." + (declare (obsolete nil "31.1")) (when (featurep 'dbusbind) (setq gnus-dbus-sleep-registration-object (dbus-register-signal :system @@ -54,6 +71,7 @@ Used to unregister the signal.") (defun gnus-dbus-sleep-handler (sleep-start) ;; Sleep-start is t before sleeping. + (declare (obsolete nil "31.1")) (when (and sleep-start (gnus-alive-p)) (condition-case nil @@ -61,6 +79,7 @@ Used to unregister the signal.") (error nil)))) (defun gnus-dbus-unregister-sleep-signal () + (declare (obsolete gnus-sleep-handler "31.1")) (condition-case nil (dbus-unregister-object gnus-dbus-sleep-registration-object) commit 9bf2a19bb21418ca0716142629a9f0f32b7e3b31 Author: Morgan Smith Date: Wed May 6 20:24:50 2026 -0400 Move gnus-dbus.el to obsolete/gnus-dbus.el * lisp/gnus/gnus-dbus.el: Move from here... * lisp/obsolete/gnus-dbus.el: ...to here. diff --git a/lisp/gnus/gnus-dbus.el b/lisp/obsolete/gnus-dbus.el similarity index 100% rename from lisp/gnus/gnus-dbus.el rename to lisp/obsolete/gnus-dbus.el commit 984024daf3cea96d760c9d6a3a89d826a0750fb6 Author: Morgan Smith Date: Tue Jan 20 15:18:33 2026 -0500 Gnus: Use new sleep library * etc/NEWS: Announce. * lisp/gnus/gnus-start.el: Don't require gnus-dbus. (gnus-sleep-handler): New function. (gnus-close-on-sleep): New variable. (gnus-1): Add `gnus-sleep-handler' to `system-sleep-event-functions' when `gnus-close-on-sleep' is non-nil. * doc/misc/gnus.texi: Update documentation. diff --git a/doc/misc/gnus.texi b/doc/misc/gnus.texi index a440aac1a90..fffe81eac06 100644 --- a/doc/misc/gnus.texi +++ b/doc/misc/gnus.texi @@ -849,7 +849,7 @@ Various * Spam Package:: A package for filtering and processing spam. * The Gnus Registry:: A package for tracking messages by Message-ID. * The Gnus Cloud:: A package for synchronizing Gnus marks. -* D-Bus Integration:: Closing Gnus servers on system sleep. +* System Sleep Integration:: Closing Gnus servers on system sleep. * Other modes:: Interaction with other modes. * Various Various:: Things that are really various. @@ -22712,7 +22712,7 @@ For instance, @code{nnir-notmuch-program} is now * Spam Package:: A package for filtering and processing spam. * The Gnus Registry:: A package for tracking messages by Message-ID. * The Gnus Cloud:: A package for synchronizing Gnus marks. -* D-Bus Integration:: Closing Gnus servers on system sleep. +* System Sleep Integration:: Closing Gnus servers on system sleep. * Other modes:: Interaction with other modes. * Various Various:: Things that are really various. @end menu @@ -26680,11 +26680,11 @@ CloudSynchronizationDataPack(TM)s. It's easiest to set this from the Server buffer (@pxref{Gnus Cloud Setup}). @end defvar -@node D-Bus Integration -@section D-Bus Integration -@cindex dbus -@cindex D-Bus -@cindex gnus-dbus +@node System Sleep Integration +@section System Sleep Integration +@c Section name changed from this in Emacs 31. @c +@c This anchor allows old links to continue working. @c +@anchor{D-Bus Integration} @cindex system sleep @cindex closing servers automatically @cindex hung connections @@ -26692,13 +26692,10 @@ Server buffer (@pxref{Gnus Cloud Setup}). When using laptops or other systems that have a sleep or hibernate functionality, it's possible for long-running server connections to become ``hung'', requiring the user to manually close and re-open the -connections after the system resumes. On systems compiled with D-Bus -support (check the value of @code{(featurep 'dbusbind)}), Gnus can -register a D-Bus signal to automatically close all server connections -before the system goes to sleep. To enable this, set -@code{gnus-dbus-close-on-sleep} to a non-@code{nil} value. - -For more information about D-Bus and Emacs, @pxref{Top,,, dbus, D-Bus integration in Emacs}. +connections after the system resumes. Using the system sleep library, +Gnus can automatically close all server connections before the system +goes to sleep. To enable this, set @code{gnus-close-on-sleep} to a +non-@code{nil} value. @node Other modes @section Interaction with other modes diff --git a/etc/NEWS b/etc/NEWS index 0c221d049e0..1fe24bd61e4 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1967,6 +1967,14 @@ Gnus, see "(gnus) Symbolic Prefixes" in the Gnus manual. --- *** Sorting selected groups is now possible with 'gnus-topic-mode'. ++++ +*** System sleep integration is now independent of D-Bus. +The system sleep integration previously provided by customizing the +variable 'gnus-dbus-close-on-sleep' is now deprecated. A new system +using the builtin sleep library is now available by customizing +'gnus-close-on-sleep'. This will work on all systems that the sleep +library supports. + ** Sieve +++ diff --git a/lisp/gnus/gnus-start.el b/lisp/gnus/gnus-start.el index f63fc41ea5e..ba308990407 100644 --- a/lisp/gnus/gnus-start.el +++ b/lisp/gnus/gnus-start.el @@ -31,7 +31,6 @@ (require 'gnus-range) (require 'gnus-util) (require 'gnus-cloud) -(require 'gnus-dbus) (autoload 'message-make-date "message") (autoload 'gnus-agent-read-servers-validate "gnus-agent") (autoload 'gnus-agent-save-local "gnus-agent") @@ -733,6 +732,22 @@ the first newsgroup." ;; Remove Gnus frames. (gnus-kill-gnus-frames)) +(defcustom gnus-close-on-sleep nil + "When non-nil, close Gnus servers on system sleep." + :type 'boolean + :group 'gnus-start) + +(defun gnus-sleep-handler (sleep-event) + "Close connection to servers before system sleep. +See `gnus-close-on-sleep' to enable this functionality. + +SLEEP-EVENT is checked to ensure this is only run before sleep." + (when (and (eq 'pre-sleep (sleep-event-state sleep-event)) + (gnus-alive-p)) + (condition-case nil + (gnus-close-all-servers) + (error nil)))) + (defun gnus-no-server-1 (&optional arg child) "Read network news. If ARG is a positive number, Gnus will use that as the startup @@ -800,8 +815,9 @@ prompt the user for the name of an NNTP server to use." (gnus-run-hooks 'gnus-setup-news-hook) (when gnus-agent (gnus-request-create-group "queue" '(nndraft ""))) - (when gnus-dbus-close-on-sleep - (gnus-dbus-register-sleep-signal)) + (when gnus-close-on-sleep + (add-hook 'system-sleep-event-functions + #'gnus-sleep-handler)) (gnus-start-draft-setup) ;; Generate the group buffer. (gnus-group-list-groups level) commit d7c130972e08875c97731ceeb31e198696659b05 Author: Eli Zaretskii Date: Sat May 9 12:44:59 2026 +0300 ; * lisp/term/pgtk-win.el (icon-map-list): Fix :type. diff --git a/lisp/term/pgtk-win.el b/lisp/term/pgtk-win.el index abf3ab0951e..ffa20d21e63 100644 --- a/lisp/term/pgtk-win.el +++ b/lisp/term/pgtk-win.el @@ -302,7 +302,10 @@ If you don't want stock icons, set the variable to nil." :type '(choice (const :tag "Don't use stock icons" nil) (repeat (choice symbol (cons (string :tag "Emacs icon") - (string :tag "Stock/named"))))) + (choice + (group (string "Named") + (string "Stock")) + (string :tag "Stock/named")))))) :group 'pgtk) (defconst x-gtk-stock-cache (make-hash-table :weakness t :test 'equal)) commit 5579893ed7c786608d79fd57c4dc53de58d2bfe2 Author: Eli Zaretskii Date: Sat May 9 12:22:02 2026 +0300 ; Don't block/unblock input in text_extents methods * src/xftfont.c (xftfont_text_extents): * src/ftcrfont.c (ftcrfont_text_extents): Don't block/unblock input. (Bug#80863) diff --git a/src/ftcrfont.c b/src/ftcrfont.c index 3a609187a53..ac5f52eccc2 100644 --- a/src/ftcrfont.c +++ b/src/ftcrfont.c @@ -415,7 +415,6 @@ ftcrfont_text_extents (struct font *font, { int width, i; - block_input (); width = ftcrfont_glyph_extents (font, code[0], metrics); for (i = 1; i < nglyphs; i++) { @@ -435,7 +434,6 @@ ftcrfont_text_extents (struct font *font, } width += w; } - unblock_input (); if (metrics) metrics->width = width; diff --git a/src/xftfont.c b/src/xftfont.c index f15dbae1e7a..113c51eebe0 100644 --- a/src/xftfont.c +++ b/src/xftfont.c @@ -466,10 +466,8 @@ xftfont_text_extents (struct font *font, const unsigned int *code, struct font_info *xftfont_info = (struct font_info *) font; XGlyphInfo extents; - block_input (); XftGlyphExtents (xftfont_info->display, xftfont_info->xftfont, code, nglyphs, &extents); - unblock_input (); metrics->lbearing = - extents.x; metrics->rbearing = - extents.x + extents.width; commit 547b1ee7b6dadb65ebddea5a700042bd3976b56c Author: Eli Zaretskii Date: Sat May 9 12:01:07 2026 +0300 Fix Rmail behavior wrt globalized minor modes Previously, "M-x rmail" would not call 'run-mode-hooks', which didn't let globalized minor modes a chance to turn on themselves in Rmail buffers. This modifies the way Rmail runs the various hooks so as to abide by behavior required by Emacs 30 and later. * lisp/mail/rmail.el (rmail-mode-2): Call 'run-mode-hooks'. (rmail-mode): Call 'run-hooks', not 'run-mode-hooks'. Suggested by Mark Lillibridge . (Bug#80879) diff --git a/lisp/mail/rmail.el b/lisp/mail/rmail.el index 5f1cabaccdc..d51a90d1e63 100644 --- a/lisp/mail/rmail.el +++ b/lisp/mail/rmail.el @@ -1328,13 +1328,14 @@ Instead, these commands are available: (when rmail-display-summary (rmail-summary)) (rmail-construct-io-menu)) - (run-mode-hooks 'rmail-mode-hook))) + (run-hooks 'rmail-mode-hook))) (defun rmail-mode-2 () (kill-all-local-variables) (rmail-mode-1) (rmail-perm-variables) - (rmail-variables)) + (rmail-variables) + (run-mode-hooks)) (defun rmail-mode-1 () (setq major-mode 'rmail-mode) commit 6ba05106f4e29af8434885134306a25ba00ef983 Author: Eli Zaretskii Date: Sat May 9 08:58:59 2026 +0300 Fix display images in the display margins * src/xdisp.c (handle_single_display_spec): Set the iterator face to use 'margin' when displaying in the margins. (Bug#80693) diff --git a/src/xdisp.c b/src/xdisp.c index 773aba2789f..b485d9ccf40 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -6534,10 +6534,19 @@ handle_single_display_spec (struct it *it, Lisp_Object spec, Lisp_Object object, if (NILP (location)) it->area = TEXT_AREA; - else if (EQ (location, Qleft_margin)) - it->area = LEFT_MARGIN_AREA; else - it->area = RIGHT_MARGIN_AREA; + { + if (EQ (location, Qleft_margin)) + it->area = LEFT_MARGIN_AREA; + else + it->area = RIGHT_MARGIN_AREA; + /* Use the 'margin' face for displaying text and images + in the margins. */ + it->face_id = + NILP (Vface_remapping_alist) + ? MARGIN_FACE_ID + : lookup_basic_face (it->w, it->f, MARGIN_FACE_ID); + } if (STRINGP (value)) { commit 56f27dd9f066a1dc80942a100f1045bdf5376152 Author: João Távora Date: Sat May 9 00:58:36 2026 +0100 Eglot: fix eglot--sig-info with non-UTF-32 positionEncoding Github-reference: https://github.com/joaotavora/eglot/discussions/1588 When the server negotiates positionEncoding utf-8 or utf-16, ParameterInformation.label vector offsets are byte/code-unit counts into the signature label, not character counts. Using them raw caused wrong highlights and crashes on Unicode-rich signatures. * lisp/progmodes/eglot.el (eglot--sig-info): Mostly rewrite. (eglot-move-to-utf-8-linepos-function): Tweak docstring. (eglot-move-to-utf-8-linepos, eglot-move-to-utf-16-linepos): Return position moved to. diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index b41fd3d2212..e97b1749b79 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -2152,19 +2152,18 @@ LBP defaults to `eglot--bol'." (funcall eglot-current-linepos-function))))) (defvar eglot-move-to-linepos-function #'eglot-move-to-utf-16-linepos - "Function to move to a position within a line reported by the LSP server. + "Move point to LSP-reported position within a line. -Per the LSP spec, character offsets in LSP Position objects count -UTF-16 code units, not actual code points. So when LSP says -position 3 of a line containing just \"aXbc\", where X is a funny -looking character in the UTF-16 \"supplementary plane\", it -actually means `b', not `c'. The default value -`eglot-move-to-utf-16-linepos' accounts for this. +Per the LSP spec, character offsets in LSP Position objects count UTF-16 +code units, not actual code points. So when LSP says position 3 of a +line containing just \"aXbc\", where X is a funny looking character in +the UTF-16 \"supplementary plane\", it actually means `b', not `c'. The +default value `eglot-move-to-utf-16-linepos' accounts for this. This variable can also be set to `eglot-move-to-utf-8-linepos' or -`eglot-move-to-utf-32-linepos' for servers not closely following -the spec. Also, since LSP 3.17 server and client may agree on an -encoding and Eglot will set this variable automatically.") +`eglot-move-to-utf-32-linepos' for servers not closely following the +spec. Also, since LSP 3.17 server and client may agree on an encoding +and Eglot will set this variable automatically.") (defun eglot-move-to-utf-8-linepos (n) "Move to line's Nth byte as computed by LSP's UTF-8 criterion." @@ -2175,7 +2174,8 @@ encoding and Eglot will set this variable automatically.") (while (and (< (position-bytes (point)) goal-byte) (< (point) eol)) ;; raw bytes take 2 bytes in the buffer (when (>= (char-after) #x3fff80) (setq goal-byte (1+ goal-byte))) - (forward-char 1)))) + (forward-char 1)) + (point))) (defun eglot-move-to-utf-16-linepos (n) "Move to line's Nth code unit as computed by LSP's UTF-16 criterion." @@ -2186,7 +2186,8 @@ encoding and Eglot will set this variable automatically.") (while (and (< (point) goal-char) (< (point) eol)) ;; code points in the "supplementary place" use two code units (when (<= #x010000 (char-after) #x10ffff) (setq goal-char (1- goal-char))) - (forward-char 1)))) + (forward-char 1)) + (point))) (defun eglot-move-to-utf-32-linepos (n) "Move to line's Nth codepoint as computed by LSP's UTF-32 criterion." @@ -4108,66 +4109,67 @@ for which LSP on-type-formatting should be requested." (mapconcat #'eglot--format-markup (if (vectorp contents) contents (list contents)) "\n")) -(defun eglot--sig-info (sig &optional sig-active briefp) +(cl-defun eglot--sig-info (sig &optional sig-active briefp + &aux (move-fn eglot-move-to-linepos-function) + first-parlabel + fpardoc) (eglot--dbind ((SignatureInformation) ((:label siglabel)) ((:documentation sigdoc)) parameters activeParameter) sig (with-temp-buffer - (insert siglabel) - ;; Add documentation, indented so we can distinguish multiple signatures - (when-let* ((doc (and (not briefp) sigdoc (eglot--format-markup sigdoc)))) - (goto-char (point-max)) - (insert "\n" (replace-regexp-in-string "^" " " doc))) - ;; Try to highlight function name only - (let (first-parlabel) - (cond ((and (cl-plusp (length parameters)) - (vectorp (setq first-parlabel - (plist-get (aref parameters 0) :label)))) - (save-excursion - (goto-char (elt first-parlabel 0)) - (skip-syntax-backward "^w") - (add-face-text-property (point-min) (point) - 'font-lock-function-name-face))) - ((save-excursion - (goto-char (point-min)) - (looking-at "\\([^(]*\\)([^)]*)")) - (add-face-text-property (match-beginning 1) (match-end 1) - 'font-lock-function-name-face)))) + (save-excursion + ;; Insert main siglabel line + (insert siglabel) + ;; Add function documentation to end on a new line, indented so + ;; we can distinguish multiple signatures + (when-let* ((doc (and (not briefp) sigdoc (eglot--format-markup sigdoc)))) + (goto-char (point-max)) + (insert "\n" (replace-regexp-in-string "^" " " doc)))) + ;; Back to point-min: try to highlight function name only + (cond ((and (cl-plusp (length parameters)) + (vectorp (setq first-parlabel + (plist-get (aref parameters 0) :label)))) + (funcall move-fn (elt first-parlabel 0)) + (skip-syntax-backward "^w") + (add-face-text-property (point-min) (point) + 'font-lock-function-name-face)) + ((looking-at "\\([^(]*\\)([^)]*)") + (add-face-text-property (match-beginning 1) (match-end 1) + 'font-lock-function-name-face))) ;; Now to the parameters (cl-loop with active-param = (or activeParameter sig-active) + with case-fold-search = nil for i from 0 for parameter across parameters do (eglot--dbind ((ParameterInformation) ((:label parlabel)) ((:documentation pardoc))) parameter - ;; ...perhaps highlight it in the formals list - (when (eq i active-param) - (save-excursion - (goto-char (point-min)) - (pcase-let - ((`(,beg ,end) - (if (stringp parlabel) - (let ((case-fold-search nil)) - (and (search-forward parlabel (line-end-position) t) - (list (match-beginning 0) (match-end 0)))) - (list (1+ (aref parlabel 0)) (1+ (aref parlabel 1)))))) - (if (and beg end) - (add-face-text-property - beg end - 'eldoc-highlight-function-argument))))) - ;; ...and/or maybe add its doc on a line by its own. - (let (fpardoc) + (cl-flet ((parlabel-bounds () + (cond ((stringp parlabel) + (and (search-forward parlabel (line-end-position) t) + (match-data))) + (t (mapcar move-fn parlabel))))) + ;; ...perhaps highlight it in the formals list + (when-let* ((b (and (eq i active-param) + (parlabel-bounds)))) + (add-face-text-property + (car b) (cadr b) + 'eldoc-highlight-function-argument)) + ;; ...and/or maybe add its doc on a line by its own. (when (and pardoc (not briefp) (not (string-empty-p (setq fpardoc (eglot--format-markup pardoc))))) - (insert "\n " - (propertize - (if (stringp parlabel) parlabel - (substring siglabel (aref parlabel 0) (aref parlabel 1))) - 'face (and (eq i active-param) 'eldoc-highlight-function-argument)) - ": " fpardoc))))) + (unless (stringp parlabel) + (setq parlabel (apply #'buffer-substring (parlabel-bounds)))) + (save-excursion + (goto-char (point-max)) + (insert "\n " + (propertize + parlabel + 'face (and (eq i active-param) 'eldoc-highlight-function-argument)) + ": " fpardoc)))))) (buffer-string)))) (defun eglot-signature-eldoc-function (cb &rest _ignored) commit 543d8a7a9d7edadced77aced028b848bd9802a6b Author: Alan Third Date: Thu May 7 22:57:13 2026 +0100 [NS] Fix deprecated variable (bug#80985) * src/nsterm.h (NSLevelIndicatorStyleContinuousCapacity): Define in macOS < 10.15. diff --git a/src/nsterm.h b/src/nsterm.h index 610ca4c4acc..b21dd519e4b 100644 --- a/src/nsterm.h +++ b/src/nsterm.h @@ -1384,6 +1384,11 @@ enum NSWindowTabbingMode #define NSButtonTypeMomentaryPushIn NSMomentaryPushInButton #endif +#if !defined (NS_IMPL_COCOA) || !defined (MAC_OS_X_VERSION_10_15) +/* Deprecated in macOS 10.15. */ +#define NSLevelIndicatorStyleContinuousCapacity NSContinuousCapacityLevelIndicatorStyle +#endif + extern void mark_nsterm (void); #endif /* HAVE_NS */ commit 876a1db6ee00f1d1b2af0329236acc8bdcceda5b Merge: 5e0b4b96bc5 2d496b842d6 Author: Sean Whitton Date: Fri May 8 13:48:22 2026 +0100 Merge from origin/emacs-31 2d496b842d6 ; Fix Gregor Schmid's attribution for lua-mode.el. 69c50dcb473 ; package-activate-all: Drop requiring package now not pr... f94637749a2 vc-switch-working-tree: Use project-current again 060451d6e0b treesit-explore-mode usability improvements (bug#80935) 48b064a2aa3 Fix 'vc-dir-resynch-file' again (bug#80967) commit 2d496b842d6c6f2a6eb0ca891e00396caedb5178 Author: Sean Whitton Date: Fri May 8 13:47:52 2026 +0100 ; Fix Gregor Schmid's attribution for lua-mode.el. diff --git a/etc/AUTHORS b/etc/AUTHORS index 4547a4c618d..ab4abbfc62b 100644 --- a/etc/AUTHORS +++ b/etc/AUTHORS @@ -977,7 +977,7 @@ and co-wrote longlines.el tango-dark-theme.el tango-theme.el and changed simple.el display.texi xdisp.c files.el frames.texi cus-edit.el files.texi custom.el subr.el text.texi faces.el keyboard.c startup.el package.el misc.texi emacs.texi modes.texi mouse.el - custom.texi image.c window.el and 903 other files + custom.texi image.c window.el and 920 other files Chris Chase: co-wrote idlw-shell.el idlwave.el @@ -1439,7 +1439,7 @@ and changed cedet/semantic.el db.el insert.el semantic/complete.el c.by c.el db-el.el db-file.el db-find.el ede-grammar.el eieio-opt.el eieio.el eieio.texi gnus.texi registry.el srecode/compile.el wisent/python.el analyze.el bovine/el.el bovine/grammar.el - decorate/mode.el and 87 other files + decorate/mode.el and 88 other files Davide Pola: changed comp-cstr.el comp-run.el @@ -1811,7 +1811,7 @@ and co-wrote help-tests.el and changed xdisp.c display.texi w32.c msdos.c simple.el w32fns.c files.el fileio.c keyboard.c configure.ac emacs.c text.texi w32term.c dispnew.c frames.texi files.texi w32proc.c xfaces.c process.c window.c - dispextern.h and 1437 other files + dispextern.h and 1451 other files Eliza Velasquez: changed server.el simple.el @@ -1914,7 +1914,7 @@ and changed c.srt ede.texi info.el rmail.el speedbspec.el cedet.el ede-autoconf.srt ede-make.srt eieio.texi gud.el sb-dir-minus.xpm sb-dir-plus.xpm sb-dir.xpm sb-mail.xpm sb-pg-minus.xpm sb-pg-plus.xpm sb-pg.xpm sb-tag-gt.xpm sb-tag-minus.xpm sb-tag-plus.xpm - and 34 other files + and 35 other files Eric Schulte: wrote ob-awk.el ob-calc.el ob-comint.el ob-css.el ob-dot.el ob-emacs-lisp.el ob-eval.el ob-forth.el ob-gnuplot.el ob-haskell.el @@ -2313,7 +2313,7 @@ and changed configure.ac Makefile.in src/Makefile.in calendar.el lisp/Makefile.in diary-lib.el files.el make-dist rmail.el progmodes/f90.el bytecomp.el admin.el misc/Makefile.in simple.el authors.el startup.el emacs.texi lib-src/Makefile.in display.texi - ack.texi subr.el and 1753 other files + ack.texi subr.el and 1771 other files Glynn Clements: wrote gamegrid.el snake.el tetris.el @@ -2357,8 +2357,9 @@ Gregorio Gervasio, Jr.: changed gnus-sum.el Gregor Kappler: changed ox.el -Gregor Schmid: changed intervals.c intervals.h tcl-mode.el textprop.c - dispnew.c indent.c xdisp.c +Gregor Schmid: co-wrote lua-mode.el +and changed intervals.c intervals.h tcl-mode.el textprop.c dispnew.c + indent.c xdisp.c Gregory Chernov: changed nnslashdot.el @@ -3337,7 +3338,7 @@ and co-wrote help-tests.el keymap-tests.el and changed subr.el desktop.el w32fns.c bs.el faces.el simple.el emacsclient.c files.el server.el help-fns.el xdisp.c org.el w32term.c w32.c buffer.c keyboard.c ido.el image.c window.c eval.c allout.el - and 1191 other files + and 1206 other files Juan Pechiar: changed ob-octave.el @@ -3390,7 +3391,7 @@ Juri Linkov: wrote compose.el emoji.el files-x.el misearch.el and changed isearch.el simple.el replace.el info.el dired.el treesit.el minibuffer.el dired-aux.el window.el outline.el progmodes/grep.el subr.el diff-mode.el repeat.el vc.el mouse.el files.el image-mode.el - menu-bar.el project.el display.texi and 525 other files + menu-bar.el project.el display.texi and 526 other files Jussi Lahdenniemi: changed w32fns.c ms-w32.h msdos.texi w32.c w32.h w32console.c w32heap.c w32inevt.c w32term.h @@ -3420,7 +3421,7 @@ and co-wrote longlines.el tramp-sh.el tramp.el and changed message.el gnus-agent.el gnus-sum.el files.el nnmail.el tramp.texi nntp.el gnus.el simple.el ange-ftp.el dired.el paragraphs.el bindings.el files.texi gnus-art.el gnus-group.el man.el INSTALL - Makefile.in crisp.el fileio.c and 44 other files + Makefile.in crisp.el fileio.c and 45 other files Kailash C. Chowksey: changed HELLO ind-util.el kannada.el knd-util.el lisp/Makefile.in loadup.el @@ -3552,7 +3553,7 @@ and co-wrote ps-def.el ps-mule.el ps-print.el ps-samp.el quail.el and changed coding.c mule-cmds.el mule.el fontset.c charset.c xdisp.c font.c fontset.el xterm.c fileio.c mule-conf.el ftfont.c characters.el fns.c mule-diag.el coding.h charset.h ccl.c xfaces.c editfns.c - composite.c and 370 other files + composite.c and 385 other files Kenichi Okada: co-wrote sasl-cram.el sasl-digest.el @@ -3779,7 +3780,7 @@ and co-wrote gnus-kill.el gnus-mh.el gnus-msg.el gnus-score.el and changed subr.el simple.el gnus.texi files.el display.texi process.c help-fns.el text.texi image.c dired.el help.el image.el package.el edebug.el shortdoc.el dired-aux.el gnutls.c minibuffer.el subr-x.el - auth-source.el smtpmail.el and 1050 other files + auth-source.el smtpmail.el and 1051 other files Lars Rasmusson: changed ebrowse.c @@ -3915,7 +3916,7 @@ Luc Teirlinck: wrote help-at-pt.el and changed files.el autorevert.el cus-edit.el subr.el simple.el frames.texi startup.el display.texi files.texi dired.el comint.el modes.texi custom.texi emacs.texi fns.c frame.el ielm.el minibuf.texi - variables.texi buffers.texi commands.texi and 210 other files + variables.texi buffers.texi commands.texi and 211 other files Ludovic Courtès: wrote nnregistry.el and changed configure.ac gnus.texi loadup.el @@ -4417,7 +4418,7 @@ Michael Olson: changed erc.el erc-backend.el Makefile erc-track.el erc-log.el erc-stamp.el erc-autoaway.el erc-dcc.el erc-goodies.el erc-list.el erc-compat.el erc-identd.el erc.texi erc-bbdb.el erc-match.el erc-notify.el erc-ibuffer.el erc-services.el remember.el - erc-button.el erc-nicklist.el and 53 other files + erc-button.el erc-nicklist.el and 54 other files Michael Orlitzky: changed tex-mode.el @@ -5261,9 +5262,10 @@ Protesilaos Stavrou: wrote modus-operandi-deuteranopia-theme.el modus-vivendi-deuteranopia-theme.el modus-vivendi-theme.el modus-vivendi-tinted-theme.el modus-vivendi-tritanopia-theme.el and changed modus-themes.org eww.el vc-dir.el TUTORIAL.el_GR log-view.el - time.el vc-git.el appt.el apropos.el custom.el diff-mode.el flymake.el - ibuffer.el language/greek.el log-edit.el minibuffer.el package.el - perl-mode.el shortdoc.el shr.el vc-cvs.el and 5 other files + modus-themes.texi time.el vc-git.el appt.el apropos.el custom.el + diff-mode.el flymake.el ibuffer.el language/greek.el log-edit.el + minibuffer.el package.el perl-mode.el shortdoc.el shr.el + and 6 other files Przemsyław Kryger: wrote package-vc-tests.el @@ -5988,7 +5990,7 @@ and co-wrote help-tests.el keymap-tests.el and changed subr.el package.el image-dired.el checkdoc.el efaq.texi cperl-mode.el help.el simple.el progmodes/python.el dired.el files.el bookmark.el browse-url.el gnus.texi keymap.c dired-x.el erc.el image.c - cl-macs.el message.el subr-tests.el and 1947 other files + cl-macs.el message.el subr-tests.el and 1948 other files Stefan Merten: co-wrote rst.el @@ -6005,7 +6007,7 @@ and co-wrote font-lock.el gitmerge.el pcvs.el visual-wrap.el and changed subr.el simple.el cl-macs.el bytecomp.el files.el keyboard.c lisp.h vc.el eval.c xdisp.c alloc.c help-fns.el buffer.c sh-script.el package.el tex-mode.el progmodes/compile.el lread.c keymap.c window.c - easy-mmode.el and 1743 other files + easy-mmode.el and 1744 other files Stefano Facchini: changed gtkutil.c @@ -6050,7 +6052,7 @@ and changed dired.el wid-edit.el wdired.el dired-tests.el files.el todo-mode.texi dabbrev-tests.el wdired-tests.el diary-lib.el menu-bar.el minibuffer.el dabbrev.el dired-aux.el doc-view.el info.el outline.el simple.el todo-test-1.todo widget.texi INSTALL_BEGIN - allout.el and 89 other files + allout.el and 90 other files Stephen C. Gilardi: changed configure.ac @@ -6242,7 +6244,7 @@ and changed spam.el gnus.el nnimap.el gnus.texi gnutls.c gnus-sum.el auth.texi cfengine.el gnus-sync.el gnus-util.el gnus-start.el netrc.el gnutls.h message.el spam-stat.el .gitlab-ci.yml encrypt.el mail-source.el nnir.el nnmail.el auth-source-tests.el - and 124 other files + and 125 other files Terje Rosten: changed xfns.c version.el xterm.c xterm.h diff --git a/lisp/progmodes/lua-mode.el b/lisp/progmodes/lua-mode.el index dca93a86888..72c1932ab64 100644 --- a/lisp/progmodes/lua-mode.el +++ b/lisp/progmodes/lua-mode.el @@ -8,10 +8,10 @@ ;; 2004 various (support for Lua 5 and byte compilation) ;; 2001 Christian Vogler ;; 1997 Bret Mogilefsky -;; Bret Mogilefsky started from tcl-mode by -;; Gregor Schmid -;; with tons of assistance from Paul Du Bois -;; and Aaron Smith . +;; Gregor Schmid +;; Bret Mogilefsky started from tcl-mode by Gregor Schmid with tons of +;; assistance from Paul Du Bois +;; and Aaron Smith . ;; Maintainer: emacs-devel@gnu.org ;; Keywords: languages, processes, tools commit 69c50dcb47338f178758e30d59d7346a4512a3a4 Author: Sean Whitton Date: Fri May 8 12:27:24 2026 +0100 ; package-activate-all: Drop requiring package now not preloaded. diff --git a/lisp/emacs-lisp/package-activate.el b/lisp/emacs-lisp/package-activate.el index d79628b172b..53a3fa30836 100644 --- a/lisp/emacs-lisp/package-activate.el +++ b/lisp/emacs-lisp/package-activate.el @@ -451,15 +451,13 @@ The variable `package-load-list' controls which packages to load." (setq package-activated-list nil)) (load qs nil 'nomessage) t))) - (progn - (require 'package) - ;; Silence the "unknown function" warning when this is compiled - ;; inside `loaddefs.el'. - ;; FIXME: We use `with-no-warnings' because the effect of - ;; `declare-function' is currently not scoped, so if we use - ;; it here, we end up with a redefinition warning instead :-) - (with-no-warnings - (package--activate-all)))))) + ;; Silence the "unknown function" warning when this is compiled + ;; inside `loaddefs.el'. + ;; FIXME: We use `with-no-warnings' because the effect of + ;; `declare-function' is currently not scoped, so if we use + ;; it here, we end up with a redefinition warning instead :-) + (with-no-warnings + (package--activate-all))))) (defun package--activate-all () (dolist (elt (package--alist)) commit f94637749a26d12892c1044483350fbe43d048f5 Author: Dmitry Gutov Date: Fri May 8 12:25:49 2026 +0100 vc-switch-working-tree: Use project-current again * lisp/vc/vc.el (vc-switch-working-tree): Use project-current instead of manually constructing VC project objects. diff --git a/lisp/vc/vc.el b/lisp/vc/vc.el index 4ecfb2d1e98..4a6ae7e4290 100644 --- a/lisp/vc/vc.el +++ b/lisp/vc/vc.el @@ -5789,14 +5789,11 @@ to the root of this working tree." (let ((backend (or (vc-deduce-backend) (vc-responsible-backend default-directory) (error "No VC backend")))) - ;; Manually construct VC project objects because `project-current' - ;; might find a non-VC project within the VC working tree containing - ;; DIRECTORY, but we should ignore that (bug#80939). + ;; Skip to the VC root, otherwise `project-current' could find a + ;; non-VC project between DEFAULT-DIRECTORY and there (bug#80939). (funcall project-find-matching-buffer-function - `(vc ,backend ,(vc-root-dir backend)) - `(vc ,backend - ,(let ((default-directory directory)) - (vc-root-dir backend)))))) + (project-current nil (vc-root-dir backend)) + (project-current nil directory)))) ;;;###autoload (defun vc-working-tree-switch-project (dir) commit 5e0b4b96bc5ad23ca8d7fad4595e465eef69bf96 Author: Michael Albinus Date: Fri May 8 10:47:26 2026 +0200 ; Adapt files in admin/notes for emacs-31 branch * admin/notes/emba: Mention scheduled pipelines. * admin/notes/git-workflow: Adapt for emacs-31 branch. diff --git a/admin/notes/emba b/admin/notes/emba index 4bf6f3a9c76..83688e7287b 100644 --- a/admin/notes/emba +++ b/admin/notes/emba @@ -76,6 +76,10 @@ Every pipeline generates a JUnit test report for the respective test jobs, which can be inspected on the pipeline web page. This test report counts completed ERT tests, aborted tests are not counted. +Twice a day, a pipeline for branch 'master', and another pipeline for +branch 'emacs-31' are started automatically, running all stages for +normal and expensive tests. + * Emba configuration The emba configuration files are hosted on diff --git a/admin/notes/git-workflow b/admin/notes/git-workflow index 8e7d597db17..a1d4044df77 100644 --- a/admin/notes/git-workflow +++ b/admin/notes/git-workflow @@ -16,14 +16,14 @@ Initial setup Then we want to clone the repository. We normally want to have both the current master and (if there is one) the active release branch -(eg emacs-30). +(eg emacs-31). mkdir ~/emacs cd ~/emacs git clone @git.sv.gnu.org:/srv/git/emacs.git master cd master git config push.default current -git worktree add ../emacs-30 emacs-30 +git worktree add ../emacs-31 emacs-31 You now have both branches conveniently accessible, and you can do "git pull" in them once in a while to keep updated. @@ -67,7 +67,7 @@ which will look like commit 958b768a6534ae6e77a8547a56fc31b46b63710b -cd ~/emacs/emacs-30 +cd ~/emacs/emacs-31 git cherry-pick -xe 958b768a6534ae6e77a8547a56fc31b46b63710b and add "Backport:" to the commit string. Then @@ -109,7 +109,7 @@ up-to-date by doing a pull. Then start Emacs with emacs -l admin/gitmerge.el -f gitmerge You'll be asked for the branch to merge, which will default to -(eg) 'origin/emacs-30', which you should accept. Merging a local tracking +(eg) 'origin/emacs-31', which you should accept. Merging a local tracking branch is discouraged, since it might not be up-to-date, or worse, contain commits from you which are not yet pushed upstream. commit 730d3884dc3ee540c3d68f64edffa35cb5561f34 Author: Eli Zaretskii Date: Fri May 8 09:51:13 2026 +0300 ; Fix the build broken by a typo in configure.ac * configure.ac (module_env_snippet_32): Fix typo. diff --git a/configure.ac b/configure.ac index 6c45f2ccb48..9832ef2f7ba 100644 --- a/configure.ac +++ b/configure.ac @@ -5108,7 +5108,7 @@ module_env_snippet_28="$srcdir/src/module-env-28.h" module_env_snippet_29="$srcdir/src/module-env-29.h" module_env_snippet_30="$srcdir/src/module-env-30.h" module_env_snippet_31="$srcdir/src/module-env-31.h" -module_env_snippet_31="$srcdir/src/module-env-32.h" +module_env_snippet_32="$srcdir/src/module-env-32.h" emacs_major_version=`AS_ECHO([$PACKAGE_VERSION]) | sed 's/[[.]].*//'` AC_SUBST([emacs_major_version]) commit 060451d6e0b3c91882c62ae1a574125c1f448487 Author: Stéphane Marks Date: Thu Apr 30 15:34:23 2026 -0400 treesit-explore-mode usability improvements (bug#80935) Improve the usability of treesit-explore-mode. - Prompt for the primary parser first, if there is one, rather than the first in the list reported by 'treesit-parser-list'. Previously, in a multi-parser buffer like 'markdown-ts-mode', one had to hunt for the primary parser. - Kill the tree buffer and its window if the source buffer is killed or 'treesit-explore-mode'. Previously, when 'treesit-explore-mode' is disabled in the source buffer, its companion explorer tree buffer was left dangling and window open (with an unrelated buffer). - Improve 'treesit--explorer-refresh-1' to recenter the window around the selected nodes when the selected region in the source buffer changes. Previously, one had to navigate manually to find the corresponding highlighted node in the tree window which may be far away from that the source buffer's region represents. - Disable 'treesit-explore-mode' in the source buffer if its companion tree buffer is killed. Previously, 'treesit-explore-mode' remained active in the source buffer in an effectively unusable state. - Disable 'treesit-explore-mode' if the user quits 'completing-read' in 'treesit-explorer-switch-parser' when enabling the mode. Previously, 'treesit-explore-mode' was left enabled after quit. - New command to switch back and forth between the source buffer and tree buffer windows to make navigating more convenient. Previously, in a multi-window frame, one had to navigate to/from these two related windows in a more cumbersome way. - New command to quit 'treesit-explore-mode' and 'treesit--explorer-tree-mode' and handle buffer and window cleanup. * lisp/treesit.el (treesit--explorer-refresh-1): Recenter the window, if amenable, to the node selected in the source buffer. (treesit--explorer-kill-explorer-buffer): Remove function. (treesit--explorer-generate-parser-alist): Prioritize the primary parser, if there is one. (treesit--explorer-tree-mode-cleanup): New defun. (treesit-explore-quit): New command. (treesit-explorer-tree-window): New defun. (treesit-explorer-source-buffer-window): New defun. (treesit-explore-mode-map): Revise key bindings. (treesit--explorer-tree-mode-map): Revise key bindings. (treesit--explorer-tree-mode): New keymap. (treesit-explorer-switch-parser): Add a default to completing-read. (treesit-explore-mode): Guard completing read quit. Wire up the new cleanup functions. diff --git a/lisp/treesit.el b/lisp/treesit.el index 01e82b56bb8..5253439a9dd 100644 --- a/lisp/treesit.el +++ b/lisp/treesit.el @@ -5035,6 +5035,10 @@ in the region." (while (and (null (pos-visible-in-window-p pos window)) (= (forward-line 4) 0)) (set-window-start window (point)))) + ;; Recenter if amenable. + (when (< scroll-conservatively 101) + (with-selected-window window + (recenter))) (set-window-point window pos))))))) (defun treesit--explorer-refresh () @@ -5197,11 +5201,6 @@ leaves point at the end of the last line of NODE." (when (not named) (overlay-put ov 'face 'treesit-explorer-anonymous-node))))) -(defun treesit--explorer-kill-explorer-buffer () - "Kill the explorer buffer of this buffer." - (when (buffer-live-p treesit--explorer-buffer) - (kill-buffer treesit--explorer-buffer))) - (defun treesit--explorer-generate-parser-alist () "Return an alist of (PARSER-NAME . PARSER) for relevant parsers. Relevant parsers include all global parsers and local parsers that @@ -5210,7 +5209,12 @@ covers point. PARSER-NAME are unique." (local-parsers-at-point (treesit-local-parsers-at (point))) res) - (dolist (parser (treesit-parser-list nil nil t)) + ;; Add `treesit-primary-parser' first in the list, if populated. + (dolist (parser (delete-dups + (delq nil + (append + (list treesit-primary-parser) + (treesit-parser-list nil nil t))))) ;; Exclude local parsers that doesn't cover point. (when (or (memq parser local-parsers-at-point) (not (memq parser local-parsers))) @@ -5230,20 +5234,68 @@ covers point. PARSER-NAME are unique." res))) (nreverse res))) +(defun treesit--explorer-tree-mode-cleanup () + "Clean up `treesit--explorer-tree-mode'. +If called from the source buffer, quit the tree buffer window and kill +the explorer buffer. +If called from the explorer tree buffer, disable `treesit-explore-mode' +in the source buffer, quit the tree window and kill its buffer." + (cond + ;; Called from the source buffer. + ((buffer-live-p treesit--explorer-buffer) + (when (window-live-p (get-buffer-window treesit--explorer-buffer)) + (let ((buf treesit--explorer-buffer)) + (with-selected-window (get-buffer-window treesit--explorer-buffer) + (quit-window)) + (kill-buffer buf)))) + ;; Called from the tree buffer. + ((buffer-live-p treesit--explorer-source-buffer) + (with-current-buffer treesit--explorer-source-buffer + (treesit-explore-mode -1)) + (when (window-live-p (get-buffer-window (current-buffer))) + (with-selected-window (get-buffer-window (current-buffer)) + (quit-window 'kill)))))) + +(defun treesit-explorer-tree-window-select () + "Select the `treesit--explorer-buffer' window. +Invoke this command from the source window." + (interactive) + (if (buffer-live-p treesit--explorer-buffer) + (select-window (get-buffer-window treesit--explorer-buffer)) + (user-error "The `treesit-explorer-mode' tree buffer does not exist"))) + +(defun treesit-explorer-source-buffer-window-select () + "Select the `treesit--explorer-buffer' window. +Invoke this command from the tree window." + (interactive) + (if (buffer-live-p treesit--explorer-source-buffer) + (select-window (get-buffer-window treesit--explorer-source-buffer)) + (user-error "The `treesit-explorer-mode' source buffer does not exist"))) + +(defvar-keymap treesit-explore-mode-map + :doc "Keymap for the treesit explore mode." + "C-c C-t o" #'treesit-explorer-tree-window-select + "C-c C-t q" #'treesit-explore-quit) + (defvar-keymap treesit--explorer-tree-mode-map :doc "Keymap for the treesit tree explorer. - Navigates from button to button." :parent special-mode-map - "n" #'forward-button - "p" #'backward-button - "TAB" #'forward-button - "" #'backward-button) + "n" #'forward-button + "p" #'backward-button + "q" #'treesit-explore-quit + "TAB" #'forward-button + "" #'backward-button + "C-c C-t o" #'treesit-explorer-source-buffer-window-select + "C-c C-t q" #'treesit-explore-quit) (define-derived-mode treesit--explorer-tree-mode special-mode "TS Explorer" "Mode for displaying syntax trees for `treesit-explore-mode'." - nil) + ;; Clean up `treesit--explorer-tree-mode' when the tree buffer is + ;; killed. + (add-hook 'kill-buffer-hook + #'treesit--explorer-tree-mode-cleanup 0 t)) (defun treesit-explorer-switch-parser (parser) "Switch explorer to use PARSER." @@ -5252,8 +5304,14 @@ Navigates from button to button." (treesit--explorer-generate-parser-alist)) (parser-name (if (= (length parser-alist) 1) (car parser-alist) + ;; Default to the first parser in the + ;; list which we hope is + ;; `treesit-primary-parser'. (completing-read - "Parser: " (mapcar #'car parser-alist))))) + "Parser: " + (mapcar #'car parser-alist) + nil t nil nil + (caar parser-alist))))) (alist-get parser-name parser-alist nil nil #'equal)))) (unless treesit-explore-mode @@ -5262,7 +5320,9 @@ Navigates from button to button." (display-buffer treesit--explorer-buffer (cons nil '((inhibit-same-window . t)))) (setq-local treesit--explorer-last-node nil) - (treesit--explorer-refresh)) + (treesit--explorer-refresh) + ;; Signal that `completing-read' did not quit. + t) (define-minor-mode treesit-explore-mode "Enable exploring the current buffer's syntax tree. @@ -5281,33 +5341,41 @@ window." (buffer-name)))) (with-current-buffer treesit--explorer-buffer (treesit--explorer-tree-mode))) - ;; Select parser. - (call-interactively #'treesit-explorer-switch-parser) - ;; Set up variables and hooks. - (add-hook 'post-command-hook - #'treesit--explorer-post-command 0 t) - (add-hook 'kill-buffer-hook - #'treesit--explorer-kill-explorer-buffer 0 t) - ;; Tell `desktop-save' to not save explorer buffers. - (when (boundp 'desktop-modes-not-to-save) - (unless (memq 'treesit--explorer-tree-mode - desktop-modes-not-to-save) - (push 'treesit--explorer-tree-mode - desktop-modes-not-to-save))) - ;; Tell `desktop-save' to not save this minor mode - ;; that might disrupt loading the desktop - ;; with the prompt to select a parser. - (when (boundp 'desktop-minor-mode-table) - (unless (member '(treesit-explore-mode nil) - desktop-minor-mode-table) - (push '(treesit-explore-mode nil) - desktop-minor-mode-table)))) + ;; Select parser. `treesit-explorer-switch-parser' will return + ;; t if its `completing-read' did not quit. + (if (not (condition-case _ + (call-interactively #'treesit-explorer-switch-parser) + (quit))) + (setq treesit-explore-mode nil) + ;; Track the `treesit--explorer-source-buffer' active region. + (add-hook 'post-command-hook + #'treesit--explorer-post-command 0 t) + ;; Clean up when the `treesit-explore-mode' buffer is killed. + (add-hook 'kill-buffer-hook + #'treesit--explorer-tree-mode-cleanup 0 t) + ;; Tell `desktop-save' to not save explorer buffers. + (when (boundp 'desktop-modes-not-to-save) + (unless (memq 'treesit--explorer-tree-mode + desktop-modes-not-to-save) + (push 'treesit--explorer-tree-mode + desktop-modes-not-to-save))) + ;; Tell `desktop-save' to not save this minor mode + ;; that might disrupt loading the desktop + ;; with the prompt to select a parser. + (when (boundp 'desktop-minor-mode-table) + (unless (member '(treesit-explore-mode nil) + desktop-minor-mode-table) + (push '(treesit-explore-mode nil) + desktop-minor-mode-table))))) ;; Turn off explore mode. (remove-hook 'post-command-hook #'treesit--explorer-post-command t) (remove-hook 'kill-buffer-hook - #'treesit--explorer-kill-explorer-buffer t) - (treesit--explorer-kill-explorer-buffer))) + #'treesit--explorer-tree-mode-cleanup t) + ;; Clean up if the user disables `treesit-explore-mode' interactively; e.g., + ;; via M-x while leaving the source buffer alive. + (when (called-interactively-p 'any) + (treesit--explorer-tree-mode-cleanup)))) (defun treesit-explore () "Show the explorer." @@ -5317,6 +5385,15 @@ window." (display-buffer treesit--explorer-buffer '(nil (inhibit-same-window . t))) (treesit-explore-mode))) +(defun treesit-explore-quit () + "Quit and clean up `treesit-explore-mode'. +Invoke this command from the source buffer or its tree buffer." + (interactive) + ;; Called from the source buffer. + (when (buffer-live-p treesit--explorer-buffer) + (treesit-explore-mode -1)) + (treesit--explorer-tree-mode-cleanup)) + ;;; Install & build language grammar (defvar treesit-language-source-alist nil commit 48b064a2aa3bb25137c31aac9994cd2d36fe71c2 Author: João Távora Date: Thu May 7 22:06:45 2026 +0100 Fix 'vc-dir-resynch-file' again (bug#80967) This unbreak project-vc-dir for dirs under non-truename hierarchies. The following commit presumably makes 'M-x vc-dir' usable again for versioned directories inside non-truename hierarchies, commit e05fab5775c96f8f88eab8d75dea40253bfb78eb Author: Stephen Berman Date: Sat May 2 15:11:37 2026 +0200 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. However the similar command 'M-x project-vc-dir' was broken and made unusable in similar circumstances. This relatively simple fix addresses both situations touching only the problematic 'vc-resynch-file' and one of its callees, 'vc-dir-recompute-file-state', which now discerns clearly between the short/familiar name to present in the list and the "fname" to use to call into the backend to gather the VC state. Since this function is also called from another context, where the requirements are less clear, keeping current smenatics in that situation seemed prudent, so the new behaviour is activate with a new optional parameter. * lisp/vc/vc-dir.el (vc-dir-resynch-file): Call vc-dir-recompute-file-state with truename=t. (vc-dir-recompute-file-state): Accept optional truename param. diff --git a/lisp/vc/vc-dir.el b/lisp/vc/vc-dir.el index 21658312a13..3c9222d725f 100644 --- a/lisp/vc/vc-dir.el +++ b/lisp/vc/vc-dir.el @@ -1261,8 +1261,12 @@ that file." (vc-dir-fileinfo->state crt-data)) result)) (nreverse result))) -(defun vc-dir-recompute-file-state (fname def-dir) - (let* ((file-short (file-relative-name fname def-dir)) +(defun vc-dir-recompute-file-state (fname def-dir &optional truename) + "Compute state of FNAME known to live inside DEF-DIR. +If TRUENAME is non-nil, FNAME is a truename, DEF-DIR not necessarily." + (let* ((file-short (file-relative-name + fname (if truename (file-truename def-dir) def-dir))) + (fname (if truename (expand-file-name file-short def-dir) fname)) (_remove-me-when-CVS-works (when (eq vc-dir-backend 'CVS) ;; FIXME: Warning: UGLY HACK. The CVS backend caches the state @@ -1330,7 +1334,11 @@ that file." (vc-dir-resync-directory-files file) (ewoc-set-hf vc-ewoc (vc-dir-headers vc-dir-backend ddir) "")) - (let* ((complete-state (vc-dir-recompute-file-state file ddir)) + (let* ((complete-state + ;; Make sure 'vc-dir-recompute-file-state' + ;; knows about the truename nature of 'file' + ;; (bug#80967). + (vc-dir-recompute-file-state file ddir t)) (state (cadr complete-state))) (vc-dir-update (list complete-state) commit 90f8f27a5806896614d22e299abef2a6b2d78cf8 Merge: 868fd126aea 8d0bf280a64 Author: Sean Whitton Date: Thu May 7 20:15:39 2026 +0100 Merge from origin/emacs-31 8d0bf280a64 ; * ChangeLog.5: Some fixes and tidying up. 1ec79b48f38 ; Update exported ChangeLog files and etc/AUTHORS 991f6100eb1 ; * admin/make-tarball.txt: Suggest load-file, not require. 3c6c3f5a690 ; Fix two file headers misunderstood by authors.el. commit 868fd126aea6d57072f4a2aeb36a2c8bc6f38a60 Merge: ddde687b3f9 311f1fe2ba2 Author: Sean Whitton Date: Thu May 7 20:15:39 2026 +0100 ; Merge from origin/emacs-31 The following commit was skipped: 311f1fe2ba2 Cut the emacs-31 release branch commit 8d0bf280a64a96deec4e4bf62bf36af7b42d2f4a Author: Sean Whitton Date: Thu May 7 20:14:12 2026 +0100 ; * ChangeLog.5: Some fixes and tidying up. diff --git a/ChangeLog.5 b/ChangeLog.5 index ff0d0aebc80..c20a552f2c5 100644 --- a/ChangeLog.5 +++ b/ChangeLog.5 @@ -5193,31 +5193,6 @@ dimensions to real values. (Fimage_transforms_p): Fix typo. -2026-03-04 Michael Albinus - - Revert "Repair serious breakage in the batch tests." - - This reverts commit feac53141577161c32a7a6dfe75399a5ae98a7c1. - - This patch has deactivated 253 test cases without a sufficient reasoning. - Instead it speaks about a shotgun in its commite message. - - The patch is reverted because - - - It hasn't been discussed on emacs-devel. It should have, because it is - a serious change in our infrastructure (new official tag :nobatch). Any - documentation of this change, for example in test/README, is missing. - - - The proper way to deactivate such tests would have been - - (skip-when noninteractive) - - Even better to skip for the respective reasons. - - - There is no fault report. There is no information about how these tests - have failed. Since it hasn't been a problem so far for us, nobody will - work on a fix forever. - 2026-03-04 Stefan Monnier (flymake-start): Give a bit more info in the log @@ -5254,14 +5229,6 @@ * lisp/frame.el (frame--purify-parameters): 'frame-inherited-parameters' is a parameter list, not an alist. -2026-03-03 Mattias Engdegård - - Revert "Rename 'any' to 'member-if' and deprecate 'cl-member-if'" - - This reverts commit 2bdf15f6d8293b21234cd236f39ce68f62e1f6c3. - - There is no consensus for this change. - 2026-03-03 Mattias Engdegård Faster JSON string serialisation (bug#80529) @@ -5324,22 +5291,6 @@ * lisp/vc/vc.el (vc-print-change-log, vc-print-root-change-log): Respect vc-log-show-limit when there is no prefix argument (bug#80532). -2026-03-03 Sean Whitton - - Rename 'any' to 'member-if' and deprecate 'cl-member-if' - - * lisp/subr.el (any): Rename from this ... - (member-if): ... to this. All uses changed. - Implement '&key KEY-FN' for backwards compatibility. - (any): New function alias. - * lisp/emacs-lisp/cl-seq.el (cl-member-if): Make an alias for - 'member-if'. - * lisp/obsolete/cl.el (member-if): Delete obsolete function - alias. - * doc/lispref/lists.texi (List Elements): - * doc/misc/cl.texi (Lists as Sets): - * etc/NEWS: Document the change. - 2026-03-03 Sean Whitton dired-diff: Fix default input in inserted subdirectory @@ -5816,20 +5767,6 @@ indentation rules in other TS modes for embedding C/++ segments in other languages. -2026-02-26 Eric S. Raymond - - Repair another test bollixed by aggressive optimization. - - Repair ab ecal test by making a variable kexical, - - Complete the test set for floatfns,c. - - Tesrts for the portable primitives in fileio.c. - - Tests for primitives in coding.c and charset.c. - - Tests for primitives from the character.c module. - 2026-02-26 Stefan Monnier lisp/vc/smerge-mode.el (smerge-refine-shadow-cursor): Make it thinner @@ -5843,67 +5780,6 @@ because it's occasionally bad enough that it's unclear which cursor is the real one. -2026-02-26 Eric S. Raymond - - Tests for the lreaf.c amd print.c primitives. - -2026-02-25 Eric S. Raymond - - Tests for remaining functions iun eval.c. - - Completing test coverage for dataa.c orimitives. - - More correctness tesrs for orinitives from fns.c. - - More tests for edit functions, buffers, and markers. - - Added more buffer/marker/editing test coverage. - - Category/charset/coding + char-table tests. - - More test coverage improvements. - -2026-02-25 Eric S. Raymond - - Repair serious breakage in the batch tests. - - There were a bunch of tests that were breaking make check and should - never be run in batch mode, because they do things like assuming there - is a controlling tty or assuming we can access network services when - we can't (e/g. in a CI/CD environment). I have shotgunned this - problem by tagging all the failing tests with :nobatch and then - changing the default and expensive selectors so make check won't barf - all over its shoes. - - As many of these :nobatch should be individually removed as possible, after - upgrading the test harness to mock the environmental stuff they need. - Investigate these failures with "make check-nobatch". - -2026-02-25 Eric S. Raymond - - More test coverage improvements. - - Bignum corner-case tests in data-tests.el. - More buffer-primitive tests in editfns-test.el - Some condition-case tesrs in eval-tests.el. - And another marker-primitive test in marker-tests.el. - -2026-02-25 Eric S. Raymond - - More test coverage improvements for ERT. - - In marker-tests.el, editfbs-tests.el, and data-tests.el. - -2026-02-25 Eric S. Raymond - - Crrections to tedt coverrage extensuion after bootstrap build. - - Files: data-tests.el, editfns-tests.el. - -2026-02-25 Eric S. Raymond - - Improve test coverage of builtin predicates. - 2026-02-25 Sean Whitton New function multiple-command-partition-arguments @@ -5924,25 +5800,6 @@ Don't autoload the `treesit-language-source-alist` setting. Generate simpler code for the common case where AUTO-MODE is a string. -2026-02-25 Eric S. Raymond - - Tests for 2 marker primitives previously not covered. - - - insertion-type - - last-position-after-kill - -2026-02-25 Eric S. Raymond - - Tests for 7 editor primitives previously not covered. - - - byte-to-position - - byte-to-string - - insert-byte - - insert-buffer-substring - - insert-before-markers-and-inherit - - field-string-and-delete - - constrain-to-field - 2026-02-25 Liu Hui calendar-check-holidays: Call calendar-increment-month @@ -17182,10 +17039,10 @@ 2025-11-10 Sean Whitton - vc-do-command: Support discarding standard error + vc-do-command: Support discarding stdout and stderr * lisp/vc/vc-dispatcher.el (vc-do-command): Support discarding - standard error. + standard output and standard error. * lisp/vc/vc-hg.el (vc-hg-dir-status-files): Discard standard error of 'hg status' to avoid parsing mistakes. (vc-hg-command): Update docstring given new meaning of first @@ -25520,7 +25377,7 @@ 2025-07-25 Sean Whitton - VC: New support for other working trees + VC: New support for other working trees (bug#79024) * lisp/vc/vc-git.el (vc-git--read-start-point): New function, factored out of vc-git-create-tag. @@ -36502,7 +36359,7 @@ * lisp/vc/log-edit.el (log-edit-mode): Don't add rear-nonsticky to font-lock-extra-managed-props (bug#77197). Investigated by - Paul D. Nelson . Fix due to Stefan Monnier. + Paul D. Nelson . Fix due to Stefan Monnier. 2025-03-23 Stefan Kangas commit 1ec79b48f3802b71b017399aef29b2dbdc5e7d2d Author: Sean Whitton Date: Thu May 7 20:08:43 2026 +0100 ; Update exported ChangeLog files and etc/AUTHORS There are still unfixed problems in *Authors Errors*. * ChangeLog.3: Fix typos. * ChangeLog.5: Export from VCS history. * Makefile.in: Update PREFERRED_BRANCH. * admin/authors.el (authors-aliases, authors-ignored-files) (authors-valid-file-names, authors-renamed-files-alist): Add some entries. * etc/AUTHORS: Regenerate. diff --git a/ChangeLog.3 b/ChangeLog.3 index 7d9ac034cdc..e34e7bfc6f4 100644 --- a/ChangeLog.3 +++ b/ChangeLog.3 @@ -3149,7 +3149,7 @@ Remove mention of removed `gnus-treat-play-sounds' variable from manual - * info/gnus.info: Remove `gnus-treat-play-sounds' from + * doc/misc/gnus.texi: Remove `gnus-treat-play-sounds' from manual. According to lisp/gnus/ChangeLog.3 this variable was removed in 2010 (bug#53192). @@ -46460,7 +46460,7 @@ * lisp/subr.el (ctl-x-map): Initialize inside the declaration. - * src/command.h (control_x_map): + * src/commands.h (control_x_map): * src/keymap.c (control_x_map): Delete variable. (syms_of_keymap): * src/keyboard.c (keys_of_keyboard): diff --git a/ChangeLog.5 b/ChangeLog.5 index 43806515e08..ff0d0aebc80 100644 --- a/ChangeLog.5 +++ b/ChangeLog.5 @@ -1,3 +1,63126 @@ +2026-05-07 Sean Whitton + + Cut the emacs-31 release branch + + * README: + * configure.ac: + * exec/configure.ac: + * java/AndroidManifest.xml.in (Version-code): + * msdos/sed2v2.inp: + * nt/README.W32: Bump Emacs version to 31.0.60. + * lisp/cus-edit.el (customize-changed-options-previous-release): + Set last version to 30.2. + +2026-05-07 Zeke Dou (tiny change) + + Move ns_init_colors() after init_callproc() (bug#80752) + + 'data-directory' needs to be established in advance of 'ns_init_colors' + to ensure the file "etc/rgb.txt" is read. This was encountered on an + out-of-tree Nix build. + + * src/emacs.c (main): Move the 'ns_init_colors' after 'init_callproc'. + +2026-05-07 Jonas Bernoulli + + Update to Transient v0.13.3-10-g87d0ca08 + +2026-05-07 Eli Zaretskii + + Fix infloop in redisplay due to continuation glyphs + + * src/xdisp.c (display_line): When inserting continuation glyphs, + account for the border glyph in non-rightmost windows on TTY + frames. (Bug#80975) + +2026-05-07 Sean Whitton + + Unpreload package-activate-all + + See for my reasoning. + + * lisp/emacs-lisp/package-activate.el (package-activate-all): + Unpreload. + +2026-05-07 Dmitry Gutov + + [GTK3] Move the frame to position before showing + + * src/xterm.c (x_make_frame_visible): Move XMoveWindow call before + gtk_widget_show_all, so that the move happens before the frame + become visible (bug#80662). + +2026-05-07 Dmitry Gutov + + [GTK3] Improve the resize -> hide -> show scenario + + * src/gtkutil.c (xg_frame_set_char_size) + (xg_frame_set_size_and_position): Call gtk_window_resize for + child frames too, to record _GtkWindowGeometryInfo#resize_width + and resize_height. They are later looked up by + gtk_widget_show_all in x_make_frame_visible (bug#80662). + Without this the widgets go back and forth between the + remembered and actual sizes after make-visible. + +2026-05-06 Michael Albinus + + Handle long environment variables in Tramp oricesses + + * lisp/net/tramp-sh.el (tramp-sh-handle-make-process): + Handle loooong environment variables. (Bug#80783) + + * test/lisp/net/tramp-tests.el (tramp-test33-environment-variables): + Adapt test. + +2026-05-06 Dmitry Gutov + + Fix flicker of child frame right after make-frame-visible + + * src/xterm.c (x_make_frame_visible): Call SET_FRAME_GARBAGED + before making a child frame visible (bug#80943). + +2026-05-05 Stefan Monnier + + (help--symbol-completion-table): Try and fix bug#80873 + + * lisp/help-fns.el (help--symbol-completion-table): Don't let + `test-completion` pretend that `definition-prefixes` are + actually valid function names. + +2026-05-05 Juri Linkov + + Improve 'context-menu-send-to' (bug#79512) + + * lisp/mouse.el (context-menu-functions): Add missing + 'context-menu-send-to' to :type. + (context-menu-send-to): Get non-nil items to avoid + useless message "Nothing to send". + + * lisp/send-to.el (send-to-handlers): Change from + 'defvar-local' to 'defvar' to allow easier global configuration. + (send-to-supported-p): Check for non-nil handler. + +2026-05-05 Sean Whitton + + vc-switch-working-tree: Don't find non-VC projects + + * lisp/vc/vc.el (project-current-directory-override): + Delete declaration. + (project-find-matching-buffer-function): Declare. + (vc-switch-working-tree): + Don't find non-VC projects (bug#80939). + +2026-05-05 Sean Whitton + + vc-finish-logentry: Skip displaying async command buffer sometimes + + * lisp/vc/vc-dispatcher.el (vc-finish-logentry): Don't display + the async command buffer if vc-display-failed-async-commands is + non-nil. + +2026-05-05 Eshel Yaron + + elisp-mode: Cache 'help-echo' function results (bug#80948) + + This ensures we only compute the 'help-echo' string once per + symbol in a certain position. + + * lisp/progmodes/elisp-mode.el + (elisp--annotate-symbol-with-help-echo): Add caching for + when the symbol role's :help property is a function. + +2026-05-05 Andrea Alberti + + Introduce 'margin' face for window margin background + + A new basic face 'margin' is used for text displayed in the left and + right margin areas, i.e., the areas typically used by VCS and LSP + packages for per-line annotations. Its background defaults to the + frame default, preserving existing behavior for users who do not + customize it. + * etc/NEWS: Document the new 'margin' face. + * lisp/faces.el (margin): Add 'margin' face, inheriting from 'default'. + * src/dispextern.h (face_id): Add MARGIN_FACE_ID. + * src/xdisp.c (face_at_pos): Use 'margin' as the base face for + strings displayed in margin areas so that they inherit the gutter + background by default. + (extend_face_to_end_of_line): Compute 'margin_fill_face_id' from the + 'margin' face. Use while loops to explicitly fill all empty character + slots in both left and right margins for both GUI and TTY branches. + (display_line): Call 'extend_face_to_end_of_line' for beyond-EOB rows + when the window has margins. Also extend the existing condition for + text rows with empty margins to trigger when the 'margin' face + background differs from the frame default, not only when the default + face is remapped. + * src/xfaces.c (realize_basic_faces): Realize 'margin' as a basic + face to support face-remapping and efficient lookup. + (Bug#80693) + +2026-05-05 Augusto Stoffel + + Accept stream type as 4th argument of X-Message-SMTP-Method header + + * lisp/gnus/message.el (message-multi-smtp-send-mail): Set + 'smtpmail-stream-type' if specificed in the header. + * doc/misc/message.texi (Mail Variables): Document that. + (Bug#80880) + +2026-05-05 Rahul Martim Juliato + Stéphane Marks + + Improve 'markdown-ts-mode' + + Overhaul 'markdown-ts-mode' with comprehensive fontification, + code block handling, editing commands, fill-paragraph support, + inline image previews, a mode menu, and table editing. Add + 'markdown-ts-mode-x.el' with conversion/export and TOC support. + + * lisp/textmodes/markdown-ts-mode.el: Add Version, + Package-Requires, and Keywords header fields. Expand Commentary + with documentation for code block language modes, code block + commands, bidirectional text, and known tree-sitter grammar + bugs. + (require 'goto-addr, xref, icons): New requirements. + (markdown-ts): New customization group with :version and + :package-version tags. + (markdown-ts-hide-markup): Add :version and :package-version. + (markdown-ts-ellipsis): New option for folded heading ellipsis. + (markdown-ts-menu-bar-show): New option to toggle mode menu. + (markdown-ts-default-folding): New option for default fold + level. + (markdown-ts-inline-images): New option to display inline + images. + (markdown-ts-image-max-width): New option for image max width. + (markdown-ts-display-remote-inline-images): New option for + remote image handling. + (markdown-ts--resolve-display-value): New helper to resolve + display values using char-displayable-p. + (markdown-ts-checked-checkbox): New option for checked checkbox + display string or icon. + (markdown-ts-checked-checkbox-icon): New icons.el icon for the + checked variant. + (markdown-ts-unchecked-checkbox): New option for unchecked + checkbox display string or icon. + (markdown-ts-unchecked-checkbox-icon): New icons.el icon for the + unchecked variant. + (markdown-ts-thematic-break-character): New option for thematic + break display character. + (markdown-ts-hard-line-break-backslash): New option for the + glyph shown in place of a trailing-backslash hard line break + when markup is hidden. + (markdown-ts-hard-line-break-space): New option for the glyph + shown in place of a trailing-spaces hard line break when markup + is hidden. + (markdown-ts-code-block-in-context-mode-lighter): New option for + code block context mode lighter string. + (markdown-ts-inhibit-code-block-mode-warnings): New option to + inhibit code-block major-mode messages and warnings. + (markdown-ts-table-default-column-width): New option for default + table column width. + (markdown-ts-enable-table-mode): New option to enable table + mode. + (markdown-ts-table-auto-align): New option for triggers that + cause automatic table alignment. + (markdown-ts-table-align-features): New option for table align + features. + (markdown-ts-in-table-mode-lighter): New option for + markdown-ts-in-table-mode lighter string. + (markdown-ts-emphasis, markdown-ts-bold) + (markdown-ts-strikethrough, markdown-ts-link) + (markdown-ts-link-destination, markdown-ts-code-span) + (markdown-ts-code-block, markdown-ts-code-block-markup-hidden) + (markdown-ts-indented-code-block, markdown-ts-html-tag) + (markdown-ts-html-block, markdown-ts-thematic-break) + (markdown-ts-entity-reference) + (markdown-ts-numeric-character-reference, markdown-ts-latex) + (markdown-ts-table-header, markdown-ts-table-cell) + (markdown-ts-table-delimiter-cell, markdown-ts-task-unchecked) + (markdown-ts-task-checked): New faces. + (markdown-ts-hard-line-break-backslash) + (markdown-ts-hard-line-break-backslash-hidden) + (markdown-ts-hard-line-break-space) + (markdown-ts-hard-line-break-space-hidden): New faces for the + two hard-line-break variants, with distinct shown/hidden + appearances. + (markdown-ts-in-code-block): New face for when point is inside a + fenced code block. + (markdown-ts-table): New face for Markdown pipe table + background. + (markdown-ts-in-table): New face for markdown-ts-in-table-mode + when point is in a table. + (markdown-ts--set-up-inline): New variable for lightweight setup + in embedded inline markdown-ts-mode buffers used by code block + commands. + (markdown-ts-default-code-block-mode): New variable to define + default mode for anonymous code blocks. + (markdown-ts-fontify-code-blocks-natively): New option to + enable/disable native fontification of fenced code block + contents. + (markdown-ts-enable-code-block-context-mode): New option to + enable/disable code block context integration. + (markdown-ts-code-block-modes): New variable replacing + markdown-ts--code-block-language-map and + markdown-ts-code-block-source-mode-map with unified language to + mode mapping including heuristic lookup. + (markdown-ts-code-block-force-conventional-modes): New variable + listing tree-sitter modes whose fontification of code block + content should be harvested via a temporary buffer. + (markdown-ts-table-export-buffer): New constant for table export + output buffer name. + (markdown-ts--table-row-types, markdown-ts--table-cell-types) + (markdown-ts--table-delimiter-cell-types) + (markdown-ts--table-delimiter-cell-subtypes): New constants for + pipe table node type classification. + (markdown-ts--make-link-button): New function to create + clickable link buttons with mailto, browse-url, and find-file + dispatch. + (markdown-ts--fontify-link-destination): New fontifier that + hides link destinations when markup is hidden. + (markdown-ts--link-ref-cache, markdown-ts--link-ref-cache-tick) + (markdown-ts--link-ref-definitions) + (markdown-ts--resolve-link-ref): New link reference definition + cache with case-insensitive matching per CommonMark spec. + (markdown-ts--fontify-link-node): New fontifier for inline, + reference, shortcut, and collapsed links as clickable buttons. + (markdown-ts--fontify-autolink): New fontifier for URI and email + autolinks with angle bracket hiding. + (markdown-ts--fontify-link-ref-label) + (markdown-ts--fontify-link-ref-destination): New fontifiers for + link reference definitions. + (markdown-ts--slug-github-strip-re, markdown-ts--slug-github) + (markdown-ts--slug-pandoc, markdown-ts--explicit-id-re) + (markdown-ts--heading-text-and-id) + (markdown-ts--heading-id-cache) + (markdown-ts--heading-id-cache-tick) + (markdown-ts--build-heading-ids, markdown-ts--heading-ids) + (markdown-ts--follow-fragment): New fragment link support with + GitHub and Pandoc slug algorithms. + (markdown-ts--latex-block-valid-p) + (markdown-ts--fontify-latex-block): New LaTeX/math fontifier + with cross-paragraph validation. + (markdown-ts--fontify-backslash-escape): New fontifier that + hides backslash in escapes except inside LaTeX blocks. + (markdown-ts--decode-entity, markdown-ts--fontify-entity): New + HTML entity decoder and fontifier using org-entities and + sgml-char-names with display property for decoded values. + (markdown-ts--fontify-checkbox): New fontifier showing the + configured checkbox glyph when markup is hidden. + (markdown-ts--fontify-hard-line-break): New fontifier for + 'hard_line_break' nodes. + (markdown-ts--fontify-heading): New fontifier for ATX and Setext + headings. + (markdown-ts--fontify-thematic-break): New fontifier showing + horizontal line when markup is hidden. + (markdown-ts--fontify-code-block): New fontifier applying + background overlay to code blocks with language and mode + properties. + (markdown-ts-at-code-block-p) + (markdown-ts-code-block-language-at) + (markdown-ts-code-block-mode-at): New accessors for code block + overlay properties. + (markdown-ts--imenu-code-block-node-p): New helper for imenu + code block detection. + (markdown-ts--image-alone-on-line-p) + (markdown-ts--fontify-image): New inline image display with + standalone and inline positioning. + (markdown-ts--bare-url-regexp) + (markdown-ts--bare-email-uri-regexp) + (markdown-ts--fontify-bare-uri): New bare URL/email + fontification via jit-lock using goto-addr regexps. + (markdown-ts--treesit-settings): Rewrite with dedicated faces, + custom fontifiers, and new font-lock features for all inline and + block elements. Also add a new 'error' font-lock feature + highlighting tree-sitter ERROR nodes. + (markdown-ts--code-block-languages) + (markdown-ts--code-block-non-ts-modes): New buffer-local alists + for code block language tracking. + (markdown-ts--harvest-mode-treesit-configuration): Rename from + markdown-ts--harvest-treesit-configs with delay-mode-hooks. + (markdown-ts--configure-current-buffer): Rename from + markdown-ts--add-config-for-mode. + (markdown-ts--language-at-node): New helper extracting language + symbol from code_fence_content node. + (markdown-ts--non-ts-fontify-cache): New buffer-local hash table + caching fontification results for non-tree-sitter code blocks. + (markdown-ts--fontify-non-ts-collect-faces): New function + harvesting face properties from a mode in an indirect or + temporary buffer. + (markdown-ts--fontify-non-ts-code-block): New fontifier for + non-tree-sitter modes via temporary buffer font-lock. + (markdown-ts--code-block-language-mode): New function with + heuristic mode name probing. + (markdown-ts--code-block-ts-language): Rename from + markdown-ts--convert-code-block-language with support for non-ts + modes and recursive markdown handling. + (markdown-ts-code-block-commands) + (markdown-ts-code-block-thing-commands) + (markdown-ts-code-block-region-commands): New command lists for + code-block context execution. + (markdown-ts--enable-code-block-in-context-mode) + (markdown-ts--maybe-run-command-in-code-block) + (markdown-ts--code-block-xref-find-definitions) + (markdown-ts--run-command-in-code-block): New code block minor + mode with temp-buffer command dispatch. + (markdown-ts--code-block-newline): New command for newline + inside code blocks, dispatching to the block's mode. + (markdown-ts--range-settings): Update range configuration; + remove :range-fn treesit-range-fn-exclude-children. + (markdown-ts--remove-image-overlays) + (markdown-ts--outline-view-change): New helpers for image + overlay management during outline fold/unfold. + (markdown-ts--outline-invisible-p): New helper testing whether a + position is inside an outline-folded region. + (markdown-ts--host-ranges-notifier): New function pruning stale + code block overlays when the host parse tree changes. + (markdown-ts--barf-if-not-mode): New helper signaling a + user-error when the current buffer is not derived from + markdown-ts-mode. + (markdown-ts-code-block-in-context-mode): New minor mode enabled + when point is within a fenced code block, activating + markdown-ts-code-block-in-context-mode-map so eligible commands + dispatch to the block's major mode. + (markdown-ts--set-hide-markup): Clear image overlays when + toggling markup visibility. + (markdown-ts--set-inline-images) + (markdown-ts-toggle-inline-images): New functions for toggling + inline image display. + (markdown-ts--parser-heading-max-level): New constant. + (markdown-ts--heading-at-point, markdown-ts--heading-level) + (markdown-ts--section-at-point): New heading navigation helpers. + (markdown-ts-promote, markdown-ts-demote): New commands for + heading and list item promotion/demotion with region support. + (markdown-ts--promote-or-demote) + (markdown-ts--promote-or-demote-region): New heading level + adjustment functions. + (markdown-ts-move-subtree-up, markdown-ts-move-subtree-down): + New commands for moving sections and list items. + (markdown-ts--section-folded-p) + (markdown-ts--move-subtree-up-or-down): New subtree move with + folding state preservation. + (markdown-ts-toggle-checkbox): New command to toggle task list + checkboxes. + (markdown-ts--list-item-at-point): New helper with block quote + marker handling. + (markdown-ts--list-marker-width, markdown-ts--list-item-region) + (markdown-ts--list-ordered-item-p): New list item helpers. + (markdown-ts--list-promote-or-demote): New function with ordered + list nesting guard. + (markdown-ts--list-node-bol, markdown-ts--list-move): New list + item movement functions. + (markdown-ts-renumber-list): New command for sequential list + renumbering with prefix argument support. + (markdown-ts--list-item-new-marker) + (markdown-ts--new-marker-for-line): New marker string generators + for list item insertion. + (markdown-ts--line-block-quote-depth): New helper counting block + quote depth. + (markdown-ts-newline): New RET command for context-aware + newline. + (markdown-ts-insert-list-item): New M-RET command that creates a + new list item. + (markdown-ts--fill-unfillable-block-query): New tree-sitter + query for unfillable blocks. + (markdown-ts--list-item-text-column): New helper for list item + text alignment. + (markdown-ts--fill-list-item): New function filling within list + items without merging adjacent items. + (markdown-ts--adaptive-fill): New adaptive fill function for + list item continuation. + (markdown-ts--fill-forward-paragraph): New paragraph motion + respecting list items and unfillable blocks. + (markdown-ts-fill-paragraph): New fill-paragraph-function with + cond* dispatch for lists, block quotes, HTML comments, and + unfillable blocks. + (markdown-ts--fill-html-comment): New HTML comment filler with + continuation alignment. + (markdown-ts--block-quote-prefix): New helper preserving + existing block quote marker style. + (markdown-ts--fill-block-quote): New block quote filler with + list item text column alignment. + (markdown-ts-emphasis-alist): New alist of emphasis markers. + (markdown-ts--emphasis-node-at-point): New tree-sitter based + emphasis detection. + (markdown-ts-remove-emphasis): New command removing emphasis at + point or region. + (markdown-ts-emphasize): New interactive emphasis insertion with + region wrapping and word-at-point support. + (markdown-ts-insert-structure): New command for inserting code + blocks, block quotes, and dividers. + (markdown-ts--insert-code-block): New function with language + completion from known modes. + (markdown-ts--insert-block-quote): New function with region + wrapping. + (markdown-ts--insert-divider): New function for horizontal + rules. + (markdown-ts--apply-ellipsis): New function applying custom + ellipsis via display table. + (markdown-ts--set-up): Rename from markdown-ts-setup. + (markdown-ts-outline-cycle): New TAB command cycling outline + visibility on headings. + (markdown-ts-mode-map): New keymap for code block navigation, + bindings for heading navigation, structure editing, emphasis, + and list operations. + (markdown-ts-code-block-in-context-mode-map): New keymap for + code block context. + (markdown-ts-mode-menu): New menu bar with Show/Hide, Navigate + Headings, Edit Structure, and Editing sections. + (markdown-ts-mode): Add outline-minor-mode, ellipsis, folding, + fill-paragraph integration, code-block-context-mode hooks, + comment settings, and outline-view-change-hook. Use + markdown-ts-mode directly in auto-mode-alist instead of + markdown-ts-mode-maybe. + (markdown-ts-mode-install-parsers): New command for installing + required and optional tree-sitter language grammars. + (markdown-ts-code-block-context-mode): New minor mode for code + block context. + (markdown-ts--find-code-block-delimiter) + (markdown-ts--find-next-code-block-delimiter): New helpers for + locating fenced code block delimiter nodes. + (markdown-ts-move-to-next-code-block) + (markdown-ts-move-to-previous-code-block): New commands for + navigating between fenced code blocks. + (markdown-ts--table-abutting-pos, markdown-ts--table-node-cell) + (markdown-ts--table-node-row, markdown-ts--table-parse-error-p): + New pipe table node helpers. + (markdown-ts-at-table-p): New predicate returning non-nil when + point is at or within a pipe table. + (markdown-ts--enable-in-table-mode): New helper enabling + markdown-ts-in-table-mode from post-command-hook. + (markdown-ts--table-body-row-near-pos): New helper locating the + nearest body row to a position. + (markdown-ts--table-compute-node-column) + (markdown-ts-table--goto-column): New helpers for column-based + table navigation. + (markdown-ts--table-aligners, markdown-ts--table-make-aligner) + (markdown-ts--table-align-cell): New table cell alignment + helpers. + (markdown-ts-table-insert-table): New command to insert a pipe + table with configurable rows and columns. + (markdown-ts-table-delete-table): New command to delete the pipe + table at point. + (markdown-ts-table-previous-row, markdown-ts-table-next-row) + (markdown-ts-table-previous-cell, markdown-ts-table-next-cell): + New commands for navigating table rows and cells. + (markdown-ts-table-insert-row-below) + (markdown-ts-table-insert-row-above) + (markdown-ts-table-clone-row-below) + (markdown-ts-table-clone-row-above) + (markdown-ts-table-insert-row): New commands for inserting and + cloning table rows. + (markdown-ts-table-delete-row, markdown-ts-table-move-row-up) + (markdown-ts-table-move-row-down, markdown-ts-table-move-row): + New commands for deleting and moving table rows. + (markdown-ts-table-insert-column-left) + (markdown-ts-table-insert-column-right) + (markdown-ts-table-clone-column-left) + (markdown-ts-table-clone-column-right) + (markdown-ts-table-insert-column): New commands for inserting + and cloning table columns. + (markdown-ts-table-delete-column) + (markdown-ts-table-move-column-left) + (markdown-ts-table-move-column-right) + (markdown-ts-table-move-column): New commands for deleting and + moving table columns. + (markdown-ts-table-align-column-left) + (markdown-ts-table-align-column-center) + (markdown-ts-table-align-column-right) + (markdown-ts-table-align-column): New commands for setting + column alignment. + (markdown-ts-table-align-table): New command to align all + columns in the table at point. + (markdown-ts-table-transpose-table): New command to transpose + the table at point. + (markdown-ts-table-convert-csv-region) + (markdown-ts-table-convert-tsv-region) + (markdown-ts-table-convert-region): New commands to convert + delimited text regions to pipe tables. + (markdown-ts-table-export-table-csv) + (markdown-ts-table-export-table-tsv) + (markdown-ts-table-export-table): New commands to export the + pipe table at point to CSV or TSV format. + (markdown-ts-in-table-mode-map): New keymap for + markdown-ts-in-table-mode. + (markdown-ts--code-block-in-context-mode-ov): New buffer-local + variable for code block context overlay. + (markdown-ts--code-block-in-context-mode-update-ov): New + function updating the code block context overlay. + (markdown-ts-in-table-mode): New minor mode enabled when point + is within a pipe table, activating markdown-ts-in-table-mode-map. + (markdown-ts--in-table-mode-ov) + (markdown-ts--in-table-mode-get-ov) + (markdown-ts--table-tick-update) + (markdown-ts--table-tick-stale-p) + (markdown-ts--in-table-mode-update-ov): New helpers supporting + markdown-ts-in-table-mode overlay management. + (markdown-ts-table-mode): New minor mode providing table editing + commands and auto-alignment. + (markdown-ts-mode-maybe): Remove; auto-mode-alist now uses + markdown-ts-mode directly. + Auto-mode-alist: Add .markdown and .mdx extensions. + + * lisp/textmodes/markdown-ts-mode-x.el: New file providing extra + features for markdown-ts-mode. + (markdown-ts-commonmark-spec-url, markdown-ts-gfm-spec-url): New + constants for CommonMark and GFM specification URLs. + (markdown-ts-browse-commonmark-spec) + (markdown-ts-browse-gfm-spec): New commands to browse the + CommonMark and GFM specifications. + (markdown-ts-convert): New customization group for Markdown + conversion/export features. + (markdown-ts-default-converter): New option to set the default + format and converter for Markdown export. + (markdown-ts-convert-display-function): New option specifying + the function used to display converted output. + (markdown-ts-converters): New variable listing format/converter + configurations for pandoc, cmark, cmark-gfm, markdown, and + markdown.pl. + (markdown-ts-convert-file, markdown-ts-convert): New commands to + convert Markdown buffers or files to HTML, PDF, and other + formats via external converters. + (markdown-ts-toc): New customization group for table of contents + features. + (markdown-ts-toc-generate-warn-if-none): New option controlling + behavior when no TOC is found during generation. + (markdown-ts-toc-update-before-save-mode-lighter): New option + for the before-save TOC update minor mode lighter string. + (markdown-ts-toc-slug-function): New option to select the slug + algorithm used for TOC heading anchors. + (markdown-ts--toc-handles, markdown-ts--toc-handle-classes): New + variables defining TOC handle types and classes. + (markdown-ts--toc-expand-candidate-handles, markdown-ts--tocs) + (markdown-ts--tocs-sanity-check) + (markdown-ts--toc-collect-candidates) + (markdown-ts--toc-list-item-depth) + (markdown-ts--toc-atx_header-normalize) + (markdown-ts--toc-text-normalizers) + (markdown-ts--toc-text-normalize): New internal helpers for TOC + parsing and text normalization. + (markdown-ts-toc-update-before-save-mode): New minor mode that + regenerates tables of contents before saving. + (markdown-ts-toc-clear-and-remove, markdown-ts-toc-clear): New + commands to remove or clear TOC bodies. + (markdown-ts-toc-insert-template): New command to insert a TOC + template at point. + (markdown-ts-toc-generate): New command to generate tables of + contents in the current buffer. + + Includes fixes for: (bug#80613) (bug#80625) (bug#80690) + +2026-05-05 Stefan Monnier + + nadvice.el: Make it easier to find how to change an interactive-form + + * lisp/emacs-lisp/nadvice.el (advice--how-alist): Add ':interactive-only'. + * doc/lispref/functions.texi (Advice Combinators): Document it. + (Core Advising Primitives): Use it. + +2026-05-05 Stefan Monnier + + lisp/emacs-lisp/lisp-mode.el (lisp-fdefs): Avoid obsolete "face vars" + +2026-05-04 Stefan Monnier + + keyboard.c: Allow SIGINT to `quit` in batch mode, instead of exit + + In terminal sessions, SIGINT is turned into a `quit` ELisp signal, + but in batch it has traditionally killed Emacs. It can be very + useful to cause a `quit` from outside the process when running + in batch (e.g. for "batch" sessions that provide a REPL via stdin/out), + so add a new var 'kill-emacs-on-sigint' to control that behavior. + (bug#80942) + + * src/keyboard.c (handle_interrupt_signal): Obey `kill_emacs_on_sigint`. + (init_keyboard): Use `deliver_interrupt_signal` for SIGINT also for + batch sessions. + (syms_of_keyboard): New variable `kill_emacs_on_sigint`. + + * test/src/keyboard-tests.el (keyboard-sigint-to-quit): New test. + + * doc/emacs/cmdargs.texi (Initial Options): Mention the effect of + `kill-emacs-on-sigint` in batch mode. + +2026-05-04 Michael Albinus + + Adaot tramp-tests.el + + * test/lisp/net/tramp-tests.el (ert-remote-temporary-file-directory): + Ensure that it is expanded. + +2026-05-04 Michael Albinus + + * admin/notes/documentation: Recommend not using "it's". + +2026-05-04 João Távora + + Jsonrpc: add new tests using Python subprocesses + + Most of these tests are for the scontrol/"anxious continuation" + mechanism (bug#80623) + + The new ERT tests use Python subprocesses via stdin/stdout pipe as + JSONRPC endpoints. A shared framing library lives in + jsonrpc-resources/common.py. + + * test/lisp/jsonrpc-tests.el (jsonrpc--test-dir): New constant. + (jsonrpc--with-python-fixture): New macro. + (scontrol-remote-during-sync-1) + (scontrol-remote-during-sync-2) + (scontrol-anxious-nested) + (scontrol-remote-error) + (shutdown-clean-after-notification): New tests. + + * test/lisp/jsonrpc-resources/common.py: New file. + + * test/lisp/jsonrpc-resources/server-remote-during-sync-1.py: New file. + + * test/lisp/jsonrpc-resources/server-remote-during-sync-2.py: New file. + + * test/lisp/jsonrpc-resources/server-anxious-nested.py: New file. + + * test/lisp/jsonrpc-resources/server-remote-error.py: New file. + + * test/lisp/jsonrpc-resources/server-harakiri.py: New file. + +2026-05-04 João Távora + + Jsonrpc: rework sync request handling (bug#80623) + + When the remote endpoint is handling a local request 'LR' it can + sometimes make a remote sync request 'RR' as part of its handling. Some + endpoints (like Go's gopls) wait for Emacs's reply to 'RR' before + responding to 'LR'. Others (like Julia's JETLS) respond to 'LR' + immediately, and only then wait for Emacs's reply to 'RR'. Both + approaches are valid. However, in the latter case, the handling of 'RR' + (which could well be waiting for user input from the minibuffer to + complete) is vulnerable to 'throw' from process filters handling (which + is just what happens when the endpoints replies to 'LR'), so if that + happens it will unexpectedly be aborted when the reply to 'LR' comes in, + suprising the user and causing a spurious -32603 reply to be sent. + + To solve this problem, this commit first refactors the sync + request/anxious queue handling and replace plain integer keys of the + rebaptized "scontrol" alist with structured (:local ID) / (:remote ID) + pairs, using `equal' for comparisons. This is to introduce some + clarity/sanity into this somewhat hairy code. + + Then, the 'RR' situation is fixed: we push a (:remote ID) entry onto the + top of the 'jsonrpc-connection--control' stack and only then call + rdispatcher. Any 'LR' reply arriving during dispatch is deferred as an + "anxious" continuation rather than firing its `throw' immediately. When + rdispatcher is done, we call jsonrpc--continue at a safe point, this + will run any "anxious" continuations. + + * lisp/jsonrpc.el (jsonrpc-connection): Rename -sync-request-alist to + -scontrol; accessor from jsonrpc--sync-request-alist to + jsonrpc--scontrol; update docstring for new key structure. + + (jsonrpc-connection-receive): Update with-slots binding to scontrol. + Tighten anxious check to match (:local ID) keys with `equal'. In + remote-request branch, push (:remote ID) entry before rdispatcher and + call jsonrpc--continue after jsonrpc--reply. + + (jsonrpc-request): Pass (:local ID) to jsonrpc--continue. + + (jsonrpc--continue): Use jsonrpc--scontrol; change `=' to `equal' in + sanity check. + p + (jsonrpc--async-request-1): Push (:local ID) entry. + + (jsonrpc--log-event): Use jsonrpc--scontrol. + +2026-05-04 Dmitry Gutov + + Re-add a call to clear_under_internal_border in clear_garbaged_frames + + * src/xdisp.c (clear_garbaged_frames): Re-add the call to + clear_under_internal_border, reverting that part of commit + a6a3b32208c5 as not essential to the fix (bug#80662). + +2026-05-03 Philip Kaludercic + + Ensure package archives are loaded for 'package-isolate' + + * lisp/emacs-lisp/package.el (package-isolate): Use + 'package--archive-contents' instead of the actual variable, to + ensure that we load the archive contents if missing. This is + likely the case if 'package-isolate' is the first package + function invoked during a session. + +2026-05-03 Philip Kaludercic + + Add for to 'html-tag-alist' and 'html-tag-help' + + * lisp/textmodes/sgml-mode.el (html-tag-alist): Add very basic skeleton. + (html-tag-help): Add description. + +2026-05-03 Philip Kaludercic + + Add for
to 'html-tag-alist' and 'html-tag-help' + + * lisp/textmodes/sgml-mode.el (html-tag-alist): Add skeleton. + (html-tag-help): Add description. + +2026-05-03 Philip Kaludercic + + 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