commit db3d6f06c389e06922964fb4869d19ba9040c16e (HEAD, refs/remotes/origin/master) Author: Eli Zaretskii Date: Wed Sep 24 08:56:37 2025 +0300 * lisp/window.el (display-buffer-alist): Fix :key-type (bug#79496). diff --git a/lisp/window.el b/lisp/window.el index 512e85dc29a..b3ee73189f7 100644 --- a/lisp/window.el +++ b/lisp/window.el @@ -8048,11 +8048,8 @@ where: `display-buffer' scans this alist until the CONDITION is satisfied and adds the associated ACTION to the list of actions it will try." - :type `(alist :key-type - (choice :tag "Condition" - regexp - (function :tag "Matcher function")) - :value-type ,display-buffer--action-custom-type) + :type `(alist :key-type (buffer-predicate :tag "Condition") + :value-type ,display-buffer--action-custom-type) :risky t :version "24.1" :group 'windows) commit d0b81f5d80568352663764d282a7809a8dfc752a Author: Stefan Monnier Date: Tue Sep 23 16:38:49 2025 -0400 (electric-indent-actions): Fix corner case and refine setup * lisp/electric.el (electric-indent--activate-indent-actions): Rename from `electric-indent-toggle-indent-actions`. Adjust callers. Make its result less dependent on the input state. Support `yank` and `before-save` at the same time. (electric-indent-actions): Use it instead if `electric-indent-mode`. diff --git a/lisp/electric.el b/lisp/electric.el index fdf3e6ae65d..6ecdea57d6b 100644 --- a/lisp/electric.el +++ b/lisp/electric.el @@ -208,8 +208,7 @@ significant." :set (lambda (var val) (set-default var val) (when (bound-and-true-p electric-indent-mode) - (electric-indent-mode -1) - (electric-indent-mode +1))) + (electric-indent--activate-indent-actions t))) :safe (lambda (v) (and (proper-list-p v) (null (seq-filter (lambda (e) (not (symbolp e)) ) v)))) @@ -273,17 +272,15 @@ mode set `electric-indent-inhibit', but this can be used as a workaround.") (with-demoted-errors "Error reindenting: %S" (indent-region (point-min) (point-max)))))) -(defun electric-indent-toggle-indent-actions (enable) +(defun electric-indent--activate-indent-actions (enable) "Enable the actions specified in `electric-indent-actions'." - (cond - ((memq 'yank electric-indent-actions) - (if enable - (advice-add #'yank :around #'electric-indent--yank-advice) - (advice-remove #'yank #'electric-indent--yank-advice))) - ((memq 'before-save electric-indent-actions) - (if enable - (add-hook 'before-save-hook #'electric-indent-save-hook) - (remove-hook 'before-save-hook #'electric-indent-save-hook))))) + (advice-remove 'yank #'electric-indent--yank-advice) + (remove-hook 'before-save-hook #'electric-indent-save-hook) + (when enable + (when (memq 'yank electric-indent-actions) + (advice-add 'yank :around #'electric-indent--yank-advice)) + (when (memq 'before-save electric-indent-actions) + (add-hook 'before-save-hook #'electric-indent-save-hook)))) (defun electric-indent-post-self-insert-function () "Function that `electric-indent-mode' adds to `post-self-insert-hook'. @@ -401,7 +398,7 @@ use `electric-indent-local-mode'." 60)) ;; Toggle the reindentation on actions - (electric-indent-toggle-indent-actions electric-indent-mode)) + (electric-indent--activate-indent-actions electric-indent-mode)) ;;;###autoload (define-minor-mode electric-indent-local-mode commit 34bcac0d62751fa9ab9f3f5660b6132f5c9166c5 Author: Stefan Monnier Date: Tue Sep 23 16:24:13 2025 -0400 lisp/electric.el (electric-indent-actions): Fix bootstrap diff --git a/lisp/electric.el b/lisp/electric.el index 7825bbe43ec..fdf3e6ae65d 100644 --- a/lisp/electric.el +++ b/lisp/electric.el @@ -207,7 +207,7 @@ significant." (const :tag "Before saving" before-save))) :set (lambda (var val) (set-default var val) - (when electric-indent-mode + (when (bound-and-true-p electric-indent-mode) (electric-indent-mode -1) (electric-indent-mode +1))) :safe (lambda (v) commit 2a782c8d2803edeb01aef592c2b89fc8bcd80660 Author: Elías Gabriel Pérez Date: Thu Sep 11 19:13:21 2025 -0600 Add electric-indent actions to reindent (bug#79371) * lisp/electric.el (electric-indent-actions) (electric-indent-function): New user options. (electric-indent-should-reindent-p, electric-indent--yank-advice) (electric-indent-save-hook): New functions. (electric-indent-post-self-insert-function): Use them. diff --git a/lisp/electric.el b/lisp/electric.el index 302fb8d08bc..7825bbe43ec 100644 --- a/lisp/electric.el +++ b/lisp/electric.el @@ -192,6 +192,29 @@ Returns nil when we can't find this char." ;;; Electric indentation. +(defcustom electric-indent-actions nil + "List of actions to indent. + +The valid elements of this list can be: + - yank: Indent the yanked text only if point is not in a string or + comment and yanked region is longer than 1 line. + - save: Indent the whole buffer before saving it. + +The indentation will not happen when the major mode is unable to +reindent code reliably, such as in buffers where indentation is +significant." + :type '(repeat (choice (const :tag "After yanking" yank) + (const :tag "Before saving" before-save))) + :set (lambda (var val) + (set-default var val) + (when electric-indent-mode + (electric-indent-mode -1) + (electric-indent-mode +1))) + :safe (lambda (v) + (and (proper-list-p v) + (null (seq-filter (lambda (e) (not (symbolp e)) ) v)))) + :version "31.1") + ;; Autoloading variables is generally undesirable, but major modes ;; should usually set this variable by adding elements to the default ;; value, which only works well if the variable is preloaded. @@ -218,6 +241,50 @@ If `indent-line-function' is one of those, then `electric-indent-mode' will not try to reindent lines. It is normally better to make the major mode set `electric-indent-inhibit', but this can be used as a workaround.") +(defun electric-indent-can-reindent-p () + "Return t if `electric-indent-mode' can performs reindentation." + (not (or (memq indent-line-function + electric-indent-functions-without-reindent) + electric-indent-inhibit))) + +(defun electric-indent--yank-advice (fn &rest r) + (let ((p (point)) + (end (line-beginning-position))) + (apply fn r) + (when (and electric-indent-mode + (memq 'yank electric-indent-actions) + (electric-indent-can-reindent-p) + ;; Ensure yanked text is longer than 1 line + (> (point) p) + (not (= end (line-beginning-position)))) + (undo-boundary) + (save-excursion + (with-demoted-errors "Error reindenting: %S" + (indent-region p (point))))))) + +(defun electric-indent-save-hook () + (when (and electric-indent-mode + ;; Ensure this hook is called interactively + (memq real-this-command '(save-buffer basic-save-buffer)) + (memq 'before-save electric-indent-actions) + (not buffer-read-only) + (electric-indent-can-reindent-p)) + (save-excursion + (with-demoted-errors "Error reindenting: %S" + (indent-region (point-min) (point-max)))))) + +(defun electric-indent-toggle-indent-actions (enable) + "Enable the actions specified in `electric-indent-actions'." + (cond + ((memq 'yank electric-indent-actions) + (if enable + (advice-add #'yank :around #'electric-indent--yank-advice) + (advice-remove #'yank #'electric-indent--yank-advice))) + ((memq 'before-save electric-indent-actions) + (if enable + (add-hook 'before-save-hook #'electric-indent-save-hook) + (remove-hook 'before-save-hook #'electric-indent-save-hook))))) + (defun electric-indent-post-self-insert-function () "Function that `electric-indent-mode' adds to `post-self-insert-hook'. This indents if the hook `electric-indent-functions' returns non-nil, @@ -256,10 +323,7 @@ or comment." (when at-newline (let ((before (copy-marker (1- pos) t))) (save-excursion - (unless - (or (memq indent-line-function - electric-indent-functions-without-reindent) - electric-indent-inhibit) + (when (electric-indent-can-reindent-p) ;; Don't reindent the previous line if the ;; indentation function is not a real one. (goto-char before) @@ -331,9 +395,13 @@ use `electric-indent-local-mode'." (if electric-indent-mode (throw 'found t))))) (remove-hook 'post-self-insert-hook #'electric-indent-post-self-insert-function)) + (add-hook 'post-self-insert-hook #'electric-indent-post-self-insert-function - 60))) + 60)) + + ;; Toggle the reindentation on actions + (electric-indent-toggle-indent-actions electric-indent-mode)) ;;;###autoload (define-minor-mode electric-indent-local-mode commit 578aeedbe986efbae63382a7b9ba1a94372df659 Author: Stefan Monnier Date: Tue Sep 23 13:48:06 2025 -0400 (electric-indent-functions-without-reindent): Pare down * lisp/electric.el (electric-indent-functions-without-reindent): Remove obsolete entries: Haskell modes, python-mode, and coffer-mode already disable electric reindentation on their side, so we don't need those entries any more. * lisp/org/org.el (org-mode): Don't rely on `electric-indent-functions-without-reindent'. diff --git a/lisp/electric.el b/lisp/electric.el index 39e13e1ca0c..302fb8d08bc 100644 --- a/lisp/electric.el +++ b/lisp/electric.el @@ -212,9 +212,7 @@ Python does not lend itself to fully automatic indentation.") (defvar electric-indent-functions-without-reindent '(indent-relative indent-to-left-margin indent-relative-maybe - indent-relative-first-indent-point py-indent-line coffee-indent-line - org-indent-line yaml-indent-line haskell-indentation-indent-line - haskell-indent-cycle haskell-simple-indent yaml-indent-line) + indent-relative-first-indent-point yaml-indent-line) "List of indent functions that can't reindent. If `indent-line-function' is one of those, then `electric-indent-mode' will not try to reindent lines. It is normally better to make the major diff --git a/lisp/org/org.el b/lisp/org/org.el index 9051bf6c8c9..9e220b2d3df 100644 --- a/lisp/org/org.el +++ b/lisp/org/org.el @@ -4995,8 +4995,9 @@ The following commands are available: ;; Initialize radio targets. (org-update-radio-target-regexp) ;; Indentation. - (setq-local indent-line-function 'org-indent-line) - (setq-local indent-region-function 'org-indent-region) + (setq-local indent-line-function #'org-indent-line) + (setq-local indent-region-function #'org-indent-region) + (setq-local electric-indent-inhibit t) ;; Filling and auto-filling. (org-setup-filling) ;; Comments. commit c16b0d9af3cfa7b853415c0bda997595c574b091 Author: Michael Albinus Date: Tue Sep 23 11:23:11 2025 +0200 ; Instrument file-notify-test07-many-events * test/lisp/filenotify-tests.el (file-notify--test-monitor): Improve instrumentation. diff --git a/test/lisp/filenotify-tests.el b/test/lisp/filenotify-tests.el index 42c4ade7ae3..0c48b112819 100644 --- a/test/lisp/filenotify-tests.el +++ b/test/lisp/filenotify-tests.el @@ -256,29 +256,31 @@ must be a valid watch descriptor." ;; - GPollFileMonitor (gio on cygwin) ;; - SMBSamba (smb-notify on Samba server) ;; - SMBWindows (smb-notify on MS Windows). - (when file-notify--test-desc - (or (alist-get file-notify--test-desc file-notify--test-monitors) - (when (member - (file-notify--test-library) '("gfilenotify" "gio" "smb-notify")) - (add-to-list - 'file-notify--test-monitors - (cons file-notify--test-desc - (if (file-remote-p file-notify--test-rootdir) - ;; `file-notify--test-desc' is the connection process. - (progn - (while (and (process-live-p file-notify--test-desc) - (not (tramp-connection-property-p - file-notify--test-desc "file-monitor"))) - (accept-process-output file-notify--test-desc 0)) - (tramp-get-connection-property - file-notify--test-desc "file-monitor")) - (and (functionp 'gfile-monitor-name) - (gfile-monitor-name file-notify--test-desc))))) - ;; If we don't know the monitor, there are good chances the - ;; test will fail. We skip it. - (unless (alist-get file-notify--test-desc file-notify--test-monitors) - (ert-skip "Cannot determine test monitor"))) - (alist-get file-notify--test-desc file-notify--test-monitors)))) + (if file-notify--test-desc + (or (alist-get file-notify--test-desc file-notify--test-monitors) + (when (member + (file-notify--test-library) '("gfilenotify" "gio" "smb-notify")) + (add-to-list + 'file-notify--test-monitors + (cons file-notify--test-desc + (if (file-remote-p file-notify--test-rootdir) + ;; `file-notify--test-desc' is the connection process. + (progn + (while + (and (process-live-p file-notify--test-desc) + (not (tramp-connection-property-p + file-notify--test-desc "file-monitor"))) + (accept-process-output file-notify--test-desc 0)) + (tramp-get-connection-property + file-notify--test-desc "file-monitor")) + (and (functionp 'gfile-monitor-name) + (gfile-monitor-name file-notify--test-desc))))) + ;; If we don't know the monitor, there are good chances the + ;; test will fail. We skip it. + (unless (alist-get file-notify--test-desc file-notify--test-monitors) + (ert-skip "Cannot determine test monitor"))) + (alist-get file-notify--test-desc file-notify--test-monitors)) + (ert-skip "`file-notify--test-desc' is nil when checking for test monitor"))) (defmacro file-notify--deftest-remote (test docstring &optional unstable) "Define ert `TEST-remote' for remote files. @@ -1168,18 +1170,18 @@ delivered." :tags '(:expensive-test) (skip-unless (file-notify--test-local-enabled)) - (let ((file-notify-debug ;; Temporarily. - (or file-notify-debug - (and (getenv "EMACS_EMBA_CI") - (string-equal (file-notify--test-library) "gio") - (eq (file-notify--test-monitor) 'GInotifyFileMonitor))))) (with-file-notify-test (should (setq file-notify--test-desc (file-notify--test-add-watch file-notify--test-tmpdir '(change) #'file-notify--test-event-handler))) - (let ((n 10);00) + (let ((file-notify-debug ;; Temporarily. + (or file-notify-debug + (and (getenv "EMACS_EMBA_CI") + (string-equal (file-notify--test-library) "gio") + (eq (file-notify--test-monitor) 'GInotifyFileMonitor)))) + (n 10);00) source-file-list target-file-list (default-directory file-notify--test-tmpdir)) (dotimes (i n) @@ -1244,7 +1246,7 @@ delivered." (file-notify--rm-descriptor file-notify--test-desc) ;; The environment shall be cleaned up. - (file-notify--test-cleanup-p))))) + (file-notify--test-cleanup-p)))) (file-notify--deftest-remote file-notify-test07-many-events "Check that events are not dropped for remote directories.") commit bea8b0956888c6878d6c3f5cb570d606c1a365fa Author: Eli Zaretskii Date: Tue Sep 23 09:00:57 2025 +0300 ; * doc/lispref/symbols.texi (Shorthands): Fix markup. diff --git a/doc/lispref/symbols.texi b/doc/lispref/symbols.texi index 532d3b84a97..c6c3e6e189a 100644 --- a/doc/lispref/symbols.texi +++ b/doc/lispref/symbols.texi @@ -772,7 +772,7 @@ There are two exceptions to rules governing Shorthand transformations: @itemize @bullet @item Symbol forms comprised entirely of characters of -the set @code{^*+-/<=>_|} are not transformed. +the set @samp{^*+-/<=>_|} are not transformed. For example, it's possible to use @code{-} or @code{/=} as shorthand prefixes, but that won't shadow the arithmetic @emph{functions} of those names. commit 7b8c17d52704548ff2d2ea706247b1cf52de7d36 Author: Yuan Fu Date: Sat Sep 20 23:36:23 2025 -0700 Fix python-ts-mode font-lock breakage by grammar change (bug#79457) * lisp/progmodes/python.el (python--treesit-fontify-string): Use new algorithm. diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el index e5cc3b0078b..22a4e2c2e5d 100644 --- a/lisp/progmodes/python.el +++ b/lisp/progmodes/python.el @@ -1092,36 +1092,43 @@ NODE is the string node. Do not fontify the initial f for f-strings. OVERRIDE is the override flag described in `treesit-font-lock-rules'. START and END mark the region to be fontified." - (let* ((maybe-expression (treesit-node-parent node)) - (grandparent (treesit-node-parent - (treesit-node-parent - maybe-expression))) - (maybe-defun grandparent) - (face (if (and (or (member (treesit-node-type maybe-defun) - '("function_definition" - "class_definition")) - ;; If the grandparent is null, meaning the - ;; string is top-level, and the string has - ;; no node or only comment preceding it, - ;; it's a BOF docstring. - (and (null grandparent) - (cl-loop - for prev = (treesit-node-prev-sibling - maybe-expression) - then (treesit-node-prev-sibling prev) - while prev - if (not (equal (treesit-node-type prev) - "comment")) - return nil - finally return t))) - ;; This check filters out this case: - ;; def function(): - ;; return "some string" - (equal (treesit-node-type maybe-expression) - "expression_statement")) - 'font-lock-doc-face - 'font-lock-string-face)) - + ;; Criteria for docstring: go up the parse tree until top-level or a + ;; node under function/class, at each level, the node is the first + ;; child (excluding comments). This condition also rules out negative + ;; cases like + ;; + ;; def function(): + ;; return "some string" + ;; + ;; And it recognizes for BOF docstrings, and allows comments before + ;; the docstring. + ;; + ;; Older grammar has function_definition -> block -> expression_statement -> string + ;; Newer grammar has function_definition -> block -> string + ;; This algorithm works for both. + (let* ((cursor node) + (face (catch 'break + (while t + (let ((parent (treesit-node-parent cursor)) + (cursor-idx (treesit-node-index cursor))) + (when (null parent) + (throw 'break 'font-lock-doc-face)) + + (when (and (member (treesit-node-type parent) + '("function_definition" + "class_definition")) + (equal (treesit-node-field-name-for-child + parent cursor-idx) + "body")) + (throw 'break 'font-lock-doc-face)) + + (let ((idx 0)) + (while (< idx cursor-idx) + (unless (equal (treesit-node-type + (treesit-node-child parent idx)) + "comment") + (throw 'break 'font-lock-string-face)))) + (setq cursor parent))))) (ignore-interpolation (not (seq-some (lambda (feats) (memq 'string-interpolation feats)) commit 192a0e177305e896d9e0ebc098be1e4cbb63eece Author: Sean Whitton Date: Mon Sep 22 17:21:38 2025 +0100 ; * lisp/vc/vc-git.el (vc-git--checkin): Fix error message. diff --git a/lisp/vc/vc-git.el b/lisp/vc/vc-git.el index 868948c3a35..e7192e092d9 100644 --- a/lisp/vc/vc-git.el +++ b/lisp/vc/vc-git.el @@ -1144,7 +1144,7 @@ For a regular checkin, FILES is the list of files to check in. To check in a patch, PATCH-STRING is the patch text. It is an error to supply both or neither." (unless (xor files patch-string) - (error "Invalid call to `vc-hg--checkin'")) + (error "Invalid call to `vc-git--checkin'")) (let* ((file1 (or (car files) default-directory)) (root (vc-git-root file1)) (default-directory (expand-file-name root)) commit 85c42b3a87dde7ce9bd375e89dd70fcdc9d03dba Author: Roi Martin Date: Sun Sep 21 20:07:12 2025 +0200 Fix font lock in php-ts-mode Fix font lock in php-ts-mode when the tree-sitter grammars are automatically installed. Also, update php-ts-mode to call the new `mhtml-ts-mode--treesit-font-lock-settings' function. * lisp/progmodes/php-ts-mode.el (php-ts-mode--keywords) (php-ts-mode--operators): Generate the lists after the tree-sitter grammars are installed. (php-ts-mode--font-lock-settings, php-ts-mode--font-lock-settings-cached) (php-ts-mode--custom-html-font-lock-settings) (php-ts-mode--custom-html-font-lock-settings-cached): Evaluate the rules after the tree-sitter grammars are installed. Also, add cache for the rules. (php-ts-mode): Call the new `php-ts-mode--custom-html-font-lock-settings' function. (Bug#79363) diff --git a/lisp/progmodes/php-ts-mode.el b/lisp/progmodes/php-ts-mode.el index 0e3bf324b43..fd5727b0f00 100644 --- a/lisp/progmodes/php-ts-mode.el +++ b/lisp/progmodes/php-ts-mode.el @@ -888,31 +888,29 @@ characters of the current line." "Return t if the operator '|>' is defined, nil otherwise." (treesit-query-valid-p 'php '("|>"))) -(defvar php-ts-mode--keywords - (when (treesit-available-p) - (append - '("abstract" "and" "array" "as" "break" "case" "catch" - "class" "clone" "const" "continue" "declare" "default" "do" "echo" - "else" "elseif" "enddeclare" "endfor" "endforeach" "endif" - "endswitch" "endwhile" "enum" "exit" "extends" "final" "finally" "fn" - "for" "foreach" "function" "global" "goto" "if" "implements" - "include" "include_once" "instanceof" "insteadof" "interface" - "list" "match" "namespace" "new" "null" "or" "print" "private" - "protected" "public" "readonly" "require" "require_once" "return" - "static" "switch" "throw" "trait" "try" "unset" "use" "while" "xor" - "yield") - (if (php-ts-mode--test-yield-from-p) '("yield from") '("from")))) - "PHP keywords for tree-sitter font-locking.") - -(defvar php-ts-mode--operators - (when (treesit-available-p) - (append - '("--" "**=" "*=" "/=" "%=" "+=" "-=" ".=" "<<=" ">>=" "&=" "^=" - "|=" "??" "??=" "||" "&&" "|" "^" "&" "==" "!=" "<>" "===" "!==" - "<" ">" "<=" ">=" "<=>" "<<" ">>" "+" "-" "." "*" "**" "/" "%" - "->" "?->" "...") - (when (php-ts-mode--test-pipe-p) '("|>")))) - "PHP operators for tree-sitter font-locking.") +(defun php-ts-mode--keywords () + "PHP keywords for tree-sitter font-locking." + (append + '("abstract" "and" "array" "as" "break" "case" "catch" + "class" "clone" "const" "continue" "declare" "default" "do" "echo" + "else" "elseif" "enddeclare" "endfor" "endforeach" "endif" + "endswitch" "endwhile" "enum" "exit" "extends" "final" "finally" "fn" + "for" "foreach" "function" "global" "goto" "if" "implements" + "include" "include_once" "instanceof" "insteadof" "interface" + "list" "match" "namespace" "new" "null" "or" "print" "private" + "protected" "public" "readonly" "require" "require_once" "return" + "static" "switch" "throw" "trait" "try" "unset" "use" "while" "xor" + "yield") + (if (php-ts-mode--test-yield-from-p) '("yield from") '("from")))) + +(defun php-ts-mode--operators () + "PHP operators for tree-sitter font-locking." + (append + '("--" "**=" "*=" "/=" "%=" "+=" "-=" ".=" "<<=" ">>=" "&=" "^=" + "|=" "??" "??=" "||" "&&" "|" "^" "&" "==" "!=" "<>" "===" "!==" + "<" ">" "<=" ">=" "<=>" "<<" ">>" "+" "-" "." "*" "**" "/" "%" + "->" "?->" "...") + (when (php-ts-mode--test-pipe-p) '("|>")))) (defconst php-ts-mode--predefined-constant '(;; predefined constant @@ -957,228 +955,244 @@ characters of the current line." ("::" . ?∷)) "Value for `prettify-symbols-alist' in `php-ts-mode'.") -(defun php-ts-mode--font-lock-settings () - "Tree-sitter font-lock settings." - (treesit-font-lock-rules - - :language 'php - :feature 'keyword - :override t - `([,@php-ts-mode--keywords] @font-lock-keyword-face - ,@(when (php-ts-mode--test-visibility-modifier-operation-p) - '((visibility_modifier (operation) @font-lock-builtin-face))) - (var_modifier) @font-lock-builtin-face) - - :language 'php - :feature 'comment - :override t - '((comment) @font-lock-comment-face) - - :language 'php - :feature 'constant - `((boolean) @font-lock-constant-face - (null) @font-lock-constant-face - ;; predefined constant or built in constant (part of PHP core) - ((name) @font-lock-builtin-face - (:match ,(rx-to-string - `(: bos (or ,@php-ts-mode--predefined-constant) eos)) - @font-lock-builtin-face)) - ;; user defined constant - ((name) @font-lock-constant-face - (:match "\\`_*[A-Z][0-9A-Z_]+\\'" @font-lock-constant-face)) - (const_declaration - (const_element (name) @font-lock-constant-face)) - ;; declare directive - (declare_directive ["strict_types" "encoding" "ticks"] @font-lock-constant-face)) - - :language 'php - :feature 'name - '((goto_statement (name) @font-lock-constant-face) - (named_label_statement (name) @font-lock-constant-face)) - - :language 'php - :feature 'delimiter - `((["," ":" ";" "\\"]) @font-lock-delimiter-face) - - :language 'php - :feature 'operator - `((error_suppression_expression "@" @font-lock-keyword-face) - [,@php-ts-mode--operators] @font-lock-operator-face) - - :language 'php - :feature 'variable-name - :override t - '(((name) @font-lock-keyword-face (:equal "this" @font-lock-keyword-face)) - (variable_name (name) @font-lock-variable-name-face) - (relative_scope ["parent" "self" "static"] @font-lock-builtin-face) - (relative_scope) @font-lock-constant-face - (dynamic_variable_name (name) @font-lock-variable-name-face) - (member_access_expression - name: (_) @font-lock-variable-name-face) - (scoped_property_access_expression - scope: (name) @font-lock-constant-face) - (nullsafe_member_access_expression (name) @font-lock-variable-name-face) - (error_suppression_expression (name) @font-lock-property-name-face)) - - :language 'php - :feature 'string - `(("\"") @font-lock-string-face - (encapsed_string) @font-lock-string-face - (string_content) @font-lock-string-face - (string) @font-lock-string-face) - - :language 'php - :feature 'literal - '((integer) @font-lock-number-face - (float) @font-lock-number-face - (heredoc identifier: (heredoc_start) @font-lock-constant-face) - (heredoc_body (string_content) @font-lock-string-face) - (heredoc end_tag: (heredoc_end) @font-lock-constant-face) - (nowdoc identifier: (heredoc_start) @font-lock-constant-face) - (nowdoc_body (nowdoc_string) @font-lock-string-face) - (nowdoc end_tag: (heredoc_end) @font-lock-constant-face) - (shell_command_expression) @font-lock-string-face) - - :language 'php - :feature 'type - :override t - '((union_type "|" @font-lock-operator-face) - (union_type) @font-lock-type-face - (bottom_type) @font-lock-type-face - (primitive_type) @font-lock-type-face - ((primitive_type) @font-lock-keyword-face - (:equal "callable" @font-lock-keyword-face)) - (cast_type) @font-lock-type-face - (named_type) @font-lock-type-face - (optional_type) @font-lock-type-face) - - :language 'php - :feature 'definition - :override t - `((php_tag) @font-lock-preprocessor-face - ,@(if (php-ts-mode--test-php-end-tag-p) - '((php_end_tag) @font-lock-preprocessor-face) - '(("?>") @font-lock-preprocessor-face)) - ;; Highlights identifiers in declarations. - (class_declaration - name: (_) @font-lock-type-face) - (class_interface_clause (name) @font-lock-type-face) - (interface_declaration - name: (_) @font-lock-type-face) - (trait_declaration - name: (_) @font-lock-type-face) - (enum_declaration - name: (_) @font-lock-type-face) - (function_definition - name: (_) @font-lock-function-name-face) - ,@(when (php-ts-mode--test-property-hook-p) - '((property_hook (name) @font-lock-function-name-face))) - (method_declaration - name: (_) @font-lock-function-name-face) - (method_declaration - name: (name) @font-lock-builtin-face - (:match ,(rx-to-string - `(: bos (or ,@php-ts-mode--class-magic-methods) eos)) - @font-lock-builtin-face)) - ("=>") @font-lock-keyword-face - (object_creation_expression - (name) @font-lock-type-face) - ,@(when (php-ts-mode--test-namespace-name-as-prefix-p) - '((namespace_name_as_prefix "\\" @font-lock-delimiter-face) - (namespace_name_as_prefix - (namespace_name (name)) @font-lock-type-face))) - ,@(if (php-ts-mode--test-namespace-aliasing-clause-p) - '((namespace_aliasing_clause (name) @font-lock-type-face)) - '((namespace_use_clause alias: (name) @font-lock-type-face))) - ,@(when (not (php-ts-mode--test-namespace-use-group-clause-p)) - '((namespace_use_group - (namespace_use_clause (name) @font-lock-type-face)))) - (namespace_use_clause (name) @font-lock-type-face) - (namespace_name "\\" @font-lock-delimiter-face) - (namespace_name (name) @font-lock-type-face) - (use_declaration (name) @font-lock-property-use-face) - (use_instead_of_clause (name) @font-lock-type-face) - (binary_expression - operator: "instanceof" - right: (name) @font-lock-type-face)) - - :language 'php - :feature 'function-scope - :override t - '((scoped_call_expression - scope: (name) @font-lock-constant-face) - (class_constant_access_expression (name) @font-lock-constant-face)) +(defvar php-ts-mode--font-lock-settings-cached nil + "Cached tree-sitter font-lock settings for `php-ts-mode'.") - :language 'php - :feature 'function-call - :override t - '((function_call_expression - function: (name) @font-lock-function-call-face) - (scoped_call_expression - name: (name) @font-lock-function-call-face) - (member_call_expression - name: (name) @font-lock-function-call-face) - (nullsafe_member_call_expression - name: (_) @font-lock-function-call-face)) - - :language 'php - :feature 'argument - '((argument - name: (_) @font-lock-constant-face)) - - :language 'php - :feature 'escape-sequence - :override t - '((string (escape_sequence) @font-lock-escape-face) - (encapsed_string (escape_sequence) @font-lock-escape-face) - (heredoc_body (escape_sequence) @font-lock-escape-face)) - - :language 'php - :feature 'base-clause - :override t - `((base_clause (name) @font-lock-type-face) - (use_as_clause (name) @font-lock-property-use-face) - ,@(when (not (php-ts-mode--test-namespace-name-as-prefix-p)) - '((qualified_name prefix: "\\" @font-lock-delimiter-face))) - (qualified_name (name) @font-lock-constant-face) - ,@(when (php-ts-mode--test-relative-name-p) - '((relative_name (name) @font-lock-constant-face)))) - - :language 'php - :feature 'property - '((enum_case - name: (_) @font-lock-type-face)) - - :language 'php - :feature 'attribute - '((((attribute (_) @attribute_name) @font-lock-preprocessor-face) - (:equal "Deprecated" @attribute_name)) - (attribute_group (attribute (name) @font-lock-constant-face))) - - :language 'php - :feature 'bracket - '((["(" ")" "[" "]" "{" "}"]) @font-lock-bracket-face) - - :language 'php - :feature 'error - :override t - '((ERROR) @php-ts-mode--fontify-error))) +(defun php-ts-mode--font-lock-settings () + "Return tree-sitter font-lock settings for `php-ts-mode'. + +Tree-sitter font-lock settings are evaluated the first time this +function is called. Subsequent calls return the first evaluated value." + (or php-ts-mode--font-lock-settings-cached + (setq php-ts-mode--font-lock-settings-cached + (treesit-font-lock-rules + + :language 'php + :feature 'keyword + :override t + `([,@(php-ts-mode--keywords)] @font-lock-keyword-face + ,@(when (php-ts-mode--test-visibility-modifier-operation-p) + '((visibility_modifier (operation) @font-lock-builtin-face))) + (var_modifier) @font-lock-builtin-face) + + :language 'php + :feature 'comment + :override t + '((comment) @font-lock-comment-face) + + :language 'php + :feature 'constant + `((boolean) @font-lock-constant-face + (null) @font-lock-constant-face + ;; predefined constant or built in constant (part of PHP core) + ((name) @font-lock-builtin-face + (:match ,(rx-to-string + `(: bos (or ,@php-ts-mode--predefined-constant) eos)) + @font-lock-builtin-face)) + ;; user defined constant + ((name) @font-lock-constant-face + (:match "\\`_*[A-Z][0-9A-Z_]+\\'" @font-lock-constant-face)) + (const_declaration + (const_element (name) @font-lock-constant-face)) + ;; declare directive + (declare_directive ["strict_types" "encoding" "ticks"] @font-lock-constant-face)) + + :language 'php + :feature 'name + '((goto_statement (name) @font-lock-constant-face) + (named_label_statement (name) @font-lock-constant-face)) + + :language 'php + :feature 'delimiter + `((["," ":" ";" "\\"]) @font-lock-delimiter-face) + + :language 'php + :feature 'operator + `((error_suppression_expression "@" @font-lock-keyword-face) + [,@(php-ts-mode--operators)] @font-lock-operator-face) + + :language 'php + :feature 'variable-name + :override t + '(((name) @font-lock-keyword-face (:equal "this" @font-lock-keyword-face)) + (variable_name (name) @font-lock-variable-name-face) + (relative_scope ["parent" "self" "static"] @font-lock-builtin-face) + (relative_scope) @font-lock-constant-face + (dynamic_variable_name (name) @font-lock-variable-name-face) + (member_access_expression + name: (_) @font-lock-variable-name-face) + (scoped_property_access_expression + scope: (name) @font-lock-constant-face) + (nullsafe_member_access_expression (name) @font-lock-variable-name-face) + (error_suppression_expression (name) @font-lock-property-name-face)) + + :language 'php + :feature 'string + `(("\"") @font-lock-string-face + (encapsed_string) @font-lock-string-face + (string_content) @font-lock-string-face + (string) @font-lock-string-face) + + :language 'php + :feature 'literal + '((integer) @font-lock-number-face + (float) @font-lock-number-face + (heredoc identifier: (heredoc_start) @font-lock-constant-face) + (heredoc_body (string_content) @font-lock-string-face) + (heredoc end_tag: (heredoc_end) @font-lock-constant-face) + (nowdoc identifier: (heredoc_start) @font-lock-constant-face) + (nowdoc_body (nowdoc_string) @font-lock-string-face) + (nowdoc end_tag: (heredoc_end) @font-lock-constant-face) + (shell_command_expression) @font-lock-string-face) + + :language 'php + :feature 'type + :override t + '((union_type "|" @font-lock-operator-face) + (union_type) @font-lock-type-face + (bottom_type) @font-lock-type-face + (primitive_type) @font-lock-type-face + ((primitive_type) @font-lock-keyword-face + (:equal "callable" @font-lock-keyword-face)) + (cast_type) @font-lock-type-face + (named_type) @font-lock-type-face + (optional_type) @font-lock-type-face) + + :language 'php + :feature 'definition + :override t + `((php_tag) @font-lock-preprocessor-face + ,@(if (php-ts-mode--test-php-end-tag-p) + '((php_end_tag) @font-lock-preprocessor-face) + '(("?>") @font-lock-preprocessor-face)) + ;; Highlights identifiers in declarations. + (class_declaration + name: (_) @font-lock-type-face) + (class_interface_clause (name) @font-lock-type-face) + (interface_declaration + name: (_) @font-lock-type-face) + (trait_declaration + name: (_) @font-lock-type-face) + (enum_declaration + name: (_) @font-lock-type-face) + (function_definition + name: (_) @font-lock-function-name-face) + ,@(when (php-ts-mode--test-property-hook-p) + '((property_hook (name) @font-lock-function-name-face))) + (method_declaration + name: (_) @font-lock-function-name-face) + (method_declaration + name: (name) @font-lock-builtin-face + (:match ,(rx-to-string + `(: bos (or ,@php-ts-mode--class-magic-methods) eos)) + @font-lock-builtin-face)) + ("=>") @font-lock-keyword-face + (object_creation_expression + (name) @font-lock-type-face) + ,@(when (php-ts-mode--test-namespace-name-as-prefix-p) + '((namespace_name_as_prefix "\\" @font-lock-delimiter-face) + (namespace_name_as_prefix + (namespace_name (name)) @font-lock-type-face))) + ,@(if (php-ts-mode--test-namespace-aliasing-clause-p) + '((namespace_aliasing_clause (name) @font-lock-type-face)) + '((namespace_use_clause alias: (name) @font-lock-type-face))) + ,@(when (not (php-ts-mode--test-namespace-use-group-clause-p)) + '((namespace_use_group + (namespace_use_clause (name) @font-lock-type-face)))) + (namespace_use_clause (name) @font-lock-type-face) + (namespace_name "\\" @font-lock-delimiter-face) + (namespace_name (name) @font-lock-type-face) + (use_declaration (name) @font-lock-property-use-face) + (use_instead_of_clause (name) @font-lock-type-face) + (binary_expression + operator: "instanceof" + right: (name) @font-lock-type-face)) + + :language 'php + :feature 'function-scope + :override t + '((scoped_call_expression + scope: (name) @font-lock-constant-face) + (class_constant_access_expression (name) @font-lock-constant-face)) + + :language 'php + :feature 'function-call + :override t + '((function_call_expression + function: (name) @font-lock-function-call-face) + (scoped_call_expression + name: (name) @font-lock-function-call-face) + (member_call_expression + name: (name) @font-lock-function-call-face) + (nullsafe_member_call_expression + name: (_) @font-lock-function-call-face)) + + :language 'php + :feature 'argument + '((argument + name: (_) @font-lock-constant-face)) + + :language 'php + :feature 'escape-sequence + :override t + '((string (escape_sequence) @font-lock-escape-face) + (encapsed_string (escape_sequence) @font-lock-escape-face) + (heredoc_body (escape_sequence) @font-lock-escape-face)) + + :language 'php + :feature 'base-clause + :override t + `((base_clause (name) @font-lock-type-face) + (use_as_clause (name) @font-lock-property-use-face) + ,@(when (not (php-ts-mode--test-namespace-name-as-prefix-p)) + '((qualified_name prefix: "\\" @font-lock-delimiter-face))) + (qualified_name (name) @font-lock-constant-face) + ,@(when (php-ts-mode--test-relative-name-p) + '((relative_name (name) @font-lock-constant-face)))) + + :language 'php + :feature 'property + '((enum_case + name: (_) @font-lock-type-face)) + + :language 'php + :feature 'attribute + '((((attribute (_) @attribute_name) @font-lock-preprocessor-face) + (:equal "Deprecated" @attribute_name)) + (attribute_group (attribute (name) @font-lock-constant-face))) + + :language 'php + :feature 'bracket + '((["(" ")" "[" "]" "{" "}"]) @font-lock-bracket-face) + + :language 'php + :feature 'error + :override t + '((ERROR) @php-ts-mode--fontify-error))))) ;;; Font-lock helpers -(defconst php-ts-mode--custom-html-font-lock-settings - (treesit-replace-font-lock-feature-settings - (treesit-font-lock-rules - :language 'html - :override t - :feature 'comment - '((comment) @font-lock-comment-face - ;; handle shebang path and others type of comment - (document (text) @font-lock-comment-face))) - mhtml-ts-mode--treesit-font-lock-settings) +(defvar php-ts-mode--custom-html-font-lock-settings-cached nil + "Cached tree-sitter font-lock settings for HTML when embedded in PHP.") + +(defun php-ts-mode--custom-html-font-lock-settings () "Tree-sitter Font-lock settings for HTML when embedded in PHP. -Like `mhtml-ts-mode--font-lock-settings' but adapted for `php-ts-mode'.") +Like `mhtml-ts-mode--font-lock-settings' but adapted for `php-ts-mode'. + +Tree-sitter font-lock settings are evaluated the first time this +function is called. Subsequent calls return the first evaluated value." + (or php-ts-mode--custom-html-font-lock-settings-cached + (setq php-ts-mode--custom-html-font-lock-settings-cached + (treesit-replace-font-lock-feature-settings + (treesit-font-lock-rules + :language 'html + :override t + :feature 'comment + '((comment) @font-lock-comment-face + ;; handle shebang path and others type of comment + (document (text) @font-lock-comment-face))) + (mhtml-ts-mode--treesit-font-lock-settings))))) (defvar php-ts-mode--phpdoc-font-lock-settings (treesit-font-lock-rules @@ -1676,7 +1690,7 @@ If FORCE is t setup comment for PHP. Depends on (setq-local treesit-font-lock-settings (append (php-ts-mode--font-lock-settings) - php-ts-mode--custom-html-font-lock-settings + (php-ts-mode--custom-html-font-lock-settings) php-ts-mode--phpdoc-font-lock-settings)) (setq-local treesit-font-lock-feature-list php-ts-mode--feature-list) commit 77ca60b48d018425c95c110b4c736cddd2d1d336 Author: Stephen Berman Date: Mon Sep 22 16:04:42 2025 +0200 Navigate *Completions* buffer based on 'completions-format' This patch makes 'next-completion' and 'previous-completion' work in the vertical completions format analogously to how they work in the default horizontal format (bug#78959). It also fixes wrapping in the vertical format and confines navigation (including wrapping) in column-wise movement in the vertical format to the current line, analogously to how navigation (including wrapping) in line-wise movement in the horizontal format is confined to the current column. * doc/emacs/mini.texi (Completion): Fix several typos and improve wording is several places. (Completion Commands): Document navigation of the *Completions* buffer in the vertical format. Document the difference between format-sensitive movement and strictly column-wise or line-wise movement. Document 'minibuffer-complete-and-exit' and update the documentation of 'minibuffer-completion-auto-choose' and 'minibuffer-choose-completion'. Document the use of a numeric prefix argument with the navigation commands. (Completion Options): Rearrange and improve documentation of 'completions-sort', 'completions-format' and 'completion-auto-wrap', updating the latter to document the new behavior. * lisp/minibuffer.el (minibuffer-visible-completions-map): Rebind "" to 'minibuffer-previous-column-completion' and "" to 'minibuffer-next-column-completion'. (minibuffer-next-completion): Add check for whether completions format is vertical to decide whether to call 'next-line-completion' and replace calling 'next-completion' by 'next-column-completion'. (minibuffer-next-column-completion) (minibuffer-previous-column-completion): New commands. * lisp/simple.el (completion-list-mode-map): Rebind "" to 'previous-column-completion' and "" to 'next-column-completion'. (last-completion): Add handling for vertical completions format. (completion--move-to-candidate-end): Always move point to the position immediately after the last character of the completion candidate. This unifies the behavior, simplifies the implementation and facilitates implementing the improved navigation of the *Completions* buffer. (previous-column-completion, next-column-completion): New commands, replacing the previous definitions of 'previous-completion' and 'next-completion' to reflect their column-wise operation. Confine navigation (including wrapping) in vertical format to the current line. (previous-line-completion, next-line-completion): Implement line-wise navigation (including wrapping) through all completions in vertical format, not just those in the current column as in horiztonal format. Update doc strings. (next-completion, previous-completion): Redefine to call '{next,previous}-line-completion' when completions format is vertical and '{next,previous}-column-completion' otherwise. * test/lisp/minibuffer-tests.el (completions-format-navigation--tests): New function providing a template to define tests of the navigation and wrapping behavior with specified numbers of completion candidates. (completions-format-navigation-test-{2,3,4,5,10,15,16}): New tests. diff --git a/doc/emacs/mini.texi b/doc/emacs/mini.texi index c1b11f1ab1c..a9dfefc58f8 100644 --- a/doc/emacs/mini.texi +++ b/doc/emacs/mini.texi @@ -276,9 +276,11 @@ can fill in the rest, or some of it, based on what was typed so far. @key{RET}, and @key{SPC}) are rebound in the minibuffer to special completion commands (@pxref{Completion Commands}). These commands attempt to complete the text in the minibuffer, based on a set of -@dfn{completion alternatives} provided by the command that requested -the argument. You can usually type @kbd{?} to see a list of -completion alternatives. +@dfn{completion alternatives} provided by the command that requested the +argument. You can usually type @kbd{?} to have Emacs pop up a buffer +(named @samp{*Completions*}) displaying the relevant completion +alternatives. As described in more detail below, you can navigate this +buffer and choose the desired completion from it. Although completion is usually done in the minibuffer, the feature is sometimes available in ordinary buffers too. @xref{Symbol @@ -336,8 +338,8 @@ at the end of the minibuffer, so that the minibuffer contains @node Completion Commands @subsection Completion Commands - Here is a list of the completion commands defined in the minibuffer -when completion is allowed. + Here is a list of the completion commands defined in the minibuffer or +the completions buffer when completion is available. @table @kbd @item @key{TAB} @@ -357,7 +359,7 @@ Display a list of completions and a few useful key bindings (@code{minibuffer-completion-help}). @item M-@key{DOWN} @itemx M-@key{UP} -Navigate through list of completions. +Navigate through the list of completions. @item M-v @itemx M-g M-c @itemx @key{PageUp} @@ -369,13 +371,17 @@ In the completions buffer, choose the completion at point. @itemx mouse-2 In the completions buffer, choose the completion at mouse click. @item @key{TAB} -@itemx @key{RIGHT} -@itemx @key{n} +@itemx n In the completions buffer, move to the following completion candidate. -@item @key{S-TAB} -@itemx @key{LEFT} -@itemx @key{p} +@item S-@key{TAB} +@itemx p In the completions buffer, move to the previous completion candidate. +@item @key{RIGHT} +@itemx @key{LEFT} +In the completions buffer, navigate column-wise through the completion list. +@item @key{DOWN} +@itemx @key{UP} +In the completions buffer, navigate line-wise through the completion list. @item q Quit the completions window and switch to the minibuffer window. @item z @@ -416,19 +422,24 @@ with the completion list: @findex minibuffer-next-completion @findex minibuffer-previous-completion @findex minibuffer-choose-completion -While in the minibuffer or in the completion list buffer, @kbd{M-@key{DOWN}} -(@code{minibuffer-next-completion} and @kbd{M-@key{UP}} -(@code{minibuffer-previous-completion}) navigate through the -completions displayed in the completions buffer. When -@code{minibuffer-completion-auto-choose} is non-@code{nil} (which is -the default), using these commands also inserts the current completion -candidate into the minibuffer. If -@code{minibuffer-completion-auto-choose} is @code{nil}, you can use -the @kbd{M-@key{RET}} command (@code{minibuffer-choose-completion}) to -insert the completion candidates into the minibuffer. By default, -that exits the minibuffer, but with a prefix argument, @kbd{C-u -M-@key{RET}} inserts the currently active candidate without exiting -the minibuffer. +@findex minibuffer-complete-and-exit +While in the minibuffer or in the completion list buffer, +@kbd{M-@key{DOWN}} (@code{minibuffer-next-completion}) and +@kbd{M-@key{UP}} (@code{minibuffer-previous-completion}) navigate +through the completions displayed in the completions buffer. These +commands are sensitive to the completion list format: they move point +column-wise when the value of @code{completions-format} is +@code{horizontal} and line-wise when its value is @code{vertical} +(@pxref{Completion Options}). If you set +@code{minibuffer-completion-auto-choose} to non-@code{nil}, using these +commands also inserts the current completion candidate into the +minibuffer. You can use @kbd{M-@key{RET}} +(@code{minibuffer-choose-completion}) or @kbd{@key{RET}} +(@code{minibuffer-complete-and-exit}) to choose the selected completion +candidate. By default, these commands exit the minibuffer, but with a +prefix argument (that is, @kbd{C-u M-@key{RET}} or @kbd{C-u @key{RET}}) +they insert the currently selected candidate into the minibuffer without +exiting it. @findex switch-to-completions Typing @kbd{M-v}, while in the minibuffer, selects the window showing @@ -446,11 +457,27 @@ minibuffer, but doesn't exit the minibuffer---thus, you can change your mind and choose another candidate. @findex next-completion -While in the completion list buffer, you can use @kbd{@key{TAB}}, -@kbd{@key{RIGHT}}, or @kbd{n} to move point to the following completion -candidate (@code{next-completion}). You can also use @kbd{@key{S-TAB}}, -@kbd{@key{LEFT}}, and @kbd{p} to move point to the previous completion -alternative (@code{previous-completion}). +@findex previous-completion +While in the completion list buffer, you can use @kbd{@key{TAB}} or +@kbd{n} to move point to the following completion candidate +(@code{next-completion}) and @kbd{S-@key{TAB}} or @kbd{p} to move point +to the previous completion alternative (@code{previous-completion}). +Like @kbd{M-@key{DOWN}} and @kbd{M-@key{UP}}, these commands are also +sensitive to the completion list format. + +@findex next-column-completion +@findex next-line-completion +@findex previous-column-completion +@findex previous-line-completion +In contrast, @kbd{@key{RIGHT}} (@code{next-column-completion}) and +@kbd{@key{LEFT}} (@code{previous-column-completion}) always move point +column-wise, regardless of the completion list format. Likewise, +@kbd{@key{DOWN}} (@code{next-line-completion}) and @kbd{@key{UP}} +(@code{previous-line-completion}) always move point line-wise. + + All of these movement commands (and also @kbd{M-@key{DOWN}} and +@kbd{M-@key{UP}}) accept a numeric prefix argument @var{n}, which makes +point move to the @var{n}th following or preceding completion candidate. @findex minibuffer-complete-history @findex minibuffer-complete-defaults @@ -475,10 +502,14 @@ completion buffer and delete the window showing it If the variable @code{minibuffer-visible-completions} is customized to a non-@code{nil} value, it changes the commands bound to the arrow keys: instead of moving in the minibuffer, they move between completion -candidates, like meta-arrow keys do by default. Similarly, -@kbd{@key{RET}} selects the current candidate, like @kbd{M-@key{RET}} -does normally. @code{C-g} hides the completion window, but leaves the -minibuffer active, so you can continue typing at the prompt. +candidates, like meta-arrow keys do by default (but note that, just as +when the window showing the completion list is selected, here too, +@kbd{@key{RIGHT}} and @kbd{@key{LEFT}} only move point column-wise and +@kbd{@key{DOWN}} and @kbd{@key{UP}} only move point line-wise, +regardless of the completion list format). Similarly, @kbd{@key{RET}} +selects the current candidate, like @kbd{M-@key{RET}} does normally. +@code{C-g} hides the completion window, but leaves the minibuffer +active, so you can continue typing at the prompt. @node Completion Exit @subsection Completion Exit @@ -725,18 +756,6 @@ then move to a candidate by cursor motion commands and select it with @code{second-tab}, then the first @kbd{@key{TAB}} will pop up the completions list buffer, and the second one will switch to it. -@findex previous-line-completion -@findex next-line-completion -@vindex completion-auto-wrap - When the window showing the completions is selected, either because -you customized @code{completion-auto-select} or because you switched to -it by typing @kbd{C-x o}, the @kbd{@key{UP}} and @kbd{@key{DOWN}} arrow -keys (@code{previous-line-completion} and @code{next-line-completion}, -respectively) move by lines between completion candidates; with a prefix -numeric argument, they move that many lines. If -@code{completion-auto-wrap} is non-@code{nil}, these commands will wrap -at bottom and top of the candidate list. - @vindex completion-cycle-threshold If @code{completion-cycle-threshold} is non-@code{nil}, completion commands can cycle through completion alternatives. Normally, if @@ -750,24 +769,57 @@ in a cyclic manner. If you give @code{completion-cycle-threshold} a numeric value @var{n}, completion commands switch to this cycling behavior only when there are @var{n} or fewer alternatives. +@vindex completions-sort + The user option @code{completions-sort} controls the order in which +the completions are sorted in the @samp{*Completions*} buffer. With the +default value @code{alphabetical}, the completions are displayed in +alphabetical order. The value @code{nil} disables sorting; the value +@code{historical} sorts alphabetically first, and then rearranges +according to the order of the candidates in the minibuffer history. The +value can also be a function, which will be called with the list of +completions, and should return the list in the desired order. + @vindex completions-format - When displaying completions, Emacs will normally pop up a new buffer -to display the completions. The completions will by default be sorted -horizontally, using as many columns as will fit in the window-width, -but this can be changed by customizing the @code{completions-format} -user option. If its value is @code{vertical}, Emacs will sort the -completions vertically instead, and if it's @code{one-column}, Emacs -will use just one column. + By default, the @samp{*Completions*} buffer lists the sorted +completions horizontally (that is, from left to right across the columns +and continuing to the next line below), using as many columns as will +fit in the window-width. You can change this by customizing the user +option @code{completions-format}. If its value is @code{vertical}, the +@samp{*Completions*} buffer lists the sorted completions vertically +(that is, within each column and continuing with the next column to the +left), and with the value @code{one-column}, all completions appear in a +single column.@footnote{If a completion candidate is too long for more +than one column to fit in the window, or it there are three or less +candidates, all completions appear in a single column even if the value +of @code{completions-format} is @code{horizontal} or @code{vertical}.} -@vindex completions-sort - The @code{completions-sort} user option controls the order in which -the completions are sorted in the @samp{*Completions*} buffer. The -default is @code{alphabetical}, which sorts in alphabetical order. -The value @code{nil} disables sorting; the value @code{historical} -sorts alphabetically first, and then rearranges according to the order -of the candidates in the minibuffer history. The value can also be a -function, which will be called with the list of completions, and -should return the list in the desired order. +@vindex completion-auto-wrap + When point is on the last completion candidate, typing a key that +moves point to the next candidate (according to the sorting order of +current format) ``wraps around'' by default, that is, moves point to the +first candidate in the completions buffer. Likewise, when point is on +the first candidate, typing a key to move to the previous candidate puts +point on the last candidate in the buffer. You can suppress this +wrapping around by customizing the user option +@code{completion-auto-wrap} to @code{nil}; then typing @kbd{@key{TAB}} +or @kbd{n} on the last candidate, or typing @kbd{S-@key{TAB}} or @kbd{p} +on the first candidate, does not move point. + +With the arrow keys, the wrapping behavior depends on the completions +format in use. In the horizontal format, you can use @kbd{@key{RIGHT}} +and @kbd{@key{LEFT}}, and in the vertical format @kbd{@key{DOWN}} and +@kbd{@key{UP}}, exactly like the format-sensitive keys for moving +between completion candidates in the sorting order, and with the same +wrapping behavior. In contrast, in the horizontal format +@kbd{@key{DOWN}} and @kbd{@key{UP}} move---and wrap, if wrapping is +enabled---only within the current column of completions, and in the +vertical format @kbd{@key{RIGHT}} and @kbd{@key{LEFT}} move (and wrap, +if enabled) only within the current line of completions.@footnote{If the +completions buffer contains only single column even in the horizontal or +vertical format, then both the format-sensitive keys and the arrow keys +move point to the next (respectively, previous) candidate, wrapping only +at the last (respectively, first) candidate, if the value of +@code{completion-auto-wrap} is non-@code{nil}.} @vindex completions-max-height When @code{completions-max-height} is non-@code{nil}, it limits the diff --git a/etc/NEWS b/etc/NEWS index d3e25bf5d5d..602c375dc2f 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -216,6 +216,23 @@ different values for completion-affecting variables like applies for the styles configuration in 'completion-category-overrides' and 'completion-category-defaults'. ++++++ +*** Navigating "*Completions*" now accommodates 'completions-format'. +When 'completions-format' is set to 'vertical', typing 'n', '' or +'M-' in the "*Completions*" buffer (the latter also in the +minibuffer) now moves point to the completion candidate in the next line +in the current column, and wraps to the next column when typed on the +last completion candidate of the current column. Likewise, typing 'p', +'S-' or 'M-' moves point to the completion candidate in the +previous line or wraps to the previous column. Previously, these keys +ignored the vertical format, i.e., moved point only to the item in the +same line of the next or previous column, in accordance with the default +horizontal format. In vertical format, typing '' and '' in +the "*Completions*" buffer (and when 'minibuffer-visible-completions' is +non-nil, also in the minibuffer) moves point only within the current +line, analogously to how, in horizontal format, '' and '' move +point only within the current column. + --- *** Selected completion candidate is preserved across "*Completions*" updates. When point is on a completion candidate in the "*Completions*" buffer @@ -2936,6 +2953,19 @@ by default and controlled by this variable; it can be set to non-nil to keep the old behavior. This change is to accomodate screen readers. +--- +** 'next-completion' and 'previous-completion' now use 'completions-format'. +Previously, these commands only took horizontal format into account; +now, they call either '{next,previous}-line-completion' or the new +commands '{next,previous}-column-completion', depending on the value of +'completions-format'. The latter two commands improve and extend the +previous implementations of '{next,previous}-completion', which better +reflect that they only take the (default) horizontal completions format +into account. Any external code using '{next,previous}-completion' that +assumes the previous implementation must be adjusted accordingly; see +'minibuffer-next-completion' for an example of such an adjustment in +Emacs core. + +++ ** A thread's current buffer can now be killed. We introduce a new attribute for threads called 'buffer-disposition'. diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el index 70ad0c7abc5..ccd2314fac7 100644 --- a/lisp/minibuffer.el +++ b/lisp/minibuffer.el @@ -3440,8 +3440,8 @@ displaying the *Completions* buffer exists." (defvar-keymap minibuffer-visible-completions-map :doc "Local keymap for minibuffer input with visible completions." - "" (minibuffer-visible-completions--bind #'minibuffer-previous-completion) - "" (minibuffer-visible-completions--bind #'minibuffer-next-completion) + "" (minibuffer-visible-completions--bind #'minibuffer-previous-column-completion) + "" (minibuffer-visible-completions--bind #'minibuffer-next-column-completion) "" (minibuffer-visible-completions--bind #'minibuffer-previous-line-completion) "" (minibuffer-visible-completions--bind #'minibuffer-next-line-completion) "C-g" (minibuffer-visible-completions--bind #'minibuffer-hide-completions)) @@ -5224,15 +5224,15 @@ selected by these commands to the minibuffer." "Move to the next item in its completions window from the minibuffer. When the optional argument VERTICAL is non-nil, move vertically to the next item on the next line using `next-line-completion'. -Otherwise, move to the next item horizontally using `next-completion'. +Otherwise, move to the next item horizontally using `next-column-completion'. When `minibuffer-completion-auto-choose' is non-nil, then also insert the selected completion candidate to the minibuffer." (interactive "p") (let ((auto-choose minibuffer-completion-auto-choose)) (with-minibuffer-completions-window - (if vertical + (if (or vertical (eq completions-format 'vertical)) (next-line-completion (or n 1)) - (next-completion (or n 1))) + (next-column-completion (or n 1))) (when auto-choose (let ((completion-auto-deselect nil)) (choose-completion nil t t)))))) @@ -5262,6 +5262,26 @@ insert the selected completion candidate to the minibuffer." (interactive "p") (minibuffer-next-completion (- (or n 1)) t)) +(defun minibuffer-next-column-completion (&optional n) + "Move to the next completion column from the minibuffer. +This means to move to the completion candidate in the next column +in the *Completions* buffer while point stays in the minibuffer. +When `minibuffer-completion-auto-choose' is non-nil, then also +insert the selected completion candidate to the minibuffer." + (interactive "p") + (with-minibuffer-completions-window + (next-column-completion (or n 1)))) + +(defun minibuffer-previous-column-completion (&optional n) + "Move to the previous completion column from the minibuffer. +This means to move to the completion candidate on the previous column +in the *Completions* buffer while point stays in the minibuffer. +When `minibuffer-completion-auto-choose' is non-nil, then also +insert the selected completion candidate to the minibuffer." + (interactive "p") + (with-minibuffer-completions-window + (next-column-completion (- (or n 1))))) + (defun minibuffer-choose-completion (&optional no-exit no-quit) "Run `choose-completion' from the minibuffer in its completions window. With prefix argument NO-EXIT, insert the completion candidate at point to diff --git a/lisp/simple.el b/lisp/simple.el index 2a13d59e5cd..fe5eb4da60a 100644 --- a/lisp/simple.el +++ b/lisp/simple.el @@ -10068,8 +10068,8 @@ makes it easier to edit it." (define-key map [remap keyboard-quit] #'delete-completion-window) (define-key map [up] 'previous-line-completion) (define-key map [down] 'next-line-completion) - (define-key map [left] 'previous-completion) - (define-key map [right] 'next-completion) + (define-key map [left] 'previous-column-completion) + (define-key map [right] 'next-column-completion) (define-key map [?\t] 'next-completion) (define-key map [backtab] 'previous-completion) (define-key map [M-up] 'minibuffer-previous-completion) @@ -10159,21 +10159,54 @@ the completions is popped up and down." (defun last-completion () "Move to the last item in the completions buffer." (interactive) + ;; Move to the last item in horizontal or one-column format. (goto-char (previous-single-property-change (point-max) 'mouse-face nil (point-min))) - ;; Move to the start of last one. + ;; Move to the start of the item. (unless (get-text-property (point) 'mouse-face) (when-let* ((pos (previous-single-property-change (point) 'mouse-face))) - (goto-char pos)))) - -(defun previous-completion (n) - "Move to the previous item in the completions buffer. -With prefix argument N, move back N items (negative N means move + (goto-char pos))) + ;; In vertical format the last item is in the last column even if its + ;; line number is less than that of the last item in earlier columns. + (when (eq completions-format 'vertical) + (let ((pt (point)) + (col (current-column)) + (last-col (progn + (first-completion) + (goto-char (pos-eol)) + (goto-char (previous-single-property-change + (point) 'mouse-face)) + (current-column)))) + (if (zerop last-col) + ;; If there is only one column of completions, the last + ;; completion in vertical format is the same as in horizontal + ;; format, so go there now. + (goto-char pt) + ;; Otherwise, we set `pt' to the beginning of first item in last + ;; column here because if the last column contains only one + ;; item, `pt' will not be set below.) + (setq pt (point)) + ;; If all columns contain the same number of items, `col' (which + ;; specifies the column of the last item in horizontal format) + ;; equals `last-col', so the test must be with `>=', not `>'. + (when (>= last-col col) + (while (= (current-column) last-col) + (forward-line) + (unless (eobp) + (goto-char (pos-eol)) + (move-to-column last-col) + (when (= (current-column) last-col) + (setq pt (point)))))) + (goto-char pt))))) + +(defun previous-column-completion (n) + "Move to the item in the previous column of the completions buffer. +With prefix argument N, move back N columns (negative N means move forward). Also see the `completion-auto-wrap' variable." (interactive "p") - (next-completion (- n))) + (next-column-completion (- n))) (defun completion--move-to-candidate-start () "If in a completion candidate, move point to its start." @@ -10183,21 +10216,26 @@ Also see the `completion-auto-wrap' variable." (goto-char (previous-single-property-change (point) 'mouse-face)))) (defun completion--move-to-candidate-end () - "If in a completion candidate, move point to its end." - (when (and (get-text-property (point) 'mouse-face) - (not (eobp)) - (get-text-property (1+ (point)) 'mouse-face)) - (goto-char (or (next-single-property-change (point) 'mouse-face) (point-max))))) - -(defun next-completion (n) - "Move to the next item in the completions buffer. -With prefix argument N, move N items (negative N means move + "If in a completion candidate, move point to its end. +More precisely, point moves the the position immediately after the last +character of the completion candidate." + (when (get-text-property (point) 'mouse-face) + (goto-char (or (next-single-property-change (point) 'mouse-face) + (point-max))))) + +(defun next-column-completion (n) + "Move to the item in the next column of the completions buffer. +With prefix argument N, move N columns (negative N means move backward). Also see the `completion-auto-wrap' variable." (interactive "p") (let ((tabcommand (member (this-command-keys) '("\t" [backtab]))) - pos) + (one-col (save-excursion + (first-completion) + (completion--move-to-candidate-end) + (eolp))) + pos line last first) (catch 'bound (when (and (bobp) (> n 0) @@ -10208,32 +10246,61 @@ Also see the `completion-auto-wrap' variable." (setq n (1- n))) (while (> n 0) - (setq pos (point)) + (setq pos (point) line (line-number-at-pos) + last (if one-col + (save-excursion (and (forward-line) (eobp))) + (save-excursion (completion--move-to-candidate-end) (eolp)))) ;; If in a completion, move to the end of it. (when (get-text-property pos 'mouse-face) (setq pos (next-single-property-change pos 'mouse-face))) (when pos (setq pos (next-single-property-change pos 'mouse-face))) - (if pos + (if (and pos + (if last + (not (eq completions-format 'vertical)) + t)) ;; Move to the start of next one. (goto-char pos) ;; If at the last completion option, wrap or skip ;; to the minibuffer, if requested. - (when completion-auto-wrap + (when (and completion-auto-wrap + (or one-col + (not (eq completions-format 'vertical)))) (if (and (eq completion-auto-select t) tabcommand (minibufferp completion-reference-buffer)) (throw 'bound nil) (first-completion)))) + (when (and (eq completions-format 'vertical) + (or last + (= (point) (save-excursion (first-completion) (point))))) + (if (> (line-number-at-pos) line) + (forward-line -1) + (when completion-auto-wrap + (goto-char (pos-bol)) + (completion--move-to-candidate-start)))) (setq n (1- n))) (while (< n 0) - (setq pos (point)) + (setq pos (point) line (line-number-at-pos) + first (if one-col + (save-excursion + (forward-line -1) + (not (get-text-property (point) 'mouse-face))) + (save-excursion (completion--move-to-candidate-start) + (bolp)))) ;; If in a completion, move to the start of it. (when (and (get-text-property pos 'mouse-face) (not (bobp)) (get-text-property (1- pos) 'mouse-face)) (setq pos (previous-single-property-change pos 'mouse-face))) (when pos (setq pos (previous-single-property-change pos 'mouse-face))) - (if pos + (if (and pos + (not (and completion-auto-wrap + (eq completions-format 'vertical) + (not one-col) + (bolp))) + (if first + (not (eq completions-format 'vertical)) + t)) (progn (goto-char pos) ;; Move to the start of that one. @@ -10243,11 +10310,19 @@ Also see the `completion-auto-wrap' variable." ;; If at the first completion option, wrap or skip ;; to the minibuffer, if requested. (when completion-auto-wrap - (if (and (eq completion-auto-select t) tabcommand - (minibufferp completion-reference-buffer)) - (progn - (throw 'bound nil)) - (last-completion)))) + (cond ((and (eq completions-format 'vertical) + (not one-col) + (or first (not pos))) + (when (> line (line-number-at-pos)) + (forward-line)) + (goto-char (1- (pos-eol))) + (completion--move-to-candidate-start)) + ((and (eq completion-auto-select t) tabcommand + (minibufferp completion-reference-buffer)) + (progn + (throw 'bound nil))) + (t + (last-completion))))) (setq n (1+ n)))) (when (/= 0 n) @@ -10255,7 +10330,11 @@ Also see the `completion-auto-wrap' variable." (defun previous-line-completion (&optional n) "Move to completion candidate on the previous line in the completions buffer. -With prefix argument N, move back N lines (negative N means move forward). +With prefix argument N, move back N lines (negative N means move +forward). In vertical format (see user option `completions-format') +this command moves line-wise through all columns in the completions +buffer, in horizontal format movement is confined to the current column +of completions. Also see the `completion-auto-wrap' variable." (interactive "p") @@ -10263,11 +10342,15 @@ Also see the `completion-auto-wrap' variable." (defun next-line-completion (&optional n) "Move to completion candidate on the next line in the completions buffer. -With prefix argument N, move N lines forward (negative N means move backward). +With prefix argument N, move N lines forward (negative N means move +backward). In vertical format (see user option `completions-format') +this command moves line-wise through all columns in the completions +buffer, in horizontal format movement is confined to the current column +of completions. Also see the `completion-auto-wrap' variable." (interactive "p") - (let (line column pos found) + (let (line column pos found last first) (when (and (bobp) (> n 0) (get-text-property (point) 'mouse-face) @@ -10292,54 +10375,109 @@ Also see the `completion-auto-wrap' variable." ((< n 0) (first-completion))))) (while (> n 0) - (setq found nil pos nil column (current-column) line (line-number-at-pos)) - (completion--move-to-candidate-end) - (while (and (not found) - (eq (forward-line 1) 0) - (not (eobp)) - (move-to-column column)) - (when (get-text-property (point) 'mouse-face) - (setq found t))) - (when (not found) - (if (not completion-auto-wrap) - (last-completion) - (save-excursion - (goto-char (point-min)) - (when (and (eq (move-to-column column) column) - (get-text-property (point) 'mouse-face)) - (setq pos (point))) - (while (and (not pos) (> line (line-number-at-pos))) - (forward-line 1) + (setq found nil pos (point) column (current-column) + line (line-number-at-pos) + last (= (point) (save-excursion (last-completion) (point)))) + (if (and (eq completions-format 'vertical) + completion-auto-wrap last) + (first-completion) ; Wrap from last to first item. + (completion--move-to-candidate-end) + (while (and (not found) + (eq (forward-line 1) 0) + (not (eobp)) + (move-to-column column)) + (when (get-text-property (point) 'mouse-face) + (setq found t))) + (when (not found) + (if (and (not completion-auto-wrap) + (if (eq completions-format 'vertical) + (and (or last (get-text-property (point) 'mouse-face)) + (last-completion)) + (goto-char pos))) + t + (save-excursion + (setq pos nil) + (goto-char (point-min)) (when (and (eq (move-to-column column) column) (get-text-property (point) 'mouse-face)) - (setq pos (point))))) - (if pos (goto-char pos)))) + (setq pos (point))) + (while (and (not pos) (> line (line-number-at-pos))) + (forward-line 1) + (when (and (eq (move-to-column column) column) + (get-text-property (point) 'mouse-face)) + (setq pos (point))))) + (if pos (goto-char pos)) + (when (eq completions-format 'vertical) + (next-column-completion 1))))) ; Move to next column. (setq n (1- n))) (while (< n 0) - (setq found nil pos nil column (current-column) line (line-number-at-pos)) - (completion--move-to-candidate-start) - (while (and (not found) - (eq (forward-line -1) 0) - (move-to-column column)) - (when (get-text-property (point) 'mouse-face) - (setq found t))) - (when (not found) - (if (not completion-auto-wrap) - (first-completion) - (save-excursion - (goto-char (point-max)) - (when (and (eq (move-to-column column) column) - (get-text-property (point) 'mouse-face)) - (setq pos (point))) - (while (and (not pos) (< line (line-number-at-pos))) - (forward-line -1) + (setq found nil pos (point) column (current-column) + line (line-number-at-pos) + first (= (point) (save-excursion (first-completion) (point)))) + (if (and (eq completions-format 'vertical) + completion-auto-wrap first) + (last-completion) ; Wrap from first to last item. + (completion--move-to-candidate-start) + (while (and (not found) + (eq (forward-line -1) 0) + (move-to-column column)) + (when (get-text-property (point) 'mouse-face) + (setq found t))) + (when (not found) + (if (and (not completion-auto-wrap) + (if (eq completions-format 'vertical) + (and (or last first + (get-text-property (point) 'mouse-face)) + first (first-completion)) + (goto-char pos))) + t + (save-excursion + (setq pos nil) + (goto-char (point-max)) (when (and (eq (move-to-column column) column) (get-text-property (point) 'mouse-face)) - (setq pos (point))))) - (if pos (goto-char pos)))) + (setq pos (point))) + (while (and (not pos) (< line (line-number-at-pos))) + (forward-line -1) + (when (and (eq (move-to-column column) column) + (get-text-property (point) 'mouse-face)) + (setq pos (point))))) + (if pos (goto-char pos)) + (when (eq completions-format 'vertical) + (previous-column-completion 1) ; Move to previous column. + (setq column (current-column)) + ;; Move to last item in this column (previous column may + ;; have fewer items). + (while (not (eobp)) + (move-to-column column) + (setq pos (point)) + (forward-line)) + (goto-char pos))))) (setq n (1+ n))))) +(defun next-completion (&optional n) + "Move according to `completions-format' to next completion item. +In horizontal format movement is between columns within the same line, +in vertical format between lines within the same column. With non-nil +`completion-auto-wrap', movement continues to the next line or column, +respectively." + (interactive "p") + (pcase completions-format + ('vertical (next-line-completion n)) + (_ (next-column-completion n)))) + +(defun previous-completion (&optional n) + "Move according to `completions-format' to previous completion item. +In horizontal format movement is between columns within the same line, +in vertical format between lines within the same column. With non-nil +`completion-auto-wrap', movement continues to the next line or column, +respectively." + (interactive "p") + (pcase completions-format + ('vertical (previous-line-completion n)) + (_ (previous-column-completion n)))) + (defvar choose-completion-deselect-if-after nil "If non-nil, don't choose a completion candidate if point is right after it. diff --git a/test/lisp/minibuffer-tests.el b/test/lisp/minibuffer-tests.el index 6954643976a..27e9bbbefb4 100644 --- a/test/lisp/minibuffer-tests.el +++ b/test/lisp/minibuffer-tests.el @@ -734,10 +734,303 @@ (let ((completion-auto-wrap nil)) (first-completion) (next-line-completion 7) - (should (equal "ac2" (get-text-property (point) 'completion--string))) + (should (equal "ac1" (get-text-property (point) 'completion--string))) (previous-line-completion 7) (should (equal "aa1" (get-text-property (point) 'completion--string)))))) +(defun completions-format-navigation--tests (n) + "Make tests for navigating buffer of N completion candidate. +The tests check expected results of navigating with and without wrapping +for combinations of the values of `completion-auto-wrap' and +`completions-format' (see bug#78959 for motivation and discussion of the +expected behavior). The tests are actually run by calling this +function, with specific values of N (> 1 to have a \"*Completions*\" +buffer), from functions defined by `ert-deftest.'" + (let* ( + ;; Make list of N unique completions. + (letters (mapcar 'string (number-sequence 97 122))) + (gen-compl (lambda (x) + (let (comps) + (dotimes (_ x) + (push (concat (car comps) (pop letters)) comps)) + (nreverse comps)))) + (completions (funcall gen-compl n)) + + ;; Navigation tests. + ;; (i) For both horizontal and vertical formats. + (all-completions + (lambda (type) + (let ((next-fn (pcase type + ('any 'next-completion) + ('column 'next-column-completion) + ('line 'next-line-completion))) + (prev-fn (pcase type + ('any 'previous-completion) + ('column 'previous-column-completion) + ('line 'previous-line-completion)))) + (completing-read-with-minibuffer-setup completions + (insert (car completions)) + (minibuffer-completion-help) + (switch-to-completions) + ;; Sanity check that we're on first completion candidate. + (should + (equal (car completions) + (get-text-property (point) 'completion--string))) + ;; Double check. + (first-completion) + (should + (equal (car completions) + (get-text-property (point) 'completion--string))) + ;; Test moving from first to Ith next completion + ;; candidate (0 (current-column) 0) + (save-excursion (and (forward-line) (eobp))) + (unless one-col + (save-excursion + (and (progn + (completion--move-to-candidate-start) + (bolp)) + (progn + (completion--move-to-candidate-end) + (eolp)))))) + (completion--move-to-candidate-end))) + (backward-char) + (completion--move-to-candidate-start) + (setq last (get-text-property (point) 'completion--string) + pos (point)) + ;; We're on the last column, so next move either + ;; wraps or stays put. + (next-column-completion 1) + (if completion-auto-wrap + ;; We wrapped around to first candidate in this line. + (progn + (should (bolp)) + (should + (equal (get-text-property (point) 'completion--string) + first)) + ;; Go back to last completion in this line for next test. + (goto-char (if one-col pos (pos-eol))) + (backward-char)) + (should + (equal (get-text-property (point) 'completion--string) + last))) + ;; Test moving to previous column in this line. + (while (if one-col + (save-excursion + (forward-line -1) + (get-text-property (point) 'completion--string)) + (not (bolp))) + (previous-column-completion 1) + (let ((prev (get-text-property (point) 'completion--string))) + (should + ;; FIXME: tautology? + (equal (nth (seq-position completions prev) completions) + prev)))) + ;; We're on the first column, so next move either + ;; wraps or stay put. + (previous-column-completion 1) + (if completion-auto-wrap + ;; We wrapped around to last candidate in this line. + (progn + (completion--move-to-candidate-end) + (should (eolp)) + (backward-char) + (should + (equal (get-text-property (point) 'completion--string) + last))) + ;; We stayed on the first candidate. + (should + (equal (get-text-property (point) 'completion--string) + first))) + (if one-col + (goto-char (point-max)) + (forward-line))))))))) + + ;; Run tests. + ;; Test navigation with wrapping... + (let ((completion-auto-wrap t)) + ;; ...in horizontal format, + (let ((completions-format 'horizontal)) + (funcall all-completions 'any) + (funcall all-completions 'column) + (funcall within-column)) + ;; ...in vertical format. + (let ((completions-format 'vertical)) + (funcall all-completions 'any) + (funcall all-completions 'line) + (funcall within-line))) + ;; Test navigation without wrapping... + (let ((completion-auto-wrap nil)) + ;; ...in horizontal format, + (let ((completions-format 'horizontal)) + (funcall all-completions 'any) + (funcall all-completions 'column) + (funcall within-column)) + ;; ...in vertical format. + (let ((completions-format 'vertical)) + (funcall all-completions 'any) + (funcall all-completions 'line) + (funcall within-line))))) + +;; (ert-deftest completions-format-navigation-test-1 () +;; (completions-format-navigation--tests 1)) + +(ert-deftest completions-format-navigation-test-2 () + (completions-format-navigation--tests 2)) + +(ert-deftest completions-format-navigation-test-3 () + (completions-format-navigation--tests 3)) + +(ert-deftest completions-format-navigation-test-4 () + (completions-format-navigation--tests 4)) + +(ert-deftest completions-format-navigation-test-5 () + (completions-format-navigation--tests 5)) + +(ert-deftest completions-format-navigation-test-9 () + (completions-format-navigation--tests 9)) + +(ert-deftest completions-format-navigation-test-10 () + (completions-format-navigation--tests 10)) + +(ert-deftest completions-format-navigation-test-15 () + (completions-format-navigation--tests 15)) + +(ert-deftest completions-format-navigation-test-16 () + (completions-format-navigation--tests 16)) + (ert-deftest completion-cycle () (completing-read-with-minibuffer-setup '("aaa" "bbb" "ccc") (let ((completion-cycle-threshold t)) commit aeadaf77488a85838547ed8253a2f0b017cf4774 Author: Philipp Stephani Date: Mon Sep 22 14:40:32 2025 +0200 Fix minor incorrect statement about shorthands. * doc/lispref/symbols.texi (Shorthands): Make documentation match reality. See symbol_char_span in src/lread.c. diff --git a/doc/lispref/symbols.texi b/doc/lispref/symbols.texi index 3efbf1c29df..532d3b84a97 100644 --- a/doc/lispref/symbols.texi +++ b/doc/lispref/symbols.texi @@ -771,8 +771,8 @@ There are two exceptions to rules governing Shorthand transformations: @itemize @bullet @item -Symbol forms comprised entirely of characters in the Emacs Lisp symbol -constituent class (@pxref{Syntax Class Table}) are not transformed. +Symbol forms comprised entirely of characters of +the set @code{^*+-/<=>_|} are not transformed. For example, it's possible to use @code{-} or @code{/=} as shorthand prefixes, but that won't shadow the arithmetic @emph{functions} of those names. commit dac0040f8de3874970c6f14df1aa7668f581261a Author: Philipp Stephani Date: Mon Sep 22 14:27:29 2025 +0200 ; * doc/lispref/symbols.texi (Shorthands): Add missing parenthesis. diff --git a/doc/lispref/symbols.texi b/doc/lispref/symbols.texi index 6cddd50c920..3efbf1c29df 100644 --- a/doc/lispref/symbols.texi +++ b/doc/lispref/symbols.texi @@ -761,7 +761,7 @@ local variables section of your file. ;; Local Variables: ;; read-symbol-shorthands: (("t/" . "my-tricks-") -;; ("t//" . "my-tricks--") +;; ("t//" . "my-tricks--")) ;; End: @end example commit ec4066d53246f00167995bf62628c8dd86d48037 Author: Andrea Corallo Date: Mon Sep 22 10:07:30 2025 +0200 Revert "* Clean-up some compilation warnings in vc-hooks.el" This reverts commit dd622e06e7db8362ecdb69ecbd42b2fb01a5168b as functions are autoloaded. diff --git a/lisp/vc/vc-hooks.el b/lisp/vc/vc-hooks.el index 15ec3adf00e..da67f7f1815 100644 --- a/lisp/vc/vc-hooks.el +++ b/lisp/vc/vc-hooks.el @@ -31,12 +31,6 @@ (eval-when-compile (require 'cl-lib)) -(declare-function vc-diff-outgoing-base "vc" (&optional upstream-location fileset)) -(declare-function vc-root-diff-outgoing-base "vc" (&optional upstream-location)) -(declare-function vc-apply-to-other-working-tree "vc" (directory &optional move)) -(declare-function vc-apply-root-to-other-working-tree "vc" - (directory &optional move preview)) - ;; Faces (defgroup vc-faces nil commit 0b9e96ce5e887fcd6d1fbfa390b826fc51996e79 Author: Andrea Corallo Date: Wed Sep 17 15:15:40 2025 +0200 Make native lisp code reflect EQ C implementation * src/comp.c (ABI_VERSION): Bump new version (comp_t): add 'eq'. (helper_link_table): add 'slow_eq'. (emit_slow_eq): New function. (emit_EQ): Rework to reflect EQ implementation. (declare_runtime_imported_funcs): Import 'slow_eq'. (Fcomp__init_ctxt): Register emitter 'emit_eq' for op code Qeq. This and the previous 2 commits increase elisp-benchmarks performance by 3.6% on my test machine. Also a (small) reduction in eln size comes with it. diff --git a/src/comp.c b/src/comp.c index 1e06f83dcbd..8ab167ab592 100644 --- a/src/comp.c +++ b/src/comp.c @@ -468,7 +468,7 @@ load_gccjit_if_necessary (bool mandatory) /* Increase this number to force a new Vcomp_abi_hash to be generated. */ -#define ABI_VERSION "11" +#define ABI_VERSION "12" /* Length of the hashes used for eln file naming. */ #define HASH_LENGTH 8 @@ -630,6 +630,7 @@ typedef struct { gcc_jit_function *add1; gcc_jit_function *sub1; gcc_jit_function *negate; + gcc_jit_function *eq; gcc_jit_function *car; gcc_jit_function *cdr; gcc_jit_function *setcar; @@ -693,6 +694,7 @@ static void *helper_link_table[] = helper_unbind_n, helper_save_restriction, helper_GET_SYMBOL_WITH_POSITION, + slow_eq, helper_sanitizer_assert, record_unwind_current_buffer, set_internal, @@ -1480,17 +1482,6 @@ emit_CONSP (gcc_jit_rvalue *obj) return emit_TAGGEDP (obj, Lisp_Cons); } -static gcc_jit_rvalue * -emit_BARE_SYMBOL_P (gcc_jit_rvalue *obj) -{ - emit_comment ("BARE_SYMBOL_P"); - - return gcc_jit_context_new_cast (comp.ctxt, - NULL, - emit_TAGGEDP (obj, Lisp_Symbol), - comp.bool_type); -} - static gcc_jit_rvalue * emit_SYMBOL_WITH_POS_P (gcc_jit_rvalue *obj) { @@ -1511,52 +1502,46 @@ emit_SYMBOL_WITH_POS_P (gcc_jit_rvalue *obj) } static gcc_jit_rvalue * -emit_SYMBOL_WITH_POS_SYM (gcc_jit_rvalue *obj) +emit_slow_eq (gcc_jit_rvalue *x, gcc_jit_rvalue *y) { - emit_comment ("SYMBOL_WITH_POS_SYM"); + gcc_jit_rvalue *args[] = + { emit_coerce (comp.lisp_obj_type, x), + emit_coerce (comp.lisp_obj_type, y) }; - gcc_jit_rvalue *arg [] = { obj }; - return gcc_jit_context_new_call (comp.ctxt, - NULL, - comp.symbol_with_pos_sym, - 1, - arg); + return emit_call (intern_c_string ("slow_eq"), + comp.bool_type, + ARRAYELTS (args), + args, + false); } static gcc_jit_rvalue * emit_EQ (gcc_jit_rvalue *x, gcc_jit_rvalue *y) { - return - emit_OR ( - gcc_jit_context_new_comparison ( - comp.ctxt, NULL, - GCC_JIT_COMPARISON_EQ, - emit_XLI (x), emit_XLI (y)), - emit_AND ( - gcc_jit_lvalue_as_rvalue ( - gcc_jit_rvalue_dereference (comp.f_symbols_with_pos_enabled_ref, - NULL)), - emit_OR ( - emit_AND ( - emit_SYMBOL_WITH_POS_P (x), - emit_OR ( - emit_AND ( - emit_SYMBOL_WITH_POS_P (y), - emit_BASE_EQ ( - emit_XLI (emit_SYMBOL_WITH_POS_SYM (x)), - emit_XLI (emit_SYMBOL_WITH_POS_SYM (y)))), - emit_AND ( - emit_BARE_SYMBOL_P (y), - emit_BASE_EQ ( - emit_XLI (emit_SYMBOL_WITH_POS_SYM (x)), - emit_XLI (y))))), - emit_AND ( - emit_BARE_SYMBOL_P (x), - emit_AND ( - emit_SYMBOL_WITH_POS_P (y), - emit_BASE_EQ ( - emit_XLI (x), - emit_XLI (emit_SYMBOL_WITH_POS_SYM (y)))))))); + gcc_jit_rvalue *base_eq = emit_BASE_EQ (x, y); + gcc_jit_rvalue *symbols_with_pos_enabled_rval = gcc_jit_lvalue_as_rvalue ( + gcc_jit_rvalue_dereference (comp.f_symbols_with_pos_enabled_ref, + NULL)); + + gcc_jit_rvalue *expect_args[] = + { emit_coerce (comp.long_type, symbols_with_pos_enabled_rval), + gcc_jit_context_new_rvalue_from_int (comp.ctxt, + comp.long_type, + 0) }; + + gcc_jit_rvalue *unlikely_symbols_with_pos_enabled = emit_coerce ( + comp.bool_type, + gcc_jit_context_new_call ( + comp.ctxt, + NULL, + gcc_jit_context_get_builtin_function (comp.ctxt, + "__builtin_expect"), + 2, + expect_args)); + + return emit_OR ( + base_eq, + emit_AND (unlikely_symbols_with_pos_enabled, emit_slow_eq (x, y))); } static gcc_jit_rvalue * @@ -2598,6 +2583,21 @@ emit_consp (Lisp_Object insn) 1, &res); } +static gcc_jit_rvalue * +emit_eq (Lisp_Object insn) +{ + gcc_jit_rvalue *x = emit_mvar_rval (SECOND (insn)); + gcc_jit_rvalue *y = emit_mvar_rval (THIRD (insn)); + gcc_jit_rvalue *res = emit_EQ (x, y); + res = emit_coerce (comp.bool_type, res); + return gcc_jit_context_new_call (comp.ctxt, + NULL, + comp.bool_to_lisp_obj, + 1, + &res); +} + + static gcc_jit_rvalue * emit_car (Lisp_Object insn) { @@ -2930,6 +2930,10 @@ declare_runtime_imported_funcs (void) ADD_IMPORTED (helper_GET_SYMBOL_WITH_POSITION, comp.lisp_symbol_with_position_ptr_type, 1, args); + args[0] = comp.lisp_obj_type; + args[1] = comp.lisp_obj_type; + ADD_IMPORTED (slow_eq, comp.bool_type, 2, args); + args[0] = comp.lisp_obj_type; args[1] = comp.lisp_obj_type; ADD_IMPORTED (helper_sanitizer_assert, comp.lisp_obj_type, 2, args); @@ -4515,6 +4519,7 @@ Return t on success. */) register_emitter (Qadd1, emit_add1); register_emitter (Qsub1, emit_sub1); register_emitter (Qconsp, emit_consp); + register_emitter (Qeq, emit_eq); register_emitter (Qcar, emit_car); register_emitter (Qcdr, emit_cdr); register_emitter (Qsetcar, emit_setcar); commit e7380934d0a633ef54161cee0a0abe307364dacc Author: Andrea Corallo Date: Thu Sep 18 00:03:42 2025 +0200 * Make sure the compiler optimizes for symbols_with_pos_enabled 0 in EQ * src/lisp.h (EQ): Make use '__builtin_expect'. diff --git a/src/lisp.h b/src/lisp.h index 88406aa0ce8..ecc20600dd0 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -1328,7 +1328,7 @@ EQ (Lisp_Object x, Lisp_Object y) { if (BASE_EQ (x, y)) return true; - else if (!symbols_with_pos_enabled) + else if (!__builtin_expect (symbols_with_pos_enabled, false)) return false; else return slow_eq (x, y); commit 020e2f5ddbbc2f380f694e1537aaa8405daf5829 Author: Pip Cet Date: Wed Dec 11 22:31:07 2024 +0000 Change EQ to move slow code path into a separate function * src/data.c (slow_eq): New function. * src/lisp.h (EQ): Call it. diff --git a/src/data.c b/src/data.c index 5b3c9792ea0..333d908aedc 100644 --- a/src/data.c +++ b/src/data.c @@ -155,6 +155,16 @@ circular_list (Lisp_Object list) /* Data type predicates. */ +/* NO_INLINE to avoid excessive code growth when LTO is in use. */ +NO_INLINE bool +slow_eq (Lisp_Object x, Lisp_Object y) +{ + return BASE_EQ ((symbols_with_pos_enabled && SYMBOL_WITH_POS_P (x) + ? XSYMBOL_WITH_POS_SYM (x) : x), + (symbols_with_pos_enabled && SYMBOL_WITH_POS_P (y) + ? XSYMBOL_WITH_POS_SYM (y) : y)); +} + DEFUN ("eq", Feq, Seq, 2, 2, 0, doc: /* Return t if the two args are the same Lisp object. */ attributes: const) diff --git a/src/lisp.h b/src/lisp.h index 1782b12e7fd..88406aa0ce8 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -634,6 +634,7 @@ extern AVOID wrong_type_argument (Lisp_Object, Lisp_Object); extern Lisp_Object default_value (Lisp_Object symbol); extern void defalias (Lisp_Object symbol, Lisp_Object definition); extern char *fixnum_to_string (EMACS_INT number, char *buffer, char *end); +extern bool slow_eq (Lisp_Object x, Lisp_Object y); /* Defined in emacs.c. */ @@ -1325,10 +1326,12 @@ INLINE bool INLINE bool EQ (Lisp_Object x, Lisp_Object y) { - return BASE_EQ ((__builtin_expect (symbols_with_pos_enabled, false) - && SYMBOL_WITH_POS_P (x) ? XSYMBOL_WITH_POS_SYM (x) : x), - (__builtin_expect (symbols_with_pos_enabled, false) - && SYMBOL_WITH_POS_P (y) ? XSYMBOL_WITH_POS_SYM (y) : y)); + if (BASE_EQ (x, y)) + return true; + else if (!symbols_with_pos_enabled) + return false; + else + return slow_eq (x, y); } INLINE intmax_t