commit 1668290c9d4082f89e3a12355c9afb415418dfd6 (HEAD, refs/remotes/origin/master) Author: Juri Linkov Date: Mon Nov 17 09:34:36 2025 +0200 Improve hs-indicator-mouse-toggle-hiding for non-selected windows * lisp/progmodes/hideshow.el (hs-indicator-mouse-toggle-hiding): Use 'mouse-set-point' to select the clicked window when it was not selected. diff --git a/lisp/progmodes/hideshow.el b/lisp/progmodes/hideshow.el index ae7e050ecb6..87c98b5b2a1 100644 --- a/lisp/progmodes/hideshow.el +++ b/lisp/progmodes/hideshow.el @@ -1388,6 +1388,8 @@ Argument E should be the event that triggered this action." (interactive "e") (hs-life-goes-on (when hs-show-indicators + (when (mouse-event-p event) + (mouse-set-point event)) (let* ((overlays (save-excursion (goto-char (posn-point (event-end event))) (overlays-in (pos-bol) (pos-eol)))) commit ec08011af4f3dab75e9c5da5681d7f1216ebad72 Author: Juri Linkov Date: Mon Nov 17 09:30:45 2025 +0200 New hideshow option 'hs-cycle-filter' for visibility-cycling with 'TAB' * doc/emacs/programs.texi (Hideshow): Add 'hs-cycle-filter' and remove duplicate 'hs-toggle-hiding'. * lisp/progmodes/hideshow.el (hs-cycle-filter): New defcustom (bug#79585). (hs-minor-mode-map): Bind 'TAB' to 'hs-toggle-hiding' using the filter. diff --git a/doc/emacs/programs.texi b/doc/emacs/programs.texi index f08a3834c17..385bfafb1d1 100644 --- a/doc/emacs/programs.texi +++ b/doc/emacs/programs.texi @@ -1699,7 +1699,6 @@ count as blocks. @findex hs-show-region @findex hs-hide-level @findex hs-toggle-hiding -@findex hs-toggle-hiding @kindex C-c @@ C-h @kindex C-c @@ C-s @kindex C-c @@ C-c @@ -1736,6 +1735,7 @@ Hide all blocks @var{n} levels below this block @vindex hs-indicator-maximum-buffer-size @vindex hs-isearch-open @vindex hs-hide-block-behavior +@vindex hs-cycle-filter These variables can be used to customize Hideshow mode: @table @code @@ -1773,6 +1773,12 @@ performance.) By default, buffers larger than 2MB have the indicators disabled; the value of @code{nil} will activate the indicators regardless of the buffer size. +@item hs-cycle-filter +This variable controls where on the line with hideable blocks the +@kbd{@key{TAB}} key cycles their visibility. Depending on its non-nil +values, @kbd{@key{TAB}} can be active on different parts of such lines. +Anywhere else on the line this key has its default keybinding. + @item hs-isearch-open This variable specifies the conditions under which incremental search should unhide a hidden block when matching text occurs within the diff --git a/etc/NEWS b/etc/NEWS index d7c04f38ab4..0c9c4962310 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1112,6 +1112,11 @@ should hide a block. If set to 'after-bol', hide the innermost block to which the current line belongs. If set to 'after-cursor', hide the block after cursor position. By default this is set to 'after-bol'. ++++ +*** New user option 'hs-cycle-filter' for visibility-cycling with 'TAB'. +This user option controls where on the line with hideable blocks +the 'TAB' key cycles their visibility. + +++ *** The variable 'hs-special-modes-alist' is now obsolete. Instead of customizing Hideshow for a mode by setting the elements of diff --git a/lisp/progmodes/hideshow.el b/lisp/progmodes/hideshow.el index 93b5cb10670..ae7e050ecb6 100644 --- a/lisp/progmodes/hideshow.el +++ b/lisp/progmodes/hideshow.el @@ -468,6 +468,29 @@ info node `(elisp)Overlays'." :type 'function :version "28.1") +(defcustom hs-cycle-filter nil + "Control where \\`TAB' cycles the visibility. +This option controls where on a line where a block begins, typing +the key sequences bound to the visibility-cycling commands like +`hs-toggle-hiding' will invoke those commands. When t, you can invoke +these commands by typing \\`TAB' anywhere on a headline. Customizing +this option to other values can make those bindings be in effect only at +specific positions on the headline, like only at the line's beginning or +line's end. This allows these keys to be bound to their usual commands, +as determined by the major mode, elsewhere on the headlines." + :type `(choice (const :tag "Nowhere" nil) + (const :tag "Everywhere on the headline" t) + (const :tag "At block beginning" + ,(lambda () + (pcase-let ((`(,beg ,end) (hs-block-positions))) + (and beg (hs-hideable-region-p beg end))))) + (const :tag "At line beginning" bolp) + (const :tag "Not at line beginning" + ,(lambda () (not (bolp)))) + (const :tag "At line end" eolp) + (function :tag "Custom filter function")) + :version "31.1") + ;;--------------------------------------------------------------------------- ;; internal variables @@ -494,6 +517,18 @@ Use the command `hs-minor-mode' to toggle or set this variable.") :doc "Keymap for hideshow minor mode." "S-" #'hs-toggle-hiding "C-c @" hs-prefix-map + "TAB" `(menu-item + "" hs-toggle-hiding + :filter + ,(lambda (cmd) + (when (and hs-cycle-filter + ;; On the headline with hideable blocks + (save-excursion + (goto-char (line-beginning-position)) + (hs-get-first-block)) + (or (not (functionp hs-cycle-filter)) + (funcall hs-cycle-filter))) + cmd))) " " #'hs-indicator-mouse-toggle-hiding) (defvar-keymap hs-indicators-map commit 6663df56556b6a30b0be0ff68d54f5e6708765d7 Author: Elías Gabriel Pérez Date: Sun Nov 16 12:01:30 2025 -0600 hideshow: Simplify code. (Bug#79585) * lisp/progmodes/hideshow.el (hs-get-first-block): New function. (hs--add-indicators, hs-hide-block): Use the new function and simplify. diff --git a/lisp/progmodes/hideshow.el b/lisp/progmodes/hideshow.el index f7742be1f2a..93b5cb10670 100644 --- a/lisp/progmodes/hideshow.el +++ b/lisp/progmodes/hideshow.el @@ -841,27 +841,8 @@ point." (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) - (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))))) + (when-let* ((b-beg (hs-get-first-block))) + (hs--make-indicators-overlays b-beg))) ;; Only 1 indicator per line (forward-line)) `(jit-lock-bounds ,beg . ,end)) @@ -1002,6 +983,22 @@ Otherwise, return nil." (goto-char (if end q (min p (match-end 0)))) nil))))) +(defun hs-get-first-block () + "Return the position of the first valid block found on the current line. +This searches for a valid block on the current line and returns the +first block found. Otherwise, if no block is found, it returns nil." + (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 (match-beginning 0)) + t)))) + exit)) + (defun hs-inside-comment-p () (declare (obsolete "Call `hs-inside-comment-predicate' instead." "31.1")) (funcall hs-inside-comment-predicate)) @@ -1274,21 +1271,11 @@ Upon completion, point is repositioned and the normal hook (c-reg (hs-hide-block-at-point end c-reg)) ((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)))) + (and-let* ((_ (eq hs-hide-block-behavior 'after-bol)) + (_ (goto-char (line-beginning-position))) + (pos (hs-get-first-block)) + (_ (goto-char pos)) + (_ (hs-hide-block-at-point end)))))) ((or (funcall hs-looking-at-block-start-predicate) (and (goto-char (line-beginning-position)) commit ad13e0b864da9e13a5f5f9f03ddf86dd27ddb686 Author: Thomas Fitzsimmons Date: Sun Nov 16 17:39:58 2025 -0500 ntlm.el: Add Package-Requires and fix two docstrings * lisp/net/ntlm.el (Package-Requires): Add emacs 27.1. * lisp/net/ntlm.el (ntlm-build-auth-response, ntlm-string-xor): Fix docstrings to satisfy `checkdoc'. diff --git a/lisp/net/ntlm.el b/lisp/net/ntlm.el index bec3d9e4dae..43331351472 100644 --- a/lisp/net/ntlm.el +++ b/lisp/net/ntlm.el @@ -6,6 +6,7 @@ ;; Maintainer: Thomas Fitzsimmons ;; Keywords: NTLM, SASL, comm ;; Version: 2.1.0 +;; Package-Requires: ((emacs "27.1")) ;; Created: February 2001 ;; This is a GNU ELPA :core package. Avoid functionality that is not @@ -228,11 +229,12 @@ Return a random eight byte unibyte string." (random 256) (random 256) (random 256) (random 256))) (defun ntlm-build-auth-response (challenge user password-hashes) - "Return the response string to a challenge string CHALLENGE given by -the NTLM based server for the user USER and the password hash list -PASSWORD-HASHES. NTLM uses two hash values which are represented -by PASSWORD-HASHES. PASSWORD-HASHES should be a return value of - (list (ntlm-smb-passwd-hash password) (ntlm-md4hash password))" + "Return the NTLM authentication response string. +Return the response string to a challenge string CHALLENGE given by the +NTLM based server for the user USER and the password hash list +PASSWORD-HASHES. NTLM uses two hash values which are represented by +PASSWORD-HASHES. PASSWORD-HASHES should be the return value +of (list (ntlm-smb-passwd-hash password) (ntlm-md4hash password))" (let* ((rchallenge (if (multibyte-string-p challenge) (progn ;; FIXME: Maybe it would be better to @@ -615,7 +617,7 @@ length of STR is LEN." (concat (substring str c len) (substring str 0 c)))) (defsubst ntlm-string-xor (in1 in2 n) - "Return exclusive-or of sequences in1 and in2." + "Return exclusive-or of sequences IN1 and IN2 of length N." (let ((w (make-string n 0)) (i 0)) (while (< i n) (aset w i (logxor (aref in1 i) (aref in2 i))) commit c81d8fd4905e603bbf82ccd299b4387d9eaaf213 Author: João Távora Date: Sun Nov 16 22:02:58 2025 +0000 ; Eglot: minor documentation cleanup of last change * lisp/progmodes/eglot.el (eglot--semtok-cache) (eglot--semtok-inflight): Minimally enhance docstrings. (eglot--semtok-font-lock-2): Remove FIXME comment. bug#79374 diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 288d37bfa4f..2e46e3fe766 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -4621,10 +4621,14 @@ If NOERROR, return predicate, else erroring function." semtok-cache) probe)))) -(defvar-local eglot--semtok-cache nil "Recent semtok responses.") +(defvar-local eglot--semtok-cache nil + "List of plists describing recent semtok response. +See `eglot--semtok-request' implementation for details.") (defvar-local eglot--semtok-inflight (make-hash-table) - "Info about inflight semtok requests.") + "Map of JSONRPC request ID to (METHOD DOCVER . REGIONS). +REGIONS is a list of (BEG . END) of positions that can be serviced by +this request.") (cl-defmethod eglot-handle-request (server (_method (eql workspace/semanticTokens/refresh))) @@ -4807,10 +4811,6 @@ lock machinery calls us again." 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 - ;; faces applied earlier with `add-face-text-property' from - ;; `eglot--semtok-font-lock-1' disappear for a moment while the - ;; request is in flight. "Repaint from stale-but-not-that-much local properties." (eglot--widening (with-silent-modifications commit 4fad95d9fc77855193bb95a5a53ae1aacee8c11e Author: Juri Linkov Date: Sun Nov 16 19:37:23 2025 +0200 * lisp/outline.el: Small fixes. (outline-font-lock-keywords, outline-minor-mode-highlight-buffer): Fall back to eol when there is no newline at the end of outline at eob. (outline-after-change-functions): Remove the temporarily added variable. (outline--fix-buttons): Remove its last use. diff --git a/lisp/outline.el b/lisp/outline.el index 392c60a4604..8dcb7263da3 100644 --- a/lisp/outline.el +++ b/lisp/outline.el @@ -267,10 +267,13 @@ non-nil and point is located on the heading line.") ;; This is equivalent to adding ".*" in the regexp below. (set-match-data (list (match-beginning 0) - (save-excursion - (save-match-data - (re-search-forward - (concat ".*" outline-heading-end-regexp) nil t))))) + (or (save-excursion + (save-match-data + (re-search-forward + (concat ".*" outline-heading-end-regexp) nil t))) + ;; Fall back to eol when there is no newline + ;; at the end of outline at eob. + (pos-eol)))) ret))) (concat "^\\(?:" outline-regexp "\\).*" outline-heading-end-regexp)) 0 '(if outline-minor-mode @@ -531,10 +534,13 @@ outline font-lock faces to those of major mode." ;; This is equivalent to adding ".*" in the regexp above. (set-match-data (list (match-beginning 0) - (save-excursion - (save-match-data - (re-search-forward - (concat ".*" outline-heading-end-regexp) nil t))))) + (or (save-excursion + (save-match-data + (re-search-forward + (concat ".*" outline-heading-end-regexp) nil t))) + ;; Fall back to eol when there is no newline + ;; at the end of outline at eob. + (pos-eol)))) ret) (re-search-forward regexp nil t)) (let ((overlay (make-overlay (match-beginning 0) (match-end 0)))) @@ -2030,12 +2036,7 @@ With a prefix argument, show headings up to that LEVEL." from to) (restore-buffer-modified-p modified)))) -(defvar outline-after-change-functions nil - "Hook run before updating buttons in a region in outline-mode. -Called with three arguments (BEG END DUMMY). Don't use DUMMY.") - (defun outline--fix-buttons (&optional beg end) - (run-hook-with-args 'outline-after-change-functions beg end 0) ;; Handle whole lines (save-excursion (setq beg (if (null beg) (point-min) (goto-char beg) (pos-bol))) commit 7469ee7e2787de67043fb29123037f28bfe931ac Author: João Távora Date: Sun Nov 16 15:16:07 2025 +0000 Eglot: rework semtok feature again This time, I bit the bullet and made eglot--semtok-cache a list of responses. This is motivated by "range" request which happen in for multiple regions asynchronously. They must all be (eventually) sent and cannot stomp on each others' cache. Figuring out when to delete outdated entries from eglot--semtok-cache is also tricky. It must be done only once we're certain an outdated entry cannot be reused for a "delta" request. * lisp/progmodes/eglot.el (eglot--semtok-cache): Reword docstring. (eglot--semtok-apply-delta-edits): Tweak variable name. (eglot--semtok-request): Rework again. (eglot--semtok-font-lock): Rework. (eglot--semtok-font-lock-1): DATA no longer optional. diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 6b3398d9fd0..288d37bfa4f 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -4621,8 +4621,7 @@ 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-cache nil "Recent semtok responses.") (defvar-local eglot--semtok-inflight (make-hash-table) "Info about inflight semtok requests.") @@ -4652,50 +4651,58 @@ If NOERROR, return predicate, else erroring function." (defsubst eglot--semtok-apply-delta-edits (old-data edits) "Apply EDITS obtained from full/delta request to OLD-DATA." (cl-loop - for old-token-index = 0 then (+ (plist-get edit :start) (plist-get edit :deleteCount)) + for old-i = 0 then (+ (plist-get edit :start) (plist-get edit :deleteCount)) for edit across edits - when (< old-token-index (plist-get edit :start)) - vconcat (substring old-data old-token-index (plist-get edit :start)) into new + when (< old-i (plist-get edit :start)) + vconcat (substring old-data old-i (plist-get edit :start)) into new vconcat (plist-get edit :data) into new finally - (cl-return (vconcat new (substring old-data old-token-index (length old-data)))))) + (cl-return (vconcat new (substring old-data old-i (length old-data)))))) -(cl-defun eglot--semtok-request (beg end &aux (doc-id eglot--versioned-identifier)) +(cl-defun eglot--semtok-request + (beg end &aux (docver eglot--versioned-identifier) reused) "Ask for tokens. Arrange for BEG..END to be font-lock flushed." (cl-labels ((fullish-p (m) (memq m '(:textDocument/semanticTokens/full/delta :textDocument/semanticTokens/full))) - (req (method params cont - &aux - req-id (buf (current-buffer))) + (prune-outdated () + (setq eglot--semtok-cache + (cl-delete-if (lambda (e) + (not (eq docver (plist-get e :docver)))) + eglot--semtok-cache))) + (req (method params &optional cont + &aux req-id (buf (current-buffer))) (setq req-id (eglot--async-request (eglot--current-server-or-lose) method params :success-fn (lambda (response) (eglot--when-live-buffer buf - (pcase-let ((`(,method ,doc-id ,regions) + (pcase-let ((`(,method ,docver ,regions) (gethash req-id eglot--semtok-inflight))) (remhash req-id eglot--semtok-inflight) ;; (trace-values "Response: " ;; method - ;; eglot--versioned-identifier doc-id + ;; eglot--versioned-identifier docver ;; "edits: " ;; (length (cl-getf response :edits)) ;; "data: " ;; (length (cl-getf response :data))) ;; A user edit may have come in while the request ;; was inflight, changing the state of the buffer... - (when (eq doc-id eglot--versioned-identifier) - (setq eglot--semtok-cache - (list :documentVersion doc-id - :method method - :response (funcall cont response) - :valid (if (fullish-p method) - (eglot--widening - (cons (point-min) (point-max))) - (cons beg end))))) + (when (eq docver eglot--versioned-identifier) + (push + (list :docver docver + :method method + :resultId (plist-get response :resultId) + :data (if cont (funcall cont response) + (plist-get response :data)) + :valid (if (fullish-p method) + (eglot--widening + (cons (point-min) (point-max))) + (cons beg end))) + eglot--semtok-cache)) ;; ... but we should flush unconditionally. If ;; this response was out-of-date, ;; `eglot--semtok-font-lock' should just trigger @@ -4705,66 +4712,69 @@ If NOERROR, return predicate, else erroring function." ;; (trace-values "Flushed" (length regions) ;; "regions" regions) ))) - :hint method)) - (puthash req-id (list method doc-id (list (cons beg end))) - eglot--semtok-inflight)) - (cache-get (&rest path) - (let ((x eglot--semtok-cache)) - (dolist (op path x) (setq x (if (natnump op) (aref x op) - (plist-get x op))))))) + ;; For "range" requests, make sure we have one unique + ;; request defeating part of the "deferred" mechanism. + :hint (if (fullish-p method) method + (gensym (symbol-name method))))) + ;; Can prune outdated entries now, not earlier, since "delta" + ;; requests rely on outdated entries by definition. + (prune-outdated) + (puthash req-id (list method docver (list (cons beg end))) + eglot--semtok-inflight))) ;; JT@2025-11-16: Many back-to-back calls for ;; `eglot--semtok-request' and small regions occur even on ;; trivial/fast edits. We try to send just one request. If there ;; is a "full" or "full/delta" request in flight, we can piggy back - ;; onto it our region and our doc-id, and exit. That's because very + ;; onto it our region and our docver, and exit. That's because very ;; likely it's not actually inflight yet (because of the "deferred" ;; mechanism, it's waiting for didChange), so we can still do ;; changes to the state it represents when it is actually sent. (cl-loop for v being the hash-values of eglot--semtok-inflight when (fullish-p (car v)) do (push (cons beg end) (caddr v)) - (setf (cadr v) doc-id) - (cl-return-from eglot--semtok-request (cons 'skipped doc-id))) + (setf (cadr v) docver) + (cl-return-from eglot--semtok-request (cons 'skipped docver))) (cond ((and (eglot-server-capable :semanticTokensProvider :full :delta) - (cache-get :response :data) - (fullish-p (cache-get :method))) + (setq reused (cl-find-if + (lambda (e) (fullish-p (plist-get e :method))) + eglot--semtok-cache))) (req :textDocument/semanticTokens/full/delta (list :textDocument (eglot--TextDocumentIdentifier) - :previousResultId (cache-get :response :resultId)) + :previousResultId (plist-get reused :resultId)) (lambda (response) (if-let* ((edits (plist-get response :edits))) - (plist-put response :data - (eglot--semtok-apply-delta-edits - (cache-get :response :data) - edits)) - response)))) + (eglot--semtok-apply-delta-edits + (plist-get reused :data) + edits) + (plist-get response :data))))) ((eglot-server-capable :semanticTokensProvider :range) (req :textDocument/semanticTokens/range (list :textDocument (eglot--TextDocumentIdentifier) - :range (eglot-region-range beg end)) - #'identity)) + :range (eglot-region-range beg end)))) (t (req :textDocument/semanticTokens/full - (list :textDocument (eglot--TextDocumentIdentifier)) - #'identity))))) + (list :textDocument (eglot--TextDocumentIdentifier))))))) (cl-defun eglot--semtok-font-lock (limit &aux (beg (point)) (end limit)) - "Endeavor to semantically font-lock from point until LIMIT. + "Arrange for font-lock to happen from point until LIMIT. Either do it immediately if the information available is up-to-date or request new information from the server and return and hope the font lock machinery calls us again." - (cond ((and (eq (plist-get eglot--semtok-cache :documentVersion) - eglot--versioned-identifier) - (let ((valid (plist-get eglot--semtok-cache :valid))) - (<= (car valid) beg end (cdr valid)))) - (eglot--semtok-font-lock-1 beg end)) - (t - (eglot--semtok-font-lock-2 beg end) - (eglot--semtok-request beg end))) + (let ((probe + (cl-find-if + (jsonrpc-lambda (&key docver valid &allow-other-keys) + (and (eq docver eglot--versioned-identifier) + (<= (car valid) beg end (cdr valid)))) + eglot--semtok-cache))) + (cond (probe + (eglot--semtok-font-lock-1 beg end (plist-get probe :data))) + (t + (eglot--semtok-font-lock-2 beg end) + (eglot--semtok-request beg end)))) nil) -(defun eglot--semtok-font-lock-1 (beg end &optional data) +(defun eglot--semtok-font-lock-1 (beg end data) "Do the face-painting work for `eglot--semtok-font-lock'." (eglot--widening (with-silent-modifications @@ -4772,7 +4782,6 @@ lock machinery calls us again." eglot--semtok-faces)) (goto-char (point-min)) (cl-loop - with data = (or data (plist-get (plist-get eglot--semtok-cache :response) :data)) with column = 0 with p-beg = 0 with p-end = 0 for i from 0 below (length data) by 5 when (> (aref data i) 0) do commit 364b5306c0354625ac113f540d4ea29b45d1fc3b Author: Eli Zaretskii Date: Sun Nov 16 17:17:37 2025 +0200 Fix last change in keyboard.c * src/keyboard.c (adjust_point_for_property): Handle 'display' property on the same text as 'invisible' property the same as the overlay with a 'display' property. (Bug#79787) diff --git a/src/keyboard.c b/src/keyboard.c index da15022a03d..016a79082aa 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -1846,16 +1846,15 @@ adjust_point_for_property (ptrdiff_t last_pt, bool modified) modified, but only if the invisible text is not shown on display. If it _is_ shown, we don't need to move point, since the normal display of the cursor will DTRT. - Invisible text might be "shown on display" if there's an - overlay with a replacing display property on the same text: - then whatever is specified by the display property will be - shown instead of the invisible text. */ + Invisible text might be "shown on display" if there's a + replacing display property on the same text: then whatever + is specified by the display property will be shown instead + of the invisible text. */ bool shown = !NILP (val = get_char_property_and_overlay (make_fixnum (beg), Qdisplay, selected_window, &overlay)) - && display_prop_intangible_p (val, overlay, beg, CHAR_TO_BYTE (beg)) - && OVERLAYP (overlay); + && display_prop_intangible_p (val, overlay, beg, CHAR_TO_BYTE (beg)); /* If the "invisible" text is shown, undo any point adjustments due to invisible property, as cursor commit 3888979ee16d0191e952542d1ee92a61b08448be Author: Eli Zaretskii Date: Sun Nov 16 16:38:37 2025 +0200 Fix cursor motion across invisible text with an overlay * src/keyboard.c (adjust_point_for_property): Handle the case where invisible text has an overlay over it with a replacing 'display' property, which leaves some "trace" of the invisible text on display. (Bug#79787) diff --git a/src/keyboard.c b/src/keyboard.c index c06c38f2e3f..da15022a03d 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -1726,7 +1726,7 @@ adjust_point_for_property (ptrdiff_t last_pt, bool modified) deleting characters around point. */ bool check_composition = ! modified; bool check_display = true, check_invisible = true; - ptrdiff_t orig_pt = PT; + ptrdiff_t orig_pt = PT, pt_before_invis = PT; eassert (XBUFFER (XWINDOW (selected_window)->contents) == current_buffer); @@ -1765,6 +1765,7 @@ adjust_point_for_property (ptrdiff_t last_pt, bool modified) check_composition = check_invisible = true; } check_display = false; + pt_before_invis = PT; if (check_invisible && PT > BEGV && PT < ZV) { int inv; @@ -1841,10 +1842,28 @@ adjust_point_for_property (ptrdiff_t last_pt, bool modified) point-motion hooks, intangibility, etc. */ eassert (PT == beg || PT == end); #endif - /* Pretend the area doesn't exist if the buffer is not - modified. */ - if (!modified && !ellipsis && beg < end) + modified, but only if the invisible text is not shown on + display. If it _is_ shown, we don't need to move point, + since the normal display of the cursor will DTRT. + Invisible text might be "shown on display" if there's an + overlay with a replacing display property on the same text: + then whatever is specified by the display property will be + shown instead of the invisible text. */ + bool shown = + !NILP (val = get_char_property_and_overlay + (make_fixnum (beg), Qdisplay, selected_window, + &overlay)) + && display_prop_intangible_p (val, overlay, beg, CHAR_TO_BYTE (beg)) + && OVERLAYP (overlay); + + /* If the "invisible" text is shown, undo any point + adjustments due to invisible property, as cursor + positioning by the display engine will do a better job. */ + if (shown) + SET_PT (pt_before_invis); + + if (!modified && !shown && !ellipsis && beg < end) { if (last_pt == beg && PT == end && end < ZV) (check_composition = check_display = true, SET_PT (end + 1)); commit 858d54d51d73dbf9bc8bd90b2b9acf6313f413b4 Author: Eli Zaretskii Date: Sun Nov 16 11:07:32 2025 +0200 ; Fix cleanup in buffer-tests * test/src/buffer-tests.el (test-restore-buffer-modified-p) (test-buffer-chars-modified-ticks): Don't leave unsaved and modified file buffers after the tests. diff --git a/test/src/buffer-tests.el b/test/src/buffer-tests.el index 0879b928565..2349e63ac33 100644 --- a/test/src/buffer-tests.el +++ b/test/src/buffer-tests.el @@ -8507,7 +8507,10 @@ Finally, kill the buffer and its temporary file." ;; Clean up. (when (file-exists-p buffer-auto-save-file-name) - (delete-file buffer-auto-save-file-name)))) + (delete-file buffer-auto-save-file-name)) + ;; Don't leave modified and unsaved files, to avoid confirmation + ;; prompts when exiting Emacs in interactive sessions. + (restore-buffer-modified-p nil))) (ert-with-temp-file file (setq file (file-truename file)) @@ -8518,7 +8521,10 @@ Finally, kill the buffer and its temporary file." (should (buffer-modified-p)) (should-not (eq (buffer-modified-p) 'autosaved)) (restore-buffer-modified-p 'autosaved) - (should (eq (buffer-modified-p) 'autosaved))))) + (should (eq (buffer-modified-p) 'autosaved)) + ;; Don't leave modified and unsaved files, to avoid confirmation + ;; prompts when exiting Emacs in interactive sessions. + (restore-buffer-modified-p nil)))) (ert-deftest test-buffer-chars-modified-ticks () "Test `buffer-chars-modified-tick'." @@ -8532,7 +8538,11 @@ Finally, kill the buffer and its temporary file." (write-region text nil f2 nil 'silent) (insert-file-contents f2) (should (= (buffer-chars-modified-tick) (buffer-modified-tick))) - (should (> (buffer-chars-modified-tick) 1))))))) + (should (> (buffer-chars-modified-tick) 1)) + ;; Don't leave modified and unsaved files, to avoid + ;; confirmation prompts when exiting Emacs in interactive + ;; sessions. + (restore-buffer-modified-p nil)))))) (ert-deftest test-labeled-narrowing () "Test `with-restriction' and `without-restriction'." commit 50c30a924086460c1723c5830a4cf6a89e0a9c2a Author: Martin Rudalics Date: Sun Nov 16 09:28:30 2025 +0100 ; Improve documentation of 'clone-of' window parameter * lisp/window.el (window-state-get, window-state-put): Mention 'clone-of' parameter in doc-strings. * doc/lispref/windows.texi (Window Configurations, Window Parameters): Improve description of 'clone-of' parameter. diff --git a/doc/lispref/windows.texi b/doc/lispref/windows.texi index 1611ecb7389..739f61905f3 100644 --- a/doc/lispref/windows.texi +++ b/doc/lispref/windows.texi @@ -7068,9 +7068,10 @@ not use markers for sampling positions like @code{window-point} or @code{window-start}. This argument should be non-@code{nil} when the state will be written to disk and read back in another session. -Together, the argument @var{writable} and the variable +Together, @var{writable} and the variable @code{window-persistent-parameters} specify which window parameters are -saved by this function. @xref{Window Parameters}. +saved by this function. The default is to save the @code{clone-of} +parameter if @var{writable} is @code{nil}. @xref{Window Parameters}. @vindex window-state-normalize-buffer-name Bind @code{window-state-normalize-buffer-name} to non-@code{nil} to @@ -7085,14 +7086,19 @@ and read back in another session. In either case, use the following function to restore the state of the window. @defun window-state-put state &optional window ignore -This function puts the window state @var{state} into @var{window}. -The argument @var{state} should be the state of a window returned by -an earlier invocation of @code{window-state-get}, see above. The -optional argument @var{window} can be either a live window or an -internal window (@pxref{Windows and Frames}). If @var{window} is not -a live window, it is replaced by a new live window created on the same -frame before putting @var{state} into it. If @var{window} is @code{nil}, -it puts the window state into a new window. +This function puts the window state @var{state} into @var{window}. The +argument @var{state} should be the state of a window returned by an +earlier invocation of @code{window-state-get}, see above. The optional +argument @var{window} can be either a live window or an internal window +(@pxref{Windows and Frames}). If @var{window} is not a live window, it +is replaced by a new live window created on the same frame before +putting @var{state} into it. If @var{window} is @code{nil}, it puts the +window state into a new window. + +This function assigns all window parameters that have been stored in +@var{state}. By default, this is the @code{clone-of} parameter provided +@var{state} was obtained from an invocation of @code{window-state-get} +with the argument @var{writable} @code{nil}. @xref{Window Parameters}. This function consults the variable @code{window-restore-killed-buffer-windows} (see below) when it tries to @@ -7330,9 +7336,10 @@ specific window. @item clone-of @vindex clone-of@r{, a window parameter} -This parameter specifies the window that this one has been cloned -from. It is installed by @code{window-state-get} (@pxref{Window -Configurations}). +This parameter specifies the window that this one has been cloned from. +It is persistent by default and as such gets saved by +@code{window-state-get} and installed by @code{window-state-put} +(@pxref{Window Configurations}). @item window-preserved-size @vindex window-preserved-size@r{, a window parameter} diff --git a/lisp/window.el b/lisp/window.el index 89293c37257..702ec1de65d 100644 --- a/lisp/window.el +++ b/lisp/window.el @@ -6469,14 +6469,14 @@ its prefixes and suffixes removed; otherwise return BUFFER's WINDOW can be any window and defaults to the root window of the selected frame. -Optional argument WRITABLE non-nil means do not use markers for -sampling `window-point' and `window-start'. Together, WRITABLE -and the variable `window-persistent-parameters' specify which -window parameters are saved by this function. WRITABLE should be -non-nil when the return value shall be written to a file and read -back in another session. Otherwise, an application may run into -an `invalid-read-syntax' error while attempting to read back the -value from file. +Optional argument WRITABLE non-nil means do not use markers for sampling +`window-point' and `window-start'. Together, WRITABLE and the variable +`window-persistent-parameters' specify which parameters of WINDOW to +save. By default, this is the `clone-of' parameter if WRITABLE is nil. +WRITABLE should be non-nil when the return value shall be written to a +file and read back in another session. Otherwise, an application may +run into an `invalid-read-syntax' error while attempting to read back +the value from file. The return value can be used as argument for `window-state-put' to put the state recorded here into an arbitrary window. The @@ -6762,6 +6762,10 @@ specify a valid window. If WINDOW is not a live window, replace WINDOW by a new live window created on the same frame. If WINDOW is nil, create a new window before putting STATE into it. +Assign all window parameters that have been stored in STATE. By +default, this is the `clone-of’ parameter provided STATE was obtained +from an invocation of `window-state-get’ with WRITABLE nil. + Optional argument IGNORE non-nil means ignore minimum window sizes and fixed size restrictions. IGNORE equal `safe' means windows can get as small as `window-safe-min-height' and commit 361807bf18c85491a14826afc0f79400c850d8dd Author: Elías Gabriel Pérez Date: Fri Nov 14 21:22:44 2025 -0600 hideshow: Make ellipsis a little noticeable in TTY frames bug#79585 * lisp/progmodes/hideshow.el (hs-ellipsis): Use 'shadow' face. (hs--get-ellipsis): Rework. (hs-already-hidden-p): Fix regression. diff --git a/lisp/progmodes/hideshow.el b/lisp/progmodes/hideshow.el index 255dea4a095..f7742be1f2a 100644 --- a/lisp/progmodes/hideshow.el +++ b/lisp/progmodes/hideshow.el @@ -288,7 +288,7 @@ :group 'languages) (defface hs-ellipsis - '((t :height 0.80 :box (:line-width -1) :inherit default)) + '((t :height 0.80 :box (:line-width -1) :inherit (shadow default))) "Face used for hideshow ellipsis. Note: If `selective-display' ellipsis already has a face, hideshow will use that face for the ellipsis instead." @@ -881,30 +881,34 @@ This returns the ellipsis string to use and its face." (d-t-ellipsis (display-table-slot standard-display-table 'selective-display)) ;; Convert ellipsis vector to a propertized string + (ellipsis + (and (vectorp d-t-ellipsis) ; Ensure the vector is not empty + (not (length= d-t-ellipsis 0)) + (mapconcat + (lambda (g) + (apply #'propertize (char-to-string (glyph-char g)) + (and (glyph-face g) (list 'face (glyph-face g))))) + d-t-ellipsis))) + (ellipsis-face (and ellipsis (get-text-property 0 'face ellipsis))) + (apply-face (lambda (str) + (apply #'propertize str + (and ellipsis-face (list 'face ellipsis-face))))) + (lines (when-let* (hs-display-lines-hidden + (l (1- (count-lines b e))) + (l-str (format "%d %s" l + (if (= l 1) "line" "lines")))) + (funcall apply-face l-str))) + (tty-strings (and hs-display-lines-hidden (not (display-graphic-p)))) (string - (if (and (vectorp d-t-ellipsis) - ;; Ensure the vector is not empty - (not (length= d-t-ellipsis 0))) - (mapconcat - (lambda (g) - (apply #'propertize (char-to-string (glyph-char g)) - (if (glyph-face g) (list 'face (glyph-face g))))) - d-t-ellipsis))) - (string-face (if string (get-text-property 0 'face string))) - (lines (if-let* (hs-display-lines-hidden - (l (1- (count-lines b e))) - (l-str (concat (number-to-string l) - (if (= l 1) " line" " lines")))) - (apply #'propertize l-str - (if string-face - (list 'face string-face)))))) - (if string-face - ;; Return STRING and LINES if STRING has no face - (concat lines string) + (concat (and tty-strings (funcall apply-face "[")) + lines + (or ellipsis (truncate-string-ellipsis)) + (and tty-strings (funcall apply-face "]"))))) + (if ellipsis-face + ;; Return ELLIPSIS and LINES if ELLIPSIS has no face + string ;; Otherwise propertize both with `hs-ellipsis' - (propertize - (concat lines (or string (truncate-string-ellipsis))) - 'face 'hs-ellipsis)))) + (propertize string 'face 'hs-ellipsis)))) (defun hs-isearch-show (ov) "Delete overlay OV, and set `hs-headline' to nil. @@ -1179,7 +1183,7 @@ Return point, or nil if original point was not in a block." ;; 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))))) + (eq 'hs (get-char-property (if (bobp) (point) (1- (point))) 'invisible))))) ;; This function is not used anymore (Bug#700). (defun hs-c-like-adjust-block-beginning (initial) commit 2ab07033ea7c92c7f2f0bbc465d6729e1f2dcfbc Author: Eli Zaretskii Date: Sun Nov 16 09:36:03 2025 +0200 Workaround for MSVCRT stdio on MS-Windows for CJK locales MSVCRT implementation of stdio functions which output characters one by one fails for CJK double-byte encodings. Gnulib provides replacement functions which work around those bugs. This change makes Emacs and lib-src programs use the replacements when Emacs is linked against MSVCRT. * src/conf_post.h (fwrite, fprintf, printf, vfprintf, vprintf) [WINDOWSNT]: Redirect to Gnulib replacements when Emacs is linked against MSVCRT (as opposed to UCRT). Suggested by Bruno Haible . diff --git a/src/conf_post.h b/src/conf_post.h index aaf4fb59ffb..e946f6fc90b 100644 --- a/src/conf_post.h +++ b/src/conf_post.h @@ -396,3 +396,41 @@ extern int emacs_setenv_TZ (char const *); : S_ISCHR (mode) ? DT_CHR : S_ISFIFO (mode) ? DT_FIFO \ : S_ISSOCK (mode) ? DT_SOCK : DT_UNKNOWN) #endif /* MSDOS */ + +#if defined WINDOWSNT +# if !defined _UCRT +# include +# include +# include + +/* Workarounds for MSVCRT bugs. + + The functions below are in Gnulib, but their prototypes and + redirections must be here because the MS-Windows build omits the + Gnulib stdio-h module, which does the below in Gnulib's stdio.h + file, which is not used by the MS-Windows build. */ + +extern size_t gl_consolesafe_fwrite (const void *ptr, size_t size, + size_t nmemb, FILE *fp) + ARG_NONNULL ((1, 4)); +extern int gl_consolesafe_fprintf (FILE *restrict fp, + const char *restrict format, ...) + ATTRIBUTE_FORMAT_PRINTF (2, 3) + ARG_NONNULL ((1, 2)); +extern int gl_consolesafe_printf (const char *restrict format, ...) + ATTRIBUTE_FORMAT_PRINTF (1, 2) + ARG_NONNULL ((1)); +extern int gl_consolesafe_vfprintf (FILE *restrict fp, + const char *restrict format, va_list args) + ATTRIBUTE_FORMAT_PRINTF (2, 0) + ARG_NONNULL ((1, 2)); +extern int gl_consolesafe_vprintf (const char *restrict format, va_list args) + ATTRIBUTE_FORMAT_PRINTF (1, 0) + ARG_NONNULL ((1)); +# define fwrite gl_consolesafe_fwrite +# define fprintf gl_consolesafe_fprintf +# define printf gl_consolesafe_printf +# define vfprintf gl_consolesafe_vfprintf +# define vprintf gl_consolesafe_vprintf +# endif /* !_UCRT */ +#endif /* WINDOWSNT */ commit cc9ec87e537f890d8fb91c84578c3963c5d92f62 Author: João Távora Date: Sun Nov 16 02:08:21 2025 +0000 Eglot: rework semtok feature again This version was tested with the clangd, rust-analyzer and lean servers. Each server is slightly different. The main change is to recognize that that eglot--semtok-request is a promise to get some data for BEG to END and a promise to flush it. The eglot--semtok-inflight variable is overhauled so that it more precisely encodes this information. The lean server insists on many workspace/semanticTokens/refresh waiting for a textDocument/semanticTokens/full request from our part, but we don't give in. The spec doesn't actually say what types of requests the clients should issue. It eventualy gives up. The only thing we do on this request is to flush everything we know. After this flush, the font-lock machinery will know what to do. * lisp/progmodes/eglot.el (eglot--async-request): Return id. (eglot--semtok-inflight): Redesign variable. (eglot-handle-request): Just flush. (eglot--semtok-request): Rewrite. (eglot--semtok-font-lock): Tweak. diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 2b901ed7875..6b3398d9fd0 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -1957,7 +1957,8 @@ and also used as a hint of the request cancellation mechanism (see moreargs)))) (when (and hint eglot-advertise-cancellation) (push id - (plist-get eglot--inflight-async-requests hint)))))) + (plist-get eglot--inflight-async-requests hint))) + id))) (cl-defun eglot--delete-overlays (&optional (prop 'eglot--overlays)) (eglot--widening @@ -4623,21 +4624,22 @@ If NOERROR, return predicate, else erroring function." (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.") +(defvar-local eglot--semtok-inflight (make-hash-table) + "Info about 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) - (setq eglot--semtok-cache nil) - (font-lock-flush))))) + (eglot--widening + (font-lock-flush))))) (define-minor-mode eglot-semantic-tokens-mode "Minor mode for fontifying buffer with LSP server's semantic tokens." :global nil + (setq eglot--semtok-cache nil) + (clrhash eglot--semtok-inflight) (cond (eglot-semantic-tokens-mode (if (not (eglot-server-capable :semanticTokensProvider)) (eglot-semantic-tokens-mode -1) @@ -4658,77 +4660,94 @@ If NOERROR, return predicate, else erroring function." finally (cl-return (vconcat new (substring old-data old-token-index (length old-data)))))) -(cl-defun eglot--semtok-request (beg end) - "Ask server for tokens. Font-lock flush from BEG to END." - (let* ((buf (current-buffer)) - (id eglot--versioned-identifier)) - (cl-labels ((req (method from to params cont) - ;; (trace-values - ;; "Requesting: " from to method params) - (eglot--async-request - (eglot--current-server-or-lose) method params - :success-fn - (lambda (response) - ;; (trace-values "Response: " eglot--versioned-identifier id - ;; "edits: " - ;; (length (cl-getf response :edits)) - ;; "data: " - ;; (length (cl-getf response :data))) - (eglot--when-live-buffer buf - ;; A user edit may have come in while the request - ;; was inflight, changing the state of the buffer... - (when (eq id eglot--versioned-identifier) - (setq eglot--semtok-cache - (list :documentVersion id - :method method - :response (funcall cont response) - :from from :to to))) - ;; ... but we should flush unconditionally. If - ;; this response is out-of-date, - ;; `eglot--semtok-font-lock' should just trigger - ;; another request. - (cl-loop for (b . e) in eglot--semtok-inflight - do (font-lock-flush b e)) - ;; (trace-values "Flushed" (length eglot--semtok-inflight) - ;; "regions") - (setq eglot--semtok-inflight nil))) - :hint method)) - (cache-get (&rest path) - (let ((x eglot--semtok-cache)) - (dolist (op path x) (setq x (if (natnump op) (aref x op) - (plist-get x op))))))) - (push (cons beg end) eglot--semtok-inflight) - (cond - ((and (eglot-server-capable :semanticTokensProvider :full :delta) - (cache-get :response :data) - (not (eq :textDocument/semanticTokens/range (cache-get :method)))) - ;; JT@2025-11-12: many back-to-back calls for - ;; `eglot--semtok-request' and small regions occur even on - ;; 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 'skipped)) - (req :textDocument/semanticTokens/full/delta (point-min) (point-max) - (list :textDocument (eglot--TextDocumentIdentifier) - :previousResultId (cache-get :response :resultId)) - (lambda (response) - (if-let* ((edits (plist-get response :edits))) - (progn - (plist-put response :data - (eglot--semtok-apply-delta-edits - (cache-get :response :data) - edits))) - ;; (trace-values "Server send full response instead") - response)))) - ((eglot-server-capable :semanticTokensProvider :range) - (req :textDocument/semanticTokens/range beg end - (list :textDocument (eglot--TextDocumentIdentifier) - :range (eglot-region-range beg end)) - #'identity)) - (t - (req :textDocument/semanticTokens/full (point-min) (point-max) - (list :textDocument (eglot--TextDocumentIdentifier)) - #'identity)))))) +(cl-defun eglot--semtok-request (beg end &aux (doc-id eglot--versioned-identifier)) + "Ask for tokens. Arrange for BEG..END to be font-lock flushed." + (cl-labels + ((fullish-p (m) + (memq m '(:textDocument/semanticTokens/full/delta + :textDocument/semanticTokens/full))) + (req (method params cont + &aux + req-id (buf (current-buffer))) + (setq req-id + (eglot--async-request + (eglot--current-server-or-lose) method params + :success-fn + (lambda (response) + (eglot--when-live-buffer buf + (pcase-let ((`(,method ,doc-id ,regions) + (gethash req-id eglot--semtok-inflight))) + (remhash req-id eglot--semtok-inflight) + ;; (trace-values "Response: " + ;; method + ;; eglot--versioned-identifier doc-id + ;; "edits: " + ;; (length (cl-getf response :edits)) + ;; "data: " + ;; (length (cl-getf response :data))) + ;; A user edit may have come in while the request + ;; was inflight, changing the state of the buffer... + (when (eq doc-id eglot--versioned-identifier) + (setq eglot--semtok-cache + (list :documentVersion doc-id + :method method + :response (funcall cont response) + :valid (if (fullish-p method) + (eglot--widening + (cons (point-min) (point-max))) + (cons beg end))))) + ;; ... but we should flush unconditionally. If + ;; this response was out-of-date, + ;; `eglot--semtok-font-lock' should just trigger + ;; another request. + (cl-loop for (b . e) in regions + do (font-lock-flush b e)) + ;; (trace-values "Flushed" (length regions) + ;; "regions" regions) + ))) + :hint method)) + (puthash req-id (list method doc-id (list (cons beg end))) + eglot--semtok-inflight)) + (cache-get (&rest path) + (let ((x eglot--semtok-cache)) + (dolist (op path x) (setq x (if (natnump op) (aref x op) + (plist-get x op))))))) + ;; JT@2025-11-16: Many back-to-back calls for + ;; `eglot--semtok-request' and small regions occur even on + ;; trivial/fast edits. We try to send just one request. If there + ;; is a "full" or "full/delta" request in flight, we can piggy back + ;; onto it our region and our doc-id, and exit. That's because very + ;; likely it's not actually inflight yet (because of the "deferred" + ;; mechanism, it's waiting for didChange), so we can still do + ;; changes to the state it represents when it is actually sent. + (cl-loop for v being the hash-values of eglot--semtok-inflight + when (fullish-p (car v)) do + (push (cons beg end) (caddr v)) + (setf (cadr v) doc-id) + (cl-return-from eglot--semtok-request (cons 'skipped doc-id))) + (cond + ((and (eglot-server-capable :semanticTokensProvider :full :delta) + (cache-get :response :data) + (fullish-p (cache-get :method))) + (req :textDocument/semanticTokens/full/delta + (list :textDocument (eglot--TextDocumentIdentifier) + :previousResultId (cache-get :response :resultId)) + (lambda (response) + (if-let* ((edits (plist-get response :edits))) + (plist-put response :data + (eglot--semtok-apply-delta-edits + (cache-get :response :data) + edits)) + response)))) + ((eglot-server-capable :semanticTokensProvider :range) + (req :textDocument/semanticTokens/range + (list :textDocument (eglot--TextDocumentIdentifier) + :range (eglot-region-range beg end)) + #'identity)) + (t + (req :textDocument/semanticTokens/full + (list :textDocument (eglot--TextDocumentIdentifier)) + #'identity))))) (cl-defun eglot--semtok-font-lock (limit &aux (beg (point)) (end limit)) "Endeavor to semantically font-lock from point until LIMIT. @@ -4737,8 +4756,8 @@ request new information from the server and return and hope the font 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) - (<= end (plist-get eglot--semtok-cache :to)))) + (let ((valid (plist-get eglot--semtok-cache :valid))) + (<= (car valid) beg end (cdr valid)))) (eglot--semtok-font-lock-1 beg end)) (t (eglot--semtok-font-lock-2 beg end) commit 28f0658d8f5e9dce9564bb94826654e8e0e625fa Author: Eli Zaretskii Date: Sat Nov 15 19:48:08 2025 +0200 Fix processing sub-process exit when keyboard input is pending * src/process.c (wait_reading_process_output): Process status changes of sub-processes when called with read_kbd zero and some "keyboard input" is available, but no output from any sub-process. (Bug#79777) diff --git a/src/process.c b/src/process.c index 86e83e58c56..3d4ecb82b08 100644 --- a/src/process.c +++ b/src/process.c @@ -5844,6 +5844,18 @@ wait_reading_process_output (intmax_t time_limit, int nsecs, int read_kbd, if (nfds == 0) { + if (!read_kbd && update_tick != process_tick) + { + /* This is for the case where we bypassed a similar call + above, after the first thread_select, because some + input was available, but later found in the second + thread_select that input was only "from keyboard", + which we need to ignore because we were called with + read_kbd zero. We should therefore process the changed + status of sub-processes. */ + got_some_output = status_notify (NULL, wait_proc); + if (do_display) redisplay_preserve_echo_area (113); + } /* Exit the main loop if we've passed the requested timeout, or have read some bytes from our wait_proc (either directly in this call or indirectly through timers / process filters), commit fd5e3b123e7b000c2d80c73b2ff6970d84d7ce04 Author: Eli Zaretskii Date: Sat Nov 15 19:29:20 2025 +0200 Avoid extra newline in user-error logged in *Messages* * src/xdisp.c (reset_message_log_need_newline): New function. * src/lisp.h: Add prototype of 'reset_message_log_need_newline'. * src/print.c (print_error_message): Call 'reset_message_log_need_newline' to prevent message3 from outputting a newline after "user-error:". (Bug#79840) diff --git a/src/lisp.h b/src/lisp.h index 4c2f8e24987..eb1aa437dba 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -4418,6 +4418,7 @@ extern void message3_nolog (Lisp_Object); extern void message_dolog (const char *, ptrdiff_t, bool, bool); extern void message_with_string (const char *, Lisp_Object, bool); extern void message_log_maybe_newline (void); +extern void reset_message_log_need_newline (void); extern void update_echo_area (void); extern void truncate_echo_area (ptrdiff_t); extern void redisplay (void); diff --git a/src/print.c b/src/print.c index 55d8701ea0f..ac7f164ff1b 100644 --- a/src/print.c +++ b/src/print.c @@ -1130,6 +1130,8 @@ print_error_message (Lisp_Object data, Lisp_Object stream, const char *context, we throw any information away. */ && !NILP (XCAR (tail)) && NILP (XCDR (tail))) { + /* Prevent message3 from outputting a newline aftere "user-error:". */ + reset_message_log_need_newline (); message3 (XCAR (tail)); return; } diff --git a/src/xdisp.c b/src/xdisp.c index aef40c38e54..68ba07e6d61 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -12165,6 +12165,15 @@ message_log_maybe_newline (void) message_dolog ("", 0, true, false); } +/* Reset the message_log_need_newline flag, for when we split the + message into two or more parts, and don't want a newline between + them. */ + +void +reset_message_log_need_newline (void) +{ + message_log_need_newline = false; +} /* Add a string M of length NBYTES to the message log, optionally terminated with a newline when NLFLAG is true. MULTIBYTE, if commit a0761ddb4bd486ec148eba94eed130ddf28b937c Author: Paul Nelson Date: Sat Mar 29 19:07:13 2025 +0100 Improve foldout-exit-fold with negative arg (bug#77370) * lisp/foldout.el (foldout-exit-fold): When called with a negative prefix argument (so that the exited fold is not hidden), preserve the position of point and do not recenter. diff --git a/etc/NEWS b/etc/NEWS index 6bb32667731..d7c04f38ab4 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -2268,6 +2268,11 @@ string instead of preprending it and 'tmm-mid-prompt' to said entry. ** Foldout +*** Improved behavior of 'foldout-exit-fold' with negative prefix argument. +When 'foldout-exit-fold' is called with a negative argument (so that the +exited fold remains visible), the position of point and window view are +preserved. + --- *** New command 'foldout-widen-to-current-fold'. This command widens the view to the current fold level when in a fold, diff --git a/lisp/foldout.el b/lisp/foldout.el index fcc5e294561..748f56c0c9c 100644 --- a/lisp/foldout.el +++ b/lisp/foldout.el @@ -290,10 +290,10 @@ optional arg EXPOSURE \(interactively with prefix arg) changes this:- "Return to the ARG'th enclosing fold view. With ARG = 0 exit all folds. Normally causes exited folds to be hidden, but with ARG < 0, -ARG folds are -exited and text is left visible." +exited, text is left visible, and point position is preserved." (interactive "p") (let ((hide-fold t) start-marker end-marker - beginning-of-heading end-of-subtree) + beginning-of-heading end-of-subtree (original-point (point))) ;; check there are some folds to leave (if (null foldout-fold-list) @@ -369,7 +369,10 @@ exited and text is left visible." (if end-marker (1- (marker-position end-marker)) (point-max))))) - (recenter) + + (if hide-fold + (recenter) + (goto-char original-point)) ;; update the mode line (foldout-update-mode-line))) commit c9372ced9c03eac6dfaa2dedbb4033ce0a253499 Author: Eli Zaretskii Date: Sat Nov 15 07:25:19 2025 -0500 : Update ldefs-boot.el. diff --git a/lisp/ldefs-boot.el b/lisp/ldefs-boot.el index 7b0f356f476..d5b00ad9a2c 100644 --- a/lisp/ldefs-boot.el +++ b/lisp/ldefs-boot.el @@ -2800,7 +2800,7 @@ disabled. (put 'byte-compile-dynamic 'safe-local-variable 'booleanp) (put 'byte-compile-dynamic-docstrings 'safe-local-variable 'booleanp) (put 'byte-compile-error-on-warn 'safe-local-variable 'booleanp) -(put 'byte-compile-warnings 'safe-local-variable (lambda (v) (or (symbolp v) (null (delq nil (mapcar (lambda (x) (not (symbolp x))) v)))))) +(put 'byte-compile-warnings 'safe-local-variable (lambda (v) (or (symbolp v) (all #'symbolp v)))) (autoload 'byte-compile-warning-enabled-p "bytecomp" "\ Return non-nil if WARNING is enabled, according to `byte-compile-warnings'. @@ -5424,7 +5424,7 @@ A `cond*' construct is a series of clauses, and a clause normally has the form (CONDITION BODY...). CONDITION can be a Lisp expression, as in `cond'. -Or it can be one of`(bind* BINDINGS...)', `(match* PATTERN DATUM)', +Or it can be one of `(bind* BINDINGS...)', `(match* PATTERN DATUM)', or `(pcase* PATTERN DATUM)', `(bind* BINDINGS...)' means to bind BINDINGS (as if they were in `let*') @@ -5436,6 +5436,10 @@ and runs the body of the clause if the first binding's value is non-nil. For its patterns, see `match*'. The condition counts as true if PATTERN matches DATUM. +`(bind-and* BINDINGS...)' means to bind BINDINGS (as if they were in +`if-let*') for only the the body of the clause. If any expression +evaluates to nil, the condition counts as false. + `(pcase* PATTERN DATUM)' means to match DATUM against the pattern PATTERN, using the same pattern syntax as `pcase'. The condition counts as true if PATTERN matches DATUM. @@ -5455,7 +5459,84 @@ are passed along to the rest of the clauses in this `cond*' construct. \\[match*] for documentation of the patterns for use in `match*'. (fn &rest CLAUSES)" nil t) -(register-definition-prefixes "cond-star" '("cond*-" "match*")) +(autoload 'match* "cond-star" "\ +This specifies matching DATUM against PATTERN. +It is not really a Lisp function, and it is meaningful +only in the CONDITION of a `cond*' clause. + +`_' matches any value. +KEYWORD matches that keyword. +nil matches nil. +t matches t. +SYMBOL matches any value and binds SYMBOL to that value. + If SYMBOL has been matched and bound earlier in this pattern, + it matches here the same value that it matched before. +REGEXP matches a string if REGEXP matches it. + The match must cover the entire string from its first char to its last. +ATOM (meaning any other kind of non-list not described above) + matches anything `equal' to it. +(rx REGEXP) uses a regexp specified in s-expression form, + as in the function `rx', and matches the data that way. +(rx REGEXP SYM0 SYM1...) uses a regexp specified in s-expression form, + and binds the symbols SYM0, SYM1, and so on + to (match-string 0 DATUM), (match-string 1 DATUM), and so on. + You can use as many SYMs as regexp matching supports. + +\\=`OBJECT matches any value `equal' to OBJECT. +(cons CARPAT CDRPAT) + matches a cons cell if CARPAT matches its car and CDRPAT matches its cdr. +(list ELTPATS...) + matches a list if the ELTPATS match its elements. + The first ELTPAT should match the list's first element. + The second ELTPAT should match the list's second element. And so on. +(vector ELTPATS...) + matches a vector if the ELTPATS match its elements. + The first ELTPAT should match the vector's first element. + The second ELTPAT should match the vector's second element. And so on. +(cdr PATTERN) matches PATTERN with strict checking of cdrs. + That means that `list' patterns verify that the final cdr is nil. + Strict checking is the default. +(cdr-safe PATTERN) matches PATTERN with lax checking of cdrs. + That means that `list' patterns do not examine the final cdr. +(and CONJUNCTS...) matches each of the CONJUNCTS against the same data. + If all of them match, this pattern succeeds. + If one CONJUNCT fails, this pattern fails and does not try more CONJUNCTS. +(or DISJUNCTS...) matches each of the DISJUNCTS against the same data. + If one DISJUNCT succeeds, this pattern succeeds + and does not try more DISJUNCTs. + If all of them fail, this pattern fails. +(COND*-EXPANDER ...) + Here the car is a symbol that has a `cond*-expander' property + which defines how to handle it in a pattern. The property value + is a function. Trying to match such a pattern calls that + function with one argument, the pattern in question (including its car). + The function should return an equivalent pattern + to be matched instead. +(PREDICATE SYMBOL) + matches datum if (PREDICATE DATUM) is true, + then binds SYMBOL to DATUM. +(PREDICATE SYMBOL MORE-ARGS...) + matches datum if (PREDICATE DATUM MORE-ARGS...) is true, + then binds SYMBOL to DATUM. + MORE-ARGS... can refer to symbols bound earlier in the pattern. +(constrain SYMBOL EXP) + matches datum if the form EXP is true. + EXP can refer to symbols bound earlier in the pattern. + +(fn PATTERN DATUM)" nil t) +(autoload 'bind* "cond-star" "\ +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. + +(fn &rest BINDINGS)" nil t) +(autoload 'bind-and* "cond-star" "\ +This macro evaluates BINDINGS like `if-let*'. +It is not really a Lisp function, and it is meaningful +only in the CONDITION of a `cond*' clause. + +(fn &rest BINDINGS)" nil t) +(register-definition-prefixes "cond-star" '("cond*-")) ;;; Generated autoloads from textmodes/conf-mode.el @@ -9632,7 +9713,7 @@ Turn on EDT Emulation." t) ;;; Generated autoloads from progmodes/eglot.el -(push '(eglot 1 18) package--builtin-versions) +(push '(eglot 1 19) package--builtin-versions) (define-obsolete-function-alias 'eglot-update #'eglot-upgrade-eglot "29.1") (autoload 'eglot "eglot" "\ Start LSP server for PROJECT's buffers under MANAGED-MAJOR-MODES. @@ -9974,6 +10055,73 @@ mode hooks. (make-obsolete 'elide-head 'elide-head-mode "29.1") (register-definition-prefixes "elide-head" '("elide-head-")) + +;;; Generated autoloads from emacs-lisp/elisp-scope.el + +(autoload 'elisp-scope-get-symbol-role-property "elisp-scope" "\ +Return value of property PROP for symbol role ROLE. + +(fn ROLE PROP)") +(autoload 'elisp-scope-set-symbol-role-property "elisp-scope" "\ +Set value of property PROP for symbol role ROLE to VALUE. + +(fn ROLE PROP VALUE)") +(autoload 'elisp-scope-symbol-role-p "elisp-scope" "\ +Check whether a symbol SYM is the name of a \"symbol role\". + +(fn SYM)") +(autoload 'elisp-scope-add-symbol-roles-to-describe-symbol "elisp-scope") +(autoload 'elisp-scope-describe-symbol-role "elisp-scope" "\ +Describe ROLE of a symbol. +Interactively, prompt for ROLE. + +(fn ROLE &rest _)" t) +(autoload 'elisp-scope-analyze-form "elisp-scope" "\ +Read and analyze code from STREAM, reporting findings via CALLBACK. + +Call CALLBACK for each analyzed symbol SYM with arguments ROLE, POS, +SYM, ID and DEF, where ROLE is a symbol that specifies the semantics of +SYM; POS is the position of SYM in STREAM; ID is an object that uniquely +identifies (co-)occurrences of SYM in the current defun; and DEF is the +position in which SYM is locally defined, or nil. If SYM is itself a +binding occurrence, then POS and DEF are equal. If SYM is not lexically +bound, then DEF is nil. + +If STREAM is nil, it defaults to the current buffer. When reading from +the current buffer, this function leaves point at the end of the form. + +This function recursively analyzes Lisp forms (HEAD . TAIL), usually +starting with a top-level form, by inspecting HEAD at each level: + +- If HEAD is a symbol with a non-nil `elisp-scope-analyzer' symbol + property, then the value of that property specifies a bespoke analzyer + function, AF, that is called as (AF HEAD . TAIL) to analyze the form. + See more details about writing analyzer functions below. + +- If HEAD satisfies `functionp', which means it is a function in the + running Emacs session, analzye the form as a function call. + +- If HEAD is a safe macro (see `elisp-scope-safe-macro-p'), expand it + and analyze the resulting form. + +- If HEAD is unknown, then the arguments in TAIL are ignored, unless + `elisp-scope-assume-func' is non-nil, in which case they are analyzed + as evaluated forms (i.e. HEAD is assumed to be a function). + +An analyzer (function specified via the `elisp-scope-analyzer' property) +can use the functions `elisp-scope-report-s', `elisp-scope-1' and +`elisp-scope-n' to analyze its arguments, and it can consult the +variable `elisp-scope-output-spec' to obtain the expected output spec of +the analyzed form. For example, the following is a suitable analyzer +for the `identity' function: + + (lambda (fsym arg) + (elisp-scope-report-s fsym \\='function) + (elisp-scope-1 arg elisp-scope-output-spec)) + +(fn CALLBACK &optional STREAM)") +(register-definition-prefixes "elisp-scope" '("elisp-scope-")) + ;;; Generated autoloads from progmodes/elixir-ts-mode.el @@ -10675,7 +10823,7 @@ Look at CONFIG and try to expand GROUP. ;;; Generated autoloads from erc/erc.el -(push '(erc 5 6 1 -4) package--builtin-versions) +(push '(erc 5 6 2 -4) package--builtin-versions) (dolist (symbol '( erc-sasl erc-spelling ; 29 erc-imenu erc-nicks)) ; 30 (custom-add-load symbol symbol)) @@ -13298,7 +13446,7 @@ lines. ;;; Generated autoloads from progmodes/flymake.el -(push '(flymake 1 4 1) package--builtin-versions) +(push '(flymake 1 4 3) package--builtin-versions) (autoload 'flymake-log "flymake" "\ Log, at level LEVEL, the message MSG formatted with ARGS. LEVEL is passed to `display-warning', which is used to display @@ -16624,44 +16772,7 @@ disabled. ;;; Generated autoloads from progmodes/hideshow.el -(defvar hs-special-modes-alist '((c-mode "{" "}" "/[*/]" nil nil) (c-ts-mode "{" "}" "/[*/]" nil nil) (c++-mode "{" "}" "/[*/]" nil nil) (c++-ts-mode "{" "}" "/[*/]" nil nil) (bibtex-mode ("@\\S(*\\(\\s(\\)" 1)) (java-mode "{" "}" "/[*/]" nil nil) (java-ts-mode "{" "}" "/[*/]" nil nil) (js-mode "{" "}" "/[*/]" nil) (js-ts-mode "{" "}" "/[*/]" nil) (mhtml-mode "{\\|<[^/>]*?" "}\\|]*[^/]>" "