commit 0eec0d5174a38c2f3a4a2c38e3af9da4a924b0bc Author: Stefan Monnier Date: Mon Dec 15 22:04:50 2025 -0500 (cconv-make-interpreted-closure): Be more conservative We can't use `cconv-make-interpreted-closure` until it's compiled because it would get called in an infinite recursion. The same holds for a lof of the code needed to compute the interpreted closures used during the computation of `cconv-make-interpreted-closure`. We used to work around this inf-loop in `loadup.el` with a hack that checks if two specific functions have been compiled. In practice that worked well enough in normal use, but was brittle if you ever tried to load `cconv.el`, or Edebug or `M-C-x` that code for whatever reason. So we replace it with a more conservative approach which just short-circuits any recursive invocation. It makes it conservative in that it will sometimes redirect the call to the "dumb" `make-interpreted-closure` even though it would be able to complete the call properly, but it should be infrequent enough to be worth the added reliability. * lisp/loadup.el: Simplify setting of `internal-make-interpreted-closure-function`. * lisp/emacs-lisp/cconv.el (cconv-make-interpreted-closure): Avoid recursive invocations. diff --git a/lisp/emacs-lisp/cconv.el b/lisp/emacs-lisp/cconv.el index ad7fc1de5c2..ce5d990c2f5 100644 --- a/lisp/emacs-lisp/cconv.el +++ b/lisp/emacs-lisp/cconv.el @@ -910,10 +910,28 @@ FUN is the closure's source code, must be a lambda form. ENV is the runtime representation of the lexical environment, i.e. a list whose elements can be either plain symbols (which indicate that this symbol should use dynamic scoping) or pairs (SYMBOL . VALUE) -for the lexical bindings." +for the lexical bindings. + +The purpose of this function is to reduce ENV to the part actually used by the +function, so we are closer to the ideal of \"safe for space\". In practice it has +two benefits: it makes closures a bit more predictable and human-readable, +and more importantly it avoids accidentally including values that we cannot +print `read'ably, which can break code that can save closures in files, such as +in bookmark or savehist." (cl-assert (consp body)) (cl-assert (listp args)) - (let ((lexvars (delq nil (mapcar #'car-safe env)))) + ;; Temporarily rebind `internal-make-interpreted-closure-function' + ;; to avoid infinite recursion during bootstrap, but also if you + ;; happen to reload or Edebug `cconv.el' or `macroexp.el'. + ;; FIXME: This means that we will not reduce the ENV of interpreted + ;; closures built during the computation of another interpreted closure, + ;; as in: cconv-make-interpreted-closure => macroexpand-all + ;; => cl-typep--inliner => cconv-make-interpreted-closure. + ;; We expect these cases are sufficiently infrequent and those closures are + ;; sufficiently short-lived that it's worth the tradeoff of avoiding + ;; nasty inf-loops when some of the code of cconv itself is interpreted. + (let ((internal-make-interpreted-closure-function #'make-interpreted-closure) + (lexvars (delq nil (mapcar #'car-safe env)))) (if (or ;; Functions with a `:closure-dont-trim-context' marker ;; should keep their whole context untrimmed (bug#59213). diff --git a/lisp/loadup.el b/lisp/loadup.el index f5a725708e5..7178639fb1e 100644 --- a/lisp/loadup.el +++ b/lisp/loadup.el @@ -388,8 +388,10 @@ (load "emacs-lisp/eldoc") (load "emacs-lisp/cconv") -(when (and (compiled-function-p (symbol-function 'cconv-fv)) - (compiled-function-p (symbol-function 'macroexpand-all))) +;; It should be safe to set `internal-make-interpreted-closure-function' +;; unconditionally, but if cconv and friends haven't been compiled yet, +;; it's excruciatingly slow. +(when (compiled-function-p (symbol-function 'cconv-fv)) (setq internal-make-interpreted-closure-function #'cconv-make-interpreted-closure)) (dlet ((cus-start--preload t)) ;; Tell `cus-start' we're preloading. commit 4aff16bf9e8be9e45b5ac5b98a323957e3af6444 Author: João Távora Date: Mon Dec 15 17:14:41 2025 +0000 Eglot: improve pull diagnostics support * lisp/progmodes/eglot.el (eglot--diagnostics): Move up here. (eglot--managed-mode): Use eglot--flymake-push. (eglot-handle-notification): Simplify. (eglot-flymake-backend): Simplify. (eglot--flymake-pull): Rewrite. (eglot--flymake-push): Tweak. * etc/EGLOT-NEWS: Improve slightly. diff --git a/etc/EGLOT-NEWS b/etc/EGLOT-NEWS index fbd1a87ec5b..fea878a47ca 100644 --- a/etc/EGLOT-NEWS +++ b/etc/EGLOT-NEWS @@ -23,15 +23,10 @@ https://github.com/joaotavora/eglot/issues/1234. ** Support for pull diagnostics (github#1559, github#1290) For servers supporting the 'diagnosticProvider' capability, Eglot -requests diagnostics on-demand rather than relying solely on -server-pushed 'publishDiagnostics' notifications. This may provide a -more responsive user experience. The 'ty' server is known to support it -while the 'tsgo' server is known to support is exclusively. However, no -server has been found to support the 'relatedDocumentSupport' -sub-capability, which e.g. introducing a problem in one file to bring -diagnostics for another related file. The traditional push diagnostics, -as supported by some servers, are at the moment still a superior user -experience in this particular regard. +requests diagnostics explicitly rather than relying on sporadic +'publishDiagnostics' notifications, aka. "push diagnostics". The 'tsgo' +server is known to support the "pull" variant exclusively, while the +'ty' server is known to support it alongside "push". ** Support for semantic tokens (bug#79374) diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 2f140ff510a..aec949fc5b1 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -2221,6 +2221,14 @@ Use `eglot-managed-p' to determine if current buffer is managed.") (defvar eglot--highlights nil "Overlays for `eglot-highlight-eldoc-function'.") +(defvar-local eglot--diagnostics nil + "A list (DIAGNOSTICS VERSION RESULT-ID) for current buffer. +DIAGNOSTICS is a list of Flymake diagnostics objects. VERSION is the +LSP Document version reported for DIAGNOSTICS (comparable to +`eglot--docver') or nil if server didn't bother. RESULT-ID is an +optional string identifying this diagnostic result for pull +diagnostics, used for incremental updates.") + (defvar-local eglot--suggestion-overlay (make-overlay 0 0) "Overlay for `eglot-code-action-suggestion'.") @@ -2300,7 +2308,8 @@ Use `eglot-managed-p' to determine if current buffer is managed.") do (set (make-local-variable var) saved-binding)) (remove-function (local 'imenu-create-index-function) #'eglot-imenu) (when eglot--flymake-push-report-fn - (eglot--flymake-push nil nil) + (setq eglot--diagnostics nil) + (eglot--flymake-push) (setq eglot--flymake-push-report-fn nil)) (run-hooks 'eglot-managed-mode-hook) (let ((server eglot--cached-server)) @@ -2337,12 +2346,6 @@ Use `eglot-managed-p' to determine if current buffer is managed.") (or (eglot-current-server) (jsonrpc-error "No current JSON-RPC connection"))) -(defvar-local eglot--diagnostics nil - "A cons (DIAGNOSTICS . VERSION) for current buffer. -DIAGNOSTICS is a list of Flymake diagnostics objects. VERSION is the -LSP Document version reported for DIAGNOSTICS (comparable to -`eglot--docver') or nil if server didn't bother.") - (defvar revert-buffer-preserve-modes) (defvar eglot-semantic-tokens-mode) ;; forward decl (defun eglot--after-revert-hook () @@ -2780,14 +2783,12 @@ expensive cached value of `file-truename'.") for diag-spec across diagnostics collect (eglot--flymake-make-diag diag-spec version) into diags - finally (cond ((and - ;; only add to current report if Flymake - ;; starts on idle-timer (github#958) - (not (null flymake-no-changes-timeout)) - eglot--flymake-push-report-fn) - (eglot--flymake-push diags version)) - (t - (setq eglot--diagnostics (cons diags version)))))) + finally + (setq eglot--diagnostics (list diags version nil)) + (when (not (null flymake-no-changes-timeout )) + ;; only add to current report if Flymake + ;; starts on idle-timer (github#957) + (eglot--flymake-push)))) (cl-loop for diag-spec across diagnostics collect (eglot--dbind ((Diagnostic) code range message severity source) diag-spec @@ -3229,66 +3230,86 @@ publishes diagnostics. Between calls to this function, REPORT-FN may be called multiple times (respecting the protocol of `flymake-diagnostic-functions')." (cond (eglot--managed-mode + (setq eglot--flymake-push-report-fn report-fn) (cond ;; Use pull diagnostics if server supports it ((eglot-server-capable :diagnosticProvider) - (eglot--flymake-pull report-fn)) - ;; Otherwise use traditional push diagnostics - (t - (setq eglot--flymake-push-report-fn report-fn) - (eglot--flymake-push (car eglot--diagnostics) - (cdr eglot--diagnostics))))) + (eglot--flymake-pull)) + ;; Otherwise push whatever we might have, and wait for + ;; `textDocument/publishDiagnostics'. + (t (eglot--flymake-push)))) (t (funcall report-fn nil)))) -(defun eglot--flymake-pull (report-fn) - "Pull diagnostics from server and call REPORT-FN." - (let ((buf (current-buffer)) - (server (eglot--current-server-or-lose)) - (version eglot--docver)) - (eglot--async-request - server - :textDocument/diagnostic - (list :textDocument (eglot--TextDocumentIdentifier)) - :success-fn - (lambda (result) - (eglot--when-live-buffer buf - (eglot--dbind ((DocumentDiagnosticReport) kind items) result - (pcase kind - ("full" - (let ((diags - (cl-loop - for diag-spec across items - collect (eglot--flymake-make-diag diag-spec version)))) - (setq eglot--diagnostics (cons diags version)))) - ("unchanged" - ;; Server says diagnostics haven't changed, report what we have - )) - (funcall report-fn (car eglot--diagnostics) - :region (cons (point-min) (point-max)))))) - :hint :textDocument/diagnostic))) - -(defun eglot--flymake-push (diags version) - "Push previously collected diagnostics to `eglot--flymake-push-report-fn'." - (save-restriction - (widen) - (if (or (null version) (= version eglot--docver)) - (funcall eglot--flymake-push-report-fn diags - ;; If the buffer hasn't changed since last - ;; call to the report function, flymake won't - ;; delete old diagnostics. Using :region - ;; keyword forces flymake to delete - ;; them (github#159). - :region (cons (point-min) (point-max))) - ;; Here, we don't have anything up to date to give Flymake: we - ;; just want to keep whatever diagnostics it has annotated in - ;; the buffer. However, as a nice-to-have, we still want to - ;; signal we're alive and clear a possible "Wait" state. We - ;; hackingly achieve this by reporting an empty list and making - ;; sure it pertains to a 0-length region. - (funcall eglot--flymake-push-report-fn nil - :region (cons (point-min) (point-min))))) - (setq eglot--diagnostics (cons diags version))) +(cl-defun eglot--flymake-pull (&aux (server (eglot--current-server-or-lose)) + (origin (current-buffer))) + "Pull diagnostics from server, for all managed buffers. +When response arrives call registered `eglot--flymake-push-report-fn'." + (cl-flet + ((pull-for (buf &optional then) + (with-current-buffer buf + (let ((version eglot--docver) + (prev-result-id (nth 2 eglot--diagnostics))) + (eglot--async-request + server + :textDocument/diagnostic + (append + `(:textDocument ,(eglot--TextDocumentIdentifier) + ,@(when prev-result-id + `(:previousResultId ,prev-result-id)))) + :success-fn + (eglot--lambda ((DocumentDiagnosticReport) kind items resultId) + (eglot--when-live-buffer buf + (pcase kind + ("full" + (setq eglot--diagnostics + (list + (cl-loop + for spec across items + collect (eglot--flymake-make-diag spec version)) + version + resultId)) + (eglot--flymake-push)) + ("unchanged" + (when (eq buf origin) (eglot--flymake-push 'void))))) + (when then (funcall then))) + :hint :textDocument/diagnostic))))) + ;; JT@2025-12-15: No known server yet supports "relatedDocuments" so + ;; the only way we have to get related diagnostics is to explicitly + ;; request them of all open documents. Moreover, experience has + ;; shown this needs to happen after the 'origin''s response. + (pull-for origin + (unless (zerop eglot--docver) + (lambda () + (mapc #'pull-for + (remove origin (eglot--managed-buffers server)))))))) + +(cl-defun eglot--flymake-push + (&optional void &aux (diags (nth 0 eglot--diagnostics)) + (version (nth 1 eglot--diagnostics))) + "Push previously collected diagnostics to `eglot--flymake-push-report-fn'. +If VOID, knowingly push a dummy do-nothing update." + (unless eglot--flymake-push-report-fn + ;; Occasionally called from contexts where report-fn not setup, such + ;; as a `didOpen''ed but yet undisplayed buffer. + (cl-return-from eglot--flymake-push)) + (eglot--widening + (if (or void (and version (< version eglot--docver))) + ;; Here, we don't have anything interesting to give to Flymake: we + ;; just want to keep whatever diagnostics it has annotated in the + ;; buffer. However, as a nice-to-have, we still want to signal + ;; we're alive and clear a possible "Wait" state. We hackingly + ;; achieve this by reporting an empty list and making sure it + ;; pertains to a 0-length region. + (funcall eglot--flymake-push-report-fn nil + :region (cons (point-min) (point-min))) + (funcall eglot--flymake-push-report-fn diags + ;; If the buffer hasn't changed since last + ;; call to the report function, flymake won't + ;; delete old diagnostics. Using :region + ;; keyword forces flymake to delete + ;; them (github#159). + :region (cons (point-min) (point-max)))))) (defun eglot-xref-backend () "Eglot xref backend." 'eglot) commit 45285a41d4f784b6ec9d9f808920d20add59e17b Author: Eli Zaretskii Date: Mon Dec 15 18:23:53 2025 +0200 ; * lisp/simple.el (kill-visual-line): Fix a thinko in last change. diff --git a/lisp/simple.el b/lisp/simple.el index 6222608bbd0..c1d876de63e 100644 --- a/lisp/simple.el +++ b/lisp/simple.el @@ -8609,7 +8609,7 @@ even beep.)" ;; like display or overlay strings, intangible text, etc.: ;; otherwise, we don't want to kill a character that's ;; unrelated to the place where the visual line wraps. - (and (numberp (nth 6 (posn-at-point))) + (and (numberp (cdr (nth 6 (posn-at-point)))) (= (cdr (nth 6 (posn-at-point))) orig-vlnum) ;; Make sure we delete the character where the line wraps ;; under visual-line-mode, be it whitespace or a commit 1b31023dabc8df23563eccf28490a153a4c22668 Author: Michael Albinus Date: Mon Dec 15 15:20:29 2025 +0100 Finish process error buffer support in tramp-smb.el * doc/misc/tramp.texi (Remote processes): Provide more details in "Running remote processes on MS Windows hosts". * lisp/net/tramp-smb.el (tramp-smb-handle-make-process): Suppress lock files. * test/lisp/net/tramp-tests.el (auto-revert-notify-watch-descriptor) (auto-revert-remote-files, auto-revert-use-notify): Declare. Set proper values. (tramp-test30-make-process): Adapt test. diff --git a/doc/misc/tramp.texi b/doc/misc/tramp.texi index 72f8bde47cf..1f5b790016f 100644 --- a/doc/misc/tramp.texi +++ b/doc/misc/tramp.texi @@ -4501,8 +4501,20 @@ remote MS Windows host, and @value{tramp} uses it for @code{make-process}, @code{process-file} and @code{start-file-process}. It does not work for remote Samba servers. -Redirection from stdin and to sterr as well as process filters are not -supported (yet). +Redirection from stdin and process filters are not supported (yet). + +Check, that the default remote temporary file directory is usable. +Otherwise, change it, for example (with adapted @t{"user"} and +@t{"host"}): + +@lisp +(add-to-list 'tramp-connection-properties + (list (regexp-quote "host") + "tmpdir" "/Users/user/AppData/Local/Temp")) +@end lisp + +@noindent +@xref{Predefined connection information}. @c FIXME: Verify powershell version. @vindex tramp-smb-winexe-program diff --git a/lisp/net/tramp-smb.el b/lisp/net/tramp-smb.el index 3269efadc95..a82eaae886d 100644 --- a/lisp/net/tramp-smb.el +++ b/lisp/net/tramp-smb.el @@ -1366,7 +1366,8 @@ will be used." (lambda (_proc _msg) (with-current-buffer stderr (auto-revert-tail-mode -1) - (revert-buffer nil 'noconfirm)) + (let ((remote-file-name-inhibit-locks t)) + (revert-buffer nil 'noconfirm))) (ignore-errors (delete-file remote-tmpstderr))))) ;; Return value. diff --git a/test/lisp/net/tramp-tests.el b/test/lisp/net/tramp-tests.el index 96e4e28de46..65f0f15e8d5 100644 --- a/test/lisp/net/tramp-tests.el +++ b/test/lisp/net/tramp-tests.el @@ -80,6 +80,9 @@ (declare-function tramp-method-out-of-band-p "tramp-sh") (declare-function tramp-smb-get-localname "tramp-smb") (defvar ange-ftp-make-backup-files) +(defvar auto-revert-notify-watch-descriptor) +(defvar auto-revert-remote-files) +(defvar auto-revert-use-notify) (defvar comp-warn-primitives) (defvar tramp-connection-properties) (defvar tramp-copy-size-limit) @@ -147,6 +150,8 @@ (setq auth-source-cache-expiry nil auth-source-save-behavior nil + auto-revert-remote-files t + auto-revert-use-notify t ert-batch-backtrace-right-margin nil password-cache-expiry nil remote-file-name-inhibit-cache nil @@ -5861,10 +5866,10 @@ If UNSTABLE is non-nil, the test is tagged as `:unstable'." (rx (| "No such file or directory" "Cannot find path")) (buffer-string))) - (if (processp (get-buffer-process stderr)) - (while (accept-process-output - (get-buffer-process stderr) 0 nil t)) - (revert-buffer nil 'noconfirm)))) + (when-let* ((p (or (get-buffer-process stderr) + auto-revert-notify-watch-descriptor)) + ((processp p))) + (while (accept-process-output p 0 nil t))))) (delete-process proc) (should (string-match-p @@ -6209,7 +6214,7 @@ INPUT, if non-nil, is a string sent to the process." (ignore-errors (delete-file tmp-name))) ;; Test `{async-}shell-command' with error buffer. - ;; FIXME: tramp-smb.el should implement this. + ;; Method "smb" does not support ">&2" construct. (unless (or (tramp--test-smb-p) (tramp-direct-async-process-p)) (let ((stderr (generate-new-buffer "*stderr*"))) (unwind-protect