commit a6bfc3cb87e91d37e0a7b67e9c68224fb432c989 (HEAD, refs/remotes/origin/master) Author: Po Lu Date: Mon Apr 11 12:25:51 2022 +0800 Fix IM event source attribution on GTK * src/xterm.c (handle_one_xevent): Set pending times on GTK as well. * src/gtkutil.c (xg_widget_key_press_event_cb): Respect pending keystroke time on XI2. diff --git a/src/gtkutil.c b/src/gtkutil.c index ec2864e34a..4fc0edf8ac 100644 --- a/src/gtkutil.c +++ b/src/gtkutil.c @@ -6347,6 +6347,10 @@ xg_widget_key_press_event_cb (GtkWidget *widget, GdkEvent *event, guint keysym = event->key.keyval; unsigned int xstate; gunichar uc; +#ifdef HAVE_XINPUT2 + Time pending_keystroke_time; + struct xi_device_t *source; +#endif FOR_EACH_FRAME (tail, tem) { @@ -6361,6 +6365,14 @@ xg_widget_key_press_event_cb (GtkWidget *widget, GdkEvent *event, if (!f) return true; +#ifdef HAVE_XINPUT2 + pending_keystroke_time + = FRAME_DISPLAY_INFO (f)->pending_keystroke_time; + + if (event->key.time >= pending_keystroke_time) + FRAME_DISPLAY_INFO (f)->pending_keystroke_time = 0; +#endif + if (!x_gtk_use_native_input && !FRAME_DISPLAY_INFO (f)->prefer_native_input) return true; @@ -6375,6 +6387,17 @@ xg_widget_key_press_event_cb (GtkWidget *widget, GdkEvent *event, |= x_x_to_emacs_modifiers (FRAME_DISPLAY_INFO (f), xstate); inev.ie.timestamp = event->key.time; +#ifdef HAVE_XINPUT2 + if (event->key.time == pending_keystroke_time) + { + source = xi_device_from_id (FRAME_DISPLAY_INFO (f), + FRAME_DISPLAY_INFO (f)->pending_keystroke_source); + + if (source) + inev.ie.device = source->name; + } +#endif + if (event->key.is_modifier) goto done; diff --git a/src/xterm.c b/src/xterm.c index 94cfe63ba0..801a964105 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -17379,6 +17379,12 @@ handle_one_xevent (struct x_display_info *dpyinfo, || (x_gtk_use_native_input && x_filter_event (dpyinfo, event))) { + /* Try to attribute core key events from the input + method to the input extension event that caused + them. */ + dpyinfo->pending_keystroke_time = xev->time; + dpyinfo->pending_keystroke_source = xev->sourceid; + *finish = X_EVENT_DROP; goto XI_OTHER; } @@ -17400,6 +17406,12 @@ handle_one_xevent (struct x_display_info *dpyinfo, || dpyinfo->prefer_native_input) && xg_filter_key (any, event)) { + /* Try to attribute core key events from the input + method to the input extension event that caused + them. */ + dpyinfo->pending_keystroke_time = xev->time; + dpyinfo->pending_keystroke_source = xev->sourceid; + *finish = X_EVENT_DROP; goto XI_OTHER; } commit bdd7cd0d2ee006e95b10ef12151a5ff1bb4f8abd Author: Po Lu Date: Mon Apr 11 09:16:22 2022 +0800 * src/xterm.c (handle_one_event): Make event attribution more accurate. diff --git a/src/xterm.c b/src/xterm.c index 88122416e2..94cfe63ba0 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -14818,7 +14818,12 @@ handle_one_xevent (struct x_display_info *dpyinfo, Qcoding, coding, inev.ie.arg); #ifdef HAVE_XINPUT2 - if (event->xkey.time == pending_keystroke_time) + if (event->xkey.time == pending_keystroke_time + /* I-Bus sometimes sends events generated from + multiple filtered keystrokes with a time of 0, + so just use the recorded source device if it + exists. */ + || (pending_keystroke_time && !event->xkey.time)) { source = xi_device_from_id (dpyinfo, dpyinfo->pending_keystroke_source); commit 48cb9c4aaadfe7bd50c13f658a6e8e9e97587867 Author: Augusto Stoffel Date: Sat Apr 9 12:47:28 2022 +0200 Add lazy highlight when reading 'query-replace' arguments * lisp/replace.el (query-replace-read-args): Use 'minibuffer-lazy-highlight-setup' to highlight the text to be replaced in the original buffer (and a match count, if applicable). (replace--region-filter): New function for code that used to be inlined in perform-replace but is useful elsewhere. (perform-replace): Use 'replace--region-filter'. diff --git a/lisp/replace.el b/lisp/replace.el index e6f565d802..00d30d1e38 100644 --- a/lisp/replace.el +++ b/lisp/replace.el @@ -365,11 +365,33 @@ should a regexp." (unless noerror (barf-if-buffer-read-only)) (save-mark-and-excursion - (let* ((from (query-replace-read-from prompt regexp-flag)) + (let* ((delimited-flag (and current-prefix-arg + (not (eq current-prefix-arg '-)))) + (from (minibuffer-with-setup-hook + (minibuffer-lazy-highlight-setup + :case-fold case-fold-search + :filter (when (use-region-p) + (replace--region-filter + (funcall region-extract-function 'bounds))) + :highlight query-replace-lazy-highlight + :regexp regexp-flag + :regexp-function (or replace-regexp-function + delimited-flag + (and replace-char-fold + (not regexp-flag) + #'char-fold-to-regexp)) + :transform (lambda (string) + (let* ((split (query-replace--split-string string)) + (from-string (if (consp split) (car split) split))) + (when (and case-fold-search search-upper-case) + (setq isearch-case-fold-search + (isearch-no-upper-case-p from-string regexp-flag))) + from-string))) + (query-replace-read-from prompt regexp-flag))) (to (if (consp from) (prog1 (cdr from) (setq from (car from))) (query-replace-read-to from prompt regexp-flag)))) (list from to - (or (and current-prefix-arg (not (eq current-prefix-arg '-))) + (or delimited-flag (and (plist-member (text-properties-at 0 from) 'isearch-regexp-function) (get-text-property 0 'isearch-regexp-function from))) (and current-prefix-arg (eq current-prefix-arg '-)))))) @@ -2778,6 +2800,26 @@ to a regexp that is actually used for the search.") ,search-str ,next-replace) ,stack)) +(defun replace--region-filter (bounds) + "Return a function that decides if a region is inside BOUNDS. +BOUNDS is a list of cons cells of the form (START . END). The +returned function takes as argument two buffer positions, START +and END." + (let ((region-bounds + (mapcar (lambda (position) + (cons (copy-marker (car position)) + (copy-marker (cdr position)))) + bounds))) + (lambda (start end) + (delq nil (mapcar + (lambda (bounds) + (and + (>= start (car bounds)) + (<= start (cdr bounds)) + (>= end (car bounds)) + (<= end (cdr bounds)))) + region-bounds))))) + (defun perform-replace (from-string replacements query-flag regexp-flag delimited-flag &optional repeat-count map start end backward region-noncontiguous-p) @@ -2862,22 +2904,9 @@ characters." ;; Unless a single contiguous chunk is selected, operate on multiple chunks. (when region-noncontiguous-p - (let ((region-bounds - (mapcar (lambda (position) - (cons (copy-marker (car position)) - (copy-marker (cdr position)))) - (funcall region-extract-function 'bounds)))) - (setq region-filter - (lambda (start end) - (delq nil (mapcar - (lambda (bounds) - (and - (>= start (car bounds)) - (<= start (cdr bounds)) - (>= end (car bounds)) - (<= end (cdr bounds)))) - region-bounds)))) - (add-function :after-while isearch-filter-predicate region-filter))) + (setq region-filter (replace--region-filter + (funcall region-extract-function 'bounds))) + (add-function :after-while isearch-filter-predicate region-filter)) ;; If region is active, in Transient Mark mode, operate on region. (if backward commit 4c0c9d23abc28c7fa7eacf2f4d7a5aff02d84ab0 Author: Augusto Stoffel Date: Sat Apr 9 12:38:14 2022 +0200 Rewrite the minibuffer lazy highlight feature The new API was discussed in bug#53126. It's more robust and easier to use in complex cases like that of 'query-replace'. * etc/NEWS: Amend the feature announcement * lisp/isearch.el (isearch-edit-string): Use new API. (minibuffer-lazy-highlight-transform, minibuffer-lazy-highlight--overlay, minibuffer-lazy-highlight--count, minibuffer-lazy-highlight--after-change, minibuffer-lazy-highlight--exit) Remove helper functions, which are now kept together with the lazy highlight configuration variables within a closure. (minibuffer-lazy-highlight-setup): This function now takes the lazy highlighting configuration variables as argument, and returns a closure that is intended to run as part of the minibuffer setup. diff --git a/etc/NEWS b/etc/NEWS index 2fac893cc5..2cddfcc8db 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1593,9 +1593,8 @@ the clipboard, and insert it into the buffer. --- ** New function 'minibuffer-lazy-highlight-setup'. -This function is intended to be added to 'minibuffer-setup-hook'. -It sets up the minibuffer for lazy highlighting of matches -in the original window. +This function allows to set up the minibuffer so that lazy +highlighting of its content is applied in the original window. +++ ** New text property 'inhibit-isearch'. diff --git a/lisp/isearch.el b/lisp/isearch.el index 956b115ce4..168d71ada3 100644 --- a/lisp/isearch.el +++ b/lisp/isearch.el @@ -1812,20 +1812,20 @@ The following additional command keys are active while editing. (minibuffer-history-symbol) ;; Search string might have meta information on text properties. (minibuffer-allow-text-properties t)) - (when isearch-lazy-highlight - (add-hook 'minibuffer-setup-hook #'minibuffer-lazy-highlight-setup)) (setq isearch-new-string - (read-from-minibuffer - (isearch-message-prefix nil isearch-nonincremental) - (cons isearch-string (1+ (or (isearch-fail-pos) - (length isearch-string)))) - minibuffer-local-isearch-map nil - (if isearch-regexp - (cons 'regexp-search-ring - (1+ (or regexp-search-ring-yank-pointer -1))) - (cons 'search-ring - (1+ (or search-ring-yank-pointer -1)))) - nil t) + (minibuffer-with-setup-hook + (minibuffer-lazy-highlight-setup) + (read-from-minibuffer + (isearch-message-prefix nil isearch-nonincremental) + (cons isearch-string (1+ (or (isearch-fail-pos) + (length isearch-string)))) + minibuffer-local-isearch-map nil + (if isearch-regexp + (cons 'regexp-search-ring + (1+ (or regexp-search-ring-yank-pointer -1))) + (cons 'search-ring + (1+ (or search-ring-yank-pointer -1)))) + nil t)) isearch-new-message (mapconcat 'isearch-text-char-description isearch-new-string ""))))) @@ -4361,57 +4361,81 @@ Attempt to do the search exactly the way the pending Isearch would." :group 'lazy-count :version "29.1") -(defvar minibuffer-lazy-highlight-transform #'identity - "Function to transform minibuffer text into a `isearch-string' for highlighting.") - -(defvar minibuffer-lazy-highlight--overlay nil - "Overlay for minibuffer prompt updates.") - -(defun minibuffer-lazy-highlight--count () - "Display total match count in the minibuffer prompt." - (when minibuffer-lazy-highlight--overlay - (overlay-put minibuffer-lazy-highlight--overlay - 'before-string - (and isearch-lazy-count-total - (not isearch-error) - (format minibuffer-lazy-count-format - isearch-lazy-count-total))))) - -(defun minibuffer-lazy-highlight--after-change (_beg _end _len) - "Update lazy highlight state in minibuffer selected window." - (when isearch-lazy-highlight - (let ((inhibit-redisplay t) ;; Avoid cursor flickering - (string (minibuffer-contents))) - (with-minibuffer-selected-window - (setq isearch-string (funcall minibuffer-lazy-highlight-transform string)) - (isearch-lazy-highlight-new-loop))))) - -(defun minibuffer-lazy-highlight--exit () - "Unwind changes from `minibuffer-lazy-highlight-setup'." - (remove-hook 'after-change-functions - #'minibuffer-lazy-highlight--after-change) - (remove-hook 'lazy-count-update-hook #'minibuffer-lazy-highlight--count) - (remove-hook 'minibuffer-exit-hook #'minibuffer-lazy-highlight--exit) - (setq minibuffer-lazy-highlight--overlay nil) - (when lazy-highlight-cleanup - (lazy-highlight-cleanup))) - -(defun minibuffer-lazy-highlight-setup () +(cl-defun minibuffer-lazy-highlight-setup + (&key (highlight isearch-lazy-highlight) + (cleanup lazy-highlight-cleanup) + (transform #'identity) + (filter nil) + (regexp isearch-regexp) + (regexp-function isearch-regexp-function) + (case-fold isearch-case-fold-search) + (lax-whitespace (if regexp + isearch-regexp-lax-whitespace + isearch-lax-whitespace))) "Set up minibuffer for lazy highlight of matches in the original window. -This function is intended to be added to `minibuffer-setup-hook'. -Note that several other isearch variables influence the lazy -highlighting, including `isearch-regexp', -`isearch-lazy-highlight' and `isearch-lazy-count'." - (remove-hook 'minibuffer-setup-hook #'minibuffer-lazy-highlight-setup) - (add-hook 'after-change-functions - #'minibuffer-lazy-highlight--after-change) - (add-hook 'lazy-count-update-hook #'minibuffer-lazy-highlight--count) - (add-hook 'minibuffer-exit-hook #'minibuffer-lazy-highlight--exit) - (setq minibuffer-lazy-highlight--overlay - (and minibuffer-lazy-count-format - (make-overlay (point-min) (point-min) (current-buffer) t))) - (minibuffer-lazy-highlight--after-change nil nil nil)) +This function return a closure intended to be added to +`minibuffer-setup-hook'. It accepts the following keyword +arguments, all of which have a default based on the current +isearch settings. + +HIGHLIGHT: Whether to perform lazy highlight. +CLEANUP: Whether to clean up the lazy highlight when the minibuffer +exits. +TRANSFORM: A function taking one argument, the minibuffer contents, +and returning the `isearch-string' to use for lazy highlighting. +FILTER: A function to add to `isearch-filter-predicate'. +REGEXP: The value of `isearch-regexp' to use for lazy highlighting. +REGEXP-FUNCTION: The value of `isearch-regexp-function' to use for +lazy highlighting. +CASE-FOLD: The value of `isearch-case-fold' to use for lazy +highlighting. +LAX-WHITESPACE: The value of `isearch-lax-whitespace' and +`isearch-regexp-lax-whitespace' to use for lazy highlighting." + (if (not highlight) + #'ignore + (let ((unwind (make-symbol "minibuffer-lazy-highlight--unwind")) + (after-change (make-symbol "minibuffer-lazy-highlight--after-change")) + (display-count (make-symbol "minibuffer-lazy-highlight--display-count")) + overlay) + (fset unwind + (lambda () + (remove-function isearch-filter-predicate filter) + (remove-hook 'lazy-count-update-hook display-count) + (when overlay (delete-overlay overlay)) + (remove-hook 'after-change-functions after-change) + (remove-hook 'minibuffer-exit-hook unwind) + (let ((lazy-highlight-cleanup cleanup)) + (lazy-highlight-cleanup)))) + (fset after-change + (lambda (_beg _end _len) + (let ((inhibit-redisplay t) ;; Avoid cursor flickering + (string (minibuffer-contents))) + (with-minibuffer-selected-window + (let* ((isearch-forward t) + (isearch-regexp regexp) + (isearch-regexp-function regexp-function) + (isearch-case-fold-search case-fold) + (isearch-lax-whitespace lax-whitespace) + (isearch-regexp-lax-whitespace lax-whitespace) + (isearch-string (funcall transform string))) + (isearch-lazy-highlight-new-loop)))))) + (fset display-count + (lambda () + (overlay-put overlay 'before-string + (and isearch-lazy-count-total + (not isearch-error) + (format minibuffer-lazy-count-format + isearch-lazy-count-total))))) + (lambda () + (add-hook 'minibuffer-exit-hook unwind) + (add-hook 'after-change-functions after-change) + (when minibuffer-lazy-count-format + (setq overlay (make-overlay (point-min) (point-min) (current-buffer) t)) + (add-hook 'lazy-count-update-hook display-count)) + (when filter + (add-function :after-while isearch-filter-predicate filter)) + (funcall after-change nil nil nil))))) (defun isearch-resume (string regexp word forward message case-fold) commit 7c6cdc1effe247a727a53eb3855894c0f00ef2ca Author: Lars Ingebrigtsen Date: Sun Apr 10 16:15:21 2022 +0200 Fix markup in Drag and Drop * doc/emacs/frames.texi (Drag and Drop): Fix markup. diff --git a/doc/emacs/frames.texi b/doc/emacs/frames.texi index 560870a4ed..7c564a8776 100644 --- a/doc/emacs/frames.texi +++ b/doc/emacs/frames.texi @@ -1201,7 +1201,7 @@ protocol, are currently supported. It can be difficult to scroll a window or determine where dropped text will be inserted while dragging text onto an Emacs window. -Setting the option @var{dnd-indicate-insertion-point} to a +Setting the option @code{dnd-indicate-insertion-point} to a non-@code{nil} value makes point move to the location any dropped text will be inserted when the mouse moves in a window during drag, and setting @code{dnd-scroll-margin} to an integer value causes a window commit 8addfafc2afac21e34d320524a80567f80926069 Author: Eli Zaretskii Date: Sun Apr 10 16:45:43 2022 +0300 Fix WebP image support on MS-Windows * src/image.c (init_webp_functions) [WINDOWSNT]: Load Demux functions from the WebPDemux DLL. Load internal functions where the public APIs are inline functions defined in the WebP headers. (WebPAnimDecoderOptionsInit) [WINDOWSNT]: Define to call 'WebPAnimDecoderOptionsInitInternal'. (WebPDemux): Define to call 'WebPDemuxInternal'. (WebPAnimDecoderNew): Define to call 'WebPAnimDecoderNewInternal'. (syms_of_image) : New symbol. * lisp/term/w32-win.el (dynamic-library-alist): Add a member for 'webpdemux'. diff --git a/lisp/term/w32-win.el b/lisp/term/w32-win.el index 4ed01de9ae..7eaa604776 100644 --- a/lisp/term/w32-win.el +++ b/lisp/term/w32-win.el @@ -275,6 +275,7 @@ See the documentation of `create-fontset-from-fontset-spec' for the format.") '(gif "libgif-5.dll" "giflib4.dll" "libungif4.dll" "libungif.dll"))) '(svg "librsvg-2-2.dll") '(webp "libwebp-7.dll" "libwebp.dll") + '(webpdemux "libwebpdemux-2.dll" "libwebpdemux.dll") '(sqlite3 "libsqlite3-0.dll") '(gdk-pixbuf "libgdk_pixbuf-2.0-0.dll") '(glib "libglib-2.0-0.dll") diff --git a/src/image.c b/src/image.c index 64438ef967..e3e540e5e2 100644 --- a/src/image.c +++ b/src/image.c @@ -9120,38 +9120,41 @@ DEF_DLL_FN (VP8StatusCode, WebPGetFeaturesInternal, DEF_DLL_FN (uint8_t *, WebPDecodeRGBA, (const uint8_t *, size_t, int *, int *)); DEF_DLL_FN (uint8_t *, WebPDecodeRGB, (const uint8_t *, size_t, int *, int *)); DEF_DLL_FN (void, WebPFree, (void *)); -DEF_DLL_FN (uint32_t, WebPDemuxGetI, (const WebPDemuxer* dmux, - WebPFormatFeature feature)); -DEF_DLL_FN (WebPDemuxer*, WebPDemux, (const WebPData* data)); -DEF_DLL_FN (void, WebPDemuxDelete, (WebPDemuxer* dmux)); +DEF_DLL_FN (uint32_t, WebPDemuxGetI, (const WebPDemuxer *, WebPFormatFeature)); +DEF_DLL_FN (WebPDemuxer *, WebPDemuxInternal, + (const WebPData *, int, WebPDemuxState *, int)); +DEF_DLL_FN (void, WebPDemuxDelete, (WebPDemuxer *)); DEF_DLL_FN (int, WebPAnimDecoderGetNext, - (WebPAnimDecoder* dec, uint8_t** buf, int* timestamp)); -DEF_DLL_FN (WebPAnimDecoder*, WebPAnimDecoderNew, - (const WebPData* webp_data, - const WebPAnimDecoderOptions* dec_options)); -DEF_DLL_FN (int, WebPAnimDecoderHasMoreFrames, (const WebPAnimDecoder* dec)); -DEF_DLL_FN (void, WebPAnimDecoderDelete, (WebPAnimDecoder* dec)); + (WebPAnimDecoder *, uint8_t **, int *)); +DEF_DLL_FN (WebPAnimDecoder *, WebPAnimDecoderNewInternal, + (const WebPData *, const WebPAnimDecoderOptions *, int)); +DEF_DLL_FN (int, WebPAnimDecoderOptionsInitInternal, + (WebPAnimDecoderOptions *, int)); +DEF_DLL_FN (int, WebPAnimDecoderHasMoreFrames, (const WebPAnimDecoder *)); +DEF_DLL_FN (void, WebPAnimDecoderDelete, (WebPAnimDecoder *)); static bool init_webp_functions (void) { - HMODULE library; + HMODULE library1, library2; - if (!(library = w32_delayed_load (Qwebp))) + if (!((library1 = w32_delayed_load (Qwebp)) + && (library2 = w32_delayed_load (Qwebpdemux)))) return false; - LOAD_DLL_FN (library, WebPGetInfo); - LOAD_DLL_FN (library, WebPGetFeaturesInternal); - LOAD_DLL_FN (library, WebPDecodeRGBA); - LOAD_DLL_FN (library, WebPDecodeRGB); - LOAD_DLL_FN (library, WebPFree); - LOAD_DLL_FN (library, WebPDemuxGetI); - LOAD_DLL_FN (library, WebPDemux); - LOAD_DLL_FN (library, WebPDemuxDelete); - LOAD_DLL_FN (library, WebPAnimDecoderGetNext); - LOAD_DLL_FN (library, WebPAnimDecoderNew); - LOAD_DLL_FN (library, WebPAnimDecoderHasMoreFrames); - LOAD_DLL_FN (library, WebPAnimDecoderDelete); + LOAD_DLL_FN (library1, WebPGetInfo); + LOAD_DLL_FN (library1, WebPGetFeaturesInternal); + LOAD_DLL_FN (library1, WebPDecodeRGBA); + LOAD_DLL_FN (library1, WebPDecodeRGB); + LOAD_DLL_FN (library1, WebPFree); + LOAD_DLL_FN (library2, WebPDemuxGetI); + LOAD_DLL_FN (library2, WebPDemuxInternal); + LOAD_DLL_FN (library2, WebPDemuxDelete); + LOAD_DLL_FN (library2, WebPAnimDecoderGetNext); + LOAD_DLL_FN (library2, WebPAnimDecoderNewInternal); + LOAD_DLL_FN (library2, WebPAnimDecoderOptionsInitInternal); + LOAD_DLL_FN (library2, WebPAnimDecoderHasMoreFrames); + LOAD_DLL_FN (library2, WebPAnimDecoderDelete); return true; } @@ -9165,6 +9168,7 @@ init_webp_functions (void) #undef WebPDemuxDelete #undef WebPAnimDecoderGetNext #undef WebPAnimDecoderNew +#undef WebPAnimDecoderOptionsInit #undef WebPAnimDecoderHasMoreFrames #undef WebPAnimDecoderDelete @@ -9175,10 +9179,14 @@ init_webp_functions (void) #define WebPDecodeRGB fn_WebPDecodeRGB #define WebPFree fn_WebPFree #define WebPDemuxGetI fn_WebPDemuxGetI -#define WebPDemux fn_WebPDemux +#define WebPDemux(d) \ + fn_WebPDemuxInternal(d,0,NULL,WEBP_DEMUX_ABI_VERSION) #define WebPDemuxDelete fn_WebPDemuxDelete #define WebPAnimDecoderGetNext fn_WebPAnimDecoderGetNext -#define WebPAnimDecoderNew fn_WebPAnimDecoderNew +#define WebPAnimDecoderNew(d,o) \ + fn_WebPAnimDecoderNewInternal(d,o,WEBP_DEMUX_ABI_VERSION) +#define WebPAnimDecoderOptionsInit(o) \ + fn_WebPAnimDecoderOptionsInitInternal(o,WEBP_DEMUX_ABI_VERSION) #define WebPAnimDecoderHasMoreFrames fn_WebPAnimDecoderHasMoreFrames #define WebPAnimDecoderDelete fn_WebPAnimDecoderDelete @@ -11716,6 +11724,7 @@ non-numeric, there is no explicit limit on the size of images. */); #if defined (HAVE_WEBP) || (defined (HAVE_NATIVE_IMAGE_API) \ && defined (HAVE_HAIKU)) DEFSYM (Qwebp, "webp"); + DEFSYM (Qwebpdemux, "webpdemux"); add_image_type (Qwebp); #endif commit a64e5a3a847407200e0dc44b6acb60bdbede9080 Author: Kévin Le Gouguec Date: Sun Apr 10 14:43:02 2022 +0200 Fix a ja-dic compilation warning * leim/Makefile.in (${leimdir}/ja-dic/ja-dic.el): Increase specpdl-size to avoid a compilation warning (bug#54816). diff --git a/leim/Makefile.in b/leim/Makefile.in index 6cf0abb40c..4e70e8b7e9 100644 --- a/leim/Makefile.in +++ b/leim/Makefile.in @@ -131,6 +131,7 @@ ${leimdir}/ja-dic/ja-dic.el: | $(leimdir)/ja-dic ${leimdir}/ja-dic/ja-dic.el: $(srcdir)/SKK-DIC/SKK-JISYO.L $(AM_V_GEN)$(RUN_EMACS) -batch -l ja-dic-cnv \ + --eval "(setq max-specpdl-size 5000)" \ -f batch-skkdic-convert -dir "$(leimdir)/ja-dic" "$<" ${srcdir}/../lisp/language/pinyin.el: ${srcdir}/MISC-DIC/pinyin.map commit 02b521ad7456ae6834b0399ec80f56ee8ca09522 Author: Alan Mackenzie Date: Sun Apr 10 12:20:39 2022 +0000 CC Mode: Fix unwanted fontification of function call as function declaration This happens when the enclosing function's return type is a struct, etc. This fixes bug #54743; * lisp/progmodes/cc-engine.el (c-update-brace-stack): Replace "(" by ")" in a `member' call. diff --git a/lisp/progmodes/cc-engine.el b/lisp/progmodes/cc-engine.el index ebc1ef4301..b2fa9e0691 100644 --- a/lisp/progmodes/cc-engine.el +++ b/lisp/progmodes/cc-engine.el @@ -6139,7 +6139,7 @@ comment at the start of cc-engine.el for more info." (setq s (cons -1 (cdr s)))) ((and (equal match ",") (eq (car s) -1))) ; at "," in "class foo : bar, ..." - ((member match '(";" "*" "," "(")) + ((member match '(";" "*" "," ")")) (when (and s (cdr s) (<= (car s) 0)) (setq s (cdr s)))) ((c-keyword-member kwd-sym 'c-flat-decl-block-kwds) commit cca47ae555bfddf87b4871988555738c335f8457 Author: Kaushal Modi Date: Sun Apr 10 13:52:15 2022 +0200 Update docstrings for shortdoc.el FUNC lisp form API * lisp/emacs-lisp/shortdoc.el (define-short-documentation-group): Updated docstrings. diff --git a/lisp/emacs-lisp/shortdoc.el b/lisp/emacs-lisp/shortdoc.el index 658edd6752..ebf3c6b1fe 100644 --- a/lisp/emacs-lisp/shortdoc.el +++ b/lisp/emacs-lisp/shortdoc.el @@ -47,30 +47,67 @@ "Add GROUP to the list of defined documentation groups. FUNCTIONS is a list of elements on the form: - (fun + (FUNC :no-manual BOOL :args ARGS - :eval EXAMPLE-FORM + :eval EVAL :no-eval EXAMPLE-FORM - :no-eval* EXAMPLE-FORM :no-value EXAMPLE-FORM + :no-eval* EXAMPLE-FORM :result RESULT-FORM - :result-string RESULT-FORM + :result-string RESULT-STRING :eg-result RESULT-FORM - :eg-result-string RESULT-FORM) + :eg-result-string RESULT-STRING) -BOOL should be non-nil if the function isn't documented in the +FUNC is the function being documented. + +NO-MANUAL should be non-nil if FUNC isn't documented in the manual. -ARGS is optional; the function's signature is displayed if ARGS -is not present. +ARGS is optional list of function FUNC's arguments. FUNC's +signature is displayed automatically if ARGS is not present. +Specifying ARGS might be useful where you don't want to document +some of the uncommon arguments a function might have. + +While the `:no-manual' and `:args' property can be used for +any (FUNC ..) form, all of the other properties shown above +cannot be used simultaneously in such a form. -If EVAL isn't a string, it will be printed with `prin1', and then -evaluated to give a result, which is also printed. If it's a -string, it'll be inserted as is, then the string will be `read', -and then evaluated. +Here are some common forms with examples of properties that go +together: -There can be any number of :example/:result elements." +1. Document a form or string, and its evaluated return value. + (FUNC + :eval EVAL) + +If EVAL is a string, it will be inserted as is, and then that +string will be `read' and evaluated. + +2. Document a form or string, but manually document its evalation + result. The provided form will not be evaluated. + + (FUNC + :no-eval EXAMPLE-FORM + :result RESULT-FORM ;Use `:result-string' if value is in string form + ) + +Using `:no-value' is the same as using `:no-eval'. + +Use `:no-eval*' instead of `:no-eval' where the successful +execution of the documented form depends on some conditions. + +3. Document a form or string EXAMPLE-FORM. Also manually + document an example result. This result could be unrelated to + the documented form. + + (FUNC + :no-eval EXAMPLE-FORM + :eg-result RESULT-FORM ;Use `:eg-result-string' if value is in string form + ) + +A FUNC form can have any number of `:no-eval' (or `:no-value'), +`:no-eval*', `:result', `:result-string', `:eg-result' and +`:eg-result-string' properties." (declare (indent defun)) `(progn (setq shortdoc--groups (delq (assq ',group shortdoc--groups) @@ -1408,11 +1445,14 @@ function's documentation in the Info manual"))) If GROUP doesn't exist, it will be created. If SECTION doesn't exist, it will be added. +ELEM is a Lisp form. See `define-short-documentation-group' for +details. + Example: (shortdoc-add-function - 'file \"Predicates\" - '(file-locked-p :no-eval (file-locked-p \"/tmp\")))" + \\='file \"Predicates\" + \\='(file-locked-p :no-eval (file-locked-p \"/tmp\")))" (let ((glist (assq group shortdoc--groups))) (unless glist (setq glist (list group)) commit 59186acf709d60c0ef1d61764a696408ad68312e Author: Po Lu Date: Sun Apr 10 19:33:51 2022 +0800 Attribute filtered events to the right source device * src/xterm.c (handle_one_xevent): Attribute core events sent by input methods like I-Bus to the extension device that caused them to be sent. * src/xterm.h (struct x_display_info): New fields `pending_keystroke_time' and `pending_keystroke_source'. diff --git a/src/xterm.c b/src/xterm.c index 9de46aba10..88122416e2 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -14490,6 +14490,15 @@ handle_one_xevent (struct x_display_info *dpyinfo, `event' itself. */ XKeyEvent xkey = event->xkey; int i; +#ifdef HAVE_XINPUT2 + Time pending_keystroke_time; + struct xi_device_t *source; + + pending_keystroke_time = dpyinfo->pending_keystroke_time; + + if (event->xkey.time >= pending_keystroke_time) + dpyinfo->pending_keystroke_time = 0; +#endif #ifdef USE_GTK /* Don't pass keys to GTK. A Tab will shift focus to the @@ -14612,6 +14621,18 @@ handle_one_xevent (struct x_display_info *dpyinfo, { inev.ie.kind = ASCII_KEYSTROKE_EVENT; inev.ie.code = keysym; + +#ifdef HAVE_XINPUT2 + if (event->xkey.time == pending_keystroke_time) + { + source = xi_device_from_id (dpyinfo, + dpyinfo->pending_keystroke_source); + + if (source) + inev.ie.device = source->name; + } +#endif + goto done_keysym; } @@ -14623,6 +14644,18 @@ handle_one_xevent (struct x_display_info *dpyinfo, else inev.ie.kind = MULTIBYTE_CHAR_KEYSTROKE_EVENT; inev.ie.code = keysym & 0xFFFFFF; + +#ifdef HAVE_XINPUT2 + if (event->xkey.time == pending_keystroke_time) + { + source = xi_device_from_id (dpyinfo, + dpyinfo->pending_keystroke_source); + + if (source) + inev.ie.device = source->name; + } +#endif + goto done_keysym; } @@ -14637,6 +14670,18 @@ handle_one_xevent (struct x_display_info *dpyinfo, ? ASCII_KEYSTROKE_EVENT : MULTIBYTE_CHAR_KEYSTROKE_EVENT); inev.ie.code = XFIXNAT (c); + +#ifdef HAVE_XINPUT2 + if (event->xkey.time == pending_keystroke_time) + { + source = xi_device_from_id (dpyinfo, + dpyinfo->pending_keystroke_source); + + if (source) + inev.ie.device = source->name; + } +#endif + goto done_keysym; } @@ -14741,6 +14786,18 @@ handle_one_xevent (struct x_display_info *dpyinfo, key. */ inev.ie.kind = NON_ASCII_KEYSTROKE_EVENT; inev.ie.code = keysym; + +#ifdef HAVE_XINPUT2 + if (event->xkey.time == pending_keystroke_time) + { + source = xi_device_from_id (dpyinfo, + dpyinfo->pending_keystroke_source); + + if (source) + inev.ie.device = source->name; + } +#endif + goto done_keysym; } @@ -14759,6 +14816,17 @@ handle_one_xevent (struct x_display_info *dpyinfo, Fput_text_property (make_fixnum (0), make_fixnum (nbytes), Qcoding, coding, inev.ie.arg); + +#ifdef HAVE_XINPUT2 + if (event->xkey.time == pending_keystroke_time) + { + source = xi_device_from_id (dpyinfo, + dpyinfo->pending_keystroke_source); + + if (source) + inev.ie.device = source->name; + } +#endif } if (keysym == NoSymbol) @@ -17312,6 +17380,12 @@ handle_one_xevent (struct x_display_info *dpyinfo, #else if (x_filter_event (dpyinfo, (XEvent *) &xkey)) { + /* Try to attribute core key events from the input + method to the input extension event that caused + them. */ + dpyinfo->pending_keystroke_time = xev->time; + dpyinfo->pending_keystroke_source = xev->sourceid; + *finish = X_EVENT_DROP; goto XI_OTHER; } diff --git a/src/xterm.h b/src/xterm.h index c12fd6c3fe..85b773cf0b 100644 --- a/src/xterm.h +++ b/src/xterm.h @@ -615,6 +615,9 @@ struct x_display_info int num_devices; struct xi_device_t *devices; + + Time pending_keystroke_time; + int pending_keystroke_source; #endif #ifdef HAVE_XKB commit d82e1a873df381b2c35bc9036da5665468bdfd31 Author: Lars Ingebrigtsen Date: Sun Apr 10 13:12:30 2022 +0200 Add support for animated webp images * configure.ac (HAVE_RSVG): Also include the webpdemux library. It was new in version 0.4.4, and we require 0.6.0, so it should be safe. * src/image.c: Include demux.h. (enum webp_keyword_index, webp_format): Include :index for animations. (init_webp_functions): Add Windows LOAD_DLLs. (struct webp_cache, webp_create_cache) (webp_prune_animation_cache, webp_get_animation_cache): New functions. (webp_load): Support animated webp images (bug#54242). diff --git a/configure.ac b/configure.ac index 6b834a2f65..185e4d0862 100644 --- a/configure.ac +++ b/configure.ac @@ -2695,6 +2695,9 @@ if test "${with_webp}" != "no"; then WEBP_MODULE="libwebp >= $WEBP_REQUIRED" EMACS_CHECK_MODULES([WEBP], [$WEBP_MODULE]) + if test "$HAVE_WEBP" = "yes"; then + WEBP_LIBS="-lwebp -lwebpdemux" + fi AC_SUBST(WEBP_CFLAGS) AC_SUBST(WEBP_LIBS) fi diff --git a/src/image.c b/src/image.c index 519eafb904..64438ef967 100644 --- a/src/image.c +++ b/src/image.c @@ -9053,6 +9053,7 @@ gif_load (struct frame *f, struct image *img) ***********************************************************************/ #include "webp/decode.h" +#include "webp/demux.h" /* Indices of image specification fields in webp_format, below. */ @@ -9067,6 +9068,7 @@ enum webp_keyword_index WEBP_ALGORITHM, WEBP_HEURISTIC_MASK, WEBP_MASK, + WEBP_INDEX, WEBP_BACKGROUND, WEBP_LAST }; @@ -9085,6 +9087,7 @@ static const struct image_keyword webp_format[WEBP_LAST] = {":conversion", IMAGE_DONT_CHECK_VALUE_TYPE, 0}, {":heuristic-mask", IMAGE_DONT_CHECK_VALUE_TYPE, 0}, {":mask", IMAGE_DONT_CHECK_VALUE_TYPE, 0}, + {":index", IMAGE_NON_NEGATIVE_INTEGER_VALUE, 0}, {":background", IMAGE_STRING_OR_NIL_VALUE, 0} }; @@ -9117,6 +9120,17 @@ DEF_DLL_FN (VP8StatusCode, WebPGetFeaturesInternal, DEF_DLL_FN (uint8_t *, WebPDecodeRGBA, (const uint8_t *, size_t, int *, int *)); DEF_DLL_FN (uint8_t *, WebPDecodeRGB, (const uint8_t *, size_t, int *, int *)); DEF_DLL_FN (void, WebPFree, (void *)); +DEF_DLL_FN (uint32_t, WebPDemuxGetI, (const WebPDemuxer* dmux, + WebPFormatFeature feature)); +DEF_DLL_FN (WebPDemuxer*, WebPDemux, (const WebPData* data)); +DEF_DLL_FN (void, WebPDemuxDelete, (WebPDemuxer* dmux)); +DEF_DLL_FN (int, WebPAnimDecoderGetNext, + (WebPAnimDecoder* dec, uint8_t** buf, int* timestamp)); +DEF_DLL_FN (WebPAnimDecoder*, WebPAnimDecoderNew, + (const WebPData* webp_data, + const WebPAnimDecoderOptions* dec_options)); +DEF_DLL_FN (int, WebPAnimDecoderHasMoreFrames, (const WebPAnimDecoder* dec)); +DEF_DLL_FN (void, WebPAnimDecoderDelete, (WebPAnimDecoder* dec)); static bool init_webp_functions (void) @@ -9131,6 +9145,13 @@ init_webp_functions (void) LOAD_DLL_FN (library, WebPDecodeRGBA); LOAD_DLL_FN (library, WebPDecodeRGB); LOAD_DLL_FN (library, WebPFree); + LOAD_DLL_FN (library, WebPDemuxGetI); + LOAD_DLL_FN (library, WebPDemux); + LOAD_DLL_FN (library, WebPDemuxDelete); + LOAD_DLL_FN (library, WebPAnimDecoderGetNext); + LOAD_DLL_FN (library, WebPAnimDecoderNew); + LOAD_DLL_FN (library, WebPAnimDecoderHasMoreFrames); + LOAD_DLL_FN (library, WebPAnimDecoderDelete); return true; } @@ -9139,6 +9160,13 @@ init_webp_functions (void) #undef WebPDecodeRGBA #undef WebPDecodeRGB #undef WebPFree +#undef WebPDemuxGetI +#undef WebPDemux +#undef WebPDemuxDelete +#undef WebPAnimDecoderGetNext +#undef WebPAnimDecoderNew +#undef WebPAnimDecoderHasMoreFrames +#undef WebPAnimDecoderDelete #define WebPGetInfo fn_WebPGetInfo #define WebPGetFeatures(d,s,f) \ @@ -9146,9 +9174,92 @@ init_webp_functions (void) #define WebPDecodeRGBA fn_WebPDecodeRGBA #define WebPDecodeRGB fn_WebPDecodeRGB #define WebPFree fn_WebPFree +#define WebPDemuxGetI fn_WebPDemuxGetI +#define WebPDemux fn_WebPDemux +#define WebPDemuxDelete fn_WebPDemuxDelete +#define WebPAnimDecoderGetNext fn_WebPAnimDecoderGetNext +#define WebPAnimDecoderNew fn_WebPAnimDecoderNew +#define WebPAnimDecoderHasMoreFrames fn_WebPAnimDecoderHasMoreFrames +#define WebPAnimDecoderDelete fn_WebPAnimDecoderDelete #endif /* WINDOWSNT */ +/* To speed webp animations up, we keep a cache (based on EQ-ness of + the image spec/object) where we put the libwebp animator + iterator. */ + +struct webp_cache +{ + Lisp_Object spec; + WebPAnimDecoder* anim; + int index, width, height, frames; + struct timespec update_time; + struct webp_cache *next; +}; + +static struct webp_cache *webp_cache = NULL; + +static struct webp_cache * +webp_create_cache (Lisp_Object spec) +{ + struct webp_cache *cache = xmalloc (sizeof (struct webp_cache)); + cache->anim = NULL; + + cache->index = 0; + cache->next = NULL; + /* FIXME: Does this need gc protection? */ + cache->spec = spec; + return cache; +} + +/* Discard cached images that haven't been used for a minute. */ +static void +webp_prune_animation_cache (void) +{ + struct webp_cache **pcache = &webp_cache; + struct timespec old = timespec_sub (current_timespec (), + make_timespec (60, 0)); + + while (*pcache) + { + struct webp_cache *cache = *pcache; + if (timespec_cmp (old, cache->update_time) <= 0) + pcache = &cache->next; + else + { + if (cache->anim) + WebPAnimDecoderDelete (cache->anim); + *pcache = cache->next; + xfree (cache); + } + } +} + +static struct webp_cache * +webp_get_animation_cache (Lisp_Object spec) +{ + struct webp_cache *cache; + struct webp_cache **pcache = &webp_cache; + + webp_prune_animation_cache (); + + while (1) + { + cache = *pcache; + if (! cache) + { + *pcache = cache = webp_create_cache (spec); + break; + } + if (EQ (spec, cache->spec)) + break; + pcache = &cache->next; + } + + cache->update_time = current_timespec (); + return cache; +} + /* Load WebP image IMG for use on frame F. Value is true if successful. */ @@ -9158,6 +9269,9 @@ webp_load (struct frame *f, struct image *img) ptrdiff_t size = 0; uint8_t *contents; Lisp_Object file = Qnil; + int frames = 0; + double delay = 0; + WebPAnimDecoder* anim = NULL; /* Open the WebP file. */ Lisp_Object specified_file = image_spec_value (img->spec, QCfile, NULL); @@ -9201,6 +9315,9 @@ webp_load (struct frame *f, struct image *img) goto webp_error1; } + Lisp_Object image_number = image_spec_value (img->spec, QCindex, NULL); + ptrdiff_t idx = FIXNUMP (image_number) ? XFIXNAT (image_number) : 0; + /* Get WebP features. */ WebPBitstreamFeatures features; VP8StatusCode result = WebPGetFeatures (contents, size, &features); @@ -9224,19 +9341,76 @@ webp_load (struct frame *f, struct image *img) goto webp_error1; } - /* Decode WebP data. */ - uint8_t *decoded; + uint8_t *decoded = NULL; int width, height; - if (features.has_alpha) - /* Linear [r0, g0, b0, a0, r1, g1, b1, a1, ...] order. */ - decoded = WebPDecodeRGBA (contents, size, &width, &height); + + if (features.has_animation) + { + /* Animated image. */ + WebPData webp_data; + webp_data.bytes = contents; + webp_data.size = size; + int timestamp; + + struct webp_cache* cache = webp_get_animation_cache (img->spec); + /* Get the next frame from the animation cache. */ + if (cache->anim && cache->index == idx - 1) + { + WebPAnimDecoderGetNext (cache->anim, &decoded, ×tamp); + delay = timestamp; + cache->index++; + anim = cache->anim; + width = cache->width; + height = cache->height; + frames = cache->frames; + } + else + { + /* Start a new cache entry. */ + if (cache->anim) + WebPAnimDecoderDelete (cache->anim); + + /* Get the width/height of the total image. */ + WebPDemuxer* demux = WebPDemux (&webp_data); + cache->width = width = WebPDemuxGetI (demux, WEBP_FF_CANVAS_WIDTH); + cache->height = height = WebPDemuxGetI (demux, + WEBP_FF_CANVAS_HEIGHT); + cache->frames = frames = WebPDemuxGetI (demux, WEBP_FF_FRAME_COUNT); + WebPDemuxDelete (demux); + + WebPAnimDecoderOptions dec_options; + WebPAnimDecoderOptionsInit (&dec_options); + anim = WebPAnimDecoderNew (&webp_data, &dec_options); + + cache->anim = anim; + cache->index = idx; + + while (WebPAnimDecoderHasMoreFrames (anim)) { + WebPAnimDecoderGetNext (anim, &decoded, ×tamp); + /* Each frame has its own delay, but we don't really support + that. So just use the delay from the first frame. */ + if (delay == 0) + delay = timestamp; + /* Stop when we get to the desired index. */ + if (idx-- == 0) + break; + } + } + } else - /* Linear [r0, g0, b0, r1, g1, b1, ...] order. */ - decoded = WebPDecodeRGB (contents, size, &width, &height); + { + /* Non-animated image. */ + if (features.has_alpha) + /* Linear [r0, g0, b0, a0, r1, g1, b1, a1, ...] order. */ + decoded = WebPDecodeRGBA (contents, size, &width, &height); + else + /* Linear [r0, g0, b0, r1, g1, b1, ...] order. */ + decoded = WebPDecodeRGB (contents, size, &width, &height); + } if (!decoded) { - image_error ("Error when interpreting WebP image data"); + image_error ("Error when decoding WebP image data"); goto webp_error1; } @@ -9255,7 +9429,8 @@ webp_load (struct frame *f, struct image *img) /* Create an image and pixmap serving as mask if the WebP image contains an alpha channel. */ if (features.has_alpha - && !image_create_x_image_and_pixmap (f, img, width, height, 1, &mask_img, true)) + && !image_create_x_image_and_pixmap (f, img, width, height, 1, + &mask_img, true)) { image_destroy_x_image (ximg); image_clear_image_1 (f, img, CLEAR_IMAGE_PIXMAP); @@ -9265,6 +9440,13 @@ webp_load (struct frame *f, struct image *img) /* Fill the X image and mask from WebP data. */ init_color_table (); + img->corners[TOP_CORNER] = 0; + img->corners[LEFT_CORNER] = 0; + img->corners[BOT_CORNER] + = img->corners[TOP_CORNER] + height; + img->corners[RIGHT_CORNER] + = img->corners[LEFT_CORNER] + width; + uint8_t *p = decoded; for (int y = 0; y < height; ++y) { @@ -9279,7 +9461,7 @@ webp_load (struct frame *f, struct image *img) image. WebP allows up to 256 levels of partial transparency. We handle this like with PNG (which see), using the frame's background color to combine the image with. */ - if (features.has_alpha) + if (features.has_alpha || anim) { if (mask_img) PUT_PIXEL (mask_img, x, y, *p > 0 ? PIX_MASK_DRAW : PIX_MASK_RETAIN); @@ -9310,14 +9492,24 @@ webp_load (struct frame *f, struct image *img) img->width = width; img->height = height; + /* Return animation data. */ + img->lisp_data = Fcons (Qcount, + Fcons (make_fixnum (frames), + img->lisp_data)); + img->lisp_data = Fcons (Qdelay, + Fcons (make_float (delay / 1000), + img->lisp_data)); + /* Clean up. */ - WebPFree (decoded); + if (!anim) + WebPFree (decoded); if (NILP (specified_data)) xfree (contents); return true; webp_error2: - WebPFree (decoded); + if (!anim) + WebPFree (decoded); webp_error1: if (NILP (specified_data)) @@ -9484,7 +9676,7 @@ imagemagick_filename_hint (Lisp_Object spec, char hint_buffer[MaxTextExtent]) (which is the first one, and then there's a number of images that follow. If following images have non-transparent colors, these are composed "on top" of the master image. So, in general, one has to - compute ann the preceding images to be able to display a particular + compute all the preceding images to be able to display a particular sub-image. Computing all the preceding images is too slow, so we maintain a commit 735b45191041778824460807ee5bf4d1cebd3421 Author: Juri Linkov Date: Sun Apr 10 10:37:55 2022 +0300 Allow non-interactive uses of minibuffer-next-completion * lisp/minibuffer.el (minibuffer-previous-completion) (minibuffer-next-completion): Use 1 for n by default. diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el index c79c5a7a5d..68b167ccc7 100644 --- a/lisp/minibuffer.el +++ b/lisp/minibuffer.el @@ -4362,7 +4362,7 @@ and execute the forms." (with-minibuffer-completions-window (when completions-highlight-face (setq-local cursor-face-highlight-nonselected-window t)) - (previous-completion n))) + (previous-completion (or n 1)))) (defun minibuffer-next-completion (&optional n) "Run `next-completion' from the minibuffer in its completions window." @@ -4370,7 +4370,7 @@ and execute the forms." (with-minibuffer-completions-window (when completions-highlight-face (setq-local cursor-face-highlight-nonselected-window t)) - (next-completion n))) + (next-completion (or n 1)))) (defun minibuffer-choose-previous-completion (&optional n) "Run `previous-completion' from the minibuffer in its completions window.