commit b1ec006e9f9196c1f993e0373b20a5ffb548bb4f (HEAD, refs/remotes/origin/master) Author: Gerd Möllmann Date: Thu Jan 23 07:51:55 2025 +0100 Fix setting a tty's top frame when switching frames * src/frame.c (do_switch_frame): Compare root frames correctly. diff --git a/src/frame.c b/src/frame.c index c4afbe6bbb6..258bf08788f 100644 --- a/src/frame.c +++ b/src/frame.c @@ -1773,15 +1773,19 @@ do_switch_frame (Lisp_Object frame, int track, int for_deletion, Lisp_Object nor struct tty_display_info *tty = FRAME_TTY (f); Lisp_Object top_frame = tty->top_frame; - /* Don't mark the frame garbaged if we are switching to the frame - that is already the top frame of that TTY. */ - if (!EQ (frame, top_frame) && root_frame (f) != XFRAME (top_frame)) + /* Switching to a frame on a different root frame is special. The + old root frame has to be marked invisible, and the new root + frame has to be made visible. */ + if (!EQ (frame, top_frame) + && (!FRAMEP (top_frame) + || root_frame (f) != root_frame (XFRAME (top_frame)))) { struct frame *new_root = root_frame (f); SET_FRAME_VISIBLE (new_root, true); SET_FRAME_VISIBLE (f, true); - /* Mark previously displayed frame as no longer visible. */ + /* Mark previously displayed root frame as no longer + visible. */ if (FRAMEP (top_frame)) { struct frame *top = XFRAME (top_frame); @@ -1792,7 +1796,7 @@ do_switch_frame (Lisp_Object frame, int track, int for_deletion, Lisp_Object nor tty->top_frame = frame; - /* FIXME: Why is it correct to set FrameCols/Rows? */ + /* FIXME: Why is it correct to set FrameCols/Rows here? */ if (!FRAME_PARENT_FRAME (f)) { /* If the new TTY frame changed dimensions, we need to commit 1dc529d4638469b828a24006dae5830dd9f794d0 Author: Eli Zaretskii Date: Thu Jan 23 07:45:54 2025 +0200 ; * doc/lispref/elisp.texi (Top): Move menu item to submenu. diff --git a/doc/lispref/elisp.texi b/doc/lispref/elisp.texi index 90fa5277183..c6ac904a704 100644 --- a/doc/lispref/elisp.texi +++ b/doc/lispref/elisp.texi @@ -564,7 +564,6 @@ Functions * Lambda Expressions:: How functions are expressed as Lisp objects. * Function Names:: A symbol can serve as the name of a function. * Defining Functions:: Lisp expressions for defining functions. -* Finding Definitions:: Finding function definitions created by macros. * Calling Functions:: How to use an existing function. * Mapping Functions:: Applying a function to each element of a list, etc. * Anonymous Functions:: Lambda expressions are functions with no names. @@ -584,6 +583,10 @@ Functions that have a special bearing on how functions work. +Defining Functions + +* Finding Definitions:: Finding function definitions created by macros. + Lambda Expressions * Lambda Components:: The parts of a lambda expression. commit 796665b32e9431a923881eb4c02e45bf1639a7ff Author: Eli Zaretskii Date: Thu Jan 23 07:43:40 2025 +0200 ; * doc/lispref/functions.texi (Functions): Remove extraneous menu item. diff --git a/doc/lispref/functions.texi b/doc/lispref/functions.texi index 7176c1faee5..770d54a0038 100644 --- a/doc/lispref/functions.texi +++ b/doc/lispref/functions.texi @@ -15,7 +15,6 @@ define them. * Lambda Expressions:: How functions are expressed as Lisp objects. * Function Names:: A symbol can serve as the name of a function. * Defining Functions:: Lisp expressions for defining functions. -* Finding Definitions:: Finding function definitions created by macros. * Calling Functions:: How to use an existing function. * Mapping Functions:: Applying a function to each element of a list, etc. * Anonymous Functions:: Lambda expressions are functions with no names. commit 90b4c1b6f264773e33e7984af9a95b4b503ed02f Author: Stephen Gildea Date: Wed Jan 22 19:22:24 2025 -0800 Fix ERT / find-function type integration typos * lisp/emacs-lisp/ert.el (ert-test-location, ert-describe-test): Correctly type the test object as 'ert--test'. * lisp/emacs-lisp/find-func.el (find-ert-deftest-regexp): Remove this duplicate, broken customization. diff --git a/lisp/emacs-lisp/ert.el b/lisp/emacs-lisp/ert.el index f25ba8a529c..5d9230fa3cf 100644 --- a/lisp/emacs-lisp/ert.el +++ b/lisp/emacs-lisp/ert.el @@ -1355,7 +1355,7 @@ RESULT must be an `ert-test-result-with-condition'." (when-let* ((loc (ignore-errors (find-function-search-for-symbol - (ert-test-name test) 'ert-deftest (ert-test-file-name test))))) + (ert-test-name test) 'ert--test (ert-test-file-name test))))) (let* ((buffer (car loc)) (point (cdr loc)) (file (file-relative-name (buffer-file-name buffer))) @@ -2467,7 +2467,9 @@ To be used in the ERT results buffer." (defun ert--test-name-button-action (button) "Find the definition of the test BUTTON belongs to, in another window." - (let ((name (button-get button 'ert-test-name))) + ;; work with either ert-insert-test-name-button or help-xref-button + (let ((name (or (button-get button 'ert-test-name) + (car (button-get button 'help-args))))) (ert-find-test-other-window name))) (defun ert--ewoc-position (ewoc node) @@ -2814,7 +2816,8 @@ To be used in the ERT results buffer." (file-name-nondirectory file-name))) (save-excursion (re-search-backward (substitute-command-keys "`\\([^`']+\\)'")) - (help-xref-button 1 'help-function-def test-name file-name))) + (help-xref-button 1 'ert--test-name-button + test-name file-name))) (insert ".") (fill-region-as-paragraph (point-min) (point)) (insert "\n\n") @@ -2855,7 +2858,7 @@ To be used in the ERT results buffer." (defun ert--unload-function () "Unload function to undo the side-effects of loading ert.el." - (ert--remove-from-list 'find-function-regexp-alist 'ert-deftest :key #'car) + (ert--remove-from-list 'find-function-regexp-alist 'ert--test :key #'car) (ert--remove-from-list 'minor-mode-alist 'ert--current-run-stats :key #'car) (ert--remove-from-list 'emacs-lisp-mode-hook 'ert--activate-font-lock-keywords) diff --git a/lisp/emacs-lisp/find-func.el b/lisp/emacs-lisp/find-func.el index c367d4a3624..6b7b5083620 100644 --- a/lisp/emacs-lisp/find-func.el +++ b/lisp/emacs-lisp/find-func.el @@ -123,15 +123,6 @@ should insert the feature name." :group 'xref :version "25.1") -(defcustom find-ert-deftest-regexp - "(ert-deftest +'%s" - "The regexp used to search for an `ert-deftest' definition. -Note it must contain a `%s' at the place where `format' -should insert the feature name." - :type 'regexp - :group 'xref - :version "29.1") - (defun find-function--defface (symbol) (catch 'found (while (re-search-forward (format find-face-regexp symbol) nil t) @@ -145,8 +136,7 @@ should insert the feature name." (defvar . find-variable-regexp) (defface . find-function--defface) (feature . find-feature-regexp) - (defalias . find-alias-regexp) - (ert-deftest . find-ert-deftest-regexp)) + (defalias . find-alias-regexp)) "Alist mapping definition types into regexp variables. Each regexp variable's value should actually be a format string to be used to substitute the desired symbol name into the regexp. commit aa6d2a396a493736395533cacea6b5363a24c0b6 Author: Stephen Gildea Date: Wed Jan 22 18:27:26 2025 -0800 * doc/lispref: new node "Finding Definitions" How to use variable 'find-function-regexp-alist', properties 'definition-name' and 'find-function-type-alist', and function 'find-function-update-type-alist' to help Emacs locate the definition of objects defined by macros. This material is moved from node "Standard Properties" and expanded. diff --git a/doc/lispref/elisp.texi b/doc/lispref/elisp.texi index adec6d847ea..90fa5277183 100644 --- a/doc/lispref/elisp.texi +++ b/doc/lispref/elisp.texi @@ -564,6 +564,7 @@ Functions * Lambda Expressions:: How functions are expressed as Lisp objects. * Function Names:: A symbol can serve as the name of a function. * Defining Functions:: Lisp expressions for defining functions. +* Finding Definitions:: Finding function definitions created by macros. * Calling Functions:: How to use an existing function. * Mapping Functions:: Applying a function to each element of a list, etc. * Anonymous Functions:: Lambda expressions are functions with no names. diff --git a/doc/lispref/functions.texi b/doc/lispref/functions.texi index 7f881bae7f5..7176c1faee5 100644 --- a/doc/lispref/functions.texi +++ b/doc/lispref/functions.texi @@ -15,6 +15,7 @@ define them. * Lambda Expressions:: How functions are expressed as Lisp objects. * Function Names:: A symbol can serve as the name of a function. * Defining Functions:: Lisp expressions for defining functions. +* Finding Definitions:: Finding function definitions created by macros. * Calling Functions:: How to use an existing function. * Mapping Functions:: Applying a function to each element of a list, etc. * Anonymous Functions:: Lambda expressions are functions with no names. @@ -832,6 +833,97 @@ and tells the Lisp compiler to perform inline expansion on it. To undefine a function name, use @code{fmakunbound}. @xref{Function Cells}. +@menu +* Finding Definitions:: Finding function definitions created by macros. +@end menu + +@node Finding Definitions +@subsection Finding Definitions + +Tools such as Emacs's built-in help command @kbd{C-h f} (@pxref{Help,,, +emacs, The GNU Emacs Manual}) can find the definition site of functions +and other Lisp objects in the source code. To do this, they use the +variable @code{find-function-regexp-alist}. + +@vindex @code{find-function-regexp-alist} +The alist @code{find-function-regexp-alist} associates object types with +a regexp or function that finds the definition of that object in its +source file. Each element's car is a symbol the describes the type of +object, or @code{nil} to identify functions defined with @code{defun}. +Each element's cdr is a symbol: either the value of that symbol is a +string interpreted as a regexp, or that symbol names a function that can +find the definition. + +A regexp string is actually a format string, and @code{%s} will be +substituted with the name of the symbol we are looking for. + +A function will be called with one argument, the (symbol for) the object +we are searching for. + +@cindex @code{definition-name} (symbol property) +If the function to be found is defined by a macro, it may be hard for +Emacs to find the definition site in the source code. A macro call may +have an unexpected look to it, and @code{find-function-regexp-alist} +will fail to identify the definition. + +For example, a @code{define-derived-mode} (@pxref{Derived Modes}) might +define a mode-specific function or a variable implicitly; or your Lisp +program might generate a run-time call to @code{defun} to define a +function. In these and similar cases, the @code{definition-name} +property of the symbol should be another symbol whose definition can be +found by textual search and whose code defines the original symbol. In +the example with @code{define-derived-mode}, the value of this property +of the functions and variables it defines should be the mode symbol. + +@cindex @code{find-function-type-alist} (symbol property) +In some cases, the definition cannot be found by looking for the +definition of another symbol. For example, a test file might use a +macro to generate calls to @code{ert-deftest} (@pxref{How to Write +Tests,,,ert, ERT: Emacs Lisp Regression Testing}) where the code is +boiler plate and only varying data need to be passed in. In such cases, +the @code{find-function-type-alist} property of the symbol can be an +alist that augments @code{find-function-regexp-alist} telling how to +find the definition of symbols of this type. + +The @code{find-function-regexp-alist} property is most easily maintained +with the convenience function @code{find-function-update-type-alist}. + +@defun find-function-update-type-alist symbol type variable +Update property @code{find-function-type-alist} of @var{symbol} with +a new element containing key @var{type} and value @var{variable}. +@end defun + +In the example of a macro defining calls to @code{ert-deftest}, +the macro could put the property @code{find-function-type-alist} on each +test defined, associating @code{ert--test} (the internal type of ERT +tests) with the name of a regexp or function that can find the correct +macro call. The file defining the macro would also have to provide that +definition-finding function or regexp. +Here is an example using a function to find the definition: + +@example +@group +(defmacro define-foo-test (data) + "Define a test of the foo system using DATA." + (declare (debug (&rest sexp))) + (let ((test-name (intern (concat ...)))) + `(progn + (find-function-update-type-alist + ',test-name 'ert--test 'foo-find-test-def-function) + (ert-deftest ,test-name () + ,(concat "Test foo with " ...) + ...)))) +@end group + +@group +(defun foo-find-test-def-function (test-name) + "Search for the `define-foo-test' call defining TEST-NAME. +Return non-nil if the definition is found." + (let ((regexp ...)) + (re-search-forward regexp nil t))) +@end group +@end example + @node Calling Functions @section Calling Functions @cindex function invocation diff --git a/doc/lispref/symbols.texi b/doc/lispref/symbols.texi index dc6509c1ae3..c334b5d5dfb 100644 --- a/doc/lispref/symbols.texi +++ b/doc/lispref/symbols.texi @@ -535,70 +535,12 @@ value, saved value, customized-but-unsaved value, and themed values. Do not set them directly; they are managed by @code{defcustom} and related functions. @xref{Variable Definitions}. -@cindex @code{definition-name} (symbol property) -@cindex @code{find-function-type-alist} (symbol property) @item definition-name @itemx find-function-type-alist These properties help find the definition of a symbol in the source code when it might be hard to find the definition by textual search -of the source file. -The Emacs Help commands such as @kbd{C-h f} (@pxref{Help,,, -emacs, The GNU Emacs Manual}) use these properties to show the definition -of a symbol via a button in the @file{*Help*} buffer where the -symbol's documentation is shown. - -For example, a @code{define-derived-mode} -(@pxref{Derived Modes}) might define a mode-specific function or a -variable implicitly; or your Lisp program might generate a run-time -call to @code{defun} to define a function (@pxref{Defining -Functions}). In these and similar cases, the @code{definition-name} -property of the symbol should be another symbol whose definition can -be found by textual search and whose code defines the original symbol. -In the example with @code{define-derived-mode}, the value of this -property of the functions and variables it defines should be the mode -symbol. - -In some cases, the definition cannot be found by looking for the -definition of another symbol. For example, a test file might use a -macro to generate calls to @code{ert-deftest} (@pxref{How to Write -Tests,,,ert, ERT: Emacs Lisp Regression Testing}) where the code is -boiler plate and only varying data need to be passed in. In such cases, -the @code{find-function-type-alist} property of the symbol can be an -alist that augments @code{find-function-regexp-alist} telling how to -find the definition of symbols of this type. - -In the example of a macro defining calls to @code{ert-deftest}, -the macro could put the property @code{find-function-type-alist} on each -test defined, associating @code{ert--test} (the internal type of ERT -tests) with the name of a regexp or function that can find the correct -macro call. The file defining the macro would also have to provide that -definition-finding function or regexp. -Here is an example using a function to find the definition. -The example updates the property using convenience function -@code{find-function-update-type-alist}. - -@example -@group -(defmacro define-foo-test (data) - "Define a test of the foo system using DATA." - (declare (debug (&rest sexp))) - (let ((test-name (intern (concat ...)))) - `(progn - (find-function-update-type-alist - ',test-name 'ert--test 'foo-find-test-def-function) - (ert-deftest ,test-name () - ,(concat "Test foo with " ...) - ...)))) -@end group - -@group -(defun foo-find-test-def-function (test-name) - "Search for the `define-foo-test' call defining TEST-NAME. -Return non-nil if the definition is found." - (let ((regexp ...)) - (re-search-forward regexp nil t))) -@end group -@end example +of the source file, as when the symbol is defined by a macro. +@xref{Finding Definitions}. @item disabled If the value is non-@code{nil}, the named function is disabled as a diff --git a/doc/lispref/tips.texi b/doc/lispref/tips.texi index 1bf52886971..31716ffa9e1 100644 --- a/doc/lispref/tips.texi +++ b/doc/lispref/tips.texi @@ -229,7 +229,7 @@ itself, since that would confuse these tools. If your macro cannot be written in this style, the macro can still help these tools find the defining call by putting the property @code{definition-name} or @code{find-function-type-alist} on the name. -@xref{Standard Properties}. +@xref{Finding Definitions}. @item In some other systems there is a convention of choosing variable names diff --git a/doc/misc/ert.texi b/doc/misc/ert.texi index 0e03afc98ff..4617453d06d 100644 --- a/doc/misc/ert.texi +++ b/doc/misc/ert.texi @@ -525,7 +525,7 @@ find where a test was defined only if the test was loaded from a file. If the test definition is generated by a macro, the macro may want to help ERT find the defining call to the macro by putting the property @code{find-function-type-alist} on the test name. -@xref{Standard Properties,,,elisp, GNU Emacs Lisp Reference Manual}. +@xref{Finding Definitions,,,elisp, GNU Emacs Lisp Reference Manual}. @menu commit 331bcfaee51f7fa5ff0f6046f30e940452f3a8fe Author: F. Jason Park Date: Mon Jan 20 16:32:53 2025 -0800 Reuse client cert for connectivity probing in ERC * lisp/erc/erc-backend.el (erc--server-connect-function) (erc--server-post-dial-function): Rename former to latter because the existing name is better suited for the eventual generalizing of `erc-server-connect' in a future version. (erc-server-connect): Use new name for `erc--server-connect-function', `erc--server-post-dial-function'. (erc--recon-probe-reschedule, erc--recon-probe-sentinel) (erc--recon-probe-filter, erc--recon-probe-check): New functions factored out of `erc-server-delayed-check-reconnect'. (erc-server-delayed-check-reconnect): Refactor, splitting off lambdas into top-level functions for improved tracing. * lisp/erc/erc.el (erc-message-english-recon-probe-hung-up) (erc-message-english-recon-probe-nobody-home): New variables. (Bug#62044) Thanks to Libera.Chat user arjan for reporting this bug, which is new in ERC 5.6 and Emacs 30.1. diff --git a/lisp/erc/erc-backend.el b/lisp/erc/erc-backend.el index 84c22d840fb..713547a591a 100644 --- a/lisp/erc/erc-backend.el +++ b/lisp/erc/erc-backend.el @@ -716,7 +716,8 @@ The current buffer is given by BUFFER." (run-hooks 'erc--server-post-connect-hook) (erc-login)) -(defvar erc--server-connect-function #'erc--server-propagate-failed-connection +(defvar erc--server-post-dial-function + #'erc--server-propagate-failed-connection "Function called one second after creating a server process. Called with the newly created process just before the opening IRC protocol exchange.") @@ -795,7 +796,7 @@ TLS (see `erc-session-client-certificate' for more details)." (let ((erc--msg-prop-overrides `((erc--skip . (stamp)) ,@erc--msg-prop-overrides))) (erc-display-message nil nil buffer "Opening connection..\n") - (run-at-time 1 nil erc--server-connect-function process)) + (run-at-time 1 nil erc--server-post-dial-function process)) (message "%s...done" msg) (erc--register-connection)))) @@ -846,89 +847,129 @@ Make sure you are in an ERC buffer when running this." "Double EXISTING timeout, but cap it at 5 minutes." (min 300 (* existing 2))) -;; This may appear to hang at various places. It's assumed that when -;; *Messages* contains "Waiting for socket ..." or similar, progress -;; will be made eventually. - +(defun erc--recon-probe-reschedule (proc) + "Print a message saying PROC's intended peer can't be reached. +Then call `erc-schedule-reconnect'." + (let ((buffer (process-buffer proc))) + (when (buffer-live-p buffer) + (with-current-buffer buffer + (let ((erc-server-reconnect-timeout erc--server-reconnect-timeout)) + ;; FIXME either remove this deletion or explain why the one + ;; performed by `erc-schedule-reconnect' is insufficient. + ;; Perhaps because `proc' may not equal `erc-server-process'? + (when proc ; conn refused w/o :nowait + (delete-process proc)) + (erc-display-message nil '(notice error) buffer + 'recon-probe-nobody-home) + (erc-schedule-reconnect buffer 0)))))) + +(defun erc--recon-probe-sentinel (proc event) + "Send a \"PING\" to PROC's peer on an \"open\" EVENT. +Otherwise, try connecting from scratch again after timeout." + (pcase event + ("open\n" + (let ((cookie (time-convert nil 'integer))) + (process-put proc 'erc--reconnect-cookie cookie) + ;; FIXME account for possible `file-error' when sending. + (run-at-time nil nil #'process-send-string proc + (format "PING %d\r\n" cookie)))) + ((and "connection broken by remote peer\n" + (guard (process-get proc 'erc--reconnect-cookie)) + (let buffer (process-buffer proc)) + (guard (buffer-live-p buffer))) + ;; This can run, for example, if the client dials a TLS-terminating + ;; endpoint with a non-TLS opener, like `erc-open-tls-stream', or + ;; if the server doesn't take kindly to an opening "PING" during + ;; connection registration. + (with-current-buffer buffer + (delete-process proc) + ;; Undo latest penalizing timeout increment. + (setq erc--server-reconnect-timeout + (max 1 (/ erc--server-reconnect-timeout 2))) + (erc-display-message nil '(notice error) buffer 'recon-probe-hung-up + ?t erc--server-reconnect-timeout) + (run-at-time erc--server-reconnect-timeout + nil #'erc-server-delayed-reconnect buffer))) + ((or "connection broken by remote peer\n" (rx bot "failed")) + (run-at-time nil nil #'erc--recon-probe-reschedule proc)))) + +(defun erc--recon-probe-filter (proc string) + "Reconnect, reusing PROC if STRING contains a \"PONG\"." + (when-let* ((buffer (process-buffer proc)) + (buffer-live-p buffer)) + (with-current-buffer buffer + (setq erc--server-reconnect-timeout nil)) + (if-let* ; reuse proc if string has complete message + ((cookie (process-get proc 'erc--reconnect-cookie)) + ;; Accommodate a leading ": ". + ((string-suffix-p (format "PONG %d\r\n" cookie) string))) + (progn + (erc-log-irc-protocol string nil) + (set-process-sentinel proc #'ignore) + (set-process-filter proc nil) + (run-at-time nil nil #'erc-server--reconnect-opened buffer proc)) + (delete-process proc) + (run-at-time nil nil #'erc-server-delayed-reconnect buffer)))) + +(defun erc--recon-probe-check (proc tmrx) + "Restart auto-reconnect probe if PROC has failed or TIMER has EXPIRE'd. +Expect TMRX to be a cons cell of (EXPIRE . TIMER)." + (let* ((status (process-status proc)) + (expiredp (time-less-p (pop tmrx) (current-time))) + (buffer (process-buffer proc))) + (when (or expiredp + (not (eq 'connect status)) ; e.g., `closed' + (not (buffer-live-p buffer))) + (cancel-timer tmrx)) + (cond ((not (buffer-live-p buffer))) + (expiredp + (erc-display-message nil 'error buffer "Timed out while dialing...") + (delete-process proc) + (erc--recon-probe-reschedule proc)) + ((eq 'failed status) + (erc--recon-probe-reschedule proc))))) + +;; This probing strategy may appear to hang at various junctures. It's +;; assumed that when *Messages* contains "Waiting for socket ..." or +;; similar, progress will be made eventually. (defun erc-server-delayed-check-reconnect (buffer) "Wait for internet connectivity before trying to reconnect. Use server BUFFER's cached session info to reestablish the logical -connection at the IRC protocol level. Do this by probing for a -successful response to a PING before commencing with \"connection -registration\". Do not distinguish between configuration problems and -the absence of service. For example, expect users of proxy-based -connectors, like `erc-open-socks-tls-stream', to ensure their setup -works before choosing this function as their reconnector." +connection at the IRC protocol level. Do this by probing for any +response to a PING, including a hang up, before (possibly) dialing again +and commencing with \"connection registration\". Make no distinction +between configuration issues and the absence of service in printed +feedback. For example, expect users of proxy-based connectors, like +`erc-open-socks-tls-stream', to ensure their setup works before choosing +this function as their reconnector." (when (buffer-live-p buffer) (with-current-buffer buffer (setq erc--server-reconnect-timeout (funcall erc--server-reconnect-timeout-scale-function (or erc--server-reconnect-timeout erc-server-reconnect-timeout))) - (let* ((reschedule (lambda (proc) - (when (buffer-live-p buffer) - (with-current-buffer buffer - (let ((erc-server-reconnect-timeout - erc--server-reconnect-timeout)) - (when proc ; conn refused w/o :nowait - (delete-process proc)) - (erc-display-message nil 'error buffer - "Nobody home...") - (erc-schedule-reconnect buffer 0)))))) - (conchk-exp (time-add erc--server-reconnect-timeout-check - (current-time))) - (conchk-timer nil) - (conchk (lambda (proc) - (let ((status (process-status proc)) - (xprdp (time-less-p conchk-exp (current-time)))) - (when (or xprdp (not (eq 'connect status))) - (cancel-timer conchk-timer)) - (when (buffer-live-p buffer) - (cond (xprdp (erc-display-message - nil 'error buffer - "Timed out while dialing...") - (delete-process proc) - (funcall reschedule proc)) - ((eq 'failed status) - (funcall reschedule proc))))))) - (sentinel (lambda (proc event) - (pcase event - ("open\n" - (let ((cookie (time-convert nil 'integer))) - (process-put proc 'erc--reconnect-cookie cookie) - (run-at-time nil nil #'process-send-string proc - (format "PING %d\r\n" cookie)))) - ((or "connection broken by remote peer\n" - (rx bot "failed")) - (run-at-time nil nil reschedule proc))))) - (filter (lambda (proc string) - (with-current-buffer buffer - (setq erc--server-reconnect-timeout nil)) - (if-let* ; reuse proc if string has complete message - ((cookie (process-get proc 'erc--reconnect-cookie)) - ((string-suffix-p (format "PONG %d\r\n" cookie) - string))) ; leading ": " - (progn - (erc-log-irc-protocol string nil) - (set-process-sentinel proc #'ignore) - (set-process-filter proc nil) - (run-at-time nil nil - #'erc-server--reconnect-opened - buffer proc)) - (delete-process proc) - (run-at-time nil nil #'erc-server-delayed-reconnect - buffer))))) - (condition-case _ - (let ((proc (funcall erc-session-connector - "*erc-connectivity-check*" nil - erc-session-server erc-session-port))) - (setq conchk-timer (run-at-time 1 1 conchk proc)) - (set-process-filter proc filter) - (set-process-sentinel proc sentinel) - (when (eq (process-status proc) 'open) ; :nowait is nil - (funcall sentinel proc "open\n"))) - ;; E.g., "make client process failed" "Connection refused". - (file-error (funcall reschedule nil))))))) + (condition-case _ + (let* ((cert erc-session-client-certificate) + (tmrx (list (time-add erc--server-reconnect-timeout-check + (current-time)))) + (server (if (string-match erc--server-connect-dumb-ipv6-regexp + erc-session-server) + (match-string 1 erc-session-server) + erc-session-server)) + (proc (apply erc-session-connector "*erc-connectivity-check*" + nil server erc-session-port + (and cert (list :client-certificate cert))))) + (setcdr tmrx (run-at-time 1 1 #'erc--recon-probe-check proc tmrx)) + (set-process-filter proc #'erc--recon-probe-filter) + (set-process-sentinel proc #'erc--recon-probe-sentinel) + (set-process-buffer proc buffer) + ;; Should `erc-server-process' also be set to `proc' here so + ;; that `erc-schedule-reconnect' can use it? + (cl-assert (processp proc)) + (when (eq (process-status proc) 'open) ; :nowait is nil + (erc--recon-probe-sentinel proc "open\n"))) + ;; E.g., "make client process failed" "Connection refused". + (file-error (erc--recon-probe-reschedule nil)))))) (defun erc-server-prefer-check-reconnect (buffer) "Defer to another reconnector based on BUFFER's `erc-session-connector'. diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index 28ccb5809a7..72335a444cb 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -9572,6 +9572,8 @@ SOFTP, only do so when defined as a variable." (ignore-list . "%-8p %s") (reconnecting . "Reconnecting in %ms: attempt %i/%n ...") (reconnect-canceled . "Canceled %u reconnect timer with %cs to go...") + (recon-probe-hung-up . "Server answered but hung up. Delaying by %ts...") + (recon-probe-nobody-home . "Nobody home...") (finished . "\n\n*** ERC finished ***\n") (terminated . "\n\n*** ERC terminated: %e\n") (login . "Logging in as `%n'...") commit d6b210aa09d6a5464c91e71460d9852a4445d46e Author: F. Jason Park Date: Sat Jan 18 11:12:50 2025 -0800 ; Don't quote localp argument to define-erc-module * etc/ERC-NEWS: Rearrange some entries for 5.6.1. * lisp/erc/erc-fill.el (erc-fill-wrap-mode): * lisp/erc/erc-goodies.el (erc-keep-place-indicator-mode) (erc-command-indicator-mode): * lisp/erc/erc-nicks.el (erc-nicks-mode): * lisp/erc/erc-notify.el (erc-querypoll-mode): * lisp/erc/erc-sasl.el (erc-sasl-mode): * lisp/erc/erc-services.el (erc-services-regain-mode): Don't quote localp argument. * test/lisp/erc/erc-scenarios-misc.el (erc-nicks-track-faces/defer): Skip on Emacs 27 due to recent CI failures. * test/lisp/erc/erc-scenarios-misc.el (erc-scenarios-base-flood): Adjust timeout. diff --git a/etc/ERC-NEWS b/etc/ERC-NEWS index d491c3e5132..dd960994b4f 100644 --- a/etc/ERC-NEWS +++ b/etc/ERC-NEWS @@ -14,12 +14,6 @@ GNU Emacs since Emacs version 22.1. * Changes in ERC 5.6.1 -** Reliable library access for ban lists. -Say goodbye to continually running "/BANLIST" for programmatic -purposes. Modules can instead use the function 'erc-sync-banlist' to -guarantee that the variable 'erc-channel-banlist' remains synced for -the remainder of an IRC session. - ** Option 'erc-truncate-padding-size' controls truncation frequency. In fast-moving channels and in queries with long-winded bots, the 'truncate' module has historically been asked to work overtime, mostly @@ -33,15 +27,15 @@ first having to load the package. Unfortunately, this also had the side effect of arranging for ERC to load on startup whenever someone customized the variable. +** New option determines 'keep-place-indicator's influence on 'truncate'. +Option 'erc-keep-place-indicator-truncation' manages the tension between +truncation and place keeping, prioritizing one or the other. + ** Stray key binding removed from 'erc-fill-wrap-mode-map'. The command 'erc-fill-wrap-cycle-visual-movement' was mistakenly given the key binding "C-c a" in an inadvertent holdover from development. It has been removed. -** New option determines 'keep-place-indicator's influence on 'truncate'. -Option 'erc-keep-place-indicator-truncation' manages the tension between -truncation and place keeping, prioritizing one or the other. - ** Updated defaults for the 'track' module's face-list options. The default values of options 'erc-track-faces-priority-list' and 'erc-track-faces-normal-list' have both gained a face for buttonized @@ -49,7 +43,9 @@ speaker names, with the latter option also gaining 'erc-notice-face'. This was done to provide a more frequent and practical indication of channel activity in keeping with the module's original design. -** Command 'erc-track-clear' clears the mode-line activity indicator. +** Command 'erc-track-clear' resets the mode-line activity indicator. +Clear ERC's mode-line area with a single stroke, effectively marking all +ERC buffers as having been read. ** An arguably less distracting 'erc-nicks-track-faces' variant. Setting this option to t tells the 'track' module to have the mode-line @@ -78,6 +74,12 @@ the option. ** Changes in the library API. +*** Reliable library access for ban lists. +Say goodbye to continually running "/BANLIST" for programmatic +purposes. Modules can instead use the function 'erc-sync-banlist' to +guarantee that the variable 'erc-channel-banlist' remains synced for +the remainder of an IRC session. + *** Function 'erc-normalize-port' may return 0 instead of nil. When given a nonempty, non-numeric string, this function now returns 0. Moreover, ERC officially requests that users not use its output for diff --git a/lisp/erc/erc-fill.el b/lisp/erc/erc-fill.el index 2d1d490990b..145a4c174a8 100644 --- a/lisp/erc/erc-fill.el +++ b/lisp/erc/erc-fill.el @@ -567,7 +567,7 @@ via `erc-fill-wrap-mode-hook'." #'erc-fill--wrap-merged-button-p t) (remove-hook 'erc-stamp--insert-date-hook #'erc-fill--wrap-unmerge-on-date-stamp t)) - 'local) + localp) (defvar-local erc-fill--wrap-length-function nil "Function to determine length of overhanging characters. diff --git a/lisp/erc/erc-goodies.el b/lisp/erc/erc-goodies.el index 2a5c56a0b4c..cfd0d3415ea 100644 --- a/lisp/erc/erc-goodies.el +++ b/lisp/erc/erc-goodies.el @@ -424,7 +424,7 @@ and `keep-place-indicator' in different buffers." (remove-hook 'erc-keep-place-mode-hook #'erc--keep-place-indicator-on-global-module t) (kill-local-variable 'erc--keep-place-indicator-overlay)) - 'local) + localp) (defun erc--keep-place-indicator-on-global-module () "Ensure `keep-place-indicator' survives toggling `erc-keep-place-mode'. @@ -596,7 +596,7 @@ reveal echoed command lines after they've been inserted." ((remove-hook 'erc--input-review-functions #'erc--command-indicator-permit-insertion t) (erc-command-indicator-toggle-hidden +1)) - 'local) + localp) (defun erc-command-indicator () "Return the command-indicator prompt as a string. diff --git a/lisp/erc/erc-nicks.el b/lisp/erc/erc-nicks.el index 3429bcb33af..b0629f7754c 100644 --- a/lisp/erc/erc-nicks.el +++ b/lisp/erc/erc-nicks.el @@ -607,7 +607,7 @@ Abandon search after examining LIMIT faces." nil) (unless erc-button--nick-popup-alist (kill-local-variable 'erc-button--nick-popup-alist))) - 'local) + localp) (defun erc-nicks-customize-face (nick) "Customize or create persistent face for NICK." diff --git a/lisp/erc/erc-notify.el b/lisp/erc/erc-notify.el index 4b512a734f9..82754cb1989 100644 --- a/lisp/erc/erc-notify.el +++ b/lisp/erc/erc-notify.el @@ -335,7 +335,7 @@ target buffer." (erc-querypoll-mode -1))) (kill-local-variable 'erc--querypoll-ring) (kill-local-variable 'erc--querypoll-timer)) - 'local) + localp) (cl-defmethod erc--queries-current-p (&context (erc-querypoll-mode (eql t))) t) diff --git a/lisp/erc/erc-sasl.el b/lisp/erc/erc-sasl.el index 3c08104d4c3..a16f554f2d1 100644 --- a/lisp/erc/erc-sasl.el +++ b/lisp/erc/erc-sasl.el @@ -337,7 +337,7 @@ This doesn't solicit or validate a suite of supported mechanisms." (setf (erc-sasl--state-client erc-sasl--state) client)))) ((kill-local-variable 'erc-sasl--state) (kill-local-variable 'erc-sasl--options)) - 'local) + localp) (define-erc-response-handler (AUTHENTICATE) "Begin or resume an SASL session." nil diff --git a/lisp/erc/erc-services.el b/lisp/erc/erc-services.el index c62f1243eca..d16aa8c54d8 100644 --- a/lisp/erc/erc-services.el +++ b/lisp/erc/erc-services.el @@ -611,7 +611,7 @@ somewhat experimental, is likely only useful in conjunction with SASL authentication rather than the traditional approach provided by the `services' module it shares a library with (see Info node `(erc) SASL' for more)." - nil nil 'local) + nil nil localp) (cl-defmethod erc--nickname-in-use-make-request ((want string) temp &context (erc-server-connected null) diff --git a/test/lisp/erc/erc-nicks-tests.el b/test/lisp/erc/erc-nicks-tests.el index a6d2f6034d4..1e57fb83672 100644 --- a/test/lisp/erc/erc-nicks-tests.el +++ b/test/lisp/erc/erc-nicks-tests.el @@ -681,6 +681,9 @@ (funcall assert-result '(7 . erc-notice-face))))) (ert-deftest erc-nicks-track-faces/defer () + (when (< emacs-major-version 28) + (ert-skip "Possible intermittent failures on 27")) + (should (eq erc-nicks-track-faces 'prioritize)) (let ((erc-nicks-track-faces 'defer)) (erc-nicks-tests--track-faces diff --git a/test/lisp/erc/erc-scenarios-misc.el b/test/lisp/erc/erc-scenarios-misc.el index 5b260e979af..241664f1158 100644 --- a/test/lisp/erc/erc-scenarios-misc.el +++ b/test/lisp/erc/erc-scenarios-misc.el @@ -51,7 +51,7 @@ (with-current-buffer (erc-d-t-wait-for 5 (get-buffer "#chan/foonet")) (erc-d-t-search-for 10 "") (erc-d-t-absent-for 0.1 " Date: Wed Jan 22 22:25:03 2025 +0000 Eglot: unbreak a rarely run test * test/lisp/progmodes/eglot-tests.el (eglot-test-snippet-completions-with-company): Don't expect fooba to be there, do expect two completions to be candidates. diff --git a/test/lisp/progmodes/eglot-tests.el b/test/lisp/progmodes/eglot-tests.el index 5d7fed79374..cc269abea08 100644 --- a/test/lisp/progmodes/eglot-tests.el +++ b/test/lisp/progmodes/eglot-tests.el @@ -798,7 +798,6 @@ int main() { (insert "foo") (company-mode) (company-complete) - (should (looking-back "fooba")) (should (= 2 (length company-candidates))) ;; this last one is brittle, since there it is possible that ;; clangd will change the representation of this candidate commit 47a938c8890e81c4472018df52e62ab196efb103 Author: João Távora Date: Wed Jan 22 22:24:26 2025 +0000 Eglot: unbreak tests for older Emacsen * test/lisp/progmodes/eglot-tests.el (eglot-test-basic-symlink): Use skip-unless diff --git a/test/lisp/progmodes/eglot-tests.el b/test/lisp/progmodes/eglot-tests.el index d54654b84ac..5d7fed79374 100644 --- a/test/lisp/progmodes/eglot-tests.el +++ b/test/lisp/progmodes/eglot-tests.el @@ -442,7 +442,7 @@ directory hierarchy." "Test basic symlink support." (skip-unless (executable-find "clangd")) ;; MS-Windows either fails symlink creation or pops up UAC prompts. - (skip-when (eq system-type 'windows-nt)) + (skip-unless (not (eq system-type 'windows-nt))) (eglot--with-fixture `(("symlink-project" . (("main.cpp" . "#include\"foo.h\"\nint main() { return foo(); }") commit 9068dcbd71b05bcb6ff2fdbb6e56375f8ac162fb Author: João Távora Date: Wed Jan 22 22:02:00 2025 +0000 Eglot: fix edebug spec of eglot--dbind * lisp/progmodes/eglot.el (eglot--dbind): Fix edebug spec. diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index b0e9563e22b..161c6330426 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -794,7 +794,7 @@ compile time if an undeclared LSP interface is used.")) "Destructure OBJECT, binding VARS in BODY. VARS is ([(INTERFACE)] SYMS...) Honor `eglot-strict-mode'." - (declare (indent 2) (debug (sexp sexp &rest form))) + (declare (indent 2) (debug (sexp form &rest form))) (let ((interface-name (if (consp (car vars)) (car (pop vars)))) (object-once (make-symbol "object-once")) commit 7e2f6ce0cb1e5eb2c686451836275acf234bc354 Author: João Távora Date: Wed Jan 22 21:52:56 2025 +0000 Eglot: unbreak completion in Emacs 26.3 (at least) For reasons yet to be investigated, in some Emacs older versions, callers of the completion table created by eglot-completion-at-point will destroy text properties in the strings returned by the "all-completions" call (destroy here means completely erase). This completely breaks Eglot completion. (eglot-completion-at-point): Copy substrings in all-completions call. diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index bccc1f20f94..b0e9563e22b 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -3263,7 +3263,9 @@ for which LSP on-type-formatting should be requested." (dolist (c comps) (eglot--dumb-flex pattern c completion-ignore-case)) (all-completions "" - comps + ;; copy strings, as some older emacs + ;; versions will destroy properties. + (mapcar #'substring comps) (lambda (proxy) (let* ((item (get-text-property 0 'eglot--lsp-item proxy)) (filterText (plist-get item :filterText))) commit a60249b098d22093ec79b6cbc27a759b7b4f8883 Author: João Távora Date: Wed Jan 22 21:43:10 2025 +0000 Eglot: abandon compat.el After period of months using this to help compatibility to older Emacs versions. It's not really buying us that much and complicates maintenance, so it gets the axe. A common problem is the compatibility alias for require-with-check, which often errors with some "Feature X loaded from Y is now provided by Z". The tests for Emacs 26.3 were all failing in the Github tracker ever since this (and track-changes.el) were added to Eglot. * lisp/progmodes/eglot.el (compat): No longer require. (eglot-alternatives): Reword comment. (eglot-alternatives): Call eglot--executable-find. (eglot--guess-contact): Call eglot--executable-find. (eglot--connect): Check if package-get-version exists. (eglot--format): Check if substitute-quotes exists. (eglot--format-markup): Check if text-property-search exists. diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 85a06a95b77..bccc1f20f94 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -7,7 +7,7 @@ ;; Maintainer: João Távora ;; URL: https://github.com/joaotavora/eglot ;; Keywords: convenience, languages -;; Package-Requires: ((emacs "26.3") (compat "27.1") (eldoc "1.14.0") (external-completion "0.1") (flymake "1.2.1") (jsonrpc "1.0.24") (project "0.9.8") (seq "2.23") (xref "1.6.2")) +;; Package-Requires: ((emacs "26.3") (eldoc "1.14.0") (external-completion "0.1") (flymake "1.2.1") (jsonrpc "1.0.24") (project "0.9.8") (seq "2.23") (xref "1.6.2")) ;; This is a GNU ELPA :core package. Avoid adding functionality ;; that is not available in the version of Emacs recorded above or any @@ -108,7 +108,6 @@ (require 'text-property-search nil t) (require 'diff-mode) (require 'diff) -(require 'compat) ;; These dependencies are also GNU ELPA core packages. Because of ;; bug#62576, since there is a risk that M-x package-install, despite @@ -199,8 +198,8 @@ path of the PROGRAM that was chosen (interactively or automatically)." (lambda (&optional interactive _project) ;; JT@2021-06-13: This function is way more complicated than it - ;; could be because it accounts for the fact that Compat's - ;; `executable-find' may take much longer to execute on + ;; could be because it accounts for the fact that + ;; `eglot--executable-find' may take much longer to execute on ;; remote files. (let* ((listified (cl-loop for a in alternatives collect (if (listp a) a (list a)))) @@ -212,7 +211,7 @@ automatically)." nil) (interactive (let* ((augmented (mapcar (lambda (a) - (let ((found (compat-call executable-find + (let ((found (eglot--executable-find (car a) t))) (and found (cons (car a) (cons found (cdr a)))))) @@ -232,7 +231,7 @@ automatically)." nil)))) (t (cl-loop for (p . args) in listified - for probe = (compat-call executable-find p t) + for probe = (eglot--executable-find p t) when probe return (cons probe args) finally (funcall err))))))) @@ -608,6 +607,11 @@ This can be useful when using docker to run a language server.") (defconst eglot--{} (make-hash-table :size 0) "The empty JSON object.") +(defun eglot--executable-find (command &optional remote) + "Like Emacs 27's `executable-find', ignore REMOTE on Emacs 26." + (if (>= emacs-major-version 27) (executable-find command remote) + (executable-find command))) + (defun eglot--accepted-formats () (if (and (not eglot-prefer-plaintext) (fboundp 'gfm-view-mode)) ["markdown" "plaintext"] ["plaintext"])) @@ -1332,7 +1336,7 @@ be guessed." main-mode base-prompt)) ((and program (not (file-name-absolute-p program)) - (not (compat-call executable-find program t))) + (not (eglot--executable-find program t))) (if full-program-invocation (concat (eglot--format "[eglot] I guess you want to run `%s'" @@ -1630,7 +1634,8 @@ This docstring appeases checkdoc, that's all." :clientInfo (append '(:name "Eglot") - (let ((v (package-get-version))) + (let ((v (and (functionp 'package-get-version) + (package-get-version)))) (and v (list :version v)))) ;; Maybe turn trampy `/ssh:foo@bar:/path/to/baz.py' ;; into `/path/to/baz.py', so LSP groks it. @@ -1709,7 +1714,10 @@ in project `%s'." ;;; (defun eglot--format (format &rest args) "Like `format`, but substitutes quotes." - (apply #'format (substitute-quotes format) args)) + (apply #'format (if (functionp 'substitute-quotes) + (substitute-quotes format) + format) + args)) (defun eglot--error (format &rest args) "Error out with FORMAT with ARGS." @@ -1897,9 +1905,10 @@ MARKUP is either an LSP MarkedString or MarkupContent object." (font-lock-ensure) (goto-char (point-min)) (let ((inhibit-read-only t)) - (while (setq match (text-property-search-forward 'invisible)) - (delete-region (prop-match-beginning match) - (prop-match-end match)))) + (when (fboundp 'text-property-search-forward) + (while (setq match (text-property-search-forward 'invisible)) + (delete-region (prop-match-beginning match) + (prop-match-end match))))) (string-trim (buffer-string)))))) (defun eglot--read-server (prompt &optional dont-if-just-the-one) commit ac902ddadcd236dfd1d610768569e26ea8fc5b7f Author: João Távora Date: Wed Jan 22 15:44:41 2025 +0000 Eglot: abandon track-changes.el After a ~10 month period of using track-changes.el as a support library for tracking buffer changes, I've decided to go back to manually using after-change-functions and before-change-functions. track-changes.el showed promise: - One of the selling points was to turn complicated a-c-functions and b-c-functions into something easier, but that objectively didn't pan out, with "virtual" positions, one-shot hooks, and tracker registrations being abstractions and complications mastered by very few. - The other selling point was the ability to log and detect those parts of Emacs that cheat the modification hooks and correct them. As far as I can tell, only one such cheater -- quail.el -- was identified. But with little consequence, only an ugly workaround in eglot.el (now removed). - After using Eglot daily for all this time, I didn't notice any decrease in desynchronization events. - I did notice an increase in track-changes.el related bugs, some of which still baffle me and and hard to reproduce. A common occurence is the '(cl-assertion-failed (memq id track-changes--trackers))' which is hard to track down. - The library makes it more complicated to run Eglot on older Emacsen. I might yet revisit this matter for the next version but this experience has shown that it didn't bring the advantages I thought it would, so I'm abandoning it until at least 1.19 is out. * lisp/progmodes/eglot.el (track-changes): No longer require. (eglot--virtual-pos-to-lsp-position): Delete. (eglot--managed-mode): Simplify. (eglot--track-changes): Delete this variable. (eglot--recent-changes): Reword doc. (eglot--before-change, eglot--after-change): Bring back. (eglot--track-changes-fetch): Delete. (eglot--add-one-shot-hook): Delete. (eglot--track-changes-signal): Delete. diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 181f4a9c7a0..85a06a95b77 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -7,7 +7,7 @@ ;; Maintainer: João Távora ;; URL: https://github.com/joaotavora/eglot ;; Keywords: convenience, languages -;; Package-Requires: ((emacs "26.3") (compat "27.1") (eldoc "1.14.0") (external-completion "0.1") (flymake "1.2.1") (jsonrpc "1.0.24") (project "0.9.8") (seq "2.23") (track-changes "1.2") (xref "1.6.2")) +;; Package-Requires: ((emacs "26.3") (compat "27.1") (eldoc "1.14.0") (external-completion "0.1") (flymake "1.2.1") (jsonrpc "1.0.24") (project "0.9.8") (seq "2.23") (xref "1.6.2")) ;; This is a GNU ELPA :core package. Avoid adding functionality ;; that is not available in the version of Emacs recorded above or any @@ -108,7 +108,6 @@ (require 'text-property-search nil t) (require 'diff-mode) (require 'diff) -(require 'track-changes) (require 'compat) ;; These dependencies are also GNU ELPA core packages. Because of @@ -1788,24 +1787,6 @@ LBP defaults to `eglot--bol'." :character (progn (when pos (goto-char pos)) (funcall eglot-current-linepos-function))))) -(defun eglot--virtual-pos-to-lsp-position (pos string) - "Return the LSP position at the end of STRING if it were inserted at POS." - (eglot--widening - (goto-char pos) - (forward-line 0) - ;; LSP line is zero-origin; Emacs is one-origin. - (let ((posline (1- (line-number-at-pos nil t))) - (linebeg (buffer-substring (point) pos)) - (colfun eglot-current-linepos-function)) - ;; Use a temp buffer because: - ;; - I don't know of a fast way to count newlines in a string. - ;; - We currently don't have `eglot-current-linepos-function' for strings. - (with-temp-buffer - (insert linebeg string) - (goto-char (point-max)) - (list :line (+ posline (1- (line-number-at-pos nil t))) - :character (funcall colfun)))))) - (defvar eglot-move-to-linepos-function #'eglot-move-to-utf-16-linepos "Function to move to a position within a line reported by the LSP server. @@ -2012,8 +1993,6 @@ For example, to keep your Company customization, add the symbol "A hook run by Eglot after it started/stopped managing a buffer. Use `eglot-managed-p' to determine if current buffer is managed.") -(defvar-local eglot--track-changes nil) - (define-minor-mode eglot--managed-mode "Mode for source buffers managed by some Eglot project." :init-value nil :lighter nil :keymap eglot-mode-map :interactive nil @@ -2027,10 +2006,8 @@ Use `eglot-managed-p' to determine if current buffer is managed.") ("utf-8" (eglot--setq-saving eglot-current-linepos-function #'eglot-utf-8-linepos) (eglot--setq-saving eglot-move-to-linepos-function #'eglot-move-to-utf-8-linepos))) - (unless eglot--track-changes - (setq eglot--track-changes - (track-changes-register - #'eglot--track-changes-signal :disjoint t))) + (add-hook 'after-change-functions #'eglot--after-change nil t) + (add-hook 'before-change-functions #'eglot--before-change nil t) (add-hook 'kill-buffer-hook #'eglot--managed-mode-off nil t) ;; Prepend "didClose" to the hook after the "nonoff", so it will run first (add-hook 'kill-buffer-hook #'eglot--signal-textDocument/didClose nil t) @@ -2064,6 +2041,8 @@ 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 + (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) (remove-hook 'kill-buffer-hook #'eglot--signal-textDocument/didClose t) (remove-hook 'before-revert-hook #'eglot--signal-textDocument/didClose t) @@ -2093,10 +2072,7 @@ Use `eglot-managed-p' to determine if current buffer is managed.") (delq (current-buffer) (eglot--managed-buffers server))) (when (and eglot-autoshutdown (null (eglot--managed-buffers server))) - (eglot-shutdown server)))) - (when eglot--track-changes - (track-changes-unregister eglot--track-changes) - (setq eglot--track-changes nil))))) + (eglot-shutdown server))))))) (defun eglot--managed-mode-off () "Turn off `eglot--managed-mode' unconditionally." @@ -2648,7 +2624,7 @@ buffer." `(:triggerKind 2 :triggerCharacter ,trigger) `(:triggerKind 1))))) (defvar-local eglot--recent-changes nil - "Recent buffer changes as collected by `eglot--track-changes-fetch'.") + "Recent buffer changes as collected by `eglot--before-change'.") (cl-defmethod jsonrpc-connection-ready-p ((_server eglot-lsp-server) _what) "Tell if SERVER is ready for WHAT in current buffer." @@ -2656,59 +2632,63 @@ buffer." (defvar-local eglot--change-idle-timer nil "Idle timer for didChange signals.") +(defun eglot--before-change (beg end) + "Hook onto `before-change-functions' with BEG and END." + (when (listp eglot--recent-changes) + ;; Records BEG and END, crucially convert them into LSP + ;; (line/char) positions before that information is lost (because + ;; the after-change thingy doesn't know if newlines were + ;; deleted/added). Also record markers of BEG and END + ;; (github#259) + (push `(,(eglot--pos-to-lsp-position beg) + ,(eglot--pos-to-lsp-position end) + (,beg . ,(copy-marker beg nil)) + (,end . ,(copy-marker end t))) + eglot--recent-changes))) + (defvar eglot--document-changed-hook '(eglot--signal-textDocument/didChange) "Internal hook for doing things when the document changes.") -(defun eglot--track-changes-fetch (id) - (if (eq eglot--recent-changes :pending) (setq eglot--recent-changes nil)) - (track-changes-fetch - id (lambda (beg end before) - (cl-incf eglot--versioned-identifier) - (cond - ((eq eglot--recent-changes :emacs-messup) nil) - ((eq before 'error) (setf eglot--recent-changes :emacs-messup)) - (t (push `(,(eglot--pos-to-lsp-position beg) - ,(eglot--virtual-pos-to-lsp-position beg before) - ,(length before) - ,(buffer-substring-no-properties beg end)) - eglot--recent-changes)))))) - -(defun eglot--add-one-shot-hook (hook function &optional append local) - "Like `add-hook' but calls FUNCTION only once." - (let* ((fname (make-symbol (format "eglot--%s-once" function))) - (fun (lambda (&rest args) - (remove-hook hook fname local) - (apply function args)))) - (fset fname fun) - (add-hook hook fname append local))) - -(defun eglot--track-changes-signal (id &optional distance) - (cond - (distance - ;; When distance is <100, we may as well coalesce the changes. - (when (> distance 100) (eglot--track-changes-fetch id))) - (eglot--recent-changes nil) - ;; Note that there are pending changes, for the benefit of those - ;; who check it as a boolean. - (t (setq eglot--recent-changes :pending))) +(defun eglot--after-change (beg end pre-change-length) + "Hook onto `after-change-functions'. +Records BEG, END and PRE-CHANGE-LENGTH locally." + (cl-incf eglot--versioned-identifier) + (pcase (car-safe eglot--recent-changes) + (`(,lsp-beg ,lsp-end + (,b-beg . ,b-beg-marker) + (,b-end . ,b-end-marker)) + ;; github#259 and github#367: with `capitalize-word' & friends, + ;; `before-change-functions' records the whole word's `b-beg' and + ;; `b-end'. Similarly, when `fill-paragraph' coalesces two + ;; lines, `b-beg' and `b-end' mark end of first line and end of + ;; second line, resp. In both situations, `beg' and `end' + ;; received here seemingly contradict that: they will differ by 1 + ;; and encompass the capitalized character or, in the coalescing + ;; case, the replacement of the newline with a space. We keep + ;; both markers and positions to detect and correct this. In + ;; this specific case, we ignore `beg', `len' and + ;; `pre-change-len' and send richer information about the region + ;; from the markers. I've also experimented with doing this + ;; unconditionally but it seems to break when newlines are added. + (if (and (= b-end b-end-marker) (= b-beg b-beg-marker) + (or (/= beg b-beg) (/= end b-end))) + (setcar eglot--recent-changes + `(,lsp-beg ,lsp-end ,(- b-end-marker b-beg-marker) + ,(buffer-substring-no-properties b-beg-marker + b-end-marker))) + (setcar eglot--recent-changes + `(,lsp-beg ,lsp-end ,pre-change-length + ,(buffer-substring-no-properties beg end))))) + (_ (setf eglot--recent-changes :emacs-messup))) (when eglot--change-idle-timer (cancel-timer eglot--change-idle-timer)) - (setq eglot--change-idle-timer - (run-with-idle-timer - eglot-send-changes-idle-time nil - (lambda (buf) - (eglot--when-live-buffer buf - (when eglot--managed-mode - (if (track-changes-inconsistent-state-p) - ;; Not a good time (e.g. in the middle of Quail thingy, - ;; bug#70541): reschedule for the next idle period. - (eglot--add-one-shot-hook - 'post-command-hook - (lambda () - (eglot--when-live-buffer buf - (eglot--track-changes-signal id)))) - (run-hooks 'eglot--document-changed-hook) - (setq eglot--change-idle-timer nil))))) - (current-buffer)))) + (let ((buf (current-buffer))) + (setq eglot--change-idle-timer + (run-with-idle-timer + eglot-send-changes-idle-time + nil (lambda () (eglot--when-live-buffer buf + (when eglot--managed-mode + (run-hooks 'eglot--document-changed-hook) + (setq eglot--change-idle-timer nil)))))))) (defvar-local eglot-workspace-configuration () "Configure LSP servers specifically for a given project. @@ -2812,7 +2792,6 @@ When called interactively, use the currently active server" (defun eglot--signal-textDocument/didChange () "Send textDocument/didChange to server." - (eglot--track-changes-fetch eglot--track-changes) (when eglot--recent-changes (let* ((server (eglot--current-server-or-lose)) (sync-capability (eglot-server-capable :textDocumentSync)) @@ -2838,7 +2817,6 @@ When called interactively, use the currently active server" (defun eglot--signal-textDocument/didOpen () "Send textDocument/didOpen to server." ;; Flush any potential pending change. - (eglot--track-changes-fetch eglot--track-changes) (setq eglot--recent-changes nil eglot--versioned-identifier 0 eglot--TextDocumentIdentifier-cache nil) commit d3ada49a37e19d25170bf6322fee70d527055958 Author: Stefan Kangas Date: Wed Jan 22 23:42:13 2025 +0100 Minor image_error related cleanups * src/image.c (image_create_x_image_and_pixmap_1): Avoid temporary variable by passing the value directly to image_error. (tiff_handler): Prefer image_error to add_to_log for local consistency. diff --git a/src/image.c b/src/image.c index 1db2df736a5..de6955c2a47 100644 --- a/src/image.c +++ b/src/image.c @@ -4140,10 +4140,8 @@ image_create_x_image_and_pixmap_1 (struct frame *f, int width, int height, int d if (*pixmap == NULL) { DWORD err = GetLastError (); - Lisp_Object errcode; /* All system errors are < 10000, so the following is safe. */ - XSETINT (errcode, err); - image_error ("Unable to create bitmap, error code %d", errcode); + image_error ("Unable to create bitmap, error code %d", make_fixnum (err)); image_destroy_x_image (*pimg); *pimg = NULL; return 0; @@ -9400,8 +9398,8 @@ tiff_handler (const char *log_format, const char *title, log entry, it's OK to truncate it. */ char buf[4000]; int len = vsnprintf (buf, sizeof buf, format, ap); - add_to_log (log_format, build_string (title), - make_string (buf, max (0, min (len, sizeof buf - 1)))); + image_error (log_format, build_string (title), + make_string (buf, max (0, min (len, sizeof buf - 1)))); } # undef MINGW_STATIC commit bf55eb1a8ae95051ac5c8a646b2a106053d7b1b7 Author: Pip Cet Date: Wed Jan 22 19:50:31 2025 +0000 ; * src/pdumper.c (dump_hash_table): Adjust hash; bitfields changed. diff --git a/src/pdumper.c b/src/pdumper.c index 174dfdb47ef..e7e6e2c392c 100644 --- a/src/pdumper.c +++ b/src/pdumper.c @@ -2736,7 +2736,7 @@ dump_hash_table_contents (struct dump_context *ctx, struct Lisp_Hash_Table *h) static dump_off dump_hash_table (struct dump_context *ctx, Lisp_Object object) { -#if CHECK_STRUCTS && !defined HASH_Lisp_Hash_Table_0360833954 +#if CHECK_STRUCTS && !defined HASH_Lisp_Hash_Table_267C58D687 # error "Lisp_Hash_Table changed. See CHECK_STRUCTS comment in config.h." #endif const struct Lisp_Hash_Table *hash_in = XHASH_TABLE (object); commit 34166dcf9cbd961d4f53ce9029e179a21a12c001 Author: Paul Eggert Date: Wed Jan 22 11:03:08 2025 -0800 Port allows-stdout test to overly-suspicious bwrap * test/src/emacs-tests.el (emacs-tests/bwrap/allows-stdout): Skip test if bwrap prohibits even "echo Hi", which it does on my Ubuntu 24.10 platform. This extra check means we no longer need to test separately whether the filter is readable. diff --git a/test/src/emacs-tests.el b/test/src/emacs-tests.el index 2fd6b070990..2196c22ac3c 100644 --- a/test/src/emacs-tests.el +++ b/test/src/emacs-tests.el @@ -183,7 +183,13 @@ (skip-unless bash) (skip-unless bwrap) (skip-unless (file-executable-p emacs)) - (skip-unless (file-readable-p filter)) + (skip-unless + (let ((command + (concat + (shell-quote-argument (file-name-unquote bwrap)) + " --ro-bind / / --seccomp 20 -- echo Hi 20< " + (shell-quote-argument (file-name-unquote filter))))) + (zerop (call-process bash nil nil nil "-c" command)))) (should-not (file-remote-p bwrap)) (should-not (file-remote-p emacs)) (should-not (file-remote-p filter))