commit f772fe526a6dd6726f2759e3d00b2a582fd045e0 (HEAD, refs/remotes/origin/master) Author: João Távora Date: Fri Nov 14 22:29:34 2025 +0000 Eglot: fix bug in eglot--semtok-font-lock The entry condition to eglot--semtok-font-lock-1, which checks if the region to refontify is contained in the recorded region in eglot--semtok-cache was blatantly wrong. It used 'beg' twice! * lisp/progmodes/eglot.el (eglot--semtok-font-lock): Fix bug. diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index d5a4ca096f3..2b901ed7875 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -4738,7 +4738,7 @@ lock machinery calls us again." (cond ((and (eq (plist-get eglot--semtok-cache :documentVersion) eglot--versioned-identifier) (and (<= (plist-get eglot--semtok-cache :from) beg) - (<= beg (plist-get eglot--semtok-cache :to)))) + (<= end (plist-get eglot--semtok-cache :to)))) (eglot--semtok-font-lock-1 beg end)) (t (eglot--semtok-font-lock-2 beg end) commit f83c35788aedf3f60a112e34c656df554c46ef09 Author: João Távora Date: Fri Nov 14 15:43:25 2025 +0000 Eglot: invalidate local semtok cache at server's bidding When the server sends workspace/semanticTokens/refresh, calling font-lock-flush isn't enough. It will trigger a new eglot--semtok-font-lock call, but we can't assume anything about the validity of eglot--semtok-cache. Clangd seems to send workspace/semanticTokens/refresh when complicated enough changes happen in an LSP document. If the buffer-local eglot--semtok-cache isn't flushed we'll likely request a delta, and clangd may still supply it, but it won't apply correctly to the outdated local state. * lisp/progmodes/eglot.el (eglot--semtok-cache) (eglot--semtok-inflight): Move up here. (eglot-handle-request): Flush the eglot--semtok-cache. diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index f089745e817..d5a4ca096f3 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -4620,12 +4620,20 @@ If NOERROR, return predicate, else erroring function." semtok-cache) probe)))) +(defvar-local eglot--semtok-cache nil + "Cache of the last response from the server.") + +(defvar-local eglot--semtok-inflight nil + "List of (BEG . END) regions of inflight semtok requests.") + (cl-defmethod eglot-handle-request (server (_method (eql workspace/semanticTokens/refresh))) "Handle a semanticTokens/refresh request from SERVER." (dolist (buffer (eglot--managed-buffers server)) (eglot--when-live-buffer buffer - (unless (zerop eglot--versioned-identifier) (font-lock-flush))))) + (unless (zerop eglot--versioned-identifier) + (setq eglot--semtok-cache nil) + (font-lock-flush))))) (define-minor-mode eglot-semantic-tokens-mode "Minor mode for fontifying buffer with LSP server's semantic tokens." @@ -4639,12 +4647,6 @@ If NOERROR, return predicate, else erroring function." (font-lock-remove-keywords nil '((eglot--semtok-font-lock))) (font-lock-flush)))) -(defvar-local eglot--semtok-cache nil - "Cache of the last response from the server.") - -(defvar-local eglot--semtok-inflight nil - "List of (BEG . END) regions of inflight semtok requests.") - (defsubst eglot--semtok-apply-delta-edits (old-data edits) "Apply EDITS obtained from full/delta request to OLD-DATA." (cl-loop commit d81bb68ed09821d9c9e771e7c1ce34b2bf212823 Author: João Távora Date: Fri Nov 14 10:12:47 2025 +0000 Eglot: janitorial cleanup * lisp/progmodes/eglot.el (eglot--lsp-position-to-point): Use eglot--widening. (eglot--lsp-position-to-point): Use eglot--widening. (eglot-range-region): New util. (eglot-format) (eglot--code-action-params) (eglot--update-hints-1) (eglot--semtok-request): Use eglot-range-region. (eglot--after-change): Add comment. eglot-range-region is an idea by Lua Viana Reis diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index ec346ae58ef..f089745e817 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -1259,13 +1259,18 @@ TRUENAMEP indicated PATH is already a truename." eglot--uri-path-allowed-chars))))) (defun eglot-range-region (range &optional markers) - "Return a cons (BEG . END) of positions representing LSP RANGE. + "Convert LSP \"Range\" RANGE to cons (BEG . END) of buffer positions. If optional MARKERS, make markers instead." (let* ((st (plist-get range :start)) (beg (eglot--lsp-position-to-point st markers)) (end (eglot--lsp-position-to-point (plist-get range :end) markers))) (cons beg end))) +(defun eglot-region-range (from to) + "Convert FROM and TO buffer positions to an LSP \"Range\"." + (list :start (eglot--pos-to-lsp-position from) + :end (eglot--pos-to-lsp-position to))) + (defun eglot-server-capable (&rest feats) "Determine if current server is capable of FEATS." (unless (cl-some (lambda (feat) @@ -2051,21 +2056,19 @@ encoding and Eglot will set this variable automatically.") (defun eglot--lsp-position-to-point (pos-plist &optional marker) "Convert LSP position POS-PLIST to Emacs point. If optional MARKER, return a marker instead" - (save-excursion - (save-restriction - (widen) - (goto-char (point-min)) - (forward-line (min most-positive-fixnum - (plist-get pos-plist :line))) - (unless (eobp) ;; if line was excessive leave point at eob - (let ((col (plist-get pos-plist :character))) - (unless (wholenump col) - (eglot--warn - "Caution: LSP server sent invalid character position %s. Using 0 instead." - col) - (setq col 0)) - (funcall eglot-move-to-linepos-function col))) - (if marker (copy-marker (point-marker)) (point))))) + (eglot--widening + (goto-char (point-min)) + (forward-line (min most-positive-fixnum + (plist-get pos-plist :line))) + (unless (eobp) ;; if line was excessive leave point at eob + (let ((col (plist-get pos-plist :character))) + (unless (wholenump col) + (eglot--warn + "Caution: LSP server sent invalid character position %s. Using 0 instead." + col) + (setq col 0)) + (funcall eglot-move-to-linepos-function col))) + (if marker (copy-marker (point-marker)) (point)))) ;;; More helpers @@ -3023,6 +3026,13 @@ Records BEG, END and PRE-CHANGE-LENGTH locally." `(,lsp-beg ,lsp-end ,pre-change-length ,(buffer-substring-no-properties beg end))))) (_ (setf eglot--recent-changes :emacs-messup))) + ;; JT@2025-11-14: Not 100% sure an idle timer is right to coalesce + ;; multiple edits into a single didChange notification. It allows, + ;; for example, user to modify the buffer in one place, spend + ;; arbitrary time in quick successive movement and edit again. Only + ;; after the idle delay will the change be sent. A regular timer + ;; would be simpler would notify the server to start processing + ;; changes sooner. (when eglot--change-idle-timer (cancel-timer eglot--change-idle-timer)) (let ((buf (current-buffer))) (setq eglot--change-idle-timer @@ -3455,8 +3465,7 @@ for which LSP on-type-formatting should be requested." ((and beg end) `(:textDocument/rangeFormatting :documentRangeFormattingProvider - (:range ,(list :start (eglot--pos-to-lsp-position beg) - :end (eglot--pos-to-lsp-position end))))) + (:range ,(eglot-region-range beg end)))) (t '(:textDocument/formatting :documentFormattingProvider nil))))) (eglot-server-capable-or-lose cap) @@ -4095,8 +4104,7 @@ edit proposed by the server." (cl-defun eglot--code-action-params (&key (beg (point)) (end beg) only triggerKind) (list :textDocument (eglot--TextDocumentIdentifier) - :range (list :start (eglot--pos-to-lsp-position beg) - :end (eglot--pos-to-lsp-position end)) + :range (eglot-region-range beg end) :context `(:diagnostics [,@(mapcar #'eglot--diag-to-lsp-diag @@ -4531,8 +4539,7 @@ If NOERROR, return predicate, else erroring function." (eglot--current-server-or-lose) :textDocument/inlayHint (list :textDocument (eglot--TextDocumentIdentifier) - :range (list :start (eglot--pos-to-lsp-position from) - :end (eglot--pos-to-lsp-position to))) + :range (eglot-region-range from to)) :success-fn (lambda (hints) (eglot--when-live-buffer buf (eglot--widening @@ -4714,8 +4721,7 @@ If NOERROR, return predicate, else erroring function." ((eglot-server-capable :semanticTokensProvider :range) (req :textDocument/semanticTokens/range beg end (list :textDocument (eglot--TextDocumentIdentifier) - :range (list :start (eglot--pos-to-lsp-position beg) - :end (eglot--pos-to-lsp-position end))) + :range (eglot-region-range beg end)) #'identity)) (t (req :textDocument/semanticTokens/full (point-min) (point-max) commit 78a73acf61ff38d57f572c11eba8ad2142857ef0 Author: Sean Whitton Date: Fri Nov 14 14:12:38 2025 +0000 ; * lisp/emacs-lisp/cond-star.el (cond*): New FIXME. diff --git a/lisp/emacs-lisp/cond-star.el b/lisp/emacs-lisp/cond-star.el index 326ab58befc..55322e4713e 100644 --- a/lisp/emacs-lisp/cond-star.el +++ b/lisp/emacs-lisp/cond-star.el @@ -90,6 +90,7 @@ All bindings made in CONDITION for the BODY of the non-exit clause are passed along to the rest of the clauses in this `cond*' construct. \\[match*] for documentation of the patterns for use in `match*'." + ;; FIXME: Want an Edebug declaration. (cond*-convert clauses)) ;; The following three macros are autoloaded for the sake of syntax commit 3c9bcdebe4e27812ea5ad5fb9241c3d3436fa675 Author: Sean Whitton Date: Fri Nov 14 14:11:04 2025 +0000 Autoload match*, bind*, bind-and* dummy macros * lisp/emacs-lisp/cond-star.el (match*, bind*, bind-and*): Autoload, for the sake of syntax highlighting. diff --git a/lisp/emacs-lisp/cond-star.el b/lisp/emacs-lisp/cond-star.el index a8b144ea306..326ab58befc 100644 --- a/lisp/emacs-lisp/cond-star.el +++ b/lisp/emacs-lisp/cond-star.el @@ -92,6 +92,10 @@ are passed along to the rest of the clauses in this `cond*' construct. \\[match*] for documentation of the patterns for use in `match*'." (cond*-convert clauses)) +;; The following three macros are autoloaded for the sake of syntax +;; highlighting. + +;;;###autoload (defmacro match* (pattern _datum) "This specifies matching DATUM against PATTERN. It is not really a Lisp function, and it is meaningful @@ -158,6 +162,7 @@ ATOM (meaning any other kind of non-list not described above) ;; FIXME: `byte-compile-warn-x' is not necessarily defined here. (byte-compile-warn-x pattern "`match*' used other than as a `cond*' condition")) +;;;###autoload (defmacro bind* (&rest bindings) "This macro evaluates BINDINGS like `let*'. It is not really a Lisp function, and it is meaningful @@ -165,6 +170,7 @@ only in the CONDITION of a `cond*' clause." ;; FIXME: `byte-compile-warn-x' is not necessarily defined here. (byte-compile-warn-x bindings "`bind' used other than as a `cond*' condition")) +;;;###autoload (defmacro bind-and* (&rest bindings) "This macro evaluates BINDINGS like `if-let*'. It is not really a Lisp function, and it is meaningful commit 0d4741ec924942687d9135ca43e6140955aea128 Author: Sean Whitton Date: Fri Nov 14 13:00:13 2025 +0000 diff-apply-hunk: Don't display BUF in the selected window * lisp/vc/diff-mode.el (diff-apply-hunk): Don't display BUF in the selected window. diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el index 7dd90ee5b75..07c2e013419 100644 --- a/lisp/vc/diff-mode.el +++ b/lisp/vc/diff-mode.el @@ -2197,7 +2197,9 @@ With a prefix argument, REVERSE the hunk." (delete-region (car pos) (cdr pos)) (insert (car new))) ;; Display BUF in a window - (set-window-point (display-buffer buf) (+ (car pos) (cdr new))) + (let ((display-buffer-overriding-action + '(nil (inhibit-same-window . t)))) + (set-window-point (display-buffer buf) (+ (car pos) (cdr new)))) (diff-hunk-status-msg line-offset (xor switched reverse) nil) (when diff-advance-after-apply-hunk (diff-hunk-next)))))) commit a40d8885f88cf87b2adb365f91e0a409731f504b Author: Eli Zaretskii Date: Fri Nov 14 10:14:14 2025 +0200 ; * lisp/progmodes/hideshow.el: Fix wording in commentary. diff --git a/lisp/progmodes/hideshow.el b/lisp/progmodes/hideshow.el index 4bbc968c82d..255dea4a095 100644 --- a/lisp/progmodes/hideshow.el +++ b/lisp/progmodes/hideshow.el @@ -185,23 +185,23 @@ ;; ** Migrating from `hs-special-modes-alist' ;; -;; Starting with Emacs v31.1, `hs-special-modes-alist' has been -;; deprecated. Instead, hideshow provides a new set of buffer-local -;; variables that replace each of the options in -;; `hs-special-modes-alist': +;; Starting with Emacs 31, `hs-special-modes-alist' has been deprecated. +;; Instead, modes should use the buffer-local variables that replace +;; each of the options in `hs-special-modes-alist'. The following table +;; shows the old elements of `hs-special-modes-alist' and their +;; replacement buffer-local variables: ;; -;; hs-special-modes-alist -;; '(MODE ; Not necessary, set the variables locally inside the -;; ; definition of the minor or major mode. -;; START | (START . MDATA) ::= `hs-block-start-regexp' -;; & `hs-block-start-mdata-select' -;; END ::= 'hs-block-end-regexp' -;; COMMENT-START ::= 'hs-c-start-regexp' -;; FORWARD-SEXP-FUNC ::= 'hs-forward-sexp-function' -;; ADJUST-BEG-FUNC ::= 'hs-adjust-block-beginning-function' -;; FIND-BLOCK-BEGINNING-FUNC ::= 'hs-find-block-beginning-function' -;; FIND-NEXT-BLOCK-FUNC ::= 'hs-find-next-block-function' -;; LOOKING-AT-BLOCK-START-P-FUNC ::= 'hs-looking-at-block-start-predicate') +;; Instead of this Use this +;; ----------------------------------------------------------------------- +;; START `hs-block-start-regexp' +;; (START . MDATA) `hs-block-start-regexp' and `hs-block-start-mdata-select' +;; END `hs-block-end-regexp' +;; COMMENT-START `hs-c-start-regexp' +;; FORWARD-SEXP-FUNC `hs-forward-sexp-function' +;; ADJUST-BEG-FUNC `hs-adjust-block-beginning-function' +;; FIND-BLOCK-BEGINNING-FUNC `hs-find-block-beginning-function' +;; FIND-NEXT-BLOCK-FUNC `hs-find-next-block-function' +;; LOOKING-AT-BLOCK-START-P-FUNC `hs-looking-at-block-start-predicate') ;; * Bugs ;; commit ab8d3624c414ebf8c4df7dd5385b8759eb477bf1 Author: Spencer Baugh Date: Tue Nov 11 20:11:20 2025 -0500 Do eager display of *Completions* while idle Don't block user input while rendering the *Completions* buffer due to eager-display. This allows eager-display to be used with larger and slower completion tables without interfering with the user. Like in eager-update, we use while-no-input and non-essential to ensure that eager-display happens without blocking the user. To support this, we remove the ability to set eager-display to a function. The only user was tmm.el, which nows sets eager-display to t and adds a completion-setup-hook instead. (This also fixes a bug in tmm where dismissing and redisplaying the *Completions* buffer would not have the special help text) * lisp/minibuffer.el (completion-eager-display--timer) (completions--eager-display, completions--start-eager-display): Add. (bug#79819) (completing-read-default): Call completions--start-eager-display, stop supporting functionp eager-display. * lisp/tmm.el (tmm-add-prompt): Delete. (tmm--completion-setup-hook): Add. (tmm-add-prompt): Add completion-setup-hook, set eager-display to t. (tmm-goto-completions): Call minibuffer-completion-help instead of tmm-add-prompt. diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el index e79e96bd7c7..f338b9bf453 100644 --- a/lisp/minibuffer.el +++ b/lisp/minibuffer.el @@ -141,8 +141,7 @@ This metadata is an alist. Currently understood keys are: - `cycle-sort-function': function to sort entries when cycling. Works like `display-sort-function'. - `eager-display': non-nil to request eager display of the - completion candidates. Can also be a function which is invoked - after minibuffer setup. + completion candidates. The metadata of a completion table should be constant between two boundaries." (let ((metadata (if (functionp table) (funcall table string pred 'metadata)))) @@ -1278,7 +1277,7 @@ an association list that can specify properties such as: - `group-function': function for grouping the completion candidates. - `annotation-function': function to add annotations in *Completions*. - `affixation-function': function to prepend/append a prefix/suffix. -- `eager-display': function to show *Completions* eagerly. +- `eager-display': non-nil to show *Completions* eagerly. Categories are symbols such as `buffer' and `file', used when completing buffer and file names, respectively. @@ -1300,7 +1299,7 @@ possible values are the same as in `completions-sort'. - `group-function': function for grouping the completion candidates. - `annotation-function': function to add annotations in *Completions*. - `affixation-function': function to prepend/append a prefix/suffix. -- `eager-display': function to show *Completions* eagerly. +- `eager-display': non-nil to show *Completions* eagerly. See more description of metadata in `completion-metadata'. Categories are symbols such as `buffer' and `file', used when @@ -2754,6 +2753,24 @@ so that the update is less likely to interfere with user typing." ((completion--eager-update-p (minibuffer-prompt-end)) (minibuffer-completion-help)))))) +(defvar completion-eager-display--timer nil) + +(defun completions--eager-display () + "Try to display *Completions* without blocking input." + ;; If the user has left the minibuffer, give up on eager display of + ;; *Completions*. + (when (minibufferp nil t) + (when (while-no-input + (let ((non-essential t)) + (minibuffer-completion-help))) + ;; If we got interrupted, try again the next time the user is idle. + (completions--start-eager-display)))) + +(defun completions--start-eager-display () + "Display the *Completions* buffer when the user is next idle." + (setq completion-eager-display--timer + (run-with-idle-timer 0 nil #'completions--eager-display))) + (defun completions--post-command-update () "Update displayed *Completions* buffer after command, once." (remove-hook 'post-command-hook #'completions--post-command-update) @@ -5143,14 +5160,14 @@ See `completing-read' for the meaning of the arguments." ;; `completion-eager-display' is t or if eager display ;; has been requested by the completion table. (when completion-eager-display - (let* ((md (completion-metadata + (when (or (eq completion-eager-display t) + (completion-metadata-get + (completion-metadata (buffer-substring-no-properties (minibuffer-prompt-end) (point)) - collection predicate)) - (fun (completion-metadata-get md 'eager-display))) - (when (or fun (eq completion-eager-display t)) - (funcall (if (functionp fun) - fun #'minibuffer-completion-help)))))) + collection predicate) + 'eager-display)) + (completions--start-eager-display)))) (read-from-minibuffer prompt initial-input keymap nil hist def inherit-input-method)))) (when (and (equal result "") def) diff --git a/lisp/tmm.el b/lisp/tmm.el index f893df2cb7a..c86aafc200d 100644 --- a/lisp/tmm.el +++ b/lisp/tmm.el @@ -218,7 +218,9 @@ is used to go back through those sub-menus." (car (nth index-of-default tmm-km-list)) (minibuffer-with-setup-hook (lambda () - (setq tmm-old-mb-map (tmm-define-keys t))) + (setq tmm-old-mb-map (tmm-define-keys t)) + (add-hook 'completion-setup-hook + #'tmm--completion-setup-hook 'append 'local)) ;; tmm-km-list is reversed, because history ;; needs it in LIFO order. But default list ;; needs it in non-reverse order, so that the @@ -231,7 +233,7 @@ is used to go back through those sub-menus." " (up/down to change, PgUp to menu): ") (completion-table-with-metadata tmm-km-list '((category . tmm) - (eager-display . tmm-add-prompt) + (eager-display . t) (display-sort-function . identity) (cycle-sort-function . identity))) nil t nil @@ -416,20 +418,16 @@ Stores a list of all the shortcuts in the free variable `tmm-short-cuts'." (goto-char next))) (set-buffer-modified-p nil))) -(defun tmm-add-prompt () +(defun tmm--completion-setup-hook () (unless tmm-c-prompt (error "No active menu entries")) (or tmm-completion-prompt - (add-hook 'completion-setup-hook - #'tmm-completion-delete-prompt 'append)) - (unwind-protect - (minibuffer-completion-help) - (remove-hook 'completion-setup-hook #'tmm-completion-delete-prompt)) - (with-current-buffer "*Completions*" + (tmm-completion-delete-prompt)) + (with-current-buffer standard-output (tmm-remove-inactive-mouse-face) (when tmm-completion-prompt (let ((inhibit-read-only t) - (window (get-buffer-window "*Completions*"))) + (window (get-buffer-window))) (goto-char (point-min)) (insert (if tmm-shortcut-inside-entry @@ -474,7 +472,7 @@ Stores a list of all the shortcuts in the free variable `tmm-short-cuts'." (defun tmm-goto-completions () "Jump to the completions buffer." (interactive) - (tmm-add-prompt) + (minibuffer-completion-help) (setq tmm-c-prompt (buffer-substring (minibuffer-prompt-end) (point-max))) ;; Clear minibuffer old contents before using *Completions* buffer for ;; selection. commit 4e08b2d434a9d42884aac99d7451cc5d5b5247c9 Author: Elías Gabriel Pérez Date: Wed Nov 12 10:51:24 2025 -0600 ; hideshow: Fix previous changes * lisp/progmodes/hideshow.el (hs--add-indicators): Add the indicators per line (bug#79810). (hs-already-hidden-p, hs-hide-block): Simplify. diff --git a/lisp/progmodes/hideshow.el b/lisp/progmodes/hideshow.el index 81400cdb623..4bbc968c82d 100644 --- a/lisp/progmodes/hideshow.el +++ b/lisp/progmodes/hideshow.el @@ -839,26 +839,31 @@ point." (goto-char beg) (remove-overlays beg end 'hs-indicator t) - (while (funcall hs-find-next-block-function hs-block-start-regexp end nil) - (when-let* ((b-beg (match-beginning 0)) - (_ (save-excursion - (goto-char b-beg) - (funcall hs-looking-at-block-start-predicate))) - ;; `catch' is used here if the search fails due - ;; unbalanced parentheses or any other unknown error - ;; caused in `hs-forward-sexp'. - (b-end (catch 'hs-indicator-error - (save-excursion + (while (not (>= (point) end)) + (save-excursion + (let (exit) + (while (and (not exit) + (funcall hs-find-next-block-function hs-block-start-regexp (pos-eol) nil)) + (when-let* ((b-beg (match-beginning 0)) + (_ (save-excursion (goto-char b-beg) - (condition-case _ - (funcall hs-forward-sexp-function 1) - (scan-error (throw 'hs-indicator-error nil))) - (point)))) - ;; Check if block is longer than 1 line. - (_ (hs-hideable-region-p b-beg b-end))) - ;; Only 1 indicator per line - (when (hs--make-indicators-overlays b-beg) - (forward-line)))) + (funcall hs-looking-at-block-start-predicate))) + ;; `catch' is used here if the search fails due + ;; unbalanced parentheses or any other unknown error + ;; caused in `hs-forward-sexp'. + (b-end (catch 'hs-indicator-error + (save-excursion + (goto-char b-beg) + (condition-case _ + (funcall hs-forward-sexp-function 1) + (scan-error (throw 'hs-indicator-error nil))) + (point)))) + ;; Check if block is longer than 1 line. + (_ (hs-hideable-region-p b-beg b-end))) + (hs--make-indicators-overlays b-beg) + (setq exit t))))) + ;; Only 1 indicator per line + (forward-line)) `(jit-lock-bounds ,beg . ,end)) (defun hs--refresh-indicators (from to) @@ -1168,18 +1173,13 @@ Return point, or nil if original point was not in a block." "Return non-nil if point is in an already-hidden block, otherwise nil." (save-excursion (let ((c-reg (funcall hs-inside-comment-predicate))) - (if (and c-reg (nth 0 c-reg)) - ;; point is inside a comment, and that comment is hideable - (goto-char (nth 0 c-reg)) - (when (not c-reg) - (end-of-line) - (when (not (hs-find-block-beginning-match)) - ;; We should also consider ourselves "in" a hidden block when - ;; point is right at the edge after a hidden block (bug#52092). - (beginning-of-line) - (hs-find-block-beginning-match))))) - (end-of-line) - (eq 'hs (get-char-property (point) 'invisible)))) + (when (and c-reg (nth 0 c-reg)) + ;; point is inside a comment, and that comment is hideable + (goto-char (nth 0 c-reg)))) + ;; Search for a hidden block at EOL ... + (or (eq 'hs (get-char-property (line-end-position) 'invisible)) + ;; ... or behind the current cursor position + (eq 'hs (get-char-property (1- (point)) 'invisible))))) ;; This function is not used anymore (Bug#700). (defun hs-c-like-adjust-block-beginning (initial) @@ -1261,8 +1261,7 @@ Upon completion, point is repositioned and the normal hook `hs-hide-hook' is run. See documentation for `run-hooks'." (interactive "P") (hs-life-goes-on - (let ((c-reg (funcall hs-inside-comment-predicate)) - (pos (point))) + (let ((c-reg (funcall hs-inside-comment-predicate))) (cond ((and c-reg (or (null (nth 0 c-reg)) (not (hs-hideable-region-p (car c-reg) (nth 1 c-reg))))) @@ -1270,25 +1269,26 @@ Upon completion, point is repositioned and the normal hook (c-reg (hs-hide-block-at-point end c-reg)) - ((or (and (eq hs-hide-block-behavior 'after-bol) - (setq pos (point)) - (goto-char (line-beginning-position)) - (catch 'hs--exit-hide - (while (and (funcall hs-find-next-block-function - hs-block-start-regexp - (line-end-position) nil) - (save-excursion - (goto-char (match-beginning 0)) - (if (hs-hideable-region-p) - (throw 'hs--exit-hide t) - t))))) - (goto-char (match-beginning 0))) - (and (goto-char pos) - (funcall hs-looking-at-block-start-predicate))) - (hs-hide-block-at-point end)) - - ((and (goto-char (line-beginning-position)) - (funcall hs-find-block-beginning-function)) + ((save-excursion + (and (eq hs-hide-block-behavior 'after-bol) + (goto-char (line-beginning-position)) + (let (exit) + (while (and (not exit) + (funcall hs-find-next-block-function + hs-block-start-regexp + (line-end-position) nil) + (save-excursion + (goto-char (match-beginning 0)) + (if (hs-hideable-region-p) + (setq exit t) + t)))) + exit) + (goto-char (match-beginning 0)) + (hs-hide-block-at-point end)))) + + ((or (funcall hs-looking-at-block-start-predicate) + (and (goto-char (line-beginning-position)) + (funcall hs-find-block-beginning-function))) (hs-hide-block-at-point end))) (run-hooks 'hs-hide-hook)))) commit 99ec59f1a4c376ede377fcda8b588e858d8e6124 Author: Elías Gabriel Pérez Date: Thu Nov 13 20:12:47 2025 -0600 hideshow: Update Header Commentary. (Bug#79829) * lisp/progmodes/hideshow.el: Document how to migrate from 'hs-special-modes-alist'. diff --git a/lisp/progmodes/hideshow.el b/lisp/progmodes/hideshow.el index de933710dc1..81400cdb623 100644 --- a/lisp/progmodes/hideshow.el +++ b/lisp/progmodes/hideshow.el @@ -146,7 +146,9 @@ ;; (overlay-end ov))) ;; 'face 'font-lock-type-face))))) -;; * Adding support for a major mode +;; * Extending hideshow + +;; ** Adding support for a major mode ;; ;; Normally, hideshow tries to determine appropriate values for block ;; and comment definitions by examining the major mode settings. If the @@ -170,7 +172,7 @@ ;; cases, `hs-forward-sexp-function' specifies another function to use ;; instead. -;; ** Tree-sitter support +;; *** Tree-sitter support ;; ;; All the treesit based modes already have support for hidding/showing ;; using the treesit thing `list' (see `treesit-major-mode-setup'). @@ -181,6 +183,26 @@ ;; values in `hs-adjust-block-end-function' and `hs-adjust-block-beginning-function' to ;; properly hide the code block. +;; ** Migrating from `hs-special-modes-alist' +;; +;; Starting with Emacs v31.1, `hs-special-modes-alist' has been +;; deprecated. Instead, hideshow provides a new set of buffer-local +;; variables that replace each of the options in +;; `hs-special-modes-alist': +;; +;; hs-special-modes-alist +;; '(MODE ; Not necessary, set the variables locally inside the +;; ; definition of the minor or major mode. +;; START | (START . MDATA) ::= `hs-block-start-regexp' +;; & `hs-block-start-mdata-select' +;; END ::= 'hs-block-end-regexp' +;; COMMENT-START ::= 'hs-c-start-regexp' +;; FORWARD-SEXP-FUNC ::= 'hs-forward-sexp-function' +;; ADJUST-BEG-FUNC ::= 'hs-adjust-block-beginning-function' +;; FIND-BLOCK-BEGINNING-FUNC ::= 'hs-find-block-beginning-function' +;; FIND-NEXT-BLOCK-FUNC ::= 'hs-find-next-block-function' +;; LOOKING-AT-BLOCK-START-P-FUNC ::= 'hs-looking-at-block-start-predicate') + ;; * Bugs ;; ;; (1) Sometimes `hs-headline' can become out of sync. To reset, type commit 7a3b8c6df585d4fd7f202f396f49d0af30f9089f Author: João Távora Date: Thu Nov 13 22:00:11 2025 +0000 Eglot: add two semtok tests The tests are pretty basic, I don't expect them to catch anything else than giant bugs. * test/lisp/progmodes/eglot-tests.el (eglot-test-semtok-basic) (eglot-test-semtok-refontify): New tests. (eglot--semtok-faces, eglot--semtok-wait): New helpers. diff --git a/test/lisp/progmodes/eglot-tests.el b/test/lisp/progmodes/eglot-tests.el index 8455c8ff8db..8f8f46db2bb 100644 --- a/test/lisp/progmodes/eglot-tests.el +++ b/test/lisp/progmodes/eglot-tests.el @@ -1503,6 +1503,58 @@ GUESSED-MAJOR-MODES-SYM are bound to the useful return values of (eglot--find-file-noselect "project/foolib.c") (should (eq (eglot-current-server) server)))))) +(defun eglot--semtok-faces () "Get semtok faces before point" + (get-text-property (1- (point)) 'eglot--semtok-faces)) + +(defun eglot--semtok-wait (pos) "Wait for semtok faces to appear after POS" + (eglot--with-timeout + '(3 "Timeout waiting for semantic tokens") + (while (not (save-excursion + (goto-char pos) + (text-property-search-forward 'eglot--semtok-faces))) + (accept-process-output nil 0.1) + (font-lock-ensure)))) + +(ert-deftest eglot-test-semtok-basic () + "Test basic semantic tokens fontification." + (skip-unless (executable-find "clangd")) + (eglot--with-fixture + `(("project" . (("main.c" . "int main() { int x = 42; return x; }")))) + (with-current-buffer + (eglot--find-file-noselect "project/main.c") + (eglot--tests-connect) + (should (eglot-server-capable :semanticTokensProvider)) + (should eglot-semantic-tokens-mode) + ;; Trigger initial fontification, then wait for semantic tokens + (font-lock-ensure) + (eglot--semtok-wait (point-min)) + (goto-char (point-min)) + (search-forward "main") + (should (memq 'eglot-semantic-function-face (eglot--semtok-faces))) + (search-forward "int x") + (should (memq 'eglot-semantic-variable-face (eglot--semtok-faces)))))) + +(ert-deftest eglot-test-semtok-refontify () + "Test semantic tokens refontification after edits." + (skip-unless (executable-find "clangd")) + (eglot--with-fixture + `(("project" . (("code.c" . "int foo() { return 0; }")))) + (with-current-buffer + (eglot--find-file-noselect "project/code.c") + (eglot--tests-connect) + (should eglot-semantic-tokens-mode) + (font-lock-ensure) + (eglot--semtok-wait (point-min)) + (goto-char (point-max)) + (save-excursion (insert "\nint bar() { int y = 10; return y; }")) + (font-lock-ensure) + (eglot--signal-textDocument/didChange) ; a bit unrealistic + (eglot--semtok-wait (point)) + (search-forward "bar") + (should (memq 'eglot-semantic-function-face (eglot--semtok-faces))) + (search-forward "int y") + (should (memq 'eglot-semantic-variable-face (eglot--semtok-faces)))))) + (provide 'eglot-tests) ;; Local Variables: commit 6126ab8d82bdeedb26bdc75af6ca819bf6d8b8d8 Author: João Távora Date: Thu Nov 13 20:56:50 2025 +0000 Eglot: tweak some semtok functions to be more debug-friendly * lisp/progmodes/eglot.el (eglot--semtok-request): Return value records return reason. (eglot--semtok-font-lock-1): Return number of painted tokens and exit locus. (eglot--semtok-decode-token): Rename from eglot--semtok-token-faces. Rework. (eglot--semtok-font-lock-1): Use eglot--semtok-decode-token. diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 70f5fc9093f..ec346ae58ef 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -4589,26 +4589,27 @@ If NOERROR, return predicate, else erroring function." (eglot--semtok-define-things) -(defun eglot--semtok-token-faces (tok) +(defun eglot--semtok-decode-token (tok) + "Decode TOK. Return (NAMES . FACES). Filter FACES via user options." (with-slots (semtok-cache capabilities) (eglot--current-server-or-lose) - (let ((probe (gethash tok semtok-cache :missing)) - tname) + (let ((probe (gethash tok semtok-cache :missing))) (if (eq probe :missing) (puthash tok (eglot--dbind ((SemanticTokensLegend) tokenTypes tokenModifiers) (plist-get (plist-get capabilities :semanticTokensProvider) :legend) - (setq tname (aref tokenTypes (car tok))) - (when (member tname eglot-semantic-token-types) - (cl-loop - for j from 0 for m across tokenModifiers - unless (or (zerop (logand (cdr tok) (ash 1 j))) - (not (member m eglot-semantic-token-modifiers))) - collect (intern (format "eglot-semantic-%s-face" m)) into mfaces - finally (cl-return - (cons (intern (format "eglot-semantic-%s-face" tname)) - mfaces))))) + (cl-loop + with tname = (aref tokenTypes (car tok)) + for j from 0 for m across tokenModifiers + when (cl-plusp (logand (cdr tok) (ash 1 j))) + collect m into names + and when (member m eglot-semantic-token-modifiers) + collect (intern (format "eglot-semantic-%s-face" m)) into faces + finally + (when (member tname eglot-semantic-token-types) + (push (intern (format "eglot-semantic-%s-face" tname)) faces)) + (cl-return (cons (cons tname names) faces)))) semtok-cache) probe)))) @@ -4697,7 +4698,7 @@ If NOERROR, return predicate, else erroring function." ;; trivial/fast edits. Even though it's fairly cheap to send ;; multiple delta requests, it's nicer to just send just one. (when (cdr eglot--semtok-inflight) - (cl-return-from eglot--semtok-request)) + (cl-return-from eglot--semtok-request 'skipped)) (req :textDocument/semanticTokens/full/delta (point-min) (point-max) (list :textDocument (eglot--TextDocumentIdentifier) :previousResultId (cache-get :response :resultId)) @@ -4708,7 +4709,7 @@ If NOERROR, return predicate, else erroring function." (eglot--semtok-apply-delta-edits (cache-get :response :data) edits))) - ;; server sent full response instead, so just record that. + ;; (trace-values "Server send full response instead") response)))) ((eglot-server-capable :semanticTokensProvider :range) (req :textDocument/semanticTokens/range beg end @@ -4753,20 +4754,21 @@ lock machinery calls us again." unless (< (point) beg) do (setq column (+ column (aref data (+ i 1)))) (funcall eglot-move-to-linepos-function column) - (when (> (point) end) (cl-return napplied)) + (when (> (point) end) (cl-return (cons napplied 'early))) (setq p-beg (point)) (funcall eglot-move-to-linepos-function (+ column (aref data (+ i 2)))) (setq p-end (point)) (let* ((tok (cons (aref data (+ i 3)) (aref data (+ i 4)))) - (faces (eglot--semtok-token-faces tok))) + (decoded (eglot--semtok-decode-token tok))) ;; The `eglot--semtok-token' prop doesn't serve much purpose: ;; just for debug... - (put-text-property p-beg p-end 'eglot--semtok-token tok) - (put-text-property p-beg p-end 'eglot--semtok-faces faces) - (dolist (f faces) + (put-text-property p-beg p-end 'eglot--semtok-names (car decoded)) + (put-text-property p-beg p-end 'eglot--semtok-faces (cdr decoded)) + (dolist (f (cdr decoded)) (add-face-text-property p-beg p-end f))) - count 1 into napplied)))) + count 1 into napplied + finally (cl-return (cons napplied 'normal)))))) (defun eglot--semtok-font-lock-2 (beg end) ;; JT@2025-11-11: FIXME: I wish I didn't need this kludge but the commit 2f196548f5ed4e34396fd7a593089759813ce0c4 Author: João Távora Date: Thu Nov 13 20:53:01 2025 +0000 Eglot: tidy overlay cleanup code And turn off semtok when tearing down eglot--managed-mode. * lisp/progmodes/eglot.el (eglot--delete-overlays): New helper. (eglot--managed-mode): Turn off minor modes and cleanup here. (eglot--managed-mode-off): Not here. (eglot-highlight-eldoc-function) (eglot-code-action-suggestion): Use eglot--overlay prop. (eglot-inlay-hints-mode): Use eglot--delete-overlays. diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index ceebcc80ce2..70f5fc9093f 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -1954,6 +1954,11 @@ and also used as a hint of the request cancellation mechanism (see (push id (plist-get eglot--inflight-async-requests hint)))))) +(cl-defun eglot--delete-overlays (&optional (prop 'eglot--overlays)) + (eglot--widening + (dolist (o (overlays-in (point-min) (point-max))) + (when (overlay-get o prop) (delete-overlay o))))) + ;;; Encoding fever ;;; @@ -2260,8 +2265,9 @@ Use `eglot-managed-p' to determine if current buffer is managed.") (eldoc-mode 1)) (cl-pushnew (current-buffer) (eglot--managed-buffers (eglot-current-server)))) (t - (mapc #'delete-overlay eglot--highlights) - (delete-overlay eglot--suggestion-overlay) + (eglot-inlay-hints-mode -1) + (eglot-semantic-tokens-mode -1) + (eglot--delete-overlays 'eglot--overlay) (remove-hook 'after-change-functions #'eglot--after-change t) (remove-hook 'before-change-functions #'eglot--before-change t) (remove-hook 'kill-buffer-hook #'eglot--managed-mode-off t) @@ -2302,8 +2308,6 @@ Use `eglot-managed-p' to determine if current buffer is managed.") (defun eglot--managed-mode-off () "Turn off `eglot--managed-mode' unconditionally." - (remove-overlays nil nil 'eglot--overlay t) - (eglot-inlay-hints-mode -1) (eglot--managed-mode -1)) (defun eglot-current-server () @@ -3860,6 +3864,7 @@ for which LSP on-type-formatting should be requested." (eglot-range-region range))) (let ((ov (make-overlay beg end))) (overlay-put ov 'face 'eglot-highlight-symbol-face) + (overlay-put ov 'eglot--overlay t) (overlay-put ov 'modification-hooks `(,(lambda (o &rest _) (delete-overlay o)))) ov))) @@ -4218,6 +4223,7 @@ at point. With prefix argument, prompt for ACTION-KIND." (goto-char (car bounds)) (let ((ov (make-overlay (car bounds) (cadr bounds)))) (overlay-put ov 'eglot--actions actions) + (overlay-put ov 'eglot--overlay t) (overlay-put ov 'before-string @@ -4555,7 +4561,7 @@ If NOERROR, return predicate, else erroring function." (eglot-inlay-hints-mode -1))) (t (jit-lock-unregister #'eglot--update-hints) - (remove-overlays nil nil 'eglot--inlay-hint t)))) + (eglot--delete-overlays 'eglot--inlay-hint)))) ;;; Semantic tokens commit 394090486ccbf94f42661cac592ca5f29b5b1fe8 Author: Elías Gabriel Pérez Date: Wed Nov 12 12:29:16 2025 -0600 hideshow: Fix indicator mouse clicks on TTY * lisp/progmodes/hideshow.el (hs-make-overlay) (hs--make-indicators-overlays): Rework (bug#79585). diff --git a/lisp/progmodes/hideshow.el b/lisp/progmodes/hideshow.el index 57188fe6996..de933710dc1 100644 --- a/lisp/progmodes/hideshow.el +++ b/lisp/progmodes/hideshow.el @@ -710,17 +710,15 @@ to call with the newly initialized overlay." (io (if (eq 'block hs-isearch-open) ;; backward compatibility -- `block'<=>`code' 'code - hs-isearch-open)) - (map (make-sparse-keymap))) + hs-isearch-open))) (overlay-put ov 'invisible 'hs) - (define-key map (kbd "") #'hs-show-block) (overlay-put ov 'display (propertize (hs--get-ellipsis b e) 'mouse-face 'highlight 'help-echo "mouse-1: show hidden lines" - 'keymap map)) + 'keymap '(keymap (mouse-1 . hs-toggle-hiding)))) (overlay-put ov 'hs kind) (overlay-put ov 'hs-b-offset b-offset) (overlay-put ov 'hs-e-offset e-offset) @@ -798,7 +796,8 @@ point." "+" 'display `((margin left-margin) ,(or (plist-get (icon-elements face-or-icon) 'image) - (icon-string face-or-icon))) + (propertize (icon-string face-or-icon) + 'keymap hs-indicators-map))) 'face face-or-icon 'keymap hs-indicators-map)) ;; EOL string commit 4a8b7bd21797e64da869ad8139271d274747d2e9 Author: Sean Whitton Date: Thu Nov 13 18:13:58 2025 +0000 ; * lisp/vc/vc.el (vc-revert-revision): Fix line length. diff --git a/lisp/vc/vc.el b/lisp/vc/vc.el index ae0f6378d33..e3a0593c903 100644 --- a/lisp/vc/vc.el +++ b/lisp/vc/vc.el @@ -2400,9 +2400,9 @@ See also `vc-revert-revision'." "Make a commit undoing the effects of revision REV. When called interactively, prompts for REV. -This is like `vc-revert-or-delete-revision' except that it only ever makes a new -commit undoing the effects of REV, instead of considering VCS-specific -alternative mechanisms to undo the effects of REV. +This is like `vc-revert-or-delete-revision' except that it only ever +makes a new commit undoing the effects of REV, instead of considering +VCS-specific alternative mechanisms to undo the effects of REV. When called from Lisp, there are three calling conventions for the COMMENT and INITIAL-CONTENTS optional arguments: commit 5e819f76dcdbb6777f5647148d3d2c7a3ec22519 Author: Sean Whitton Date: Tue Oct 21 18:03:11 2025 +0100 read_key_sequence: Additional check for fix_current_buffer * src/keyboard.c (read_key_sequence): If we were interrupted while initializing the terminal, also check for a change of current buffer (bug#79513). diff --git a/src/keyboard.c b/src/keyboard.c index a9cbd107dde..c06c38f2e3f 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -11150,9 +11150,10 @@ read_key_sequence (Lisp_Object *keybuf, Lisp_Object prompt, if ((FIXNUMP (key) && XFIXNUM (key) == -2) /* wrong_kboard_jmpbuf */ /* When switching to a new tty (with a new keyboard), read_char returns the new buffer, rather than -2 - (Bug#5095). This is because `terminal-init-xterm' - calls read-char, which eats the wrong_kboard_jmpbuf - return. Any better way to fix this? -- cyd */ + (bug#5095, bug#37782, bug#79513). + This is because `terminal-init-xterm' calls + read-char, which eats the wrong_kboard_jmpbuf return. + Any better way to fix this? -- cyd */ || (interrupted_kboard != current_kboard)) { bool found = false; @@ -11205,8 +11206,14 @@ read_key_sequence (Lisp_Object *keybuf, Lisp_Object prompt, if (FIXNUMP (key) && XFIXNUM (key) != -2) { /* If interrupted while initializing terminal, we - need to replay the interrupting key. See - Bug#5095 and Bug#37782. */ + need to replay the interrupting key. + There may also have been a current buffer + change we would otherwise miss. + See bug#5095, bug#37782, bug#79513. */ + if (fix_current_buffer + && (XBUFFER (XWINDOW (selected_window)->contents) + != current_buffer)) + Fset_buffer (XWINDOW (selected_window)->contents); mock_input = 1; keybuf[0] = key; } commit 8e5c4c411d0773f40cda869112e791d59b7d132f Author: Sean Whitton Date: Thu Nov 13 17:15:18 2025 +0000 New dummy defmacro for bind* * lisp/emacs-lisp/cond-star.el (bind*): New macro, like match* and bind-and*. diff --git a/lisp/emacs-lisp/cond-star.el b/lisp/emacs-lisp/cond-star.el index 7d042116578..a8b144ea306 100644 --- a/lisp/emacs-lisp/cond-star.el +++ b/lisp/emacs-lisp/cond-star.el @@ -158,6 +158,13 @@ ATOM (meaning any other kind of non-list not described above) ;; FIXME: `byte-compile-warn-x' is not necessarily defined here. (byte-compile-warn-x pattern "`match*' used other than as a `cond*' condition")) +(defmacro bind* (&rest bindings) + "This macro evaluates BINDINGS like `let*'. +It is not really a Lisp function, and it is meaningful +only in the CONDITION of a `cond*' clause." + ;; FIXME: `byte-compile-warn-x' is not necessarily defined here. + (byte-compile-warn-x bindings "`bind' used other than as a `cond*' condition")) + (defmacro bind-and* (&rest bindings) "This macro evaluates BINDINGS like `if-let*'. It is not really a Lisp function, and it is meaningful commit 6b7512ed6146e4279e5ea76f35ae2aff9070ba0b Author: Sean Whitton Date: Thu Nov 13 17:09:49 2025 +0000 ; * lisp/vc/vc-dispatcher.el (vc-do-async-command): Limit precision. diff --git a/lisp/vc/vc-dispatcher.el b/lisp/vc/vc-dispatcher.el index bce23529b47..ebbfc3223a2 100644 --- a/lisp/vc/vc-dispatcher.el +++ b/lisp/vc/vc-dispatcher.el @@ -559,7 +559,7 @@ Display the buffer in some window, but don't select it." (goto-char (process-mark proc)) (let ((inhibit-read-only t)) (insert - (format "Finished in %f seconds\n" + (format "Finished in %.2f seconds\n" (time-to-seconds (time-since start-time)))) (set-marker (process-mark proc) commit 2e8cc345d5bdc96d0b9fd3cbf7fc8f502604f96e Author: Sean Whitton Date: Wed Nov 5 16:38:48 2025 +0000 VC revert commands: Facilities to entirely delete revisions * lisp/vc/vc.el (vc-revision-revert, vc-revision-cherry-pick): Rename to ... (vc-revert-or-delete-revision, vc-cherry-pick): ... these (bug#79408). All uses changed. * lisp/vc/log-view.el (log-view-revision-revert) (log-view-revision-cherry-pick): Rename to ... (log-view-revert-or-delete-revisions, log-view-cherry-pick): ... these. All uses changed. * lisp/vc/log-view.el (log-view-revert-or-delete-revisions): * lisp/vc/vc.el (vc-revert-or-delete-revision): New INTERACTIVE and DELETE parameters, and prefix argument. Offer to entirely delete REV in certain circumstances (bug#79408). * lisp/vc/log-view.el (log-view--pick-or-revert): * lisp/vc/vc.el (vc--pick-or-revert): New INTERACTIVE and DELETE parameters. All uses changes. * lisp/vc/log-view.el (log-view-revert-revisions) (log-view-delete-revisions): * lisp/vc/vc.el (vc-revert-revision, vc-delete-revision): New commands (bug#79408). * doc/emacs/maintaining.texi (VC Change Log, VC Undo) (Copying Between Branches): * etc/NEWS: Document the changes. diff --git a/doc/emacs/maintaining.texi b/doc/emacs/maintaining.texi index 197c26410e8..147857aa836 100644 --- a/doc/emacs/maintaining.texi +++ b/doc/emacs/maintaining.texi @@ -1268,11 +1268,11 @@ Unmark all marked entries (@code{log-view-unmark-all-entries}). @item C Copy changes to a currently checked out branch; either the changes from the revision at point, or the changes from all marked revisions -(@code{log-view-revision-cherry-pick}). +(@code{log-view-cherry-pick}). @item R Undo the effects of old revisions; either the revision at point, or all -marked revisions (@code{log-view-revision-revert}). +marked revisions (@code{log-view-revert-or-delete-revisions}). @end table @vindex vc-log-show-limit @@ -1317,8 +1317,14 @@ also prompt for a specific VCS shell command to run for this purpose. Revert the work file(s) in the current VC fileset to the last revision (@code{vc-revert}). -@item M-x vc-revision-revert +@item M-x vc-revert-or-delete-revision Undo the effects of an older commit. + +@item M-x vc-revert-revision +Make a new commit which undoes the changes made by an older one. + +@item M-x vc-delete-revision +Delete an unpushed commit from the revision history. @end table @kindex C-x v u @@ -1342,20 +1348,37 @@ unlocked; you must lock again to resume editing. You can also use @kbd{C-x v u} to unlock a file if you lock it and then decide not to change it. -@findex vc-revision-revert +@findex vc-revert-or-delete-revision @cindex reverting commits To discard changes that have already been committed, by yourself or -someone else, you can use @w{@kbd{M-x vc-revision-revert}}. This is -called @dfn{reverting} a commit. The command prompts for a revision to -revert, and then the VC backend reverts it. Most backends implement -this by making a new commit which undoes the changes made by the -revision. +someone else, you can use @w{@kbd{M-x vc-revert-or-delete-revision}}. +This is called @dfn{reverting} a commit. The command prompts for a +revision to revert, and then the VC backend reverts it. - An alternative way to access this functionality is to the -@code{log-view-revision-revert} command, bound to @kbd{R} in Log View -mode buffers (@pxref{VC Change Log}). Compared to using @w{@kbd{M-x -vc-revision revert}} directly, this can make it easier to be sure you -are reverting the revision you intend. + Most backends implement this by making a new commit which undoes the +changes made by the revision. For a distributed VCS, if the commit is +one that you made and have not yet pushed, Emacs will offer to delete it +entirely, instead. With a prefix argument, this command will only try +to entirely delete the revision, failing if it has already been pushed. + + An alternative way to access this functionality is the +@code{log-view-revert-or-delete-revisions} command, bound to @kbd{R} in +Log View mode buffers (@pxref{VC Change Log}). Compared to using +@w{@kbd{M-x vc-revision revert}} directly, this can make it easier to be +sure you are reverting the revision you intend. + +@findex vc-revert-revision +@findex vc-delete-revision + More specific, specialized commands are @w{@kbd{M-x +vc-revert-revision}} and @w{@kbd{M-x vc-delete-revision}}. The first of +these prompts for a revision and then always makes a new commit which +undoes the changes made by that revision, regardless of the VCS in use +and whether or not the revision is pushed. The second command prompts +for a revision and then always deletes it, though it will stop if the +commit has been pushed. Usually @code{vc-revert-or-delete-revision} is +sufficient, but @code{vc-revert-revision} and @code{vc-delete-revision} +can sometimes be useful for constructing particular version control +histories. @node VC Ignore @subsection Ignore Version Control Files @@ -1889,7 +1912,7 @@ different revision with @kbd{C-u C-x v v}. @subsubsection Copying Changes Made By Revisions Between Branches @table @kbd -@item M-x vc-revision-cherry-pick +@item M-x vc-cherry-pick Copy a single revision to branch checked out in this working tree. @end table @@ -1910,25 +1933,25 @@ can then cherry-pick that revision onto the feature-frozen branch in order to fix the bug there, too. This is called @dfn{backporting} the revision, or backporting the fix. -@findex vc-revision-cherry-pick - You can use the command @kbd{M-x vc-revision-cherry-pick} to -cherry-pick revisions. It prompts for a revision to cherry-pick. It -then pops up a buffer for you to edit the log message for the new -revision. Normally the VC backend generates a log message including a -reference to the revision you want to copy, so that the copy can be -traced. If you wish, you can delete this reference before typing -@kbd{C-c C-c} to conclude the cherry-pick. +@findex vc-cherry-pick + You can use the command @kbd{M-x vc-cherry-pick} to cherry-pick +revisions. It prompts for a revision to cherry-pick. It then pops up a +buffer for you to edit the log message for the new revision. Normally +the VC backend generates a log message including a reference to the +revision you want to copy, so that the copy can be traced. If you wish, +you can delete this reference before typing @kbd{C-c C-c} to conclude +the cherry-pick. Alternatively you can invoke the command with a prefix argument, -i.e. @w{@kbd{C-u M-x vc-revision-cherry-pick}}. In this case the log -message from the source revision is used unmodified, and the cherry-pick -happens immediately, without popping up a buffer for log message edits. +i.e. @w{@kbd{C-u M-x vc-cherry-pick}}. In this case the log message +from the source revision is used unmodified, and the cherry-pick happens +immediately, without popping up a buffer for log message edits. An alternative way to access this functionality is the -@code{log-view-revision-cherry-pick} command, bound to @kbd{C} in Log -View mode buffers (@pxref{VC Change Log}). Compared to using -@w{@kbd{M-x vc-revision-cherry-pick}} directly, this can make it easier -to be sure you are cherry-picking the revision you intend. +@code{log-view-cherry-pick} command, bound to @kbd{C} in Log View mode +buffers (@pxref{VC Change Log}). Compared to using @w{@kbd{M-x +vc-cherry-pick}} directly, this can make it easier to be sure you are +cherry-picking the revision you intend. @ifnottex @include vc1-xtra.texi diff --git a/etc/NEWS b/etc/NEWS index 1a0589fd23d..35c536d6ee8 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -2358,11 +2358,12 @@ the contents of those files. +++ *** New commands to cherry-pick and revert revisions. -The commands 'vc-revision-cherry-pick' and 'vc-revision-revert' let you -copy revisions between branches, and revert revisions. +The commands 'vc-cherry-pick', 'vc-revert-or-delete-revision', +'vc-revert-revision' and 'vc-delete-revision' let you copy revisions +between branches, revert and delete revisions. From Log View buffers, you can use 'C' to cherry-pick the revision at -point or all marked revisions, and 'R' to revert the revision at point -or all marked revisions. +point or all marked revisions, and 'R' to undo the revision at point or +all marked revisions. *** New command 'log-edit-done-strip-cvs-lines'. This command strips all lines beginning with "CVS:" from the buffer. diff --git a/lisp/vc/log-view.el b/lisp/vc/log-view.el index 0e2d506a23a..0419b2c077a 100644 --- a/lisp/vc/log-view.el +++ b/lisp/vc/log-view.el @@ -141,8 +141,8 @@ "w" #'log-view-copy-revision-as-kill "TAB" #'log-view-msg-next "" #'log-view-msg-prev - "C" #'log-view-revision-cherry-pick - "R" #'log-view-revision-revert) + "C" #'log-view-cherry-pick + "R" #'log-view-revert-or-delete-revisions) (easy-menu-define log-view-mode-menu log-view-mode-map "Log-View Display Menu." @@ -165,9 +165,9 @@ ["Toggle Details at Point" log-view-toggle-entry-display :active log-view-expanded-log-entry-function] "-----" - ["Cherry-Pick Revision(s)" log-view-revision-cherry-pick + ["Cherry-Pick Revision(s)" log-view-cherry-pick :help "Copy changes from revision(s) to a branch"] - ["Revert Revision(s)" log-view-revision-revert + ["Revert Revision(s)" log-view-revert-or-delete-revisions :help "Undo the effects of old revision(s)"] "-----" ["Next Log Entry" log-view-msg-next @@ -703,7 +703,8 @@ If called interactively, annotate the version at point." (defvar vc-log-short-style) (declare-function vc-print-log-internal "vc") -(defun log-view--pick-or-revert (directory no-comment reverse) +(defun log-view--pick-or-revert + (directory no-comment reverse interactive delete) "Copy changes from revision at point or all marked revisions. DIRECTORY is the destination, the root of the target working tree. NO-COMMENT non-nil means use the log messages of the revisions @@ -711,12 +712,15 @@ unmodified, instead of using the backend's default cherry-pick comment for that revision. NO-COMMENT non-nil with zero or one revisions marked also means don't prompt to edit the log message. -REVERSE non-nil means to make commit(s) undoing the effects of the -revisions, instead." +REVERSE non-nil means to undo the effects of the revisions, instead. +INTERACTIVE and DELETE are passed on to `vc--pick-or-revert', except +additionally if INTERACTIVE is non-nil and `vc--pick-or-revert' returns +`deleted', pass `no-confirm' to subsequent calls to that function." (let ((default-directory directory) - (marked (log-view-get-marked))) + (marked (log-view-get-marked)) + (buf (current-buffer))) (if (length> marked 1) - (progn + (let ((deleted 0)) (save-excursion (dolist (rev (if reverse (reverse marked) marked)) ;; Unmark each revision *before* copying it. @@ -724,38 +728,48 @@ revisions, instead." ;; fails, after resolving that conflict and committing the ;; cherry-pick, the right revisions will be marked to ;; resume the original multiple cherry-pick operation. + ;; FIXME: This doesn't work so long as the backend + ;; functions just give up completely if there is a + ;; conflict (which behavior is also a FIXME). Then in + ;; fact the user has to mark the revision again. (log-view-goto-rev rev) (log-view-unmark-entry 1) - (vc--pick-or-revert rev - reverse - (if no-comment - (vc-call-backend log-view-vc-backend - 'get-change-comment - nil rev) - t) - nil - log-view-vc-backend))) - (when (vc-find-backend-function log-view-vc-backend - 'modify-change-comment) - (let (vc-log-short-style) - (vc-print-log-internal log-view-vc-backend - (list default-directory) - nil nil (length marked))) - (setq-local vc-log-short-style nil ; For \\`g'. - vc-parent-buffer-name nil) - (message (substitute-command-keys "Use \ -\\[log-view-modify-change-comment] to modify any of these messages")))) + (cl-case + (vc--pick-or-revert rev reverse interactive delete + (if no-comment + (vc-call-backend log-view-vc-backend + 'get-change-comment + nil rev) + t) + nil + log-view-vc-backend) + (deleted (incf deleted) + (when interactive + (setq interactive 'no-confirm)))))) + (let ((new-commits (- (length marked) deleted))) + (when (and (plusp new-commits) + (vc-find-backend-function log-view-vc-backend + 'modify-change-comment)) + (let (vc-log-short-style) + (vc-print-log-internal log-view-vc-backend + (list default-directory) + nil nil new-commits)) + (setq-local vc-log-short-style nil ; For \\`g'. + vc-parent-buffer-name nil) + (message (substitute-command-keys "Use \ +\\[log-view-modify-change-comment] to modify any of these messages"))))) (let ((rev (or (car marked) (log-view-current-tag)))) - (vc--pick-or-revert rev - reverse + (vc--pick-or-revert rev reverse interactive delete (and no-comment (vc-call-backend log-view-vc-backend 'get-change-comment nil rev)) nil - log-view-vc-backend))))) + log-view-vc-backend))) + (when (eq (current-buffer) buf) + (revert-buffer)))) -(defun log-view-revision-cherry-pick (directory &optional no-comment) +(defun log-view-cherry-pick (directory &optional no-comment) "Copy changes from revision at point to current branch. If there are marked revisions, use those instead of the revision at point. @@ -774,31 +788,90 @@ traced. With optional argument NO-COMMENT non-nil (interactively, with a prefix argument), use the log messages from the source revisions unmodified. -See also `vc-revision-cherry-pick'." +See also `vc-cherry-pick'." (interactive (list (vc--prompt-other-working-tree log-view-vc-backend "Cherry-pick to working tree" 'allow-empty) current-prefix-arg)) - (log-view--pick-or-revert directory no-comment nil)) + (log-view--pick-or-revert directory no-comment nil nil nil)) -(defun log-view-revision-revert (directory) +(defun log-view-revert-or-delete-revisions + (directory &optional interactive delete) "Undo the effects of the revision at point. When revisions are marked, undo the effects of each of them. When called interactively, prompts for the target working tree in which to revert; the current working tree is the default choice. When called from Lisp, DIRECTORY is the root of the target working tree. +When called interactively (or with optional argument INTERACTIVE +non-nil), then if the underlying VCS is distributed, offer to entirely +delete revisions that have not been pushed. +This is instead of creating new commits undoing their effects. + +With a prefix argument (or with optional argument DELETE non-nil), +always delete revisions and never create new commits. +In this case INTERACTIVE is ignored. +This works only for unpublished commits, unless you have customized +`vc-allow-rewriting-published-history' to a non-nil value. + +When reverting a single revision, prompts for editing the log message +for the new commit. +When reverting multiple revisions, never prompts to edit log messages. + +See also `vc-revert-or-delete-revision'." + (interactive (list (vc--prompt-other-working-tree + (vc-responsible-backend default-directory) + (if current-prefix-arg "Delete in working tree" + "Revert in working tree") + 'allow-empty) + t current-prefix-arg)) + (log-view--pick-or-revert directory nil t interactive delete)) + +;; These are left unbound by default. A user who doesn't like the DWIM +;; behavior of `log-view-revert-or-delete-revisions' can unbind that and +;; bind these two commands instead. + +(defun log-view-revert-revisions (directory) + "Make a commit undoing the effects of the revision at point. +When revisions are marked, make such a commit for each of them. +When called interactively, prompts for the target working tree in which +to make new commits; the current working tree is the default choice. +When called from Lisp, DIRECTORY is the root of the target working tree. + +This is like `log-view-revert-or-delete-revisions' except that it only +ever makes new commits undoing the effects of revisions, instead of +considering VCS-specific alternative mechanisms to undo the effects of +revisions. + When reverting a single revision, prompts for editing the log message for the new commit. When reverting multiple revisions, never prompts to edit log messages. -See also `vc-revision-revert'." +See also `vc-revert-revision'." (interactive (list (vc--prompt-other-working-tree (vc-responsible-backend default-directory) "Revert in working tree" 'allow-empty))) - (log-view--pick-or-revert directory nil t)) + (log-view--pick-or-revert directory nil t nil 'never)) + +(defun log-view-delete-revisions (directory) + "Delete the revision at point from the revision history. +When revisions are marked, delete all of them. +When called interactively, prompts for the target working tree in which +to delete revisions; the current working tree is the default choice. +When called from Lisp, DIRECTORY is the root of the target working tree. + +This works only for unpublished commits, unless you have customized +`vc-allow-rewriting-published-history' to a non-nil value. + +This is the same as `log-view-revert-or-delete-revisions' invoked +interactively with a prefix argument. See also `vc-delete-revision'." + (interactive (list (vc--prompt-other-working-tree + (vc-responsible-backend default-directory) + "Delete in working tree" + 'allow-empty))) + (log-view--pick-or-revert directory nil t nil t)) ;;;; ;;;; diff diff --git a/lisp/vc/vc-hooks.el b/lisp/vc/vc-hooks.el index d3c98311358..32b410d5c3b 100644 --- a/lisp/vc/vc-hooks.el +++ b/lisp/vc/vc-hooks.el @@ -1053,11 +1053,11 @@ other commands receive global bindings where they had none before." (defvar vc-menu-map (let ((map (make-sparse-keymap "Version Control"))) - (define-key map [vc-revision-revert] - '(menu-item "Revert Revision" vc-revision-revert + (define-key map [vc-revert-or-delete-revision] + '(menu-item "Revert Revision" vc-revert-or-delete-revision :help "Undo the effects of a revision")) - (define-key map [vc-revision-cherry-pick] - '(menu-item "Cherry-Pick Revision" vc-revision-cherry-pick + (define-key map [vc-cherry-pick] + '(menu-item "Cherry-Pick Revision" vc-cherry-pick :help "Copy the changes from a single revision to this branch")) (define-key map [separator1] menu-bar-separator) (define-key map [vc-retrieve-tag] diff --git a/lisp/vc/vc.el b/lisp/vc/vc.el index 4a505b2de19..ae0f6378d33 100644 --- a/lisp/vc/vc.el +++ b/lisp/vc/vc.el @@ -2166,22 +2166,121 @@ have changed; continue with old fileset?" (current-buffer)))) (declare-function diff-buffer-file-names "diff-mode") (declare-function diff-reverse-direction "diff-mode") -(defun vc--pick-or-revert (rev reverse comment initial-contents backend) +(defun vc--pick-or-revert + (rev reverse interactive delete comment initial-contents backend) "Copy a single revision REV to branch checked out in this working tree. -REVERSE means to undo the effects of REV, instead. + +REVERSE non-nil means to undo the effects of REV, instead. +This is affected by whether the VCS is centralized or distributed and +the INTERACTIVE and DELETE arguments, as follows: +- For a centralized VCS for which Emacs knows how to do true undos, then + unless DELETE is the special value `never', do a true undo of REV. + This function supports creating new commits undoing the effects of REV + for even a centralized VCS with true undos by passing `never' as + DELETE (as `vc-revert-revision' does). + For centralized VCS, INTERACTIVE is ignored. +- For a distributed VCS, when INTERACTIVE is non-nil, DELETE is nil, and + REV has not yet been pushed, offer to delete REV entirely instead of + creating a new commit undoing its EFFECTS. + If INTERACTIVE is `no-confirm', don't prompt to confirm the deletion. +- For a distributed VCS, when DELETE is non-nil (but not `never'), only + consider deleting REV, never create a new commit, but subject to + `vc-allow-rewriting-published-history'. + In this case INTERACTIVE is ignored. +(This complex calling convention makes for simple usage of this +workhorse function from the frontend VC commands that provide access to +all this functionality.) + COMMENT is a comment string; if omitted, a buffer is popped up to accept a comment. If INITIAL-CONTENTS is non-nil, then COMMENT is used as the initial contents of the log entry buffer. If COMMENT is t then use BACKEND's default cherry-pick comment for REV without prompting. -BACKEND is the VC backend to use." - (let* ((backend (or backend (vc-responsible-backend default-directory))) - ;; `vc-*-prepare-patch' will always give us a patch with file - ;; names relative to the VC root, so switch to there now. - ;; In particular this is needed for `diff-buffer-file-names' to - ;; work properly. - (default-directory (vc-call-backend backend 'root default-directory)) - (patch (vc-call-backend backend 'prepare-patch rev)) - files whole-patch-string diff-patch-string) +BACKEND is the VC backend to use. + +Return `deleted' if we actually undid/deleted a commit. +Any other return value means we called `vc-start-logentry'." + (cond* + ((bind* (backend (or backend + (vc-responsible-backend default-directory))))) + ((and reverse (not (eq delete 'never)) + (null (vc-find-backend-function backend + 'revision-published-p)) + (vc-find-backend-function backend 'delete-revision)) + ;; Centralized VCS implementing `delete-revision'. + (vc-call-backend backend 'delete-revision rev) + 'deleted) + ((and reverse interactive (not delete) + ;; Distributed VCS for which we can do deletions. + (vc-find-backend-function backend 'revision-published-p) + (vc-find-backend-function backend 'delete-revision) + ;; REV is safe to delete. + (not (vc-call-backend backend 'revision-published-p rev))) + ;; Require confirmation, because the commit is unpublished, and so + ;; this might be the only copy of the work in REV. Don't fall back + ;; to making a new commit undoing REV's changes because we don't + ;; know the user wants that just because they said "no" to our + ;; question here, and we want to avoid two y/n prompts in a row, + ;; which is probably a less good UI than this. + (cond ((or (eq interactive 'no-confirm) + (yes-or-no-p + (format "Permanently delete %s from the revision history?" + rev))) + (vc-call-backend backend 'delete-revision rev) + 'deleted) + ((derived-mode-p 'log-view-mode) + (user-error (substitute-command-keys "\ +Use \\[log-view-revert-revisions] to create new commits \ +undoing changes made by revision(s)"))) + (t + (user-error (substitute-command-keys "\ +Use \\[vc-revert-revision] to create a new commit undoing %s's changes") + rev)))) + ((and reverse delete (not (eq delete 'never)) + ;; Distributed VCS for which we can do deletions. + (vc-find-backend-function backend 'revision-published-p) + (vc-find-backend-function backend 'delete-revision)) + ;; Even though the user has explicitly requested deletion with a + ;; prefix argument / invoking `vc-delete-revision' / invoking + ;; `log-view-delete-revisions', by default we still confirm such a + ;; destructive operation. + ;; However, we want to avoid prompting twice in the case that the + ;; user has set `vc-allow-rewriting-published-history' to `ask', and + ;; we should avoid prompting at all in the case that + ;; `vc-allow-rewriting-published-history' is another non-nil value. + ;; These requirements lead to the nested `cond*' form here. + (cond* + ((and vc-allow-rewriting-published-history + (not (eq vc-allow-rewriting-published-history 'ask))) + (vc-call-backend backend 'delete-revision rev) + 'deleted) + ((bind* (published (vc-call-backend backend 'revision-published-p rev)))) + ((and published + (eq vc-allow-rewriting-published-history 'ask) + (yes-or-no-p + (format "Revision %s appears published; allow rewriting history?" + rev))) + (vc-call-backend backend 'delete-revision rev) + 'deleted) + (published + (user-error "Will not rewrite likely-public history")) + ((yes-or-no-p + (format "Permanently delete %s from the revision history?" + rev)) + (vc-call-backend backend 'delete-revision rev) + 'deleted) + (t + (user-error "Aborted")))) + ;; If we get this far we give up on `delete-revision', i.e. we fall + ;; back to creating a commit undoing the effects of REV. + ;; + ;; `vc-*-prepare-patch' will always give us a patch with file names + ;; relative to the VC root, so switch to there now. In particular + ;; this is needed for `diff-buffer-file-names' to work properly. + ((bind* (default-directory (vc-call-backend backend 'root + default-directory)) + (patch (vc-call-backend backend 'prepare-patch rev)) + files whole-patch-string diff-patch-string)) + (t (with-current-buffer (plist-get patch :buffer) (diff-mode) (with-restriction @@ -2218,14 +2317,14 @@ BACKEND is the VC backend to use." whole-patch-string comment)) nil backend - diff-patch-string))) + diff-patch-string)))) -;; No bindings in `vc-prefix-map' for the following two commands because -;; we expect users will usually use `log-view-revision-cherry-pick' and -;; `log-view-revision-revert', which do have bindings. +;; No bindings in `vc-prefix-map' for the following three items because +;; we expect users will usually use `log-view-cherry-pick' and +;; `log-view-revert-or-delete-revisions', which do have bindings. ;;;###autoload -(defun vc-revision-cherry-pick (rev &optional comment initial-contents backend) +(defun vc-cherry-pick (rev &optional comment initial-contents backend) "Copy the changes from a single revision REV to the current branch. When called interactively, prompts for REV. Typically REV is a revision from another branch, where that branch is @@ -2256,13 +2355,55 @@ Optional argument BACKEND is the VC backend to use." nil rev)) nil backend))) - (vc--pick-or-revert rev nil comment initial-contents backend)) + (vc--pick-or-revert rev nil nil nil comment initial-contents backend)) ;;;###autoload -(defun vc-revision-revert (rev &optional comment initial-contents backend) +(defun vc-revert-or-delete-revision + (rev &optional interactive delete comment initial-contents backend) "Undo the effects of revision REV. When called interactively, prompts for REV. +When called interactively (or with optional argument INTERACTIVE +non-nil), then if the underlying VCS is distributed and REV has not been +pushed, offer to entirely delete REV. +This is instead of creating a new commit undoing the effects of REV. + +With a prefix argument (or with optional argument DELETE non-nil), +only consider deleting REV, never create a new commit. +In this case INTERACTIVE is ignored. +This works only if REV has not been pushed, unless you have customized +`vc-allow-rewriting-published-history' to a non-nil value. + +When called from Lisp, there are three calling conventions for the +COMMENT and INITIAL-CONTENTS optional arguments: +- COMMENT a string, INITIAL-CONTENTS nil means use that comment string + without prompting the user to edit it. +- COMMENT a string, INITIAL-CONTENTS non-nil means use that comment + string as the initial contents of the log entry buffer but stop for + editing. +- COMMENT t means use BACKEND's default revert comment for REV without + prompting for editing, and ignore INITIAL-CONTENTS. + +Optional argument BACKEND is the VC backend to use. + +See also `vc-revert-revision'." + (interactive (list (vc-read-revision (if current-prefix-arg + "Revision to delete: " + "Revision to revert: ")) + t current-prefix-arg)) + (vc--pick-or-revert rev t interactive delete + comment initial-contents backend)) + +;;;###autoload +(defun vc-revert-revision + (rev &optional comment initial-contents backend) + "Make a commit undoing the effects of revision REV. +When called interactively, prompts for REV. + +This is like `vc-revert-or-delete-revision' except that it only ever makes a new +commit undoing the effects of REV, instead of considering VCS-specific +alternative mechanisms to undo the effects of REV. + When called from Lisp, there are three calling conventions for the COMMENT and INITIAL-CONTENTS optional arguments: - COMMENT a string, INITIAL-CONTENTS nil means use that comment string @@ -2275,7 +2416,18 @@ COMMENT and INITIAL-CONTENTS optional arguments: Optional argument BACKEND is the VC backend to use." (interactive (list (vc-read-revision "Revision to revert: "))) - (vc--pick-or-revert rev t comment initial-contents backend)) + (vc--pick-or-revert rev t nil 'never comment initial-contents backend)) + +;;;###autoload +(defun vc-delete-revision (rev &optional backend) + "Delete revision REV from the revision history. +This works only if REV has not been pushed, unless you have customized +`vc-allow-rewriting-published-history' to a non-nil value. + +This is the same as `vc-revert-or-delete-revision' invoked interactively +with a prefix argument." + (interactive (list (vc-read-revision "Revision to delete: "))) + (vc--pick-or-revert rev t nil t nil nil backend)) (declare-function diff-bounds-of-hunk "diff-mode") commit 5dfcba699e1a59be97e22a6f33fa5dfe59a4db9f Author: Sean Whitton Date: Thu Nov 13 16:22:20 2025 +0000 Revised bindings for diff-revert-and-kill-hunk * lisp/vc/diff-mode.el (diff-mode-shared-map): Bind diff-revert-and-kill-hunk to 'u'. (diff-mode-map): Replace binding for diff-revert-and-kill-hunk with 'C-c M-u'. * doc/emacs/files.texi (Diff Mode): * etc/NEWS: Document the change. diff --git a/doc/emacs/files.texi b/doc/emacs/files.texi index 28234fbec9c..f677804b6a4 100644 --- a/doc/emacs/files.texi +++ b/doc/emacs/files.texi @@ -1825,7 +1825,7 @@ version. If @code{diff-jump-to-old-file} is non-@code{nil}, apply the hunk to the ``old'' version of the file instead. @findex diff-revert-and-kill-hunk -@item C-c M-r +@item C-c M-u Revert this hunk, and then remove the hunk from the diffs (@code{diff-revert-and-kill-hunk}). Save the buffer visiting the target file. diff --git a/etc/NEWS b/etc/NEWS index 7168cc5bb92..1a0589fd23d 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -2003,7 +2003,7 @@ to the original text. If the selected range extends a hunk, the command attempts to look up and copy the text in between the hunks. +++ -*** New command 'diff-revert-and-kill-hunk' bound to 'C-c M-r'. +*** New command 'diff-revert-and-kill-hunk' bound to 'u' and 'C-c M-u'. This command reverts the hunk at point (i.e., applies the reverse of the hunk), and then removes the hunk from the diffs. This is useful to undo or revert changes, committed and uncommitted, when diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el index f3810f7754d..7dd90ee5b75 100644 --- a/lisp/vc/diff-mode.el +++ b/lisp/vc/diff-mode.el @@ -221,7 +221,14 @@ and with a `diff-minor-mode-prefix' prefix in `diff-minor-mode'." "A" #'diff-ediff-patch "r" #'diff-restrict-view "R" #'diff-reverse-direction - " " #'diff-undo) + " " #'diff-undo + + ;; The foregoing commands don't affect buffers beyond this one. + ;; The following command is the only one that has a single-letter + ;; binding and which affects buffers beyond this one. + ;; However, the following command asks for confirmation by default, + ;; so that seems okay. --spwhitton + "u" #'diff-revert-and-kill-hunk) (defvar-keymap diff-mode-map :doc "Keymap for `diff-mode'. See also `diff-mode-shared-map'." @@ -238,7 +245,7 @@ and with a `diff-minor-mode-prefix' prefix in `diff-minor-mode'." "C-x 4 A" #'diff-add-change-log-entries-other-window ;; Misc operations. "C-c C-a" #'diff-apply-hunk - "C-c M-r" #'diff-revert-and-kill-hunk + "C-c M-u" #'diff-revert-and-kill-hunk "C-c C-m a" #'diff-apply-buffer "C-c C-m n" #'diff-delete-other-hunks "C-c C-e" #'diff-ediff-patch commit a0c69d3d44f073fbd8f1ffb2a5a8641d37f80039 Author: Robert Pluim Date: Thu Nov 13 15:12:20 2025 +0100 Support unqualified keycap emoji * admin/unidata/emoji-zwj.awk: Add U+20E3 as a trigger codepoint so that U+0023 U+20E3 and similar are displayed using the emoji font. diff --git a/admin/unidata/emoji-zwj.awk b/admin/unidata/emoji-zwj.awk index 4264f944033..5eccb7af4a9 100644 --- a/admin/unidata/emoji-zwj.awk +++ b/admin/unidata/emoji-zwj.awk @@ -67,7 +67,7 @@ END { # The following codepoints are not emoji, but they are part of # emoji sequences. We have code in font.c:font_range that will - # try to display them with the emoji font anyway. + # try to display those sequences with the emoji font anyway. trigger_codepoints[1] = "261D" trigger_codepoints[2] = "26F9" @@ -82,6 +82,7 @@ END { trigger_codepoints[11] = "1F574" trigger_codepoints[12] = "1F575" trigger_codepoints[13] = "1F590" + trigger_codepoints[14] = "20E3" print "(setq auto-composition-emoji-eligible-codepoints" print "'(" commit ff2383a00fcc478b7d03d3deb521367b5bcd04ad Author: Stefan Monnier Date: Thu Nov 13 09:17:03 2025 -0500 * lisp/replace.el (replace--push-stack, perform-replace): Use markers Don't use integers to store match data for "long term". Otherwise, buffer edits that occur between two steps (e.g. via process filters of post-command-hooks) makes it invalid, leading to very confusing behavior, e.g. after `C-M-% ... y` the right (next) match is highlighted yet if a process-filter or post-command-hook runs, the next `y` may replace something else than what was highlighted. diff --git a/lisp/replace.el b/lisp/replace.el index 107c5eadeb2..6635cc6afe4 100644 --- a/lisp/replace.el +++ b/lisp/replace.el @@ -2782,19 +2782,19 @@ to a regexp that is actually used for the search.") (defmacro replace--push-stack (replaced search-str next-replace stack) (declare (indent 0) (debug (form form form gv-place))) `(push (list (point) ,replaced -;;; If the replacement has already happened, all we need is the -;;; current match start and end. We could get this with a trivial -;;; match like -;;; (save-excursion (goto-char (match-beginning 0)) -;;; (search-forward (match-string 0)) -;;; (match-data t)) -;;; if we really wanted to avoid manually constructing match data. -;;; Adding current-buffer is necessary so that match-data calls can -;;; return markers which are appropriate for editing. + ;; If the replacement has already happened, all we need is the + ;; current match start and end. We could get this with a trivial + ;; match like + ;; (save-excursion (goto-char (match-beginning 0)) + ;; (search-forward (match-string 0)) + ;; (match-data t)) + ;; if we really wanted to avoid manually constructing match data. + ;; Adding current-buffer is necessary so that match-data calls + ;; can return markers which are appropriate for editing. (if ,replaced (list (match-beginning 0) (match-end 0) (current-buffer)) - (match-data t)) + (match-data)) ,search-str ,next-replace) ,stack)) @@ -2962,7 +2962,7 @@ characters." (nth 0 match-again) (nth 1 match-again))) (replace-match-data - t real-match-data match-again)) + nil real-match-data match-again)) ;; MATCH-AGAIN non-nil means accept an ;; adjacent match. (match-again @@ -2972,7 +2972,7 @@ characters." case-fold-search backward) ;; For speed, use only integers and ;; reuse the list used last time. - (replace-match-data t real-match-data))) + (replace-match-data nil real-match-data))) ((and (if backward (> (1- (point)) (point-min)) (< (1+ (point)) (point-max))) @@ -2990,7 +2990,7 @@ characters." regexp-flag delimited-flag case-fold-search backward) (replace-match-data - t real-match-data) + nil real-match-data) (goto-char opoint) nil)))))) @@ -3082,7 +3082,7 @@ characters." (save-excursion (goto-char (nth 0 real-match-data)) (looking-at search-string) - (match-data t real-match-data)))) + (match-data nil real-match-data)))) ;; Matched string and next-replacement-replaced ;; stored in stack. (setq search-string-replaced (buffer-substring-no-properties @@ -3146,7 +3146,7 @@ characters." (setq replaced (nth 1 elt) real-match-data (replace-match-data - t real-match-data + nil real-match-data (nth 2 elt)))) (message "No previous match") (ding 'no-terminate) @@ -3194,7 +3194,7 @@ characters." (goto-char (match-beginning 0)) ;; We must quote the string (Bug#37073) (looking-at (regexp-quote search-string)) - (match-data t (nth 2 elt))) + (match-data nil (nth 2 elt))) noedit (replace-match-maybe-edit last-replacement nocasify literal @@ -3203,10 +3203,11 @@ characters." real-match-data (save-excursion (goto-char (match-beginning 0)) - (if regexp-flag - (looking-at last-replacement) - (looking-at (regexp-quote last-replacement))) - (match-data t (nth 2 elt)))) + (looking-at (if regexp-flag + last-replacement + (regexp-quote + last-replacement))) + (match-data nil (nth 2 elt)))) (when regexp-flag (setq next-replacement (nth 4 elt))) ;; Set replaced nil to keep in loop @@ -3250,7 +3251,7 @@ characters." noedit real-match-data backward) replace-count (1+ replace-count) real-match-data (replace-match-data - t real-match-data) + nil real-match-data) replaced t last-was-act-and-show t) (replace--push-stack replaced commit 5d9eab261fd9706be35cd799494eae4e1de81590 Author: Stefan Monnier Date: Thu Nov 13 09:06:37 2025 -0500 etc/NEWS.28: Mention `read-symbol-shorthands` to improve `C-h v` info diff --git a/etc/NEWS.28 b/etc/NEWS.28 index 924003d8ed6..cf61e762c88 100644 --- a/etc/NEWS.28 +++ b/etc/NEWS.28 @@ -3472,8 +3472,10 @@ symbolic form found in Lisp source that "abbreviates" a symbol's print name. Among other applications, this feature can be used to avoid name clashes and namespace pollution by renaming an entire file's worth of symbols with proper and longer prefixes, without actually -touching the Lisp source. For details, see the Info node "(elisp) -Shorthands". +touching the Lisp source. Instead you need to set the +'read-symbol-shorthands' variable in the file-local section of the file +to list the shorthand prefixes in use. +For details, see the Info node "(elisp) Shorthands". ** New function 'string-search'. This function takes two string parameters and returns the position of commit 05ea637d602c3c739b833c4262e6f46f6336ad1f Author: Stefan Monnier Date: Thu Nov 13 09:01:36 2025 -0500 lisp/emacs-lisp/bytecomp.el (define-widget): Add `funarg-positions` diff --git a/lisp/emacs-lisp/bytecomp.el b/lisp/emacs-lisp/bytecomp.el index b5b371f7833..58f00af8be9 100644 --- a/lisp/emacs-lisp/bytecomp.el +++ b/lisp/emacs-lisp/bytecomp.el @@ -2617,7 +2617,7 @@ Call from the source buffer." ;; for `byte-compile-dynamic-docstrings'. Most other things can be output ;; as byte-code. -(put 'autoload 'byte-hunk-handler 'byte-compile-file-form-autoload) +(put 'autoload 'byte-hunk-handler #'byte-compile-file-form-autoload) (defun byte-compile-file-form-autoload (form) (and (let ((form form)) (while (if (setq form (cdr form)) (macroexp-const-p (car form)))) @@ -2654,8 +2654,8 @@ Call from the source buffer." (byte-compile-keep-pending (byte-compile--list-with-n form 3 newdoc) #'byte-compile-normal-call))) -(put 'defvar 'byte-hunk-handler 'byte-compile-file-form-defvar) -(put 'defconst 'byte-hunk-handler 'byte-compile-file-form-defvar) +(put 'defvar 'byte-hunk-handler #'byte-compile-file-form-defvar) +(put 'defconst 'byte-hunk-handler #'byte-compile-file-form-defvar) (defun byte-compile--check-prefixed-var (sym) (when (and (symbolp sym) @@ -2679,8 +2679,8 @@ Call from the source buffer." (byte-compile-defvar form 'toplevel)) (put 'define-abbrev-table 'byte-hunk-handler - 'byte-compile-file-form-defvar-function) -(put 'defvaralias 'byte-hunk-handler 'byte-compile-file-form-defvar-function) + #'byte-compile-file-form-defvar-function) +(put 'defvaralias 'byte-hunk-handler #'byte-compile-file-form-defvar-function) (defun byte-compile-file-form-defvar-function (form) (pcase-let (((or `',name (let name nil)) (nth 1 form))) @@ -2704,7 +2704,7 @@ Call from the source buffer." (byte-compile-keep-pending form))) (put 'custom-declare-variable 'byte-hunk-handler - 'byte-compile-file-form-defvar-function) + #'byte-compile-file-form-defvar-function) (put 'custom-declare-face 'byte-hunk-handler #'byte-compile--custom-declare-face) @@ -2716,14 +2716,14 @@ Call from the source buffer." (setq form (byte-compile--list-with-n form 3 newdocs))))) (byte-compile-keep-pending form))) -(put 'require 'byte-hunk-handler 'byte-compile-file-form-require) +(put 'require 'byte-hunk-handler #'byte-compile-file-form-require) (defun byte-compile-file-form-require (form) - (let* ((args (mapcar 'eval (cdr form))) + (let* ((args (mapcar #'eval (cdr form))) ;; The following is for the byte-compile-warn in ;; `do-after-load-evaluation' (in subr.el). (byte-compile-form-stack (cons (car args) byte-compile-form-stack)) hist-new prov-cons) - (apply 'require args) + (apply #'require args) ;; Record the functions defined by the require in `byte-compile-new-defuns'. (setq hist-new load-history) @@ -3648,6 +3648,8 @@ This assumes the function has the `important-return-value' property." (dolist (fa '((plist-put 4) (alist-get 5) (add-to-list 5) (cl-merge 4 :key) (custom-declare-variable :set :get :initialize :safe) + (define-widget :convert-widget :value-to-internal + :value-to-external :match) (make-process :filter :sentinel) (make-network-process :filter :sentinel) (all-completions 2 3) (try-completion 2 3) (test-completion 2 3) commit 580d74e1e1631e5f889c9a676ba443b3c8d7ec5f Author: Stefan Monnier Date: Thu Nov 13 08:23:17 2025 -0500 (loaddefs-generate--emacs-batch): Fix bug#79821 * lisp/emacs-lisp/loaddefs-gen.el (loaddefs-generate--emacs-batch): Expand file names before changing directory, in case they're relative. diff --git a/lisp/emacs-lisp/loaddefs-gen.el b/lisp/emacs-lisp/loaddefs-gen.el index fcec04115e4..44f370bdff9 100644 --- a/lisp/emacs-lisp/loaddefs-gen.el +++ b/lisp/emacs-lisp/loaddefs-gen.el @@ -821,7 +821,7 @@ use." "Generate the loaddefs for the Emacs build. This is like `loaddefs-generate-batch', but has some specific rules for built-in packages and excluded files." - (let* ((args command-line-args-left) + (let* ((args (mapcar #'expand-file-name command-line-args-left)) ;; We're run from $BUILDDIR/lisp but all the .el(c) files reside ;; (and are generated) in `lisp-directory' which is in $SRCDIR, ;; so go there and don't look back.