commit db0bed7a68cd2308eba61247a6a77f73533ffef6 (HEAD, refs/remotes/origin/master) Author: Sean Whitton Date: Sat Mar 15 16:03:38 2025 +0800 New project-save-some-buffers command * lisp/progmodes/project.el (project-save-some-buffers): New command. (project-prefix-map): Bind it to C-x p C-x s. * etc/NEWS: Announce the new command and binding. diff --git a/etc/NEWS b/etc/NEWS index ecdf0597a90..ba28e87ca19 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -362,6 +362,10 @@ invoked standalone or from the 'project-switch-commands' dispatch menu. This user option describes projects that should always be skipped by 'project-remember-project'. +--- +*** New command 'project-save-some-buffers' bound to 'C-x p C-x s'. +This is like 'C-x s', but only for this project's buffers. + ** Registers *** New functions 'buffer-to-register' and 'file-to-register'. diff --git a/lisp/progmodes/project.el b/lisp/progmodes/project.el index d954b78a745..e2cd5bfa231 100644 --- a/lisp/progmodes/project.el +++ b/lisp/progmodes/project.el @@ -903,6 +903,7 @@ DIRS must contain directory names." (define-key map "x" 'project-execute-extended-command) (define-key map "o" 'project-any-command) (define-key map "\C-b" 'project-list-buffers) + (define-key map "\C-xs" 'project-save-some-buffers) map) "Keymap for project commands.") @@ -1828,6 +1829,12 @@ Also see the `project-kill-buffers-display-buffer-list' variable." ((funcall query-user) (mapc #'kill-buffer bufs))))) +;;;###autoload +(defun project-save-some-buffers (arg) + "Like `save-some-buffers', but only for this project's buffers." + (interactive "P") + (save-some-buffers arg (save-some-buffers-root))) + ;;; Project list commit 6f483ffdc217ea9f8dc079b2344f771214222273 Author: Sean Whitton Date: Sat Mar 15 16:02:56 2025 +0800 vc-revert: Offer to save modified buffers (bug#55310) * lisp/vc/vc.el (vc-revert): Offer to save modified buffers that otherwise prevent proceeding with the revert (bug#55310). diff --git a/lisp/vc/vc.el b/lisp/vc/vc.el index bc96173d198..5c2f848b891 100644 --- a/lisp/vc/vc.el +++ b/lisp/vc/vc.el @@ -3243,14 +3243,21 @@ to the working revision (except for keyword expansion)." ;; show the changes and ask for confirmation to discard them. (when (or (not files) (memq (buffer-file-name) files)) (vc-buffer-sync nil)) - (dolist (file files) - (let ((buf (get-file-buffer file))) - (when (and buf (buffer-modified-p buf)) - (error "Please kill or save all modified buffers before reverting"))) - (when (vc-up-to-date-p file) - (if (yes-or-no-p (format "%s seems up-to-date. Revert anyway? " file)) - (setq queried t) - (error "Revert canceled")))) + (save-some-buffers nil (lambda () + (member (buffer-file-name) files))) + (let (needs-save) + (dolist (file files) + (let ((buf (get-file-buffer file))) + (when (and buf (buffer-modified-p buf)) + (push buf needs-save))) + (when (vc-up-to-date-p file) + (if (yes-or-no-p (format "%s seems up-to-date. Revert anyway? " + file)) + (setq queried t) + (error "Revert canceled")))) + (when needs-save + (error "Cannot revert with these buffers unsaved: %s" + (string-join (mapcar #'buffer-name needs-save) ", ")))) (unwind-protect (when (if vc-revert-show-diff (progn commit f5f3fb7dd4e7d0c1fa235309d29d8d1c9432c277 Author: Eli Zaretskii Date: Sat Mar 15 22:19:52 2025 +0200 ; * lisp/menu-bar.el (menu-bar-tools-menu): Move "EDE" after "Project". diff --git a/lisp/menu-bar.el b/lisp/menu-bar.el index 27238d5c266..9a6cda24219 100644 --- a/lisp/menu-bar.el +++ b/lisp/menu-bar.el @@ -1922,6 +1922,12 @@ mail status in mode line")) :help "Toggle automatic parsing in source code buffers (Semantic mode)" :button (:toggle . (bound-and-true-p semantic-mode)))) + (define-key menu [ede] + '(menu-item "Project Support (EDE)" + global-ede-mode + :help "Toggle the Emacs Development Environment (Global EDE mode)" + :button (:toggle . (bound-and-true-p global-ede-mode)))) + (define-key menu [eglot] '(menu-item "Language Server Support (Eglot)" eglot :help "Start language server suitable for this buffer's major-mode")) @@ -1929,12 +1935,6 @@ mail status in mode line")) (define-key menu [project] menu-bar-project-item) - (define-key menu [ede] - '(menu-item "Project Support (EDE)" - global-ede-mode - :help "Toggle the Emacs Development Environment (Global EDE mode)" - :button (:toggle . (bound-and-true-p global-ede-mode)))) - (define-key menu [gdb] '(menu-item "Debugger (GDB)..." gdb :help "Debug a program from within Emacs with GDB")) commit f12ea5b703b88f0a877013bffff37a99c86143c7 Author: Jonas Bernoulli Date: Sat Mar 15 20:21:20 2025 +0100 Update to Transient v0.8.6-7-g64cb8404 diff --git a/doc/misc/transient.texi b/doc/misc/transient.texi index 4740663e987..4dcadb29dfa 100644 --- a/doc/misc/transient.texi +++ b/doc/misc/transient.texi @@ -31,7 +31,7 @@ General Public License for more details. @finalout @titlepage @title Transient User and Developer Manual -@subtitle for version 0.8.4 +@subtitle for version 0.8.6 @author Jonas Bernoulli @page @vskip 0pt plus 1filll @@ -53,7 +53,7 @@ resource to get over that hurdle is Psionic K's interactive tutorial, available at @uref{https://github.com/positron-solutions/transient-showcase}. @noindent -This manual is for Transient version 0.8.4. +This manual is for Transient version 0.8.6. @insertcopying @end ifnottex @@ -777,6 +777,11 @@ This displays the window at the bottom of the selected frame. For alternatives see @ref{Buffer Display Action Functions,,,elisp,}, and @xref{Buffer Display Action Alists,,,elisp,}. +When you switch to a different ACTION, you should keep the ALIST +entries for @code{dedicated} and @code{inhibit-same-window} in most cases. +Do not drop them because you are unsure whether they are needed; +if you are unsure, then keep them. + Note that the buffer that was current before the transient buffer is shown should remain the current buffer. Many suffix commands act on the thing at point, if appropriate, and if the transient @@ -1019,8 +1024,10 @@ that multiple suffix commands can be bound to the same key, provided they are never active at the same time, see @ref{Predicate Slots}. Unfortunately both false-positives and false-negatives are possible. -To deal with the former use non-@code{nil} @var{KEEP-OTHER@.} To deal with the -latter remove the conflicting binding explicitly. +To deal with the former use non-@code{nil} @var{KEEP-OTHER@.} The symbol @code{always} +prevents the removal of a false-positive in some cases where other +non-@code{nil} values would fail. To deal with false-negatives remove the +conflicting binding separately, using @code{transient-remove-suffix}. @end defun @defun transient-replace-suffix prefix loc suffix @@ -1880,16 +1887,9 @@ the suffix's @code{transient} slot. @item While a sub-prefix is active we nearly always want @kbd{C-g} to take the user back to the ``super-prefix'', even when the other suffixes don't -do that. However, in rare cases this may not be desirable, and that -makes the following complication necessary: - -For @code{transient-suffix} objects the @code{transient} slot is unbound. We can -ignore that for the most part because @code{nil} and the slot being unbound -are treated as equivalent, and mean ``do exit''. That isn't actually -true for suffixes that are sub-prefixes though. For such suffixes -unbound means ``do exit but allow going back'', which is the default, -while @code{nil} means ``do exit permanently'', which requires that slot to -be explicitly set to that value. +do that. However, in rare cases this may not be desirable, in which +case @code{replace} can be used as the value of the sub-prefix's @code{transient} +slot. @end itemize @anchor{Pre-commands for Infixes} @@ -1933,7 +1933,7 @@ predicate is automatically picked based on the value of the @code{transient} slot for the sub-prefix itself. @defun transient--do-recurse -Call the transient prefix command, preparing for return to active +Call the transient prefix command, preparing for return to outer transient. Whether we actually return to the parent transient is ultimately diff --git a/lisp/transient.el b/lisp/transient.el index aa0fc442638..8332377c933 100644 --- a/lisp/transient.el +++ b/lisp/transient.el @@ -5,7 +5,7 @@ ;; Author: Jonas Bernoulli ;; URL: https://github.com/magit/transient ;; Keywords: extensions -;; Version: 0.8.4 +;; Version: 0.8.6 ;; SPDX-License-Identifier: GPL-3.0-or-later @@ -32,7 +32,7 @@ ;;; Code: -(defconst transient-version "v0.8.4-7-gabee7353-builtin") +(defconst transient-version "v0.8.6-7-g64cb8404-builtin") (require 'cl-lib) (require 'eieio) @@ -52,8 +52,7 @@ (defvar Man-notify-method) (make-obsolete-variable 'transient-hide-during-minibuffer-read - "use `transient-show-during-minibuffer-read' instead." - "0.8.0") + 'transient-show-during-minibuffer-read "0.8.0") (defmacro transient--with-emergency-exit (id &rest body) (declare (indent defun)) @@ -162,6 +161,11 @@ This displays the window at the bottom of the selected frame. For alternatives see info node `(elisp)Display Action Functions' and info node `(elisp)Buffer Display Action Alists'. +When you switch to a different ACTION, you should keep the ALIST +entries for `dedicated' and `inhibit-same-window' in most cases. +Do not drop them because you are unsure whether they are needed; +if you are unsure, then keep them. + Note that the buffer that was current before the transient buffer is shown should remain the current buffer. Many suffix commands act on the thing at point, if appropriate, and if the transient @@ -230,8 +234,8 @@ See `mode-line-format' for details." "Whether to show common transient suffixes in the popup buffer. These commands are always shown after typing the prefix key -\"C-x\" when a transient command is active. To toggle the value -of this variable use \"C-x t\" when a transient is active." +\\`C-x' when a transient command is active. To toggle the value +of this variable use \\`C-x t' when a transient is active." :package-version '(transient . "0.1.0") :group 'transient :type 'boolean) @@ -429,7 +433,7 @@ Integers between 1 and 7 (inclusive) are valid levels. The levels of individual transients and/or their individual suffixes can be changed individually, by invoking the prefix and -then pressing \"C-x l\". +then pressing \\`C-x l'. The default level for both transients and their suffixes is 4. This option only controls the default for transients. The default @@ -538,7 +542,7 @@ See info node `(transient)Enabling and Disabling Suffixes'." (defface transient-higher-level `((t :box ( :line-width ,(if (>= emacs-major-version 28) (cons -1 -1) -1) - :color ,(let ((color (face-attribute 'shadow :foreground nil t))) + :color ,(let ((color (face-attribute 'shadow :foreground t t))) (or (and (not (eq color 'unspecified)) color) "grey60"))))) "Face optionally used to highlight suffixes on higher levels. @@ -562,7 +566,7 @@ character used to separate possible values from each other." (((class color) (background dark)) :inherit transient-key :foreground "#ddffdd")) - "Face used for keys of suffixes that don't exit transient state." + "Face used for keys of suffixes that don't exit the menu." :group 'transient-faces) (defface transient-key-noop @@ -582,7 +586,27 @@ character used to separate possible values from each other." (((class color) (background dark)) :inherit transient-key :foreground "#ffffcc")) - "Face used for keys of suffixes that return to the parent transient." + "Face used for keys of suffixes that return to the parent menu." + :group 'transient-faces) + +(defface transient-key-recurse + `((((class color) (background light)) + :inherit transient-key + :foreground "#2266ff") + (((class color) (background dark)) + :inherit transient-key + :foreground "#2299ff")) + "Face used for keys of sub-menus whose suffixes return to the parent menu." + :group 'transient-faces) + +(defface transient-key-stack + `((((class color) (background light)) + :inherit transient-key + :foreground "#dd4488") + (((class color) (background dark)) + :inherit transient-key + :foreground "#ff6699")) + "Face used for keys of sub-menus that exit the parent menu." :group 'transient-faces) (defface transient-key-exit @@ -592,7 +616,7 @@ character used to separate possible values from each other." (((class color) (background dark)) :inherit transient-key :foreground "#ffdddd")) - "Face used for keys of suffixes that exit transient state." + "Face used for keys of suffixes that exit the menu." :group 'transient-faces) (defface transient-unreachable-key @@ -671,7 +695,8 @@ should not change it manually.") "Save the value of `transient-history'. If `transient-save-history' is nil, then do nothing." (when transient-save-history - (transient-save-history))) + (with-demoted-errors "Error saving transient history: %S" + (transient-save-history)))) (unless noninteractive (add-hook 'kill-emacs-hook #'transient-maybe-save-history)) @@ -685,6 +710,7 @@ If `transient-save-history' is nil, then do nothing." (level :initarg :level) (init-value :initarg :init-value) (value) (default-value :initarg :value) + (return :initarg :return :initform nil) (scope :initarg :scope :initform nil) (history :initarg :history :initform nil) (history-pos :initarg :history-pos :initform 0) @@ -1387,7 +1413,8 @@ Intended for use in a group's `:setup-children' function." suffix prefix loc "suffixes and groups cannot be siblings")) (t - (when-let* ((bindingp (listp suf)) + (when-let* (((not (eq keep-other 'always))) + (bindingp (listp suf)) (key (transient--spec-key suf)) (conflict (car (transient--layout-member key prefix))) (conflictp @@ -1415,7 +1442,9 @@ LOC is a command, a key vector, a key description (a string as returned by `key-description'), or a coordination list (whose last element may also be a command or key). Remove a conflicting binding unless optional KEEP-OTHER is - non-nil. + non-nil. When the conflict appears to be a false-positive, + non-nil KEEP-OTHER may be ignored, which can be prevented + by using `always'. See info node `(transient)Modifying Existing Transients'." (declare (indent defun)) (transient--insert-suffix prefix loc suffix 'insert keep-other)) @@ -1430,7 +1459,9 @@ LOC is a command, a key vector, a key description (a string as returned by `key-description'), or a coordination list (whose last element may also be a command or key). Remove a conflicting binding unless optional KEEP-OTHER is - non-nil. + non-nil. When the conflict appears to be a false-positive, + non-nil KEEP-OTHER may be ignored, which can be prevented + by using `always'. See info node `(transient)Modifying Existing Transients'." (declare (indent defun)) (transient--insert-suffix prefix loc suffix 'append keep-other)) @@ -1650,6 +1681,10 @@ drawing in the transient buffer.") This is bound while the suffix predicate is being evaluated, and while functions that return faces are being evaluated.") +(defvar transient--current-suffix nil + "The suffix currently being invoked using a mouse event. +Do not use this; instead use function `transient-suffix-object'.") + (defvar transient--pending-group nil "The group that is currently being processed. This is bound while the suffixes are drawn in the transient buffer.") @@ -1758,6 +1793,7 @@ probably use this instead: (cl-check-type command command)) (cond (transient--pending-suffix) + (transient--current-suffix) ((or transient--prefix transient-current-prefix) (let ((suffixes @@ -1772,22 +1808,25 @@ probably use this instead: this-command)))) (or transient--suffixes transient-current-suffixes)))) - (or (if (cdr suffixes) - (cl-find-if - (lambda (obj) - (equal (listify-key-sequence (transient--kbd (oref obj key))) - (listify-key-sequence (this-command-keys)))) - suffixes) - (car suffixes)) - ;; COMMAND is only provided if `this-command' is meaningless, in - ;; which case `this-command-keys' is also meaningless, making it - ;; impossible to disambiguate redundant bindings. - (if command - (car suffixes) - ;; TODO Decide whether it is legimate to use this function - ;; as a predicate, and also whether to return an object for - ;; suffixes common to all prefixes. See #29 and #337. - (error "BUG: Cannot determine suffix object"))))) + (cond + ((length= suffixes 1) + (car suffixes)) + ((cl-find-if (lambda (obj) + (equal + (listify-key-sequence (transient--kbd (oref obj key))) + (listify-key-sequence (this-command-keys)))) + suffixes)) + ;; COMMAND is only provided if `this-command' is meaningless, in + ;; which case `this-command-keys' is also meaningless, making it + ;; impossible to disambiguate bindings for the same command. + (command (car suffixes)) + ;; If COMMAND is nil, then failure to disambiguate likely means + ;; that there is a bug somewhere. + ((length> suffixes 1) + (error "BUG: Cannot unambigiously determine suffix object")) + ;; It is legimate to use this function as a predicate of sorts. + ;; `transient--pre-command' and `transient-help' are examples. + (t nil)))) ((and-let* ((obj (transient--suffix-prototype (or command this-command))) (obj (clone obj))) (progn @@ -2046,7 +2085,7 @@ of the corresponding object." (defun transient--make-predicate-map () (let* ((default (transient--resolve-pre-command (oref transient--prefix transient-suffix))) - (return (and transient--stack (eq default t))) + (return (and transient--stack (oref transient--prefix return))) (map (make-sparse-keymap))) (set-keymap-parent map transient-predicate-map) (when (or (and (slot-boundp transient--prefix 'transient-switch-frame) @@ -2067,26 +2106,26 @@ of the corresponding object." ((slot-boundp obj 'transient) (pcase (list kind (transient--resolve-pre-command - (oref obj transient)) + (oref obj transient) nil t) return) - (`(prefix t ,_) #'transient--do-recurse) - (`(prefix nil ,_) #'transient--do-stack) - (`(infix t ,_) #'transient--do-stay) - (`(suffix t ,_) #'transient--do-call) - ('(suffix nil t) #'transient--do-return) - (`(,_ nil ,_) #'transient--do-exit) - (`(,_ ,do ,_) do))) + (`(prefix t ,_) #'transient--do-recurse) + (`(prefix nil ,_) #'transient--do-stack) + (`(infix t ,_) #'transient--do-stay) + (`(suffix t ,_) #'transient--do-call) + ('(suffix nil t) #'transient--do-return) + (`(,_ nil ,_) #'transient--do-exit) + (`(,_ ,do ,_) do))) ((not (lookup-key transient-predicate-map id)) (pcase (list kind default return) (`(prefix ,(or 'transient--do-stay 'transient--do-call) ,_) #'transient--do-recurse) - (`(prefix t ,_) #'transient--do-recurse) - (`(prefix ,_ ,_) #'transient--do-stack) - (`(infix ,_ ,_) #'transient--do-stay) - (`(suffix t ,_) #'transient--do-call) - ('(suffix nil t) #'transient--do-return) - (`(suffix nil ,_) #'transient--do-exit) - (`(suffix ,do ,_) do)))))) + (`(prefix t ,_) #'transient--do-recurse) + (`(prefix ,_ ,_) #'transient--do-stack) + (`(infix ,_ ,_) #'transient--do-stay) + (`(suffix t ,_) #'transient--do-call) + ('(suffix nil t) #'transient--do-return) + (`(suffix nil nil) #'transient--do-exit) + (`(suffix ,do ,_) do)))))) (when pre (if-let* ((alt (lookup-key map id))) (unless (eq alt pre) @@ -2223,9 +2262,9 @@ value. Otherwise return CHILDREN as is.") :level (or (alist-get t (alist-get name transient-levels)) transient-default-level) params)))) - (transient--setup-recursion obj) - (transient-init-scope obj) - (transient-init-value obj) + (transient-init-value obj) + (transient-init-return obj) + (transient-init-scope obj) obj)) (defun transient--init-suffixes (name) @@ -2279,18 +2318,14 @@ value. Otherwise return CHILDREN as is.") level (and proto (oref proto level)) transient--default-child-level))) - (let ((fn (and (symbolp cmd) - (symbol-function cmd)))) - (when (autoloadp fn) - (transient--debug " autoload %s" cmd) - (autoload-do-load fn))) + (transient--load-command-if-autoload cmd) (when (transient--use-level-p level) (let ((obj (if (child-of-class-p class 'transient-information) (apply class :parent parent :level level args) (unless (and cmd (symbolp cmd)) (error "BUG: Non-symbolic suffix command: %s" cmd)) (if proto - (apply #'clone proto :level level args) + (apply #'clone proto :parent parent :level level args) (apply class :command cmd :parent parent :level level args))))) (cond ((not cmd)) @@ -2401,13 +2436,20 @@ value. Otherwise return CHILDREN as is.") :inapt-if-mode :inapt-if-not-mode :inapt-if-derived :inapt-if-not-derived)))) +(defun transient--load-command-if-autoload (cmd) + (when-let* (((symbolp cmd)) + (fn (symbol-function cmd)) + ((autoloadp fn))) + (transient--debug " autoload %s" cmd) + (autoload-do-load fn))) + ;;; Flow-Control (defun transient--setup-transient () (transient--debug 'setup-transient) (transient--push-keymap 'transient--transient-map) (transient--push-keymap 'transient--redisplay-map) - (add-hook 'pre-command-hook #'transient--pre-command) + (add-hook 'pre-command-hook #'transient--pre-command 99) (add-hook 'post-command-hook #'transient--post-command) (advice-add 'recursive-edit :around #'transient--recursive-edit) (when transient--exitp @@ -2451,8 +2493,7 @@ value. Otherwise return CHILDREN as is.") ((not (transient--edebug-command-p)) (setq this-command 'transient-undefined)))) ((and transient--editp - ;; See TODO in that function. - (ignore-errors (transient-suffix-object)) + (transient-suffix-object) (not (memq this-command '(transient-quit-one transient-quit-all transient-help)))) @@ -2489,28 +2530,6 @@ value. Otherwise return CHILDREN as is.") (setq transient--original-buffer nil) (setq transient--window nil)) -(defun transient--delete-window () - (when (window-live-p transient--window) - (let ((win transient--window) - (remain-in-minibuffer-window - (and (minibuffer-selected-window) - (selected-window)))) - (cond - ((eq (car (window-parameter win 'quit-restore)) 'other) - ;; Window used to display another buffer. - (set-window-parameter win 'no-other-window - (window-parameter win 'prev--no-other-window)) - (set-window-parameter win 'prev--no-other-window nil)) - ((with-demoted-errors "Error while exiting transient: %S" - (if (window-parent win) - (delete-window win) - (delete-frame (window-frame win) t))))) - (when remain-in-minibuffer-window - (select-window remain-in-minibuffer-window)))) - (when (buffer-live-p transient--buffer) - (kill-buffer transient--buffer)) - (setq transient--buffer nil)) - (defun transient--export () (setq transient-current-prefix transient--prefix) (setq transient-current-command (oref transient--prefix command)) @@ -2520,12 +2539,7 @@ value. Otherwise return CHILDREN as is.") (defun transient--suspend-override (&optional nohide) (transient--debug 'suspend-override) (transient--timer-cancel) - (let ((show (if nohide 'fixed transient-show-during-minibuffer-read))) - (when (and (integerp show) - (< (frame-height (window-frame transient--window)) - (+ (abs show) - (window-height transient--window)))) - (setq show (natnump show))) + (let ((show (transient--preserve-window-p nohide))) (cond ((not show) (transient--delete-window)) ((and transient--prefix transient--redisplay-key) @@ -2598,9 +2612,11 @@ value. Otherwise return CHILDREN as is.") ,@body))) (defun transient--wrap-command () + (transient--load-command-if-autoload this-command) (letrec - ((prefix transient--prefix) - (suffix this-command) + ((command this-command) + (suffix (transient-suffix-object this-command)) + (prefix transient--prefix) (advice (lambda (fn &rest args) (interactive @@ -2608,7 +2624,7 @@ value. Otherwise return CHILDREN as is.") (let ((abort t)) (unwind-protect (prog1 (let ((debugger #'transient--exit-and-debug)) - (if-let* ((obj (transient-suffix-object suffix)) + (if-let* ((obj suffix) (grp (oref obj parent)) (adv (or (oref obj advice*) (oref grp advice*)))) @@ -2619,26 +2635,30 @@ value. Otherwise return CHILDREN as is.") (when abort (when-let* ((unwind (oref prefix unwind-suffix))) (transient--debug 'unwind-interactive) - (funcall unwind suffix)) - (advice-remove suffix advice) + (funcall unwind command)) + (when (symbolp command) + (remove-function (symbol-function command) advice)) (oset prefix unwind-suffix nil)))))) (unwind-protect (let ((debugger #'transient--exit-and-debug)) - (if-let* ((obj (transient-suffix-object suffix)) + (if-let* ((obj suffix) (grp (oref obj parent)) (adv (or (oref obj advice) - (oref grp advice) (oref obj advice*) + (oref grp advice) (oref grp advice*)))) (apply adv fn args) (apply fn args))) (when-let* ((unwind (oref prefix unwind-suffix))) (transient--debug 'unwind-command) - (funcall unwind suffix)) - (advice-remove suffix advice) + (funcall unwind command)) + (when (symbolp command) + (remove-function (symbol-function command) advice)) (oset prefix unwind-suffix nil))))) - (when (symbolp this-command) - (advice-add suffix :around advice '((depth . -99)))))) + (add-function :around (if (symbolp this-command) + (symbol-function this-command) + this-command) + advice '((depth . -99))))) (defun transient--premature-post-command () (and (equal (this-command-keys-vector) []) @@ -2683,7 +2703,8 @@ value. Otherwise return CHILDREN as is.") (transient--env-apply #'transient--redisplay))))) (setq transient-current-prefix nil) (setq transient-current-command nil) - (setq transient-current-suffixes nil))) + (setq transient-current-suffixes nil) + (setq transient--current-suffix nil))) (defun transient--post-exit (&optional command) (transient--debug 'post-exit) @@ -2719,7 +2740,8 @@ value. Otherwise return CHILDREN as is.") (when command (setq transient-current-prefix nil) (setq transient-current-command nil) - (setq transient-current-suffixes nil)) + (setq transient-current-suffixes nil) + (setq transient--current-suffix nil)) (when resume (transient--stack-pop)))) @@ -2728,9 +2750,9 @@ value. Otherwise return CHILDREN as is.") (push (list (oref transient--prefix command) transient--layout transient--editp - :transient-suffix (oref transient--prefix transient-suffix) - :scope (oref transient--prefix scope) - :value (transient-get-value)) + :value (transient-get-value) + :return (oref transient--prefix return) + :scope (oref transient--prefix scope)) transient--stack)) (defun transient--stack-pop () @@ -2757,7 +2779,7 @@ value. Otherwise return CHILDREN as is.") (not (zerop transient-show-popup)) (not transient--timer)) (transient--timer-start)) - (transient--show-brief))) + (transient--show-hint))) (defun transient--timer-start () (setq transient--timer @@ -2838,14 +2860,19 @@ exit." (oref transient--prefix transient-non-suffix) t)))) -(defun transient--resolve-pre-command (pre &optional resolve-boolean) - (cond ((booleanp pre) - (if resolve-boolean - (if pre #'transient--do-stay #'transient--do-warn) - pre)) - ((string-match-p "--do-" (symbol-name pre)) pre) - ((let ((sym (intern (format "transient--do-%s" pre)))) - (if (functionp sym) sym pre))))) +(defun transient--resolve-pre-command (pre &optional resolve-boolean correct) + (setq pre (cond ((booleanp pre) + (if resolve-boolean + (if pre #'transient--do-stay #'transient--do-warn) + pre)) + ((string-match-p "--do-" (symbol-name pre)) pre) + ((let ((sym (intern (format "transient--do-%s" pre)))) + (if (functionp sym) sym pre))))) + (cond ((not correct) pre) + ((and (eq pre 'transient--do-return) + (not transient--stack)) + 'transient--do-exit) + (pre))) (defun transient--do-stay () "Call the command without exporting variables and stay transient." @@ -2897,29 +2924,19 @@ Use that command's pre-command to determine transient behavior." (not (eq (posn-window (event-start last-command-event)) transient--window))) transient--stay - (setq this-command - (with-selected-window transient--window - (get-text-property (if (mouse-event-p last-command-event) - (posn-point (event-start last-command-event)) - (point)) - 'command))) + (with-selected-window transient--window + (let ((pos (if (mouse-event-p last-command-event) + (posn-point (event-start last-command-event)) + (point)))) + (setq this-command (get-text-property pos 'command)) + (setq transient--current-suffix (get-text-property pos 'suffix)))) (transient--call-pre-command))) (defun transient--do-recurse () - "Call the transient prefix command, preparing for return to active transient. + "Call the transient prefix command, preparing for return to outer transient. If there is no parent prefix, then just call the command." (transient--do-stack)) -(defun transient--setup-recursion (prefix-obj) - (when transient--stack - (let ((command (oref prefix-obj command))) - (when-let* ((suffix-obj (transient-suffix-object command))) - (when (memq (if (slot-boundp suffix-obj 'transient) - (oref suffix-obj transient) - (oref transient-current-prefix transient-suffix)) - (list t #'transient--do-recurse)) - (oset prefix-obj transient-suffix t)))))) - (defun transient--do-stack () "Call the transient prefix command, stacking the active transient. Push the active transient to the transient stack." @@ -2987,8 +3004,8 @@ prefix argument and pivot to `transient-update'." (put 'transient--do-exit 'transient-face 'transient-key-exit) (put 'transient--do-leave 'transient-face 'transient-key-exit) -(put 'transient--do-recurse 'transient-face 'transient-key-stay) -(put 'transient--do-stack 'transient-face 'transient-key-stay) +(put 'transient--do-recurse 'transient-face 'transient-key-recurse) +(put 'transient--do-stack 'transient-face 'transient-key-stack) (put 'transient--do-replace 'transient-face 'transient-key-exit) (put 'transient--do-suspend 'transient-face 'transient-key-exit) @@ -3107,7 +3124,8 @@ transient is active." ;;;; Help (defun transient-help (&optional interactive) - "Show help for the active transient or one of its suffixes.\n\n(fn)" + "Show help for the active transient or one of its suffixes. +\n(fn)" (interactive (list t)) (if interactive (setq transient--helpp t) @@ -3832,6 +3850,18 @@ Append \"=\ to ARG to indicate that it is an option." (or (match-string 1 match) ""))) (and (member arg args) t))) +;;; Return + +(defun transient-init-return (obj) + (when-let* ((transient--stack) + (command (oref obj command)) + (suffix-obj (transient-suffix-object command)) + ((memq (if (slot-boundp suffix-obj 'transient) + (oref suffix-obj transient) + (oref transient-current-prefix transient-suffix)) + (list t 'recurse #'transient--do-recurse)))) + (oset obj return t))) + ;;; Scope ;;;; Init @@ -3936,38 +3966,11 @@ have a history of their own.") (cons val (delete val (alist-get (transient--history-key obj) transient-history)))))) -;;; Draw +;;; Display -(defun transient--show-brief () +(defun transient--show-hint () (let ((message-log-max nil)) - (if (and transient-show-popup (<= transient-show-popup 0)) - (message "%s-" (key-description (this-command-keys))) - (message - "%s- [%s] %s" - (key-description (this-command-keys)) - (oref transient--prefix command) - (mapconcat - #'identity - (sort - (mapcan - (lambda (suffix) - (let ((key (kbd (oref suffix key)))) - ;; Don't list any common commands. - (and (not (memq (oref suffix command) - `(,(lookup-key transient-map key) - ,(lookup-key transient-sticky-map key) - ;; From transient-common-commands: - transient-set - transient-save - transient-history-prev - transient-history-next - transient-quit-one - transient-toggle-common - transient-set-level))) - (list (propertize (oref suffix key) 'face 'transient-key))))) - transient--suffixes) - #'string<) - (propertize "|" 'face 'transient-delimiter)))))) + (message "%s" (transient--format-hint)))) (defun transient--show () (transient--timer-cancel) @@ -3983,28 +3986,7 @@ have a history of their own.") (button-get (1- (point)) 'command)) (transient--heading-at-point)))) (erase-buffer) - (when setup - (when transient-force-fixed-pitch - (transient--force-fixed-pitch)) - (when (bound-and-true-p tab-line-format) - (setq tab-line-format nil)) - (setq header-line-format nil) - (setq mode-line-format - (let ((format (transient--mode-line-format))) - (if (or (natnump format) (eq format 'line)) nil format))) - (setq mode-line-buffer-identification - (symbol-name (oref transient--prefix command))) - (if transient-enable-popup-navigation - (setq-local cursor-in-non-selected-windows 'box) - (setq cursor-type nil)) - (setq display-line-numbers nil) - (setq show-trailing-whitespace nil) - (run-hooks 'transient-setup-buffer-hook)) - (transient--insert-groups) - (when (or transient--helpp transient--editp) - (transient--insert-help)) - (when-let* ((line (transient--separator-line))) - (insert line))) + (transient--insert-menu setup)) (unless (window-live-p transient--window) (setq transient--window (display-buffer transient--buffer @@ -4058,6 +4040,96 @@ have a history of their own.") (window-body-width window t) (window-body-height window t)))) +;;; Delete + +(defun transient--delete-window () + (when (window-live-p transient--window) + (let ((win transient--window) + (remain-in-minibuffer-window + (and (minibuffer-selected-window) + (selected-window)))) + (cond + ((eq (car (window-parameter win 'quit-restore)) 'other) + ;; Window used to display another buffer. + (set-window-parameter win 'no-other-window + (window-parameter win 'prev--no-other-window)) + (set-window-parameter win 'prev--no-other-window nil)) + ((with-demoted-errors "Error while exiting transient: %S" + (if (window-parent win) + (delete-window win) + (delete-frame (window-frame win) t))))) + (when remain-in-minibuffer-window + (select-window remain-in-minibuffer-window)))) + (when (buffer-live-p transient--buffer) + (kill-buffer transient--buffer)) + (setq transient--buffer nil)) + +(defun transient--preserve-window-p (&optional nohide) + (let ((show (if nohide 'fixed transient-show-during-minibuffer-read))) + (when (and (integerp show) + (window-live-p transient--window) + (< (frame-height (window-frame transient--window)) + (+ (abs show) + (window-height transient--window)))) + (setq show (natnump show))) + show)) + +;;; Format + +(defun transient--format-hint () + (if (and transient-show-popup (<= transient-show-popup 0)) + (format "%s-" (key-description (this-command-keys))) + (format + "%s- [%s] %s" + (key-description (this-command-keys)) + (oref transient--prefix command) + (mapconcat + #'identity + (sort + (mapcan + (lambda (suffix) + (let ((key (kbd (oref suffix key)))) + ;; Don't list any common commands. + (and (not (memq (oref suffix command) + `(,(lookup-key transient-map key) + ,(lookup-key transient-sticky-map key) + ;; From transient-common-commands: + transient-set + transient-save + transient-history-prev + transient-history-next + transient-quit-one + transient-toggle-common + transient-set-level))) + (list (propertize (oref suffix key) 'face 'transient-key))))) + transient--suffixes) + #'string<) + (propertize "|" 'face 'transient-delimiter))))) + +(defun transient--insert-menu (setup) + (when setup + (when transient-force-fixed-pitch + (transient--force-fixed-pitch)) + (when (bound-and-true-p tab-line-format) + (setq tab-line-format nil)) + (setq header-line-format nil) + (setq mode-line-format + (let ((format (transient--mode-line-format))) + (if (or (natnump format) (eq format 'line)) nil format))) + (setq mode-line-buffer-identification + (symbol-name (oref transient--prefix command))) + (if transient-enable-popup-navigation + (setq-local cursor-in-non-selected-windows 'box) + (setq cursor-type nil)) + (setq display-line-numbers nil) + (setq show-trailing-whitespace nil) + (run-hooks 'transient-setup-buffer-hook)) + (transient--insert-groups) + (when (or transient--helpp transient--editp) + (transient--insert-help)) + (when-let* ((line (transient--separator-line))) + (insert line))) + (defun transient--mode-line-format () (if (slot-boundp transient--prefix 'mode-line-format) (oref transient--prefix mode-line-format) @@ -4330,6 +4402,7 @@ apply the face `transient-unreachable' to the complete string." (funcall (oref transient--prefix suffix-description) obj))))) (when-let* ((transient--docsp) + ((slot-boundp obj 'command)) (cmd (oref obj command)) ((not (memq 'transient--default-infix-command (function-alias-p cmd)))) @@ -4708,7 +4781,8 @@ This is used when a tooltip is needed.") ((doc (cond ((functionp summary) (funcall summary obj)) (summary) - ((car (split-string (documentation command) "\n"))))) + ((documentation command) + (car (split-string (documentation command) "\n"))))) ((stringp doc)) ((not (equal doc (car (split-string (documentation @@ -4880,22 +4954,22 @@ search instead." (remove-hook 'transient-exit-hook #'transient--resume-which-key-mode))) (defun transient-bind-q-to-quit () - "Modify some keymaps to bind \"q\" to the appropriate quit command. + "Modify some keymaps to bind \\`q' to the appropriate quit command. -\"C-g\" is the default binding for such commands now, but Transient's -predecessor Magit-Popup used \"q\" instead. If you would like to get +\\`C-g' is the default binding for such commands now, but Transient's +predecessor Magit-Popup used \\`q' instead. If you would like to get that binding back, then call this function in your init file like so: (with-eval-after-load \\='transient (transient-bind-q-to-quit)) -Individual transients may already bind \"q\" to something else +Individual transients may already bind \\`q' to something else and such a binding would shadow the quit binding. If that is the -case then \"Q\" is bound to whatever \"q\" would have been bound +case then \\`Q' is bound to whatever \\`q' would have been bound to by setting `transient-substitute-key-function' to a function -that does that. Of course \"Q\" may already be bound to something -else, so that function binds \"M-q\" to that command instead. -Of course \"M-q\" may already be bound to something else, but +that does that. Of course \\`Q' may already be bound to something +else, so that function binds \\`M-q' to that command instead. +Of course \\`M-q' may already be bound to something else, but we stop there." (keymap-set transient-base-map "q" #'transient-quit-one) (keymap-set transient-sticky-map "q" #'transient-quit-seq) commit f3925732bc55be3d6d32d20b95f7a88926d4ebda Author: Juri Linkov Date: Sat Mar 15 20:37:46 2025 +0200 Add the treesit 'list' thing to csharp/go/php/rust-ts-mode (bug#73404) * lisp/progmodes/csharp-mode.el (csharp-ts-mode): Add the 'list' and 'sentence' things to 'treesit-thing-settings'. Set 'treesit-outline-predicate'. * lisp/progmodes/go-ts-mode.el (go-ts-mode): Add 'treesit-thing-settings' with the 'list' and 'sentence' things. * lisp/progmodes/php-ts-mode.el (php-ts-mode): Add the 'list' thing to 'treesit-thing-settings'. * lisp/progmodes/rust-ts-mode.el (rust-ts-mode): Add 'treesit-thing-settings' with the 'list' thing. diff --git a/lisp/progmodes/csharp-mode.el b/lisp/progmodes/csharp-mode.el index 6f3fd08089f..5e9b8d680eb 100644 --- a/lisp/progmodes/csharp-mode.el +++ b/lisp/progmodes/csharp-mode.el @@ -1067,6 +1067,68 @@ Key bindings: (setq-local treesit-thing-settings `((c-sharp + (list + ,(rx bos (or "global_attribute" + "attribute_argument_list" + "attribute_list" + "enum_member_declaration_list" + "type_parameter_list" + "declaration_list" + "accessor_list" + "bracketed_parameter_list" + "parameter_list" + "argument_list" + "tuple_pattern" + "block" + "bracketed_argument_list" + "type_argument_list" + "array_rank_specifier" + "function_pointer_type" + "tuple_type" + "_for_statement_conditions" + "switch_body" + "catch_declaration" + "catch_filter_clause" + "parenthesized_pattern" + "list_pattern" + "positional_pattern_clause" + "property_pattern_clause" + "parenthesized_variable_designation" + "_switch_expression_body" + "interpolated_string_expression" + "interpolation" + "parenthesized_expression" + "_parenthesized_lvalue_expression" + "anonymous_object_creation_expression" + "initializer_expression" + "_with_body" + "tuple_expression" + "preproc_parenthesized_expression") + eos)) + (sentence + ,(rx bos (or "extern_alias_directive" + "using_directive" + "file_scoped_namespace_declaration" + "enum_declaration" + "delegate_declaration" + "_declaration_list_body" + "field_declaration" + "event_declaration" + "event_field_declaration" + "indexer_declaration" + "property_declaration" + "_function_body" + "break_statement" + "continue_statement" + "do_statement" + "empty_statement" + "expression_statement" + "return_statement" + "yield_statement" + "throw_statement" + "goto_statement" + "local_declaration_statement") + eos)) (text ,(regexp-opt '("comment" "verbatim_string-literal" @@ -1100,6 +1162,18 @@ Key bindings: ("Struct" "\\`struct_declaration\\'" nil nil) ("Method" "\\`method_declaration\\'" nil nil))) + ;; Outline minor mode. + (setq-local treesit-outline-predicate + (rx bos (or "namespace_declaration" + "class_declaration" + "interface_declaration" + "enum_declaration" + "record_declaration" + "struct_declaration" + "method_declaration" + "local_function_statement") + eos)) + (treesit-major-mode-setup) (add-to-list 'auto-mode-alist '("\\.cs\\'" . csharp-ts-mode))) diff --git a/lisp/progmodes/go-ts-mode.el b/lisp/progmodes/go-ts-mode.el index 7abf9ddaeb5..d117fe21590 100644 --- a/lisp/progmodes/go-ts-mode.el +++ b/lisp/progmodes/go-ts-mode.el @@ -290,6 +290,25 @@ "type_declaration"))) (setq-local treesit-defun-name-function #'go-ts-mode--defun-name) + (setq-local treesit-thing-settings + `((go + (list + ,(rx bos (or "import_spec_list" + "var_spec_list" + "type_parameter_list" + "parameter_list" + "parenthesized_type" + "type_arguments" + "field_declaration_list" + "block" + "parenthesized_expression" + "special_argument_list" + "argument_list" + "literal_value") + eos)) + (sentence + (or "declaration" "statement"))))) + ;; Imenu. (setq-local treesit-simple-imenu-settings `(("Function" "\\`function_declaration\\'" nil nil) diff --git a/lisp/progmodes/php-ts-mode.el b/lisp/progmodes/php-ts-mode.el index a5d81449ae5..e948406d9b4 100644 --- a/lisp/progmodes/php-ts-mode.el +++ b/lisp/progmodes/php-ts-mode.el @@ -1470,6 +1470,22 @@ Depends on `c-ts-common-comment-setup'." `((php (defun ,treesit-defun-type-regexp) (sexp (not ,(rx (or "{" "}" "[" "]" "(" ")" ",")))) + (list + ,(rx bos (or "namespace_use_group" + "enum_declaration_list" + "declaration_list" + "property_hook_list" + "use_list" + "anonymous_function_use_clause" + "formal_parameters" + "match_block" + "switch_block" + "compound_statement" + "parenthesized_expression" + "_array_destructing" + "arguments" + "_complex_string_part") + eos)) (sentence ,(regexp-opt '("break_statement" "case_statement" diff --git a/lisp/progmodes/rust-ts-mode.el b/lisp/progmodes/rust-ts-mode.el index 0fe5fcc083b..ae2bc8ee78c 100644 --- a/lisp/progmodes/rust-ts-mode.el +++ b/lisp/progmodes/rust-ts-mode.el @@ -597,6 +597,40 @@ See `prettify-symbols-compose-predicate'." "struct_item"))) (setq-local treesit-defun-name-function #'rust-ts-mode--defun-name) + (setq-local treesit-thing-settings + `((rust + (list + ,(rx bos (or "token_tree_pattern" + "token_tree" + "attribute_item" + "inner_attribute_item" + "declaration_list" + "enum_variant_list" + "field_declaration_list" + "ordered_field_declaration_list" + "type_parameters" + "use_list" + "parameters" + "bracketed_type" + "array_type" + "for_lifetimes" + "tuple_type" + "unit_type" + "use_bounds" + "type_arguments" + "delim_token_tree" + "arguments" + "array_expression" + "parenthesized_expression" + "tuple_expression" + "unit_expression" + "field_initializer_list" + "match_block" + "block" + "tuple_pattern" + "slice_pattern") + eos))))) + (treesit-major-mode-setup))) (derived-mode-add-parents 'rust-ts-mode '(rust-mode)) commit 4f4105e6227cdb00109efb683d96beb47fd894c4 Author: Eshel Yaron Date: Sat Mar 15 18:24:28 2025 +0100 ; * lisp/winner.el (winner--set-dont-bind-my-keys): Fix typo. diff --git a/lisp/winner.el b/lisp/winner.el index 7fcb44131e6..a052ef00945 100644 --- a/lisp/winner.el +++ b/lisp/winner.el @@ -49,7 +49,7 @@ (when (boundp 'winner-mode-map) (if value (progn (keymap-unset winner-mode-map "C-c ") - (keymap-unset winner-mode-map "C-c ")) + (keymap-unset winner-mode-map "C-c ")) ;; Default bindings. (keymap-set winner-mode-map "C-c " #'winner-undo) (keymap-set winner-mode-map "C-c " #'winner-redo))) commit 3860562a715b8ba7b727f7831e06ce5a0e676784 Author: Gerd Möllmann Date: Sat Mar 15 14:52:51 2025 +0100 ; * lisp/help-fns.el (help-fns-edit-variable): Revert buffer. diff --git a/lisp/help-fns.el b/lisp/help-fns.el index 1af37df3792..fabdda521dc 100644 --- a/lisp/help-fns.el +++ b/lisp/help-fns.el @@ -1569,7 +1569,8 @@ it is displayed along with the global value." (let ((str (read-string-from-buffer (format ";; Edit the `%s' variable." (nth 0 var)) (prin1-to-string (nth 1 var))))) - (set (nth 0 var) (read str))))) + (set (nth 0 var) (read str)) + (revert-buffer)))) (autoload 'shortdoc-help-fns-examples-function "shortdoc") commit 9e670088c70f1606d0bd089017bdba960eda87e1 Merge: 515e242c3dc 1a8fbf69e33 Author: Eli Zaretskii Date: Sat Mar 15 09:22:51 2025 -0400 Merge from origin/emacs-30 1a8fbf69e33 Fix 'whitespace-mode' in CJK locales 01c03043e6e ; Improve documentation of 'gui-get-selection' use on X commit 515e242c3dc6155bf728aac396d0cc32d14dbbf9 Merge: ea49eb4752a 66ec9ae7195 Author: Eli Zaretskii Date: Sat Mar 15 09:22:51 2025 -0400 ; Merge from origin/emacs-30 The following commit was skipped: 66ec9ae7195 ; Fix documentation of mouse-click events commit ea49eb4752a0632d8d0dfd4215821ab21bbd5a99 Author: Eli Zaretskii Date: Sat Mar 15 13:58:44 2025 +0200 Improve conversion between half-width and full-width characters * lisp/textmodes/text-mode.el (text-mode--get-fullwidth-table): Add conversion of SPC to IDEOGRAPHIC SPACE. (Bug#71822) diff --git a/lisp/textmodes/text-mode.el b/lisp/textmodes/text-mode.el index 3b27efbcf87..2d9bb9fb784 100644 --- a/lisp/textmodes/text-mode.el +++ b/lisp/textmodes/text-mode.el @@ -286,6 +286,9 @@ The argument NLINES says how many lines to center." (aset tbl ch (+ ch #xFEE0)) (aset rev-tbl (+ ch #xFEE0) ch) (setq ch (1+ ch))) + ;; SPC -> U+3000 IDEOGRAPHIC SPACE + (aset tbl ?\ #x3000) + (aset rev-tbl #x3000 ?\ ) (set-char-table-extra-slot tbl 0 rev-tbl) (set-char-table-extra-slot tbl 1 1) (set-char-table-extra-slot rev-tbl 1 1) commit 52e49a5616011173a6bd52e8501cd720e262d3af Merge: 4b8b7bb5cfc a57e9f45db6 Author: Michael Albinus Date: Sat Mar 15 12:52:37 2025 +0100 Merge branch 'master' of git.sv.gnu.org:/srv/git/emacs commit 4b8b7bb5cfc27b88fdf892a87931f5b89ff8c4a3 Author: Michael Albinus Date: Sat Mar 15 12:43:35 2025 +0100 ; Minor Tramp changes * doc/misc/tramp.texi (Top): Add Key Index to menu. (Key Index): New node. * lisp/net/tramp-cmds.el (tramp-dired-buffer-command-completion-p): New defun. (tramp-dired-find-file-with-sudo): Add property `completion-predicate'. diff --git a/doc/misc/tramp.texi b/doc/misc/tramp.texi index 6e66de552de..a2f998753b7 100644 --- a/doc/misc/tramp.texi +++ b/doc/misc/tramp.texi @@ -101,6 +101,7 @@ For the developer: * GNU Free Documentation License:: The license for this documentation. * Function Index:: @value{tramp} functions. * Variable Index:: User options and variables. +* Key Index:: Key bindings for @value{tramp} commands. * Concept Index:: An item for each concept. @detailmenu @@ -6811,6 +6812,11 @@ maintainers, analyzing the remote commands for performance analysis. @printindex vr +@node Key Index +@unnumbered Key Index +@printindex ky + + @node Concept Index @unnumbered Concept Index @printindex cp diff --git a/lisp/net/tramp-cmds.el b/lisp/net/tramp-cmds.el index be122642b96..d9903783f2f 100644 --- a/lisp/net/tramp-cmds.el +++ b/lisp/net/tramp-cmds.el @@ -708,14 +708,30 @@ If the buffer runs `dired', the buffer is reverted." (dired-advertise) (revert-buffer))))) +;; This function takes action, when `read-extended-command-predicate' +;; is set to `command-completion-default-include-p'. +(defun tramp-dired-buffer-command-completion-p (_symbol buffer) + "A predicate for Tramp interactive commands. +They are completed by `M-x TAB' only in Dired buffers." + (declare (tramp-suppress-trace t)) + (with-current-buffer buffer + (tramp-dired-buffer-p))) + ;;;###autoload (defun tramp-dired-find-file-with-sudo () "In Dired, visit the file or directory named on this line. This is performed with \"sudo\" permissions." + ;; (declare (completion tramp-dired-buffer-command-completion-p)) (interactive) (with-tramp-file-name-with-method (find-file (tramp-file-name-with-sudo (dired-get-file-for-visit))))) +;; `tramp-dired-buffer-command-completion-p' is not autoloaded, and this +;; setting isn't either. +(function-put + #'tramp-dired-find-file-with-sudo 'completion-predicate + #'tramp-dired-buffer-command-completion-p) + ;;; Recompile on ELPA ;; This function takes action, when `read-extended-command-predicate' commit 1a8fbf69e3378996e633cf299b6a2d0716722d86 Author: Eli Zaretskii Date: Sat Mar 15 13:42:12 2025 +0200 Fix 'whitespace-mode' in CJK locales * lisp/international/characters.el (ambiguous-width-chars): Remove U+00A4 and U+00B7 from the list of ambiguous-width characters. (cjk-ambiguous-chars-are-wide): Doc fix. (Bug#76852) diff --git a/lisp/international/characters.el b/lisp/international/characters.el index 318957c15b9..7be8bf32a93 100644 --- a/lisp/international/characters.el +++ b/lisp/international/characters.el @@ -1397,12 +1397,14 @@ with L, LRE, or LRO Unicode bidi character type.") ;; A: East Asian "Ambiguous" characters. (let ((l '((#x00A1 . #x00A1) - (#x00A4 . #x00A4) + ; (#x00A4 . #x00A4) whitespace-mode uses this (#x00A7 . #x00A8) (#x00AA . #x00AA) (#x00AD . #x00AE) (#x00B0 . #x00B4) - (#x00B6 . #x00BA) + ; (#x00B6 . #x00BA) whitespace-mode uses U+00B7 + (#x00B6 . #x00B6) + (#x00B8 . #x00BA) (#x00BC . #x00BF) (#x00C6 . #x00C6) (#x00D0 . #x00D0) @@ -1620,6 +1622,10 @@ fonts being used. In some CJK locales the fonts are set so that these characters are displayed as full-width. This setting is most important for text-mode frames, because there Emacs cannot access the metrics of the fonts used by the console or the terminal emulator. +You should configure the terminal emulator to behave consistently +with the value of this option, by making sure it dispays ambiguous-width +characters as half-width or full-width, depending on the value of this +option. Do not set this directly via `setq'; instead, use `setopt' or the Customize commands. Alternatively, call `update-cjk-ambiguous-char-widths' commit a57e9f45db6007bdb4af21a444f0ed62d1b2010e Author: Sean Whitton Date: Sat Mar 15 19:32:35 2025 +0800 ; Touch up tramp-*-with-sudo documentation, add FIXMEs * doc/emacs/dired.texi (Dired Visiting): * doc/emacs/files.texi (Reverting): * doc/misc/tramp.texi (Ad-hoc multi-hops): * etc/NEWS: * lisp/net/tramp-cmds.el (tramp-revert-buffer-with-sudo) (tramp-dired-find-file-with-sudo): Touch up documentation of these features. Add FIXMEs about renaming the two commands. diff --git a/doc/emacs/dired.texi b/doc/emacs/dired.texi index 0d7d7c4cf26..602c8e5bfb2 100644 --- a/doc/emacs/dired.texi +++ b/doc/emacs/dired.texi @@ -463,10 +463,11 @@ navigate the buffer but forbids changing it; @xref{View Mode}. @item @@ @kindex @@ @r{(Dired)} @findex tramp-dired-find-file-with-sudo -Open the file described on the current line, with root permissions -(@code{tramp-dired-find-file-with-sudo}). Calling it with the @kbd{C-u} -prefix argument asks for another Tramp method interactively but -@option{sudo}. @xref{Ad-hoc multi-hops, Tramp,, tramp, The Tramp Manual}. +Open the file described on the current line, with superuser, or root, +permissions (@code{tramp-dired-find-file-with-sudo}). If called with a +@kbd{C-u} prefix argument, it prompts for another Tramp method to use +other than the default, @option{sudo}. @xref{Ad-hoc multi-hops, Tramp,, +tramp, The Tramp Manual}. @item ^ @kindex ^ @r{(Dired)} diff --git a/doc/emacs/files.texi b/doc/emacs/files.texi index 9b2ce5c5ee2..6f4f47d6d84 100644 --- a/doc/emacs/files.texi +++ b/doc/emacs/files.texi @@ -1174,14 +1174,16 @@ the major mode actually turned on as result of reverting a buffer depends on mode remapping, and could be different from the original mode if you customized @code{major-mode-remap-alist} in-between. +@cindex reverting with superuser permissions @cindex reverting with root permissions @findex tramp-revert-buffer-with-sudo @kindex C-x x @@ -A variant of reverting a buffer is visiting it by the -@code{tramp-revert-buffer-with-sudo} (@kbd{C-x x @@}) command. It -reopens the file or Dired buffer with root permissions. With a prefix -argument of @kbd{C-u}, you could change the default Tramp method -(@option{sudo}). @xref{Ad-hoc multi-hops, Tramp,, tramp, The Tramp Manual}. +The @kbd{C-x x @@} keystroke is bound to the +@code{tramp-revert-buffer-with-sudo} command. This visits the file +again, but with superuser, or root, permissions. If called with a +@kbd{C-u} prefix argument, it prompts for another Tramp method to use +other than the default, @option{sudo}. @xref{Ad-hoc multi-hops, Tramp,, +tramp, The Tramp Manual}. @node Auto Revert @section Auto Revert: Keeping buffers automatically up-to-date diff --git a/doc/misc/tramp.texi b/doc/misc/tramp.texi index 6e66de552de..97398ebb90c 100644 --- a/doc/misc/tramp.texi +++ b/doc/misc/tramp.texi @@ -3934,9 +3934,8 @@ method. The commands @code{tramp-revert-buffer-with-sudo} (@kbd{C-x x @kindex C-x x @@ @deffn Command tramp-revert-buffer-with-sudo -This command shows the current buffer with @option{sudo} permissions. -The buffer must either visit a file, or a directory -(@code{dired-mode}). +This command visits the current buffer with @option{sudo} permissions. +The buffer must either visit a file, or a directory in @code{dired-mode}. @end deffn @kindex @@ @r{(in dired}) diff --git a/etc/NEWS b/etc/NEWS index 79bbd1e8981..ecdf0597a90 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1084,12 +1084,12 @@ cherry pick via 'tramp-cleanup-connection' or clear them all via +++ *** New command 'tramp-dired-find-file-with-sudo'. This command, bound to '@' in Dired, visits the file or directory on the -recent Dired line with root permissions. +recent Dired line with superuser, or root, permissions. +++ -*** Command 'tramp-revert-buffer-with-sudo' is bound to 'C-x x @' now. -Called with the prefix argument 'C-u', the used Tramp method is asked -interactively. This happens also for 'tramp-dired-find-file-with-sudo'. +*** 'C-x x @' is now bound to 'tramp-revert-buffer-with-sudo'. +You can use 'C-u C-x x @' to select a Tramp method other than the +default, 'sudo'. +++ *** Connection method "kubernetes" supports now optional namespace. diff --git a/lisp/net/tramp-cmds.el b/lisp/net/tramp-cmds.el index be122642b96..b66954eedf0 100644 --- a/lisp/net/tramp-cmds.el +++ b/lisp/net/tramp-cmds.el @@ -683,10 +683,21 @@ An alternative method could be chosen with `tramp-file-name-with-method'." (make-tramp-file-name :method tramp-file-name-with-method :localname filename)))) +;; FIXME: We would like to rename this for Emacs 31.1 to a name that +;; does not encode the default method. It is intended as a generic +;; privilege-elevation command. Some ideas from bug#76974: +;; `tramp-revert-buffer-obtain-root', +;; `tramp-revert-buffer-as-superuser'. + ;;;###autoload (defun tramp-revert-buffer-with-sudo () - "Revert current buffer to visit with \"sudo\" permissions. -An alternative method could be chosen with `tramp-file-name-with-method'. + "Visit the current file again with superuser, or root, permissions. + +By default this is done using the \"sudo\" Tramp method. +You can customize `tramp-file-name-with-method' to change this. + +Interactively, with a prefix argument, prompt for a different method. + If the buffer visits a file, the file is replaced. If the buffer runs `dired', the buffer is reverted." (interactive) @@ -708,10 +719,16 @@ If the buffer runs `dired', the buffer is reverted." (dired-advertise) (revert-buffer))))) +;; FIXME: See FIXME above about renaming this before Emacs 31.1. + ;;;###autoload (defun tramp-dired-find-file-with-sudo () - "In Dired, visit the file or directory named on this line. -This is performed with \"sudo\" permissions." + "Visit the file or directory named on this line as the superuser. + +By default this is done using the \"sudo\" Tramp method. +YOu can customize `tramp-file-name-with-method' to change this. + +Interactively, with a prefix argument, prompt for a different method." (interactive) (with-tramp-file-name-with-method (find-file (tramp-file-name-with-sudo (dired-get-file-for-visit))))) commit 87f9c09ab5fbfa9652aabe2799df654456d5e04c Author: Phil Sainty Date: Wed Feb 19 01:02:22 2025 +1300 ; * lisp/so-long.el: Documentation diff --git a/lisp/so-long.el b/lisp/so-long.el index b572fd46ca9..59a833d2c32 100644 --- a/lisp/so-long.el +++ b/lisp/so-long.el @@ -383,6 +383,43 @@ ;; Finally, the `so-long-predicate' user option enables the automated behavior ;; to be determined by a custom function, if greater control is needed. +;; * Non-file buffers +;; ------------------ +;; As noted in the introduction, `global-so-long-mode' only affects buffers +;; visiting files, and only at the point in time that they are visited. The +;; library does not automatically detect if long lines are inserted into an +;; existing buffer, which means that non-file buffers are not processed at all +;; by the global mode (although the `so-long' command can be invoked manually). +;; To handle such buffers additional glue code will be required, and that code +;; should likely be specific to the particular use-case to avoid unintended +;; behaviours. +;; +;; An example to handle `compilation-mode' (and derivative) buffers follows: +;; +;; ;; Trigger `so-long-minor-mode' for long compile output. +;; (with-eval-after-load 'compile +;; (require 'so-long)) +;; +;; (add-hook 'compilation-mode-hook 'my-so-long-compilation-mode) +;; +;; (defun my-so-long-compilation-mode () +;; "Add `my-so-long-compilation-filter' to local `compilation-filter-hook'." +;; (add-hook 'compilation-filter-hook +;; 'my-so-long-compilation-filter nil :local)) +;; +;; (defun my-so-long-compilation-filter () +;; "Maybe call `so-long-minor-mode' during `compilation-filter-hook'." +;; (let ((start (save-excursion (goto-char compilation-filter-start) +;; (line-beginning-position)))) +;; (when (> (- (point) start) so-long-threshold) +;; (save-restriction +;; (narrow-to-region start (point)) +;; (when (let (so-long-max-lines so-long-skip-leading-comments) +;; (funcall so-long-predicate)) +;; (so-long-minor-mode 1) +;; (remove-hook 'compilation-filter-hook +;; 'my-so-long-compilation-filter :local)))))) + ;; * Implementation notes ;; ---------------------- ;; This library advises `set-auto-mode' (in order to react after Emacs has @@ -1517,14 +1554,14 @@ The variables are set in accordance with what was remembered in `so-long'." (kill-local-variable variable)))) (defun so-long-mode-maintain-preserved-variables () - "Set any \"preserved\" variables. + "Set variables listed in `so-long-mode-preserved-variables'. The variables are set in accordance with what was remembered in `so-long'." (dolist (var (so-long-original 'so-long-mode-preserved-variables)) (so-long-restore-variable var))) (defun so-long-mode-maintain-preserved-minor-modes () - "Enable or disable \"preserved\" minor modes. + "Enable or disable modes listed in `so-long-mode-preserved-minor-modes'. The modes are set in accordance with what was remembered in `so-long'." (dolist (mode (so-long-original 'so-long-mode-preserved-minor-modes)) commit 25eabcde0d6ec7d8c161c2e05a5600b1c22fa43d Author: Phil Sainty Date: Wed Feb 19 01:07:04 2025 +1300 ; * lisp/so-long.el: Backwards-compatibility fix for Emacs < 30 We support Emacs 24.4 and later (for ELPA releases), so revert the change from commit 7705bdfa5b89f78dab049f73f636b9680a3c12bc diff --git a/lisp/so-long.el b/lisp/so-long.el index 0da9a7c6783..b572fd46ca9 100644 --- a/lisp/so-long.el +++ b/lisp/so-long.el @@ -1716,7 +1716,8 @@ major mode is a member (or derivative of a member) of `so-long-target-modes'. (not so-long--inhibited) (not so-long--calling) (or (eq so-long-target-modes t) - (derived-mode-p so-long-target-modes)) + ;; Maintain `derived-mode-p' compatibility with Emacs < 30. + (apply #'derived-mode-p so-long-target-modes)) (setq so-long-detected-p (funcall so-long-predicate)) ;; `so-long' should be called; but only if and when the buffer is ;; displayed in a window. Long lines in invisible buffers are generally commit 03a6d5a76331e814368f17a61718208242e44bee Author: Phil Sainty Date: Thu Jan 9 21:21:55 2025 +1300 ; * lisp/so-long.el: Backwards-compatibility fix for Emacs < 27 We support Emacs 24.4 and later (for ELPA releases), and ad-find-advice only changed from a macro to a function in Emacs 27, so this change is to restore compatibility with older versions where we do need the macro definition loaded at byte-compilation time. This effectively combines the related changes from: - commit 986c12b20fa29c37f13563846fddf6edcd0b4945 - commit c221db0402031c23b983eea3a6bc129e5abb98f6 diff --git a/lisp/so-long.el b/lisp/so-long.el index 1a2de984e47..0da9a7c6783 100644 --- a/lisp/so-long.el +++ b/lisp/so-long.el @@ -2028,7 +2028,10 @@ If it appears in `%s', you should remove it." ;; Update to version 1.0 from earlier versions: (when (version< so-long-version "1.0") (remove-hook 'change-major-mode-hook 'so-long-change-major-mode) - (require 'advice) ;; It should already be loaded, but just in case. + (require 'advice) + ;; `ad-find-advice' is a macro in Emacs 26 and earlier. + (eval-when-compile (when (< emacs-major-version 27) + (require 'advice))) (declare-function ad-find-advice "advice") (declare-function ad-remove-advice "advice") (declare-function ad-activate "advice") commit 01c03043e6e93dd12570a960ca54108d543578db Author: Eli Zaretskii Date: Sat Mar 15 11:48:36 2025 +0200 ; Improve documentation of 'gui-get-selection' use on X * doc/lispref/frames.texi (Window System Selections): * lisp/select.el (gui-get-selection): Suggest to use an explicit DATA-TYPE when calling 'gui-get-selection'. diff --git a/doc/lispref/frames.texi b/doc/lispref/frames.texi index 39bfed48e4e..bc7a7e6c7c7 100644 --- a/doc/lispref/frames.texi +++ b/doc/lispref/frames.texi @@ -4079,8 +4079,14 @@ programs. It takes two optional arguments, @var{type} and The @var{data-type} argument specifies the form of data conversion to use, to convert the raw data obtained from another program into Lisp -data. @xref{X Selections}, for an enumeration of data types valid under -X, and @pxref{Other Selections} for those elsewhere. +data. It defaults to @code{STRING}. @xref{X Selections}, for an +enumeration of data types valid on X, and @pxref{Other Selections} for +those elsewhere. On X Window system, we recommend to always specify a +particular @var{data-type}, especially if the selection is expected to +be non-ASCII text (in which case Lisp programs should prefer +@code{UTF8_STRING} as the value of @var{data-type}). This is because +the default @var{data-type} value, @code{STRING}, can only support +Latin-1 text, which in many cases is nowadays inadequate. @end defun @defopt selection-coding-system diff --git a/lisp/select.el b/lisp/select.el index d8e97f33001..59eedc79263 100644 --- a/lisp/select.el +++ b/lisp/select.el @@ -372,10 +372,15 @@ all upper-case names. The most often used ones, in addition to `PRIMARY', are `SECONDARY' and `CLIPBOARD'. DATA-TYPE is usually `STRING', but can also be one of the symbols -in `selection-converter-alist', which see. Window systems other -than X usually support only a small subset of these symbols, in -addition to `STRING'; MS-Windows supports `TARGETS', which reports -the formats available in the clipboard if TYPE is `CLIPBOARD'." +in `selection-converter-alist', which see. On X, we recommend +to always use a specific DATA-TYPE expected from the selection +owner. In particular, if the data is expected to be non-ASCII +text, in many cases using \\='UTF8_STRING is the most reasonable +value for DATA-TYPE. + +Window systems other than X usually support only a small subset of +these symbols, in addition to `STRING'; MS-Windows supports `TARGETS', +which reports the formats available in the clipboard if TYPE is `CLIPBOARD'." (let ((data (gui-backend-get-selection (or type 'PRIMARY) (or data-type 'STRING)))) (when (and (stringp data) commit 5d1b696e9953f69ad918af094e5d09562a4deeaf Author: Michael Albinus Date: Sat Mar 15 09:57:39 2025 +0100 * lisp/bindings.el (tramp-revert-buffer-with-sudo): Declare. diff --git a/lisp/bindings.el b/lisp/bindings.el index a5737db840b..5764143673c 100644 --- a/lisp/bindings.el +++ b/lisp/bindings.el @@ -25,6 +25,8 @@ ;;; Code: +(declare-function tramp-revert-buffer-with-sudo "tramp-cmds") + (defun make-mode-line-mouse-map (mouse function) "\ Return a keymap with single entry for mouse key MOUSE on the mode line. MOUSE is defined to run function FUNCTION with no args in the buffer commit f69cc74c897b2e9c3eb0ad9a140743ad4d69a019 Author: Eli Zaretskii Date: Sat Mar 15 10:52:45 2025 +0200 ; Fix last change (bug#76789) * doc/misc/speedbar.texi (Frames Windows and Faces): Fix node name. Fix markup and punctuation. (Introduction): Fix markup. diff --git a/doc/misc/speedbar.texi b/doc/misc/speedbar.texi index 716135dfc4a..18e0a78857d 100644 --- a/doc/misc/speedbar.texi +++ b/doc/misc/speedbar.texi @@ -126,8 +126,8 @@ frame to displaying it in a window and vice versa simply by using the @cindex @code{speedbar-prefer-window} The user option @code{speedbar-prefer-window} changes the behavior of -the @code{speedbar} command, if set to t the @code{speedbar} command -will display the speedbar in a window instead of in a frame. +the @code{speedbar} command: if set to @code{t}, the @code{speedbar} +command will display the speedbar in a window instead of in a frame. @node Basic Navigation @chapter Basic Navigation @@ -674,14 +674,14 @@ Customize speedbar's many colors and fonts. @end table @menu -* Frames, Windows and Faces:: Visible behaviors. +* Frames Windows and Faces:: Visible behaviors. * Tag Hierarchy Methods:: Customizing how tags are displayed. * Version Control:: Adding new VC detection modes. * Hooks:: The many hooks you can use. @end menu -@node Frames and Faces -@section Frames and Faces +@node Frames Windows and Faces +@section Frames, Windows and Faces @cindex faces @cindex frame parameters @@ -727,14 +727,15 @@ side on which the @code{speedbar-window} is displayed by changing the The size of the @code{speedbar-window}, when opened on the left or right side, can be defined by changing the @code{speedbar-window-default-width} option, which defines the default -window size. The width of the speedbar window can be greater than +window size. The width of the speedbar window can be greater than @code{speedbar-window-max-width}, but if it is closed and later reopened, the width will be equal to @code{speedbar-window-max-width}. @cindex @code{speedbar-window-dedicated-window} By default, @code{speedbar-window} is displayed in a dedicated window, -so @code{display-buffer} will never use this window. Setting -@code{speedbar-window-dedicated-window} to nil can change this behavior. +so @code{display-buffer} will never use this window. Setting +@code{speedbar-window-dedicated-window} to @code{nil} changes this +behavior. @node Tag Hierarchy Methods @section Tag Hierarchy Methods commit c1aab8c49b763abc482a6dbc402782bbe5370da0 Author: Vincenzo Pupillo Date: Thu Mar 6 13:39:01 2025 +0100 Add a new command `speedbar-window'. Speedbar now can be opened in a window instead of a separate frame. The frame remains the default. * doc/emacs/frames.texi: Mention Speedbar window mode. * doc/misc/speedbar.texi: Document 'speedbar-window'. * lisp/speedbar.el (speedbar-prefer-window): New user option. If t, the command `speedbar' open the speedbar in a window. (speedbar-window-dedicated-window): New user option. If t the window is dedicated. (speedbar-window-side): New user option. The side of 'speedbar-window', defaults to left. (speedbar-window-default-width): New user option. The default size of the 'speedbar-window'. (speedbar-window-max-width): New user option. Limits the width of the 'speedbar-window'. The user can resize the window as desired, but this option will be the width of the window when restored. (speedbar--buffer-name): New variable, the buffer name used for both 'speedbar-frame-mode' and 'speedbar-window-mode'. (speedbar--window): New variable, the window displaying 'speedbar-window'. (speedbar--window-width): New variable, store the current width of 'speedbar-window'. (speedbar-easymenu-definition-trailer): Now it is a function that returns a different trailer for 'speedbar-frame' and 'speedbar-window'. (speedbar): Now it is a function that calls 'speedbar-frame-mode', the default or 'speedbar-window-mode' based on the value of 'speedbar-prefer-window'. (speedbar-frame-mode): Before opening a frame, close 'speedbar-window' if it is open. (speedbar-frame-or-window): New function, returns 'frame', 'window' or nil if speedbar is not open. (speedbar-window): New alias for 'speedbar-window-mode'. (speedbar-window-mode): Enable of disable 'speedbar-window'. (speedbar-window--window-live-p): New function, return non-nil if the 'speedbar-window' is live. (speedbar-window--buffer-live-p): New function, return non-nil if the 'speedbar-buffer' is live. (speedbar-window--live-p): New function, return t if 'speedbar-window' is open. (speedbar-window-current-window): New function, return t if the selected window is speedbar-window. (speedbar-window--close): New function, close the 'speedbar-window'. (speedbar-window--width): New function, return the current width of 'speedbar-window'. (speedbar-width): New function, return the 'speedbar' of 'speedbar-frame-mode' of 'speedbar-frame-mode'. (speedbar-set-mode-line-format): Use the new 'speedbar-width' function. (speedbar-directory-buttons): Use the new 'speedbar-width' function. (speedbar--speedbar-live-p): New function, returns t if 'speedbar-frame-mode' or 'speedbar-window-mode' are open. (speedbar-timer-fn): Now handle 'speedbar-frame-mode' and 'speedbar-window-mode'. diff --git a/doc/emacs/frames.texi b/doc/emacs/frames.texi index 9992c39dcc9..1fa6e0b5b33 100644 --- a/doc/emacs/frames.texi +++ b/doc/emacs/frames.texi @@ -964,6 +964,9 @@ select. For example, in Rmail mode, the speedbar shows a list of Rmail files, and lets you move the current message to another Rmail file by clicking on its @samp{} box. +The speedbar can optionally be displayed as a window rather than a +separate frame, in both terminal and graphics mode. + For more details on using and programming the speedbar, @xref{Top, Speedbar,,speedbar, Speedbar Manual}. diff --git a/doc/misc/speedbar.texi b/doc/misc/speedbar.texi index 69c54f5b552..716135dfc4a 100644 --- a/doc/misc/speedbar.texi +++ b/doc/misc/speedbar.texi @@ -46,11 +46,11 @@ information related to the current buffer. Its original inspiration is the ``explorer'' often used in modern development environments, office packages, and web browsers. -Speedbar displays a narrow frame in which a tree view is shown. This -tree view defaults to containing a list of files and directories. Files -can be ``expanded'' to list tags inside. Directories can be expanded to -list the files within them. Each file or tag can be jumped to -immediately. +Speedbar displays a narrow frame or a narrow window in which a tree view +is shown. This tree view defaults to containing a list of files and +directories. Files can be ``expanded'' to list tags inside. +Directories can be expanded to list the files within them. Each file or +tag can be jumped to immediately. Speedbar expands upon ``explorer'' windows by maintaining context with the user. For example, when using the file view, the current buffer's file @@ -114,6 +114,21 @@ The function to use when switching between frames using the keyboard is @code{speedbar-get-focus}. This function will toggle between frames, and it's useful to bind it to a key in terminal mode. @xref{Customizing}. +@cindex @code{speedbar-window} +Optionally, the speedbar can be displayed as a window, splitting the +windows of the selected frame, in both terminal and graphics modes. +Only one speedbar window can be open at a time. + +It is possible to switch from displaying the speedbar in a separate +frame to displaying it in a window and vice versa simply by using the +@kbd{M-x speedbar-window @key{RET}} or +@kbd{M-x speedbar-frame@key{RET}} command. + +@cindex @code{speedbar-prefer-window} +The user option @code{speedbar-prefer-window} changes the behavior of +the @code{speedbar} command, if set to t the @code{speedbar} command +will display the speedbar in a window instead of in a frame. + @node Basic Navigation @chapter Basic Navigation @@ -138,10 +153,11 @@ These key bindings are common across all modes: @table @kbd @item Q @cindex quitting speedbar -Quit speedbar, and kill the frame. +Quit speedbar, and kill the frame. This is only available for +@code{speed-frame}. @item q -Quit speedbar, and hide the frame. This makes it faster to restore the -speedbar frame, than if you press @kbd{Q}. +Quit speedbar, and hide the frame or close the window. This makes it +faster to restore the speedbar frame, than if you press @kbd{Q}. @item g @cindex refresh speedbar display Refresh whatever contents are in speedbar. @@ -658,10 +674,10 @@ Customize speedbar's many colors and fonts. @end table @menu -* Frames and Faces:: Visible behaviors. -* Tag Hierarchy Methods:: Customizing how tags are displayed. -* Version Control:: Adding new VC detection modes. -* Hooks:: The many hooks you can use. +* Frames, Windows and Faces:: Visible behaviors. +* Tag Hierarchy Methods:: Customizing how tags are displayed. +* Version Control:: Adding new VC detection modes. +* Hooks:: The many hooks you can use. @end menu @node Frames and Faces @@ -701,6 +717,25 @@ the alist @code{speedbar-frame-parameters}. This variable is used to set up initial details. Height is also automatically added when speedbar is created, though you can override it. +@cindex @code{speedbar-window-side} +If the speedbar is displayed in a window, you can also customize the +side on which the @code{speedbar-window} is displayed by changing the +@code{speedbar-window-side} option. + +@cindex @code{speedbar-window-default-width} +@cindex @code{speedbar-window-max-width} +The size of the @code{speedbar-window}, when opened on the left or right +side, can be defined by changing the +@code{speedbar-window-default-width} option, which defines the default +window size. The width of the speedbar window can be greater than +@code{speedbar-window-max-width}, but if it is closed and later +reopened, the width will be equal to @code{speedbar-window-max-width}. + +@cindex @code{speedbar-window-dedicated-window} +By default, @code{speedbar-window} is displayed in a dedicated window, +so @code{display-buffer} will never use this window. Setting +@code{speedbar-window-dedicated-window} to nil can change this behavior. + @node Tag Hierarchy Methods @section Tag Hierarchy Methods @cindex tag hierarchy diff --git a/etc/NEWS b/etc/NEWS index 707a54ad15e..79bbd1e8981 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1501,6 +1501,34 @@ Meant to be given a global binding convenient to the user. Example: (keymap-global-set "C-c M-r" 'remember-prefix-map) +** Speedbar + ++++ +*** The new command 'speedbar-window-mode' opens Speedbar in a window +instead of a frame. + ++++ +*** New command 'speedbar-window' is an alias for 'speedbar-window-mode'. + ++++ +*** The new user option 'speedbar-prefer-window', tells 'speedbar' to +open a side window instead of a frame. + ++++ +*** The new user option ‘speedbar-dedicated-window’ defines whether the +‘speedbar’ is displayed in a dedicated window. + ++++ +*** The new user option 'speedbar-window-default-width' defines the +initial width of the 'speedbar-window' + ++++ +*** The new user option 'speedbar-window-max-width' defines the maximum +width of the 'speedbar-window' when it is closed and then restored. + +--- +*** 'speedbar-easymenu-definition-trailer' is now a function. + ** Miscellaneous --- diff --git a/lisp/speedbar.el b/lisp/speedbar.el index 152b4c9d651..6ad96c979d6 100644 --- a/lisp/speedbar.el +++ b/lisp/speedbar.el @@ -22,9 +22,9 @@ ;;; Commentary: ;; -;; The speedbar provides a frame in which files, and locations in -;; files are displayed. These items can be clicked on with mouse-2 in -;; to display that file location. +;; The speedbar provides a frame or a window in which files, and +;; locations in files are displayed. These items can be clicked on with +;; mouse-2 in to display that file location. ;; ;;; Customizing and Developing for speedbar ;; @@ -139,8 +139,66 @@ :version "21.1" :type 'boolean) +(defcustom speedbar-select-frame-method 'attached + "Specify how to select a frame for displaying a file. +A number such as 1 or -1 means to pass that number to `other-frame' +while selecting a frame from speedbar. Any other value means to use +the attached frame (the frame that speedbar was started from)." + :group 'speedbar + :type '(choice integer (other :tag "attached" attached))) + +(defcustom speedbar-prefer-window nil + "If t, the command `speedbar' opens the speedbar in a window." + :type 'boolean + :group 'speedbar + :version "31.1") + +(defcustom speedbar-window-dedicated-window t + "Whether to make the `speedbar-window' dedicated." + :group 'speedbar + :type 'boolean + :version "31.1") + +(defcustom speedbar-window-side 'left + "Control the side of the frame on which to show the speedbar window. +The value can be `left', `right', `top' or `bottom'. +See `display-buffer-in-side-window' for more details." + :type '(radio (const :tag "Left" left) + (const :tag "Right" right) + (const :tag "Top" top) + (const :tag "Bottom" bottom)) + :group 'speedbar + :version "31.1") + +(defcustom speedbar-window-default-width 20 + "Initial width in characters of `speedbar-window'. +Specified the desired width when `speedbar-window' is opened on the left or +right side. The default value is the same width of `speedbar-frame-mode'." + :type 'integer + :group 'speedbar + :version "31.1") + +(defcustom speedbar-window-max-width 40 + "The maximum allowed width in characters of the `speedbar-window'. +The `speedbar-window' width can exceed the value defined here, however +if the window is closed and then reopened, this will be the width of the +`speedbar-window'." + :type 'integer + :group 'speedbar + :version "31.1") + ;;; Code: +(defconst speedbar--buffer-name " SPEEDBAR" + "Speedbar buffer name.") + +(defvar speedbar--window nil + "The window displaying `speedbar-window'.") + +(defvar speedbar--window-width speedbar-window-default-width +"Stores the current width of `speedbar-window'. +Subsequent calls to `speedbar-window' will open a window of this width.") + (defvar speedbar-initial-expansion-mode-alist '(("buffers" speedbar-buffer-easymenu-definition speedbar-buffers-key-map speedbar-buffer-buttons) @@ -845,11 +903,18 @@ This basically creates a sparse keymap, and makes its parent be ) "Additional menu items while in file-mode.") -(defvar speedbar-easymenu-definition-trailer - '(["Customize..." speedbar-customize t] - ["Close" dframe-close-frame t] - ["Quit" delete-frame t]) - "Menu items appearing at the end of the speedbar menu.") +(defun speedbar-easymenu-definition-trailer () + "Return menu items appearing at the end of the speedbar menu." + (let ((type (speedbar-frame-or-window))) + (cond ((eq type 'frame) + '(["Customize..." speedbar-customize t] + ["Close" dframe-close-frame t] + ["Quit" delete-frame t])) + ((eq type 'window) + '(["Customize..." speedbar-customize t] + ["Close" + (lambda () (interactive) (speedbar-window--close)) + :keys "q" :active t]))))) (defvar speedbar-desired-buffer nil "Non-nil when speedbar is showing buttons specific to a special mode. @@ -892,7 +957,19 @@ directories.") ;; ;;;###autoload -(defalias 'speedbar 'speedbar-frame-mode) +(defun speedbar (&optional arg) + "Open or close the `speedbar'. +Positive ARG means turn on, negative turn off. +A nil ARG means toggle. If `speedbar-prefer-window' is t, open the +speedbar in a window instead of in a separate frame." + (interactive "P") + (if speedbar-prefer-window + (speedbar-window-mode arg) + (speedbar-frame-mode arg))) + +;;;###autoload +(defalias 'speedbar-frame 'speedbar-frame-mode) + ;;;###autoload (defun speedbar-frame-mode (&optional arg) "Enable or disable speedbar. @@ -902,10 +979,12 @@ be displayed. Currently, only one speedbar is supported at a time. `speedbar-before-popup-hook' is called before popping up the speedbar frame. `speedbar-before-delete-hook' is called before the frame is deleted." (interactive "P") + (when (eq (speedbar-frame-or-window) 'window) + (speedbar-window--close)) ;; Get the buffer to play with (if (not (buffer-live-p speedbar-buffer)) (with-current-buffer - (setq speedbar-buffer (get-buffer-create " SPEEDBAR")) + (setq speedbar-buffer (get-buffer-create speedbar--buffer-name)) (speedbar-mode))) ;; Do the frame thing (dframe-frame-mode arg @@ -935,6 +1014,119 @@ be displayed. Currently, only one speedbar is supported at a time. (message (substitute-command-keys "Use \\[speedbar-get-focus] to see the speedbar window")))) +(defsubst speedbar-current-frame () + "Return the frame to use for speedbar based on current context." + (dframe-current-frame 'speedbar-frame 'speedbar-mode)) + +(defsubst speedbar-window--window-live-p () + "Return non-nil if `speedbar--window' is defined and live." + (when (and speedbar--window (window-live-p speedbar--window)) + speedbar--window)) + +(defsubst speedbar-window--buffer-live-p () + "Return non-nil if `speedbar-buffer' is live." + (when (and speedbar-buffer (buffer-live-p speedbar-buffer)) + speedbar-buffer)) + +(defun speedbar-window--live-p () + "Return t if `speedbar-window' is live." + (and (speedbar-window--buffer-live-p) (speedbar-window--window-live-p))) + +(defsubst speedbar-window-current-window () + "Return t if the selected windows is the `speedbar--window'." + (eq (selected-window) speedbar--window)) + +(defsubst speedbar-window--width () + "Return the width of `speedbar-window'." + (let ((edges (window-edges speedbar--window))) + (- (nth 2 edges) (nth 0 edges)))) + +(defun speedbar-frame-or-window () + "Return `frame' or `window' if one of each are open. +Return nil if both are closed." + (cond + ((speedbar-window--live-p) + 'window) + ((and (frame-live-p (speedbar-current-frame)) + speedbar-buffer + (not (speedbar-window--live-p))) + 'frame) + (t nil))) + +;;;###autoload +(defalias 'speedbar-window 'speedbar-window-mode) +;;;###autoload +(defun speedbar-window-mode (&optional arg) + "Enable or disable speedbar window mode. +Positive ARG means turn on, negative turn off. +A nil ARG means toggle. Once the speedbar window is activated, a buffer in +`speedbar-mode' will be displayed. Currently, only one speedbar is +supported at a time. +`speedbar-before-popup-hook' is called before popping up the speedbar frame. +`speedbar-before-delete-hook' is called before the frame is deleted." + (interactive "P") + (when (eq (speedbar-frame-or-window) 'frame) + (delete-frame (speedbar-current-frame))) + + (if (or (and (not arg) (speedbar-window--live-p)) + (and (numberp arg) (< arg 0))) + (speedbar-window--close) + (let ((current-window (selected-window))) + (unless (speedbar-window--buffer-live-p) + (setq speedbar-buffer (get-buffer-create speedbar--buffer-name))) + + (setq speedbar-frame (selected-frame) + dframe-attached-frame (selected-frame) + speedbar-select-frame-method 'attached + speedbar-last-selected-file nil) + + (set-buffer speedbar-buffer) + (speedbar-mode) + + ;; let's create the window + (setq speedbar--window + (display-buffer-in-side-window speedbar-buffer + `((side ,@speedbar-window-side) + (slot . 0) + (dedicated ,@speedbar-window-dedicated-window) + (window-width ,@speedbar--window-width)))) + ;; additional window parameters + (set-window-parameter speedbar--window 'no-other-window t) + (set-window-parameter speedbar--window 'no-delete-other-windows t) + + ;; `speedbar-reconfigure-keymaps' checks if the `speedbar-window' is open, so + ;; should stay after the buffer and window definition. + (speedbar-reconfigure-keymaps) + (speedbar-update-contents) + (speedbar-set-timer dframe-update-speed) + + ;; hscroll + (setq-local auto-hscroll-mode nil) + ;; reset the selection variable + (setq speedbar-last-selected-file nil) + (select-window current-window)))) + +(defun speedbar-window--close () + "Close `speedbar-window'." + (when (speedbar-window--live-p) + (let ((current-window (selected-window))) + ;; store the current window width + (setq speedbar--window-width + (let ((current-width (speedbar-window--width))) + (if (> current-width speedbar-window-max-width) + speedbar-window-max-width + current-width))) + + (delete-window speedbar--window) + (setq speedbar--window nil + speedbar-frame nil + dframe-attached-frame nil) + (speedbar-set-timer nil) + (kill-buffer speedbar-buffer) + (setq speedbar-buffer nil) + (when (and current-window (window-live-p current-window)) + (select-window current-window))))) + (defun speedbar-frame-reposition-smartly () "Reposition the speedbar frame to be next to the attached frame." (cond ((or (assoc 'left speedbar-frame-parameters) @@ -952,10 +1144,6 @@ be displayed. Currently, only one speedbar is supported at a time. (dframe-attached-frame speedbar-frame) speedbar-default-position)))) -(defsubst speedbar-current-frame () - "Return the frame to use for speedbar based on current context." - (dframe-current-frame 'speedbar-frame 'speedbar-mode)) - (defun speedbar-handle-delete-frame (e) "Handle a delete-frame event E. If the deleted frame is the frame speedbar is attached to, @@ -981,6 +1169,14 @@ selected. If the speedbar frame is active, then select the attached frame." Return nil if it doesn't exist." (frame-width speedbar-frame)) +(defun speedbar-width () + "Return the width of the `speedbar'. +if `speedbar-window-mode' is open, the width is `speedbar-window--width' +otherwise the width is `speedbar-frame-width'." + (if (speedbar-window--live-p) + (speedbar-window--width) + (speedbar-frame-width))) + (define-derived-mode speedbar-mode fundamental-mode "Speedbar" "Major mode for managing a display of directories and tags. \\ @@ -1067,7 +1263,7 @@ frame and window to be the currently active frame and window." (if (and (frame-live-p (speedbar-current-frame)) speedbar-buffer) (with-current-buffer speedbar-buffer - (let* ((w (or (speedbar-frame-width) 20)) + (let* ((w (or (speedbar-width) 20)) (p1 "<<") (p5 ">>") (p3 (if speedbar-update-flag "#" "!")) @@ -1129,7 +1325,7 @@ and the existence of packages." (setq alist (cdr alist))) displays))) ;; The trailer - speedbar-easymenu-definition-trailer)) + (speedbar-easymenu-definition-trailer))) (localmap (save-excursion (let ((cf (selected-frame))) (prog2 @@ -1840,7 +2036,7 @@ INDEX is not used, but is required by the caller." ;; Nuke the beginning of the directory if it's too long... (cond ((eq speedbar-directory-button-trim-method 'span) (beginning-of-line) - (let ((ww (or (speedbar-frame-width) 20))) + (let ((ww (or (speedbar-width) 20))) (move-to-column ww nil) (while (>= (current-column) ww) (re-search-backward "[/\\]" nil t) @@ -1856,7 +2052,7 @@ INDEX is not used, but is required by the caller." (move-to-column ww nil))))) ((eq speedbar-directory-button-trim-method 'trim) (end-of-line) - (let ((ww (or (speedbar-frame-width) 20)) + (let ((ww (or (speedbar-width) 20)) (tl (current-column))) (if (< ww tl) (progn @@ -2527,56 +2723,67 @@ Also resets scanner functions." ;; change this if it changed for some reason (speedbar-set-mode-line-format)) +(defun speedbar--speedbar-live-p () + "Return non-nil if `speedbar-window-mode' or `speedbar-frame-mode' are active." + (cond + ((and (speedbar-current-frame) + (frame-live-p (speedbar-current-frame))) + t) + ((speedbar-window--window-live-p) t) + (t nil))) + (defun speedbar-timer-fn () "Run whenever Emacs is idle to update the speedbar item." - (if (or (not (speedbar-current-frame)) - (not (frame-live-p (speedbar-current-frame)))) + (if (not (speedbar--speedbar-live-p)) (speedbar-set-timer nil) ;; Save all the match data so that we don't mess up executing fns (save-match-data ;; Only do stuff if the frame is visible, not an icon, and if ;; it is currently flagged to do something. (if (and speedbar-update-flag - (speedbar-current-frame) + (or (speedbar-window-current-window) + (speedbar-current-frame)) (frame-visible-p (speedbar-current-frame)) (not (eq (frame-visible-p (speedbar-current-frame)) 'icon))) (let ((af (selected-frame))) - (dframe-select-attached-frame speedbar-frame) - ;; make sure we at least choose a window to - ;; get a good directory from - (if (window-minibuffer-p) - nil - ;; Check for special modes - (speedbar-maybe-add-localized-support (current-buffer)) - ;; Update for special mode all the time! - (if (and speedbar-mode-specific-contents-flag - (consp speedbar-special-mode-expansion-list) - (local-variable-p - 'speedbar-special-mode-expansion-list - (current-buffer))) - ;;(eq (get major-mode 'mode-class 'special))) - (progn - (if (<= 2 speedbar-verbosity-level) + (dframe-select-attached-frame speedbar-frame) + ;; make sure we at least choose a window to + ;; get a good directory from + (if (window-minibuffer-p) + nil + ;; Check for special modes + (speedbar-maybe-add-localized-support (current-buffer)) + ;; Update for special mode all the time! + (if (and speedbar-mode-specific-contents-flag + (consp speedbar-special-mode-expansion-list) + (local-variable-p + 'speedbar-special-mode-expansion-list + (current-buffer))) + ;;(eq (get major-mode 'mode-class 'special))) + (progn + (if (<= 2 speedbar-verbosity-level) + (dframe-message + "Updating speedbar to special mode: %s..." + major-mode)) + (speedbar-update-special-contents) + (if (<= 2 speedbar-verbosity-level) + (progn (dframe-message - "Updating speedbar to special mode: %s..." - major-mode)) - (speedbar-update-special-contents) - (if (<= 2 speedbar-verbosity-level) - (progn - (dframe-message - "Updating speedbar to special mode: %s...done" - major-mode) - (dframe-message nil)))) - - ;; Update all the contents if directories change! - (unless (and (or (member major-mode speedbar-ignored-modes) - (eq af (speedbar-current-frame)) - (not (buffer-file-name))) - ;; Always update for GUD. - (not (string-equal "GUD" - speedbar-initial-expansion-list-name))) - (speedbar-update-localized-contents))) - (select-frame af)) + "Updating speedbar to special mode: %s...done" + major-mode) + (dframe-message nil)))) + + ;; Update all the contents if directories change! + (unless (and (or (member major-mode speedbar-ignored-modes) + (and + (eq af (speedbar-current-frame)) + (speedbar-window-current-window)) + (not (buffer-file-name))) + ;; Always update for GUD. + (not (string-equal "GUD" + speedbar-initial-expansion-list-name))) + (speedbar-update-localized-contents))) + (select-frame af)) ;; Now run stealthy updates of time-consuming items (speedbar-stealthy-updates))))) (run-hooks 'speedbar-timer-hook)) @@ -3366,13 +3573,6 @@ TOKEN will be the list, and INDENT is the current indentation level." ;;; Loading files into the attached frame. ;; -(defcustom speedbar-select-frame-method 'attached - "Specify how to select a frame for displaying a file. -A number such as 1 or -1 means to pass that number to `other-frame' -while selecting a frame from speedbar. Any other value means to use -the attached frame (the frame that speedbar was started from)." - :group 'speedbar - :type '(choice integer (other :tag "attached" attached))) (defun speedbar-find-file-in-frame (file) "Load FILE into the frame attached to speedbar. commit c8471129dd6e576aaf2dfdc65e09fcf297d1ff3c Author: Eli Zaretskii Date: Sat Mar 15 10:17:20 2025 +0200 ; * doc/lispref/commands.texi (Click Events): Fix a thinko. diff --git a/doc/lispref/commands.texi b/doc/lispref/commands.texi index 13af0e93ade..e365e691096 100644 --- a/doc/lispref/commands.texi +++ b/doc/lispref/commands.texi @@ -1676,7 +1676,7 @@ if the event was outside the text area, the window area where it occurred. It is one of the symbols @code{mode-line}, @code{header-line}, @code{tab-line}, @code{vertical-line}, @code{left-margin}, @code{right-margin}, @code{left-fringe}, -@code{right-fringe}, @code{tab-bar}, or @code{menu-bar}. +or @code{right-fringe}. In one special case, @var{pos-or-area} is a list containing a symbol (one of the symbols listed above) instead of just the symbol. This commit 66ec9ae71959fd562646c8c3b482aff7f41adc50 Author: Eli Zaretskii Date: Sat Mar 15 10:04:01 2025 +0200 ; Fix documentation of mouse-click events * doc/lispref/commands.texi (Click Events): Add menu-bar events to the description. Add missing details about tab-bar click events. (cherry picked from commit 6e28c2019e772aa19040cf17e314fcee23bb33d4) diff --git a/doc/lispref/commands.texi b/doc/lispref/commands.texi index dce6af9b7b5..506788340c8 100644 --- a/doc/lispref/commands.texi +++ b/doc/lispref/commands.texi @@ -1792,17 +1792,18 @@ handle), @code{up} (the up arrow at one end of the scroll bar), or @c The 'top', 'bottom', and 'end-scroll' codes don't seem to be used. @end table -For clicks on the frame's internal border (@pxref{Frame Layout}), -the frame's tool bar (@pxref{Tool Bar}) or tab bar, @var{position} -has this form: +For clicks on the frame's internal border (@pxref{Frame Layout}), the +frame's tool bar (@pxref{Tool Bar}) or tab bar or menu bar, +@var{position} has this form: @example - (@var{frame} @var{part} (@var{X} . @var{Y}) @var{timestamp}) + (@var{frame} @var{part} (@var{X} . @var{Y}) @var{timestamp} @var{object}) @end example @table @asis @item @var{frame} -The frame whose internal border or tool bar or tab bar was clicked on. +The frame whose internal border or tool bar or tab bar or menu bar was +clicked on. @item @var{part} The part of the frame which was clicked on. This can be one @@ -1817,6 +1818,13 @@ The frame has a tool bar, and the event was in the tool-bar area. @item tab-bar The frame has a tab bar, and the event was in the tab-bar area. +@cindex menu-bar mouse events +@item menu-bar +The event was on the frame's menu bar area. This kind of click event +can happen only on text-only frames or on X frames in a non-toolkit +build of Emacs. (In toolkit builds of Emacs, menu-bar clicks are +handled by the toolkit, and are not visible to Emacs as click events.) + @item left-edge @itemx top-edge @itemx right-edge @@ -1838,6 +1846,9 @@ the frame doesn't have its @code{drag-internal-border} parameter (@pxref{Mouse Dragging Parameters}) set to a non-@code{nil} value. @end table +@item @var{object} +This member is present only for clicks on the tab bar, and it is the +propertized string with information about the clicked button. @end table commit 82e44e86754427864305fcc2e03a52a54a1cae2f Author: Eli Zaretskii Date: Sat Mar 15 10:10:14 2025 +0200 ; Fix documentation of recent change in mouse.el * lisp/mouse.el (mouse-event-areas-with-no-buffer-positions): Doc fix. (mouse-posn-property): Add commentary. diff --git a/lisp/mouse.el b/lisp/mouse.el index 44c359e5257..0775ec2f4ec 100644 --- a/lisp/mouse.el +++ b/lisp/mouse.el @@ -1664,7 +1664,7 @@ is dragged over to." "Event areas not containing buffer positions. Example: mouse clicks in the fringe come with a position in (nth 5). This is useful but is not where we clicked, so -don't look up that position's properties!. Likewise for +don't look up that position's properties! Likewise for the other event areas.") (defun mouse-posn-property (pos property) @@ -1690,6 +1690,9 @@ because such events do not contain buffer positions." (get-text-property (cdr str) property (car str))) (and pt (not (memq (posn-area pos) + ;; Don't return position of these mouse + ;; events because they don't describe the + ;; position of the click. mouse-event-areas-with-no-buffer-positions)) (get-char-property pt property w)))) (get-char-property pos property))) commit 6e28c2019e772aa19040cf17e314fcee23bb33d4 Author: Eli Zaretskii Date: Sat Mar 15 10:04:01 2025 +0200 ; Fix documentation of mouse-click events * doc/lispref/commands.texi (Click Events): Add menu-bar events to the description. Add missing details about tab-bar click events. diff --git a/doc/lispref/commands.texi b/doc/lispref/commands.texi index 0a4a41818c3..13af0e93ade 100644 --- a/doc/lispref/commands.texi +++ b/doc/lispref/commands.texi @@ -1791,17 +1791,18 @@ handle), @code{up} (the up arrow at one end of the scroll bar), or @c The 'top', 'bottom', and 'end-scroll' codes don't seem to be used. @end table -For clicks on the frame's internal border (@pxref{Frame Layout}), -the frame's tool bar (@pxref{Tool Bar}) or tab bar, @var{position} -has this form: +For clicks on the frame's internal border (@pxref{Frame Layout}), the +frame's tool bar (@pxref{Tool Bar}) or tab bar or menu bar, +@var{position} has this form: @example - (@var{frame} @var{part} (@var{X} . @var{Y}) @var{timestamp}) + (@var{frame} @var{part} (@var{X} . @var{Y}) @var{timestamp} @var{object}) @end example @table @asis @item @var{frame} -The frame whose internal border or tool bar or tab bar was clicked on. +The frame whose internal border or tool bar or tab bar or menu bar was +clicked on. @item @var{part} The part of the frame which was clicked on. This can be one @@ -1816,6 +1817,13 @@ The frame has a tool bar, and the event was in the tool-bar area. @item tab-bar The frame has a tab bar, and the event was in the tab-bar area. +@cindex menu-bar mouse events +@item menu-bar +The event was on the frame's menu bar area. This kind of click event +can happen only on text-only frames or on X frames in a non-toolkit +build of Emacs. (In toolkit builds of Emacs, menu-bar clicks are +handled by the toolkit, and are not visible to Emacs as click events.) + @item left-edge @itemx top-edge @itemx right-edge @@ -1837,6 +1845,9 @@ the frame doesn't have its @code{drag-internal-border} parameter (@pxref{Mouse Dragging Parameters}) set to a non-@code{nil} value. @end table +@item @var{object} +This member is present only for clicks on the tab bar, and it is the +propertized string with information about the clicked button. @end table commit e09cb3aee9a7dfb851d0470f30bd71426c3434bf Author: Eli Zaretskii Date: Sat Mar 15 09:36:03 2025 +0200 ; * lisp/dired.el (project-root): Declare. diff --git a/lisp/dired.el b/lisp/dired.el index ff6a31ed725..9a8c8d74394 100644 --- a/lisp/dired.el +++ b/lisp/dired.el @@ -3470,6 +3470,8 @@ If EOL, it should be an position to use instead of ;;; Copy names of marked files into kill-ring +(declare-function project-root "project" (project)) + (defun dired-copy-filename-as-kill (&optional arg) "Copy names of marked (or next ARG) files into the kill ring. If there are several names, they will be separated by a space, commit b105089715c8b5d467282513965035faf955a5e3 Author: Gerd Möllmann Date: Fri Mar 14 15:44:04 2025 +0100 Fix mouse-posn-property * doc/lispref/commands.texi (Click Events): Mention menu-bar. * lisp/mouse.el (mouse-event-areas-with-no-buffer-positions): New defvar including all areas mentioned in the Lisp reference. (mouse-posn-property): Use it. diff --git a/doc/lispref/commands.texi b/doc/lispref/commands.texi index c3891b70406..0a4a41818c3 100644 --- a/doc/lispref/commands.texi +++ b/doc/lispref/commands.texi @@ -1675,8 +1675,8 @@ The buffer position of the character clicked on in the text area; or, if the event was outside the text area, the window area where it occurred. It is one of the symbols @code{mode-line}, @code{header-line}, @code{tab-line}, @code{vertical-line}, -@code{left-margin}, @code{right-margin}, @code{left-fringe}, or -@code{right-fringe}. +@code{left-margin}, @code{right-margin}, @code{left-fringe}, +@code{right-fringe}, @code{tab-bar}, or @code{menu-bar}. In one special case, @var{pos-or-area} is a list containing a symbol (one of the symbols listed above) instead of just the symbol. This diff --git a/lisp/mouse.el b/lisp/mouse.el index 1f0ca6a51b6..44c359e5257 100644 --- a/lisp/mouse.el +++ b/lisp/mouse.el @@ -1655,6 +1655,18 @@ is dragged over to." ;; operation. (put 'mouse-drag-region 'undo-inhibit-region t) +(defvar mouse-event-areas-with-no-buffer-positions + '( mode-line header-line vertical-line + left-fringe right-fringe + left-margin right-margin + tab-bar menu-bar + tab-line) + "Event areas not containing buffer positions. +Example: mouse clicks in the fringe come with a position in +(nth 5). This is useful but is not where we clicked, so +don't look up that position's properties!. Likewise for +the other event areas.") + (defun mouse-posn-property (pos property) "Look for a property at click position. POS may be either a buffer position or a click position like @@ -1663,7 +1675,9 @@ a string, the text property PROPERTY is examined. If this is nil or the click is not on a string, then the corresponding buffer position is searched for PROPERTY. If PROPERTY is encountered in one of those places, -its value is returned." +its value is returned. Mouse events in areas listed in +`mouse-event-areas-with-no-buffer-positions' always return nil +because such events do not contain buffer positions." (if (consp pos) (let ((w (posn-window pos)) (pt (posn-point pos)) (str (posn-string pos))) @@ -1674,12 +1688,9 @@ its value is returned." (or (and str (< (cdr str) (length (car str))) (get-text-property (cdr str) property (car str))) - ;; Mouse clicks in the fringe come with a position in - ;; (nth 5). This is useful but is not exactly where we clicked, so - ;; don't look up that position's properties! - (and pt (not (memq (posn-area pos) - '(left-fringe right-fringe - left-margin right-margin tab-bar))) + (and pt + (not (memq (posn-area pos) + mouse-event-areas-with-no-buffer-positions)) (get-char-property pt property w)))) (get-char-property pos property))) commit 88e30e1ac5a154d9828954f8052e1a04aed15999 Author: Po Lu Date: Sat Mar 15 12:47:40 2025 +0800 ; * src/w32.c (term_ntproc): Fix capitalization of commentary. diff --git a/src/w32.c b/src/w32.c index 567a1894df8..d7bf173ce25 100644 --- a/src/w32.c +++ b/src/w32.c @@ -10462,11 +10462,11 @@ term_ntproc (int ignored) term_timers (); - /* shutdown the socket interface if necessary. */ + /* Shut down the socket interface if necessary. */ term_winsock (); term_w32select (); - /* exit all worker threads of sys_select if necessary. */ + /* Exit all worker threads of sys_select if necessary. */ free_wait_pool (); #if HAVE_NATIVE_IMAGE_API w32_gdiplus_shutdown (); commit 1749ad825e2d7bd456665ce02fb7faf79a6e9143 Author: Po Lu Date: Sat Mar 15 12:46:32 2025 +0800 ; Fix punctuation and typos in w32proc.c * src/w32proc.c (WFO_MAX_WAIT, wait_objects_context) (start_wait_objects, stop_wait_objects, end_wait_and_return) (shrink_wait_pool, free_wait_pool, wait_for_objects) (msg_wait_for_objects, delete_child, WINAPI, create_child) (waitpid): Repunctuate sentences and correct typos. diff --git a/src/w32proc.c b/src/w32proc.c index d88ca1c30ce..77042149f96 100644 --- a/src/w32proc.c +++ b/src/w32proc.c @@ -885,12 +885,12 @@ alarm (int seconds) on MS-Windows. As noted in the MS documentation, WaitForMultipleObjects can wait on - a maximum of MAXIMUM_WAIT_OBJECTS (64) objects. Due to this + a maximum of MAXIMUM_WAIT_OBJECTS (64) objects. Due to this limitation, earlier versions of Emacs (at least 30) could only support up to 32 subprocesses or network connections. The documentation suggests using multiple threads or a thread pool to - wait on a larger number of objects. With Windows 2000, Microsoft has + wait on a larger number of objects. With Windows 2000, Microsoft has added new thread pooling functions to make thread creation, destruction, and general management easier: @@ -906,19 +906,19 @@ alarm (int seconds) However, since the system thread pool in versions prior to Windows 7 does not support adjusting thread stack sizes by calling SetThreadpoolStackInformation, using a large number of threads may - consume a lot of address space. This can have a significant impact on - the limited 2GB address space available on 32-bit systems. To avoid + consume a lot of address space. This can have a significant impact on + the limited 2GB address space available on 32-bit systems. To avoid this issue on Windows Vista and earlier systems and make the code as generic as possible, a simple waiting thread pool is manually implemented here. The waiting thread pool contains a maximum of 32 threads, with each thread capable of waiting on up to 63 objects (one object is reserved - for communication with the main thread). Combined with the main + for communication with the main thread). Combined with the main thread's WaitForMultipleObjects call, this implementation supports - waiting on a maximum of 2048 objects. Since Windows only supports + waiting on a maximum of 2048 objects. Since Windows only supports creating subprocesses using 'pipe method, this allows Emacs to create - a maximum of approximately 1024 subprocesses. This limit is close to + a maximum of approximately 1024 subprocesses. This limit is close to the number of subprocesses that can be created using the 'pty method on Linux when the default FD_SETSIZE is 1024. @@ -926,13 +926,13 @@ alarm (int seconds) they enter an infinite loop, waiting for an event to trigger and begin their WaitForMultipleObjects tasks, thereby eliminating the additional time and power consumption associated with frequently - creating and destroying threads. If a thread is not triggered after a + creating and destroying threads. If a thread is not triggered after a certain number of sys_select calls, it can be terminated to free up resources. */ /* The values returned by WaitForMultipleObjects, WAIT_ABANDONED_0 (0x80) and WAIT_TIMEOUT (0x102), are less than 2048 (0x800), so - define new constants to replace them. 'WFO' prefix stands for + define new constants to replace them. 'WFO' prefix stands for 'WaitForObjects'. */ #define WFO_ABANDONED 0x10000 #define WFO_TIMEOUT 0x20002 @@ -940,10 +940,9 @@ alarm (int seconds) #define WFO_MAX_WAIT FD_SETSIZE /* When debugging, use SetThreadDescription to provide additional - debugging information. They are already defined in w32fns.c. */ + debugging information. They are already defined in w32fns.c. */ typedef BOOL (WINAPI *IsDebuggerPresent_Proc) (void); -typedef HRESULT (WINAPI *SetThreadDescription_Proc) - (HANDLE hThread, PCWSTR lpThreadDescription); +typedef HRESULT (WINAPI *SetThreadDescription_Proc) (HANDLE, PCWSTR); extern IsDebuggerPresent_Proc is_debugger_present; extern SetThreadDescription_Proc set_thread_description; @@ -963,7 +962,7 @@ typedef struct HANDLE wait_ready; /* pHandles and nCount are part of the WaitForMultipleObjects parameters, specifying the array of objects to wait on and the - number of objects. bWaitAll and dwMilliseconds are not needed. */ + number of objects. bWaitAll and dwMilliseconds are not needed. */ HANDLE *pHandles; int nCount; /* The return value of the thread's call to WaitForMultipleObjects. */ @@ -1008,14 +1007,14 @@ typedef struct static wait_objects_pool wait_pool; static wait_objects_info wait_info; -/* Thread proc for worker threads waiting on objects. Each thread is +/* Thread proc for worker threads waiting on objects. Each thread is normally blocked until woken by the main thread to wait on objects. When the wait completes, wait_ready is signaled to wake up the main thread and the thread blocks itself again. The worker thread needs to wait for the timeout event from the main - thread. When timeout_event is signaled by the main thread, it will - end the current wait and start the next round. Similarly, threads + thread. When timeout_event is signaled by the main thread, it will + end the current wait and start the next round. Similarly, threads also need to wait for an quit event, which usually occurs when the thread has not been used for a certain period of time. */ static DWORD WINAPI @@ -1088,7 +1087,7 @@ wait_objects_thread (void *arg) /* Determine the grouping based on the given wait objects and assign them to the worker threads to begin waiting, creating new worker - threads if necessary. The function also performs initialization of + threads if necessary. The function also performs initialization of some static resources. */ static int start_wait_objects (wait_objects_info *p, @@ -1105,13 +1104,13 @@ start_wait_objects (wait_objects_info *p, /* Set init_flag. */ wait_pool.init_flag = TRUE; } - /* Check if all threads in the thread pool are working properly. If + /* Check if all threads in the thread pool are working properly. If any thread has exited, exit immediately. */ for (int i = 0; i < wait_pool.count; i++) { /* The MS documentation suggests that callers should call the GetExitCodeThread function only after the thread has been - confirmed to have exited. Use the WaitForSingleObject with a + confirmed to have exited. Use the WaitForSingleObject with a wait duration of zero to determine whether a thread has exited. */ DWORD res = WaitForSingleObject (wait_pool.wocs[i].thread, 0); @@ -1123,7 +1122,7 @@ start_wait_objects (wait_objects_info *p, else if (res == WAIT_OBJECT_0) { /* The last-error code is kept in thread local storage so that - multiple threads do not overwrite each other's values. Pass + multiple threads do not overwrite each other's values. Pass the error by using SetLastError to set the error value. */ SetLastError (wait_pool.wocs[i].errcode); return WFO_FAILED; @@ -1135,41 +1134,41 @@ start_wait_objects (wait_objects_info *p, } /* Calculate the required number of threads and the number of objects the main thread needs to wait for based on the number of objects to - be waited on. To avoid increasing the number of threads for a small + be waited on. To avoid increasing the number of threads for a small increase in the number of objects (e.g., using two threads for 65 objects), the following calculation allows the main thread's array to hold up to 32 wait objects, excluding the thread event - handles. With this approach, the minimum number of wait objects for + handles. With this approach, the minimum number of wait objects for the worker threads is 32, and it allows a maximum of 63 * 32 + 32 = 2048 objects to be waited on. The number of child process in Emacs is limited by MAX_CHILDREN, - which is half of MAXDESC (FD_SETSIZE). When FD_SETSIZE is set to + which is half of MAXDESC (FD_SETSIZE). When FD_SETSIZE is set to 2048, a maximum of 1024 child processes and network/serial are - allowed. Compared to network/serial, the process handles of child + allowed. Compared to network/serial, the process handles of child processes are also waited on, which is why only a maximum of 1024 child processes can be created. When there are 1024 child processes, the main thread needs to wait for 64 objects, which seems to exceed the 63 allowed by - MsgWaitForMultipleObjects. However, due to the influence of + MsgWaitForMultipleObjects. However, due to the influence of emacs_pipe, a maximum of only 1021 child processes can be created, so the number of elements in the main thread's wait array will not - exceed 63. The explanation is as follows: + exceed 63. The explanation is as follows: . emacs_pipe calls the pipe2 function located in w32.c. . pipe2 makes sure that file descriptors return by _pipe less than MAXDESC (2048), Therefore, the range of available file descriptor values is from 3 to 2047 (0, 1, 2 for stdin, stdout and - stderr). Since pipe file descriptors are opened in pairs, the + stderr). Since pipe file descriptors are opened in pairs, the actual range is from 3 to 2046, meaning there are 2044 available file descriptors. . When create_process creates a process using the 'pipe' method, it - creates two pipes, one for reading and one for writing. It then + creates two pipes, one for reading and one for writing. It then closes one file descriptor for each pipe, as each pipe is only - used for reading or writing. This results in 4 file descriptors + used for reading or writing. This results in 4 file descriptors being used initially for each process creation, and then 2 are - released. When only 2 empty slots remain, no new child processes + released. When only 2 empty slots remain, no new child processes can be created. . In the end, we can use 2042 file descriptors, which allows for 1021 child processes. */ @@ -1179,7 +1178,7 @@ start_wait_objects (wait_objects_info *p, objects in the main thread's array exceeds 32. */ p->nt = 1 + (nCount - 33) / 63; /* The number of objects the main thread needs to wait for is the - required number of threads plus the remaining objects. If the + required number of threads plus the remaining objects. If the existing threads are sufficient to wait for all the objects, then nh is the number of threads. */ p->nh = (p->nt * 63 >= nCount) ? p->nt : (p->nt + nCount - p->nt * 63); @@ -1188,17 +1187,19 @@ start_wait_objects (wait_objects_info *p, if (p->nh != p->nt) memcpy (p->hs + p->nt, lpHandles + p->nt * 63, sizeof (HANDLE) * (nCount - p->nt * 63)); - /* Set the pHandles and nCount parameters for each thread. Since the + /* Set the pHandles and nCount parameters for each thread. Since the waiting task is relatively simple, we do not need a dedicated - task queue mechanism. We can simply wait for the corresponding + task queue mechanism. We can simply wait for the corresponding objects in the order of the thread context array wait_pool.wocs. */ for (int i = 0; i < p->nt; i++) { /* If it is the last thread and the thread is sufficient to wait all the objects, then the count needs to be calculated; otherwise, it will be 63. */ - int count = (i == p->nt - 1) - ? (p->nt == p->nh) ? (nCount - i * 63) : 63 : 63; + int count = ((i == p->nt - 1) + ? ((p->nt == p->nh) + ? (nCount - i * 63) : 63) + : 63); wait_pool.wocs[i].nCount = count; wait_pool.wocs[i].pHandles = lpHandles + i * 63; } @@ -1224,7 +1225,7 @@ start_wait_objects (wait_objects_info *p, /* Set call_count and errcode to ZERO. */ ctx->call_count = 0; ctx->errcode = 0; - /* Creating new worker threads. Please refer to the comment in + /* Creating new worker threads. Please refer to the comment in w32proc.c within the new_child function, where the reader_thread thread is created, to understand why these parameters are needed. */ @@ -1233,7 +1234,7 @@ start_wait_objects (wait_objects_info *p, /* CreateThread failed. */ if (!ctx->thread) return WFO_FAILED; - /* Set Thread Description information. This may be handy with + /* Set Thread Description information. This may be handy with debugging. */ if (is_debugger_present && is_debugger_present () && set_thread_description) @@ -1251,7 +1252,7 @@ start_wait_objects (wait_objects_info *p, { p->hs[i] = wait_pool.wocs[i].wait_ready; /* Reset the wait_ready event and set wait_start event for threads - that are not newly created. Newly created threads start + that are not newly created. Newly created threads start execution immediately. */ if (i < orig_thread_count) { @@ -1274,13 +1275,13 @@ stop_wait_objects (wait_objects_info *p) /* Set timeout_event to tell all worker threads stop waiting. */ if (!SetEvent (wait_pool.timeout_event)) return WFO_FAILED; - /* Wait for all the used worker threads to signal wait_ready. This + /* Wait for all the used worker threads to signal wait_ready. This typically takes no more than a few dozen microseconds, so a timeout indicates that an error has occurred. */ DWORD result = WaitForMultipleObjects (p->nt, p->hs, TRUE, 20); if (result == WAIT_FAILED || result == WAIT_ABANDONED) return WFO_FAILED; - /* Timeout means there exists a thread exit abnormally. Since the + /* Timeout means there exists a thread exit abnormally. Since the WAIT_FAILED error in the worker threads signals wait_ready, this can only be an error occurring when the worker threads are waiting for curr_start and wait_quit. */ @@ -1298,7 +1299,7 @@ stop_wait_objects (wait_objects_info *p) return WFO_FAILED; } /* Even if the wait succeeds, there is still a possibility that some - wait_ready might be signaled by a WAIT_FAILED error. To determine + wait_ready might be signaled by a WAIT_FAILED error. To determine if no WAIT_FAILED error occurred, check the errcode of all threads. */ for (int i = 0; i < p->nt; i++) { @@ -1327,22 +1328,22 @@ end_wait_and_return (wait_objects_info *p, DWORD result) else if (result == WAIT_FAILED) return WFO_FAILED; /* It seems that all the wait objects are just process handles and - event handles. WAIT_ABANDONED may not occur, but let's just + event handles. WAIT_ABANDONED may not occur, but let's just handle it here. */ else if (result >= WAIT_ABANDONED_0 && result < p->nh + WAIT_ABANDONED_0) { result -= WAIT_ABANDONED_0; - /* The event object wait_ready is not mutex object. This is + /* The event object wait_ready is not mutex object. This is unlikely to happen... */ if (result < p->nt) return WFO_FAILED; /* There are 62 * nt objects before the objects in the main - thread. Each thread can wait for 63 objects, and each + thread. Each thread can wait for 63 objects, and each thread's wait_ready occupies one position in the main thread - array. Therefore, the index of the waiting object in the main + array. Therefore, the index of the waiting object in the main thread array should be incremented by (63 - 1) multiplied by - the number of worker threads. We add WFO_ABANDONED to + the number of worker threads. We add WFO_ABANDONED to distinguish it from normal object index. */ result = result + 62 * p->nt + WFO_ABANDONED; } @@ -1358,7 +1359,7 @@ end_wait_and_return (wait_objects_info *p, DWORD result) and the number of worker threads is equal to the number of wait objects for the main thread, it indicates that the main thread is using MsgWaitForMultipleObjects and that there are - no wait objects in the main thread's array. We should return + no wait objects in the main thread's array. We should return the total number of wait objects to indicate that a message was received during the wait. */ if (p->nt == p->nh) @@ -1371,7 +1372,7 @@ end_wait_and_return (wait_objects_info *p, DWORD result) else { DWORD t_result = wait_pool.wocs[idx].result; - /* WAIT_FAILED or WAIT_TIMEOUT. Since the wait time in the + /* WAIT_FAILED or WAIT_TIMEOUT. Since the wait time in the worker thread is set to INFINITE, WAIT_TIMEOUT should not occur. */ if (t_result == WAIT_FAILED || t_result == WAIT_TIMEOUT) @@ -1379,15 +1380,15 @@ end_wait_and_return (wait_objects_info *p, DWORD result) SetLastError (wait_pool.wocs[idx].errcode); return WFO_FAILED; } - /* Abandoned. Compared to the waiting objects in the main + /* Abandoned. Compared to the waiting objects in the main thread's array, the index in the worker threads needs to be incremented by 63 times the number of preceding threads, rather than 62. */ else if (t_result >= WAIT_ABANDONED_0 && t_result < wait_pool.wocs[idx].nCount + WAIT_ABANDONED_0) - result = idx * 63 + t_result - + WFO_ABANDONED - WAIT_ABANDONED_0; + result = (idx * 63 + t_result + + WFO_ABANDONED - WAIT_ABANDONED_0); /* The worker thread is signaled by timeout_event, but at this point, timeout_event has not yet been signaled. */ else if (t_result == wait_pool.wocs[idx].nCount + WAIT_OBJECT_0) @@ -1410,7 +1411,7 @@ static int shrink_wait_pool (void) { wait_pool.main_thread_waits++; - /* Check the thread every 64 Wait call. 64 is just a value that might + /* Check the thread every 64 Wait call. 64 is just a value that might be reasonably suitable. */ if (wait_pool.main_thread_waits <= 64) return 0; @@ -1451,7 +1452,7 @@ shrink_wait_pool (void) } /* Exit all worker threads and release the start and timeout - handles. This function is called when exiting Emacs. */ + handles. This function is called when exiting Emacs. */ void free_wait_pool (void) { @@ -1466,7 +1467,7 @@ free_wait_pool (void) CloseHandle (wait_pool.wocs[i].wait_start); CloseHandle (wait_pool.wocs[i].wait_ready); } - /* Wait all workder threads exit. Stop if failed. */ + /* Wait all worker threads exit. Stop if failed. */ for (int i = 0; i < wait_pool.count; i++) { if (WaitForSingleObject (wait_pool.wocs[i].thread, 1) @@ -1479,7 +1480,7 @@ free_wait_pool (void) } /* Replacement of WaitForMultipleObjects, with the bWaitAll parameter - removed. The function's return values are as follows: + removed. The function's return values are as follows: [0 ~ nCount-1], the return value indicates the lpHandles array index of the object that satisfied the wait. @@ -1490,7 +1491,7 @@ free_wait_pool (void) [WFO_TIMEOUT], The time-out interval elapsed. - [WFO_FAILED], The function has failed. To get extended error + [WFO_FAILED], The function has failed. To get extended error information, call GetLastError. */ static DWORD wait_for_objects (DWORD nCount, HANDLE *lpHandles, @@ -1552,7 +1553,7 @@ wait_for_objects (DWORD nCount, HANDLE *lpHandles, FALSE, dwMilliseconds); /* If the main thread wait times out, call WaitForMultipleObjects again with zero time-out interval to check if any objects have - completed. The default clock resolution of Windows is 64 Hz. If a + completed. The default clock resolution of Windows is 64 Hz. If a time less than 15.625ms is specified, the waiting time may be longer than the specified time, and some objects that were not completed in the previous call may now be completed. */ @@ -1565,7 +1566,7 @@ wait_for_objects (DWORD nCount, HANDLE *lpHandles, } /* Replacement of MsgWaitForMultipleObjects, with the bWaitAll and - dwWakeMask parameters removed. The function's return values are as + dwWakeMask parameters removed. The function's return values are as follows: [0 ~ nCount-1], the return value indicates the lpHandles array @@ -1580,7 +1581,7 @@ wait_for_objects (DWORD nCount, HANDLE *lpHandles, [WFO_TIMEOUT], The time-out interval elapsed. - [WFO_FAILED], The function has failed. To get extended error + [WFO_FAILED], The function has failed. To get extended error information, call GetLastError. */ static DWORD msg_wait_for_objects (DWORD nCount, HANDLE *lpHandles, @@ -1599,7 +1600,7 @@ msg_wait_for_objects (DWORD nCount, HANDLE *lpHandles, return WFO_TIMEOUT; /* The return value of MsgWaitForMultipleObjects can be WAIT_OBJECT_0 + nCount, indicating that a message was - received. So use (<=) rather than (<) here. */ + received. So use (<=) rather than (<) here. */ else if (res >= WAIT_OBJECT_0 && res <= WAIT_OBJECT_0 + nCount) return res - WAIT_OBJECT_0; @@ -1630,7 +1631,7 @@ msg_wait_for_objects (DWORD nCount, HANDLE *lpHandles, && res < WAIT_OBJECT_0 + count) return offset + res - WAIT_OBJECT_0; /* When a message is received during the wait, return nCount - directly. This is the distinction that needs to be made + directly. This is the distinction that needs to be made compared to wait_for_objects. */ else if (res == WAIT_OBJECT_0 + count) return nCount; @@ -1907,7 +1908,7 @@ delete_child (child_process *cp) { int i; - /* Should not be deleting a child that is still needed. */ + /* Should not be deleting a child that is still needed. */ for (i = 0; i < MAXDESC; i++) if (fd_info[i].cp == cp) emacs_abort (); @@ -1993,10 +1994,10 @@ release_listen_threads (void) } } -/* Thread proc for child process and socket reader threads. Each thread +/* Thread proc for child process and socket reader threads. Each thread is normally blocked until woken by select() to check for input by reading one char. When the read completes, char_avail is signaled - to wake up the select emulator and the thread blocks itself again. */ + to wake up the select emulator and the thread blocks itself again. */ static DWORD WINAPI reader_thread (void *arg) { @@ -2030,7 +2031,7 @@ reader_thread (void *arg) break; /* The name char_avail is a misnomer - it really just means the - read-ahead has completed, whether successfully or not. */ + read-ahead has completed, whether successfully or not. */ if (!SetEvent (cp->char_avail)) { DebPrint (("reader_thread.SetEvent(0x%x) failed with %lu for fd %ld (PID %d)\n", @@ -2143,7 +2144,7 @@ create_child (char *exe, char *cmdline, char *env, int is_gui_app, /* CreateProcess handles batch files as exe specially. This special handling fails when both the batch file and arguments are quoted. - We pass NULL as exe to avoid the special handling. */ + We pass NULL as exe to avoid the special handling. */ if (exe && cmdline[0] == '"' && (ext = strrchr (exe, '.')) && (xstrcasecmp (ext, ".bat") == 0 @@ -2335,7 +2336,7 @@ waitpid (pid_t pid, int *status, int options) if (dont_wait) timeout_ms = 0; else - timeout_ms = 1000; /* check for quit about once a second. */ + timeout_ms = 1000; /* Check for quit about once a second. */ do { @@ -2451,7 +2452,7 @@ open_input_file (file_data *p_file, char *filename) } /* Return pointer to section header for section containing the given - relative virtual address. */ + relative virtual address. */ IMAGE_SECTION_HEADER * rva_to_section (DWORD_PTR rva, IMAGE_NT_HEADERS * nt_header) { commit aa66edeeba508bf5be89a5257216a5d995e47d89 Author: Stefan Kangas Date: Sat Mar 15 02:37:44 2025 +0100 Delete pre-Emacs 20 documentation from term.el * lisp/term.el: Delete comment titled "Converting process modes to use term mode". This comment from 1994 is only of historical interest. diff --git a/lisp/term.el b/lisp/term.el index 4f0c5337c1e..862103d88e6 100644 --- a/lisp/term.el +++ b/lisp/term.el @@ -4741,77 +4741,6 @@ The return value may be nil for a special serial port." :button (:toggle . ,(equal (plist-get config (nth 0 y)) (nth 1 y)))))))) - -;;; Converting process modes to use term mode -;; =========================================================================== -;; Renaming variables -;; Most of the work is renaming variables and functions. These are the common -;; ones: -;; Local variables: -;; last-input-start term-last-input-start -;; last-input-end term-last-input-end -;; shell-prompt-pattern term-prompt-regexp -;; shell-set-directory-error-hook -;; Miscellaneous: -;; shell-set-directory -;; shell-mode-map term-mode-map -;; Commands: -;; shell-send-input term-send-input -;; shell-send-eof term-delchar-or-maybe-eof -;; kill-shell-input term-kill-input -;; interrupt-shell-subjob term-interrupt-subjob -;; stop-shell-subjob term-stop-subjob -;; quit-shell-subjob term-quit-subjob -;; kill-shell-subjob term-kill-subjob -;; kill-output-from-shell term-kill-output -;; show-output-from-shell term-show-output -;; copy-last-shell-input Use term-previous-input/term-next-input -;; -;; SHELL-SET-DIRECTORY is gone, its functionality taken over by -;; SHELL-DIRECTORY-TRACKER, the shell mode's term-input-filter-functions. -;; Term mode does not provide functionality equivalent to -;; shell-set-directory-error-hook; it is gone. -;; -;; term-last-input-start is provided for modes which want to munge -;; the buffer after input is sent, perhaps because the inferior -;; insists on echoing the input. The LAST-INPUT-START variable in -;; the old shell package was used to implement a history mechanism, -;; but you should think twice before using term-last-input-start -;; for this; the input history ring often does the job better. -;; -;; If you are implementing some process-in-a-buffer mode, called foo-mode, do -;; *not* create the term-mode local variables in your foo-mode function. -;; This is not modular. Instead, call term-mode, and let *it* create the -;; necessary term-specific local variables. Then create the -;; foo-mode-specific local variables in foo-mode. Set the buffer's keymap to -;; be foo-mode-map, and its mode to be foo-mode. Set the term-mode hooks -;; (term-{prompt-regexp, input-filter, input-filter-functions, -;; get-old-input) that need to be different from the defaults. Call -;; foo-mode-hook, and you're done. Don't run the term-mode hook yourself; -;; term-mode will take care of it. The following example, from shell.el, -;; is typical: -;; -;; (defvar shell-mode-map -;; (let ((map (make-sparse-keymap))) -;; (define-key map "\C-c\C-f" 'shell-forward-command) -;; (define-key map "\C-c\C-b" 'shell-backward-command) -;; (define-key map "\t" 'term-dynamic-complete) -;; (define-key map "\M-?" -;; 'term-dynamic-list-filename-completions))) -;; -;; (define-derived-mode shell-mode term-mode "Shell" -;; "A shell mode." -;; (setq-local term-prompt-regexp shell-prompt-pattern) -;; (setq-local shell-directory-stack nil) -;; (add-hook 'term-input-filter-functions #'shell-directory-tracker nil t)) -;; -;; Completion for term-mode users -;; -;; For modes that use term-mode, term-dynamic-complete-functions is the -;; hook to add completion functions to. Functions on this list should return -;; non-nil if completion occurs (i.e., further completion should not occur). -;; You could use completion-in-region to do the bulk of the -;; completion job. (provide 'term) commit 3864b9352a01804af2e4b43a6bc171953375cdf7 Author: Stefan Kangas Date: Sat Mar 15 01:17:24 2025 +0100 ; * etc/symbol-releases.eld: Add current-global-map. This avoids saying that it was introduced in Emacs 29.1. diff --git a/etc/symbol-releases.eld b/etc/symbol-releases.eld index 4d5ba1ca606..93f0f5419f4 100644 --- a/etc/symbol-releases.eld +++ b/etc/symbol-releases.eld @@ -59,6 +59,7 @@ ("19.34" fun make-directory) ("19.7" fun defsubst) + ("18.59" fun current-global-map) ("18.59" fun mark) ("13.8" fun nthcdr) ("13.8" fun nreverse) commit ecd756dd391c1436820593e240f1f7b8a28b2c6e Author: Stefan Kangas Date: Sat Mar 15 01:01:16 2025 +0100 Recommend using 'keymap-set' in Commentaries * lisp/calendar/timeclock.el, lisp/comint.el, lisp/ehelp.el: * lisp/gnus/gnus-bookmark.el, lisp/gnus/smiley.el, lisp/ido.el: * lisp/isearchb.el, lisp/mail/mailabbrev.el, lisp/net/ange-ftp.el: * lisp/net/goto-addr.el, lisp/play/fortune.el, lisp/saveplace.el: * lisp/shell.el, lisp/term.el, lisp/textmodes/makeinfo.el: * lisp/textmodes/refer.el, lisp/textmodes/remember.el: * lisp/textmodes/table.el, lisp/woman.el: Recommend using 'keymap-set' instead of 'define-key' in Commentaries. diff --git a/lisp/calendar/timeclock.el b/lisp/calendar/timeclock.el index 5fa3ec333d4..5d32e37695f 100644 --- a/lisp/calendar/timeclock.el +++ b/lisp/calendar/timeclock.el @@ -37,12 +37,12 @@ ;; You'll probably want to bind the timeclock commands to some handy ;; keystrokes. Assuming C-c t is unbound, you might use: ;; -;; (define-key (kbd "C-c t i") 'timeclock-in) -;; (define-key (kbd "C-c t o") 'timeclock-out) -;; (define-key (kbd "C-c t c") 'timeclock-change) -;; (define-key (kbd "C-c t r") 'timeclock-reread-log) -;; (define-key (kbd "C-c t u") 'timeclock-update-mode-line) -;; (define-key (kbd "C-c t w") 'timeclock-when-to-leave-string) +;; (keymap-set "C-c t i" 'timeclock-in) +;; (keymap-set "C-c t o" 'timeclock-out) +;; (keymap-set "C-c t c" 'timeclock-change) +;; (keymap-set "C-c t r" 'timeclock-reread-log) +;; (keymap-set "C-c t u" 'timeclock-update-mode-line) +;; (keymap-set "C-c t w" 'timeclock-when-to-leave-string) ;; If you want Emacs to display the amount of time "left" to your ;; workday in the mode-line, you can either set the value of diff --git a/lisp/comint.el b/lisp/comint.el index f0305e4de3c..f92ee382473 100644 --- a/lisp/comint.el +++ b/lisp/comint.el @@ -3215,8 +3215,8 @@ its response can be seen." ;; These are not installed in the comint-mode keymap. But they are ;; available for people who want them. Shell-mode installs them: -;; (define-key shell-mode-map "\t" 'completion-at-point) -;; (define-key shell-mode-map "\M-?" +;; (keymap-set shell-mode-map "TAB" 'completion-at-point) +;; (keymap-set shell-mode-map "M-?" ;; 'comint-dynamic-list-filename-completions))) ;; ;; Commands like this are fine things to put in load hooks if you diff --git a/lisp/ehelp.el b/lisp/ehelp.el index 42212f69196..ed86f663100 100644 --- a/lisp/ehelp.el +++ b/lisp/ehelp.el @@ -31,9 +31,9 @@ ;; buffer. ;; To make this the default, you must do -;; (define-key global-map "\C-h" 'ehelp-command) -;; (define-key global-map [help] 'ehelp-command) -;; (define-key global-map [f1] 'ehelp-command) +;; (keymap-global-set "C-h" 'ehelp-command) +;; (keymap-global-set "" 'ehelp-command) +;; (keymap-global-set "" 'ehelp-command) ;;; Code: diff --git a/lisp/gnus/gnus-bookmark.el b/lisp/gnus/gnus-bookmark.el index dbe587491ff..6c07e15fa49 100644 --- a/lisp/gnus/gnus-bookmark.el +++ b/lisp/gnus/gnus-bookmark.el @@ -55,10 +55,10 @@ (require 'gnus-sum) ;; FIXME: should avoid using C-c (no?) -;; (define-key gnus-summary-mode-map "\C-crm" 'gnus-bookmark-set) -;; (define-key global-map "\C-crb" 'gnus-bookmark-jump) -;; (define-key global-map "\C-crj" 'gnus-bookmark-jump) -;; (define-key global-map "\C-crl" 'gnus-bookmark-bmenu-list) +;; (keymap-set gnus-summary-mode-map "C-c r m" 'gnus-bookmark-set) +;; (keymap-global-set "C-c r b" 'gnus-bookmark-jump) +;; (keymap-global-set "C-c r j" 'gnus-bookmark-jump) +;; (keymap-global-set "C-c r l" 'gnus-bookmark-bmenu-list) ;; FIXME: Add keybindings, see ;; http://thread.gmane.org/gmane.emacs.gnus.general/63101/focus=63379 [dead link] diff --git a/lisp/gnus/smiley.el b/lisp/gnus/smiley.el index c8cbde28de6..8b023cd1dbb 100644 --- a/lisp/gnus/smiley.el +++ b/lisp/gnus/smiley.el @@ -189,12 +189,9 @@ regexp to replace with IMAGE. IMAGE is the name of an image file in smiley-cached-regexp-alist)))))))) ;; Not implemented: -;; (defvar smiley-mouse-map -;; (let ((map (make-sparse-keymap))) -;; (define-key map [down-mouse-2] 'ignore) ; override widget -;; (define-key map [mouse-2] -;; 'smiley-mouse-toggle-buffer) -;; map)) +;; (defvar-keymap smiley-mouse-map +;; "" #'ignore ; override widget +;; "" #'smiley-mouse-toggle-buffer) ;;;###autoload (defun smiley-region (start end) diff --git a/lisp/ido.el b/lisp/ido.el index 8987fa471de..cd1f9260262 100644 --- a/lisp/ido.el +++ b/lisp/ido.el @@ -214,7 +214,7 @@ ;; `ido-buffer-completion-map'. ;; ;; (with-eval-after-load 'ido -;; (define-key ido-common-completion-map " " 'ido-next-match)) +;; (keymap-set ido-common-completion-map "SPC" 'ido-next-match)) ;; Seeing all the matching buffers or files ;; ---------------------------------------- diff --git a/lisp/isearchb.el b/lisp/isearchb.el index 599f7976044..9ecd83a2b6a 100644 --- a/lisp/isearchb.el +++ b/lisp/isearchb.el @@ -41,7 +41,7 @@ ;; mode, just like with isearch. I use C-z for this. The binding in ;; my .emacs looks like: ;; -;; (define-key global-map [(control ?z)] 'isearchb-activate) +;; (keymap-global-set "C-z" 'isearchb-activate) ;; ;; Now, after pressing C-z (for example), each self-inserting ;; character thereafter will search for a buffer containing those diff --git a/lisp/mail/mailabbrev.el b/lisp/mail/mailabbrev.el index 15d697db6f9..534f277ac57 100644 --- a/lisp/mail/mailabbrev.el +++ b/lisp/mail/mailabbrev.el @@ -109,8 +109,8 @@ ;; (add-hook ;; 'mail-mode-hook ;; (lambda () -;; (define-key mail-mode-map [remap next-line] 'mail-abbrev-next-line) -;; (define-key mail-mode-map [remap end-of-buffer] 'mail-abbrev-end-of-buffer))) +;; (keymap-set mail-mode-map " " 'mail-abbrev-next-line) +;; (keymap-set mail-mode-map " " 'mail-abbrev-end-of-buffer))) ;; ;; If you want multiple addresses separated by a string other than ", " then ;; you can set the variable mail-alias-separator-string to it. This has to diff --git a/lisp/net/ange-ftp.el b/lisp/net/ange-ftp.el index db889bfec68..84e170987ef 100644 --- a/lisp/net/ange-ftp.el +++ b/lisp/net/ange-ftp.el @@ -1460,7 +1460,7 @@ only return the directory part of FILE." ;;;; ------------------------------------------------------------ ;; (setq ange-ftp-tmp-keymap (make-sparse-keymap)) -;; (define-key ange-ftp-tmp-keymap "\C-m" 'exit-minibuffer) +;; (keymap-set ange-ftp-tmp-keymap "C-m" 'exit-minibuffer) (defun ange-ftp-repaint-minibuffer () "Clear any existing minibuffer message; let the minibuffer contents show." @@ -4101,8 +4101,8 @@ E.g., ;; Put these lines uncommented in your .emacs if you want C-r to refresh ;; ange-ftp's cache whilst doing filename completion. ;; -;;(define-key minibuffer-local-completion-map "\C-r" 'ange-ftp-reread-dir) -;;(define-key minibuffer-local-must-match-map "\C-r" 'ange-ftp-reread-dir) +;;(keymap-set minibuffer-local-completion-map "C-r" 'ange-ftp-reread-dir) +;;(keymap-set minibuffer-local-must-match-map "C-r" 'ange-ftp-reread-dir) ;;;###autoload (define-obsolete-function-alias 'ange-ftp-re-read-dir #'ange-ftp-reread-dir "29.1") diff --git a/lisp/net/goto-addr.el b/lisp/net/goto-addr.el index ad273663523..d803a050e90 100644 --- a/lisp/net/goto-addr.el +++ b/lisp/net/goto-addr.el @@ -43,9 +43,8 @@ ;; (for example): ;; ;; (setq goto-address-highlight-keymap -;; (let ((m (make-sparse-keymap))) -;; (define-key m [S-mouse-2] 'goto-address-at-point) -;; m)) +;; (define-keymap +;; "S-" 'goto-address-at-point)) ;; ;; Known bugs/features: diff --git a/lisp/play/fortune.el b/lisp/play/fortune.el index 0fa98f486be..f917b3f701a 100644 --- a/lisp/play/fortune.el +++ b/lisp/play/fortune.el @@ -57,9 +57,8 @@ ;; ;; I also have this in my .gnus: ;; -;; (add-hook 'gnus-article-mode-hook -;; (lambda () -;; (define-key gnus-article-mode-map "i" #'fortune-from-region))) +;; (with-eval-after-load 'gnus-art +;; (keymap-set gnus-article-mode-map "i" #'fortune-from-region)) ;; ;; which allows marking a region and then pressing "i" so that the marked ;; region will be automatically added to my favorite fortune file. diff --git a/lisp/saveplace.el b/lisp/saveplace.el index 65dbcf41be2..8b8d9a445d7 100644 --- a/lisp/saveplace.el +++ b/lisp/saveplace.el @@ -38,7 +38,7 @@ (require 'cl-lib) ;; this is what I was using during testing: -;; (define-key ctl-x-map "p" 'toggle-save-place-globally) +;; (keymap-set ctl-x-map "p" 'toggle-save-place-globally) (defgroup save-place nil "Automatically save place in files." diff --git a/lisp/shell.el b/lisp/shell.el index 54a404f1ee2..a35a0840651 100644 --- a/lisp/shell.el +++ b/lisp/shell.el @@ -48,8 +48,8 @@ ;; Some suggestions for your init file. ;; ;; ;; Define M-# to run some strange command: -;; (eval-after-load "shell" -;; '(define-key shell-mode-map "\M-#" 'shells-dynamic-spell)) +;; (with-eval-after-load 'shell +;; (keymap-set shell-mode-map "M-#" 'shells-dynamic-spell)) ;; Brief Command Documentation: ;;============================================================================ diff --git a/lisp/term.el b/lisp/term.el index 8d57f949615..4f0c5337c1e 100644 --- a/lisp/term.el +++ b/lisp/term.el @@ -4228,8 +4228,8 @@ This is a good place to put keybindings.") ;; These are not installed in the term-mode keymap. But they are ;; available for people who want them. Shell-mode installs them: -;; (define-key shell-mode-map "\t" 'term-dynamic-complete) -;; (define-key shell-mode-map "\M-?" +;; (keymap-set shell-mode-map "TAB" 'term-dynamic-complete) +;; (keymap-set shell-mode-map "M-?" ;; 'term-dynamic-list-filename-completions))) ;; ;; Commands like this are fine things to put in load hooks if you diff --git a/lisp/textmodes/makeinfo.el b/lisp/textmodes/makeinfo.el index b2d58b41bfa..9c1d0c5223a 100644 --- a/lisp/textmodes/makeinfo.el +++ b/lisp/textmodes/makeinfo.el @@ -34,10 +34,10 @@ ;;; Keybindings (defined in `texinfo.el') ;; makeinfo bindings -; (define-key texinfo-mode-map "\C-c\C-m\C-r" 'makeinfo-region) -; (define-key texinfo-mode-map "\C-c\C-m\C-b" 'makeinfo-buffer) -; (define-key texinfo-mode-map "\C-c\C-m\C-k" 'kill-compilation) -; (define-key texinfo-mode-map "\C-c\C-m\C-l" +; (keymap-set texinfo-mode-map "C-c C-m C-r" 'makeinfo-region) +; (keymap-set texinfo-mode-map "C-c C-m C-b" 'makeinfo-buffer) +; (keymap-set texinfo-mode-map "C-c C-m C-k" 'kill-compilation) +; (keymap-set texinfo-mode-map "C-c C-m C-l" ; 'makeinfo-recenter-compilation-buffer) ;;; Code: diff --git a/lisp/textmodes/refer.el b/lisp/textmodes/refer.el index 0e38c2e0969..c7569ea8f35 100644 --- a/lisp/textmodes/refer.el +++ b/lisp/textmodes/refer.el @@ -46,7 +46,7 @@ ;; refer-yank-key to insert it at point in the current buffer ;; (typically as the argument of a \cite{} command). ;; -;; I use (define-key tex-mode-map "\C-c\C-y" 'refer-yank-key) +;; I use (keymap-set tex-mode-map "C-c C-y" 'refer-yank-key) ;; to bind this often-used function to a key in (la)tex-mode. ;; ;; If the list of bibliography files changes, reinitialize the variable diff --git a/lisp/textmodes/remember.el b/lisp/textmodes/remember.el index ac0dd384dda..d04d811d78f 100644 --- a/lisp/textmodes/remember.el +++ b/lisp/textmodes/remember.el @@ -129,7 +129,7 @@ ;; To map the primary remember function to the keystroke F8, do the ;; following. ;; -;; (define-key global-map [f8] 'remember) +;; (keymap-global-set "" 'remember) ;; ;; * Feedback ;; diff --git a/lisp/textmodes/table.el b/lisp/textmodes/table.el index e8f4170e729..0e024f9261e 100644 --- a/lisp/textmodes/table.el +++ b/lisp/textmodes/table.el @@ -343,7 +343,7 @@ ;; ;; (add-hook 'table-cell-map-hook ;; (lambda () -;; (define-key table-cell-map [] '))) +;; (keymap-set table-cell-map "" '))) ;; ;; ----- ;; Menu: diff --git a/lisp/woman.el b/lisp/woman.el index 0d740fd071e..67f2b5cf9d7 100644 --- a/lisp/woman.el +++ b/lisp/woman.el @@ -89,9 +89,8 @@ ;; structure. ;; Or (3): Put this in your init file: -;; (add-hook 'dired-mode-hook -;; (lambda () -;; (define-key dired-mode-map "W" 'woman-dired-find-file))) +;; (with-eval-after-load 'dired +;; (keymap-set dired-mode-map "W" 'woman-dired-find-file)) ;; and open the directory containing the man page file using Dired, ;; put the cursor on the file, and press `W'. commit 679ad95a67fbecafd203c9e7f55547a312b3833b Author: Stefan Kangas Date: Sat Mar 15 00:09:45 2025 +0100 Recommend using 'keymap-global-set' in Commentaries * lisp/bs.el, lisp/calculator.el, lisp/cedet/data-debug.el: * lisp/help-at-pt.el, lisp/hilit-chg.el, lisp/mail/mspools.el: * lisp/mh-e/mh-e.el, lisp/mouse-copy.el, lisp/mouse-drag.el: * lisp/net/browse-url.el, lisp/net/webjump.el, lisp/printing.el: * lisp/progmodes/cfengine.el, lisp/progmodes/ebnf2ps.el: * lisp/ps-print.el, lisp/repeat.el, lisp/term/wyse50.el: * lisp/term/x-win.el, lisp/vcursor.el, lisp/woman.el: Recommend using 'keymap-global-set' instead of 'global-set-key'. diff --git a/lisp/bs.el b/lisp/bs.el index f5e99375fa8..29af72f762b 100644 --- a/lisp/bs.el +++ b/lisp/bs.el @@ -73,8 +73,8 @@ ;; This package offers two functions for buffer cycling. If you want to cycle ;; through buffer list you can use `bs-cycle-next' or `bs-cycle-previous'. ;; Bind these function to a key like -;; (global-set-key [(f9)] 'bs-cycle-previous) -;; (global-set-key [(f10)] 'bs-cycle-next) +;; (keymap-global-set "" #'bs-cycle-previous) +;; (keymap-global-set "" #'bs-cycle-next) ;; ;; Both functions use a special subset of all buffers for cycling to avoid ;; to go through internal buffers like *Messages*. diff --git a/lisp/calculator.el b/lisp/calculator.el index ccb101befe9..337560d38c9 100644 --- a/lisp/calculator.el +++ b/lisp/calculator.el @@ -28,7 +28,7 @@ ;; ;; You can bind this to a key by adding this to your Init file: ;; -;; (global-set-key [(control return)] 'calculator) +;; (keymap-global-set "C-" #'calculator) ;; ;; Written by Eli Barzilay, eli@barzilay.org diff --git a/lisp/cedet/data-debug.el b/lisp/cedet/data-debug.el index 02cda6c85d6..2c3591c47c9 100644 --- a/lisp/cedet/data-debug.el +++ b/lisp/cedet/data-debug.el @@ -29,7 +29,7 @@ ;; ;; The best way to get started is to bind M-: to 'data-debug-eval-expression. ;; -;; (global-set-key "\M-:" 'data-debug-eval-expression) +;; (keymap-global-set "M-:" 'data-debug-eval-expression) ;; ;; If you write functions with complex output that need debugging, you ;; can make them interactive with data-debug-show-stuff. For example: diff --git a/lisp/help-at-pt.el b/lisp/help-at-pt.el index 3c0c60bfa4d..68054016dc5 100644 --- a/lisp/help-at-pt.el +++ b/lisp/help-at-pt.el @@ -40,8 +40,8 @@ ;; ;; Suggested key bindings: ;; -;; (global-set-key [C-tab] 'scan-buf-next-region) -;; (global-set-key [C-M-tab] 'scan-buf-previous-region) +;; (keymap-global-set "C-" #'scan-buf-next-region) +;; (keymap-global-set "C-M-" #'scan-buf-previous-region) ;;; Code: diff --git a/lisp/hilit-chg.el b/lisp/hilit-chg.el index 93ced6a5484..28a4b5a6907 100644 --- a/lisp/hilit-chg.el +++ b/lisp/hilit-chg.el @@ -113,8 +113,8 @@ ;; Possible bindings: -;; (global-set-key '[C-right] #'highlight-changes-next-change) -;; (global-set-key '[C-left] #'highlight-changes-previous-change) +;; (keymap-global-set "C-" #'highlight-changes-next-change) +;; (keymap-global-set "C-" #'highlight-changes-previous-change) ;; ;; Other interactive functions (that could be bound if desired): ;; `highlight-changes-mode' diff --git a/lisp/mail/mspools.el b/lisp/mail/mspools.el index df956e8f58a..d9f54642029 100644 --- a/lisp/mail/mspools.el +++ b/lisp/mail/mspools.el @@ -62,8 +62,8 @@ ;; Extras. ;; -;; (global-set-key '[S-f1] 'mspools-show) ;Bind mspools-show to Shift F1. -;; (setq mspools-update t) ;Automatically update buffer. +;; (keymap-global-set "S-" #'mspools-show) ;Bind mspools-show to Shift F1. +;; (setopt mspools-update t) ;Automatically update buffer. ;; Interface with the mail filter. ;; We assume that the mail filter drops new mail into the spool diff --git a/lisp/mh-e/mh-e.el b/lisp/mh-e/mh-e.el index e9e4e271065..e507996c581 100644 --- a/lisp/mh-e/mh-e.el +++ b/lisp/mh-e/mh-e.el @@ -42,9 +42,9 @@ ;; M-x mh-smail to send mail. From within the mail reader, "s" works, too. ;; Your .emacs might benefit from these bindings: -;; (global-set-key "\C-cr" 'mh-rmail) -;; (global-set-key "\C-xm" 'mh-smail) -;; (global-set-key "\C-x4m" 'mh-smail-other-window) +;; (keymap-global-set "C-c r" #'mh-rmail) +;; (keymap-global-set "C-x m" #'mh-smail) +;; (keymap-global-set "C-x 4 m" #'mh-smail-other-window) ;; Mailing Lists: ;; mh-e-users@lists.sourceforge.net diff --git a/lisp/mouse-copy.el b/lisp/mouse-copy.el index d022d57796b..cbd3f360c3e 100644 --- a/lisp/mouse-copy.el +++ b/lisp/mouse-copy.el @@ -36,14 +36,15 @@ ;; for ``one-click scrolling''. ;; ;; To use mouse-copy, place the following in your init file: -;; (require 'mouse-copy) -;; (global-set-key [M-down-mouse-1] 'mouse-drag-secondary-pasting) -;; (global-set-key [M-S-down-mouse-1] 'mouse-drag-secondary-moving) -;; -;; (These definitions override the old binding of M-mouse-1 to -;; mouse-drag-secondary. I find I don't use that command much so its -;; loss is not important, and it can be made up with a M-mouse-1 -;; followed by a M-mouse-3. I personally reserve M-mouse bindings +;; (autoload 'mouse-drag-secondary-pasting "mouse-copy") +;; (autoload 'mouse-drag-secondary-moving "mouse-copy") +;; (keymap-global-set "M-" #'mouse-drag-secondary-pasting) +;; (keymap-global-set "M-S-" #'mouse-drag-secondary-moving) +;; +;; (These definitions override the old binding of `M-' to +;; `mouse-drag-secondary'. I find I don't use that command much so its +;; loss is not important, and it can be made up with a `M-' +;; followed by a `M-mouse-3'. I personally reserve M-mouse bindings ;; for my window manager and bind everything to C-mouse.) ;; ;; diff --git a/lisp/mouse-drag.el b/lisp/mouse-drag.el index e86c0f3590c..bb27994bbca 100644 --- a/lisp/mouse-drag.el +++ b/lisp/mouse-drag.el @@ -48,9 +48,9 @@ ;; ;; To use mouse-drag, place the following in your init file: ;; -either- -;; (global-set-key [down-mouse-2] 'mouse-drag-throw) +;; (keymap-global-set "" #'mouse-drag-throw) ;; -or- -;; (global-set-key [down-mouse-2] 'mouse-drag-drag) +;; (keymap-global-set "" #'mouse-drag-drag) ;; ;; ;; diff --git a/lisp/net/browse-url.el b/lisp/net/browse-url.el index b9610fec150..3b2d3983002 100644 --- a/lisp/net/browse-url.el +++ b/lisp/net/browse-url.el @@ -72,7 +72,7 @@ ;; M-x browse-url ;; To display a URL by shift-clicking on it, put this in your init file: -;; (global-set-key [S-mouse-2] 'browse-url-at-mouse) +;; (keymap-global-set "S-" 'browse-url-at-mouse) ;; (Note that using Shift-mouse-1 is not desirable because ;; that event has a standard meaning in Emacs.) diff --git a/lisp/net/webjump.el b/lisp/net/webjump.el index e401042e2ef..746688765a3 100644 --- a/lisp/net/webjump.el +++ b/lisp/net/webjump.el @@ -41,13 +41,13 @@ ;; You may wish to add something like the following to your init file: ;; -;; (global-set-key "\C-cj" 'webjump) -;; (setq webjump-sites -;; (append '( -;; ("My Home Page" . "www.someisp.net/users/joebobjr/") -;; ("Pop's Site" . "www.joebob-and-son.com/") -;; ) -;; webjump-sample-sites)) +;; (keymap-global-set "C-c j" #'webjump) +;; (setopt webjump-sites +;; (append '( +;; ("My Home Page" . "www.someisp.net/users/joebobjr/") +;; ("Pop's Site" . "www.joebob-and-son.com/") +;; ) +;; webjump-sample-sites)) ;; ;; The above loads this package, binds `C-c j' to invoke WebJump, and adds your ;; personal favorite sites to the hotlist. diff --git a/lisp/printing.el b/lisp/printing.el index c84ee54eb7f..99408eef6fe 100644 --- a/lisp/printing.el +++ b/lisp/printing.el @@ -450,21 +450,21 @@ ;; ;; Current global keyboard mapping is: ;; -;; (global-set-key [print] 'pr-ps-fast-fire) -;; (global-set-key [M-print] 'pr-ps-mode-using-ghostscript) -;; (global-set-key [S-print] 'pr-ps-mode-using-ghostscript) -;; (global-set-key [C-print] 'pr-txt-fast-fire) -;; (global-set-key [C-M-print] 'pr-txt-fast-fire) +;; (keymap-global-set "" 'pr-ps-fast-fire) +;; (keymap-global-set "M-" 'pr-ps-mode-using-ghostscript) +;; (keymap-global-set "S-" 'pr-ps-mode-using-ghostscript) +;; (keymap-global-set "C-" 'pr-txt-fast-fire) +;; (keymap-global-set "C-M-" 'pr-txt-fast-fire) ;; ;; As a suggestion of global keyboard mapping for some `printing' commands: ;; -;; (global-set-key "\C-ci" 'pr-interface) -;; (global-set-key "\C-cbp" 'pr-ps-buffer-print) -;; (global-set-key "\C-cbx" 'pr-ps-buffer-preview) -;; (global-set-key "\C-cbb" 'pr-ps-buffer-using-ghostscript) -;; (global-set-key "\C-crp" 'pr-ps-region-print) -;; (global-set-key "\C-crx" 'pr-ps-region-preview) -;; (global-set-key "\C-crr" 'pr-ps-region-using-ghostscript) +;; (keymap-global-set "C-c i" 'pr-interface) +;; (keymap-global-set "C-c b p" 'pr-ps-buffer-print) +;; (keymap-global-set "C-c b x" 'pr-ps-buffer-preview) +;; (keymap-global-set "C-c b b" 'pr-ps-buffer-using-ghostscript) +;; (keymap-global-set "C-c r p" 'pr-ps-region-print) +;; (keymap-global-set "C-c r x" 'pr-ps-region-preview) +;; (keymap-global-set "C-c r r" 'pr-ps-region-using-ghostscript) ;; ;; ;; Options @@ -2969,13 +2969,13 @@ Calls `pr-update-menus' to adjust menus." (global-set-key [(control meta print)] 'pr-txt-fast-fire) ;;; You can also use something like: -;;;(global-set-key "\C-ci" 'pr-interface) -;;;(global-set-key "\C-cbp" 'pr-ps-buffer-print) -;;;(global-set-key "\C-cbx" 'pr-ps-buffer-preview) -;;;(global-set-key "\C-cbb" 'pr-ps-buffer-using-ghostscript) -;;;(global-set-key "\C-crp" 'pr-ps-region-print) -;;;(global-set-key "\C-crx" 'pr-ps-region-preview) -;;;(global-set-key "\C-crr" 'pr-ps-region-using-ghostscript) +;;;(keymap-global-set "C-c i" 'pr-interface) +;;;(keymap-global-set "C-c b p" 'pr-ps-buffer-print) +;;;(keymap-global-set "C-c b x" 'pr-ps-buffer-preview) +;;;(keymap-global-set "C-c b b" 'pr-ps-buffer-using-ghostscript) +;;;(keymap-global-set "C-c r p" 'pr-ps-region-print) +;;;(keymap-global-set "C-c r x" 'pr-ps-region-preview) +;;;(keymap-global-set "C-c r r" 'pr-ps-region-using-ghostscript) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/lisp/progmodes/cfengine.el b/lisp/progmodes/cfengine.el index 9d9d1e8aa9d..acdb9a10cf7 100644 --- a/lisp/progmodes/cfengine.el +++ b/lisp/progmodes/cfengine.el @@ -52,7 +52,7 @@ ;; the policy, it's a quick way to make it more legible without ;; manually reindenting it. For instance: -;; (global-set-key [(control f4)] 'cfengine3-reformat-json-string) +;; (keymap-global-set "C-" 'cfengine3-reformat-json-string) ;; This is not the same as the mode written by Rolf Ebert ;; , distributed with cfengine-2.0.5. It does diff --git a/lisp/progmodes/ebnf2ps.el b/lisp/progmodes/ebnf2ps.el index 0147707f80d..d5a10eab2a8 100644 --- a/lisp/progmodes/ebnf2ps.el +++ b/lisp/progmodes/ebnf2ps.el @@ -148,9 +148,9 @@ ;; ;; Any of the `ebnf-' commands can be bound to keys. Here are some examples: ;; -;; (global-set-key 'f22 'ebnf-print-buffer) ;f22 is prsc -;; (global-set-key '(shift f22) 'ebnf-print-region) -;; (global-set-key '(control f22) 'ebnf-despool) +;; (keymap-global-set "" #'ebnf-print-buffer) ;f22 is prsc +;; (keymap-global-set "S-" #'ebnf-print-region) +;; (keymap-global-set "C-" #'ebnf-despool) ;; ;; ;; Invoking Ebnf2ps in Batch diff --git a/lisp/ps-print.el b/lisp/ps-print.el index ba0d543bbcc..53302a71efb 100644 --- a/lisp/ps-print.el +++ b/lisp/ps-print.el @@ -145,9 +145,9 @@ ;; `ps-spool-buffer-with-faces', `ps-spool-region-with-faces', and ;; `ps-despool'. Here are the bindings I use on my Sun 4 keyboard: ;; -;; (global-set-key 'f22 'ps-spool-buffer-with-faces) ;f22 is prsc -;; (global-set-key '(shift f22) 'ps-spool-region-with-faces) -;; (global-set-key '(control f22) 'ps-despool) +;; (keymap-global-set "" #'ps-spool-buffer-with-faces) ;f22 is prsc +;; (keymap-global-set "S-" #'ps-spool-region-with-faces) +;; (keymap-global-set "C-" #'ps-despool) ;; ;; ;; The Printer Interface diff --git a/lisp/repeat.el b/lisp/repeat.el index f014d8266db..daa53fe7195 100644 --- a/lisp/repeat.el +++ b/lisp/repeat.el @@ -54,8 +54,8 @@ ;; correctly if `repeat' is invoked through a rebinding to a single keystroke ;; and the global variable repeat-on-final-keystroke is set to a value ;; that doesn't include that keystroke. For example, the lines -;; (global-set-key "\C-z" 'repeat) -;; (setq repeat-on-final-keystroke "z") +;; (keymap-global-set "C-z" #'repeat) +;; (setopt repeat-on-final-keystroke "z") ;; in your .emacs would allow `edit-kbd-macro' to work correctly when C-z was ;; used in a keyboard macro to invoke `repeat', but would still allow C-x z ;; to be used for `repeat' elsewhere. The real reason for documenting this diff --git a/lisp/term/wyse50.el b/lisp/term/wyse50.el index e956ee120bc..875218d7269 100644 --- a/lisp/term/wyse50.el +++ b/lisp/term/wyse50.el @@ -145,14 +145,14 @@ M-r M-x move-to-window-line, Funct up-arrow or down-arrow are similar" ;; ;; By unsetting C-a and then binding it to a prefix, we ;; ;; allow the rest of the function keys which start with C-a ;; ;; to be recognized. - ;; '(("\C-a" nil) - ;; ("\C-k" nil) - ;; ("\C-j" nil) - ;; ("\C-l" nil) - ;; ("\C-h" nil) - ;; ("\er" nil))) - ;; (global-set-key (car key-definition) - ;; (nth 1 key-definition))) + ;; '(("C-a" nil) + ;; ("C-k" nil) + ;; ("C-j" nil) + ;; ("C-l" nil) + ;; ("C-h" nil) + ;; ("M-r" nil))) + ;; (keymap-global-set (car key-definition) + ;; (nth 1 key-definition))) (fset 'enable-arrow-keys nil)) (provide 'term/wyse50) diff --git a/lisp/term/x-win.el b/lisp/term/x-win.el index 91b5bd5838a..1863ff92c77 100644 --- a/lisp/term/x-win.el +++ b/lisp/term/x-win.el @@ -1318,7 +1318,7 @@ This returns an error if any Emacs frames are X frames." ;; and turned the Emacs f10 back on. ;; ;; Motif normally handles f10 itself, so don't try to handle it a second time. ;; (if (featurep 'motif) - ;; (global-set-key [f10] 'ignore)) + ;; (keymap-global-set "" 'ignore)) ;; Enable CLIPBOARD copy/paste through menu bar commands. (menu-bar-enable-clipboard) diff --git a/lisp/textmodes/table.el b/lisp/textmodes/table.el index 1090df8a8e1..e8f4170e729 100644 --- a/lisp/textmodes/table.el +++ b/lisp/textmodes/table.el @@ -333,7 +333,7 @@ ;; ;; (add-hook 'table-cell-map-hook ;; (lambda () -;; (local-set-key [] '))) +;; (keymap-local-set "" '))) ;; ;; Adding the above to your init file is a common way to customize a ;; mode specific keymap. However it does not work for this package. diff --git a/lisp/vcursor.el b/lisp/vcursor.el index 645102c198b..ff381c01b12 100644 --- a/lisp/vcursor.el +++ b/lisp/vcursor.el @@ -250,7 +250,7 @@ ;; In addition to any other bindings, vcursor-map contains key definitions ;; for handling the vcursor. You should assign this to a prefix key ;; in the usual way, e.g. -;; (global-set-key [f14] vcursor-map) +;; (keymap-global-set "" vcursor-map) ;; and also as usual \C-h in this map will list the key definitions, which ;; are designed to be easy to remember. ;; diff --git a/lisp/woman.el b/lisp/woman.el index a5ab82be40d..0d740fd071e 100644 --- a/lisp/woman.el +++ b/lisp/woman.el @@ -124,12 +124,11 @@ ;; The variable `woman-use-topic-at-point' can be rebound locally, ;; which may be useful to provide special private key bindings, e.g. -;; (global-set-key "\C-cw" -;; (lambda () -;; (interactive) -;; (let ((woman-use-topic-at-point t)) -;; (woman))))) - +;; (keymap-global-set "C-c w" +;; (lambda () +;; (interactive) +;; (let ((woman-use-topic-at-point t)) +;; (woman))))) ;; Customization, Hooks and Imenu ;; ============================== commit 2009ae85935beb345ce605abe398dbbe6cd84b9b Author: Stefan Kangas Date: Fri Mar 14 23:38:42 2025 +0100 Use defvar-keymap in which-func.el * lisp/progmodes/which-func.el (which-func-keymap): Use defvar-keymap. (which-func-maxout): Change :type to natnum. (which-func-format): Use substitute-command-keys. diff --git a/lisp/progmodes/which-func.el b/lisp/progmodes/which-func.el index c636cef1591..dab1fc2da14 100644 --- a/lisp/progmodes/which-func.el +++ b/lisp/progmodes/which-func.el @@ -105,7 +105,7 @@ If Which Function delays the initial display of buffers too much, e.g., when it is used with Eglot, and the language server takes a long time to send the information, you can use this option to delay activation of Which Function until Imenu is used for the first time." - :type 'integer) + :type 'natnum) (defcustom which-func-update-delay ;; Backwards-compatibility: if users had changed this before @@ -119,18 +119,15 @@ doing an update." :group 'display :version "30.1") -(defvar which-func-keymap - (let ((map (make-sparse-keymap))) - (define-key map [mode-line mouse-1] 'beginning-of-defun) - (define-key map [mode-line mouse-2] - (lambda () - (interactive) - (if (eq (point-min) 1) - (narrow-to-defun) - (widen)))) - (define-key map [mode-line mouse-3] 'end-of-defun) - map) - "Keymap to display on mode line which-func.") +(defvar-keymap which-func-keymap + :doc "Keymap to display on mode line which-func." + " " #'beginning-of-defun + " " (lambda () + (interactive) + (if (eq (point-min) 1) + (narrow-to-defun) + (widen))) + " " #'end-of-defun) (defface which-func ;; Whether `font-lock-function-name-face' is an appropriate face to @@ -164,11 +161,12 @@ doing an update." local-map ,which-func-keymap face which-func mouse-face mode-line-highlight - help-echo ,(concat - "Current function\n" - "mouse-1: go to beginning\n" - "mouse-2: toggle rest visibility\n" - "mouse-3: go to end")) + help-echo ,(substitute-command-keys + (concat + "Current function\n" + "\\`mouse-1': go to beginning\n" + "\\`mouse-2': toggle rest visibility\n" + "\\`mouse-3': go to end"))) "]") "Format for displaying the function in the mode line." :version "28.1" commit 33cc5427cbec224d55fac9d76b7c2978fdd5034b Author: Stefan Kangas Date: Fri Mar 14 23:22:11 2025 +0100 Add :set attribute to winner-dont-bind-my-keys * lisp/winner.el (winner--set-dont-bind-my-keys): New function. (winner-dont-bind-my-keys): Allow setting with setopt. (winner-mode-map): Use defvar-keymap. * test/lisp/winner-tests.el: New file. diff --git a/lisp/winner.el b/lisp/winner.el index b2f462365cf..7fcb44131e6 100644 --- a/lisp/winner.el +++ b/lisp/winner.el @@ -24,11 +24,11 @@ ;;; Commentary: ;; Winner mode is a global minor mode that records the changes in the -;; window configuration (i.e. how the frames are partitioned into -;; windows) so that the changes can be "undone" using the command -;; `winner-undo'. By default this one is bound to the key sequence -;; ctrl-c left. If you change your mind (while undoing), you can -;; press ctrl-c right (calling `winner-redo'). +;; window configuration (in other words, how the frames are partitioned +;; into windows), so that the changes can be "undone" using the command +;; `winner-undo'. By default, it is bound to the key sequence `C-c +;; '. If you change your mind (while undoing), you can press +;; `C-c ' (`winner-redo'). ;;; Code: @@ -44,9 +44,21 @@ "Restoring window configurations." :group 'windows) +(defun winner--set-dont-bind-my-keys (symbol value) + (defvar winner-mode-map) + (when (boundp 'winner-mode-map) + (if value + (progn (keymap-unset winner-mode-map "C-c ") + (keymap-unset winner-mode-map "C-c ")) + ;; Default bindings. + (keymap-set winner-mode-map "C-c " #'winner-undo) + (keymap-set winner-mode-map "C-c " #'winner-redo))) + (set-default symbol value)) + (defcustom winner-dont-bind-my-keys nil - "Non-nil means do not bind keys in Winner mode." - :type 'boolean) + "Non-nil means do not bind default keys in Winner mode." + :type 'boolean + :set #'winner--set-dont-bind-my-keys) (defcustom winner-ring-size 200 "Maximum number of stored window configurations per frame." @@ -321,13 +333,9 @@ You may want to include buffer names such as *Help*, *Apropos*, "Functions to run whenever Winner mode is turned off." :type 'hook) -(defvar winner-mode-map - (let ((map (make-sparse-keymap))) - (unless winner-dont-bind-my-keys - (define-key map [(control c) left] #'winner-undo) - (define-key map [(control c) right] #'winner-redo)) - map) - "Keymap for Winner mode.") +(defvar-keymap winner-mode-map + :doc "Keymap for Winner mode.") +(setopt winner-dont-bind-my-keys winner-dont-bind-my-keys) (defvar-keymap winner-repeat-map :doc "Keymap to repeat winner key sequences. Used in `repeat-mode'." diff --git a/test/lisp/winner-tests.el b/test/lisp/winner-tests.el new file mode 100644 index 00000000000..549ab2c117e --- /dev/null +++ b/test/lisp/winner-tests.el @@ -0,0 +1,32 @@ +;;; winner-tests.el --- Tests for winner.el -*- lexical-binding: t; -*- + +;; Copyright (C) 2025 Free Software Foundation, Inc. + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see . + +;;; Code: + +(require 'winner) + +(ert-deftest winner-dont-bind-my-keys () + (should (keymap-lookup winner-mode-map "C-c ")) + (setopt winner-dont-bind-my-keys t) + (should-not (keymap-lookup winner-mode-map "C-c ")) + (setopt winner-dont-bind-my-keys nil) + (should (keymap-lookup winner-mode-map "C-c "))) + +(provide 'winner-tests) +;;; winner-tests.el ends here commit 6b295347a9f7491ae4c16d942b3b12a54fc2373a Author: Stefan Kangas Date: Fri Mar 14 23:11:15 2025 +0100 Use defvar-keymap for command-history-mode-map * lisp/chistory.el (command-history-mode-map): Use defvar-keymap. diff --git a/lisp/chistory.el b/lisp/chistory.el index 6ccbd2f2df0..8d8ec7eda01 100644 --- a/lisp/chistory.el +++ b/lisp/chistory.el @@ -119,16 +119,14 @@ The buffer is left in Command History mode." (error "No command history") (command-history-mode))))) -(defvar command-history-mode-map - (let ((map (make-sparse-keymap))) - (set-keymap-parent map (make-composed-keymap lisp-mode-shared-map - special-mode-map)) - (define-key map "x" #'command-history-repeat) - (define-key map "\n" #'next-line) - (define-key map "\r" #'next-line) - (define-key map "\177" #'previous-line) - map) - "Keymap for `command-history-mode'.") +(defvar-keymap command-history-mode-map + :doc "Keymap for `command-history-mode'." + :parent (make-composed-keymap lisp-mode-shared-map + special-mode-map) + "x" #'command-history-repeat + "C-j" #'next-line + "RET" #'next-line + "DEL" #'previous-line) (define-derived-mode command-history-mode special-mode "Command History" "Major mode for listing and repeating recent commands. commit c29be6a2ecdf3b3746b5dccbd195dafb08b8b2d7 Author: Stefan Kangas Date: Fri Mar 14 22:07:25 2025 +0100 Move pulse.el from lisp/cedet/ to lisp/ * lisp/cedet/pulse.el: Move from here... * lisp/pulse.el: ...to here. This library is no longer relevant only to CEDET. It is used by other built-in libraries, GNU ELPA packages, and very likely third-party libraries too. diff --git a/lisp/cedet/pulse.el b/lisp/pulse.el similarity index 100% rename from lisp/cedet/pulse.el rename to lisp/pulse.el commit af81b3af4ce99c664b040313b548910770bc5854 Author: Dmitry Gutov Date: Sat Mar 15 04:26:43 2025 +0200 dired-copy-filename-as-kill: Support project-relative names * lisp/dired.el (dired-copy-filename-as-kill): Support new value of ARG, to mean file name relative to project root (bug#76792). * doc/emacs/dired.texi (Misc Dired Features): Mention it. * etc/NEWS: Same. diff --git a/doc/emacs/dired.texi b/doc/emacs/dired.texi index f52b001c121..0d7d7c4cf26 100644 --- a/doc/emacs/dired.texi +++ b/doc/emacs/dired.texi @@ -1868,8 +1868,9 @@ names of the marked (or next @var{n}) files into the kill ring, as if you had killed them with @kbd{C-w}. The names are separated by a space. - With a zero prefix argument, this uses the absolute file name of -each marked file. With just @kbd{C-u} as the prefix argument, it uses + With a zero prefix argument, this uses the absolute file name of each +marked file. With prefix value 1, it uses names relative to the +project root. With just @kbd{C-u} as the prefix argument, it uses file names relative to the Dired buffer's default directory. (This can still contain slashes if in a subdirectory.) As a special case, if point is on a directory header line, @kbd{w} gives you the absolute diff --git a/etc/NEWS b/etc/NEWS index b33f7cec73f..707a54ad15e 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1211,6 +1211,10 @@ Without 'dired-hide-details-hide-absolute-location': When 'dired-make-directory-clickable' is non-nil, clicking on the base name of the directory now reverts the Dired buffer. +*** 'dired-copy-filename-as-kill' supports project-relative names. +With a new value of the prefix argument (1), this command copies file +names relative to the root directory of the current project. + ** Grep +++ diff --git a/lisp/dired.el b/lisp/dired.el index b5bb658b503..ff6a31ed725 100644 --- a/lisp/dired.el +++ b/lisp/dired.el @@ -3478,6 +3478,7 @@ be quoted (with double quotes). (When there's a single file, no quoting is done.) With a zero prefix arg, use the absolute file name of each marked file. +With a prefix value 1, use the names relative to the current project root. With \\[universal-argument], use the file name relative to the Dired buffer's `default-directory'. (This still may contain slashes if in a subdirectory.) @@ -3489,8 +3490,13 @@ You can then feed the file name(s) to other commands with \\[yank]." (let* ((files (or (ensure-list (dired-get-subdir)) (if arg - (cond ((zerop (prefix-numeric-value arg)) + (cond ((eql 0 arg) (dired-get-marked-files)) + ((eql 1 arg) + (let ((root (project-root (project-current t)))) + (mapcar + (lambda (file) (file-relative-name file root)) + (dired-get-marked-files)))) ((consp arg) (dired-get-marked-files t)) (t commit 4980287e081d3efd29f64973938ca2a0575f521e Author: Tomas Nordin Date: Sat Mar 15 04:08:47 2025 +0200 Teach diff-apply-hunk to handle hunks with empty context * lisp/vc/diff-mode.el (diff-find-source-location): Consider the case when there is no diff context above or below edited lines. (bug#72556) * test/lisp/vc/diff-mode-tests.el: Add tests for undoing hunks from diffs with addtions only in the beginning or end of the source file. diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el index a99487b0370..459154f534b 100644 --- a/lisp/vc/diff-mode.el +++ b/lisp/vc/diff-mode.el @@ -2048,9 +2048,11 @@ SWITCHED is non-nil if the patch is already applied." (goto-char (point-min)) (forward-line (1- (string-to-number line))) (let* ((orig-pos (point)) (switched nil) - ;; FIXME: Check for case where both OLD and NEW are found. - (pos (or (diff-find-text (car old)) - (progn (setq switched t) (diff-find-text (car new))) + (maybe-old (diff-find-text (car old))) + (maybe-new (diff-find-text (car new))) + (pos (or (and maybe-new maybe-old (null reverse) (setq switched t) maybe-new) + maybe-old + (progn (setq switched t) maybe-new) (progn (setq switched nil) (condition-case nil (diff-find-approx-text (car old)) diff --git a/test/lisp/vc/diff-mode-tests.el b/test/lisp/vc/diff-mode-tests.el index bbd66824e48..5611e9abc79 100644 --- a/test/lisp/vc/diff-mode-tests.el +++ b/test/lisp/vc/diff-mode-tests.el @@ -597,5 +597,101 @@ baz")))) (should (eq (get-text-property (match-beginning 0) 'face) 'diff-context))))) +(ert-deftest diff-mode-test-topmost-addition-undo () + (let ((patch "diff --git a/fruits b/fruits +index 0dcecd3..d0eb2e7 100644 +--- a/fruits ++++ b/fruits +@@ -1,2 +1,3 @@ ++fruits + apple + orange +") + (text-before "apple +orange +") + (text-after "fruits +apple +orange +")) + (ert-with-temp-directory temp-dir + (let ((buf-after + (find-file-noselect (format "%s/%s" temp-dir "fruits")))) + (cd temp-dir) + + (with-current-buffer buf-after (insert text-after) (save-buffer)) + (with-temp-buffer + (insert patch) + (goto-char (point-min)) + (diff-hunk-next) + ;; Undo hunk by non-nil REVERSE argument (C-u C-c C-a) + (diff-apply-hunk t)) + (with-current-buffer buf-after + (should (string-equal (buffer-string) text-before))) + + (with-current-buffer buf-after + (erase-buffer) (insert text-after) (save-buffer)) + (with-temp-buffer + (insert patch) + (goto-char (point-min)) + (diff-hunk-next) + ;; Undo hunk by dwim behaviour + (cl-letf (((symbol-function 'y-or-n-p) #'always)) + (diff-apply-hunk))) + (with-current-buffer buf-after + (should (string-equal (buffer-string) text-before))) + + (with-current-buffer buf-after + (set-buffer-modified-p nil) + (kill-buffer buf-after)))))) + +(ert-deftest diff-mode-test-bottommost-addition-undo () + (let ((patch "diff --git a/fruits b/fruits +index 0dcecd3..6f210ff 100644 +--- a/fruits ++++ b/fruits +@@ -1,2 +1,3 @@ + apple + orange ++plum +") + (text-before "apple +orange +") + (text-after "apple +orange +plum +")) + (ert-with-temp-directory temp-dir + (let ((buf-after + (find-file-noselect (format "%s/%s" temp-dir "fruits")))) + (cd temp-dir) + + (with-current-buffer buf-after (insert text-after) (save-buffer)) + (with-temp-buffer + (insert patch) + (goto-char (point-min)) + (diff-hunk-next) + ;; Undo hunk by non-nil REVERSE argument (C-u C-c C-a) + (diff-apply-hunk t)) + (with-current-buffer buf-after + (should (string-equal (buffer-string) text-before))) + + (with-current-buffer buf-after + (erase-buffer) (insert text-after) (save-buffer)) + (with-temp-buffer + (insert patch) + (goto-char (point-min)) + (diff-hunk-next) + ;; Undo hunk by dwim behaviour + (cl-letf (((symbol-function 'y-or-n-p) #'always)) + (diff-apply-hunk))) + (with-current-buffer buf-after + (should (string-equal (buffer-string) text-before))) + + (with-current-buffer buf-after + (set-buffer-modified-p nil) + (kill-buffer buf-after)))))) + (provide 'diff-mode-tests) ;;; diff-mode-tests.el ends here commit af5a75a0bdd891686c67648bc0fa09ffb7e3ed0d Author: john muhl Date: Fri Mar 14 10:52:44 2025 -0500 ; Fix 'diff-mode' tests (Bug#77014) * test/lisp/vc/diff-mode-resources/git.patch: Restore missing space. diff --git a/test/lisp/vc/diff-mode-resources/git.patch b/test/lisp/vc/diff-mode-resources/git.patch index fdb586d37d3..05ec90d105c 100644 --- a/test/lisp/vc/diff-mode-resources/git.patch +++ b/test/lisp/vc/diff-mode-resources/git.patch @@ -47,5 +47,5 @@ index 9f6c5fe43e47eab441232e54456c5c2b06297b65..7b3f91a8b4ed923c8f43183276e3ab36 if __name__ == "__main__": main() --- +-- 2.40.0 commit bad2cd198abbd150cc205aee7879ad718e724dad Author: Stefan Kangas Date: Fri Mar 14 19:50:22 2025 +0100 Make turn-on-flyspell/turn-off-flyspell obsolete * lisp/textmodes/flyspell.el (turn-on-flyspell): Make into obsolete function alias for 'flyspell-mode'. (turn-off-flyspell): Declare obsolete. (text-mode-hook): Don't redundantly set :options here. * lisp/textmodes/text-mode.el (text-mode-hook): Prefer 'flyspell-mode' to 'turn-on-flyspell' in :options. (Bug#76535) diff --git a/etc/NEWS b/etc/NEWS index e634de18c69..b33f7cec73f 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1065,6 +1065,12 @@ allows idle timers and other code to run during this delay period. We consider making this behavior the default in a future Emacs version, so we invite Flyspell users to enable this new option and report issues. +--- +*** 'turn-on-flyspell' and 'turn-off-flyspell' are obsolete. +To unconditionally enable 'flyspell-mode' from a hook, use this instead: + + (add-hook 'text-mode-hook #'flyspell-mode) + ** Tramp +++ diff --git a/lisp/textmodes/flyspell.el b/lisp/textmodes/flyspell.el index b25f39c440b..d62c3fd135d 100644 --- a/lisp/textmodes/flyspell.el +++ b/lisp/textmodes/flyspell.el @@ -530,18 +530,6 @@ in your init file. (flyspell-mode -1))) (flyspell--mode-off))) -;;;###autoload -(defun turn-on-flyspell () - "Unconditionally turn on Flyspell mode." - (flyspell-mode 1)) - -;;;###autoload -(defun turn-off-flyspell () - "Unconditionally turn off Flyspell mode." - (flyspell-mode -1)) - -(custom-add-option 'text-mode-hook 'turn-on-flyspell) - (defvar flyspell-buffers nil "For remembering buffers running flyspell.") (make-obsolete-variable 'flyspell-buffers "not used." "28.1") @@ -2408,6 +2396,15 @@ This function is meant to be added to `flyspell-incorrect-hook'." (define-obsolete-function-alias 'flyspell-mode-on 'flyspell--mode-on "30.1") (define-obsolete-function-alias 'flyspell-mode-off 'flyspell--mode-off "30.1") +;;;###autoload +(define-obsolete-function-alias 'turn-on-flyspell #'flyspell-mode "31.1") + +;;;###autoload +(defun turn-off-flyspell () + "Unconditionally turn off Flyspell mode." + (declare (obsolete flyspell-mode "31.1")) + (flyspell-mode -1)) + (provide 'flyspell) ;;; flyspell.el ends here diff --git a/lisp/textmodes/text-mode.el b/lisp/textmodes/text-mode.el index cfacb144e40..3b27efbcf87 100644 --- a/lisp/textmodes/text-mode.el +++ b/lisp/textmodes/text-mode.el @@ -34,7 +34,8 @@ (defcustom text-mode-hook '(text-mode-hook-identify) "Normal hook run when entering Text mode and many related modes." :type 'hook - :options '(turn-on-auto-fill turn-on-flyspell) + :options '(turn-on-auto-fill flyspell-mode) + :version "31.1" :group 'text) (defvar text-mode-variant nil commit 24ffcbb3da9a010cf564bb496af3f5ce0b805f17 Author: Michael Albinus Date: Fri Mar 14 16:31:51 2025 +0100 Improve tramp-*-with-sudo commands * doc/emacs/dired.texi (Dired Visiting): Add tramp-dired-find-file-with-sudo. * doc/emacs/files.texi (Reverting): Add tramp-revert-buffer-with-sudo. * doc/misc/tramp.texi (Ad-hoc multi-hops): Extend wrt `tramp-*-with-sudo' commands. * etc/NEWS: Add tramp-dired-find-file-with-sudo. Fix typos. * lisp/bindings.el (ctl-x-x-map): * lisp/dired.el (dired-mode-map): Add "@" binding. (Bug#76974) * lisp/net/tramp-cmds.el (dired-get-file-for-visit): Declare. (with-tramp-file-name-with-method): New macro. (tramp-revert-buffer-with-sudo): Autoload. Preserve position. Use `with-tramp-file-name-with-method'. (tramp-dired-find-file-with-sudo): New command. diff --git a/doc/emacs/dired.texi b/doc/emacs/dired.texi index 07142e71713..f52b001c121 100644 --- a/doc/emacs/dired.texi +++ b/doc/emacs/dired.texi @@ -460,6 +460,14 @@ View the file described on the current line, with View mode (@code{dired-view-file}). View mode provides convenient commands to navigate the buffer but forbids changing it; @xref{View Mode}. +@item @@ +@kindex @@ @r{(Dired)} +@findex tramp-dired-find-file-with-sudo +Open the file described on the current line, with root permissions +(@code{tramp-dired-find-file-with-sudo}). Calling it with the @kbd{C-u} +prefix argument asks for another Tramp method interactively but +@option{sudo}. @xref{Ad-hoc multi-hops, Tramp,, tramp, The Tramp Manual}. + @item ^ @kindex ^ @r{(Dired)} @findex dired-up-directory diff --git a/doc/emacs/files.texi b/doc/emacs/files.texi index d11d7767353..9b2ce5c5ee2 100644 --- a/doc/emacs/files.texi +++ b/doc/emacs/files.texi @@ -1174,6 +1174,15 @@ the major mode actually turned on as result of reverting a buffer depends on mode remapping, and could be different from the original mode if you customized @code{major-mode-remap-alist} in-between. +@cindex reverting with root permissions +@findex tramp-revert-buffer-with-sudo +@kindex C-x x @@ +A variant of reverting a buffer is visiting it by the +@code{tramp-revert-buffer-with-sudo} (@kbd{C-x x @@}) command. It +reopens the file or Dired buffer with root permissions. With a prefix +argument of @kbd{C-u}, you could change the default Tramp method +(@option{sudo}). @xref{Ad-hoc multi-hops, Tramp,, tramp, The Tramp Manual}. + @node Auto Revert @section Auto Revert: Keeping buffers automatically up-to-date @cindex Global Auto Revert mode diff --git a/doc/misc/tramp.texi b/doc/misc/tramp.texi index 81feb56ec31..6e66de552de 100644 --- a/doc/misc/tramp.texi +++ b/doc/misc/tramp.texi @@ -3928,23 +3928,31 @@ containers on the remote host. A common use case for ad-hoc specifications is to visit a file or a directory with proper permissions, for example with the @option{sudo} -method. The command @code{tramp-revert-buffer-with-sudo} supports -this. +method. The commands @code{tramp-revert-buffer-with-sudo} (@kbd{C-x x +@@}), and @code{tramp-dired-find-file-with-sudo} (@kbd{@@} in +@code{dired-mode}) support this. +@kindex C-x x @@ @deffn Command tramp-revert-buffer-with-sudo This command shows the current buffer with @option{sudo} permissions. The buffer must either visit a file, or a directory (@code{dired-mode}). @end deffn +@kindex @@ @r{(in dired}) +@deffn Command tramp-dired-find-file-with-sudo +In @code{dired-mode}, visit the file or directory named on this line. +This is performed with @option{sudo} permissions. +@end deffn + @defopt tramp-file-name-with-method -The method @code{tramp-revert-buffer-with-sudo} shows an alternate -buffer. It defaults to @option{sudo}, other valid methods are -@option{su}, @option{doas}, @option{run0}, and @option{ksu}. +The method used in @code{tramp-revert-buffer-with-sudo} and +@code{tramp-dired-find-file-with-sudo}. It defaults to @option{sudo}, +other valid methods are @option{su}, @option{doas}, @option{run0}, and +@option{ksu}. -@lisp -(customize-set-variable 'tramp-file-name-with-method "doas") -@end lisp +If a command is called with a prefix argument @kbd{C-u}, the option's +value is read interactively. @end defopt These methods apply the user @samp{root} as default. If another user diff --git a/etc/NEWS b/etc/NEWS index 5ebc109f734..e634de18c69 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -285,7 +285,6 @@ return value windows whose buffers share their text with BUFFER-OR-NAME. With such an entry, 'display-buffer-reuse-window' may also choose a window whose buffer shares text with the buffer to display. - ** Frames +++ @@ -294,7 +293,7 @@ Calling this function before 'delete-frame' is useful to avoid that the latter throws an error when the argument FRAME cannot be deleted. +++ -*** New value 'force' for option `frame-inhibit-implied-resize'. +*** New value 'force' for user option 'frame-inhibit-implied-resize'. This will inhibit implied resizing while a new frame is made and can be useful on tiling window managers where the initial frame size should be specified by external means. @@ -363,8 +362,6 @@ invoked standalone or from the 'project-switch-commands' dispatch menu. This user option describes projects that should always be skipped by 'project-remember-project'. -*** - ** Registers *** New functions 'buffer-to-register' and 'file-to-register'. @@ -396,14 +393,14 @@ please contact us if you still need it for some reason. --- ** Modified settings for an enabled theme now apply immediately. -Evaluating a custom-theme-set-faces or custom-theme-set-variables +Evaluating a 'custom-theme-set-faces' or 'custom-theme-set-variables' call for an enabled theme causes the settings to apply immediately, without a need to re-load the theme. --- ** 'describe-variable' now automatically says if 'setopt' is needed. -If a user option has a defcustom :set function, users will normally need -to set it with 'setopt' for it to take an effect. If the docstring +If a user option has a defcustom ':set' function, users will normally +need to set it with 'setopt' for it to take an effect. If the docstring doesn't already mention 'setopt', the 'describe-variable' command will now add a note about this automatically. @@ -545,6 +542,7 @@ default is nil, which retains the old format. *** The terminal emulator now supports auto-margins control. Term mode now handles DECAWM escape sequences that control whether text automatically wraps at the right margin: + - \e[?7h enables auto-margins (default) - \e[?7l disables auto-margins @@ -603,12 +601,13 @@ Emacs 25.1), and gnudoit (obsolete since Emacs 25.1). *** Some cl-lib functions and macros are now built-in. These functions or macros have been added to Emacs Lisp, and the old names are now aliases for the built-in equivalents: - - 'cl-incf' renamed to 'incf' - - 'cl-decf' renamed to 'decf' - - 'cl-oddp' renamed to 'oddp' - - 'cl-evenp' renamed to 'evenp' - - 'cl-plusp' renamed to 'plusp' - - 'cl-minusp' renamed to 'minusp' + +- 'cl-incf' renamed to 'incf' +- 'cl-decf' renamed to 'decf' +- 'cl-oddp' renamed to 'oddp' +- 'cl-evenp' renamed to 'evenp' +- 'cl-plusp' renamed to 'plusp' +- 'cl-minusp' renamed to 'minusp' The old names are considered deprecated, and will be marked as obsolete in some future release. @@ -687,6 +686,7 @@ minibuffer use, they are now saved only once in the file specified by 'savehist-file'. Previously, they were saved twice. ** Rectangle Mark + --- *** New user option to control whether empty rectangle selections are shown. The new user option 'rectangle-indicate-zero-width-rectangle' can be @@ -740,9 +740,8 @@ It removes all the buttons in the specified region. ** Shell ---- ++++ *** Shell buffers now support bookmarks. - You can now bookmark local and remote shell buffers using the bookmark menu 'bookmark-bmenu-list', or by using the command 'bookmark-set'. Shell bookmarks can be loaded via the menu and by using the command @@ -1021,7 +1020,7 @@ positives. --- *** IELM input history is now saved also when the IELM process is killed. -When you kill the IELM process with "C-c C-c", the input history is now +When you kill the IELM process with 'C-c C-c', the input history is now saved to the file specified by 'ielm-history-file-name', just like when you exit the Emacs session or kill the IELM buffer. @@ -1076,6 +1075,16 @@ connections after you close remote-file buffers without having to either cherry pick via 'tramp-cleanup-connection' or clear them all via 'tramp-cleanup-all-connections'. ++++ +*** New command 'tramp-dired-find-file-with-sudo'. +This command, bound to '@' in Dired, visits the file or directory on the +recent Dired line with root permissions. + ++++ +*** Command 'tramp-revert-buffer-with-sudo' is bound to 'C-x x @' now. +Called with the prefix argument 'C-u', the used Tramp method is asked +interactively. This happens also for 'tramp-dired-find-file-with-sudo'. + +++ *** Connection method "kubernetes" supports now optional namespace. The host name for Kubernetes connections can be of kind @@ -1371,19 +1380,19 @@ the directory into which the repository was cloned. *** 'C-x v u' ('vc-revert') now works on directories listed in VC Directory. Reverting a directory means reverting changes to all files inside it. -*** New function 'log-edit-done-strip-cvs-lines'. -This function strips all lines beginning with "CVS:" from the buffer. +*** New command 'log-edit-done-strip-cvs-lines'. +This command strips all lines beginning with "CVS:" from the buffer. It is intended to be added to the 'log-edit-done-hook' so that -'vc-cvs-checkin' behaves like invoking "cvs commit [files...]" from the +'vc-cvs-checkin' behaves like invoking 'cvs commit [files...]' from the command line. ** Diff mode +++ -*** diff-apply-buffer now considers the region and can reverse-apply. +*** 'diff-apply-buffer' now considers the region and can reverse-apply. If the region is active, this command now applies all hunks that the region overlaps; otherwise, it applies all hunks. -With a prefix arguments, it now reverse-applies the hunks. +With a prefix argument, it now reverse-applies the hunks. This matches the existing prefix argument to 'diff-apply-hunk'. ** Package @@ -1448,13 +1457,13 @@ runs its body, and removes the current buffer from ** Strokes -- -** 'strokes-mode' no longer demands the presence of a mouse. +*** 'strokes-mode' no longer demands the presence of a mouse. 'strokes-mode' now permits itself to be enabled if no mouse is connected, to facilitate enabling 'strokes-mode' in sessions where the availability of a mouse device varies during execution (as is frequently observed on Android). -** Yank media +** Yank Media +++ *** 'yank-media' now auto-selects the most preferred MIME type. @@ -1472,7 +1481,7 @@ the 'remember-buffer'. This allows users to customize the major mode used to write notes. --- -*** New handler that append remember data in directory. +*** New handler that appends remember data in directory. The 'remember-append-in-data-directory' handler appends remember data in a file, that file being choosen by the user through the minibuffer. @@ -1593,28 +1602,28 @@ restore the old behavior, you can set 'eshell-pwd-convert-function' to 'identity'. --- -** The rx 'eval' form now uses the current elisp dialect for evaluation. +** The rx 'eval' form now uses the current Elisp dialect for evaluation. Previously, its argument was always evaluated using dynamic binding. * Lisp Changes in Emacs 31.1 +++ -** New macros 'static-when' and 'static-unless' implement conditional -compilation like 'static-if'. -These macros evaluate their condition at macro-expansion time and are useful -for writing code that can work across different Emacs versions. +** New macros 'static-when' and 'static-unless'. +Like 'static-if', these macros evaluate their condition at +macro-expansion time and are useful for writing code that can work +across different Emacs versions. --- ** You can change the default value of 'lexical-binding'. -While the default is still the use dynamic binding dialect of Elisp +While the default is still the use of dynamic binding dialect of Elisp in those places that don't explicitly set 'lexical-binding' you can change it globally with: (set-default-toplevel-value 'lexical-binding t) +++ -*** New macros 'incf' and 'decf'. +** New macros 'incf' and 'decf'. They increment or decrement the value stored in a variable (a symbol), or in a generalized variable. @@ -1638,11 +1647,12 @@ This means that you can now call it with just one argument, like *** Some experimental ERT macros are now considered stable. The following macros, previously only available in the experimental 'ert-x' module, are now considered stable and have been moved to 'ert': + - ert-with-test-buffer - ert-with-buffer-selected - ert-with-buffer-renamed -See the ERT manual for more information. +See "(ert) Helper Functions" node in the ERT manual for more information. ** Time & Date @@ -1852,7 +1862,7 @@ maps will not activate the repeat map in 'repeat-mode'. It will only continue the already activated repeating sequence. Also 'defvar-keymap' supports a new keyword ':continue' with a list of commands that only continue the active repeating sequence, and the 'use-package' and -'bind-keys' macros supports a similar keyword ':continue-only'. +'bind-keys' macros support a similar keyword ':continue-only'. ** New function 'completion-table-with-metadata'. It offers a more concise way to create a completion table with metadata. diff --git a/lisp/bindings.el b/lisp/bindings.el index f829c1bea26..a5737db840b 100644 --- a/lisp/bindings.el +++ b/lisp/bindings.el @@ -1606,7 +1606,8 @@ if `inhibit-field-text-motion' is non-nil." "u" #'rename-uniquely "n" #'clone-buffer "i" #'insert-buffer - "t" #'toggle-truncate-lines) + "t" #'toggle-truncate-lines + "@" #'tramp-revert-buffer-with-sudo) (define-key ctl-x-map "x" ctl-x-x-map) (define-key esc-map "\C-l" 'reposition-window) diff --git a/lisp/dired.el b/lisp/dired.el index 68e1da13171..b5bb658b503 100644 --- a/lisp/dired.el +++ b/lisp/dired.el @@ -2416,6 +2416,7 @@ Do so according to the former subdir alist OLD-SUBDIR-ALIST." "x" #'dired-do-flagged-delete "y" #'dired-show-file-type "+" #'dired-create-directory + "@" #'tramp-dired-find-file-with-sudo ;; moving "<" #'dired-prev-dirline ">" #'dired-next-dirline diff --git a/lisp/net/tramp-cmds.el b/lisp/net/tramp-cmds.el index cba300049ae..be122642b96 100644 --- a/lisp/net/tramp-cmds.el +++ b/lisp/net/tramp-cmds.el @@ -32,6 +32,7 @@ ;; Pacify byte-compiler. (declare-function dired-advertise "dired") +(declare-function dired-get-file-for-visit "dired") (declare-function dired-unadvertise "dired") (declare-function mml-mode "mml") (declare-function mml-insert-empty-tag "mml") @@ -637,6 +638,19 @@ For details, see `tramp-rename-files'." (const "ksu")) :link '(tramp-info-link :tag "Tramp manual" tramp-file-name-with-method)) +(defmacro with-tramp-file-name-with-method (&rest body) + "Ask user for `tramp-file-name-with-method' if needed. +Run BODY." + (declare (indent 0) (debug t)) + `(let ((tramp-file-name-with-method + (if current-prefix-arg + (completing-read + "Tramp method: " + (mapcar #'cadr (cdr (get 'tramp-file-name-with-method 'custom-type))) + nil t tramp-file-name-with-method) + tramp-file-name-with-method))) + ,@body)) + (defun tramp-file-name-with-sudo (filename) "Convert FILENAME into a multi-hop file name with \"sudo\". An alternative method could be chosen with `tramp-file-name-with-method'." @@ -669,27 +683,38 @@ An alternative method could be chosen with `tramp-file-name-with-method'." (make-tramp-file-name :method tramp-file-name-with-method :localname filename)))) -;;;###tramp-autoload +;;;###autoload (defun tramp-revert-buffer-with-sudo () "Revert current buffer to visit with \"sudo\" permissions. An alternative method could be chosen with `tramp-file-name-with-method'. If the buffer visits a file, the file is replaced. If the buffer runs `dired', the buffer is reverted." (interactive) - (cond - ((buffer-file-name) - (find-alternate-file (tramp-file-name-with-sudo (buffer-file-name)))) - ((tramp-dired-buffer-p) - (dired-unadvertise (expand-file-name default-directory)) - (setq default-directory (tramp-file-name-with-sudo default-directory) - list-buffers-directory - (tramp-file-name-with-sudo list-buffers-directory)) - (if (consp dired-directory) - (setcar - dired-directory (tramp-file-name-with-sudo (car dired-directory))) - (setq dired-directory (tramp-file-name-with-sudo dired-directory))) - (dired-advertise) - (revert-buffer)))) + (with-tramp-file-name-with-method + (cond + ((buffer-file-name) + (let ((pos (point))) + (find-alternate-file (tramp-file-name-with-sudo (buffer-file-name))) + (goto-char pos))) + ((tramp-dired-buffer-p) + (dired-unadvertise (expand-file-name default-directory)) + (setq default-directory (tramp-file-name-with-sudo default-directory) + list-buffers-directory + (tramp-file-name-with-sudo list-buffers-directory)) + (if (consp dired-directory) + (setcar + dired-directory (tramp-file-name-with-sudo (car dired-directory))) + (setq dired-directory (tramp-file-name-with-sudo dired-directory))) + (dired-advertise) + (revert-buffer))))) + +;;;###autoload +(defun tramp-dired-find-file-with-sudo () + "In Dired, visit the file or directory named on this line. +This is performed with \"sudo\" permissions." + (interactive) + (with-tramp-file-name-with-method + (find-file (tramp-file-name-with-sudo (dired-get-file-for-visit))))) ;;; Recompile on ELPA commit 21371aa106e6924377e916237d8418bfff2a754c Author: Eli Zaretskii Date: Fri Mar 14 15:22:24 2025 +0200 ; * etc/NEWS: Announce the larger number of sub-processes on w32. diff --git a/etc/NEWS b/etc/NEWS index 7467af6055c..5ebc109f734 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1909,6 +1909,9 @@ allowing Emacs users access to speech recognition utilities. Note: Accepting this permission allows the use of system APIs, which may send user data to Apple’s speech recognition servers. ++++ +** On Mac OS X, stipples now render with color. + --- ** Emacs on MS-Windows now supports GUI dialogs and message boxes better. In particular, it is now possible to show text with embedded newlines in @@ -1939,6 +1942,12 @@ current buffer, if the major mode supports it. (Support for 'yank-media' will be unavailable on MS-Windows if Emacs was configured '--without-native-image-api'.) +--- +** Emacs on MS-Windows now supports up to 1024 sub-processes. +Changes in implementation of monitoring sub-processes allow Emacs on +MS-Windows to start up to 1024 sub-processes, similar to GNU/Linux and +other free systems. + --- ** Images on MS-Windows now support the ':transform-smoothing' flag. Transformed images are smoothed using the bilinear interpolation by @@ -1955,9 +1964,6 @@ Accordingly, we have revised our recommendations for a suitable DJGPP toolchain to GCC 14.2.0 and Binutils 2.35.1 in lieu of GCC 3.4.x and Binutils 2.26. -+++ -** On Mac OS X, stipples now render with color. - ---------------------------------------------------------------------- This file is part of GNU Emacs. commit c87f0e8a199826f1d05d5c1ff9722577b5fc70de Author: Eli Zaretskii Date: Fri Mar 14 15:05:29 2025 +0200 ; * src/w32.h (free_wait_pool): Move prototype from w32.c. diff --git a/src/w32.c b/src/w32.c index dde82d1896d..567a1894df8 100644 --- a/src/w32.c +++ b/src/w32.c @@ -10453,8 +10453,6 @@ check_windows_init_file (void) /* from w32fns.c */ extern void remove_w32_kbdhook (void); -/* from w32proc.c */ -extern void free_wait_pool (void); void term_ntproc (int ignored) { diff --git a/src/w32.h b/src/w32.h index f876967d21c..ae3999ffcfd 100644 --- a/src/w32.h +++ b/src/w32.h @@ -254,6 +254,9 @@ extern Lisp_Object w32_read_registry (HKEY, Lisp_Object, Lisp_Object); /* Used instead of execvp to restart Emacs. */ extern int w32_reexec_emacs (char *, const char *); +/* From w32proc.c. */ +extern void free_wait_pool (void); + #ifdef HAVE_GNUTLS #include commit e02466a579a58fceda33ad51d822e39543bc883c Author: Yue Yi Date: Tue Mar 11 00:30:40 2025 +0800 Increase FD_SETSIZE on Windows to support more subprocesses Earlier versions of Emacs were limited to at most 32 subprocesses or network connections on Windows due to the 64-object limit imposed by WaitForMultipleObjects. To overcome this, a simple waiting thread pool is implemented, allowing Emacs to efficiently wait on up to 2048 objects. Each thread in the pool can wait on up to 63 objects, and a total of 32 threads are used, together with the main thread, to expand the waiting capability. This enables Emacs to support approximately 1024 subprocesses, which is comparable to the 'pty' method on GNU/Linux when using the default FD_SETSIZE of 1024. To minimize overhead, the threads remain active instead of being frequently created and destroyed, reducing unnecessary system resource consumption. Idle threads can be terminated after a period of inactivity to free up memory. * src/w32.h (FD_SETSIZE): Change from 64 to 2048. * src/w32.c (term_ntproc): Call 'free_wait_pool' to free waiting threads. * src/w32proc.c (WFO_ABANDONED, WFO_TIMEOUT, WFO_FAILED) (WFO_MAX_WAIT): New macros. (wait_objects_context, wait_objects_pool, wait_objects_info): New structures for managing the thread pool. (wait_objects_pool, wait_objects_info): New static variables for managing the thread pool. (wait_objects_thread, start_wait_objects, stop_wait_objects) (end_wait_and_return, shrink_wait_pool, free_wait_pool): New functions for waiting and managing the thread pool. (wait_for_objects, msg_wait_for_objects): New functions as replacements for WaitForMultipleObjects and MsgWaitForMultipleObjects. (wait_pid): Use 'wait_for_objects' and new macros. (sys_select): Make some variables static to avoid stack allocation. Use 'wait_for_objects', 'msg_wait_for_objects', and the new macros. diff --git a/src/w32.c b/src/w32.c index 85916ad74a9..dde82d1896d 100644 --- a/src/w32.c +++ b/src/w32.c @@ -10451,9 +10451,10 @@ check_windows_init_file (void) } } -/* from w32fns.c */ +/* from w32fns.c */ extern void remove_w32_kbdhook (void); - +/* from w32proc.c */ +extern void free_wait_pool (void); void term_ntproc (int ignored) { @@ -10463,11 +10464,12 @@ term_ntproc (int ignored) term_timers (); - /* shutdown the socket interface if necessary */ + /* shutdown the socket interface if necessary. */ term_winsock (); term_w32select (); - + /* exit all worker threads of sys_select if necessary. */ + free_wait_pool (); #if HAVE_NATIVE_IMAGE_API w32_gdiplus_shutdown (); #endif diff --git a/src/w32.h b/src/w32.h index 84059278a2a..f876967d21c 100644 --- a/src/w32.h +++ b/src/w32.h @@ -29,7 +29,11 @@ along with GNU Emacs. If not, see . */ /* File descriptor set emulation. */ /* MSVC runtime library has limit of 64 descriptors by default */ -#define FD_SETSIZE 64 +#ifdef FD_SETSIZE +# undef FD_SETSIZE +#endif + +#define FD_SETSIZE 2048 typedef struct { unsigned int bits[FD_SETSIZE / 32]; } fd_set; diff --git a/src/w32proc.c b/src/w32proc.c index 000eb9bee3f..d88ca1c30ce 100644 --- a/src/w32proc.c +++ b/src/w32proc.c @@ -881,7 +881,785 @@ alarm (int seconds) } +/* Here's an overview of how support for waiting more than 64 objects + on MS-Windows. + + As noted in the MS documentation, WaitForMultipleObjects can wait on + a maximum of MAXIMUM_WAIT_OBJECTS (64) objects. Due to this + limitation, earlier versions of Emacs (at least 30) could only + support up to 32 subprocesses or network connections. + + The documentation suggests using multiple threads or a thread pool to + wait on a larger number of objects. With Windows 2000, Microsoft has + added new thread pooling functions to make thread creation, + destruction, and general management easier: + + . https://jacobfilipp.com/MSJ/pooling.html + + Thread pools implement waiting by calling WaitForMultipleObjects or + relying on completion ports (Windows 8 or above) in their internal + threads, as The Old New Thing said: + + . https://devblogs.microsoft.com/oldnewthing/20081117-00/?p=20183 + . https://devblogs.microsoft.com/oldnewthing/20220406-00/?p=106434 + + However, since the system thread pool in versions prior to Windows 7 + does not support adjusting thread stack sizes by calling + SetThreadpoolStackInformation, using a large number of threads may + consume a lot of address space. This can have a significant impact on + the limited 2GB address space available on 32-bit systems. To avoid + this issue on Windows Vista and earlier systems and make the code as + generic as possible, a simple waiting thread pool is manually + implemented here. + + The waiting thread pool contains a maximum of 32 threads, with each + thread capable of waiting on up to 63 objects (one object is reserved + for communication with the main thread). Combined with the main + thread's WaitForMultipleObjects call, this implementation supports + waiting on a maximum of 2048 objects. Since Windows only supports + creating subprocesses using 'pipe method, this allows Emacs to create + a maximum of approximately 1024 subprocesses. This limit is close to + the number of subprocesses that can be created using the 'pty method + on Linux when the default FD_SETSIZE is 1024. + + Once created, the threads do not exit after finish waiting; instead, + they enter an infinite loop, waiting for an event to trigger and + begin their WaitForMultipleObjects tasks, thereby eliminating the + additional time and power consumption associated with frequently + creating and destroying threads. If a thread is not triggered after a + certain number of sys_select calls, it can be terminated to free up + resources. */ + +/* The values returned by WaitForMultipleObjects, WAIT_ABANDONED_0 + (0x80) and WAIT_TIMEOUT (0x102), are less than 2048 (0x800), so + define new constants to replace them. 'WFO' prefix stands for + 'WaitForObjects'. */ +#define WFO_ABANDONED 0x10000 +#define WFO_TIMEOUT 0x20002 +#define WFO_FAILED 0xfffff +#define WFO_MAX_WAIT FD_SETSIZE + +/* When debugging, use SetThreadDescription to provide additional + debugging information. They are already defined in w32fns.c. */ +typedef BOOL (WINAPI *IsDebuggerPresent_Proc) (void); +typedef HRESULT (WINAPI *SetThreadDescription_Proc) + (HANDLE hThread, PCWSTR lpThreadDescription); +extern IsDebuggerPresent_Proc is_debugger_present; +extern SetThreadDescription_Proc set_thread_description; + +/* Structure for waiting thread's context. */ +typedef struct +{ + /* Handle to the thread itself. */ + HANDLE thread; + /* Handle to an event object that is signaled by the main thread + to tell thread start waiting. */ + HANDLE wait_start; + /* Handle to an event object that is signaled when this thread is not + used for a period of time, it tells thread to quit. */ + HANDLE wait_quit; + /* Handle to an event object that is signaled when wokrer thread is + ready to call WaitForMultipleObjects on the given objects. */ + HANDLE wait_ready; + /* pHandles and nCount are part of the WaitForMultipleObjects + parameters, specifying the array of objects to wait on and the + number of objects. bWaitAll and dwMilliseconds are not needed. */ + HANDLE *pHandles; + int nCount; + /* The return value of the thread's call to WaitForMultipleObjects. */ + DWORD result; + /* Used to store GetLastError value of failed wait. */ + DWORD errcode; + /* The thread's wait count. */ + int call_count; +} wait_objects_context; + +/* Structure for the waiting thread pool */ +typedef struct +{ + /* An array for the context of each worker thread. */ + wait_objects_context wocs[32]; + /* The number of the current threads for waiting. */ + int count; + /* times of waiting in main thread. */ + int main_thread_waits; + /* The event handle that signals the worker thread's timeout. */ + HANDLE timeout_event; + /* The flag that indicates whether the thread pool is initialized. */ + int init_flag; +} wait_objects_pool; + +/* Structure for information about the grouping of waits. */ +typedef struct +{ + /* An array of handles used as parameters for WaitForMultipleObjects + in the main thread, which includes event handles wait_ready + signaled by the worker threads, as well as any possible remaining + wait objects. */ + HANDLE hs[64]; + /* The number of threads to be used. */ + int nt; + /* The number of handles the main thread needs to wait for. */ + int nh; +} wait_objects_info; + +/* Declare wait_pool and wait_info as static variables to avoid + unnecessary stack allocation. */ +static wait_objects_pool wait_pool; +static wait_objects_info wait_info; + +/* Thread proc for worker threads waiting on objects. Each thread is + normally blocked until woken by the main thread to wait on objects. + When the wait completes, wait_ready is signaled to wake up the main + thread and the thread blocks itself again. + + The worker thread needs to wait for the timeout event from the main + thread. When timeout_event is signaled by the main thread, it will + end the current wait and start the next round. Similarly, threads + also need to wait for an quit event, which usually occurs when the + thread has not been used for a certain period of time. */ +static DWORD WINAPI +wait_objects_thread (void *arg) +{ + wait_objects_context *ctx = NULL; + /* Thread's wait handle array. */ + HANDLE hs[64] = {NULL}; + /* start and quit event handle array. */ + HANDLE start_or_quit[2] = {NULL}; + /* return value of waiting. */ + DWORD res = 0; + /* Thread's context. */ + ctx = (wait_objects_context *) arg; + /* Place start event in start_or_quit[0]. */ + start_or_quit[0] = ctx->wait_start; + /* Place quit event in start_or_quit[1]. */ + start_or_quit[1] = ctx->wait_quit; + for (;;) + { + /* increase thread's wait call count by 1. */ + ctx->call_count++; + /* The timeout event object is placed at the end. + nCount will not exceed 63. */ + hs[ctx->nCount] = wait_pool.timeout_event; + /* The contents copied by different threads do not overlap, so it + is safe to use memcpy. */ + memcpy (hs, ctx->pHandles, ctx->nCount * sizeof (HANDLE)); + /* Start waiting. */ + ctx->result = WaitForMultipleObjects (ctx->nCount + 1, hs, + FALSE, INFINITE); + /* Get the error code when the wait fails. */ + if (ctx->result == WAIT_FAILED) + { + ctx->errcode = GetLastError (); + SetEvent (ctx->wait_ready); + return WFO_FAILED; + } + /* After waiting, signal wait_ready and wait for the wait_start + and wait_quit events from the main thread. */ + if (!SetEvent (ctx->wait_ready)) + { + ctx->errcode = GetLastError (); + return WFO_FAILED; + } + res = WaitForMultipleObjects (2, start_or_quit, FALSE, INFINITE); + switch (res) + { + /* wait_start, continue loop. */ + case WAIT_OBJECT_0: + break; + /* wait_quit, exit with code 0. */ + case WAIT_OBJECT_0 + 1: + /* Close its wait_quit event handle. */ + if (!CloseHandle (start_or_quit[1])) + return GetLastError (); + return 0; + /* Failure to wait for the start and quit event from the main + thread should not occur. */ + case WAIT_ABANDONED_0: + case WAIT_ABANDONED_0 + 1: + case WAIT_TIMEOUT: + case WAIT_FAILED: + ctx->errcode = GetLastError (); + return WFO_FAILED; + } + } + return 0; +} + +/* Determine the grouping based on the given wait objects and assign + them to the worker threads to begin waiting, creating new worker + threads if necessary. The function also performs initialization of + some static resources. */ +static int +start_wait_objects (wait_objects_info *p, + DWORD nCount, + HANDLE *lpHandles) +{ + /* Initialization of static resources. */ + if (!wait_pool.init_flag) + { + wait_pool.timeout_event = CreateEvent (NULL, TRUE, FALSE, NULL); + /* If resource initialization fails, exit immediately. */ + if (!wait_pool.timeout_event) + return WFO_FAILED; + /* Set init_flag. */ + wait_pool.init_flag = TRUE; + } + /* Check if all threads in the thread pool are working properly. If + any thread has exited, exit immediately. */ + for (int i = 0; i < wait_pool.count; i++) + { + /* The MS documentation suggests that callers should call the + GetExitCodeThread function only after the thread has been + confirmed to have exited. Use the WaitForSingleObject with a + wait duration of zero to determine whether a thread has + exited. */ + DWORD res = WaitForSingleObject (wait_pool.wocs[i].thread, 0); + /* Thread is alive. */ + if (res == WAIT_TIMEOUT) + continue; + /* The thread unexpectedly terminated when waiting + wait_start and wait_quit. */ + else if (res == WAIT_OBJECT_0) + { + /* The last-error code is kept in thread local storage so that + multiple threads do not overwrite each other's values. Pass + the error by using SetLastError to set the error value. */ + SetLastError (wait_pool.wocs[i].errcode); + return WFO_FAILED; + } + /* Unexpected return value from WaitForSingleObject. + WAIT_FAILED or WAIT_ABANDONED. */ + else + return WFO_FAILED; + } + /* Calculate the required number of threads and the number of objects + the main thread needs to wait for based on the number of objects to + be waited on. To avoid increasing the number of threads for a small + increase in the number of objects (e.g., using two threads for 65 + objects), the following calculation allows the main thread's array + to hold up to 32 wait objects, excluding the thread event + handles. With this approach, the minimum number of wait objects for + the worker threads is 32, and it allows a maximum of 63 * 32 + 32 = + 2048 objects to be waited on. + + The number of child process in Emacs is limited by MAX_CHILDREN, + which is half of MAXDESC (FD_SETSIZE). When FD_SETSIZE is set to + 2048, a maximum of 1024 child processes and network/serial are + allowed. Compared to network/serial, the process handles of child + processes are also waited on, which is why only a maximum of 1024 + child processes can be created. + + When there are 1024 child processes, the main thread needs to wait + for 64 objects, which seems to exceed the 63 allowed by + MsgWaitForMultipleObjects. However, due to the influence of + emacs_pipe, a maximum of only 1021 child processes can be created, + so the number of elements in the main thread's wait array will not + exceed 63. The explanation is as follows: + + . emacs_pipe calls the pipe2 function located in w32.c. + . pipe2 makes sure that file descriptors return by _pipe less than + MAXDESC (2048), Therefore, the range of available file descriptor + values is from 3 to 2047 (0, 1, 2 for stdin, stdout and + stderr). Since pipe file descriptors are opened in pairs, the + actual range is from 3 to 2046, meaning there are 2044 available + file descriptors. + . When create_process creates a process using the 'pipe' method, it + creates two pipes, one for reading and one for writing. It then + closes one file descriptor for each pipe, as each pipe is only + used for reading or writing. This results in 4 file descriptors + being used initially for each process creation, and then 2 are + released. When only 2 empty slots remain, no new child processes + can be created. + . In the end, we can use 2042 file descriptors, which allows for + 1021 child processes. */ + + /* Compared to using (nCount / 63), we use (nCount - 33) / 63, which + will cause a new thread to be added only when the number of wait + objects in the main thread's array exceeds 32. */ + p->nt = 1 + (nCount - 33) / 63; + /* The number of objects the main thread needs to wait for is the + required number of threads plus the remaining objects. If the + existing threads are sufficient to wait for all the objects, then + nh is the number of threads. */ + p->nh = (p->nt * 63 >= nCount) ? p->nt : (p->nt + nCount - p->nt * 63); + /* In the main thread's wait array hs, the first nt are thread event + handles, and the remaining are the remaining objects. */ + if (p->nh != p->nt) + memcpy (p->hs + p->nt, lpHandles + p->nt * 63, + sizeof (HANDLE) * (nCount - p->nt * 63)); + /* Set the pHandles and nCount parameters for each thread. Since the + waiting task is relatively simple, we do not need a dedicated + task queue mechanism. We can simply wait for the corresponding + objects in the order of the thread context array wait_pool.wocs. */ + for (int i = 0; i < p->nt; i++) + { + /* If it is the last thread and the thread is sufficient to wait + all the objects, then the count needs to be calculated; + otherwise, it will be 63. */ + int count = (i == p->nt - 1) + ? (p->nt == p->nh) ? (nCount - i * 63) : 63 : 63; + wait_pool.wocs[i].nCount = count; + wait_pool.wocs[i].pHandles = lpHandles + i * 63; + } + /* Get current threads count. */ + int orig_thread_count = wait_pool.count; + /* If the current thread pool is insufficient to wait for all the + objects, create new threads and initialize the event handles. */ + while (wait_pool.count < p->nt) + { + wait_objects_context *ctx = wait_pool.wocs + wait_pool.count; + /* Create wait_start, wait_quit and wait_ready events. */ + ctx->wait_start = CreateEvent (NULL, FALSE, FALSE, NULL); + if (!ctx->wait_start) + return WFO_FAILED; + ctx->wait_quit = CreateEvent (NULL, FALSE, FALSE, NULL); + if (!ctx->wait_quit) + return WFO_FAILED; + /* The wait_ready event is set to be manually reset because it may + be waited on multiple times. */ + ctx->wait_ready = CreateEvent (NULL, TRUE, FALSE, NULL); + if (!ctx->wait_ready) + return WFO_FAILED; + /* Set call_count and errcode to ZERO. */ + ctx->call_count = 0; + ctx->errcode = 0; + /* Creating new worker threads. Please refer to the comment in + w32proc.c within the new_child function, where the + reader_thread thread is created, to understand why these + parameters are needed. */ + ctx->thread = CreateThread (NULL, 64 * 1024, wait_objects_thread, + ctx, 0x00010000, NULL); + /* CreateThread failed. */ + if (!ctx->thread) + return WFO_FAILED; + /* Set Thread Description information. This may be handy with + debugging. */ + if (is_debugger_present && is_debugger_present () + && set_thread_description) + { + if (set_thread_description + (ctx->thread, L"sys_select_worker_thread") != S_OK) + return WFO_FAILED; + } + wait_pool.count++; + } + /* Fill the main thread's wait array with the wait_ready event + handles of the required worker threads, and set them to be + unsignaled. */ + for (int i = 0; i < p->nt; i++) + { + p->hs[i] = wait_pool.wocs[i].wait_ready; + /* Reset the wait_ready event and set wait_start event for threads + that are not newly created. Newly created threads start + execution immediately. */ + if (i < orig_thread_count) + { + if (ResetEvent (wait_pool.wocs[i].wait_ready) + && SetEvent (wait_pool.wocs[i].wait_start)) + continue; + /* SetEvent or ResetEvent failed. */ + else + return WFO_FAILED; + } + } + return 0; +} + +/* Ensure that all worker threads have completed their wait and are in + the ready state. */ +static int +stop_wait_objects (wait_objects_info *p) +{ + /* Set timeout_event to tell all worker threads stop waiting. */ + if (!SetEvent (wait_pool.timeout_event)) + return WFO_FAILED; + /* Wait for all the used worker threads to signal wait_ready. This + typically takes no more than a few dozen microseconds, so a + timeout indicates that an error has occurred. */ + DWORD result = WaitForMultipleObjects (p->nt, p->hs, TRUE, 20); + if (result == WAIT_FAILED || result == WAIT_ABANDONED) + return WFO_FAILED; + /* Timeout means there exists a thread exit abnormally. Since the + WAIT_FAILED error in the worker threads signals wait_ready, this + can only be an error occurring when the worker threads are waiting + for curr_start and wait_quit. */ + if (result == WAIT_TIMEOUT) + { + /* Find the first dead thread. */ + for (int i = 0; i < p->nt; i++) + { + if (WaitForSingleObject (wait_pool.wocs[i].thread, 0) == WAIT_OBJECT_0) + { + SetLastError (wait_pool.wocs[i].errcode); + return WFO_FAILED; + } + } + return WFO_FAILED; + } + /* Even if the wait succeeds, there is still a possibility that some + wait_ready might be signaled by a WAIT_FAILED error. To determine + if no WAIT_FAILED error occurred, check the errcode of all threads. */ + for (int i = 0; i < p->nt; i++) + { + if (wait_pool.wocs[i].errcode) + { + SetLastError (wait_pool.wocs[i].errcode); + return WFO_FAILED; + } + } + /* Reset the timeout event. */ + if (!ResetEvent (wait_pool.timeout_event)) + return WFO_FAILED; + return 0; +} + +/* After the main thread finishes waiting, obtain the signaled object + index based on the main thread's return value, or other possible + return values. */ +static DWORD +end_wait_and_return (wait_objects_info *p, DWORD result) +{ + /* Wait timeout in main thread. */ + if (result == WAIT_TIMEOUT) + result = WFO_TIMEOUT; + /* Wait failed in main thread. */ + else if (result == WAIT_FAILED) + return WFO_FAILED; + /* It seems that all the wait objects are just process handles and + event handles. WAIT_ABANDONED may not occur, but let's just + handle it here. */ + else if (result >= WAIT_ABANDONED_0 + && result < p->nh + WAIT_ABANDONED_0) + { + result -= WAIT_ABANDONED_0; + /* The event object wait_ready is not mutex object. This is + unlikely to happen... */ + if (result < p->nt) + return WFO_FAILED; + /* There are 62 * nt objects before the objects in the main + thread. Each thread can wait for 63 objects, and each + thread's wait_ready occupies one position in the main thread + array. Therefore, the index of the waiting object in the main + thread array should be incremented by (63 - 1) multiplied by + the number of worker threads. We add WFO_ABANDONED to + distinguish it from normal object index. */ + result = result + 62 * p->nt + WFO_ABANDONED; + } + /* Wait succeed in main thread. */ + else + { + /* Object index in main thread wait array. */ + int idx = result - WAIT_OBJECT_0; + /* Object is in main thread's wait array. */ + if (idx >= p->nt) + { + /* When the index is equal to the number of worker threads, + and the number of worker threads is equal to the number of + wait objects for the main thread, it indicates that the main + thread is using MsgWaitForMultipleObjects and that there are + no wait objects in the main thread's array. We should return + the total number of wait objects to indicate that a message + was received during the wait. */ + if (p->nt == p->nh) + result = (idx - 1) * 63 + wait_pool.wocs[idx - 1].nCount; + /* Normal case. */ + else + result = 62 * p->nt + idx; + } + /* Object is in worker threads. */ + else + { + DWORD t_result = wait_pool.wocs[idx].result; + /* WAIT_FAILED or WAIT_TIMEOUT. Since the wait time in the + worker thread is set to INFINITE, WAIT_TIMEOUT should not + occur. */ + if (t_result == WAIT_FAILED || t_result == WAIT_TIMEOUT) + { + SetLastError (wait_pool.wocs[idx].errcode); + return WFO_FAILED; + } + /* Abandoned. Compared to the waiting objects in the main + thread's array, the index in the worker threads needs to be + incremented by 63 times the number of preceding threads, + rather than 62. */ + else if (t_result >= WAIT_ABANDONED_0 + && t_result < wait_pool.wocs[idx].nCount + + WAIT_ABANDONED_0) + result = idx * 63 + t_result + + WFO_ABANDONED - WAIT_ABANDONED_0; + /* The worker thread is signaled by timeout_event, but at this + point, timeout_event has not yet been signaled. */ + else if (t_result == wait_pool.wocs[idx].nCount + WAIT_OBJECT_0) + return WFO_FAILED; + /* Normal object index. */ + else + result = idx * 63 + (t_result - WAIT_OBJECT_0); + } + } + /* Ensure that all the worker threads are ready to begin the next + round of waiting, and check for any potential errors. */ + if (stop_wait_objects (&wait_info) == WFO_FAILED) + return WFO_FAILED; + return result; +} + +/* Check if there are inactive worker threads in the waiting thread + pool and let them exit. */ +static int +shrink_wait_pool (void) +{ + wait_pool.main_thread_waits++; + /* Check the thread every 64 Wait call. 64 is just a value that might + be reasonably suitable. */ + if (wait_pool.main_thread_waits <= 64) + return 0; + wait_pool.main_thread_waits = 0; + /* return if no thread. */ + if (wait_pool.count == 0) + return 0; + /* Each time, we only check the last worker thread, which helps avoid + terminating a large number of threads at the same time. */ + int last = wait_pool.count - 1; + /* A call count of 0 indicates that the thread has not been used + during the past period of time. */ + if (wait_pool.wocs[last].call_count == 0) + { + /* Signal wait_quit to let the worker thread exit. */ + if (!SetEvent (wait_pool.wocs[last].wait_quit)) + return WFO_FAILED; + wait_pool.wocs[last].wait_quit = NULL; + /* Close thread handle. */ + if (!CloseHandle (wait_pool.wocs[last].thread)) + return WFO_FAILED; + wait_pool.wocs[last].thread = NULL; + /* Close wait_start event handle. */ + if (!CloseHandle (wait_pool.wocs[last].wait_start)) + return WFO_FAILED; + wait_pool.wocs[last].wait_start = NULL; + /* Close wait_ready event handle */ + if (!CloseHandle (wait_pool.wocs[last].wait_ready)) + return WFO_FAILED; + wait_pool.wocs[last].wait_ready = NULL; + /* decrease the number of workder thread by 1. */ + wait_pool.count--; + } + /* Reset call_count for each worker thread. */ + for (int i = 0; i <= last; i++) + wait_pool.wocs[i].call_count = 0; + return 0; +} + +/* Exit all worker threads and release the start and timeout + handles. This function is called when exiting Emacs. */ +void +free_wait_pool (void) +{ + /* Emacs has never used more than 32 child processes. */ + if (wait_pool.init_flag == FALSE) + return; + for (int i = 0; i < wait_pool.count; i++) + { + /* Send quit event to earch worker thread. */ + SetEvent (wait_pool.wocs[i].wait_quit); + /* Close wait_start, wait_ready event. */ + CloseHandle (wait_pool.wocs[i].wait_start); + CloseHandle (wait_pool.wocs[i].wait_ready); + } + /* Wait all workder threads exit. Stop if failed. */ + for (int i = 0; i < wait_pool.count; i++) + { + if (WaitForSingleObject (wait_pool.wocs[i].thread, 1) + != WAIT_OBJECT_0) + break; + } + /* Close timeout event. */ + CloseHandle (wait_pool.timeout_event); + return; +} +/* Replacement of WaitForMultipleObjects, with the bWaitAll parameter + removed. The function's return values are as follows: + + [0 ~ nCount-1], the return value indicates the lpHandles array + index of the object that satisfied the wait. + + [WFO_ABANDONED ~ nCount-1 + WFO_ABANDONED], the return value minus + WFO_ABANDONED indicates the lpHandles array index of an abandoned + mutex object that satisfied the wait. + + [WFO_TIMEOUT], The time-out interval elapsed. + + [WFO_FAILED], The function has failed. To get extended error + information, call GetLastError. */ +static DWORD +wait_for_objects (DWORD nCount, HANDLE *lpHandles, + DWORD dwMilliseconds) +{ + /* Check inactive worker threads and terminate them. */ + if (shrink_wait_pool () == WFO_FAILED) + return WFO_FAILED; + /* If the number of wait objects does not exceed 64, directly call + WaitForMultipleObjects and convert the return value. */ + if (nCount <= 64) + { + DWORD res = WaitForMultipleObjects (nCount, lpHandles, FALSE, + dwMilliseconds); + if (res == WAIT_TIMEOUT) + return WFO_TIMEOUT; + else if (res >= WAIT_OBJECT_0 + && res < WAIT_OBJECT_0 + nCount) + return res - WAIT_OBJECT_0; + else if (res >= WAIT_ABANDONED_0 + && res < WAIT_ABANDONED_0 + nCount) + return res + WFO_ABANDONED - WAIT_ABANDONED_0; + else + return WFO_FAILED; + } + /* If the wait time is 0, perform busy waiting. */ + if (dwMilliseconds == 0) + { + int rest = nCount % 64; + int group = nCount / 64; + DWORD res, count; + for (int i = 0, offset = 0; i <= group; i++, offset += 64) + { + count = (i == group) ? rest : 64; + /* When the number of waits is a multiple of 64, skipping the + last wait with a count of 0. */ + if (count == 0) + break; + res = WaitForMultipleObjects (count, lpHandles + offset, + FALSE, 0); + if (res == WAIT_TIMEOUT) + continue; + else if (res >= WAIT_OBJECT_0 + && res < WAIT_OBJECT_0 + count) + return offset + res - WAIT_OBJECT_0; + else if (res >= WAIT_ABANDONED_0 + && res < WAIT_ABANDONED_0 + count) + return offset + res + WFO_ABANDONED - WAIT_ABANDONED_0; + else + return WFO_FAILED; + } + return WFO_TIMEOUT; + } + /* If the number of objects is greater than 64 and the wait time is + not 0, use multithreaded waiting. */ + if (start_wait_objects (&wait_info, nCount, lpHandles) == WFO_FAILED) + return WFO_FAILED; + DWORD res = WaitForMultipleObjects (wait_info.nh, wait_info.hs, + FALSE, dwMilliseconds); + /* If the main thread wait times out, call WaitForMultipleObjects + again with zero time-out interval to check if any objects have + completed. The default clock resolution of Windows is 64 Hz. If a + time less than 15.625ms is specified, the waiting time may be + longer than the specified time, and some objects that were not + completed in the previous call may now be completed. */ + if (res == WAIT_TIMEOUT) + { + res = WaitForMultipleObjects (wait_info.nh, wait_info.hs, + FALSE, 0); + } + return end_wait_and_return (&wait_info, res); +} + +/* Replacement of MsgWaitForMultipleObjects, with the bWaitAll and + dwWakeMask parameters removed. The function's return values are as + follows: + + [0 ~ nCount-1], the return value indicates the lpHandles array + index of the object that satisfied the wait. + + [nCount], New input of the type QS_ALLINPUT is available in the + thread's input queue. + + [WFO_ABANDONED ~ nCount-1 + WFO_ABANDONED], the return value minus + WFO_ABANDONED indicates the lpHandles array index of an abandoned + mutex object that satisfied the wait. + + [WFO_TIMEOUT], The time-out interval elapsed. + + [WFO_FAILED], The function has failed. To get extended error + information, call GetLastError. */ +static DWORD +msg_wait_for_objects (DWORD nCount, HANDLE *lpHandles, + DWORD dwMilliseconds) +{ + /* Check inactive worker threads and terminate them. */ + if (shrink_wait_pool () == WFO_FAILED) + return WFO_FAILED; + /* If the number of wait objects does not exceed 63, directly call + MsgWaitForMultipleObjects and convert the return value. */ + if (nCount <= 63) + { + DWORD res = MsgWaitForMultipleObjects (nCount, lpHandles, FALSE, + dwMilliseconds, QS_ALLINPUT); + if (res == WAIT_TIMEOUT) + return WFO_TIMEOUT; + /* The return value of MsgWaitForMultipleObjects can be + WAIT_OBJECT_0 + nCount, indicating that a message was + received. So use (<=) rather than (<) here. */ + else if (res >= WAIT_OBJECT_0 + && res <= WAIT_OBJECT_0 + nCount) + return res - WAIT_OBJECT_0; + else if (res >= WAIT_ABANDONED_0 + && res < WAIT_ABANDONED_0 + nCount) + return res + WFO_ABANDONED - WAIT_ABANDONED_0; + else + return WFO_FAILED; + } + /* If the wait time is 0, perform busy waiting. */ + if (dwMilliseconds == 0) + { + int rest = nCount % 63; + int group = nCount / 63; + DWORD res, count; + for (int i = 0, offset = 0; i <= group; i++, offset += 63) + { + count = i == group ? rest : 63; + /* When the number of waits is a multiple of 63, skipping the + last wait with a count of 0. */ + if (count == 0) + break; + res = MsgWaitForMultipleObjects (count, lpHandles + offset, + FALSE, 0, QS_ALLINPUT); + if (res == WAIT_TIMEOUT) + continue; + else if (res >= WAIT_OBJECT_0 + && res < WAIT_OBJECT_0 + count) + return offset + res - WAIT_OBJECT_0; + /* When a message is received during the wait, return nCount + directly. This is the distinction that needs to be made + compared to wait_for_objects. */ + else if (res == WAIT_OBJECT_0 + count) + return nCount; + else if (res >= WAIT_ABANDONED_0 + && res < WAIT_ABANDONED_0 + count) + return offset + res + WFO_ABANDONED - WAIT_ABANDONED_0; + else + return WFO_FAILED; + } + return WFO_TIMEOUT; + } + /* If the number of objects is greater than 63 and the wait time is + not 0, use multithreaded waiting. */ + if (start_wait_objects (&wait_info, nCount, lpHandles) == WFO_FAILED) + return WFO_FAILED; + DWORD res = MsgWaitForMultipleObjects (wait_info.nh, wait_info.hs, + FALSE, dwMilliseconds, + QS_ALLINPUT); + /* If the main thread wait times out, call MsgWaitForMultipleObjects + again with zero time-out interval to check if any objects have + completed. */ + if (res == WAIT_TIMEOUT) + { + res = MsgWaitForMultipleObjects (wait_info.nh, wait_info.hs, + FALSE, 0, QS_ALLINPUT); + } + return end_wait_and_return (&wait_info, res); +} + /* Here's an overview of how support for subprocesses and network/serial streams is implemented on MS-Windows. @@ -1566,15 +2344,15 @@ waitpid (pid_t pid, int *status, int options) quitting in that case. */ if (!dont_wait) maybe_quit (); - active = WaitForMultipleObjects (nh, wait_hnd, FALSE, timeout_ms); - } while (active == WAIT_TIMEOUT && !dont_wait); + active = wait_for_objects (nh, wait_hnd, timeout_ms); + } while (active == WFO_TIMEOUT && !dont_wait); - if (active == WAIT_FAILED) + if (active == WFO_FAILED) { errno = EBADF; return -1; } - else if (active == WAIT_TIMEOUT && dont_wait) + else if (active == WFO_TIMEOUT && dont_wait) { /* PID specifies our subprocess, but it didn't exit yet, so its status is not yet available. */ @@ -1583,15 +2361,14 @@ waitpid (pid_t pid, int *status, int options) #endif return 0; } - else if (active >= WAIT_OBJECT_0 - && active < WAIT_OBJECT_0+MAXIMUM_WAIT_OBJECTS) + else if (active >= 0 && active < WFO_MAX_WAIT) { - active -= WAIT_OBJECT_0; + ; } - else if (active >= WAIT_ABANDONED_0 - && active < WAIT_ABANDONED_0+MAXIMUM_WAIT_OBJECTS) + else if (active >= WFO_ABANDONED + && active < WFO_ABANDONED + WFO_MAX_WAIT) { - active -= WAIT_ABANDONED_0; + active -= WFO_ABANDONED; } else emacs_abort (); @@ -2302,13 +3079,14 @@ int sys_select (int nfds, SELECT_TYPE *rfds, SELECT_TYPE *wfds, SELECT_TYPE *efds, const struct timespec *timeout, const sigset_t *ignored) { - SELECT_TYPE orfds, owfds; + static SELECT_TYPE orfds, owfds; + static child_process *cps[MAX_CHILDREN]; + static HANDLE wait_hnd[MAXDESC + MAX_CHILDREN]; + static int fdindex[MAXDESC]; /* mapping from wait handles back to descriptors */ DWORD timeout_ms, start_time; int i, nh, nc, nr; DWORD active; - child_process *cp, *cps[MAX_CHILDREN]; - HANDLE wait_hnd[MAXDESC + MAX_CHILDREN]; - int fdindex[MAXDESC]; /* mapping from wait handles back to descriptors */ + child_process *cp; timeout_ms = timeout ? (timeout->tv_sec * 1000 + timeout->tv_nsec / 1000000) : INFINITE; @@ -2501,12 +3279,11 @@ sys_select (int nfds, SELECT_TYPE *rfds, SELECT_TYPE *wfds, SELECT_TYPE *efds, /* Wait for input or child death to be signaled. If user input is allowed, then also accept window messages. */ if (FD_ISSET (0, &orfds)) - active = MsgWaitForMultipleObjects (nh + nc, wait_hnd, FALSE, timeout_ms, - QS_ALLINPUT); + active = msg_wait_for_objects (nh + nc, wait_hnd, timeout_ms); else - active = WaitForMultipleObjects (nh + nc, wait_hnd, FALSE, timeout_ms); + active = wait_for_objects (nh + nc, wait_hnd, timeout_ms); - if (active == WAIT_FAILED) + if (active == WFO_FAILED) { DebPrint (("select.WaitForMultipleObjects (%d, %lu) failed with %lu\n", nh + nc, timeout_ms, GetLastError ())); @@ -2517,7 +3294,7 @@ sys_select (int nfds, SELECT_TYPE *rfds, SELECT_TYPE *wfds, SELECT_TYPE *efds, errno = EINTR; return -1; } - else if (active == WAIT_TIMEOUT) + else if (active == WFO_TIMEOUT) { if (noninteractive) { @@ -2526,15 +3303,14 @@ sys_select (int nfds, SELECT_TYPE *rfds, SELECT_TYPE *wfds, SELECT_TYPE *efds, } return 0; } - else if (active >= WAIT_OBJECT_0 - && active < WAIT_OBJECT_0+MAXIMUM_WAIT_OBJECTS) + else if (active >= 0 && active < WFO_MAX_WAIT) { - active -= WAIT_OBJECT_0; + ; } - else if (active >= WAIT_ABANDONED_0 - && active < WAIT_ABANDONED_0+MAXIMUM_WAIT_OBJECTS) + else if (active >= WFO_ABANDONED + && active < WFO_ABANDONED + WFO_MAX_WAIT) { - active -= WAIT_ABANDONED_0; + active -= WFO_ABANDONED; } else emacs_abort (); commit 5d4056c7658d9a6c64267821bd56450e82de01cb Author: Eli Zaretskii Date: Fri Mar 14 14:00:16 2025 +0200 Fix a recent change in isearch.el * lisp/isearch.el (isearch-yank-x-selection): Ignore errors signaled by 'gui-get-primary-selection', to better emulate what 'gui-get-selection' was doing. In particular, 'gui-get-primary-selection' can signal an error on MS-Windows. diff --git a/lisp/isearch.el b/lisp/isearch.el index 6a21a548ed1..31ad96d4a78 100644 --- a/lisp/isearch.el +++ b/lisp/isearch.el @@ -2667,7 +2667,7 @@ always reads a string from the `kill-ring' using the minibuffer." (defun isearch-yank-x-selection () "Pull current PRIMARY X selection into the search string." (interactive) - (isearch-yank-string (gui-get-primary-selection)) + (isearch-yank-string (ignore-errors (gui-get-primary-selection))) ;; If `gui-get-selection' returned the text from the active region, ;; then it "used" the mark which we should hence deactivate. (when select-active-regions (deactivate-mark))) commit 0e93029353bafa530cf9278a558741c22a956278 Author: Eli Zaretskii Date: Fri Mar 14 13:52:54 2025 +0200 ; * lisp/isearch.el (isearch-yank-x-selection): Doc fix. diff --git a/lisp/isearch.el b/lisp/isearch.el index 636571e11c7..6a21a548ed1 100644 --- a/lisp/isearch.el +++ b/lisp/isearch.el @@ -2665,7 +2665,7 @@ always reads a string from the `kill-ring' using the minibuffer." (isearch-yank-string (current-kill 1))))) (defun isearch-yank-x-selection () - "Pull current X primary selection into search string." + "Pull current PRIMARY X selection into the search string." (interactive) (isearch-yank-string (gui-get-primary-selection)) ;; If `gui-get-selection' returned the text from the active region, commit 1437fc2bca2e42cd2a28754bbb4d9ac1403b87b7 Author: Po Lu Date: Fri Mar 14 15:52:59 2025 +0800 Enable yanking non-Latin-1 primary selections in I-Search * lisp/isearch.el (isearch-yank-x-selection): Call `gui-get-primary-selection', which accounts for non-Latin-1 encodings. diff --git a/lisp/isearch.el b/lisp/isearch.el index e31c5f7e59a..636571e11c7 100644 --- a/lisp/isearch.el +++ b/lisp/isearch.el @@ -2665,9 +2665,9 @@ always reads a string from the `kill-ring' using the minibuffer." (isearch-yank-string (current-kill 1))))) (defun isearch-yank-x-selection () - "Pull current X selection into search string." + "Pull current X primary selection into search string." (interactive) - (isearch-yank-string (gui-get-selection)) + (isearch-yank-string (gui-get-primary-selection)) ;; If `gui-get-selection' returned the text from the active region, ;; then it "used" the mark which we should hence deactivate. (when select-active-regions (deactivate-mark)))