commit efb83df331425ae66e9d031a1ac35c2215612b52 Author: Paul Eggert Date: Mon May 18 22:59:45 2026 -0700 Don’t trust RLIMIT_NOFILE in src/process.c Problem discovered on Fedora 44 x86-64 when using GCC 16.1.1 with -fsanitize=address, with test/src/process-tests.el tests that use process-tests--with-raised-rlimit. This function overrides the default of 1024 for the maximum number of open files, which causes undefined behavior (subscript errors) in src/process.c. * src/process.c (inrange_fd, inrange_pipe): New functions. (allocate_pty, create_process, create_pty, Fmake_pipe_process) (Fmake_serial_process, connect_network_socket) (network_interface_info, server_accept_connection) (Fprocess_send_eof, child_signal_init): Check that all newly allocated file descriptors are less than FD_SETSIZE; close them and fail otherwise. (create_pty, Fmake_pipe_process, Fmake_serial_process) (connect_network_socket, server_accept_connection) (child_signal_init): Remove no-longer-needed comparisons to FD_SETSIZE, now that inrange_fd and inrange_pipe do the checking for us. diff --git a/src/process.c b/src/process.c index 9ea2b66533b..9e807bef44e 100644 --- a/src/process.c +++ b/src/process.c @@ -476,6 +476,44 @@ clear_fd_callback_data (struct fd_callback_data* elem) elem->waiting_thread = NULL; } +/* If FD is out of range, close it and return -1, setting errno to + EMFILE. Otherwise, return FD. This module routinely does this for + file descriptors so that fd_set-based primitives work even on + platforms lacking setrlimit (RLIMIT_NOFILE, ...) or if some Emacs + module or even some other process raises Emacs's RLIMIT_NOFILE limit. */ +static int +inrange_fd (int fd) +{ + if (fd < FD_SETSIZE) + return fd; + emacs_close (fd); + errno = EMFILE; + return -1; +} + +/* Create a pipe into FD[0] and fd[1], refusing to create file + descriptors out of range. This is like inrange_fd, that it + only. */ +static int +inrange_pipe (int fd[2]) +{ + int pipefd[2]; + int result = emacs_pipe (pipefd); + if (result < 0) + return result; + else if (pipefd[0] < FD_SETSIZE && pipefd[1] < FD_SETSIZE) + { + fd[0] = pipefd[0]; + fd[1] = pipefd[1]; + return result; + } + else + { + inrange_fd (pipefd[0]); + inrange_fd (pipefd[1]); + return -1; + } +} /* Add a file descriptor FD to be monitored for when read is possible. When read is possible, call FUNC with argument DATA. */ @@ -493,7 +531,7 @@ add_read_fd (int fd, fd_callback func, void *data) void add_non_keyboard_read_fd (int fd, fd_callback func, void *data) { - add_read_fd(fd, func, data); + add_read_fd (fd, func, data); fd_callback_info[fd].flags &= ~KEYBOARD_FD; } @@ -863,6 +901,8 @@ allocate_pty (char pty_name[PTY_NAME_SIZE]) fd = emacs_open (pty_name, O_RDWR | O_NONBLOCK, 0); #endif /* no PTY_OPEN */ + fd = inrange_fd (fd); + if (fd >= 0) { #ifdef PTY_TTY_NAME_SPRINTF @@ -892,6 +932,8 @@ allocate_pty (char pty_name[PTY_NAME_SIZE]) setup_pty (fd); return fd; } + else if (errno == EMFILE) + return fd; } #endif /* HAVE_PTYS */ return -1; @@ -2184,7 +2226,7 @@ create_process (Lisp_Object process, char **new_argv, Lisp_Object current_dir) then close it and reopen it in the child. */ /* Don't let this terminal become our controlling terminal (in case we don't have one). */ - pty_tty = emacs_open (pty_name, O_RDWR | O_NOCTTY, 0); + pty_tty = inrange_fd (emacs_open (pty_name, O_RDWR | O_NOCTTY, 0)); if (pty_tty < 0) report_file_error ("Opening pty", Qnil); #endif /* not USG, or USG_SUBTTY_WORKS */ @@ -2201,7 +2243,7 @@ create_process (Lisp_Object process, char **new_argv, Lisp_Object current_dir) } else { - if (emacs_pipe (p->open_fd + SUBPROCESS_STDIN) != 0) + if (inrange_pipe (p->open_fd + SUBPROCESS_STDIN) < 0) report_file_error ("Creating pipe", Qnil); forkin = p->open_fd[SUBPROCESS_STDIN]; outchannel = p->open_fd[WRITE_TO_SUBPROCESS]; @@ -2215,7 +2257,7 @@ create_process (Lisp_Object process, char **new_argv, Lisp_Object current_dir) } else { - if (emacs_pipe (p->open_fd + READ_FROM_SUBPROCESS) != 0) + if (inrange_pipe (p->open_fd + READ_FROM_SUBPROCESS) < 0) report_file_error ("Creating pipe", Qnil); inchannel = p->open_fd[READ_FROM_SUBPROCESS]; forkout = p->open_fd[SUBPROCESS_STDOUT]; @@ -2239,11 +2281,8 @@ create_process (Lisp_Object process, char **new_argv, Lisp_Object current_dir) close_process_fd (&pp->open_fd[SUBPROCESS_STDIN]); } - if (FD_SETSIZE <= inchannel || FD_SETSIZE <= outchannel) - report_file_errno ("Creating pipe", Qnil, EMFILE); - #ifndef WINDOWSNT - if (emacs_pipe (p->open_fd + READ_FROM_EXEC_MONITOR) != 0) + if (inrange_pipe (p->open_fd + READ_FROM_EXEC_MONITOR) < 0) report_file_error ("Creating pipe", Qnil); #endif @@ -2351,14 +2390,12 @@ create_pty (Lisp_Object process) if (pty_fd >= 0) { p->open_fd[SUBPROCESS_STDIN] = pty_fd; - if (FD_SETSIZE <= pty_fd) - report_file_errno ("Opening pty", Qnil, EMFILE); #if ! defined (USG) || defined (USG_SUBTTY_WORKS) /* On most USG systems it does not work to open the pty's tty here, then close it and reopen it in the child. */ /* Don't let this terminal become our controlling terminal (in case we don't have one). */ - int forkout = emacs_open (pty_name, O_RDWR | O_NOCTTY, 0); + int forkout = inrange_fd (emacs_open (pty_name, O_RDWR | O_NOCTTY, 0)); if (forkout < 0) report_file_error ("Opening pty", Qnil); p->open_fd[WRITE_TO_SUBPROCESS] = forkout; @@ -2455,15 +2492,11 @@ usage: (make-pipe-process &rest ARGS) */) record_unwind_protect (remove_process, proc); p = XPROCESS (proc); - if (emacs_pipe (p->open_fd + SUBPROCESS_STDIN) != 0 - || emacs_pipe (p->open_fd + READ_FROM_SUBPROCESS) != 0) + if (inrange_pipe (p->open_fd + SUBPROCESS_STDIN) < 0 + || inrange_pipe (p->open_fd + READ_FROM_SUBPROCESS) < 0) report_file_error ("Creating pipe", Qnil); outchannel = p->open_fd[WRITE_TO_SUBPROCESS]; inchannel = p->open_fd[READ_FROM_SUBPROCESS]; - - if (FD_SETSIZE <= inchannel || FD_SETSIZE <= outchannel) - report_file_errno ("Creating pipe", Qnil, EMFILE); - fcntl (inchannel, F_SETFL, O_NONBLOCK); fcntl (outchannel, F_SETFL, O_NONBLOCK); @@ -3209,10 +3242,10 @@ usage: (make-serial-process &rest ARGS) */) record_unwind_protect (remove_process, proc); p = XPROCESS (proc); - fd = serial_open (port); + fd = inrange_fd (serial_open (port)); + if (fd < 0) + report_file_error ("Opening serial port", port); p->open_fd[SUBPROCESS_STDIN] = fd; - if (FD_SETSIZE <= fd) - report_file_errno ("Opening serial port", port, EMFILE); p->infd = fd; p->outfd = fd; if (fd > max_desc) @@ -3471,20 +3504,12 @@ connect_network_socket (Lisp_Object proc, Lisp_Object addrinfos, int socktype = p->socktype | SOCK_CLOEXEC; if (p->is_non_blocking_client) socktype |= SOCK_NONBLOCK; - s = socket (family, socktype, protocol); + s = inrange_fd (socket (family, socktype, protocol)); if (s < 0) { xerrno = errno; continue; } - /* Reject file descriptors that would be too large. */ - if (FD_SETSIZE <= s) - { - emacs_close (s); - s = -1; - xerrno = EMFILE; - continue; - } } if (p->is_non_blocking_client && ! (SOCK_NONBLOCK && socket_to_use < 0)) @@ -4500,7 +4525,7 @@ network_interface_info (Lisp_Object ifname) error ("Interface name too long"); lispstpcpy (rq.ifr_name, ifname); - s = socket (AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0); + s = inrange_fd (socket (AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0)); if (s < 0) return Qnil; specpdl_ref count = SPECPDL_INDEX (); @@ -4979,14 +5004,7 @@ server_accept_connection (Lisp_Object server, int channel) union u_sockaddr saddr; socklen_t len = sizeof saddr; - s = accept4 (channel, &saddr.sa, &len, SOCK_CLOEXEC); - - if (FD_SETSIZE <= s) - { - emacs_close (s); - s = -1; - errno = EMFILE; - } + s = inrange_fd (accept4 (channel, &saddr.sa, &len, SOCK_CLOEXEC)); if (s < 0) { @@ -7484,7 +7502,7 @@ process has been transmitted to the serial port. */) shutdown (old_outfd, 1); #endif close_process_fd (&p->open_fd[WRITE_TO_SUBPROCESS]); - new_outfd = emacs_open (NULL_DEVICE, O_WRONLY, 0); + new_outfd = inrange_fd (emacs_open (NULL_DEVICE, O_WRONLY, 0)); if (new_outfd < 0) report_file_error ("Opening null device", Qnil); p->open_fd[WRITE_TO_SUBPROCESS] = new_outfd; @@ -7567,17 +7585,8 @@ child_signal_init (void) return; /* already done */ int fds[2]; - if (emacs_pipe (fds) < 0) + if (inrange_pipe (fds) < 0) report_file_error ("Creating pipe for child signal", Qnil); - if (FD_SETSIZE <= fds[0]) - { - /* Since we need to `pselect' on the read end, it has to fit - into an `fd_set'. */ - emacs_close (fds[0]); - emacs_close (fds[1]); - report_file_errno ("Creating pipe for child signal", Qnil, - EMFILE); - } /* We leave the file descriptors open until the Emacs process exits. */ @@ -8726,7 +8735,13 @@ init_process_emacs (int sockfd) #endif #ifdef HAVE_SETRLIMIT - /* Don't allocate more than FD_SETSIZE file descriptors for Emacs itself. */ + /* Don't allocate more than FD_SETSIZE file descriptors for Emacs itself. + This is for performance, so that we needn't open file descriptors + only to immediately close them and fail. The rest of this module + does not rely on emacs_open, accept4, socket, emacs_pipe, etc. + to always return values less than FD_SETSIZE, since not every + platform has setrlimit, and even for those that do, an Emacs + module or even some other process can raise Emacs's limit. */ if (getrlimit (RLIMIT_NOFILE, &nofile_limit) != 0) nofile_limit.rlim_cur = 0; else if (FD_SETSIZE < nofile_limit.rlim_cur) commit 71336e837a51986aa12ca7e27e6ae0549d509aec Author: Paul Eggert Date: Mon May 18 22:47:37 2026 -0700 Pacify GCC 16.1.1 -Wanalyzer-null-dereference * src/regex-emacs.c (forall_firstchar): Avoid undefined behavior in the 2nd eassert when !bufp && !pend. This pacifies GCC 16.1.1 20260501 (Red Hat 16.1.1-1) x86-64 when Emacs is configured with --enable-gcc-warnings. diff --git a/src/regex-emacs.c b/src/regex-emacs.c index 7c8ee144257..03c4ba3e4e9 100644 --- a/src/regex-emacs.c +++ b/src/regex-emacs.c @@ -3044,8 +3044,7 @@ static bool forall_firstchar (struct re_pattern_buffer *bufp, re_char *p, re_char *pend, bool f (re_char *p, void *arg), void *arg) { - eassert (!bufp || bufp->used); - eassert (pend || bufp->used); + eassert (bufp ? !!bufp->used : !!pend); return forall_firstchar_1 (p, pend, bufp ? bufp->buffer - 1 : p, bufp ? bufp->buffer + bufp->used + 1 : pend, commit 07fe0b297bc7b9c4e344eedd8244a73edda95c77 Author: Paul Eggert Date: Sun May 17 22:49:44 2026 -0700 Fix undefined behavior in maybe_resize_hash_table Problem discovered with GCC 16.1.1 -fsanitize=undefined. * src/fns.c (maybe_resize_hash_table): Avoid undefined behavior when h->key_and_value or h->hash are null pointers, in which case we call memcpy (destination, NULL, 0) which has undefined behavior in C89 through C23. diff --git a/src/fns.c b/src/fns.c index 1158f100ea0..a2312ffa1b9 100644 --- a/src/fns.c +++ b/src/fns.c @@ -4975,13 +4975,15 @@ maybe_resize_hash_table (struct Lisp_Hash_Table *h) Lisp_Object *key_and_value = hash_table_alloc_bytes (2 * new_size * sizeof *key_and_value); - memcpy (key_and_value, h->key_and_value, - 2 * old_size * sizeof *key_and_value); + if (old_size) + memcpy (key_and_value, h->key_and_value, + 2 * old_size * sizeof *key_and_value); for (ptrdiff_t i = 2 * old_size; i < 2 * new_size; i++) key_and_value[i] = HASH_UNUSED_ENTRY_KEY; hash_hash_t *hash = hash_table_alloc_bytes (new_size * sizeof *hash); - memcpy (hash, h->hash, old_size * sizeof *hash); + if (old_size) + memcpy (hash, h->hash, old_size * sizeof *hash); ptrdiff_t old_index_size = hash_table_index_size (h); ptrdiff_t index_bits = compute_hash_index_bits (new_size); commit 7587bb2654a6c22f8c9709cefd3ad8d45938b199 Author: Paul Eggert Date: Sun May 17 22:31:26 2026 -0700 Simplify module_extract_big_integer size calcs * src/emacs-module.c (module_bignum_count_max): Now of type ptrdiff_t, instead of likely being of type size_t. (module_extract_big_integer): Omit now-unnecessary prefix +, a now-unnecessary eassert against PTRDIFF_MAX, and and an unnecessary cast to ptrdiff_t. diff --git a/src/emacs-module.c b/src/emacs-module.c index 142ca675734..3c49490e5c4 100644 --- a/src/emacs-module.c +++ b/src/emacs-module.c @@ -1046,7 +1046,7 @@ import/export overhead on most platforms. /* Documented maximum count of magnitude elements. */ #define module_bignum_count_max \ - ((ptrdiff_t) min (SIZE_MAX, PTRDIFF_MAX) / sizeof (emacs_limb_t)) + ((ptrdiff_t) (min (SIZE_MAX, PTRDIFF_MAX) / sizeof (emacs_limb_t))) /* Verify that emacs_limb_t indeed has unique object representations. */ @@ -1100,7 +1100,7 @@ module_extract_big_integer (emacs_env *env, emacs_value arg, int *sign, suffice. */ EMACS_UINT u; enum { required = (sizeof u + size - 1) / size }; - static_assert (0 < required && +required <= module_bignum_count_max); + static_assert (0 < required && required <= module_bignum_count_max); if (magnitude == NULL) { *count = required; @@ -1132,9 +1132,8 @@ module_extract_big_integer (emacs_env *env, emacs_value arg, int *sign, return true; } size_t required_size = (mpz_sizeinbase (*x, 2) + numb - 1) / numb; - eassert (required_size <= PTRDIFF_MAX); - ptrdiff_t required = (ptrdiff_t) required_size; - eassert (required <= module_bignum_count_max); + eassert (required_size <= module_bignum_count_max); + ptrdiff_t required = required_size; if (magnitude == NULL) { *count = required; commit b9e20e39953ad27c720e265311fa71bd7c6c749e Author: Paul Eggert Date: Sun May 17 22:17:47 2026 -0700 Avoid malloc/free pairs in emit_static_object * src/comp.c (emit_static_object): Avoid an malloc/free of a 1 KiB buffer; just put it on the stack. Use strnlen+mempcpy instead of strncpy as there is no need to zero-fill buff. Use int for values that must fit in int since we are passing them to gcc_jit_context_new_rvalue_from_int. diff --git a/src/comp.c b/src/comp.c index 0ac980e6276..c70b37bfdac 100644 --- a/src/comp.c +++ b/src/comp.c @@ -2783,16 +2783,17 @@ emit_static_object (const char *name, Lisp_Object obj) . Adjust if possible to reduce the number of function calls. */ - size_t chunk_size = NILP (Fcomp_libgccjit_version ()) ? 200 : 1024; - char *buff = xmalloc (chunk_size); + char buff[1024]; + int chunk_size = NILP (Fcomp_libgccjit_version ()) ? 200 : sizeof buff; for (ptrdiff_t i = 0; i < len;) { - strncpy (buff, p, chunk_size); - buff[chunk_size - 1] = 0; - uintptr_t l = strlen (buff); + int l = strnlen (p, chunk_size - 1); if (l != 0) { + char *buff_end = mempcpy (buff, p, l); + *buff_end = '\0'; + p += l; i += l; @@ -2836,7 +2837,6 @@ emit_static_object (const char *name, Lisp_Object obj) NULL)); } } - xfree (buff); gcc_jit_block_add_assignment ( block, commit f5c3ddd9ad3f6f116c1c224e55b2aecad0461863 Author: Paul Eggert Date: Sun May 17 19:55:12 2026 -0700 Prefer singed type to size_t in Fdefine_charset_internal * src/charset.c (Fdefine_charset_internal): Prefer int to size_t for a variable that has only int values. diff --git a/src/charset.c b/src/charset.c index 9ff3191b7e3..b86304024fe 100644 --- a/src/charset.c +++ b/src/charset.c @@ -1139,7 +1139,7 @@ usage: (define-charset-internal ...) */) charset_table.start = new_table; charset_table.size = new_size; Lisp_Object new_attr_table = make_vector (new_size, Qnil); - for (size_t i = 0; i < old_size; i++) + for (int i = 0; i < old_size; i++) ASET (new_attr_table, i, AREF (charset_table.attributes_table, i)); charset_table.attributes_table = new_attr_table; commit 56ae704e5b2fad285dc00e28d7c5e78f05133a8b Author: Paul Eggert Date: Sun May 17 19:47:52 2026 -0700 Fix (ash -1 1) undefined behavior Problem discovered with GCC 16.1.1 -fsanitize=undefined. * src/data.c (Fash): Don’t left-shift a negative number; behavior is undefined (ISO C23 § 6.5.8 ¶ 4). diff --git a/src/data.c b/src/data.c index b269ec6d501..2f245ce8061 100644 --- a/src/data.c +++ b/src/data.c @@ -3599,10 +3599,10 @@ discarding bits. */) else if (FIXNUMP (value)) { EMACS_INT v = XFIXNUM (value); - EMACS_UINT uv = v < 0 ? ~v : v; - EMACS_INT lz = stdc_leading_zeros (uv); + EMACS_UINT uv = v, uvcomp = v < 0 ? ~uv : uv; + EMACS_INT lz = stdc_leading_zeros (uvcomp); if (EMACS_INT_WIDTH - FIXNUM_BITS < lz - c) - return make_fixnum (v << c); + return make_fixnum ((EMACS_INT) {uv << c}); } mpz_t const *zval = bignum_integer (&mpz[0], value); commit 8c71b0d6b8800066b21794eb08cd0281d3ee9c60 Author: Stefan Monnier Date: Mon May 18 19:03:51 2026 -0400 shr.el: Don't insert image at outdated destination (bug#80945) When fetching images asynchronously, keep track of the destination region and refrain from inserting the image if that region has been modified in the mean time. * lisp/net/shr.el (shr--image-fetched, shr--async-put-image): New functions. (shr-insert-image, shr-zoom-image, shr-image-displayer, shr-tag-img): Use them. * lisp/mail/rmailmm.el (rmail-mime-render-html-shr): Add FIXME. diff --git a/lisp/mail/rmailmm.el b/lisp/mail/rmailmm.el index 9226976c114..1f9d1310782 100644 --- a/lisp/mail/rmailmm.el +++ b/lisp/mail/rmailmm.el @@ -763,6 +763,7 @@ HEADER is a header component of a MIME-entity object (see ;; Image retrieval happens asynchronously, but meanwhile ;; `rmail-swap-buffers' may have been run, leaving ;; `shr-image-fetched' trying to insert the image in the wrong buffer. + ;; FIXME: With `shr--async-put-image' this should now work correctly. (shr-inhibit-images t) ;; Bind shr-width to nil to force shr-insert-document break ;; the lines at the window margin. The default is diff --git a/lisp/net/shr.el b/lisp/net/shr.el index 7e47d93d81c..a199150bd19 100644 --- a/lisp/net/shr.el +++ b/lisp/net/shr.el @@ -636,9 +636,8 @@ the URL of the image to the kill buffer instead." (if (not url) (message "No image under point") (message "Inserting %s..." url) - (url-retrieve url #'shr-image-fetched - (list (current-buffer) (1- (point)) (point-marker)) - t)))) + (shr--async-put-image url (1- (point)) (point-marker) + :silent t)))) (defvar shr-image-zoom-level-alist `((fit "Zoom to fit" shr-rescale-image) @@ -689,11 +688,9 @@ full-buffer size." (url-is-cached url)) (shr-replace-image (shr-get-image-data url) start (set-marker (make-marker) end) flags) - (url-retrieve url #'shr-image-fetched - `(,(current-buffer) ,start - ,(set-marker (make-marker) end) - ,flags) - t)))))) + (shr--async-put-image url start end + :flags flags + :silent t)))))) ;;; Utility functions. @@ -1154,7 +1151,7 @@ the mouse click event." (defun shr-image-fetched (status buffer start end &optional flags) (let ((image-buffer (current-buffer))) - (when (and (buffer-name buffer) + (when (and (buffer-live-p buffer) (not (plist-get status :error))) (url-store-in-cache image-buffer) (goto-char (point-min)) @@ -1165,6 +1162,30 @@ the mouse click event." (shr-replace-image data start end flags))))) (kill-buffer image-buffer))) +(defun shr--image-fetched (status ol flags) + (unwind-protect + (shr-image-fetched status (overlay-buffer ol) + (overlay-start ol) + (overlay-end ol) + flags) + (delete-overlay ol))) + +(cl-defun shr--async-put-image (url beg end + &key flags silent inhibit-cookies queue) + "Fetch image from URL and place it on BEG..END. +FLAGS has the same meaning as for `shr-put-image'. +SILENT and inhibit-cookies have the same meaning as for `ulkr-retrieve.'. +If QUEUE is non-nil use `url-queue-retrieve’ instead of `url-retrieve’." + (let ((ol (make-overlay beg end nil t))) + ;; We could also try to delete the overlay when the text between BEG..END + ;; is modified (via `modification-hooks'), but then we'd have to be careful + ;; not to do it too eagerly (e.g. it's normal for text-properties to be + ;; applied). + (overlay-put ol 'evaporate t) + (funcall (if queue #'url-queue-retrieve #'url-retrieve) + url #'shr--image-fetched + (list ol flags) silent inhibit-cookies))) + (defun shr-image-from-data (data) "Return an image from the data: URI content DATA." (when (string-match @@ -1383,9 +1404,8 @@ START, and END. Note that START and END should be markers." (funcall shr-put-image-function image (buffer-substring start end)) (delete-region (point) end)))) - (url-retrieve url #'shr-image-fetched - (list (current-buffer) start end) - t t))))) + (shr--async-put-image url start end + :silent t :inhibit-cookies t))))) (defun shr-heading (dom &rest types) (shr-ensure-paragraph) @@ -1972,12 +1992,12 @@ The preference is a float determined from `shr-prefer-media-type'." (or (string-trim alt) "")) ;; No SVG support. Just use a space as our placeholder. (insert " ")) - (url-queue-retrieve - url #'shr-image-fetched - (list (current-buffer) start (set-marker (make-marker) (point)) - (list :width width :height height)) - t - (not (shr--use-cookies-p url shr-base))))) + (shr--async-put-image url start (point) + :flags (list :width width :height height) + :queue t + :silent t + :inhibit-cookies + (not (shr--use-cookies-p url shr-base))))) (when (zerop shr-table-depth) ;; We are not in a table. (put-text-property start (point) 'keymap shr-image-map) (put-text-property start (point) 'shr-alt alt) commit 641754e8704bb73858657f960f8802f8ee4230fe Merge: d4cb550dba6 28a13b01c7d Author: Sean Whitton Date: Mon May 18 22:16:46 2026 +0100 Merge from origin/emacs-31 28a13b01c7d vc-refresh-state: Override default-directory for backend ... 389874c533b Eglot: unbreak for treesit-less builds 10e91e096d8 Get selected item in newsticker list view 6bd73af2413 ; * test/lisp/jsonrpc-tests.el: Adjust timeouts for CI EM... eb90c528f38 ; * lisp/progmodes/eglot.el (eglot-code-action-indication... 1d7d6ffedbc ; * etc/PROBLEMS: Fix entries about display of Emoji on T... 6c1829bf4c5 Eglot: fix thinko in recent markdown-related commit (bug#... 36036e71c0c Jsonrpc: migrate more tests to Python subprocess fixtures 0977d5915d1 Eglot: add left-fringe code action indicator (bug#80326) b7825c3a271 Fix auth-source-backends-parse d89054627c4 Fix updates of embedded formulas by 'calc-embedded-update... 1832a93547b ; * src/fns.c (Fequal): Doc fix. f68e7a0a411 ; Improve documentation of commands that move by compilat... commit 28a13b01c7d7ccfd50e02cb34f5d119b28173df9 Author: Sean Whitton Date: Mon May 18 22:15:29 2026 +0100 vc-refresh-state: Override default-directory for backend functions I ran into the issue described in the comment with the current code in project-find-file-in and project-find-dir, when using 'C-x p p' to switch between projects. * lisp/vc/vc-hooks.el (vc-refresh-state): When calling into the backend, override any let-bindings of default-directory. diff --git a/lisp/vc/vc-hooks.el b/lisp/vc/vc-hooks.el index d0f292d2c9d..64ad4d5daec 100644 --- a/lisp/vc/vc-hooks.el +++ b/lisp/vc/vc-hooks.el @@ -955,10 +955,16 @@ In the latter case, VC mode is deactivated for this buffer." (cond ((setq backend (with-demoted-errors "VC refresh error: %S" (vc-backend buffer-file-name))) - ;; Let the backend setup any buffer-local things he needs. - (vc-call-backend backend 'find-file-hook) - ;; Compute the state and put it in the mode line. - (vc-mode-line buffer-file-name backend) + ;; When `auto-revert-handler' calls us then `default-directory' + ;; may be let-bound to something else for the purpose of some + ;; command that's currently doing some minibuffer prompting. + ;; Backend find-file-hook and mode-line-string functions should + ;; not need to be written so as to handle that possibility. + (let ((default-directory (buffer-local-toplevel-value 'default-directory))) + ;; Let the backend setup any buffer-local things it needs. + (vc-call-backend backend 'find-file-hook) + ;; Compute the state and put it in the mode line. + (vc-mode-line buffer-file-name backend)) (unless vc-make-backup-files ;; Use this variable, not make-backup-files, ;; because this is for things that depend on the file name. commit 389874c533bbd2a5594ce490510ad25bca147899 Author: João Távora Date: Mon May 18 20:33:44 2026 +0100 Eglot: unbreak for treesit-less builds * lisp/progmodes/eglot.el (eglot--builtin-mdown-p): New helper. (eglot--accepted-formats) (eglot--format-markup): Use it. diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index f00a3a265c5..e55928556f9 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -726,11 +726,15 @@ This can be useful when using docker to run a language server.") (executable-find command))) (declare-function treesit-grammar-location "treesit.c") + +(defun eglot--builtin-mdown-p () + (and (fboundp 'markdown-ts-view-mode) + (fboundp 'treesit-grammar-location) + (treesit-grammar-location 'markdown))) + (defun eglot--accepted-formats () (if (and (not eglot-prefer-plaintext) - (or (fboundp 'gfm-view-mode) - (and (fboundp 'markdown-ts-view-mode) - (treesit-grammar-location 'markdown)))) + (or (fboundp 'gfm-view-mode) (eglot--builtin-mdown-p))) ["markdown" "plaintext"] ["plaintext"])) @@ -2237,9 +2241,7 @@ Doubles as an indicator of snippet support." (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)))) + &aux string lang render extract) "Format MARKUP according to LSP's spec. MARKUP is either an LSP MarkedString or MarkupContent object. If MODE, force MODE to be used for fontifying MARKUP." @@ -2261,7 +2263,7 @@ If MODE, force MODE to be used for fontifying MARKUP." (calc2 (forced-mode) (cond (forced-mode `(,forced-mode)) - (built-in `(,#'markdown-ts-view-mode)) + ((eglot--builtin-mdown-p) `(,#'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))) commit 10e91e096d8bb07ac12d960a5bc6b473e30bb812 Author: Joshua Murphy Date: Sat May 16 13:35:23 2026 -0400 Get selected item in newsticker list view * lisp/net/newst-treeview.el (newsticker--treeview-get-selected-item): If an item is already selected, use it. (Bug#80972) Copyright-paperwork-exempt: yes diff --git a/lisp/net/newst-treeview.el b/lisp/net/newst-treeview.el index 04d796b0b90..8b5f8a8528f 100644 --- a/lisp/net/newst-treeview.el +++ b/lisp/net/newst-treeview.el @@ -1387,8 +1387,10 @@ Will move to previous feed until an item is found." (defun newsticker--treeview-get-selected-item () "Return item that is currently selected in list buffer." (with-current-buffer (newsticker--treeview-list-buffer) - (beginning-of-line) - (get-text-property (point) :nt-item))) + (goto-char (point-min)) + (if-let* ((selected (text-property-search-forward :nt-selected t t))) + (get-text-property (prop-match-beginning selected) :nt-item) + (get-text-property (point-min) :nt-item)))) (defun newsticker-treeview-mark-item-old (&optional dont-proceed) "Mark current item as old unless it is obsolete. commit 6bd73af24136b70baa932fbe5c187edd97154b55 Author: João Távora Date: Mon May 18 17:09:19 2026 +0100 ; * test/lisp/jsonrpc-tests.el: Adjust timeouts for CI EMBA testing diff --git a/test/lisp/jsonrpc-tests.el b/test/lisp/jsonrpc-tests.el index ec85210c091..28f7740ab32 100644 --- a/test/lisp/jsonrpc-tests.el +++ b/test/lisp/jsonrpc-tests.el @@ -177,25 +177,26 @@ INITARGS are passed to `make-instance' for `jsonrpc--test-client'." ;; This returns immediately (jsonrpc-async-request conn - 'sit-for [0.1] + 'sit-for [0.01] :success-fn (lambda (_result) ;; this only gets runs after the "first deferred" is stashed. (setq n-deferred-1 (hash-table-count (jsonrpc--deferred-actions conn))))) (should-error - ;; This stashes the request and waits. It will error because - ;; no-one clears the "hold deferred" flag. + ;; This stashes the request and waits. It will error with a + ;; timeout after blocking for 1 sec because no-one clears the + ;; "hold deferred" flag. (jsonrpc-request conn 'ignore ["first deferred"] :deferred "first deferred" - :timeout 0.5) + :timeout 1.0) :type 'jsonrpc-error) ;; The error means the deferred actions stash is now empty (should (zerop (hash-table-count (jsonrpc--deferred-actions conn)))) ;; Again, this returns immediately. (jsonrpc-async-request conn - 'sit-for [0.1] + 'sit-for [0.01] :success-fn (lambda (_result) ;; This gets run while "third deferred" below is waiting for commit d4cb550dba6c01223167ca71b02698f452f3d141 Author: Jim Porter Date: Mon May 18 08:34:50 2026 -0700 ; Improve last change * test/src/process-tests.el (process-tests/broken-pipe): Use CONNECTION-TYPE. (process-tests/broken-pipe/pipe-all) (process-tests/broken-pipe/pipe-stdin): Skip via 'skip-when'. (process-tests/broken-pipe/pty) (process-tests/broken-pipe/pty-stdin): Remove these invalid tests; EPIPE from a PTY doesn't make sense. diff --git a/test/src/process-tests.el b/test/src/process-tests.el index 1b1a9dfb07f..3048cada03d 100644 --- a/test/src/process-tests.el +++ b/test/src/process-tests.el @@ -1073,7 +1073,7 @@ should also run to completion, printing out the line of text it read." (message "closed stream") (sit-for 1) (message "%s" line)))) - :connection-type 'pipe))) + :connection-type connection-type))) (process-send-string proc "hello\n") (while (not (string-prefix-p "closed stream\n" (buffer-string))) (accept-process-output)) @@ -1102,20 +1102,15 @@ should also run to completion, printing out the line of text it read." ;; 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-tests/broken-pipe/pipe-all () + (skip-when noninteractive) + (process-tests/broken-pipe 'pipe)) + +(ert-deftest process-tests/broken-pipe/pipe-stdin () + (skip-when (or noninteractive + ;; Emacs doesn't support PTYs on MS-Windows. + (not (memq system-type '(ms-dos windows-nt))))) + (process-tests/broken-pipe '(pipe . pty))) (ert-deftest process-num-processors () "Sanity checks for num-processors." commit eb90c528f38af557f0cfe927463c01c02f1b13e8 Author: João Távora Date: Mon May 18 16:27:35 2026 +0100 ; * lisp/progmodes/eglot.el (eglot-code-action-indications): Tweak. diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 02fc4615acf..f00a3a265c5 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -625,6 +625,7 @@ Note additionally: (const :tag "ElDoc textual hint" eldoc-hint) (const :tag "Right besides point" nearby) (const :tag "In mode line" mode-line) + (const :tag "In left fringe" left-fringe) (const :tag "In margin" margin)) :package-version '(Eglot . "1.19")) commit 1d7d6ffedbcefaf691777d81f40846ba62ffdee5 Author: Eli Zaretskii Date: Mon May 18 16:36:05 2026 +0300 ; * etc/PROBLEMS: Fix entries about display of Emoji on TTY (bug#81052). diff --git a/etc/PROBLEMS b/etc/PROBLEMS index 2ae82292e04..b619c6e7f37 100644 --- a/etc/PROBLEMS +++ b/etc/PROBLEMS @@ -2649,6 +2649,45 @@ all such characters will look the same on display, and the only way of knowing what is the real codepoint in the buffer is to go to the character and type "C-u C-x =". +*** Display problems with Emoji on text terminals + +Some text-mode terminals cause problems with Emoji sequences: when +displaying them, the Emacs text-mode frame could show gaps, misalignment +between the display and cursor motion, and other visual artifacts and +display problems. + +This can happen if the terminal and Emacs differ in their notions of how +many columns (a.k.a. "character cells") a given sequence of characters +takes on the screen when displayed. As one example, Emoji sequences +that begin with a non-Emoji character and end in U+FE0F VARIATION +SELECTOR 16 are composed on display into an Emoji glyph, but the width +of this glyph is up to the terminal and the font it uses to show the +Emoji. If the non-Emoji character that begins the sequence has the +width 1, Emacs will think that its composition with VS-16 also takes 1 +column on the screen, because VS-16 has width of zero. But some +terminals which support Emoji sequences will show a double-width Emoji +glyph in this case, without any way for Emacs to know that. This causes +cursor addressing to get out of sync and eventually messes up the +display. In particular, Kitty, Alacritty, Ghostty, and some other +terminal emulators are known to behave like that. + +Similar problems can happen with composition of characters other than +Emoji. + +The solution is to disable 'auto-composition-mode' on these +terminals, for example, like this: + + (setq auto-composition-mode "alacritty") + +This disables 'auto-composition-mode' on frames that display on +terminals of the named type. More generally, customizing the +'auto-composition-mode' variable to have as value a string that the +'tty-type' function returns on a terminal will disable compositions in +windows shown on terminals of that type. (You can also disable +'auto-composition-mode' globally, if all your frames are on terminals +that have this problem, by setting 'auto-composition-mode' to the nil +value.) + *** Messed-up display on the Kitty text terminal This terminal has its own peculiar ideas about display of unusual @@ -2674,33 +2713,6 @@ Another workaround is to set 'nobreak-char-ascii-display' to a non-nil value, which will cause any non-ASCII space and hyphen characters to be displayed as their ASCII counterparts, with a special face. -Kitty also differs from many other character terminals in how it -handles character compositions. As one example, Emoji sequences that -begin with a non-Emoji character and end in U+FE0F VARIATION SELECTOR -16 should be composed into an Emoji glyph; Kitty assumes that all such -Emoji glyphs have 2-column width, whereas Emacs and many other text -terminals display them as 1-column glyphs. Again, this causes cursor -addressing to get out of sync and eventually messes up the display. - -One possible workaround for problems caused by character composition -is to turn off 'auto-composition-mode' on Kitty terminals, e.g. by -customizing the 'auto-composition-mode' variable to have as value a -string that the 'tty-type' function returns on those terminals. - -*** Display artifacts on the Alacritty text terminal - -This terminal is known to cause problems with Emoji sequences: when -displaying them, the Emacs text-mode frame could show gaps and other -visual artifacts. - -The solution is to disable 'auto-composition-mode' on these -terminals, for example, like this: - - (setq auto-composition-mode "alacritty") - -This disables 'auto-composition-mode' on frames that display on -terminals of this type. - ** Screen readers get confused about character position The Emacs display code sometimes emits TAB characters purely for motion @@ -2713,6 +2725,12 @@ This can confuse screen reader software under certain terminal emulators in the terminal before starting Emacs may mitigate this. See also the discussion in Bug#78474 . +Starting from version 31.1, Emacs by default no longer outputs series of +TAB characters followed by BACKSPACE, which used to confuse some of the +screen readers. If you encounter some problems in this area, verify +that the variable 'tty-cursor-movement-use-TAB-BS' is set to its default +nil value. + * Runtime problems specific to individual Unix variants ** GNU/Linux commit 6c1829bf4c5acd3e676b0cb802a096d36fdd33bc Author: Brian Leung Date: Sun May 17 19:22:39 2026 +0100 Eglot: fix thinko in recent markdown-related commit (bug#81063) * lisp/progmodes/eglot.el (eglot--format-markup): Correct return value for gfm-view-mode. diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 07d64d8209c..02fc4615acf 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -2255,12 +2255,13 @@ If MODE, force MODE to be used for fontifying MARKUP." for to = (or (next-single-property-change from 'invisible) (point-max)) when inv - do (put-text-property from to 'invisible t))) + do (put-text-property from to 'invisible t) + finally return (buffer-string))) (calc2 (forced-mode) (cond (forced-mode `(,forced-mode)) (built-in `(,#'markdown-ts-view-mode)) - ((fboundp 'gfm-view-mode) `(,#'gfm-view-mode #'gfm-extract)) + ((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)))) commit 36036e71c0c255fc80f38672ba639cc66b0c90ed Author: João Távora Date: Sun May 17 15:02:57 2026 +0100 Jsonrpc: migrate more tests to Python subprocess fixtures All tests now use 'jsonrpc--with-python-fixture' with a Python3 subprocess instead of the in-Emacs TCP server. Changed the "harakiri" method to be a request instead of a notification for to reduce chance of "Sentinel hasn't run" warning. The two in-Emacs-RPC-specific error tests ('errors-with--32601' and 'signals-an--32603-JSONRPC-error') are dropped with the fixture itself, as the error paths they exercise are internal to the Emacs Lisp dispatcher and have no direct Python equivalent. They will have to be re-done later on in other form. * test/lisp/jsonrpc-resources/server-emacsrpc.py: New file. * test/lisp/jsonrpc-resources/server-anxious-nested.py: Use new harakiri. * test/lisp/jsonrpc-resources/server-emacsrpc.py: Use new harakiri. * test/lisp/jsonrpc-resources/server-harakiri.py: Use new harakiri. * test/lisp/jsonrpc-resources/server-remote-during-sync-1.py: Use new harakiri. * test/lisp/jsonrpc-resources/server-remote-during-sync-2.py: Use new harakiri. * test/lisp/jsonrpc-resources/server-remote-error.py: Use new harakiri. * test/lisp/jsonrpc-resources/common.py (harakiri): New definition. * test/lisp/jsonrpc-tests.el (jsonrpc--with-python-fixture): Rework, move up. (jsonrpc-connection-ready-p): Move up. (jsonrpc--call-with-emacsrpc-fixture) (jsonrpc--with-emacsrpc-fixture) (errors-with--32601) (signals-an--32603-JSONRPC-error): Remove. (returns-3, times-out, doesnt-time-out, stretching-it-but-works) (deferred-action-toolate, deferred-action-intime) (deferred-action-complex-tests): Migrate to Python fixture. (scontrol-remote-during-sync-1, scontrol-remote-during-sync-2) (scontrol-anxious-nested, scontrol-remote-error) (shutdown-clean-after-notification): Tweak. diff --git a/test/lisp/jsonrpc-resources/common.py b/test/lisp/jsonrpc-resources/common.py index 6bcc1db8bba..7b103aba0c3 100644 --- a/test/lisp/jsonrpc-resources/common.py +++ b/test/lisp/jsonrpc-resources/common.py @@ -30,3 +30,12 @@ def write_msg(msg): def log(text): """Write a log line to stderr.""" print(f'[test-server] {text}', file=sys.stderr, flush=True) + +def harakiri(msg): + """Maybe handle harakiri request/notif in msg.""" + if msg.get('method') == 'harakiri': + log('-> very clean harakiri') + if (mid := msg.get('id')) is not None: + write_msg({'jsonrpc': '2.0', 'id': mid, 'result': True}) + return True + return False diff --git a/test/lisp/jsonrpc-resources/server-anxious-nested.py b/test/lisp/jsonrpc-resources/server-anxious-nested.py index 8169b0936d1..970196e619c 100755 --- a/test/lisp/jsonrpc-resources/server-anxious-nested.py +++ b/test/lisp/jsonrpc-resources/server-anxious-nested.py @@ -15,7 +15,7 @@ import os import sys sys.path.insert(0, os.path.dirname(__file__)) -from common import read_msg, write_msg, log +from common import read_msg, write_msg, log, harakiri def main(): @@ -26,9 +26,7 @@ def main(): mid = lr1.get('id') method = lr1.get('method') log(f'<- {method or "(response)"} id={mid}') - if method == 'harakiri': - log('-> very clean harakiri') - break + if harakiri(lr1): break elif method == 'LR1': # Send RR1, then immediately respond to LR1 without awaiting # anything. The response-to-LR1 will be queued as anxious on the diff --git a/test/lisp/jsonrpc-resources/server-emacsrpc.py b/test/lisp/jsonrpc-resources/server-emacsrpc.py new file mode 100644 index 00000000000..22923f5b5f2 --- /dev/null +++ b/test/lisp/jsonrpc-resources/server-emacsrpc.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +"""General-purpose JSONRPC server for jsonrpc.el tests. + +Handles arithmetic (+ - * /), sit-for, vconcat, append, and ignore, +mirroring the methods the in-process Emacs RPC server supports. +""" +import functools +import operator +import os +import sys +import time + +sys.path.insert(0, os.path.dirname(__file__)) +from common import log, read_msg, write_msg, harakiri + +HANDLERS = { + '+': lambda p: sum(p), + '-': lambda p: functools.reduce(operator.sub, p), + '*': lambda p: functools.reduce(operator.mul, p), + '/': lambda p: functools.reduce(operator.truediv, p), + 'vconcat': lambda p: sum(p, []), + 'append': lambda p: sum(p, []), + 'sit-for': lambda p: time.sleep(p[0]), + 'ignore': lambda _: None, +} + + +def main(): + while True: + msg = read_msg() + if msg is None: + break + mid = msg.get('id') + method = msg.get('method') + log(f'<- {method or "(response)"} id={mid}') + if harakiri(msg): break + if method is None or mid is None: + continue + handler = HANDLERS.get(method) + if handler is None: + write_msg({'jsonrpc': '2.0', 'id': mid, + 'error': {'code': -32601, 'message': 'Method not found'}}) + else: + try: + result = handler(msg.get('params', [])) + write_msg({'jsonrpc': '2.0', 'id': mid, 'result': result}) + log(f'-> (response {method}) id={mid}') + except Exception as exc: + write_msg({'jsonrpc': '2.0', 'id': mid, + 'error': {'code': -32603, 'message': str(exc)}}) + + +if __name__ == '__main__': + main() diff --git a/test/lisp/jsonrpc-resources/server-harakiri.py b/test/lisp/jsonrpc-resources/server-harakiri.py index c20a3fbdaee..ee7eae54073 100755 --- a/test/lisp/jsonrpc-resources/server-harakiri.py +++ b/test/lisp/jsonrpc-resources/server-harakiri.py @@ -5,7 +5,7 @@ """ import os, sys sys.path.insert(0, os.path.dirname(__file__)) -from common import read_msg, log +from common import read_msg, log, harakiri def main(): @@ -15,9 +15,7 @@ def main(): break method = msg.get('method') log(f'<- {method or "(response)"} id={msg.get("id")}') - if method == 'harakiri': - log('-> very clean harakiri') - break + if harakiri(msg): break if __name__ == '__main__': diff --git a/test/lisp/jsonrpc-resources/server-remote-during-sync-1.py b/test/lisp/jsonrpc-resources/server-remote-during-sync-1.py index b8cb549bbd5..d9d116e256e 100755 --- a/test/lisp/jsonrpc-resources/server-remote-during-sync-1.py +++ b/test/lisp/jsonrpc-resources/server-remote-during-sync-1.py @@ -11,7 +11,7 @@ import os import sys sys.path.insert(0, os.path.dirname(__file__)) -from common import read_msg, write_msg, log +from common import read_msg, write_msg, log, harakiri def main(): @@ -22,9 +22,7 @@ def main(): mid = msg.get('id') method = msg.get('method') log(f'<- {method or "(response)"} id={mid}') - if method == 'harakiri': - log('-> very clean harakiri') - break + if harakiri(msg): break elif method == 'LR1': # Send RR1 request write_msg({'jsonrpc': '2.0', 'id': 1000, diff --git a/test/lisp/jsonrpc-resources/server-remote-during-sync-2.py b/test/lisp/jsonrpc-resources/server-remote-during-sync-2.py index e6250a0553b..77e9a3eb005 100755 --- a/test/lisp/jsonrpc-resources/server-remote-during-sync-2.py +++ b/test/lisp/jsonrpc-resources/server-remote-during-sync-2.py @@ -11,7 +11,7 @@ import os import sys sys.path.insert(0, os.path.dirname(__file__)) -from common import read_msg, write_msg, log +from common import read_msg, write_msg, log, harakiri def main(): @@ -22,9 +22,7 @@ def main(): mid = msg.get('id') method = msg.get('method') log(f'<- {method or "(response)"} id={mid}') - if method == 'harakiri': - log('-> very clean harakiri') - break + if harakiri(msg): break elif method == 'LR1': # Send RR1 request write_msg({'jsonrpc': '2.0', 'id': 1000, diff --git a/test/lisp/jsonrpc-resources/server-remote-error.py b/test/lisp/jsonrpc-resources/server-remote-error.py index 00c907e189b..ea1dfdc7e96 100755 --- a/test/lisp/jsonrpc-resources/server-remote-error.py +++ b/test/lisp/jsonrpc-resources/server-remote-error.py @@ -13,7 +13,7 @@ """ import os, sys sys.path.insert(0, os.path.dirname(__file__)) -from common import read_msg, write_msg, log +from common import read_msg, write_msg, log, harakiri def main(): @@ -24,9 +24,7 @@ def main(): mid = msg.get('id') method = msg.get('method') log(f'<- {method or "(response)"} id={mid}') - if method == 'harakiri': - log('-> very clean harakiri') - break + if harakiri(msg): break elif method == 'LR1': # Send badMethod BEFORE responding to LR1; the client # rdispatcher will signal a jsonrpc-error for it. diff --git a/test/lisp/jsonrpc-tests.el b/test/lisp/jsonrpc-tests.el index cdb4e04fc39..ec85210c091 100644 --- a/test/lisp/jsonrpc-tests.el +++ b/test/lisp/jsonrpc-tests.el @@ -41,102 +41,74 @@ (defclass jsonrpc--test-client (jsonrpc--test-endpoint) ((hold-deferred :initform t :accessor jsonrpc--hold-deferred))) -(defun jsonrpc--call-with-emacsrpc-fixture (fn) - "Do work for `jsonrpc--with-emacsrpc-fixture'. Call FN." - (let* (listen-server endpoint) - (unwind-protect - (progn - (setq listen-server - (make-network-process - :name "Emacs RPC server" :server t :host "localhost" - :service (if (version<= emacs-version "26.1") - 44444 - ;; 26.1 can automatically find ports if - ;; one passes 0 here. - 0) - :log (lambda (listen-server client _message) - (push - (make-instance - 'jsonrpc--test-endpoint - :name (process-name client) - :process client - :request-dispatcher - (lambda (_endpoint method params) - (unless (memq method '(+ - * / vconcat append - sit-for ignore)) - (signal 'jsonrpc-error - '((jsonrpc-error-message - . "Sorry, this isn't allowed") - (jsonrpc-error-code . -32601)))) - (apply method (append params nil))) - :on-shutdown - (lambda (conn) - (setf (jsonrpc--shutdown-complete-p conn) t))) - (process-get listen-server 'handlers))))) - (setq endpoint - (make-instance - 'jsonrpc--test-client - :process - (open-network-stream "JSONRPC test tcp endpoint" - nil "localhost" - (process-contact listen-server - :service)) - :on-shutdown - (lambda (conn) - (setf (jsonrpc--shutdown-complete-p conn) t)))) - (funcall fn endpoint)) - (unwind-protect - (when endpoint - (kill-buffer (jsonrpc--events-buffer endpoint)) - (jsonrpc-shutdown endpoint)) - (when listen-server - (cl-loop do (delete-process listen-server) - while (progn (accept-process-output nil 0.1) - (process-live-p listen-server)) - do (jsonrpc--message - "test listen-server is still running, waiting")) - (cl-loop for handler in (process-get listen-server 'handlers) - do (ignore-errors (jsonrpc-shutdown handler))) - (mapc #'kill-buffer - (mapcar #'jsonrpc--events-buffer - (process-get listen-server 'handlers)))))))) -(cl-defmacro jsonrpc--with-emacsrpc-fixture ((endpoint-sym) &body body) +;;; Tests using Python subprocesses +;;; + +(defconst jsonrpc--test-dir + (file-name-directory (or load-file-name buffer-file-name)) + "Directory of this test file, captured at load time.") + +(cl-defmacro jsonrpc--with-python-fixture ((script conn &rest initargs) &body body) + "Start SCRIPT under python3 as a pipe subprocess, bind connection to CONN. +SCRIPT is a path relative to this file's directory. +INITARGS are passed to `make-instance' for `jsonrpc--test-client'." (declare (indent 1)) - `(jsonrpc--call-with-emacsrpc-fixture (lambda (,endpoint-sym) ,@body))) + `(let ((,conn nil)) + (skip-unless (executable-find "python3")) + (unwind-protect + (progn + (setq ,conn + (make-instance + 'jsonrpc--test-client + :name "jsonrpc-python-test" + :process (make-process + :name "jsonrpc-python-test" + :command (list "python3" + (expand-file-name + ,script + jsonrpc--test-dir)) + :connection-type 'pipe + :noquery t) + ,@initargs)) + (with-timeout (5 + (when ,conn + (let ((buf (jsonrpc--events-buffer ,conn))) + (when (buffer-live-p buf) + (if noninteractive + (progn + (message "contents of `%s':" (buffer-name buf)) + (princ (with-current-buffer buf (buffer-string)) + #'external-debugging-output)) + (message "Preserved for inspection: %s" + (buffer-name buf)))))) + (ert-fail "Test timed out after 5s")) + ,@body)) + (when ,conn + (ignore-errors + (jsonrpc-request ,conn 'harakiri nil :timeout 1) + (accept-process-output nil 0.1) + (kill-buffer (jsonrpc--events-buffer ,conn)) + (jsonrpc-shutdown ,conn)))))) + +(cl-defmethod jsonrpc-connection-ready-p + ((conn jsonrpc--test-client) what) + (and (cl-call-next-method) + (or (not (string-match "deferred" what)) + (not (jsonrpc--hold-deferred conn))))) (ert-deftest returns-3 () "A basic test for adding two numbers in our test RPC." (skip-when (eq system-type 'windows-nt)) - (jsonrpc--with-emacsrpc-fixture (conn) + (jsonrpc--with-python-fixture + ("jsonrpc-resources/server-emacsrpc.py" conn) (should (= 3 (jsonrpc-request conn '+ [1 2]))))) -(ert-deftest errors-with--32601 () - "Errors with -32601" - (skip-when (eq system-type 'windows-nt)) - (jsonrpc--with-emacsrpc-fixture (conn) - (condition-case err - (progn - (jsonrpc-request conn 'delete-directory "~/tmp") - (ert-fail "A `jsonrpc-error' should have been signaled!")) - (jsonrpc-error - (should (= -32601 (cdr (assoc 'jsonrpc-error-code (cdr err))))))))) - -(ert-deftest signals-an--32603-JSONRPC-error () - "Signals an -32603 JSONRPC error." - (skip-when (eq system-type 'windows-nt)) - (jsonrpc--with-emacsrpc-fixture (conn) - (condition-case err - (let ((jsonrpc-inhibit-debug-on-error t)) - (jsonrpc-request conn '+ ["a" 2]) - (ert-fail "A `jsonrpc-error' should have been signaled!")) - (jsonrpc-error - (should (= -32603 (cdr (assoc 'jsonrpc-error-code (cdr err))))))))) - (ert-deftest times-out () "Request for 3-sec sit-for with 1-sec timeout times out." (skip-when (eq system-type 'windows-nt)) - (jsonrpc--with-emacsrpc-fixture (conn) + (jsonrpc--with-python-fixture + ("jsonrpc-resources/server-emacsrpc.py" conn) (should-error (jsonrpc-request conn 'sit-for [3] :timeout 1)))) @@ -144,30 +116,27 @@ :tags '(:expensive-test) "Request for 1-sec sit-for with 2-sec timeout succeeds." (skip-when (eq system-type 'windows-nt)) - (jsonrpc--with-emacsrpc-fixture (conn) + (jsonrpc--with-python-fixture + ("jsonrpc-resources/server-emacsrpc.py" conn) (jsonrpc-request conn 'sit-for [1] :timeout 2))) (ert-deftest stretching-it-but-works () "Vector of numbers or vector of vector of numbers are serialized." (skip-when (eq system-type 'windows-nt)) - (jsonrpc--with-emacsrpc-fixture (conn) + (jsonrpc--with-python-fixture + ("jsonrpc-resources/server-emacsrpc.py" conn) ;; (vconcat [1 2 3] [3 4 5]) => [1 2 3 3 4 5] which can be ;; serialized. (should (equal [1 2 3 3 4 5] (jsonrpc-request conn 'vconcat [[1 2 3] [3 4 5]]))))) -(cl-defmethod jsonrpc-connection-ready-p - ((conn jsonrpc--test-client) what) - (and (cl-call-next-method) - (or (not (string-match "deferred" what)) - (not (jsonrpc--hold-deferred conn))))) - (ert-deftest deferred-action-toolate () :tags '(:expensive-test) "Deferred request fails because no one clears the flag." (skip-when (eq system-type 'windows-nt)) - (jsonrpc--with-emacsrpc-fixture (conn) + (jsonrpc--with-python-fixture + ("jsonrpc-resources/server-emacsrpc.py" conn) (should-error (jsonrpc-request conn '+ [1 2] :deferred "deferred-testing" :timeout 0.5) @@ -182,7 +151,8 @@ (skip-when (eq system-type 'windows-nt)) ;; Send an async request, which returns immediately. However the ;; success fun which sets the flag only runs after some time. - (jsonrpc--with-emacsrpc-fixture (conn) + (jsonrpc--with-python-fixture + ("jsonrpc-resources/server-emacsrpc.py" conn) (jsonrpc-async-request conn 'sit-for [0.5] :success-fn @@ -199,7 +169,8 @@ :tags '(:expensive-test) "Test a more complex situation with deferred requests." (skip-when (eq system-type 'windows-nt)) - (jsonrpc--with-emacsrpc-fixture (conn) + (jsonrpc--with-python-fixture + ("jsonrpc-resources/server-emacsrpc.py" conn) (let (n-deferred-1 n-deferred-2 second-deferred-went-through-p) @@ -252,60 +223,11 @@ (should (eq 2 n-deferred-2)) (should (eq 0 (hash-table-count (jsonrpc--deferred-actions conn))))))) - -;;; Tests using Python subprocesses (scontrol / anxious mechanism) -;;; - -(defconst jsonrpc--test-dir - (file-name-directory (or load-file-name buffer-file-name)) - "Directory of this test file, captured at load time.") - -(cl-defmacro jsonrpc--with-python-fixture ((script conn &rest initargs) &body body) - "Start SCRIPT under python3 as a pipe subprocess, bind connection to CONN. -SCRIPT is a path relative to this file's directory. -INITARGS are passed to `make-instance' for `jsonrpc-process-connection'." - (declare (indent 1)) - `(let ((,conn nil)) - (unwind-protect - (progn - (setq ,conn - (make-instance - 'jsonrpc-process-connection - :name "jsonrpc-python-test" - :process (make-process - :name "jsonrpc-python-test" - :command (list "python3" - (expand-file-name - ,script - jsonrpc--test-dir)) - :connection-type 'pipe - :noquery t) - ,@initargs)) - (with-timeout (5 - (when ,conn - (let ((buf (jsonrpc--events-buffer ,conn))) - (when (buffer-live-p buf) - (if noninteractive - (progn - (message "contents of `%s':" (buffer-name buf)) - (princ (with-current-buffer buf (buffer-string)) - #'external-debugging-output)) - (message "Preserved for inspection: %s" - (buffer-name buf)))))) - (ert-fail "Test timed out after 5s")) - ,@body)) - (when ,conn - (ignore-errors - (jsonrpc-notify ,conn 'harakiri nil) - (kill-buffer (jsonrpc--events-buffer ,conn)) - (jsonrpc-shutdown ,conn)))))) - (ert-deftest scontrol-remote-during-sync-1 () "Anxious local continuations. Endpoint sends a remote request RR1 on LR1, then replies to LR1 immediately before waiting for RR1 to resolve. This is what JETLS does (bug#80623)." - (skip-unless (executable-find "python3")) (skip-when (eq system-type 'windows-nt)) (jsonrpc--with-python-fixture ("jsonrpc-resources/server-remote-during-sync-1.py" conn @@ -322,7 +244,6 @@ Exactly the same test as 2, but different endpoint, which now still sends RR1 on LR1 but now waits for RR1 to resolve before replying to LR1. This is what GoPls does (bug#80623)." - (skip-unless (executable-find "python3")) (skip-when (eq system-type 'windows-nt)) (jsonrpc--with-python-fixture ("jsonrpc-resources/server-remote-during-sync-2.py" conn @@ -337,7 +258,6 @@ This is what GoPls does (bug#80623)." "Nested anxious continuations Two local sync requests LR1 and LR2 with a remote RR1 in between. Vaguely similar to Julia's JETLS (bug#80623), but more complex." - (skip-unless (executable-find "python3")) (skip-when (eq system-type 'windows-nt)) (let (lr2-result completed) (jsonrpc--with-python-fixture @@ -349,7 +269,8 @@ Vaguely similar to Julia's JETLS (bug#80623), but more complex." (setq lr2-result (jsonrpc-request conn 'LR2 [] :timeout 5)) (push "lr2" completed) - (push "rr1" completed)) + (push "rr1" completed) + "rr1-ok") (_ (error "unexpected method: %s" method))))) (should (equal "lr1-ok" (jsonrpc-request conn 'LR1 [] :timeout 5))) (push "lr1" completed) @@ -358,7 +279,6 @@ Vaguely similar to Julia's JETLS (bug#80623), but more complex." (ert-deftest scontrol-remote-error () "Anxious continuation even when rdispatcher signals errors." - (skip-unless (executable-find "python3")) (skip-when (eq system-type 'windows-nt)) (jsonrpc--with-python-fixture ("jsonrpc-resources/server-remote-error.py" conn @@ -372,10 +292,9 @@ Vaguely similar to Julia's JETLS (bug#80623), but more complex." (_ (error "unexpected method: %s" method))))) (should (equal "ok" (jsonrpc-request conn 'LR1 [] :timeout 5))))) -(ert-deftest shutdown-clean-after-notification () - "Server exits cleanly after harakiri notification. +(ert-deftest shutdown-clean-after-request () + "Server exits cleanly after harakiri request. `jsonrpc-shutdown' should not emit a \"Sentinel hasn't run\" warning." - (skip-unless (executable-find "python3")) (skip-when (eq system-type 'windows-nt)) (let (warned) (cl-letf (((symbol-function 'jsonrpc--warn) @@ -383,10 +302,9 @@ Vaguely similar to Julia's JETLS (bug#80623), but more complex." (setq warned (apply #'format fmt args))))) (jsonrpc--with-python-fixture ("jsonrpc-resources/server-harakiri.py" conn) - (jsonrpc-notify conn 'harakiri nil) - ;; Give the server time to exit before shutdown checks the sentinel. - (accept-process-output nil 0.3) - (jsonrpc-shutdown conn))) + (jsonrpc-request conn 'harakiri nil :timeout 3) + (jsonrpc-shutdown conn) + (setq conn nil))) (should-not warned))) (provide 'jsonrpc-tests) commit 0977d5915d1918b0c9e3e37971b4e1b6484425ea Author: João Távora Date: Sun May 17 13:01:48 2026 +0100 Eglot: add left-fringe code action indicator (bug#80326) The fringe indicator uses a custom lightning-bolt bitmap, an alternative to the margin indicator on GUI frames. It is non-interactive, however. * lisp/progmodes/eglot.el (eglot--fringe-action): New fringe bitmap. (eglot-code-action-indications): Add 'left-fringe' to default value and to docstring. Update incompatibility note. (eglot-code-action-suggestion): Handle 'left-fringe' indication. diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 3d2c267650e..07d64d8209c 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -595,15 +595,16 @@ servers." :type 'boolean) (defface eglot-code-action-indicator-face - '((t (:inherit font-lock-escape-face :weight bold))) + '((t (:inherit warning :weight bold))) "Face used for code action suggestions.") (defcustom eglot-code-action-indications - '(eldoc-hint margin) + '(eldoc-hint left-fringe margin) "How Eglot indicates there's are code actions available at point. Value is a list of symbols, more than one can be specified: - `eldoc-hint': ElDoc is used to hint about at-point actions; +- `left-fringe': A special indicator appears on the left fringe; - `margin': A special indicator appears in the margin; - `nearby': A special indicator appears near point; - `mode-line': A special indicator appears in the mode-line. @@ -612,10 +613,13 @@ If the list is empty, Eglot will not hint about code actions at point. Note additionally: -- `margin' and `nearby' are incompatible. If both are specified, - the latter takes priority; -- `mode-line' only works if `eglot-mode-line-action-suggestion' exists in - `eglot-mode-line-format' (which see)." +- Some values are incompatible; if one or more of `nearby', + `left-fringe' and `margin' are specified, earlier values take + precedence. +- The indicators for many of these are customizable via + `eglot-code-action-indicator' (which see), except for `left-fringe'. +- `mode-line' only works if `eglot-mode-line-action-suggestion' exists + in `eglot-mode-line-format' (which see)." :type '(set :tag "Tick the ones you're interested in" (const :tag "ElDoc textual hint" eldoc-hint) @@ -4720,6 +4724,19 @@ at point. With prefix argument, prompt for ACTION-KIND." (eglot--code-action eglot-code-action-rewrite "refactor.rewrite") (eglot--code-action eglot-code-action-quickfix "quickfix") +(define-fringe-bitmap 'eglot--fringe-action + [#b00000111 + #b00001110 + #b00011100 + #b00111000 + #b01111111 + #b00001110 + #b01011100 + #b01111000 + #b01110000 + #b01111000] + nil nil 'center) + (defun eglot-code-action-suggestion (cb &rest _ignored) "A member of `eldoc-documentation-functions', for suggesting actions." (when (and (eglot-server-capable :codeActionProvider) @@ -4759,13 +4776,19 @@ at point. With prefix argument, prompt for ACTION-KIND." (overlay-put ov 'before-string - (cond ((memq 'nearby eglot-code-action-indications) - tooltip) - ((memq 'margin eglot-code-action-indications) - (propertize "⚡" - 'display - `((margin left-margin) - ,tooltip))))) + (cond + ((memq 'nearby eglot-code-action-indications) + tooltip) + ((and + (memq 'left-fringe eglot-code-action-indications) + (< 0 (nth 0 (window-fringes)))) + (propertize + "⚡" 'display `(left-fringe + eglot--fringe-action + eglot-code-action-indicator-face))) + ((memq 'margin eglot-code-action-indications) + (propertize + "⚡" 'display `((margin left-margin) ,tooltip))))) (setq eglot--suggestion-overlay ov)))) (when use-text-p (funcall cb blurb)))) :hint :textDocument/codeAction) commit b7825c3a271e72f46a4b9712ec78f8a49cb9503d Author: Michael Albinus Date: Sun May 17 18:21:49 2026 +0200 Fix auth-source-backends-parse * lisp/auth-source.el (auth-source-backend-parse): Drop backends of type `ignore'. (Bug#81024) (auth-source-backends): Drop duplicate backends. diff --git a/lisp/auth-source.el b/lisp/auth-source.el index bc660b2f4ab..f541dd3d141 100644 --- a/lisp/auth-source.el +++ b/lisp/auth-source.el @@ -361,9 +361,19 @@ soon as a function returns non-nil.") (defun auth-source-backend-parse (entry) "Create an `auth-source-backend' from an ENTRY in `auth-sources'." - (let ((backend - (run-hook-with-args-until-success 'auth-source-backend-parser-functions - entry))) + (let* ((auth-source-backend-parser-functions + ;; The functions shall drop backends of type `ignore', in + ;; order to let the hook continue. + (mapcar + (lambda (fun) + `(lambda (entry) + (and-let* ((result (funcall ',fun entry)) + ((not (eq (slot-value result 'type) 'ignore))) + result)))) + auth-source-backend-parser-functions)) + (backend + (run-hook-with-args-until-success + 'auth-source-backend-parser-functions entry))) (unless backend ;; none of the parsers worked @@ -378,12 +388,12 @@ soon as a function returns non-nil.") "List of usable backends from `auth-sources'. Filter out backends with type `ignore'. A fallback backend is added to ensure, that at least `read-passwd' is called." - `(or (seq-keep + `(or (seq-uniq (seq-keep (lambda (entry) (and-let* ((backend (auth-source-backend-parse entry)) ((not (eq (slot-value backend 'type) 'ignore))) backend))) - auth-sources) + auth-sources)) ;; Fallback. (list (auth-source-backend :source "" commit d89054627c4ead0b35c09e895884d4cfca926e24 Author: Eli Zaretskii Date: Sun May 17 10:57:53 2026 +0300 Fix updates of embedded formulas by 'calc-embedded-update-formula' * lisp/calc/calc-embed.el (calc-embedded-update): Use 'buffer-substring' to better track the string representation of the formula when it is being edited. Suggested by gnu@publik.slmail.me. Also, update commentary. (Bug#80901) diff --git a/lisp/calc/calc-embed.el b/lisp/calc/calc-embed.el index 7b3c5daaede..631efe9069b 100644 --- a/lisp/calc/calc-embed.el +++ b/lisp/calc/calc-embed.el @@ -103,7 +103,8 @@ ;; 3 Bottom of current formula (marker). ;; 4 Top of current formula's delimiters (marker). ;; 5 Bottom of current formula's delimiters (marker). -;; 6 String representation of current formula. +;; 6 String representation of current formula (actually, the +;; buffer-substring between positions given by 2 and 3 above. ;; 7 Non-nil if formula is embedded within a single line. ;; 8 Internal representation of current formula. ;; 9 Variable assigned by this formula, or nil. @@ -1140,7 +1141,8 @@ The command \\[yank] can retrieve it from there." (insert str) (set-marker (aref info 3) (+ (point) adjbot)) (set-marker (aref info 5) (+ (point) delta)) - (aset info 6 str)))))) + (aset info 6 (buffer-substring (aref info 2) + (aref info 3)))))))) (if (eq (car-safe val) 'calcFunc-evalto) (progn (setq evalled (nth 2 val) commit 1832a93547bf71f94bdefba01e69e25836443348 Author: Eli Zaretskii Date: Sun May 17 10:37:01 2026 +0300 ; * src/fns.c (Fequal): Doc fix. diff --git a/src/fns.c b/src/fns.c index 1041938531c..1158f100ea0 100644 --- a/src/fns.c +++ b/src/fns.c @@ -2804,7 +2804,8 @@ DEFUN ("equal", Fequal, Sequal, 2, 2, 0, doc: /* Return t if two Lisp objects have similar structure and contents. They must have the same data type. Conses are compared by comparing the cars and the cdrs. -Vectors and strings are compared element by element. +Vectors and strings are compared element by element (so text properties +of strings are ignored). Numbers are compared via `eql', so integers do not equal floats. \(Use `=' if you want integers and floats to be able to be equal.) Symbols must match exactly. */) commit f68e7a0a41188a7932544699ca23be7199ac3191 Author: Eli Zaretskii Date: Sun May 17 09:05:13 2026 +0300 ; Improve documentation of commands that move by compilation errors * lisp/simple.el (next-error): * lisp/progmodes/compile.el (compilation-next-error) (compilation-previous-error): Doc fixes. diff --git a/lisp/progmodes/compile.el b/lisp/progmodes/compile.el index 5222579592e..aaad8622c95 100644 --- a/lisp/progmodes/compile.el +++ b/lisp/progmodes/compile.el @@ -2821,13 +2821,23 @@ and runs `compilation-filter-hook'." (defun compilation-next-error (n &optional different-file pt) "Move point to the next error in the compilation buffer. -This function does NOT find the source line like \\[next-error]. +This function does NOT find the source line like \\[next-error], +but you can use \\[compilation-display-error] to find and +display the corresponding source code. Prefix arg N says how many error messages to move forwards (or backwards, if negative). +Where the current error ends and the next one begins is determined +by the rules from `compilation-error-regexp-alist' that matched +the compilation messages; this function moves point to where +the \\+`compilation-message' text property changes its value. +In general, all the messages that have the same line and column +numbers are considered parts of a single compilation message. + Optional arg DIFFERENT-FILE, if non-nil, means find next error for a file that is different from the current one. Optional arg PT, if non-nil, specifies the value of point to start -looking for the next message." +looking for the next message. +In interacvtive invocations, DIFFERENT-FILE and PT are always nil." (interactive "p") (or (compilation-buffer-p (current-buffer)) (error "Not in a compilation buffer")) @@ -2871,7 +2881,8 @@ looking for the next message." "Move point to the previous error in the compilation buffer. Prefix arg N says how many error messages to move backwards (or forwards, if negative). -Does NOT find the source line like \\[previous-error]." +Does NOT find the source line like \\[previous-error]. +This is like `compilation-next-error', but moves in the other direction." (interactive "p") (compilation-next-error (- n))) diff --git a/lisp/simple.el b/lisp/simple.el index 9346af5e8af..fd9ba28c762 100644 --- a/lisp/simple.el +++ b/lisp/simple.el @@ -362,7 +362,10 @@ until you use it in some other buffer that uses Compilation mode or Compilation Minor mode. To control which errors are matched, customize the variable -`compilation-error-regexp-alist'." +`compilation-error-regexp-alist'. The rules there determine the +boundaries between error messages. In general, messages that share +the same line and column numbers are considered parts of a single +error message." (interactive "P") (if (consp arg) (setq reset t arg nil)) (let ((buffer (next-error-find-buffer)))